基本的な使い方
Playframework で国際化するときには play.api.i18n.Messages()
を使う。
val title = Messages("home.title")
でもって conf/messages
, conf/messages.ja
などに次のように書いておくと翻訳される。
home.title=ホーム
Messages("home.title")
は play.api.i18n.Messages
の apply()
メソッドを呼び出している。apply()
メソッドは play.api.i18n.Lang
型 (以下 Lang
)の implicit 引数を取る。
def apply(key: String, args: Any*)(implicit lang: Lang): String
Messages.apply()
はテンプレートの中で使われることが多い。
公式ドキュメントでは twirl テンプレートに implicit で Lang を渡すように Note が書かれている。
@* テンプレートの引数宣言。implicit で Lang を渡す *@
@()(implicit lang: Lang)
<h1>@Messages("home.title")</h1>
テンプレート以外の場合でも同じように implicit で渡すことができる。
// Scala コード中で implicit として渡す
def title(implicit l: Lang) = Messages("home.title")
コントローラメソッドを Action で書くときは implicit な play.api.mvc.Request
を取るようにして使う。
def index = Action { implicit request =>
Ok(views.html.index())
}
問題
Lang
implicit を渡し忘れてもコンパイルエラーにならない。
Lang
companion object に implicit が定義されている。引数で渡し忘れるとそちらが使われる。
なお companion object の実装は、JVMが動作している環境のデフォルト言語となる。リクエストごとに言語を切り替えたいことが多いため、companion object の実装が使われると残念な思いをすることが多い。
よくある例1. テンプレートの引数で宣言し忘れる
@* (implicit lang: Lang) を付け忘れ *@
@()
<h1>@Messages("home.title")</h1>
よくある例2. 関数の引数で宣言し忘れる
// (implicit l: Lang) を付け忘れ
def title = Messages("home.title")
よくある例3. implicit request を付け忘れる
Request
から Lang
へは play.api.mvc.Controller#request2lang()
の implicit 変換がなされるが、Request
がなければ Lang
へも変換されない。
// implicit request => を付け忘れ
def index = Action {
Ok(views.html.index())
}
よくはないけどはまった例
わかったよ。implicit で引き回すから忘れるんだろう。普通の引数にすればいいじゃないか。
// Play 2.2 では companion object の Lang が使われてしまう
def index = Action { implicit request =>
// 暗黙的なものを取り出して、
val lang = implicitly[Lang]
// 以下 lang を明示的に渡して回る
...
}
Play 2.2 では Request
から Lang
に変換する implicit def の名前が lang で、local 変数によって implicit が効かなくなる模様。(Scala のバグ?)
lang 以外の変数名(例えば _lang
)を使えば期待したとおり動作する。
Play 2.3 では request2lang という名前に置き換わった。 val request2lang = ...
とすれば同じような問題が発生しそう。
https://github.com/playframework/playframework/issues/2605
https://gist.github.com/tkawachi/8e4987441f775786d1d8
解決法
だれか良い解決法をください。
コメント欄に素敵な解決策が。
メモ
IntelliJ で Cmd+Shift+p を押すと、カーソルがある場所で使われている implicit がどこで定義されているかを参照することができる。便利。知らなかった。