Message & Internationalization

Message

화면에서 표시되는 용어에 대해 일괄적으로 관리하는 것이 ‘Message’ 이다.

가령, 상품 관리 애플리케이션이 있다고 했을 때, 상품명, 가격, 수량, 등의 용어를 사용하게 되는데, 갑자기 요구사항이 변경되어 이러한 용어가 수정 되었다고 하면 여러 html에서 해당 부분을 수정해줘야한다. 하지만, message를 이용하게 되면 message.properties 값만 수정하면 자동으로 수정이 된다.

Internationalization

국제화를 이용한 메세지를 각 나라별로 서비스를 제공할 수 있다.

message_en.properties, message_ko.properties 를 설정하게 되면, 영어권 국가에서는 message_en.properties을 사용하게 되고, 한국에서는 message_ko.properties을 이용하게 되는 ㄱ서이다.

이를 가능하게 하는 것이, http request의 accept-language 헤더 값이다.

해당 헤더값에 설정되어 있는 언어 정보에 따라 서로 다른 언어로 제공할 수 있게 되는 것이다.

Spring을 이용하게 되면 위의 2기능에 대해 편리하게 제공한다.

Message

Message 기능을 사용하기 위해서는 MessageSource를 Spring Bean으로 등록해야한다. MessageSource Interface에 대한 구현체인 ResourceBoundMessageSource를 등록해줘야한다.

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

basename을 messages로 지정하면 messages.properties 파일을 읽어서 message 기능을 수행한다. 그리고, 다른 언어를 사용하고자 하면, messages_en과 같이 끝에 언어 정보를 붙여서 만든다.

이와 같이 Spring Bean을 수동으로 등록해서 해도 되지만, Sprint Boot을 활용하면 MessageSource를 자동으로 SpringBean으로 등록해준다.

application.properties

spring.messages.basename=messages,config.i18n.messages

위와 같이 기본값으로 설정되어 있는데, 필요에 따라서 변경하면 된다.

따라서, 우리는 messages.properties만 만들게 되면 message 기능을 바로 활용할 수 있다.

messages.properties

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

messages_en.properties

hello=hello
hello.name=hello {0}

{}을 이용해서 message에 인자를 전달해서 출력할 수도 있다.

Message Source Interface

public interface MessageSource {

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

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

Test

  1. Basic
@SpringBootTest
public class MessageSourceTest {
  @Autowired
  MessageSource ms;

  @Test
  void helloMessage() {
  String result = ms.getMessage("hello", null, null);
  assertThat(result).isEqualTo("안녕");
  }
}

위와 getMessage를 호출하게 되면, code=”hello”, args=null,locale=null로 설정되어 message.properties의 hello code인 hello가 출력되게 된다.

  1. Not matching Code
void notFoundMessageCode() {
  assertThatThrownBy(() -> ms.getMessage("no_code", null, null)).isInstanceOf(NoSuchMessageException.class);
}

위 처럼 message.properties에 없는 code를 호출하게 되면 NoSuchMessageException 에러가 발생한다.

  1. Default Message
    @Test
    void notFoundMessageCodeDefaultMessage() {
      String result = ms.getMessage("no_code", null, "기본 메시지", null);
      assertThat(result).isEqualTo("기본 메시지");
    }
    

    메세지 code가 일치하는 것이 없어도 기본메세지가 설정되어 있으면 기본메세지가 출력된다.

  2. Message Arguments
@Test
void argumentMessage() {
  String result = ms.getMessage("hello.name", new Object[] {"Spring"},null);
  assertThat(result).isEqualTo("안녕 Spring");
}

위와 같이 인자를 받을 수 있는 code로 호출하게 되면 message + argument가 합쳐진 형태로 출력된다.

  1. Internationalization
@Test
void enLang() {
  assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
}

다음과 같이 Locale 정보를 ENGLISH로 주게 되면, message_en.properties의 메세지가 출력된다.

default 값은 한국어로 message_ko.properties = message.properties 는 서로 같은 파일을 참조하게 된다.

웹어플리케이션 적용

message.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=취소

thymeleaf에서 message에서 값을 받아오고자 하면 #{} 표현식을 활용한다. 표현식 안에 message code 값을 넣어주면 해당 메세지 코드 값이 반환된다.

예시

<div th:text="#{label.item}"></h2>

위와 같이 설정하게 되면

아래와 같은 결과로 랜더링된다.

<div>상품</h2>

Applying Internationalization

국제화를 적용하기 위해 필요한 것은 message_en.properties을 작성하는 것뿐이다.

message_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

그리고 국제화가 적용됬는지 확인하기 위해, chrome의 언어 설정을 영어로 바꿔보면 해당 홈페이지에 접속할 때, 한글로 표기된 부분이 영어로 표기되는 것을 확인할 수 있다.

Spring에서는 기본적으로 Accept-Header의 값을 토대로 Locale을 설정하게 되는데, 이때, AcceptHeaderLocaleResolver가 동작한다. 만약, Locale 선택 방식을 달리하고자 하면 LocaleResolver의 구현체를 다른것으로 설정해서 쿠키나 세션 기반의 Locale 선택 기능을 활용할 수 도 있다.

References

link: inflearn

link:springmvc

댓글남기기