✔️messages.properties
item=상품
item.id=상품ID
item.itemName=상품명
item.price=가격
item.quantity=수량
✔️적용
<label for="itemName" th:text="#{item.itemName}"></label>
메시지에서 설정한 메시지 파일을 각 나라별로 별도 관리하면 서비스를 국제화 할 수 있다.
예를 들어 다음과 같이 한국어, 영어 버전 메시지 파일로 별도 관리할 수 있다.
// messages_ko.properties
item=상품
item.id=상품ID
item.itemName=상품명
item.price=가격
item.quantity=수량
// messages_en.properties
item=Item
item.id=Item ID
item.itemName=Item Name
item.price=price
item.quantity=quantity
한국어로 접근한 것인지 영어로 접근한 것인지를 인식하려면 HTTP 헤더에 있는 Accept-Language
헤더 값을 사용하거나 사용자가 직접 언어를 선택하도록 하고 쿠키 등을 사용해서 처리하면 된다.
직접 등록하는 방법도 있으나 스프링 부트를 사용하면 스프링 부트가 MessageSource
를 자동으로 스프링 빈으로 등록한다.
spring.messages.basename=messages,config.i18n.messages
✔️테스트 코드를 통해서 확인
@SpringBootTest
public class MessageSourceTest {
private final MessageSource messageSource;
@Autowired
public MessageSourceTest(MessageSource messageSource) {
this.messageSource = messageSource;
}
@Test
@DisplayName("한국어")
void helloMessage() {
String hello = messageSource.getMessage("hello", null, Locale.KOREA);
Assertions.assertThat(hello).isEqualTo("안녕");
}
@Test
@DisplayName("영어")
void helloMessage2() {
String hello = messageSource.getMessage("hello", null, Locale.ENGLISH);
Assertions.assertThat(hello).isEqualTo("hello");
}
@Test
@DisplayName("못 찾는 경우")
void notFoundMessageCode() {
Assertions.assertThatThrownBy(() -> messageSource.getMessage("no_code", null, null))
.isInstanceOf(NoSuchMessageException.class);
}
@Test
@DisplayName("인자 추가")
void argumentMessage() {
String message = messageSource.getMessage("hello.name", new Object[]{"스프링"}, Locale.KOREA);
Assertions.assertThat(message).isEqualTo("안녕 스프링");
}
}
<div class="py-5 text-center">
<h2 th:text="#{page.items}"></h2>
</div>
크롬의 언어 설정을 변경해서 테스트 할 수 있다.
메시지 기능은 Locale
정보를 알아야 언어를 선택할 수 있다.
스프링은 언어 선택 시 기본으로 HTTP의 Accept-Language
헤더의 값을 사용한다.
✔️LocaleResolver
스프링은 Locale
선택 방식을 변경할 수 있도록 LocaleResolver
라는 인터페이스를 지원하는데 스프링 부트는 기본적으로 Accept-Language
를 활용하는 AcceptHeaderLocaleResolver
를 사용한다.
public class AcceptHeaderLocaleResolver implements LocaleResolver {
private final List<Locale> supportedLocales = new ArrayList<>(4);
@Nullable
private Locale defaultLocale;
/**
* Configure supported locales to check against the requested locales
* determined via {@link HttpServletRequest#getLocales()}. If this is not
* configured then {@link HttpServletRequest#getLocale()} is used instead.
* @param locales the supported locales
* @since 4.3
*/
public void setSupportedLocales(List<Locale> locales) {
this.supportedLocales.clear();
this.supportedLocales.addAll(locales);
}
/**
* Return the configured list of supported locales.
* @since 4.3
*/
public List<Locale> getSupportedLocales() {
return this.supportedLocales;
}
/**
* Configure a fixed default locale to fall back on if the request does not
* have an "Accept-Language" header.
* <p>By default this is not set in which case when there is "Accept-Language"
* header, the default locale for the server is used as defined in
* {@link HttpServletRequest#getLocale()}.
* @param defaultLocale the default locale to use
* @since 4.3
*/
public void setDefaultLocale(@Nullable Locale defaultLocale) {
this.defaultLocale = defaultLocale;
}
/**
* The configured default locale, if any.
* <p>This method may be overridden in subclasses.
* @since 4.3
*/
@Nullable
public Locale getDefaultLocale() {
return this.defaultLocale;
}
@Override
public Locale resolveLocale(HttpServletRequest request) {
Locale defaultLocale = getDefaultLocale();
if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
return defaultLocale;
}
Locale requestLocale = request.getLocale();
List<Locale> supportedLocales = getSupportedLocales();
if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
return requestLocale;
}
Locale supportedLocale = findSupportedLocale(request, supportedLocales);
if (supportedLocale != null) {
return supportedLocale;
}
return (defaultLocale != null ? defaultLocale : requestLocale);
}
@Nullable
private Locale findSupportedLocale(HttpServletRequest request, List<Locale> supportedLocales) {
Enumeration<Locale> requestLocales = request.getLocales();
Locale languageMatch = null;
while (requestLocales.hasMoreElements()) {
Locale locale = requestLocales.nextElement();
if (supportedLocales.contains(locale)) {
if (languageMatch == null || languageMatch.getLanguage().equals(locale.getLanguage())) {
// Full match: language + country, possibly narrowed from earlier language-only match
return locale;
}
}
else if (languageMatch == null) {
// Let's try to find a language-only match as a fallback
for (Locale candidate : supportedLocales) {
if (!StringUtils.hasLength(candidate.getCountry()) &&
candidate.getLanguage().equals(locale.getLanguage())) {
languageMatch = candidate;
break;
}
}
}
}
return languageMatch;
}
@Override
public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
throw new UnsupportedOperationException(
"Cannot change HTTP accept header - use a different locale resolution strategy");
}
}