0. 메시지, 국제화

  • MVC 2 타임리프에서 마지막에 만들었던 상품관리 페이지 사용
    • 소스파일 message-start 사용
    • 타임리프에서 체크 박스, 라디오 버튼, 셀렉트 박스는 뺐다.

메시지

  • 하드 코딩된 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 )을 각 나라별로 별도로 관리하면 서비스를 국제화 할 수 있다.

  • 예를 들어 다음과 같이 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 를 사용하고, 한국어를 사용하는 사람이면 messages_ko.properties 를 사용하게 개발하면 된다.\

  • 한국에서 접근한 것인지 영어에서 접근한 것인지는 인식하는 방법은 HTTP accept-language 해더 값을 사용하거나 사용자가 직접 언어를 선택하도록 하고, 쿠키 등을 사용해서 처리하면 된다.

스프링과 메시지, 국제화

  • 메시지와 국제화 기능을 직접 구현할 수도 있겠지만, 스프링은 기본적인 메시지와 국제화 기능을 모두 제공한다.
  • 그리고 타임리프도 스프링이 제공하는 메시지와 국제화 기능을 편리하게 통합해서 제공한다.

1. 스프링 메시지 소스 설정

  • 스프링에서 @Bean public MessageSource messageSource() 으로 직접 등록해도 되지만 스프링 부트에서 MessageSource 를 자동으로 스프링 빈으로 등록한다.

스프링 부트 메시지 소스 기본 값

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

메시지 파일 만들기

messages.properties :기본 값으로 사용(한글)

  • /resources/messages.properties

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

messages_en.properties : 영어 국제화 사용

  • /resources/messages_en.properties

  • hello=hello
    hello.name=hello {0}
    

2. 스프링 메시지 소스 사용

스프링 메시지 소스 사용

  • 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;
    
  • MessageSource 인터페이스를 보면 코드를 포함한 일부 파라미터로 메시지를 읽어오는 기능을 제공한다.

테스트 코드

  • 내부 주석 참조

  • package hello.itemservice.message;
      
    import org.assertj.core.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.context.MessageSource;
    import org.springframework.context.NoSuchMessageException;
      
    import java.util.Locale;
      
    import static org.assertj.core.api.Assertions.*;
      
    @SpringBootTest
    public class MessageSourceTest {
      
        //springbean 으로 필드 주입
        @Autowired MessageSource ms;
      
        @Test
        //getMessage()로 "hello" 의 value 값 = "안녕"
        void helloMessage(){
            String result = ms.getMessage("hello", null, null);
            assertThat(result).isEqualTo("안녕");
        }
      
        @Test
        //메세지가 없는 경우에는 NoSuchMessageException 을 터트린다.
        void notFoundMessageCode(){
            assertThatThrownBy(() -> ms.getMessage("no_code", null, null))
                    .isInstanceOf(NoSuchMessageException.class);
        }
      
        @Test
        //defaultMessage 를 생성하면 그 메세지가 나온다.
        void notFoundMessageCodeDefaultMessage(){
            String result = ms.getMessage("no_code", null, "기본 메시지", null);
            assertThat(result).isEqualTo("기본 메시지");
        }
      
        @Test
        //properties 에서 "hello.name=안녕 {0}" 이다. 여기서 new Object[]{"Spring"} 으로 리스트로 매개변수 전달이 가능하다. 그러면 {0} 에 "Spring" 이 들어간다.==> "안녕 Spring"
        void argumentMessage(){
            String message = ms.getMessage("hello.name", new Object[]{"Spring"}, null);
            assertThat(message).isEqualTo("안녕 Spring");
        }
      
        @Test
        //기본값이 Locale.KOREA 이므로 당연한 결과
        void defaultLang(){
            assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕");
            assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
      
        }
      
        @Test
        //locale 정보가 Locale.ENGLISH 이므로 messages_en 을 찾아서 사용
        void enLang(){
            assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
      
        }
    }
    

로케일을 찾는 순서

  • Locale 정보가 없는 경우 Locale.getDefault() 을 호출해서 시스템의 기본 로케일을 사용한다.
    • 예) locale = null 인 경우 시스템 기본 locale 이 ko_KR 이므로 messages_ko.properties 조회 시도
    • 조회 실패 시 messages.properties 조회

3. 웹 어플리케이션에 메시지 적용하기

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

타임리프에 메시지 적용

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

예시 (addForm)

  • <!DOCTYPE HTML>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="utf-8">
        <link th:href="@{/css/bootstrap.min.css}"
              href="../css/bootstrap.min.css" rel="stylesheet">
        <style>
            .container {
                max-width: 560px;
            }
        </style>
    </head>
    <body>
      
    <div class="container">
      
        <div class="py-5 text-center">
            <h2 th:text="#{page.addItem}">상품 등록 폼</h2>
        </div>
      
        <form action="item.html" th:action th:object="${item}" method="post">
            <div>
                <label for="itemName" th:text="#{label.item.itemName}">상품명</label>
                <input type="text" id="itemName" th:field="*{itemName}" class="form-control" placeholder="이름을 입력하세요">
            </div>
            <div>
                <label for="price" th:text="#{label.item.price}">가격</label>
                <input type="text" id="price" th:field="*{price}" class="form-control" placeholder="가격을 입력하세요">
            </div>
            <div>
                <label for="quantity" th:text="#{label.item.quantity}">수량</label>
                <input type="text" id="quantity" th:field="*{quantity}" class="form-control" placeholder="수량을 입력하세요">
            </div>
      
            <hr class="my-4">
      
            <div class="row">
                <div class="col">
                    <button class="w-100 btn btn-primary btn-lg" type="submit" th:text="#{button.save}">상품 등록</button>
                </div>
                <div class="col">
                    <button class="w-100 btn btn-secondary btn-lg"
                            onclick="location.href='items.html'"
                            th:onclick="|location.href='@{/message/items}'|"
                            type="button" th:text="#{button.cancel}">취소</button>
                </div>
            </div>
        </form>
      
    </div> <!-- /container -->
    </body>
    </html>
    
    • <h2 th:text="#{page.addItem}">상품 등록 폼</h2> 처럼 properties 에 있는 page.addItem =상품 등록 으로 치환되어 렌더링 된다.
    • 나머지도 다 똑같다..

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

영어 메시지를 추가

  • 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
    
  • 앞에서 템플릿 파일에는 모두 #{…} 를 통해서 메시지를 사용하도록 적용해두었기 때문에 작업이 끝났다.

  • 웹 브라우저의 언어 설정 값을 변경하면 설정값으로 나온다.

  • 웹 브라우저의 언어 설정 값을 변경하면 요청시 Accept-Language 의 값이 변경된다.

  • 스프링은 언어 선택시 기본으로 AcceptLanguage 헤더의 값을 사용한다.

LocaleResolver

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

LocaleResolver 변경

  • 만약 Locale 선택 방식을 변경하려면 LocaleResolver 의 구현체를 변경해서 쿠키나 세션 기반의 Locale 선택 기능을 사용할 수 있다.
  • 예를 들어서 고객이 직접 Locale 을 선택하도록 하는 것이다.
  • 관련해서 LocaleResolver 를 검색하면 수 많은 예제가 나온다.

댓글남기기