스프링 메시지, 국제화(Message, Internationalization)
-다양한 메시지를 한 곳에서 관리
- 메시지
- 국제화
- 메시지 소스 설정
- 메시지 소스 사용하기
메시지
화면에서 공통으로 사용되는 다양한 메시지를 한 곳에서 관리하는 기능을 메시지 기능 이라고 합니다.
만약, HTML에 하드코딩으로 문구가 적혀있는 경우 특정 문구가 변경되었을 때 해당 문구가 사용되는 모든 파일을 찾아 수정해주어야 합니다.
스프링에서는 다양한 메시지를 한 곳에서 관리하고 화면(View)에서 정의된 메시지를 사용할 수 있는 메시지 기능을 제공합니다.
- messages.properties 라는 메시지 관리용 파일을 만들고, HTML안에서 Key 값으로 해당 데이터를 불러올 수 있다.
- 쉽게, 여러 페이지에서 사용하는 다양한 메시지를 한 곳에서 관리하는 기능이다.
<!-- 기존 코드 -->
<label for="itemId">상품 ID</label>
<input type="text" th:field="${item.id}" readonly />
<!-- 메시지 기능 사용 코드 -->
<label for="itemId" th:text="#{item.id}"></label>
<input type="text" th:field="${item.id}" readonly />
국제화
국제화는 하드웨어 또는 소프트웨어 등의 제품을 언어 및 문화권 등이 다른 여러 환경에 대해 사용할 수 있도록 지원하는 서비스를 의미합니다. 국제화는 제품 자체가 여러 환경을 지원할 수 있도록 제품을 설계하는 것을 의미하고, 현지화는 제품을 각 환경에 대해 지원하는 것을 의미합니다.
스프링에서는 MessageSource 인터페이스를 통해 국제화(i18N)를 지원합니다. 즉, 메시지 설정 파일(Properties)을 작성하여 국가마다 로컬라이징 함으로써 편리하게 각 지역/국가에 맞는 메시지를 제공할 수 있습니다.
- 국제화 기능을 적용하기 위해서는 messages_en.properties, messages_ko.properties와 같이 파일명 마지막에 언어 정보를 추가하면 됩니다.
- 만약, 찾을 수 있는 국제화 파일이 없으면 messages.properties를 기본으로 사용하게 됩니다.
- HTTP 헤더(accept-language) 값을 사용하거나 처음에 사용자가 직접 언어를 선택하게 하여 쿠키 등을 사용해서 언어를 선택하는 방법이 있습니다.
# message_en.properties 파일
item=Item
item.id=Item ID
item.itemName=Item Name
item.price=price
item.quantity=quantity
# message_ko.properties 파일
item=상품
item.id=상품 ID
item.itemName=상품명
item.price=가격
item.quantity=수량
메시지 소스 설정
스프링이 제공하는 메시지 관리 기능을 사용하려면 MessageSource를 스프링 빈으로 등록하면 됩니다.
MessageSource는 인터페이스로 여러가지 구현체가 존재하며, 기본적으로 ResourceBundleMessageSource가 사용됩니다.
- 스프링 메시지 소스 설정
Basenames: 'messages'로 지정하면 'messages.properties' 파일을 읽어서 메시지 기능을 사용하게 됩니다.
추가적으로 국제화 기능은 파일명 마지막에 언어 정보를 추가하면 됩니다. (ex: messages-en.proerties)
만약, 해당하는 국제화 파일이 없으면 기본으로 messages.properties를 사용하게 됩니다.
파일의 위치는 '/resources/'
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("messages", "error");
messageSource.setDefaultEncoding("utf-8");
return messageSource;
}
- 스프링 부트 메시지 소스 설정
스프링 부트는 기본적으로 MessageSource를 자동으로 스프링 빈으로 등록합니다.
때문에, 따로 설정하지 않는 경우 'messages' 라는 이름으로 기본 등록되어 '/resources' 폴더 하위에 message.properties를 읽어 메시지 기능을 사용할 수 있습니다.
만약, 메시지 소스 파일의 경로를 변경하고 싶은 경우 application 설정파일(properties, yml)에 옵션만 설정해주면 됩니다.
아래 예시는 '/resources/config/i18n/messages' 폴더 하위에 위치하는 messages 파일로 메시지, 국제화 기능을 정의할 수 있습니다.
spring.messages.basename=messages, config.i18n.messages
- 스프링 Legacy 메시지 소스 설정 (XML)
<!--
web.xml: 스프링 설정 파일인 message-context.xml 파일을 로딩하도록 수정한다. (context-param: 초기 웹 로딩시 읽을 설정 파일 지정)
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/root-context.xml
/WEB-INF/spring/message-context.xml
</param-value>
</context-param>
<!--
message-context.xml: 메시지 소스 기능을 설정합니다. (MessageSource 인터페이스 구현체를 빈으로 등록)
-->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<!-- 메세지 파일의 위치를 지정합니다. message_언어.properties 파일을 찾습니다. -->
<value>/WEB-INF/messages/message</value>
</list>
</property>
<!-- 파일의 기본 인코딩을 지정합니다. -->
<property name="defaultEncoding" value="UTF-8" />
<!-- properties 파일이 변경되었는지 확인하는 주기를 지정합니다. 60초 간격으로 지정했습니다. -->
<property name="cacheSeconds" value="60" />
</bean>
<!-- 언어 정보를 세션에 저장하여 사용합니다. -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />
메시지 소스 사용하기
1. 메시지 파일 만들기
- messages.properties: 기본 메시지 파일
hello=안녕
hello.name=안녕 {0}
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=취소
- messages_en.properties: 영어권 국제화 파일
hello=hello
hello.name=hello {0}
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
2. MessageSource 사용하기
MessageSource 인터페이스는 메시지를 읽어오는 2가지 메소드를 제공합니다.
- code: 메시지 파일에 정의된 key 값
- args: 해당 key 메시지에 넘겨줄 인수 (동적 메시지로 사용 가능)
- defaultMessage: key가 존재하지 않는 경우 대체 메시지
- locale: Locale 정보 (ex: Locale.KOREA, Locale.ENGLISH 등)
public interface MessageSource {
@Nullable
String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}
- MessageSource 테스트
MessageSource가 스프링 빈으로 등록되야 사용 가능하기 때문에 SpringBootTest 어노테이션을 사용합니다.
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 {
@Autowired
MessageSource ms;
@Test
void helloMessage() {
// getMessage(code, args, locale) : Local 정보를 넘기지 않으면 messages.properties를 기본적으로 사용한다.
String result = ms.getMessage("hello", null, null);
assertThat(result).isEqualTo("안녕");
}
@Test
void notFoundMessageCode() {
// 메시지 코드가 존재하지 않으면 NoSuchMessageException이 발생한다.
assertThatThrownBy(() -> ms.getMessage("no_code", null, null))
.isInstanceOf(NoSuchMessageException.class);
}
@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");
}
@Test
void defaultLang() {
// Locale을 지정하지 않으면 messages.properties를 사용합니다.
// 또한, 지정한 Locale를 찾을 수 없으면 messages.properties를 사용합니다.
assertThat(ms.getMessage("hello", null, null)).isEqualTo("안녕");
assertThat(ms.getMessage("hello", null, Locale.KOREA)).isEqualTo("안녕");
}
@Test
void enLang() {
// 지정한 Local을 찾으면 해당 국제화 메시지를 사용합니다.
assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
}
}
3. 웹 애플리케이션에 메시지 적용하기
- 타임리프 템플릿 엔진을 이용한 방법
타임리프에서 메시지 기능을 사용하기 위해서는 메시지 표현식(#{..})을 사용하여 편리하게 조회할 수 있습니다.
#{메시지 코드}: 메시지 조회
#{메시지 코드(파라미터, ..)}: 메시지 조회 (인수 넘기기)
<!-- 렌더링 전 -->
<h2 th:text="#{label.item}"></h2>
<label for="itemName" th:text"#{label.item.itemName}">상품명</label>
<input type="text" th:field="${item.itemName}" class="form-control" />
<p th:text="#{hello.name(${item.itemName})}"></p>
<!-- 렌더링 후 -->
<h2>상품</h2>
<label for="itemName">상품명</label>
<input type="text" id="itemName" name="itemName" value="" class="form-control" />
<p>안녕 파라미터값</p>
- 스프링 커스텀 태그를 이용한 방법 (JSP)
JSP에서 정의한 메시지를 가져오기 위해서는 taglib를 적용하여 Spring의 커스텀 태그를 사용합니다.
메시지 코드에 파라미터를 넘기는 경우에는 EL 표기법으로 value 값을 argument 인자로 넣어주면 됩니다.
<spring:message code="item.itemName" text="대체 상품명" />
<spring:message code="hello.name" arguments="${item.itemName}" />
4. 웹 애플리케이션에 국제화 적용하기
템플릿 파일에는 모두 메시지를 사용하도록 적용해두었기 때문에, messages_[언어].properties 파일만 따로 작성되어있다면 해당 언어권에 국제화가 자동으로 적용됩니다.
- 국제화 테스트 하기
크롬에서 언어 설정을 영어로 변경하면 국제화 테스트를 진행할 수 있습니다.
웹브라우저(Chrome)의 언어 설정을 변경하면 요청 헤더에 Accept-Language의 값을 'en'을 우선순위로 설정하게 됩니다.
Accept-Language 헤더는 클라이언트가 서버에 기대하는 언어 정보를 담아서 요청하는 HTTP 요청 헤더입니다.
- 언어 선택 방식 변경 (LocaleResolver)
스프링은 기본적으로 Accept-Language 헤더 값을 통해 언어 정보를 설정합니다.
스프링에서는 Locale 선택 방식을 변경할 수 있는 LocaleResolver 인터페이스를 제공합니다.
스프링은 기본적으로 Accept-Language를 활용하는 AcceptHeaderLocaleResolver 구현체를 사용합니다.
만약, Locale 선택 방식을 변경하려면 LocaleResolver의 구현체를 변경해서 쿠키나 세션 기반의 Locale 선택 기능을 사용할 수 있습니다.
AcceptHeaderLocaleResolver
- 웹 브라우저가 전송한 Accept-Language 헤더로부터 Locale을 선택한다.
- setLocale() 메소드를 지원하지 않는다.
CookieLocaleResolver
- 쿠키를 이용해서 Locale을 선택한다.
- setLocale() 메소드는 쿠키에 Locale 정보를 저장한다.
SessionLocaleResolver
- 세션으로부터 Locale을 선택한다.
- setLocale() 메소드는 세션에 Locale 정보를 저장한다.
FixedLocaleResolver
- 웹 요청에 상관없이 특정한 Locale로 설정한다.
- setLocale() 메소드를 지원하지 않는다.
'BACKEND > 스프링 Spring' 카테고리의 다른 글
스프링의 콘셉트(IoC, DI, AOP, PSA) 쉽게 이해하기 (0) | 2024.01.19 |
---|---|
스프링으로 게시판 페이징처리 회원가입 댓글 로그인 파일업로드 암호화 회원탈퇴 키워드 검색 (0) | 2024.01.18 |
Spring MVC jQuery $ 사용 예제 (0) | 2024.01.14 |
Spring MVC를 사용하여 Ajax 요청 (0) | 2024.01.14 |
[Spring] logging 에 대해 알아보자 (0) | 2024.01.10 |