Railsガイドにきちんと目を通して新しい知識を得る - Rails 国際化(I18n)API編 -

ドキュメントを読み込むのは大事、ということでRailsガイドを頭から読んでいく取り組みをしています。 各章ごとに、(Railsガイドにちゃんと書いてあるのに)知らなかった機能を雑にまとめていきます。

今回は、Rails 国際化(I18nAPIの章です。

railsguides.jp

パラメータでロケールを管理する際の工夫されたURL設計

リンクはこちら

ルーティングを以下のように書くことで、https://xxx.com/yyy?locale=jaなどではなく、https://xxx.com/en/yyyにアクセスした際にparamslocaleを受け取ることができます。
※あくまでparamslocaleで値を受け取れるだけで、ロケールが変更されるというわけではないです。

# routes.rb
scope "/:locale" do
  resources :books
end

Railsガイド中の、以下の記載もなるほどなーと思いました。

アプリケーションの設計面から見れば、ロケールはアプリケーションドメインの他の部分よりも構造上の上位に位置するのだから、ロケール情報もURLの上位に置くべきという考え方もあります。

アプリによっては、基本は日本が対象であれば、Railsガイドにも紹介されている以下のように省略(デフォルト)できるようにするのが良さそうです。
さらに以下では言語も絞っていて、enかja以外だとRoutingErrorになります。

# routes.rb
scope "(:locale)", locale: /en|ja/ do
  resources :books
end

セッションやcookieロケール情報を含めるのはNG

リンクはこちら

ロケールはURLの一部にするべきであるということが書いてあります。
URLを共有した相手が同じページを見れる「RESTful」であることを守るためということです。

YAMLエスケープが必要なキー

リンクはこちら

つい先日会社の勉強会でも話題になったのですが、YAMLのクセについてです。
いくつか特定のキーはエスケープしないと訳文が見つからないエラーになってしまいます。

trueとかfalseとかはなんとなくわかりますが、yes, no, on, offあたりもエスケープしないといけないのは個人的に驚きがある仕様です。
知らないと地味にハマりポイントになりそうです。

# YAML
sample:
  on: 'Sample On!'
dummy:
  'on': 'Dummy On!'
I18n.t('sample.on') #=> Translation missing
I18n.t('dummy.on') #=> Dummy On!

index.en.html.erbでテンプレートファイルごと区別する

リンクはこちら

たとえば3つ上のところで設定したようなルーティング(英語と日本語のロケールがあるもの)のとき、index.en.html.erbなどとすることでロケールによってテンプレートファイルを分けることができます。
1つのファイルで表現するのは難しいとか、内容が大きく異なるときなどはこちらを使うのが良さそうです。

ただ、試したところ、たとえばデフォルトロケールが日本語の状態で、index.ja.html.erbindex.html.erbだけ用意して英語のロケールでアクセスすると、index.ja.html.erbが使われました。
なんとなくindex.html.erbが使われるのかな?と思っていたのでこの優先度は意外でした。

translateメソッドの参照

リンクはこちら

scope

# YAML
sample:
    parent:
      child1:
        child2: 'Child2!'

上記は以下で呼び出せます。文字列の形で.で繋ぐ方法しか使ってこなかったので知りませんでした。

I18n.t(:child2, scope: [:sample, :parent, :child1])
#=> 'Child2!'

default

訳文が見つけられなかったときに表示するものを指定できます。

# YAML
dummy: '見つけられなかったらこれ'
I18n.t('no_exist', default: :dummy)
#=> "見つけられなかったらこれ"
I18n.t('no_exist', default: [:no_exist1, :no_exist2, :dummy])
=> "見つけられなかったらこれ"

ハッシュで複数取得

# YAML
parent:
    child1: 1つ目
    child2: 2つ目
    child3: 3つ目

配列で複数取得したり、親要素を指定してハッシュで取得したりもできます。

I18n.t(['parent.child1', 'parent.child2'])
#=> ["1つ目", "2つ目"]
I18n.t('parent')
#=> {:child1=>"1つ目", :child2=>"2つ目", :child3=>"3つ目"}

deep_interpolation: trueで変数を使う

まとめて取得する際に変数に値を入れたい場合はdeep_interpolation: trueを使う必要があります。

# YAML
parent:
    child1: 1つ目
    child2: 2つ目 %{foo}
I18n.t('parent', deep_interpolation: true, foo: 'フー')
#=> {:child1=>"1つ目", :child2=>"2つ目 フー"}

ロケール固有のルール

i18n.plural.ruleで固有の複数形化のルールを設定できるというものです。
I18n::Backend::Simple.include(I18n::Backend::Pluralization)を使うことで柔軟な複数形の対応ができるようになります。

# YAML
apples:
    one: 0もしくは1
    other: 1より大きい
I18n.t :apples, count: 0, locale: :ja
#=> "1より大きい"
I18n::Backend::Simple.include(I18n::Backend::Pluralization)
I18n.backend.store_translations :ja, i18n: { plural: { rule: lambda { |n| [0, 1].include?(n) ? :one : :other } } }

I18n.t :apples, count: 0, locale: :ja
#=> "0もしくは1"

バックエンドを切り替える

リンクはこちら

I18n.backend=でバックエンドを切り替えることができます。
Redisを読みに行くようにするなどをすることがあるみたいですね。

たとえば以下のように適当にバックエンドを指定してあげると、.ymlファイルでは翻訳ができなくなりました。

# YAML
hello: こんにちは
# application.rb

config.i18n.backend = I18n::Backend::KeyValue.new(String.new)
I18n.t('hello')
#=> 'Translation missing'

訳文が見つからなかった時に例外を発生させる

リンクはこちら

config.i18n.raise_on_missing_translations = trueにすることで訳文が見つからなかった場合に例外が発生します。
viewでtranslateメソッドを使った場合には訳文が見つからないとkey名をそのまま返すため、訳文の実装を忘れていても意外と気づきにくいです。
Railsガイドで書いてあるようにtest環境(もしかしたらdevelopment環境も?)をtrueにするのはアリだなと思いました。