[spring mvc]ロケールをURL(パス)の一部で切り替える

ロケール(言語)の切り替えをパス(URLの一部)で切り替えます
例えば、http://localhost/en/test1/が英語で、http://localhost/ja/test1/ が日本語といった具合です

URLパラメータによるロケールの切り替え

springには「LocaleChangeInterceptor」クラスが用意されており、URLパラメータにロケールを指定することにより言語の切り替えができるような機構が備わっています。
springの設定で、以下のようにすると、URLパラメータ「?lang=en」をつけることによりロケールが切り替えられます

<interceptors>
  <beans:bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <beans:property name="paramName" value="lang"/>
  </beans:bean>
<interceptors>

これだとちょっとカッコ悪いのでなんとかしたい

URL(パス)によるロケールの切り替え

URLの一部にロケールを入れることにより言語を切り替えられるようにしたいと思います。
例えば http://localhost/en/test1/ が英語で、 http://localhost/ja/test1/ が日本語といった感じです
使用するspringのバージョンは 4.1.6 です

Interceptorの作成

UrLの一部からロケールを判断・設定するInterceptorを作成します
HandlerInterceptorAdapterを継承して、以下の様に作成します

package test.interceptor;

import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.support.RequestContextUtils;

public class PathLocaleChangeInterceptor extends HandlerInterceptorAdapter {

	  public static final String DEFAULT_PARAM_NAME = "locale";
	  private String paramName = DEFAULT_PARAM_NAME;
	  /**
	   * Controllerの@RequestMappingで指定するパラメータ名を返します
	   * @param paramName
	   */
	  public void setParamName(String paramName) {
	    this.paramName = paramName;
	  }
	  /**
	   * Controllerの@RequestMappingで指定するパラメータ名を設定します
	   * @param paramName
	   */
	  public String getParamName() {
	    return this.paramName;
	  }
	  @Override
	  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException {
	    Locale locale = extractLocale(request);
	    if(locale != null) {
	      LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
	      if (localeResolver != null) {
	        localeResolver.setLocale(request, response, locale);
	      }
	    }
	    return true;
	  }
	  /**
	   * パスからロケールを返します
	   * パスにない場合、デフォルトのロケールを返します
	   * @param request
	   * @return
	   */
	  private Locale extractLocale(HttpServletRequest request) {
	    String locale = extractLocaleString(request);
	    if(StringUtils.hasText(locale)) {
	      return StringUtils.parseLocaleString(locale);
	    }
	    return request.getLocale();
	  }
	  /**
	   * パスからロケール部分の文字列を取り出す
	   * @param request
	   * @return
	   */
	  private String extractLocaleString(HttpServletRequest request) {
	    @SuppressWarnings("unchecked")
		Map templateVariables = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
	    String locale = null;
	    if(templateVariables != null) {
	      locale = (String)templateVariables.get(getParamName());
	    }
	    return locale;
	  }
}

spring設定ファイルへの定義

作成したInterceptorを設定ファイルに追加します

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
 :   :
 :   :
  <interceptors>
    <beans:bean id="interceptor" class="test.interceptor.PathLocaleChangeInterceptor">
      <beans:property name="paramName" value="locale" />
    </beans:bean>
  </interceptors>
  <beans:bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
    <beans:property name="defaultLocale" value="ja" />
  </beans:bean>
 :   :
 :   :
</beans:beans>

コントローラの作成

準備は整いましたのでコントローラを作成します
URLの一部をロケールにしますが、設定ファイルに

<interceptors>
  <beans:bean id="interceptor" class="com.testdrive.regex.interceptor.PathLocaleChangeInterceptor">
    <beans:property name="paramName" value="locale" />
  </beans:bean>
</interceptors>

と、パラメータ名を「locale」として定義していますので、@RequestMappingに「{locale}」として定義します

package example.locale;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping(value = "{locale}/localetest/")
public class LocaleController {

  @RequestMapping(value = {"/test1/"}, method = RequestMethod.GET)
  public String home(Locale locale, Model model, HttpServletRequest request) {
    Date date = new Date();
    DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
    String formattedDate = dateFormat.format(date);
    model.addAttribute("serverTime", formattedDate );
    model.addAttribute("locale", locale.toString());
      return "Hello";
	}
}

JSP

<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ page session="false" %>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Home</title>
<link href="<c:url value="/resources/css/main.css" />" rel="stylesheet">
</head>
<body>
<h1 class="title">Hello, world!</h1>
<P>${serverTime}</P>
<P>Locale : ${locale}</P>
</body>
</html>

実行

完成したので実行してみます
ロケールが切り替えられているのが確認できます

URLを日本語(/ja/localetest/test1/)にした場合

Springロケール

URLを英語(/en/localetest/test1/)にした場合

Springロケール

あとは、メッセージソースをロケール毎に用意してやれば国際化は完成です
メッセージソースを使った国際化の方法については[spring mvc]MessageSourceを使ったページの国際化に記載しておりますのであわせてどうぞ