인프런/스프링 MVC 2편

6) 메시지 ,국제화

backend dev 2023. 2. 2.

메시지 

기획자가 화면에 보이는 문구가 마음에 들지 않는다고,

 

상품명이라는 단어를 모두 상품이름으로 고쳐달라고 하면 어떻게 해야할까?

 

여러 화면에 보이는 상품명, 가격, 수량 등, label 에 있는 단어를 변경하려면 

그게 존재하는 뷰템플릿으로 가서 모두 수정해줘야한다.

화면 수가 적으면 문제가 되지 않지만 화면이 수십개 이상이라면 수십개의 파일을 모두 고쳐야 한다.

 

왜냐하면 해당 HTML 파일에 메시지가 하드코딩 되어 있기 때문이다.

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

 

국제화

메시지에서 한 발 더 나가보자.
메시지에서 설명한 메시지 파일( 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 해더 값을 사용하거나 

사용자가 직접 언어를 선택하도록 하고, 쿠키에 저장해서 처리하면 된다.


메시지와 국제화 기능을 직접 구현할 수도 있겠지만, 

스프링은 기본적인 메시지와 국제화 기능을 모두 제공한다. 

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

 


스프링 메시지 소스 설정

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


메시지 관리 기능을 사용하려면  스프링이 제공하는 MessageSource를 스프링 빈으로 등록하면 되는데,
MessageSource 는 인터페이스이다. 

따라서 구현체인 ResourceBundleMessageSource를 스프링 빈으로 등록하면 된다.

 

하지만 스프링부트를 쓰면 스프링부트가 MessageSource를 알아서 등록해준다.

 

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

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

 

application.properties

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

 

스프링부트 메시지 소스 기본값 ( 기본으로 저렇게 설정되어있으므로 application.*에 추가할 필요없다)

spring.messages.basename=messages

MessageSource를 따로 스프링 빈으로 등록하지 않고,

스프링 부트와 관련된 별도의 설정을 하지 않으면

 

messages라는 이름으로 기본 등록된다. 

따라서 

messages_en.properties,

messages_ko.properties,

messages.properties

파일만 등록하면 자동으로 인식된다.

 

영어권에서 들어왔을때 messages_en.properties를 찾고 못찾았으면 messages.properties를 호출

한국에서 들어왔을때messages_ko.properties를찾고 못찾았으면 messages.properties를 호출

 

메시지 파일 만들기

 

resources 아래  메시지이름.properties로 메시지파일을 만들어주면 된다.  (기본값으로 진행하므로 messages.properties)

 

hello = hello
hello.name = hello {0}

 내용은 이렇게 넣었다. {0}를 이용해서 파라미터도 전달 받을 수 있다.

국제화를 위해 영어버전 메시지 파일도 만들었다.

hello = hello
hello.name = hello {0}


스프링 메시지 소스 사용

스프링부트로 인해 메시지 소스가 알아서 스프링빈으로 등록되었다.

그것을 가져다 쓰기만하면된다.

메시지 소스안에 우리가 등록한 메시지파일을 다 등록해서 가지고있다!

 

 

테스트!

@SpringBootTest
public class MessageSourceTest {

    @Autowired
    MessageSource ms;

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

    }

}

MessageSource를 주입받고,

getMassege()를 통해 코드,매개변수,지역값을 받는다! 

지역정보가 null이면 Locale.getDefault()를 호출해서 시스템의 기본 로케일을 사용한다.

시스템 로케일의 메시지파일이 없다면 기본 메시지파일을 이용한다

 

해당 메시지 코드가 없을경우

@Test
void notFoundMessageCode() { //메시지 코드를 찾지 못했을때
    assertThatThrownBy(() -> ms.getMessage("noCode!!!", null, null))
        .isInstanceOf(NoSuchMessageException.class);

    // 코드로 등록해놓지않은걸 주면 NoSuchMessageException 예외가 발생한다!

}

메시지 코드가 없다면 예외가 발생한다.

기본메시지를 전달해준다.

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

메시지코드가없을때 예외가 발생하므로, 기본 메시지도 해줘서 예외를 방지한다.

(코드,전달인자,기본메시지,지역정보가  전달인자 순서이다.)

 

 

매개변수를 사용

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

 

messages.properties에 매개변수를 받을수 있게 hello.name을 세팅해두었다.

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

매개변수는  배열로 받기때문에 배열로 전달해준다.

 

 

메시지 파일이름을 주의할것 

