2012年1月8日日曜日

[Rails3] 国際化 I18n のまとめ(その3:辞書ファイルの使い方)

実行環境:
ruby 1.9.3
Rails 3.1.3
I18n
Railsアプリで表示を担当するのはビューなので国際化するのは当然ビューですね。ページに表示される文字すべてが対象となります。

ページに表示される文字をざっと分類してみましょう。
  1. DBに格納されている文字列
  2. HTMLに直接書く単語や文章
  3. モデルの名前や属性の名前などモデルに関連する文字列
  4. Railsのエラーメッセージ

1. DBに格納されている文字列

DBに格納されているデータを国際化(多言語化)する場合は、各ロケールごとのデータを用意する必要があります(日本語だけなら不要です)。
シンプルな方法としては、テーブルに複数のロケール用データをカラムを増やして各ロケールのデータを格納します。
TABLEの構造
+------+----------+------------+
|  id  | hello_en |  hello_ja  |
+------+----------+------------+
|   1  |  Hello   | こんにちは |
+------+----------+------------+
もしくはハッシュの形で各ロケールのデータを1つにまとめて(=シリアライズして)DBの1つのカラムに突っ込むという手もあります。
#{RAILS.ROOT}/app/models/greetings.rb
class Greeting < ActiveRecord::Base
  serialize :data, Hash
end
  @greeting.data = {:en => "Hello", :ja => "こんにちは"}
いずれの方法にせよ、表示の際にロケールにあわせて表示し分けるようにします。

2. HTMLに直接書く単語や文章

とりあえず自由に辞書を定義する
HTMLテンプレートに書くタイトルや文章などを辞書ファイルで定義していく、というのがとりあえず思いつくところでしょうか。辞書ファイルはハッシュの形で定義します。基本的にキーを自由につけることができますし、整理のために自由に階層をつけることができます。
#{RAILS.ROOT}/config/locale/ja.yml
ja:
    hello: こんにちは
    foods:
        vegetables:
            cucumber: きゅうり
参照するには I18n.t メソッドを使います。
Controllerの中では
#{RAILS.ROOT}/app/controllers/users_controller.rb
  I18n.t('hello')
  I18n.t('foods.vegetables.cucumber')
Viewの中では
#{RAILS.ROOT}/app/views/users/index.html.erb
  <%= t('hello') %>
  <%= t('foods.vegetables.cucumber') %>
という形で参照します。

「きゅうり」のように階層になっている用語については、上位の階層をscopeと見ることもでき、
#{RAILS.ROOT}/app/views/users/index.html.erb
  <%= t('cucumber', :scope => 'foods.vegetables') %>
  <%= t('vegetables.cucumber', :scope => 'foods') %>
という書き方も可能。

キーの指定は文字列だけでなく、シンボルでもいけます。
#{RAILS.ROOT}/app/views/users/index.html.erb
  <%= t(:hello) %>
  <%= t(:foods, :scope => 'vegetables.cucumber') %>
  <%= t(:foods, :scope => [:vegetables, :cucumber]) %>

ビュー別の定義
特定のビューで使用する辞書定義は、ルールに従って定義するとビューファイルの中で簡単に参照できます。
#{RAILS.ROOT}/config/locale/ja.yml
ja:
  user:        # コントローラ名
    index:     # アクション名
      title: ユーザー一覧
    show:      # アクション名
      title: ユーザーの詳細
このように定義するとビューファイルの中で <%= t('.title') %> で参照でき、index.html.erb と show.html.erb でそれぞれの定義が使われます。

ロケール別のHTMLテンプレート
単語・文章レベルで表示を差し替えればよいだけであれば上記の方法で対応できますが、ロケールによってページ内の構成が大幅に異なる場合などはロケールごとに別々にHTMLテンプレートを用意しておくこともできます。
#{RAILS.ROOT}/app/views/users/
[users]
  |-- index.en.html.erb
  |-- index.ja.html.erb
  |-- index.de.html.erb
ロケールにあわせて適切なHTMLテンプレートを選択してくれます。

3. モデルの名前や属性の名前などモデルに関連する文字列

モデルに関連する名前は次のように辞書ファイルに書きます。
#{RAILS.ROOT}/config/locale/ja.yml
ja:
  activerecord:
    models:
      user: ユーザー             # モデル名
    attributes:
      user:                      # モデル名
        name: 名前               # モデルの属性名 
        password: パスワード     # モデルの属性名 
モデル名、モデルの各属性名は
User.model_name.human            # モデル名
User.human_attribute_name(name)  # モデルの属性名 
のように簡単に参照できます。もちろん通常通り I18n.t('activerecord.models.attributes.name') でも参照できます。

form の中ではモデルの属性値を表す label が自動的に辞書ファイルの内容で置き換えられます。
#{RAILS.ROOT}/app/views/edit.html.erb
  <%= form_for(@user) do |f| %>
    <%= f.label :name %>    # 「名前」
  <%= end %>

※個人的には、毎回 User.model_name.human、User.human_attribute_name(name)とか書くのは若干面倒だなぁと思うので、こんなヘルパーメソッドを作ってモデル名やモデルの属性名が超簡単に拾えるようにしてます。
#{RAILS.ROOT}/app/helpers/application_helper.rb
def t_ar(label)
  arr = label.split(/./)
  if arr.length <= 2 then
    begin
      model = arr[0].constantize
    else
      if arr.length == 1
        return model.model_name.human
      else
        return model.human_attribute_name(arr[1])
      end
    end
  end
  label
end
  t_ar('User')        # モデル名(User.model_name.human)
  t_ar('User.name')   # 属性名(User.human_attribute_name(name))

4. Railsのエラーメッセージ

エラーメッセージも辞書ファイルで定義することで日本語化できます。
#{RAILS.ROOT}/config/locale/ja.yml
ja:
  activerecord:
    errors:
      messages: ユーザー一覧
        blank: "が記入されていません。"   # ①
        invalid: "が不正な値です。"
        confirmation: "が一致しません。"
        ...
エラーの種類に対応したエラーメッセージをこのように定義します。この定義はすべてのモデルのエラーをカバーします。

一方、特定のモデルや特定の属性について別のエラーメッセージを指定するには次のようにします。
特定のモデル用のエラーメッセージ
#{RAILS.ROOT}/config/locale/ja.yml
ja:
  activerecord:
    errors:
      models:
        user:
          blank: "が記入されていません。"   # ②
          ...
特定のモデルの特定の属性用のエラーメッセージ
#{RAILS.ROOT}/config/locale/ja.yml
ja:
  activerecord:
    errors:
      models:
        user:
          attributes:
            name:
              blank: "が記入されていません。"   # ③
              ...
より細かいところでの定義が優先されます。
Userモデルのname属性についてのvalidationが以下のように定義されていてnameに値にblankエラーが発生した場合、③ → ② → ① の順に探して最初に見つかった定義が使われます。
#{RAILS.ROOT}/app/models/user.rb
class User < ActiveRecord::Base
  validates_presence_of :name
end

1 件のコメント:

  1. 大変参考になりました。ありがとうございます。

    下記のソースが動かなかったので、コメントさせて頂きました。

    #{RAILS.ROOT}/app/helpers/application_helper.rb
    def t_ar(label)
    arr = label.split(/./) #<= ここ
    if arr.length <= 2 then

    正規表現のドットは全てにマッチしてしまうので、 arr = label.split('.') じゃないと動かないと思うのですが、いかがでしょう?

    返信削除