Spring MVC/02.Spring @MVC

Locale 관리

  • -

이번 포스트에서는 locale과 알아보고 spring boot에서 locale관리 방법에 대해 알아보자.

 

Locale

 

Locale이란?

Locale이란 사용자의 언어, 지역 설정을 정의하는 문자열로 Locale에 따라 날짜, 숫자등의 출력 포멧이 다양하게 표현된다.

public void afterPropertiesSet() throws Exception {
    printByLocale(Locale.KOREA);
    printByLocale(Locale.US);
}
    
private void printByLocale(Locale locale) {
    double money = 123456.789;
    LocalDateTime now = LocalDateTime.now();
    
    NumberFormat nf = NumberFormat.getCurrencyInstance(locale);
    DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd(E)", locale);
       
    String data = "Locale: %s, 국가표현: %s(%s), 통화: %s, 날짜:%s\n"
                  .formatted(locale, locale.getCountry(), locale.getDisplayCountry(), 
                                     nf.format(money), df.format(now) );
    System.out.println(data);
}

위 코드의 출력 결과는 다음과 같다.

Locale: ko_KR, 국가표현: KR(대한민국), 통화: ₩123,457, 날짜:2023-12-13(수)

Locale: en_US, 국가표현: US(미국), 통화: $123,456.79, 날짜:2023-12-13(Wed)

 

Locale.KOREA? Locale.KOREAN?

Locale을 사용하다 보면 은근 헷갈리는 부분이 Locale.KOREA(ko_KR)와 Locale.KOREAN(ko)이다. 쉽게 정리하면 Locale.KOREA는 국가 정보이고 Locale.KOREAN은 언어 정보이다. 국가 정보에는 언어 정보를 포함하므로 좀 더 광범위하게 사용된다.

따라서 Locale.KOREAN  처럼 언어 설정을 나타내는 객체는 언어에 따라 앱의 동작을 변경할 때 사용되는데 주로 i18N에 의해 국제화를 지원하는 경우가 대표적이다.

반면 Locale.KOREA는 국가 정보를 나타내기 때문에 지역의 설정에 따라 숫자, 날짜의 포멧을 변경할 때 사용된다. 따라서 위의 printByLocale을 Locale.KOREAN으로 호출하면 국가 정보가 없기 때문에 통화를 재대로 표시하지 못한다.

Locale korea = Locale.KOREA;
//Locale: ko_KR, 국가표현: KR(대한민국), 통화: ₩123,457, 날짜:2023-12-13(수) 

Locale korean = Locale.KOREAN;
//Locale: ko, 국가표현: (), 통화: ¤123,456.79, 날짜:2023-12-13(수)

 

웹 요청과 locale

이 포스트를 쓰기 시작한 이유이기도 한데 Thymeleaf는 #numbers.formatCurrency을 이용해서 숫자를 통화 기호와 함께 출력할 수 있다. 

그런데 출려 결과는 예쁘게 원화 표시(₩)와 함께 출력되지 않고 이상한 문자가 출력되었다. 물론 이 문자는 Locale.KOREAN을 이용했을 때이다.

<!-- 사용된 태그 -->
<li th:text="${#numbers.formatCurrency(num)}"></li>
<!-- 출력 결과 -->
¤300,000.14

처음에는 Chrome에서 테스트 했었는데 이상해서 Whale에서 다시 테스트를 진행했다. 동일한 코드인데 이번에는 출력 결과가 정상이다. 이런!! 브라우저마다 요청하는 형태가 다르구나.

크롬의 요청 헤더 Whale의 요청 헤더

즉 chrome의 경우 ko가 먼저 전달되고 whale의 경우는 ko-KR이 먼저 전달되고 있는데 먼저 전달 되는 Locale을 사용하는 것으로 보인다.(@Controller의 메서드에서 확인해본 결과 그렇다.)

따라서 Locale을 원활히 사용하기 위해서는 브라우저에 의존하지 않고 서버단에서 잘 결정해줄 필요가 있겠다.

 

Spring과 Locale 관리

 

LocaleResolver를 통한 초기 Locale 설정

Spring에서 Locale을 관리하기 위해서는 LocaleResolver라는 것을 사용하는데 다양한 구현체가 제공된다.

종류 설명
AcceptHeaderLocaleResolver 기본적으로 사용되는 LocaleResolver로 클라이언트의 HTTP 요청 헤더에서 Accept-Languate를 분석해서 사용한다. 브라우저의 요청 형태에 따라 다른 값이 나온 원인이다.
CookieLocaleResolver 쿠키에 Locale 정보를 저장하고 사용하는 방식이다. 쿠키를 사용하므로 브라우저를 닫아도 Locale은 유지된다.
SessionLocaleResolver Session에 Locale 정보를 저장하고 사용하는 방식이다. Session을 사용하므로 브라우저를 닫으면 Locale은 초기화 된다.

여기서는 SessionLocaleResolver를 사용해보자.

@Bean
public LocaleResolver localeResolver() {
  SessionLocaleResolver resolver= new SessionLocaleResolver();
  resolver.setDefaultLocale(Locale.KOREA);
  return resolver;
}

이제 기본 Locale이 Locale.KOREA 이므로 통화 기호가 잘 출력되는 것을 알 수 있다.

 

Locale 변경 감지

가끔 사이트에서 i18N 즉 국제화 서비스를 지원해야 하는 경우가 있다.

i18N(internationalization)

이런 경우는 LocaleChangeInterceptor라는 객체를 사용한다. LocaleChangeInterceptor를 사용할 때는 특정 파라미터 이름으로 사용할 locale의 문자열을 전달해주면 된다. 다음은 lang이라는 파라미터를 사용하기로 설정한 예이다.

@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
  LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
  lci.setParamName("lang");
  return lci;
}

당연히 Interceptor가 동작하려면 적절한 URL에 등록해주어야 한다.

@Override
public void addInterceptors(InterceptorRegistry registry) {
  registry.addInterceptor(localeChangeInterceptor()).addPathPatterns("/**");
}

이제 요청 방식에 따라 출력되는 통화 기호가 변경되는 것을 확인할 수 있다.

http://localhost:8080/ognl?addr=seoul&lang=en_US  : $300,000.14
http://localhost:8080/ognl?addr=seoul&lang=ja_JP  : ¥300,000

 

bundle 파일 지정

i18N을 사용하는 큰 이유는 다양한 언어로 서비스하기 위해서이다. 위와 같이 LocaleResolver와 LocaleChangeInterceptor를 작성해두었다면 이제 적절한 bundle 파일만 있으면 된다. bundle은 통상 properties 파일로 작성하는데 basename_locale.properties 형태로 작성한다.

locale은 상세한 표현이 우선 순위를 갖는다. 따라서 ko_KR > ko > 생략 의 순으로 bundle에서 property를 검색한다.

title=안녕!
test=ko_KR
test=en_US
title=Hello
test=default
message_ko_KR.properties message_en_US.properties message.properties

위와 같이 property가 설정된 상태에서 ko_KR로 요청하면 "안녕"과 "ko_KR"이 출력된다. en_US로 요청하는 경우 "Hello"와 "en_US"가 출력된다. 마지막으로 ja_JP 처럼 전혀 매핑되는 속성이 없을 경우는 system의 default locale이 적용되어 ko_KR과 동일한 결과가 나온다.

 

bundle 작성 및 지정

bundle 파일들은 일반적으로 src/main/resources 아래에 작성한다.

그리고 이 번들 파일을 지정하기 위해서는 spring.messages.basename 속성을 이용한다.

spring:
  messages:
    basename: lang/message

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.