おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

Rails 3 の I18n について

Ruby on Rails Guides: Rails Internationalization (I18n) APIを読んでのメモ。全部網羅してたりはしてないので、気になった方は原文見てください。

基本

I18n.translate "store.title"
I18n.localize Time.now

# 短縮形
I18n.t "store.title"
I18n.l Time.now
  • config/locales が translation 用のデフォルトロードパス。
    • I18n.load_path や config/application.rb 内で config.i18n.load_path をいじれば修正できる。
  • 辞書ファイルにはyaml形式かRubyのhash形式が使える。
  • デフォルトのlocaleは:en
    • I18n.default_localeでデフォルトのlocaleを変更できる

regional用の設定は特にないので、regional設定がしたい場合は例えばen-usとen-ukディレクトリをそれぞれ作る必要がある。それを助けるプラグインもある。

localeの判別

localeの情報をセッションやクッキーには入れるべきではない。URLの一部として解釈されるべき。友達にURL送ってその友達がそのページを見たら、同じページが見れるべき。

クエリ(またはURLの一部)でlocaleを判別する例

before_filter :set_locale

def set_locale
  # if params[:locale] is nil then I18n.default_locale will be used
  I18n.locale = params[:locale]
end

サブドメインでlocaleを判断する例

before_filter :set_locale
def set_locale
  I18n.locale = extract_locale_from_subdomain
end

def extract_locale_from_subdomain
  parsed_locale = request.subdomains.first
  I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil 
end 

URLにlocale情報を入れるときのTips

普通はURLにlocale情報を入れる。でもそうすると全てのリンクに

link_to(books_url(:locale => I18n.locale))

とかしなくちゃいけないのでめんどくさい。そんな時はApplicationController#default_url_optionsをオーバライドすると楽。

# app/controllers/application_controller.rb
def default_url_options(options={})
  { :locale => I18n.locale }
end

下記のようなルーティングを定義するとRESTfulな感じになる。

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

上記の設定だと root_url にはlocale設定がないので、下記のようなルーティングも一緒に定義しておくとよい。

# config/routes.rb
match '/:locale' => 'dashboard#index'

Accept-Languageを使ってルーティングを振り分ける例

def set_locale
  logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
  I18n.locale = extract_locale_from_accept_language_header
  logger.debug "* Locale set to '#{I18n.locale}'"
end
private
def extract_locale_from_accept_language_header
  request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first 
end

プラグインもあるみたい。

iain's http_accept_language at master - GitHub
lib/rack/locale.rb at master from rtomayko's rack-contrib - GitHub

IPベースでlocaleを判別

国別のIPデータベースであるMaxMind - GeoLite Country | Open Source IP Address to Country Databaseを使うとIPで国を判別できる。コード的にはAccept-Languageと同じような感じ。

ユーザ設定

ユーザにドロップダウンのリスト等でlocaleか選択させてDBに入れとく

RailsでのI18nの基本的な流れ

config/locales/en.yml に辞書ファイルを設定しておく

en:
  hello_world: Hello World
  hello_flash: Hello Flash

ヘルパーメソッドtで翻訳する(I18nはいらない)

# app/controllers/home_controller.rb 
class HomeController < ApplicationController
  def index
    flash[:notice] = t(:hello_flash)
  end
end 
# app/views/home/index.html.erb
<h1><%=t :hello_world %></h1>
<p><%= flash[:notice] %></p>


辞書ファイルを修正した後にはサーバの再起動が必要。

辞書ファイルにはYAMLRubyが使えるけど、YAMLの方がおすすめ。でもYAMLはスペースや特殊文字に弱くてもしかしたら適切に読み取れないかもしれない。Rubyファイルは最初のリクエストが来たときにエラーを出すので間違いに気づきやすい。なのでYAMLで変な結果が出たときには関連する場所をRubyファイルに切り出すと良い。

L18nの基本的な流れ

辞書ファイルに下記のように設定する

# config/locale/pirate.yml 
pirate:
  time:
    formats:
      short: "arrrround %H'ish"

lメソッドでlocalization。:formatオプションでフォーマットを指定できる。デフォルトは:defaultが指定されている

# app/views/home/index.html.erb
<h1><%=t :hello_world %></h1>
<p><%= flash[:notice] %></p>
<p><%= l Time.now, :format => :short %></p>

辞書ファイルの出来合いのファイルがrails/locale at master from svenfuchs's rails-i18n - GitHubにあるので使うといいらしい。

localeによるテンプレート選択

Rails 2.3 から、locale毎にテンプレートを切り替えられる機能が追加されている。例えばindex.es.html.erbファイルを用意しておくと、localeがesの時にそれが呼ばれる。大量の情報を翻訳しなくちゃならないときに便利だけど、修正が大変になるのでそのへん考えて使う必要がある。

辞書ファイルの整理

辞書ファイルの分量が多すぎるときは、辞書ファイルを config/locales 配下にディレクトリを作って分散して整理することが出来る。ネストされたディレクトリはデフォルトではload_pathに入っていないので、下記のようにして明示的にRailsに伝えてあげる必要がある。

# config/application.rb
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]

