scalaのWebフレームワーク liftで遊ぶ(11) - 国際化のお話
目次はこちら。
scalaのWebフレームワーク liftで遊ぶ 目次 - ゆろよろ日記
Lift has full support for internationalization and localization of text in code and templates.
Based on the current locale lift can selects the appropriate template. You can also localize a subsection of a default template or in inline code.
Liftでは、コード中のテキストとテンプレートでの国際化とローカライズを完全にサポートします。
Liftはロケールに合わせて適切なテンプレートを選択します。 また、テンプレートかインラインコードにおけるデフォルトの一部分をローカライズすることができます。
てなことで、さっそく実践。
テンプレートの国際化
htmlテンプレートの一部をロケールに合わせて変換するには、<lift:loc>タグで囲むようにする。
このタグ内が、プロパティファイルの内容に応じて国際化される。
<lift:loc locid="speak">のように、locidを指定した場合は、locidをキーにしてプロパティファイルから検索される。
locidを指定しない場合は、タグのtextがキーになる。
まずは、国際化するhtmlをsrc/main/webapp/hello.htmlとして作成する。
src/main/webapp/hello.html
<lift:surround with="default" at="content"> <h1><lift:loc>Hello</lift:loc></h1> <lift:loc locid="speak">I speak English</lift:loc> </lift:surround>
作成したページはSiteMapに登録しておく。
src/main/scala/bootstrap/liftweb/Boot.scalaのbootメソッドで、/pages/helloへのリンクを追加した。
val entries = Menu(Loc("Home", "/", "Home")) :: Menu(Loc("Test", "/test", "Test Page")) :: Menu(Loc("Hello", "/pages/hello", "Hello page.")) :: User.sitemap
これをやらないと、http://localhost:8080/pages/helloにアクセスしても404だった。
次に、実際に出力される文字列をロケール毎にプロパティファイルとして作成する。
公式ではフランス語でやっていたが、せっかくなので日本語でやることにした。
src/main/resources以下に、jaとenの2つのプロパティファイルを作成する。
当然、native2asciiすべし。
src/main/resources/lift_ja.properties
Hello=こんにちは speak=日本語でおk
こっちはnative2ascii後。
Hello=\u3053\u3093\u306b\u3061\u306f speak=\u65e5\u672c\u8a9e\u3067\u304ak
src/main/resources/lift_en.properties
Hello=Hello speak=I speak English
プロパティファイルを作ったら、クラスパス上に配置する必要がある。
eclipse上で作成して、src/main/resourcecの出力フォルダーを設定しても、なぜかtarget/classesにコピーされなかった。
以下のmvnコマンドで、プロパティファイルをクラスパス上に配置する。
mvn resources:resources
これで準備完了。jettyを起動して、http://localhost:8080/pages/helloにアクセスすると、ちゃんと日本語が出る。
scalaコード内の国際化
scalsaコード内の文字列を国際化したい場合は、S.?("locid")を使用すればよいようだ。
前回作ったFriendsクラスのshow_dateメソッドを修正して動作確認する。
Friends.scala
class Friends { def show = <span>Scala</span> def show_date = { val now = new Date <span>{getDateInstance(LONG, Locale.JAPANESE) format now} {S.?("speak")}</span> } }
http://localhost:8080/testで、プロパティファイルの内容で出力されていることが確認できる。
ロケールの判定
公式にドキュメントがなかったので、liftのソースを追ってみたよ。scalaわからんけど。
まず、現在のリクエストがどのロケールなのかは、S.localeメソッドで取得できる。
この中身は以下のようになっており、LiftRules.localeCalculatorに設定されている関数を呼び出すようになっているようだ。
/** * Returns the Locale for this request based on the HTTP request's * Accept-Language header. If that header corresponds to a Locale * that's installed on this JVM then return it, otherwise return the * default Locale for this JVM. */ def locale: Locale = LiftRules.localeCalculator(request.map(_.request))
次に、LiftRules.localeCalculatorを見てみる。
/** * A function that takes the current HTTP request and returns the current */ var localeCalculator: Can[HttpServletRequest] => Locale = defaultLocaleCalculator _ def defaultLocaleCalculator(request: Can[HttpServletRequest]) = request.flatMap(_.getLocale() match {case null => Empty case l: Locale => Full(l)}).openOr(Locale.getDefault())
var localeCalculatorは(Can[HttpServletRequest] => Locale)型の関数オブジェクトで、defaultLocaleCalculatorを初期値で設定している。
このlocaleCalculatorを任意の関数に差し替えることで、ロケールの決定方法をカスタマイズできるようだ。
このほかにも、TimeZoneの決定方法などもカスタマイズできるようだ。
デフォルトで設定されているdefaultLocaleCalculatorは、HttpRequestヘッダの"Accept-Language:"を元にロケールを決定している。
(HttpServletRequest#getLocale()を呼び出している。)
ロケールが決定できない場合は、JVMのロケールを返すようになっている。
では、実際にQueryStringsからlocaleを設定するように、LiftRules.localeCalculatorを置き換えてみる。
たとえば、http://localhost:8080/test?locale=enとある場合は、ロケールがenとなる。
LiftRules.localeCalculatorの設定は、Bootクラスで行う。
src/main/scala/bootstrap/liftweb/Boot.scalaのbootメソッドに、以下のように追加した。
src/main/scala/bootstrap/liftweb/Boot.scalaのbootメソッド。
LiftRules.localeCalculator = queryStringLocaleCalculator _ LiftRules.localizationLookupFailureNotice = Can.legacyNullTest(localizationFailureNoticeToConsole )
LiftRules.localeCalculatorにqueryStringLocaleCalculatorという関数を設定している。
また、LiftRules.localizationLookupFailureNoticeは、ローカライズに失敗した場合に呼び出される関数を設定できる。
ここでは、ローカライズに失敗した際に標準出力にメッセージを出す関数を設定している。
これら関数の実体も、Bootクラスに追加しておく。
def queryStringLocaleCalculator(request: Can[HttpServletRequest]) :Locale= { var l = S.param("locale") if (!l.isEmpty ) new Locale(l.open_! ) else LiftRules.defaultLocaleCalculator(request) } def localizationFailureNoticeToConsole (locid:String, locale:Locale) :Unit = Console.println("Localization Failed locid = " + locid + " locale = " + locale)
もう少しscalaっぽくスマートに書けると思うんだが、俺にはこれが限界です。
では、実際にhttp://localhost:8080/pages/hello?locale=enでアクセスしてみる。
日本語ではなく、lift_en.propertiesに書いてある内容が出力される。
さらに、Friendsクラスもちょっと修正して、ローカライズに失敗するように存在しないlocidでS.?を呼び出すようにしてみる。
def show_date = { val now = new Date <span>{getDateInstance(LONG, Locale.JAPANESE) format now} {S.locale}{S.?("hoge")}</span> }
http://localhost:8080/testにアクセスすると、LiftRules.localizationLookupFailureNoticeにより、標準出力に以下のように出力される。
Localization Failed locid = hoge locale = ja
これでカスタマイズ完了!