티스토리 뷰

메시지

우리가 개발을 하다가 만약 "상품명"이라는 글자를 "상품이름"이라고 고치고 싶다고 해봅시다.

//addForm.html
<label for="itemName">상품명</label>

 

위 addForm.html 파일에 들어가서 상품명을 상품이름으로 바꿔주면 됩니다.

HTML 파일에 하드코딩 되어 있기 때문에, 개발자가 직접 하나하나 바꿔주어야 하는 수고스러움이 있습니다.

 

위같이 한 개의 파일이 아닌, 1000개의 HTML 파일을 고쳐야한다면 아주 절망스러울 겁니다.

 

이런 다양한 메시지를 한 곳에서 관리하도록 하는 기능을 메시지 기능이라고 합니다.

 

 

예를 들어서 messages.properties 라는 메시지 관리용 파일을 만들고

item=상품
item.id=상품 ID
item.itemName=상품명
item.price=가격
item.quantity=수량

 

각 HTML들은 다음과 같이 해당 데이터를 key 값으로 불러서 사용하는 것입니다.

 

[예시 - addForm.html]

<label for="itemName" th:text="#{item.itemName}"></label>

 

 

[예시 - editForm.html]

<label for="itemName" th:text="#{item.itemName}"></label>

 

 

 

국제화

messages.properties 파일을 각 나라별로 관리하면 서비스를 국제화할 수 있습니다.

 

예를 들어, messages.properties 파일을 다음과 같이 2개의 파일로 만들어서 분류합니다.

 

 

[messages_en.properties]

item=Item
item.id=Item Id
item.itemName=Item Name
item.price=price
item.quantity=quantity

 

 

[messages_ko.properties]

item=상품
item.id=상품 ID
item.itemName=상품명
item.price=가격
item.quantity=수량

 

 

영어를 사용하는 사람이면 messages_en.properties 를 사용하고,

한국어를 사용하는 사람이면 message_ko.properties 를 사용하게 개발하면 됩니다.

 

이렇게 한다면 사이트를 국제화 할 수 있습니다.

 

 

하지만, 사용자가 한국에서 접근한 것인지 영어에서 접근한 것인지 어떻게 알 수 있을까요?

한국에서 접근한 것인지 영어에서 접근한 것인지 인식하는 방법HTTP accept-language 헤더 값을 사용하거나

사용자가 직접 언어를 선택하도록 하고, 쿠키 등을 사용해서 처리하면 됩니다.

 

 

메시지와 국제화 기능을 직접 구현할 수도 있겠지만, 스프링은 기본적인 메시지와 국제화 기능을 모두 제공합니다.

그리고 타임리프도 스프링이 제공하는 메시지와 국제화 기능을 편리하게 통합해서 제공합니다.

 


 

스프링에서 메시지 사용

 

스프링은 기본적인 메시지 관리 기능을 제공합니다.

 

메시지 관리 기능을 사용하려면 스프링이 제공하는 MessageSource 를 스프링 빈으로 등록하면 됩니다.

MessageSource 는 인터페이스이므로 구현체인 ResourceBundleMessageSource 를 스프링 빈으로 등록하면 됩니다.

 

 

[예제코드 - 직접 등록]

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasenames("messages", "errors");
    messageSource.setDefaultEncoding("utf-8");
    return messageSource;
}

 

  • setBasenames : 설정 파일의 이름을 지정합니다
    • messages 로 지정하면 messages.properties 파일을 읽어서 사용합니다.
    • 추가로 국제화 기능을 적용하려면 messages_en.properties, messages_ko.properties 와 같이 파일명 마지막에 언어 정보를 주면 됩니다.(000_en, 000_ko 등)
    • 만약 찾을수 있는 국제화 파일이 없으면 messages.properties를 기본으로 사용합니다.
    • 파일의 위치는 /resuorces/messages.properties 에 두면 됩니다.
    • 여러 파일을 한 번에 지정할 수 있습니다. 여기서는 messages, errors 두 파일을 지정했습니다.
  • defaultEncoding : 인코딩 정보를 지정합니다. utf-8을 사용하면 됩니다.

 

스프링 부트 메시지

위 과정이 번거롭기 때문에 스프링 부트는 MessageSource 를 자동으로 스프링 빈으로 등록합니다.

 

 

스프링 부트 메시지 소스 설정

스프링 부트를 사용하면 다음과 같이 메시지 소스를 설정할 수 있습니다.

 

 

[application.properties]

spring.messages.basename=messages, errors

 

 

스프링 부트 메시지 소스 기본값은 다음과 같습니다.

spring.messages.basename=messages

 

 

MessageSource를 스프링 빈으로 등록하지 않고, 스프링 부트와 관련된 별도의 설정을 하지 않으면, message라는 이름으로 기본 등록됩니다. 따라서 messages_en.properties, messages_ko.properties, messages.properties 파일만 등록한다면 자동으로 인식됩니다.

 

 

이후 예제들을 위해 메시지 파일들을 만들어주도록 하겠습니다.

 

messages.properties (기본 값, 한글)

hello=안녕
hello.name=안녕 {0}

 

 

messages_en.properties (영어 국제화)

hello=hello
hello.name=hello {0}

 

 

 

MessageSource 인터페이스

//package org.springframework.context
public interface MessageSource {
    @Nullable
    String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);

    String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;

    String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}

 

스프링이 제공하는 MessageSource 인터페이스를 보면 코드를 포함한 일부 파라미터로 메시지를 읽어오는 기능을 제공합니다. (그냥 그렇구나 하고 넘어가는 것을 권장합니다.)

 

 