translationメソッドの使い方

tメソッドは引数に文字列とシンボルをとれる

I18n.t :message
I18n.t 'message'

オプションの引数としてscopeをとれる。keyの名前空間を指定できる。

I18n.t :invalid, :scope => [:activerecord, :errors, :messages] 

下記のように、ドットで区切って文字列に渡しても同じ

I18n.translate :"activerecord.errors.messages.invalid"

下記のメソッドは全部同じ

I18n.t 'activerecord.errors.messages.invalid'
I18n.t 'errors.messages.invalid', :scope => :active_record
I18n.t :invalid, :scope  => 'activerecord.errors.messages'
I18n.t :invalid, :scope  => [:activerecord, :errors, :messages]

定義が見つからなかったときのデフォルト設定

defaultオプションを設定すると、対応する定義が見つかれなかったときにそれが使われるようになる。

# :missingが見つからなかったら'Not here'を返す
I18n.t :missing, :default => 'Not here'
# :missingが見つからなかったら:also_missingを探し、無ければ'Not here'を返す
I18n.t :missing, :default => [:also_missing, 'Not here']

一度に複数の翻訳情報をゲット

複数のkeyを配列で渡すことで、一度に複数の翻訳をすることができる。

I18n.t [:odd, :even], :scope => 'activerecord.errors.messages'
# => ["must be odd", "must be even"]

ネストされてる名前空間を引数に渡すと、Hashで複数の翻訳情報が返ってくる

I18n.t 'activerecord.errors.messages'
# => { :inclusion => "is not included in the list", :exclusion => ... }

viewで使える簡単翻訳

Rails 2.3 から実装された、viewから簡単に翻訳する方法。下記のような辞書ファイルを用意しておくと

es:
  books:
    index:
      title: "Título"

app/views/books/index.html.erb上で

<%= t '.title' %>

のようにするだけで"Título"を呼び出せる。

変数展開

変数展開を含んだ翻訳が出来る

I18n.backend.store_translations :en, :thanks => 'Thanks %{name}!'
I18n.translate :thanks,  :name => 'Jeremy'
# => 'Thanks Jeremy!'
  • :defaultや:scopeは変数展開のキーとして使えない。もし使ったらI18n::ReservedInterpolationKeyが発生する
  • translateメソッドで引数が渡されなかったらI18n::MissingInterpolationArgumentが発生する

複数形にする

下記のような感じで複数形を定義/表示できる。

I18n.backend.store_translations :en, :inbox => {
  :one => '1 message',
  :other => '%{count} messages'
}
I18n.translate :inbox, :count => 2
# => '2 messages'
  • countが1なら最初のでそれ以外なら二つ目の定義が使われる。
  • 対応したHashが無いときにはI18n::InvalidPluralizationDataが発生する。

localeの設定

# localeの設定をクラスメソッドから
I18n.locale = :de
I18n.t :foo
I18n.l Time.now
# 引数としてlocaleを使える
I18n.t :foo, :locale => :de
I18n.l Time.now, :locale => :de
# default localeの設定もクラスメソッドから可能
I18n.default_locale = :de

Modelの翻訳

Modelの翻訳のために

  • Model.human_name
  • Model.human_attribute_name(attribute)

の二つのメソッドが使える。例えば下記のようにyamlを設定しておいた場合

en:
  activerecord:
    models:
      user: Dude
    attributes:
      user:
        login: "Handle"
      # will translate User attribute "login" as "Handle"

こんな感じで翻訳結果を取り出せる

User.human_name #=> "Dude"
User.human_attribute_name("login") #=> "Handle"

エラーメッセージの翻訳

下記のようなUserモデルがあるとする

class User < ActiveRecord::Base
  validates_presence_of :name
end

nameがnilな状態でsaveしたら、blankが探される。下記の順番で最初に見つかった結果を返す

  1. activerecord.errors.models.user.attributes.name.blank
  2. activerecord.errors.models.user.blank
  3. activerecord.errors.messages.blank

一般的にすると下記のような規則になってる

  1. activerecord.errors.models.[model_name].attributes.[attribute_name]
  2. activerecord.errors.models.[model_name]
  3. activerecord.errors.messages

エラーメッセージの翻訳に継承が使える。例えば下記のようなAdminクラスがあるとする

class Admin < User
  validates_presence_of :name
end

ActiveRecordは下記の順番で定義を探す

  1. activerecord.errors.models.admin.attributes.title.blank
  2. activerecord.errors.models.admin.blank
  3. activerecord.errors.models.user.attributes.title.blank
  4. activerecord.errors.models.user.blank
  5. activerecord.errors.messages.blank

エラーメッセージで変数展開

Active Recordのエラーメッセージには"Please fill in your %{attribute}"みたいな感じで変数展開が使える。エラーに対応する辞書ファイルの表は引用するのがめんどくさいので省略。

error_messages_for用の辞書ファイル

下記のような感じで定義する

en:
  activerecord:
    errors:
      template:
        header:
          one: "1 error prohibited this %{model} from being saved"
          other: "%{count} errors prohibited this %{model} from being saved"
        body: "There were problems with the following fields:"