스프링 MVC
4. 유저 로케일 해석
스프링 MVC 어플리케이션에서 유저 로케일은 LocaleResolver 인터페이스를 구현한 로케일 리졸버가 식별한다. 로케일을 해석하는 기준에 따라 여러 LocaleResolver 구현체가 스프링 MVC 에 존재한다. 이러한 구현체를 사용해도 되고, 직접 이 인터페이스를 구현해 커스텀 로케일 리졸버를 만들어도 된다.
로케일 리졸버는 어플리케이션 컨텍스트에 LocaleResolver 형 빈으로 등록한다. 디스패처 서블릿이 이를 자동 감지하려면 로케일 리졸버 빈의 이름을 localeResolver 이라 명명한다. 참고로 로케일 리졸버는 디스패처 서블릿 하나 당 하나만 등록이 가능하다.
HTTP 요청 헤더에 따른 로케일 해석
AcceptHeaderLocaleResolver 는 스프링의 기본 로케일 리졸버로 accept-language 요청 헤더값에 따라 로케일을 해석한다. 유저 웹 브라우저는 자신을 실행한 운영체제의 로케일 설정으로 이 헤더를 설정한다.
유저 운영체제의 로케일 설정을 변경하는 것은 불가능하기 때문에, 로케일 리졸버로 유저 로케일을 변경하는 것 또한 불가능하다.
세션 속성에 따른 로케일 해석
SessionLocaleResolver 는 유저 세션에 사전 정의된 속성에 따라 로케일을 해석한다. 세션 속성이 없을 경우 accept-language 헤더로 기본 로케일을 결정한다.
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(new Locale("en"));
return localeResolver;
}
로케일 관련 세션 속성이 없는 경우 setDefaultLocale() 메소드로 대체 프로퍼티 defaultLocale 을 설정할 수 있다. 이 LocaleResolver 는 로케일이 저장된 세션 속성을 변경함으로써 유저 로케일을 변경한다.
쿠키에 따른 로케일 해석
CookieLocaleResolver 는 유저 브라우저의 쿠키값에 따라 로케일을 해석한다. 해당 쿠키가 없을 경우 SessionLocaleResolver 와 마찬가지로 accept-language 헤더로 기본 로케일을 결정한다. 쿠키 설정은 cookieName, cookeMaxAge 프로퍼티로 커스터마이징할 수 있다. cookieMaxAge 는 쿠키를 유지할 시간(초) 이며 -1 은 브라우저 종료와 동시에 쿠키를 삭제하라는 뜻이다.
@Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setCookieName("language");
localeResolver.setCookieMaxAge(3600);
localeResolver.setDefaultLocale(new Locale("en"));
return localeResolver;
}
관련 쿠키가 존재하지 않으면 setDefaultLocale() 메소드로 대체 프로퍼티 defaultLocale 을 설정할 수 있다. 이 LocaleResolver 는 로케일이 저장된 쿠키값을 변경함으로써 유저 로케일을 변경한다.
유저 로케일 변경
LocaleResolver.setLocale() 메소드를 호출해 유저 로케일을 명시적으로 변경할 수도 있지만, LocaleChangeInterceptor 를 핸들러 매핑에 적용할 수도 있다. 이 인터셉터의 특기는, 현재 HTTP 요청에 특정 매개변수가 존재하는 지 감지하는 일이다. 특정 매개변수의 이름은 이 인터셉터의 paramName 프로퍼티값으로 지정하며 그 값으로 유저 로케일을 변경할 수 있다.
@Configuration
public class I18NConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
@Bean
public LocaleChangeInterceptor localgeChangeInterceptor() {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("langauge");
return localeChangeInterceptor;
}
}
※ 위의 구성 클래스를 적용한 뒤, HTTP 요청 시 Parameter 에 language 속성값을 지정했음에도 제대로 로케일 변경이 적용되지 않았다. 그 이유에 대해 찾아보려 했는데, 몇 가지 설정값을 변경하다 보니 로케일 리졸버는 정상적으로 동작하고 있음을 확인했다. 그렇다면 LocaleChangeInterceptor 가 제대로 동작을 하지 못하고 있다는 뜻이었는데, 해서 몇 가지 자료를 찾아보다 다음의 링크에서 해결법을 찾았다.
https://stackoverflow.com/questions/51860856/spring-localresolver-based-on-query-parameter-lang-en
LocaleChangeInterceptor 가 제대로 동작하지 않는 이유는 아직 제대로 확인하지 못했지만, 위의 질문에서 찾은 이 문제에 대한 해결 방법은 LocaleChangeInterceptor 를 사용하지 않고, 로케일 리졸버에서 HTTP 요청 속성값을 읽어 이를 바로 적용하는 것이었다.
@Bean
public AcceptHeaderLocaleResolver localeResolver() {
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver() {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String locale = request.getParameter("lang");
return locale != null
? org.springframework.util.StringUtils.parseLocaleString(locale)
: super.resolveLocale(request);
}
};
localeResolver.setDefaultLocale(new Locale("en"));
return localeResolver;
}
이렇게 구성하면 요청 URL 의 lang 매개변수를 이용해 유저 로케일을 바꿀 수 있다. 예를 들어, 영어-미국 (en_US), 독일어 (de) 로케일로 바꾸려면 다음 URL 로 접속한다.
- http://localhost:8080/welcome?lang=en_US
- http://localhost:8080/welcome?lang=de
5. 로케일별 텍스트 메시지 외부화하기
다국어를 지원하는 웹 어플리케이션은 유저가 원하는 로케일로 웹 페이지를 보여줘야 한다. 로케일마다 페이지를 따로 두는 삽질을 하지 않으려면 로케일 관련 텍스트 메시지를 외부화해서 웹 페이지를 로케일에 독립적으로 개발해야 한다. 스프링은 MessageSource 인터페이스를 구현한 메시지 소스로 텍스트 메시지를 해석할 수 있다. JSP 파일에서 스프링 태그 라이브러리 <spring:message> 태그를 사용하면 원하는 코드에 맞게 해석된 메시지가 화면에 출력된다.
웹 어플리케이션 컨텍스트에 메시지 소스를 MessageSource 형 빈으로 등록한다. 이 빈을 messageSource 로 명명하면 디스패처 서블릿이 자동으로 감지하며 디스패처 서블릿 당 하나의 메시지 소스만 등록할 수 있다. ResourceBundleMessageSource 구현체는 로케일마다 따로 배치한 리소스 번들을 이용해 메시지를 해석한다. 다음과 같이 WebConfiguration 에 ResourceBundleMessageSource 구현체를 등록하면 basename 이 messages 인 리소스 번들을 로드한다.
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
return messageSource;
}
각각 기본 로케일과 독일 로케일 메시지를 담아둘 리소스 번들 messages.properties, messages_de.properties 파일을 작성해 클래스패스 루트에 둔다.
// messages.properties
welcome.title=Welcome
welcome.message=Welcome to Court Reservation System
// messages_de.properties
welcome.title=Willkommen
welcome.message=Willkommen zum Spielplatz-Reservierungssytem
이제 welcome.jsp 파일에서 <spring:message> 태그를 사용하면 주어진 코드에 해당하는 메시지를 해석해 보여줄 수 있다. 이 태그는 현재 유저 로케일을 기준으로 메시지를 자동 해석한다. 스프링 태그 라이브러리에 포함된 태그라서 꼭 JSP 파일 최상단에 선언해야 한다.
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
<title><spring:message code="welcome.title" text="Welcome"/></title>
</head>
<body>
<h2><spring:message code="welcome.message" text="Welcome to Court Reservation System"/></h2>
</body>
</html>
<spring:message> 에서 해당 코드에 맞는 메시지를 해석할 수 없을 경우 표시할 기본 텍스트는 text 속성에 설정한다.
#Reference.
'Study > Spring' 카테고리의 다른 글
[Spring 5 Recipes] Spring 5 Recipes 4장 정리 (0) | 2021.10.21 |
---|---|
[Spring 5 Recipes] Spring 5 Recipes 3장 정리 #4 (0) | 2021.10.06 |
[Spring 5 Recipes] Spring 5 Recipes 3장 정리 #2 (0) | 2021.09.30 |
[Spring] 스프링 디스패처 서블릿 (Dispatcher Servlet) (0) | 2021.09.30 |
[Spring 5 Recipes] Spring 5 Recipes 3장 정리 #1 (0) | 2021.09.29 |