스프링이 제공하는 메시지 소스를 웹 애플리케이션에 적용하기 전에 테스트 코드를 통해 어떻게 사용하는지 학습해봅시다.

 

[예제 코드]

@SpringBootTest
public class MessageSourceTest{
    @Autowired
    MessageSource ms;
    
    @Test
    void helloMessage() {
        String result = ms.getMessage("hello", null, null);
        Assertions.assertThat(result).isEqualTo("안녕");
    }
}

 

  • ms.getMessage("hello", null, null)
    • code : hello
    • args : null
    • locale : null

 

가장 단순한 테스트로 메시지 코드로는 "hello"를 입력하고 나머지 값은 null을 입력했습니다.

locale 정보가 없다면 basename에서 설정한 기본 이름 메시지 파일을 조회합니다.

basename으로 messages를 지정했으므로 messages.properties 파일에서 데이터를 조회합니다.

 

 

[예제 코드 - 여러 가지 상황]

//메시지가 없을 경우 예외 발생
@Test
void notFoundMessageCode() {
    Assertions.assertThatThrownBy(() -> ms.getMessage("noCode", null, null))
        .isInstanceOf(NoSuchMessageException.class);
}

//기본 메시지(defaultMessage)사용
@Test
void notFoundMessageCodeDefaultMessage() {
    String result = ms.getMessage("noCode", null, "기본 메시지", null);
    Assertions.assertThat(result).isEqualTo("기본 메시지");
}

//매개변수 사용
@Test
void argumentMessage() {
    String result = ms.getMessage("hello.name", new Object[]{"Baek"}, null);
    Assertions.assertThat(result).isEqualTo("안녕 Baek");
}

//국제화 파일(기본,한글) 사용 -> 두 코드 모두 결과는 동일하다.
@Test
void defaultLang() {
    Assertions.assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕");
    Assertions.assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
}

//국제화 파일 사용
@Test
void defaultLang() {
    Assertions.assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
}

 

  • 메시지가 없는 경우에는 NoSuchMessageException 이 발생합니다.
  • 메시지가 없어도 기본 메시지(defaultMessage)를 사용하면 기본 메시지가 반환됩니다.
  • 메시지 파일의 {0} 부분은 매개변수를 위 코드와 같이 전달해서 치환할 수 있습니다. MessageSource 인터페이스 메서드에서 볼 수 있듯이 매개변수를 Object 배열로 전달해야 하기 때문에 위와 같이 코드를 작성하였습니다.
  • 국제화 파일을 사용할 때 locale 정보가 없거나(기본값 한글), Locale.KOREA로 설정 시 한글 파일을 사용하고, locale 정보가 Locale.ENGLISH 라면 messages_en을 찾아서 사용합니다.

 


 

웹 애플리케이션에 메시지 적용하기

실제 웹 애플리케이션에 메시지를 적용하는 방법을 알아봅시다.

 

아래는 예제에 사용할 메시지 파일입니다.

//messages.properties
label.item=상품
label.item.id=상품 ID
label.item.itemName=상품명
label.item.price=가격
label.item.quantity=수량

page.items=상품 목록
page.item=상품 상세
page.addItem=상품 등록
page.updateItem=상품 수정

button.save=저장
button.cancel=취소

hello.name=안녕 {0}

======= 다른 파일 =======

//messages_en.properties
label.item=Item
label.item.id=Item ID
label.item.itemName=Item Name
label.item.price=price
label.item.quantity=quantity

page.items=Item List
page.item=Item Detail
page.addItem=Item Add
page.updateItem=Item Update

button.save=Save
button.cancel=Cancel

hello.name=hello {0}

 

 

타임리프의 메시지 표현식 #{...}를 사용하면 스프링의 메시지를 편리하게 조회할 수 있습니다.

 

페이지 이름에 적용, 레이블에 적용, 버튼에 적용하든 사용방법은 동일합니다.

<h2 th:text="#{page.addItem}">????</h2>

<label for="itemName" th:text="#{label.item.itemName}">???</label>
<label for="price" th:text="#{label.item.price}">???</label>

<button type="submit" th:text="#{button.save}">저장</button>

<!--파라미터 사용-->
<p th:text="#{hello.name(${item.itemName})">???</p>

 

 

 

 

웹 애플리케이션에 국제화 적용하기

사실 국제화를 적용하는 방법은 따로 없습니다.

이미 준비가 다 끝났기 때문이죠.

 

단지, 클라이언트 요청 시 Accpet-Language의 값에 따라 메시지가 다르게 보입니다.

 

Accept-Language는 클라이언트가 서버에 기대하는 언어 정보를 담아서 요청하는 HTTP 요청 헤더입니다

 

자신이 만든 페이지의 국제화를 테스트해보고 싶다면

크롬 브라우저 -> 설정 -> 언어를 검색하고, 언어 우선순위를 변경하면 확인이 가능합니다.

 

 

참고로 스프링은 Locale 선택 방식을 변경할 수 있도록 LocaleResolver라는 인터페이스를 제공하는데, 스프링 부트는 기본으로 Accept-Language를 활용하는 AcceptHeaderLocaleResolver를 사용합니다.

 

만약 Locale 선택 방식을 변경하려면 LocaleResolver의 구현체를 변경해서 쿠키나 세션 기반의 Locale 선택 기능을 사용할 수 있습니다. 예를 들어서 고객이 직접 Locale(언어)을 선택하도록 하는 것입니다.

 

이 포스팅에서는 중점으로 다루는 내용은 아니기 때문에 관련해서 LocaleResolver를 검색하면 많은 예제가 나오니 필요하신 분들은 참고하시면 좋을 것 같습니다.

 


 

참고(출처)

인프런 김영한님의 스프링 MVC 2편 강의