波打際のブログさん

主に、プログラミング備忘録など。

Ruby on RailsのI18nで使用する名前空間に関してのまとめと、ベストプラクティスの検討。

はじめに

 Ruby on RailsにはI18nというgemが標準で同梱されており、特に何もせずとも文章の国際化を行うことができる仕組みになっています。ちなみに、I18nとはinternationalizationを表しており、iとnの間に18文字あるから `I18n` だそうです。
 最近関わっているRailsのプロジェクトで国際化を行うタスクが発生し、I18nの辞書ファイルを定義する際に、定義ファイル中の名前空間(ロケール情報を定義する場所)について、どのような構造で配置するべきか調べる機会があったのでまとめておきます。

  • 2013年12月14日 Rubyの徳が高い同僚が、グローバルなattributesに関しての情報を見つけてくれたので追記しました。
  • 2014年07月04日 ロケールファイルの構成を一部見直しました。

環境

  • Rails4

想定環境

rails generateで name, email, urlを引数に指定してUserをscaffoldした環境を想定してロケールファイルを記述しています。

rails generate scaffold user name:string email:string url:string
Userモデル
User name email url

※idとかcreated_atとかupdated_atは気にしないでください。

app/views/users フォルダ
app/views/users
_form.html.erb
index.html.erb
show.html.erb
edit.html.erb

※他にも生成されますが、これくらいで許してください。

Rails公式ガイドのまとめ。

 Railsの公式ガイドを Rails Internationalization (I18n) API — Ruby on Rails Guides を読み解いた結果、ロケールファイルは次のような名前空間の構造で記述すれば良いことがわかりました。

ja:
  # モデルは全て activerecord 以下に記述する。
  # これにより、User.model_name.human / User.human_attribute_name({attr_name})でアクセス可能。
  activerecord:
    models:
      user: 'ユーザ情報'
    attributes:
      user:
        name: '名前'
        mail: 'メールアドレス'
        url:  'ウェブページ'
    errors:
      models:
        user:
          blank: 'urlかmailが空白です。'
          attributes:
            name:
              blank: '名前が空白です。'  # errosの中に同じ定義がある場合、ネストが深いほうが優先順位が高い
    
  
  # ビューはビューを格納しているフォルダ名を起点にし、ビュー名毎に記述する。
  # 対応するビューの中ではツリーを省略できる。<%= t '.title' %>
  users:
    index:
      title: 'ユーザ一覧'
    show:
      title: '%{user_name}さんのユーザ情報'
    edit:
      title: '%{user_name}さんのユーザ情報を編集'
 
 
  # グローバルな感じのエラーはerrorsを起点にする。
  errors:
    nanikano_error: 'よくわからないけど例外です。'

上記の形式で記述することで、Railsのヘルパーメソッドを利用して、容易にロケール情報へアクセスすることができます。

Modelのロケール情報へのアクセス

 モデル関連のロケール情報を、上記の形式で書くことで ActiveRecord::Base に定義されている、model_name.humanとhuman_attribute_name を利用して取得することができます。

p User.model_name.human # => `ユーザ情報` (activerecord.models.userを参照)

p User.human_attribute_name(:name) # => `名前` (activerecord.attributes.user.nameを参照)

Viewからロケール情報へのアクセス

 ビュー関連のロケール情報を、上記の形式で書くことで、対応するビューの中で t ヘルパーメソッドを利用して、キー名を省略した形で取得することができます。

# index.html.erbの中で実行
<% t '.title' %> # => 'ユーザ一覧' (users.index.titleを参照)

# show.html.erbの中で実行
<% t '.title', user_name: user.name %> # => '{ユーザ名}さんのユーザ情報' (users.show.titleを参照)

HTMLをロケールの中に記述する

 通常、ロケールファイルの中にHTMLタグを記述しても、タグがエスケープされて出力されます。しかし、キーの末尾を _html にすることで、ロケールファイル内に書かれたHTMLタグをそのまま出力することが可能です。

