Thymeleaf + Spring で i18n ~2~

2015.06.08

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

だいぶ間が開いてしまいましたが、前回の続きとして任意に言語を切り替えられるようにしてみました。

  • thymeleaf-i18n
    • デフォルトでは日本語で表示される。
    • リクエストパラメータlangjaが指定されたら日本語、enが指定されたら英語へ切り替わる。
    • 指定言語はセッションで管理される。

プロジェクト構成

ビルドツールには引き続きGradleを使っています。

example2
│  build.gradle
│  gradlew
│  gradlew.bat
│  
├─gradle
└─src
    └─main
        ├─java
        │  └─example
        │      └─controller
        │              RootController.java
        │              
        ├─resources
        │  │  logback.xml
        │  │  
        │  └─i18n
        │          Messages_en.properties
        │          Messages_ja.properties
        │          
        └─webapp
            ├─css
            ├─images
            └─WEB-INF
                │  web.xml
                │  
                ├─spring
                │  │  root-context.xml
                │  │  
                │  └─appServlet
                │          servlet-context.xml
                │          
                └─templates
                        index.html

サンプルの確認

Gradle タスクに jetty プラグインを指定してあるので jettyRun タスクの実行でサンプルを確認できます。

※前回のサンプルでは gradle-wrapper.jar が含まれていなかったため gradlew コマンドが実行できませんでした。申し訳ございませんでした。 今回のサンプルでは example1, example2 双方共に gradle-wrapper.ja が含まれています。

$ cd example2
$ ./gradlew jettyRun

http://localhost:8080/example2

WS000000

セレクトボックスで English を選択すると表示が英語に変わります。

WS000001

言語情報はセッションで保持されていますのでパラメータを指定しなくても最後に指定した言語で表示されます。

サンプルの説明

Spring の設定

example1 からの変更点は以下の通りです。

LocaleChangeInterceptor を設定
リクエストパラメータに指定された文字列を元にしたロケールを後述の LocaleResolver へ渡します。
SessionLocaleResolver を設定
LocaleResolver の実装であり、受けたロケールをセッションに保持します。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/mvc
                           http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
                           http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context-4.1.xsd">


  <!-- **************************************************************** -->
  <!--  RESOURCE FOLDERS CONFIGURATION                                  -->
  <!--  Dispatcher configuration for serving static resources           -->
  <!-- **************************************************************** -->
  <mvc:resources location="/images/" mapping="/images/**" />
  <mvc:resources location="/css/" mapping="/css/**" />


  <!-- **************************************************************** -->
  <!--  SPRING ANNOTATION PROCESSING                                    -->
  <!-- **************************************************************** -->
  <mvc:annotation-driven />
  <context:component-scan base-package="example" />


  <!-- **************************************************************** -->
  <!--  MESSAGE EXTERNALIZATION/INTERNATIONALIZATION                    -->
  <!--  Standard Spring MessageSource implementation                    -->
  <!-- **************************************************************** -->
  <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="i18n/Messages" />
    <property name="defaultEncoding" value="ISO-8859-1" />
  </bean>


  <!-- **************************************************************** -->
  <!--  THYMELEAF-SPECIFIC ARTIFACTS                                    -->
  <!--  TemplateResolver <- TemplateEngine <- ViewResolver              -->
  <!-- **************************************************************** -->

  <bean id="templateResolver"
        class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
    <property name="prefix" value="/WEB-INF/templates/" />
    <property name="suffix" value=".html" />
    <property name="templateMode" value="HTML5" />
    <property name="characterEncoding" value="UTF-8" />
  </bean>

  <bean id="templateEngine"
        class="org.thymeleaf.spring4.SpringTemplateEngine">
    <property name="templateResolver" ref="templateResolver" />
  </bean>

  <bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
    <property name="templateEngine" ref="templateEngine" />
    <property name="characterEncoding" value="UTF-8" />
  </bean>


  <!-- **************************************************************** -->
  <!--  INTERCEPTORS                                                    -->
  <!-- **************************************************************** -->
  <mvc:interceptors>
    <mvc:interceptor>
      <mvc:mapping path="/**"/>
      <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
        <property name="paramName" value="lang" />
      </bean>
    </mvc:interceptor>
  </mvc:interceptors>


  <!-- **************************************************************** -->
  <!--  LOCALE RESOLVER                                                 -->
  <!-- **************************************************************** -->
  <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
    <property name="defaultLocale" value="ja" />
  </bean>


</beans>

LocaleChangeInterceptor

      <mvc:mapping path="/**"/>

今回はインタセプトするパスを特定しないのでpath="/**"としています。

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

デフォルトのパラメータ名はlocaleですが、今回は「言語」のみを指定するのでパラメータ名をlangへ変更しています。

SessionLocaleResolver

  <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
    <property name="defaultLocale" value="ja" />
  </bean>

LocaleResolver を指定する際は bean のidを必ずlocaleResolverとします。デフォルトの言語(ロケール)は日本語jaとします。

以上でリクエストパラメータに指定した言語を、そのセッション上でロケールとして扱う事ができるようになります。

Thymeleaf テンプレート

example1 からの変更点は以下の通りです。

  • lang属性の指定
  • 言語選択セレクトボックスの設置
  • 言語の変更で GET リクエストするスクリプトの追加

example2/src/main/webapp/WEB-INF/templates/index.html

<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      th:with="lang=${#locale.language}"
      th:lang="${lang}">
  <head>
    <title>Example2</title>
    <meta charset="UTF-8" />
    <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
  </head>
  <body>
    <h1 th:text="#{hello.world}">FooBar</h1>
    <form method="get">
      <select name="lang">
        <option value="ja" th:selected="${lang=='ja'}">日本語</option>
        <option value="en" th:selected="${lang=='en'}">English</option>
      </select>
    </form>
    <script type="text/javascript">
    $('select[name="lang"]').change(function () {
        $('form').submit();
    });
    </script>
  </body>
</html>

言語の抽出

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      th:with="lang=${#locale.language}"
      th:lang="${lang}">
4 行目
th:with属性には Thymeleaf 上で用いる変数の代入式を記述できます。ここでは変数langにリクエストコンテキストのロケールオブジェクトが持つ言語値を代入しています。#localeは Thymeleaf で予め用意されているオブジェクトのひとつで、リクエストコンテキストのロケールオブジェクトを表します。
5 行目
前段で初期化した変数langhtmlタグのlang属性に指定します。この属性はレンダリングされるとlang="ja"のようになります。

言語選択セレクトボックス

      <select name="lang">
        <option value="ja" th:selected="${lang=='ja'}">日本語</option>
        <option value="en" th:selected="${lang=='en'}">English</option>
      </select>

th:selectedは真偽値を受けてセレクトボックスで選択中の項目を表現します。

langenだった場合は

      <select name="lang">
        <option value="ja">Japanese</option>
        <option value="en" selected="selected">English</option>
      </select>

とレンダリングされます。

まとめ

言語切り替えの実装はほぼ Spring の設定のみで出来てしまいました。Thymeleaf にも予め様々なオブジェクトが用意されているのでコチラをチェックしておくことをおすすめします。

今回のサンプルではセッションで言語を保持しているため、セッションが切れるとデフォルト言語に戻ってしまいます。次回は言語をクッキーで保持するよう変更して動作を確認してみたいと思います。