ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring] 메시지, 국제화
    Back-end/Spring 2022. 3. 28. 16:29

    안녕하세요 이번 포스팅은 메시지와 국제화에 대해서 알아보겠습니다.

     

    메시지

    어떤 기획자가 HTML 파일에 있는 모든 상품명이라는 단어를 상품으로 변경해달라고 요청했다고 가정을 해봅시다.

    그러면 여러 화면에 보이는 하드 코딩되어있는 상품명이라는 단어를 화면들을 다 찾아가면서 모두 변경해야 합니다. 화면수가 적다면 문제가 되지 않지만 수십 개 이상이라면 수십 개의 파일을 모두 고쳐야 한다는 번거로움이 생깁니다.

     

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

     

     

    국제화

    메시지에서 한발 더 나아가서 메시지 파일(messages.properties)을 각 나라별로 관리하면 서비스를 국제화 할 수 있습니다.

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

     

    mssages_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를, 한국어를 사용하는 사람이면 messages_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;
    }

    basenames : 설정 파일의 이름을 지정합니다.

    - messages로 지정하면 messages.properties 파일을 읽어서 사용합니다.

    - 추가로 국제화 기능을 적용하려면 messages_en.properties, messages_ko.properties와 같이 파일명 마지막에 언어 정보를 주면 됩니다. 만약 찾을 수 있는 국제화 파일이 없으면 messages.properties(언어 정보가 없는 파일 명)를 기본으로 사용합니다.

    - 파일의 위치는 /resources/messages.properties에 두면 됩니다.

    - 여러 파일을 한번에 지정할 수 있습니다. 여기서는 messages, errors 둘을 지정했습니다.

     

    defaultEncoding ; 인코딩 정보를 지정하는데 utf-8을 사용하면 됩니다.

     

    스프링 부트에서 메시지와 국제화

     

    스프링 부트를 사용하면 스프링 부트가 MessageSource를 자동으로 스프링 빈으로 등록합니다.

    다음과 같이 메시지 소스를 설정할 수 있습니다.

     

    application.properties

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

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

    즉 위의 설정을 생략할 수 있습니다.

     

    메시지 파일 만들기

    메시지 파일을 만들어보겠습니다. 국제화 테스트를 위해서 messages_en 파일도 추가해보겠습니다.

     

    messages.properties

     

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

     

    messages_en.properties

    hello=hello
    hello.name=hello {0}

     

    MessagesSource 인터페이스

    MessageSource 인터페이스를 보면 코드를 포함한 일부 파라미터로 메시지를 읽어오는 기능을 제공합니다.

    스프링이 제공하는 메시지 소스를 어떻게 사용하는지 테스트 코드를 통해서 학습해보겠습니다.

     

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

    ms.getMessage("hello", null, null);

    code: hello

    args : null

    locale : null

     

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

    locale정보가 없으면 basename에서 설정한 기본 이름 메시지 파일을 조회하므로 messages.properties 파일에서 데이터를 조회합니다.

     

    MessageSourceTest 추가 - 메시지가 없는 경우, 기본 메시지

     

    @Test
    void notFoundMessageCode() {
     	assertThatThrownBy(() -> ms.getMessage("no_code", null, null))
     				.isInstanceOf(NoSuchMessageException.class);
    }
    
    @Test
    void notFoundMessageCodeDefaultMessage() {
         String result = ms.getMessage("no_code", null, "기본 메시지", null);
         
         assertThat(result).isEqualTo("기본 메시지");
    }

    첫 번째 테스트처럼 메시지가 없는 경우에는 NoSuchMesageException이 발생합니다.

    두 번째 테스트처럼 메시지가 없어도 기본 메시지(defaultMessage)를 사용하면 기본 메시지가 반환됩니다.

     

    MessageSourceTest추가 - 매개변수 사용

     

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

     

    다음 메시지의 {0} 부분은 매개변수를 전달해서 치환할 수 있습니다.

    hello.name = 안녕 {0} -> Spring 단어를 매개변수로 전달 -> 안녕 Spring

     

    국제화 파일 선택

    locale 정보를 기반으로 국제화 파일을 선택합니다.

    -Locale이 en_US인 경우 messages_en_US -> messages_en -> messages 순서로 찾습니다.

    -Locale에 맞추어 구체적인 것이 있으면 구체적인 것을 찾고 없으면 디폴트를 찾는다고 생각하면 됩니다.

     

    MessageSourceTest 추가 - 국제화 파일 선택 1

    @Test
    void defaultLang() {
         assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕");
         assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
    }

    -ms.getMessage("hello", null, null) : locale 정보가 없으므로 messages를 사용했습니다.

    -ms.getMessage("hello", null, Locale.KOREA) : locale 정보가 있지만, message_ko 가 없으므로 messages 를 사용했습니다.

     

    MessageSourceTest 추가 - 국제화 파일 선택 2

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

    - ms.getMessage("hello", null, Locale.ENGLISH) : 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=취소

     

     

    타임리프 메시지 적용

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

    예를 들어서 방금 등록한 상품이라는 이름을 조회하려면 #{label.item}이라고 하면 됩니다.

     

    타임리프에서 스프링 메시지 기능을 사용하는 방법은 굉장히 간단합니다.

    properties파일에 있는 key value형태의 데이터를 읽어서 타임리프에서 지원하는 일정한 형태로 보여주면 됩니다.

     

    국제화 적용하기

    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

    사실 이 파일 하나만 추가해주면 모든 끝난 것입니다.

    템플릿 파일에서 th:text="#{...}"를 통해서 메시지를 사용하도록 적용해두면 클라이언트 request의 accept-language에 따라 자동으로 변환되어서 제공합니다.

     

    웹으로 확인하기

    웹 브라우저의 언어 설정 값을 변경하면서 국제화 적용을 확인해보겠습니다.

    크롬 브라우저 설정 언어를 검색하고, 우선순위를 변경하면 됩니다. 우선순위를 영어로 변경하고 테스트해보면 웹 브라우저의 언어 설정 값을 변경하고 요청 시 Accept-Language의 값이 변경됩니다.

     

    스프링의 국제화 메시지 선택

    앞서 MessageSource 테스트에서 보았듯이 메시지 기능은 Locale 정보를 알아야 언어를 선택할 수 있습니다.

    결국 스프링도 Locale정보를 알아야 언어를 선택할 수 있는데, 스프링은 언어 선택 시 기본으로 Accept-Language 헤더의 값을 사용합니다.

     

    LocaleResolver

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

     

     

    LocaleResolver 변경

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

Designed by Tistory.