ja:
  users:
    index:
      hello_html: 'Hello!<strong>Ruby</strong>World!'

 ただし、ロケールにHTMLタグを持たせることには抵抗があります。言語ファイルがビューのスタイルに影響を及ぼしてしまうことはロケール情報の範疇を超えていると個人的には考えています。なので、他に綺麗に記述することができない、止む終えない場合の最終手段程度に留めておくことをお勧めします。

オレオレベストプラクティス

 Rails公式ガイドでは ヘルパー や コントローラ、共通のワード等の名前空間の構造には触れられていませんでした。そこで、stackoverflowや既存のロケールファイルを参考に、オレオレベストプラクティス的な物を考えてみました。

ja:
  # 共通の使いまわす可能性のあるワードは dictionary を起点にする。
  dictionary:
    messages:
      hello_user: 'こんにちは%{user_name}さん'
    words:
      user: &user 'ユーザ情報'        # &user はエイリアスで *user で参照できる。(同一ファイル内のみ有効)
      user_copy: *user                # => 'ユーザ情報' となる。
      site_name: '波打際のブログさん'
 
  # モデルは全て activerecord を起点にする。
  # これにより、User.model_name.human / User.human_attribute_name({attr_name})でアクセス可能。
  activerecord:
    models:
      user: 'ユーザー情報'
    attributes:
      user:
        name: '名前'
        mail: 'メールアドレス'
        url:  'ウェブページ'
    errors:
      nanikano_model_error: 'モデルで何かのエラー'  # 特定のモデルに属さないエラーはこの階層に書く。
      models:
        user:
          blank: 'urlかmailが空白です。' # モデル内で使いまわすエラーはこの階層に書く。
          attributes:
            name:
              blank: '名前が空白です。'  # errosの中に同じ定義がある場合、ネストが深いほうが優先順位が高い
  
  # 全モデルで共通のアトリビュートはattributesを起点にattribute名を直下に記述する。
  # validation errorやhuman_attribute_nameで取得できる。
  # (ネストの深いものが優先されるため、activerecord.attributes内の優先度が最も高い。)
  attributes:
    user_id: 'ユーザー'

  # ヘルパー関数はhelpersを起点にする。
  # ヘルパー関数内で tメソッドを使用すると、呼び出し元のビューに基づいたパスが呼び出される。
  # 例えばusersのshowから呼ばれたヘルパー関数内で t('.hoge') を実行した場合 users.show.hoge が参照される。
  # 呼び出し元によって文言を変えたい場合はビュー側に記述する。(そんなことあるかわからないけど。)
  helpers:
    user:
      welcome: 'ようこそ!'
  
  # ビューはビューを格納しているフォルダ名を起点にし、ビュー名毎に記述する。
  # 対応するビューの中ではツリーを省略できる。<%= t '.title' %>
  users:
    index:
      title: 'ユーザ一覧'
    show:
      title: '%{user_name}さんのユーザ情報'
    edit:
      title: '%{user_name}さんのユーザ情報を編集'

  # ActionMailerを継承したMailerはクラス名を起点にする。
  nanikano_mailer:
    alert_email:
      subject: 'タイトル'
      body:    '本文'

  # グローバルな感じのエラーはerrorsを起点にする。
  errors:
    nanikano_error: 'よくわからないけど例外です。'

 コントローラに関する記述が無いと思いますが、コントローラで固有の言語情報を持つ設計は、そもそも設計が誤りである可能性があります。もしコントローラにロケールしなければならない文字列がある場合には、設計を見直してみてください。
(redirect_toメソッド呼び出しの際の、noticeやalertメッセージに関しては、文量がさほど多くなく割と使い回しているため、dictionary.messages以下に突っ込んでしまっています。100行200行にでもなったら考えましょ。)

 あくまでも オレオレベストプラクティス なので、おかしいところ、もっとこうする方がいいんじゃね?とかあると思います。もしそう思ったら指摘してくれると嬉しいです。