만약 여러 화면에 보이는 상품명, 가격, 수량 등, label 에 있는 단어를 변경해달라는 기획자의 요구사항이 들어온다면 어떻게 해야할까. 그렇다면 수정이 필요한 화면들을 찾아 모두 변경해야 한다. 이제껏 했던 예제들 처럼 화면의 수가 적다면 금방 변경이 가능하니 문제가 되지 않을 수 있다. 하지만 변경해야하는 화면의 수가 엄청 많아진다면 굉장히 힘든일일 것이다.
왜냐하면 해당 HTML 파일에 메시지가 하드코딩 되어 있기 때문이다.
따라서 이런 다양한 메시지를 한 곳에서 관리하도록 하는 기능을 메시지 기능이라고 한다.
예를 들어 messages.properties라는 메시지 관리용파일을 만들어 메시지들을 설정하고 데이터들을 키값으로 불러 사용할 수 있다.
item=상품
item.id = 상품 ID
item.itemName=상품명
<label for = "itemName" th:text="#{item.itemName}"></label>
한단계 업그레이드해서 한국어가 편한사람에겐 한국어로 보여주고, 영어가 필요한 사람에게 영어로 보여주는 기능을 생각해보자. properties를 생각하면 쉽다. 한국어 properties와 영어 properties를 각각 따로 만들어 데이터를 설정해주면된다.
messages_en.properites
item=Item
item.id=Item ID
item.itemName=Item Name
messages_ko.properties
item=상품
item.id=상품 ID
item.itemName=상품명
따라서 영어를 사용하는 사람이면 messages_en.properties
를 사용하고,
한국어를 사용하는 사람이면 messages_ko.properties
를 사용하게 개발하면 된다.
한국에서 접근한 것인지 영어에서 접근한 것인지는 인식하는 방법은 HTTP accept-language 해더 값을 사용하거나 사용자가 직접 언어를 선택하도록 하고, 쿠키 등을 사용해서 처리하면 된다.
사용자가 웹 브라우저에 접속하면 기본으로 보여주는 언어가 있고, 위에서 직접 언어를 선택하게 되어있다. 이때 선택한 것을 쿠키에 녹여놓고 사용한다. 그러면 해당 사용자가 웹사이트에 들어갈 때 마다 사용할 언어를 알 수 있다. 이것을 기반으로 properties를 선택하도록 개발하면 된다.
하지만 스프링은 기본적인 메시지와 국제화 기능을 모두 제공한다.
메시지 관리 기능을 사용하려면 스프링이 제공하는 MessageSource 를 스프링 빈으로 등록하면 되는데, 스프링 부트를 사용하면 직접 빈 등록을 사용하지 않아도 된다.
spring.messages.basename=messages
위의 설정이 스프링 부트 메시지 소스 기본 값이다. basename에서는 ,
를 이용해서 classpath에 대한 정보를 넣어줄 수 있다. 예를 들어 spring.messages.basename=messages,config.i18n.messages
와 같이 작성할 수 있다.
MessageSource 를 스프링 빈으로 등록하지 않고, 스프링 부트와 관련된 별도의 설정을 하지 않으면 messages 라는 이름으로 기본 등록된다. 따라서 messages_en.properties ,
messages_ko.properties , messages.properties 파일만 등록하면 자동으로 인식된다.
basename
말고도 다른 설정을 하고 싶다면 spring boot reference에서 spring.messages를 검색해 참고하면 된다. cache-duration, encoding, fallback-to-system-locale, use-code-as-default-message등의 설정이 있다.
그렇다면 실제 messages.properties를 작성해보자.
messages.properties
hello=안녕
hello.name=안녕 {0}
messages_en.properties
hello=hello
hello.name=hello {0}
MessageSource 인터페이스엔 getMessage 메서드가 존재한다. 스프링은 이 메서드를 사용해서 properties에서 데이터를 가져오는 것이다.
MessageSource ms;
MessageSource를 선언하면 설정했던 메시지 소스들이 ms에 담긴다.
예제를 실행하다가 인코딩이 제대로 되지 않아 에러가 나는 상황이 발생한다면 관련 Q&A를 참고하자
테스트 코드로 사용법을 알아보자.
@Test
void helloMessage(){
String result = ms.getMessage("hello", null, null);
assertThat(result).isEqualTo("안녕");
}
MessageSource에서 key값이 hello인 데이터를 가져오면 안녕이 가져와진다.
위의 getMessage 메서드의 두번째와 세번째 파라미터는 각각 args와 locale인데 locale은 null로 둘 경우 messages.properties에서 꺼내온다.
Locale 정보가 없는 경우 Locale.getDefault() 을 호출해서 시스템의 기본 로케일을 사용한다.
예) locale = null 인 경우 시스템 기본 locale 이 ko_KR 이므로 messages_ko.properties 조회 시도 -> 조회 실패 -> messages.properties 조회
@Test
void notFoundMessageCode() {
assertThatThrownBy(() -> ms.getMessage("no_code", null, null))
.isInstanceOf(NoSuchMessageException.class);
}
messages.properties에 없는 key값인 no_code의 데이터를 가져오면 NoSuchMessageException 이 발생한다.
@Test
void notFoundMessageCodeDefaultMessage() {
String result = ms.getMessage("no_code", null, "기본 메시지", null);
assertThat(result).isEqualTo("기본 메시지");
}
위의 getMessage 메서드의 파라미터가 하나 늘어난 것을 확인할 수 있는데, 세번째 파라미터는 defaultMessage이다. 조회한 key값이 messages.properties에 없을 경우 defaultMessage를 데이터로 가져온다.
따라서 위의 result엔 no_code가 messages.properties에 없으므로 기본메시지가 담긴것을 확인할 수 있다.
@Test
void argumentMessage(){
String message = ms.getMessage("hello.name", new Object[]{"Spring"}, null);
assertThat(message).isEqualTo("안녕 Spring");
}
여기선 args에 Spring을 넣어 파라미터를 넘겨준 것을 확인할 수 있다. 따라서 안녕 에 Spring이 합쳐져 안녕 Spring이 되었다.
@Test
void defaultLang(){
assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕");
assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
}
지금 default인 messages.properties의 Locale이 KOREA이기 때문에 null로 해도, Locale.KOREA라 해도 messages.properties의 데이터를 조회한다.
@Test
void enLang(){
assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
}
Locale을 ENGLISH로 적용하여 messages_en.properties의 데이터를 조회한다.
타임리프의 메시지 표현식 #{...} 를 사용하면 스프링의 메시지를 편리하게 조회할 수 있다.
예를 들어서 상품을 label.item=상품
이라고 properties 설정했다면,
<div th:text="#{label.item}"></h2>
라고 작성할 수 있으며 렌더링 되면 <div>상품</h2>
와 같이 된다.
참고로 파라미터는 다음과 같이 사용할 수 있다.
hello.name=안녕 {0}
<p th:text="#{hello.name(${item.itemName})}"></p>
messages.properties
label.item=상품
label.item.id=상품 ID
label.item.itemName=상품명
label.item.price=가격
label.item.quantity=수량
messages_en.properties
label.item=Item
label.item.id=Item ID
label.item.itemName=Item Name
label.item.price=price
label.item.quantity=quantity
위와 같이 MessageResource 설정을 하면 #{label.item}
으로 타임리프가 작성되어있을 때 영어와 한국어 둘 다 편리하게 보여줄 수 있다.
웹 브라우저에서 영어로 적용된것을 확인하려면 크롬 브라우저 설정에 들어가서 언어를 선택, 영어의 우선순위를 제일 위로 올리면 된다.
이렇게 영어를 제일 위로 올리게 되면 요청시 Accept-Language 의 값이 변경된다.
영어를 우선순위로 했을 때 : Accept-Language: en,ko-KR;q=0.9,ko;q=0.8,en-US;q=0.7
한국어를 우선순위로 했을 때 : Accept-Language: ko,en;q=0.9,ko-KR;q=0.8,en-US;q=0.7
위의 테스트에서도 확인했듯이 MessageResource는 getMessage를 통해 key값에 맞는 data를 가져오게 되는데 이때 Locale 정보가 필요하다. 따라서 Accept-Language헤더의 값을 사용하는 것이다.
하지만, 이렇게 크롬 설정에 들어가 언어를 직접 변경하는 것은 번거로운 일이다. 그래서 스프링은 Locale 선택 방식을 변경할 수 있도록 LocaleResolver 라는 인터페이스를 제공하는데, 스프링 부트는 기본으로 Accept-Language 를 활용하는 AcceptHeaderLocaleResolver 를 사용한다. 이를 활용하면 팝업을 띄워 사용자가 언어를 손쉽게 선택하게 할 수도 있다.
만약 Locale 선택 방식을 변경하려면 LocaleResolver 의 구현체를 변경해서 쿠키나 세션 기반의 Locale 선택 기능을 사용할 수 있다.\