messages 이다 (복수형!)

메시지 파일이 2개이상일경우 이렇게 번들로 모아준다 (스프링이) 

 

 

 

국제화 테스트

@Test
void defaultLang() {
    assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕"); 
    //지역값설정안하면 getLocale해서 시스템의 locale을 찾고 해당 locale의 메시지파일을 찾고 없으면 기본 메시지파일로
    assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕"); //설정하지않은 지역이라면 디폴트로
}

지역정보가 null이면 Locale.getDefault()를 호출해서 시스템의 기본 로케일을 사용한다.

시스템 로케일의 메시지파일이 없다면 기본 메시지파일을 이용한다

 

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

 

 


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

실제 웹 애플리케이션에 메시지를 적용해보자.

 

먼저 메시지를 추가 등록하자

 

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} 이라고 하면 된다.

 

 

렌더링전

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

타임리프의 메시지표현식을 이용함.

 

렌더링 후

<div>상품</h2>

 

페이지 이름에 적용

<div class="py-5 text-center">
    <h2 th:text="#{page.addItem}">상품등록</h2>
</div>

 

레이블 <label> 에 적용

<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>

버튼에 적용

<button class="w-100 btn btn-primary btn-lg" type="submit" th:text="#{button.save}">저장</button>

 

이렇게 적용하면된다.

 

 

파라미터 적용

<th th:text="#{hello.name(#{label.item})}">ID</th>
<th th:text="#{hello.name('22')}">상품명</th>

전달인자로 메시지도 줄수있고

 문자도 줄수있고

<label for="id" th:text="#{hello.name(${item.id})}">상품 ID</label>

변수도 줄수있다!

 

지금까지 메시지를 효율적으로 관리하는 방법을 알아보았다.

이렇게 메시지를 관리하면 대규모 수정이 필요해도 메시지파일로 가서 수정하면 되니까 

간편하고, 오류가 발생할 일이 없다.

 

이제 여기에 더해서 국제화를 웹 애플리케이션에 어떻게 적용하는지 알아보자.

 

 

 

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

 

영어 메시지 파일을 추가(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

 

사실 이것으로 국제화 작업은 거의 끝났다.

 

앞에서 템플릿 파일에는 모두 #{...} 를 통해서 메시지를 사용하도록 적용해두었기 때문이다.

이렇게 기본메시지파일에도 있고, 영어메시지파일에도 있으니까

들어오는 사람의 Locale만 알면 알아서 해당 메시지파일의 값으로 바뀌어들어간다.

 

웹으로 국제화 확인하기

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


크롬 브라우저 설정 언어를 검색하고, 우선 순위를 변경하면 된다.

영어를 가장 위로 올린다.


우선순위를 영어로 변경하고 테스트해보자.


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

 

Accept-Language의 값을 보면 en이 가장먼저온다.  (우선순위 1위를 영어로 수정했기때문)

 

 

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

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

 

결국 스프링도 Locale 정보를 알아야 언어를 선택할 수 있는데,

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

 

 

LocaleResolver

스프링은 Locale 선택 방식을 변경할 수 있도록 LocaleResolver 라는 인터페이스를 제공하는데,

 

스프링 부트는 기본으로 Accept-Language 를 활용하는 AcceptHeaderLocaleResolver 를 사용한다.

 

LocaleResolver Interface

public interface LocaleResolver {

   /**
    * Resolve the current locale via the given request.
    * Can return a default locale as fallback in any case.
    * @param request the request to resolve the locale for
    * @return the current locale (never {@code null})
    */
   Locale resolveLocale(HttpServletRequest request);

   /**
    * Set the current locale to the given one.
    * @param request the request to be used for locale modification
    * @param response the response to be used for locale modification
    * @param locale the new locale, or {@code null} to clear the locale
    * @throws UnsupportedOperationException if the LocaleResolver
    * implementation does not support dynamic changing of the locale
    */
   void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);

}

 

 

LocaleResolver 변경

만약 Locale 선택 방식을 변경하려면 LocaleResolver 의 구현체를 변경해서

 

쿠키나 세션 기반의 Locale 선택 기능을 사용할 수 있다.

 

예를 들어서 고객이 직접 Locale 을 선택하도록 하는 것이다. (선택한값을 쿠키나 세션에 저장해두고 사용)

 

관련해서 LocaleResolver 를 검색하면 수 많은 예제가 나오니 필요한 분들은 참고하자.

 

 

 

 

 

 

댓글