반응형

스프링 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

 

Spring LocalResolver based on query parameter 'lang=en'?

Can I tell spring-boot to automatically resolve the requested locale by a queryparameter, eg &lang=en? I would like to give the query param precedence over Accept-Language parameter. I found ...

stackoverflow.com

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.

 

스프링 5 레시피(4판)

이 책은 스프링 5에 새로 탑재된 기능 및 다양한 구성 옵션 등 업데이트된 프레임워크 전반을 실무에 유용한 해법을 제시하는 형식으로 다룹니다. IoC 컨테이너 같은 스프링 기초부터 스프링 AOP/A

www.hanbit.co.kr

+ Recent posts