Railsの多言語対応

Ruby on Rails(Rails2.2から)に含まれるRuby I18n(internationalization(国際化)の略記)のgemは、 アプリケーション内で英語以外の特定の言語への変換、または複数の言語サポートの提供を簡単に行えるようにし、フレームワークを拡張してくれます。

"internationalization"(国際化)のプロセスは、通常アプリケーションから全ての文字列と、 その他のロケール(地域)固有の単位(日付けや通貨のような)を抽象化することを意味します。 "localization"(地域化)のプロセスは、翻訳を提供し、これらの単位を地域用のフォーマットにすることを意味します。 1

そのため、アプリケーションでの国際化のプロセスでは、下記の事を行う必要があります。

  • i18nをサポートすることを確定します。
  • Railsにどこでロケール辞書を見つけるか教えます。
  • Railsにロケールの設定、保護、切り替えの方法を教えます。

アプリケーションのローカライズのプロセスでは、おそらくあなたは下記の3つのこと行いたいと考えるはずです。

  • Railsのデフォルトのロケールの置換または捕捉。 例).日付と時間フォーマット、月の名前、Activeレコードモデルの名前等
  • アプリケーション内の抽象化文字列を辞書に紐付けます。 例).flashメッセージ、ビュー内の静的テキスト等
  • ある場所での辞書結果の格納

このガイドで、I18nのAPIを通して使用方法を見て行きましょう。 また、Railsアプリケーションの国際化の方法についてのチュートリアルも含まれています。

このガイドを読むことで、次の事が学べるはずです。

  • Ruby on Rails内でどのようにI18nが動作するのか。
  • 様々な方法でRESTfulアプリケーション用に正しくI18nを使用する方法について。
  • ActiveRecordのエラーまたはActionMailerのEメールの題名(subject)の翻訳のためのI18nの使用方法について。
  • アプリケーションの翻訳プロセスに使用するその他のツールについて。

RubyのI18nフレームワークは、Railsアプリケーションの国際化/地域化のために必須となる全ての機能を提供してくれます。 ただし、あなたは追加機能のために利用可能な、幾つかのプラグインと拡張を使用しているかもしれません。 より詳細な情報については、RubyのI18n Wikiを参照してください。

1. Ruby on RailsのI18nはどのように動作するのか。

国際化は大変複雑なものです。 自然言語は多くの事象で違いがあり(例えば、複数形のルール)、 一度に全ての問題を解決してくれるツールを提供することは困難です。 そういった理由から、RailsのI18n APIは下記の点にフォーカスを当てています。

  • すぐに英語とそれに似た言語のサポートが出来る事
  • その他の言語のために簡単にカスタマイズとあらゆる拡張が出来る事

解決のための一部として、Railsフレームワーク内の各静的な文字列(例. Acitveレコードの検証メッセージ、時間と日付フォーマット)は国際化されており、 地域化はこれらのデフォルトを"上書き"することを意味します。

1.1 ライブラリ全体の構造

Ruby I18nのgemは2つのパーツに分かれます。

  • i18nフレームワークのpublicなAPI - ライブラリがどのように動作するか定義されたpublicメソッドを持つRubyモジュール
  • これらのメソッドを実装したデフォルトのバックエンド(意図的にSimpleバックエンドと名付けられています) (翻訳に自信なし)

あなたはユーザーとして常にI18nモジュールのpublicメソッドにだけアクセスすることになりますが、 バックエンドの内容を知っておくことは非常に有用です。

予め含まれるSimpleバックエンドが、リレーショナルデータベース、GetText辞書、またはそれに類似するものに中に翻訳日付を格納する、 より強力なものに交換されている可能性(あるいは望まれて)があります。 後述する異なるバックエンドの使用についてのセクションを参照してください。

1.2 Public I18n API

最も重要なI18n APIのメソッドは、下記のものになります。

translate # 翻訳テキストを探します
localize  # 日付と時間オブジェクトをローカル用にフォーマットにします

これらは、#tと#lのエイリアスを持ち、次のように使用することが可能です。

I18n.t 'store.title'
I18n.l Time.now

また、下記の読み込みと書き込みの属性が存在します。

load_path         # カスタム翻訳ファイルを宣言します
locale            # 現在のロケールの取得と設定を行います
default_locale    # デフォルトのロケールの取得と設定を行います
exception_handler # 異なるexception_handlerを使用します
backend           # 異なるバックエンドを使用します

それでは次のチャプターでシンプルなRailsアプリケーションの国際化を行ってみましょう。

2. Railsアプリケーション国際化のセットアップ

I18nサポートを使用した少しのシンプルなステップと、アプリケーションの実行をするだけです。

2.1 I18nモジュールの設定

設定より規約の哲学に従い、Railsはアプリケーションのセットアップを行います。 もし異なる設定が必要であれば、これを簡単に上書きすることが出来ます。

Railsは、config/locales辞書から、あなたの翻訳読み込みパスへ、 自動的に全ての.rbファイルと.ymlファイルを追加します。

この辞書内のデフォルトのen.ymlは、翻訳文字列のサンプルのペアを含みます。

en:
  hello: "Hello world"

これは:enロケール内で、helloキーはHello world文字列にマッピングされることを意味します。 Rials内の各文字列はこの方法で国際化されます。 例として、activemodel/lib/active_model/locale/en.ymlファイルのActiveモデルの検証メッセージ、 activesupport/lib/active_support/locale/en.ymlファイルの時間と日付のフォーマットを参照してみてください。 YAMLまたは標準のRubyハッシュを、デフォルトの(Simple)バックエンド内に翻訳の格納に使用することが可能です。

I18nライブラリはデフォルトのロケールとして英語を使用し、例えばもしあなたが異なるロケールを設定しなければ、 翻訳が検索される際に:enが使用されます。

i18nライブラリは、リージョン部分を含まない(:en-USまたは:en-GBなど)、 ロケール("言語")部分(:en:pl)だけを含むロケールのキーに対してプログラム的なアプローチ(詳細は後述)を取ります。 これらは伝統的に"言語(languages)"、または"地方(regional)"、または"方言(dialects)"を区分けするのに使用されます。 多くの国際化アプリケーションは、:cs:th:es(チェコ語、タイ語、スペイン語)のような、 "言語(language)"要素のロケールのみを使用します。 ただし、異なる言語グループ内で地方(リージョン)による違いも存在し、それが重要な事もあるかもしれません。 例えば、:en-USロケール内で通貨シンボルとして$を持ちますが、 :en-GBでは£になります。 リージョンを区分けする事と他の設定によってこれが妨げられることは無く、 あなたは単に:en-GB辞書内で、完全な"English - United Kingdom"のロケールを提供する必要があります。(翻訳に自信なし) Globalize3のような、 様々なRailsのI18nプラグインはこの実装の手助けをしてくれるかもしれません。

翻訳読み込みパス(I18n.load_path)は翻訳ファイルへのパスの単なるRuby配列で、 これは自動的に読み込まれ、アプリケーション内で利用可能になります。 あなたは命名規則に沿って辞書と翻訳ファイルを選択する事が出来ます。

最初に翻訳が検索される際に、バックエンドはこれらの翻訳を遅延読み込みします。 これは既に翻訳が宣言された後であっても、その他のバックエンドへ交換する事を可能にしてくれます。

デフォルトのapplication.rbファイルには、他の辞書からどのようにしてロケールを追加するのか、 またどのように異なるデフォルトのロケールを設定するかの指示が含まれています。 コメントを外し、特定の行を編集するだけです。

# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de

2.2 オプショナル(任意指定):カスタムI18n設定のセットアップ

完璧を期すために、何らかの理由でapplication.rbファイルを使用したくない場合に、 手動での設定も可能であることについても言及しておきましょう。

I18nライブラリにカスタム翻訳ファイルの場所を伝えるために、 実際に何らかの翻訳が探される前に、それを実行するアプリケーションの読み込みパスを指定することが可能です。 また、あなたはデフォルトのロケールを変更したいと考えるかもしれません。 最もシンプルにこれを可能にする方法は、イニシャライザー(initializer)に次のように指定することです。

# in config/initializers/locale.rb

# I18nライブラリへ、あなたの翻訳ファイルの場所を伝えます。
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]

# :en以外のデフォルトロケールを設定します。
I18n.default_locale = :pt

2.3 ロケールの設定と引き渡し

もしあなたがRailsアプリケーションを英語以外の単一の言語(デフォルトのロケール)に翻訳したい場合、 application.rb内のI18n.default_localeに、またはイニシャライザーに上記で確認したように、 ロケールを設定することが可能で、それはリクエストにおいて持続されます。

ただし、あなたはおそらくアプリケーション内でより多くのロケールのサポートを提供したいと考えるはずです。 そのようなケースでは、リクエスト間でロケールの設定と引き渡しが必要になります。

もしかしたら、選択したロケールをセッションまたはCookieに格納したくなるかもしれませんが、 これを行うべきではありません。 ロケールは分かり易いURLの一部であるべきです。 この方法であれば、Webが仮定する人々の基本的な情報が壊されることはありません。(翻訳に自信なし) 友人にURLを送った際に、彼らはあなたと同じページと内容を参照するべきです。 このために「RESTfulであれ」という素敵な言葉があります。(翻訳に自信なし) RESTfulなアプローチの詳細については、Stefan Tilkovの記事を読んでみてください。 時にこのルールの例外となってしまうケースも存在しますが、それについては後述します。

設定部分は簡単です。 ApplicationController内のbefore_action内で、 下記のようにしてロケールを設定することが可能です。

before_action :set_locale

def set_locale
  I18n.locale = params[:locale] || I18n.default_locale
end

これは、http://example.com/books?locale=pt内で、URLクエリーパラメーターとしてロケールを渡す事を必要とします。 (これは例えば、Googleのアプローチと同じです。) そのため、http://localhost:3000?locale=ptはポルトガルのロケール情報を読み込み、 http://localhost:3000?locale=deはドイツのロケール情報を同じように読み込みます。 もしURL内のロケールを手動で配置しページの再読み込みによって上手く解決したいのであれば、 次のセクションをスキップして、アプリケーション初期化のセクションに進んでも良いかもしれません。(翻訳に自信なし)

もちろん、アプリケーション全体で各URL毎にロケールが手動的に含まれる事を良しとしないと考えるかもしれませんし、 常にhttp://example.com/pt/bookshttp://example.com/en/booksのように、 異なるURLが見えるようにしたいと考えるかもしれません。 あなたが選択する事が出来る、それぞれの選択肢について説明していきましょう。

2.4 ドメイン名からロケールを設定

選択肢の1つとして、アプリケーション実行時にドメイン名からロケールを設定するという方法があります。 例えば、www.example.comであれば英語(またはデフォルト)のロケールを読み込み、 www.example.esであればスペインのロケールを読み込みます。 ここでは、トップレベルドメイン名がロケールの設定に使用されています。 これには幾つかの利点があります。

  • ロケールが、明確なURLの一部となります。
  • 人々が、どの言語の内容が表示されているのかを直感的に把握することが出来ます。
  • Rails内では、非常に取るに足らない実装となります。
  • 検索エンジンは、異なる言語で異なる場所に配置され、内部でドメインがリンクされることを好みます。

ApplicationController内で、下記のように実装することが可能です。

before_action :set_locale

def set_locale
  I18n.locale = extract_locale_from_tld || I18n.default_locale
end

# トップレベルドメインからロケールを取得します。
# もしそのロケールが利用可能でなければ、nilを返します。
# ローカル環境でこれを試すためには、次のように/etc/hostsファイル内に、
# 何かしらの配置をする必要があります。
#   127.0.0.1 application.com
#   127.0.0.1 application.it
#   127.0.0.1 application.pl
def extract_locale_from_tld
  parsed_locale = request.host.split('.').last
  I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
end

非常に似た方法で、サブドメインからロケールを設定する事も可能です。

# サブドメイン(http://it.application.local:3000のような)の
# リクエストからロケールを取得します。
# ローカル環境でこれを試すためには、
# /etc/hostsファイル内に次のように何かしらを配置する必要があります。
#   127.0.0.1 gr.application.local
def extract_locale_from_subdomain
  parsed_locale = request.subdomains.first
  I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil
end

もしアプリケーションにロケールの切り替えメニューが含まれる場合、 次のようにしてこれを実装します。

link_to("Deutsch", "#{APP_CONFIG[:deutsch_website_url]}#{request.env['REQUEST_URI']}")

これは、http://www.application.deのような値を、 APP_CONFIG[:deutsch_website_url]に設定するとみなしています。

これには前述した利点がありますが、 あなたは異なるドメイン上で異なるローカライゼーション("言語バージョン")を提供出来ない、または提供したくないかもしれません。 最も明白な解決策は、URLパラム(またはリクエストパス)にロケールコードを含めることでしょう。

2.5 URLパラメーターからロケールを設定

最も一般的なロケールの設定(と受け渡し)はURLパラメーターに、それを含めることでしょう。 最初の例の中で、before_actionでI18n.locale = params[:locale]とすることでそれを受け取ります。(翻訳に自信なし) このケースでは、www.example.com/books?locale=ja、 またはwww.example.com/ja/booksのようなURLを持つことが好ましいでしょう。

このアプローチはドメイン名からロケールを設定する事とほとんど同じ利点を持ち、 言い換えるとこれはRESTfulであり、且つその他はWorld Wide Webに沿った形式であると言えます。(翻訳に自信なし) ただし、少しだけ実装のための作業が余分に必要になります。

paramsからロケールを取得し、それに沿って設定をすることはそれほど難しい事ではありません。 それは各URLに含まれ、リクエストを通して渡されます。 各URL内に明確なオプションを含める(例. link_to(books_url(locale: I18n.locale))のは、 当然面倒なことであり、もしかしたら不可能かもしれません。

Railsはこういったケースで非常に便利な、ApplicationController#default_url_options内に、 "URLの動的解決を中央に集める"ための基盤を含んでくれているため、 我々はそれに依存するurl_forとヘルパーメソッドに、 "デフォルト"の設定をすることが可能になります。(このヘルパーメソッドの実装/上書きによって)

そのため、ApplicationController内に、次のような指定をすることが出来ます。

# app/controllers/application_controller.rb
def default_url_options(options={})
  #デバッグのためのログ出力
  logger.debug "default_url_options is passed options: #{options.inspect}
"
  #ロケールの指定
  { locale: I18n.locale }
end

これでurl_forに依存する各ヘルパーメソッド(例. root_pathのような名付けされたルートや、 books_pathまたはbooks_url等のresourceのルートのヘルパー)は、 http://localhost:3001/?locale=jaのように自動的にクエリー文字列内にロケールを含むようになります。

あなたはこれで十分であると考えるかもしれません。 ただし、アプリケーションの各URLのこれはURLの末尾にロケールを"引っ掛ける"際に、URLの読み方に影響を及ぼしてしまいます。 更に設計の観点から言えば、ロケールは常にアプリケーションの他のパーツの上部の階層であり、 URLにこれを反映させるべきです。

あなたはおそらく、www.example.com/en/books(英語ロケールを読み込み)や、 www.example.com/nl/books(ドイツ語ロケールを読み込み)のような、URLにしたいと考えていることでしょう。 下記のようにルートにスコープのオプションをセットアップをするだけで、 前述した"default_url_optionsの上書き"の戦略によって、これを達成することが出来ます。

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

これでbooks_pathメソッドを呼び出す際には、"/en/books"(デフォルトのロケールとして)が取得されるはずです。 http://localhost:3001/nl/booksのようなURLはオランダのロケールを読み込むはずですし、 これに従うbooks_pathの呼び出しは、"/nl/books"(ロケールが変更されたため)を返すはずです。

もしルート内にロケールを使用することを強制させたくない場合、次のように任意(optional)パスのスコープ(括弧で表される)を使用することが出来ます。

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

ロケールの無いhttp://localhost:3001/booksのようなリソースにアクセスした際に、 このアプローチではRouting Errorにはなりません。 これは何かが指定されなかった際には、デフォルトのロケールを使用したいといったケースで便利です。

当然、あなたはアプリケーションのrootのURLに特別な処置("home"や"ダッシュボード")を必要とするでしょう。 routes.rbroot to: "books#index"宣言がロケールを取得しないため、 http://localhost:3001/nlのようなURLは自動的には動作しません。 (そして当然、唯一の"root"のURLがあります。)

あなたは、おそらく下記のようなURLのマッピングを必要とするでしょう。

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

このルート宣言が他のものを"食べて"しまわないように、ルートの順番には特に気をつけてください。 (あなたはroot :to宣言の前に、直接それを指定したいと考えるかもしれません。)

このルートによる方法を簡単に行うために、2つのプラグインを紹介します。 Sven Fuchs氏のrouting_filterと、 Raul Murciano氏のtranslate_routesです。

2.6 クライアント提供情報からロケールを設定

特別なケースとして例えばURLからでは無く、クライアントが提供する情報からロケールを解釈して設定することが出来ます。 この情報は、例えばユーザーが申告した(彼らのブラウザ設定)ものかもしれませんし、 彼らのIPから推察した地理的なロケーションを基準にしたものかもしれませんし、 単純にアプリケーションのインターフェースやプロフィールで、ユーザーが選択したロケールによって提供されるものかもしれません。 このアプローチは、セッションやCookie、上記で見たRESTfulな構造のWebサイトでは無く、 Webを基盤としたアプリケーションやサービスにとってより適したものと言えます。(翻訳に自信なし)

2.6.1 Accept-Languageの使用

クライアントが提供する1つの情報源として、HTTPヘッダーのAccept-Languageがあります。 ユーザーはブラウザまたは他のクライアント(curlのような)で、これを設定することが出来ます。

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

もちろんproduction環境では、より確実で適切なコードが必要であり、 Iain Hecker氏のhttp_accept_language、 またはRyan Tomayko氏のlocaleのような、 Rackミドルウェアを使用することが出来ます。

2.6.2 GeoIP(またはそれに類似する)データベースの使用

クライアント情報からロケールを選択する方法に、 GeoIP Lite Countryのような、 クライアントのIPからデータベースを使用してその場所(region)をマッピングする方法があります。 そのコードの仕組みは前述したコードとほぼ同じになります。 ユーザーのIPをデータベースに問い合わせ、国/リージョン/都市を返すために、適切なロケールを検索します。

2.6.3 ユーザー・プロフィール

また、アプリケーションのインターフェースから入力されたユーザーのロケール情報を同様に提供し、 設定(可能であれば上書き)することも同様に可能です。 このアプローチのメカニズムも前述したコードとほぼ同じです。 おそらく、あなたはユーザーにドロップダウンリストからロケールを選択させて、 彼らのプロフィールをデータベース内に保存していることでしょう。 その値をロケールとして設定してください。

3. アプリケーションの国際化

OK!これで、I18nをサポートするRuby on Railsアプリケーションの初期化が完了しました。 これから、どのロケールを使用しそれをリクエスト間でどのように保管しておくのかを説明していきます。 とても興味深い事を始める準備が整い、そのスタート地点に居ると考えてくださいね。

各ロケール指定の部分を抽象化して、アプリケーションを国際化してみましょう。 そして、これらの抽象化に必要な翻訳を提供して、それをローカライズ化してみましょう。

おそらくアプリケーションのいずれかに、このようなものがあるはずです。

# config/routes.rb
Yourapp::Application.routes.draw do
  root to: "home#index"
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_locale

  def set_locale
    I18n.locale = params[:locale] || I18n.default_locale
  end
end
# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    flash[:notice] = "Hello Flash"
  end
end
# app/views/home/index.html.erb
<h1>Hello World</h1>
<p><%= flash[:notice] %></p>
demo_untranslated

3.1 翻訳の追加

当然、英語にローカライズされた文字列が表示されます。 このコードを国際化するには、 これらの文字列を翻訳のためのキー付きのRailsの#tヘルパーに置き換えます。

# 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>

ここでこのビューを描画すると、 翻訳するためのキーである:hello_world:hello_flashが見つからないという旨のエラーメッセージが表示されます。

demo_translation_missing

Railsはあなたが常にI18n.tと書かなくて済むように、 t(translate)ヘルパーメソッドをビューに追加します。 更にこのヘルパーは翻訳の欠落をキャッチし、 エラーメッセージの結果を<span class="translation_missing">内に出力します。

それでは、欠落している翻訳を辞書ファイル内に追加しましょう(ローカライゼーションの一部を行います)

# config/locales/en.yml
en:
  hello_world: Hello world!
  hello_flash: Hello flash!

# config/locales/pirate.yml
pirate:
  hello_world: Ahoy World
  hello_flash: Ahoy Flash

default_localeを変更していなければ、I18nは英語を使用し、 次のように表示されるはずです。

demo_translated_en

また、URLにパイレーツ(海賊?)ロケール(http://localhost:3000?locale=pirate)を渡して変更すると、 次のように表示されるはずです。

demo_translated_pirate

新しいロケールのファイルを追加した場合は、サーバーの再起動が必要になります。

SimpleStore内に翻訳を格納するのに、YAML(.yml)または素のRuby(.rb)ファイルを使用することが出来ます。 YAMLはRails開発者の間で好まれていますが、 1つ大きなデメリットがあります。 YAMLは非常に空白と特殊文字に影響を受けやすく、あなたの辞書を適切にアプリケーションが読み込まないかもしれません。 Rubyファイルであれば最初のリクエストで不具合が出るので、問題の箇所が見つけやすいでしょう。 (もし、YAML辞書で"奇妙な問題(weird issues)"に遭遇したら、 辞書内の該当部分をRubyファイルにすることを試してください。)

3.2 翻訳への変数の引き渡し

翻訳メッセージ内に変数を使用することが可能で、ビューからそれらの変数を渡します。

# app/views/home/index.html.erb
<%=t 'greet_username', user: "Bill", message: "Goodbye" %>
# config/locales/en.yml
en:
  greet_username: "%{message}, %{user}!"

3.3 日付/時間フォーマットの追加

OK!それでは、ビューへタイムスタンプを追加してみましょう。 同様に日付/時間ローカライズ機能のデモを行います。 時間フォーマットをローカライズするには、I18n.lにTimeオブジェクトを渡すか、 Railsの#lヘルパー(こちらが望ましい)を使用します。 :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に既にデフォルトで用意されています。)

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

これは次のように表示されます。

demo_localized_pirate

今あなたはより多くの日付/時間フォーマットをI18nバックエンドに期待する動作を行わせるために、必要としていることでしょう。(翻訳に自信なし) (少なくとも'pirate'ロケールのための) 誰かが既にあなたが必要とするロケールを、Railsのデフォルトの翻訳として全て作業してくれていることは、願ってもないことです。(翻訳に自信なし) 様々なロケールのアーカイブであるGitHubのrails-i18nリポジトリを参照してみてください。 これらのファイルをconfig/locales/ディレクトリ内に置くと、自動的に使用するための準備が行われます。

3.4 他言語のためのルールのねじ曲げ

Rails4.0は英語以外の言語による、ねじ曲げたルール(単数と複数のような)の定義を許可してくれます。 config/initializers/inflections.rb内に、複数ロケールのこれらのルールを定義することが出来ます。 イニシャライザー(initializer)は、英語用の特定の追加ルールのためのデフォルト例を含んでおり、 他のロケールもそのフォーマットに従います。

3.5 ローカライズされたビュー

Rails2.3は、便利なローカライゼーション機能である、ローカライズされたビュー(テンプレート)を導入しました。 これは、例えばアプリケーションがBooksControllerを持ち、 indexアクションはapp/views/books/index.html.erbテンプレートの内容を描画するとします。 すると、このテンプレートの他の言語でローカライズされたものをindex.es.html.erbとして同じディレクトリに置かれていれば、 ロケールが:esに設定されていると、Railsはこのテンプレートの内容を描画します。 ロケールがデフォルトに設定されていれば、通常のindex.html.erbビューが使用されます。 (今後のRailsのバージョンでは、public等のアセットにこの仕組を導入するかもしれません。)(翻訳に自信なし)

この機能は、例えば静的コンテンツが膨大でYMALまたはRubyの辞書では扱いにくいようなケースで有効です。 ただし、今後の何かしらの変更はそれらのテンプレート全てに行わなければいけないことに注意してください。

3.6 ロケールファイルの構成

i18nライブラリ付属のデフォルトのSimpleStoreを使用している場合、 辞書はディスク(?)上のプレーンテキスト内に格納されています。 1つのファイルに各ロケールの翻訳の全部分を配置するのは、管理することを困難にしていしまいます。 そのため、これらのファイルを理にかなった階層として格納することが可能です。

例えば、config/locales辞書は次のようになります。

|-defaults
|---es.rb
|---en.rb
|-models
|---book
|-----es.rb
|-----en.rb
|-views
|---defaults
|-----es.rb
|-----en.rb
|---books
|-----es.rb
|-----en.rb
|---users
|-----es.rb
|-----en.rb
|---navigation
|-----es.rb
|-----en.rb

この方法であれば、ビュー内のテキストと全ての"defaults"(例えば、日付と時間フォーマット)から、 モデルとモデルの属性名を分けることが可能です。 その他のi18nライブラリの格納では、そのような分離とは異なる手段を提供することが可能です。

Railsのデフォルトのロケールの読み込みメカニズムは、 ここで紹介したような入れ子の辞書のロケールファイルを読み込んでくれません。 そのため、これを動作させるために明示的な指示をRailsに伝えなければいけません。

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

翻訳管理のために利用可能なツールのリストについては、 Ruby I18nを確認してみてください。

4. I18n API機能の概要

これまでで、基本的なRailsアプリケーションを国際化するために必要となる、 i18nライブラリの使用方法の理解が得られたと思います。 これ以降のチャプターでは、より深い機能について説明していきます。

これらのチャプターでは、翻訳のビューのヘルパーメソッドだけで無くI18n.translateメソッドの両方を使用した例をお見せします (ビューのヘルパーメソッドによる追加機能の提供はありません)。

説明される機能は次のようなものになります。

  • 翻訳の検索
  • 翻訳へのデータの補完
  • 翻訳の複数形化
  • 安全なHTML翻訳(ビューヘルパーメソッドのみ)
  • 日付、数値、通貨等のローカライズ

4.1 翻訳の検索

4.1.1 基本的、スコープ、入れ子キーの検索

翻訳はシンボルと文字列の両方で同じように評価されるキーによって検索されます。

I18n.t :message
I18n.t 'message'

また、translateメソッドは、 1つ以上の追加キーを含めることが出来る:scopeオプションも取得し、 これは"名前空間"(namespece)、または翻訳キーのためのスコープを指定するのに使用されます。

I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]

これはActiveレコードのエラーメッセージ内の、:record_invalidメッセージを探します。

加えて、キーとスコープの両方は下記のようにドット区切りのキーとして指定することが可能です。

I18n.translate "activerecord.errors.messages.record_invalid"

そのため、下記の呼び出しは全て同じように評価されます。

I18n.t 'activerecord.errors.messages.record_invalid'
I18n.t 'errors.messages.record_invalid', scope: :active_record
I18n.t :record_invalid, scope: 'activerecord.errors.messages'
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]
4.1.2 デフォルト

:defaultオプションが与えられると、翻訳が無居場合にその値が返されます。

I18n.t :missing, default: 'Not here'
# => 'Not here'

もし:defaultの値がシンボルの場合、キーとして使用され翻訳されます。 defaultには、複数の値を提供することが出来ます。 値の結果として、1つ目が返されます。

例として、下記は最初に:missingキーを、次に:also_missingの翻訳を試みます。 どちらも結果として出力されなければ、文字列"Not here"が返されます。

I18n.t :missing, default: [:also_missing, 'Not here']
# => 'Not here'
4.1.3 バルク(?)と名前空間からの検索

1度に複数の翻訳を探すために、キーの配列を渡すことが出来ます。

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

また、キーはグループ化された翻訳のハッシュ(入れ子の可能性のある)を翻訳することも可能です。 例として下記は、ハッシュとして全てのActiveレコードのエラーメッセージを受け取る事が出来ます。

I18n.t 'activerecord.errors.messages'
# => {:inclusion=>"is not included in the list", :exclusion=> ... }
4.1.4 "怠惰な"検索

Railsには、ビュー内部を検索する便利な手法が実装されています。 次のような辞書を持つ場合、

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

次のようにして、app/views/books/index.html.erbテンプレート内部の、 books.index.titleの値を見つける事が出来ます。(ドットに注意)

<%= t '.title' %>

partialによる自動翻訳のスコーピングは、翻訳ビューのヘルパーメソッドからのみ利用可能です

4.2 補完

多くのケースで、あなたは値の補完が出来るように、翻訳を抽象化しておきたいと考えるでしょう。 このため、I18nのAPIは補完機能を提供します。

:default:scopeに加え、全てのオプションは#translateへ翻訳を補完するものとして渡されます。

I18n.backend.store_translations :en, thanks: 'Thanks %{name}!'
I18n.translate :thanks, name: 'Jeremy'
# => 'Thanks Jeremy!'

もし翻訳が:defaultまたは:scopeを補完の値として使用する場合、 I18n::ReservedInterpolationKey例外が発生します。 もし翻訳が補完値を期待しているが、#translateへこれが渡されなかった場合、 I18n::MissingInterpolationArgument例外が発生します。

4.3 複数形

英語には与えられた文字列に対して、例えば"1 message"と"2 messages"のような単数形と複数形の形式が存在します。 他の言語(アラビア語、日本語、ロシア語等)は、 より多いかまたはより少ない複数形の形式の異なる文法を持ちます。 そのため、I18n APIは柔軟な複数形で表すための機能を提供します。

:countの補完値は、翻訳の補完とCLDRによって定義された複数形のルールに沿った翻訳から選択する、 両方を備えた特別なルールを持ちます。

I18n.backend.store_translations :en, inbox: {
  one: 'one message',
  other: '%{count} messages'
}
I18n.translate :inbox, count: 2
# => '2 messages'

I18n.translate :inbox, count: 1
# => 'one message'

:enの複数形のためのアルゴリズムはシンプルです。

entry[count == 1 ? 0 : 1]

例えば、翻訳が:oneを単数形としてみなすという意味であれば、 その他のものは複数形として使用されます。(ゼロになるカウントを含む)

もしキーの検索が適切な複数形のハッシュを返さない場合、 18n::InvalidPluralizationData例外が発生します。

4.4 ロケールの設定と引き渡し

ロケールはI18n.locale(例えばTime.zoneのようなThread.currentを使用)へ仮グローバル的に設定するか、 #translate#localizeへオプションとして渡すことが可能です。

もしロケールが渡されなければ、I18n.localeが使用されます。

I18n.locale = :de
I18n.t :foo
I18n.l Time.now

下記は明示的にロケールを渡している例です。

I18n.t :foo, locale: :de
I18n.l Time.now, locale: :de

I18n.localeは、デフォルトが:enであるI18n.default_localeを、デフォルトとします。 デフォルトのロケールは下記のようにして設定することが出来ます。

I18n.default_locale = :de

4.5 安全なHTML翻訳の使用

'_html'接尾辞付きのキーと'html'と名付けられたキーは安全なHTMLとしてマークされます。 ビュー内でこれらを扱う際には、HTMLはエスケープ処理されません。

# config/locales/en.yml
en:
  welcome: <b>welcome!</b>
  hello_html: <b>hello!</b>
  title:
    html: <b>title!</b>
# app/views/home/index.html.erb
<div><%= t('welcome') %></div>
<div><%= raw t('welcome') %></div>
<div><%= t('hello_html') %></div>
<div><%= t('title.html') %></div>

安全なHTML翻訳テキストへの自動変換は、translateビューヘルパーメソッドからのみ利用可能です。

demo_html_safe

5. カスタム翻訳の格納方法について

Active Support付きのSimple Backendは、 素のRubyとYAMLフォーマット2の両方の翻訳の格納を許可してくれます。

例えば、Rubyが提供する翻訳のハッシュは、次のようになります。

{
  pt: {
    foo: {
      bar: "baz"
    }
  }
}

上記と同等のYAMLファイルであれば、次のようになります。

pt:
  foo:
    bar: baz

ご覧の通り、どちらのケースもトップ階層のキーはロケールになります。 :fooは名前空間キーであり、:barは翻訳"baz"のキーです。

下記はActiveサポートの翻訳YAMLファイルであるen.ymlの"実際"の例になります。

en:
  date:
    formats:
      default: "%Y-%m-%d"
      short: "%b %d"
      long: "%B %d, %Y"

そのため、下記の全ての同等の検索は、:shortの日付フォーマット"%b %d"を返します。

I18n.t 'date.formats.short'
I18n.t 'formats.short', scope: :date
I18n.t :short, scope: 'date.formats'
I18n.t :short, scope: [:date, :formats]

我々は通常、翻訳の格納にはYAMLフォーマットを使用することをお勧めしています。 ただし、例えば特別な日付フォーマットのためにロケール日付の一部をRubyのlamdaとして格納したい、といったケースも存在します。

5.1 Activeレコードモデルの翻訳

あなたは、Model.model_name.humanModel.human_attribute_name(attribute)メソッドを、 あなたのモデルと属性の名前の翻訳の検索を分かり易くするのに使用することが可能です。

例えば、次のような翻訳を追加した場合、

en:
  activerecord:
    models:
      user: Dude
    attributes:
      user:
        login: "Handle"
      # User属性"login"を"Handle"として翻訳

次にUser.model_name.humanは"Dude"を、 User.human_attribute_name("login")は"Handle"を返します。

また、下記のようにモデル名の複数形を追加することも可能です。

en:
  activerecord:
    models:
      user:
        one: Dude
        other: Dudes

User.model_name.human(count: 2)は、"Dudes"を返します。 count: 1またはパラメータが渡されなければ、"Dude"を返します。

5.1.1 エラーメッセージスコープ

Activeレコードの検証エラーメッセージも、簡単に翻訳をすることが可能です。 Activeレコードは異なるメッセージを提供するために、 翻訳メッセージを置くことができて、特定のモデル、属性そして/または検証の翻訳する1組の名前空間を提供します。 また、単一テーブルの継承も分かり易さが考慮されています。

これは、あなたのアプリケーションのニーズに合わせてメッセージを柔軟に適用させる、 非常に強力な機能を提供してくれることを意味します。

下記のような名前属性のための検証を持つ、Userモデルがあると想定してください。

class User < ActiveRecord::Base
  validates :name, presence: true
end

このエラーメッセージのためのキーは、このケースでは:blankになります。 Activeレコードは名前空間で、このキーを検索します。

activerecord.errors.models.[model_name].attributes.[attribute_name]
activerecord.errors.models.[model_name]
activerecord.errors.messages
errors.attributes.[attribute_name]
errors.messages

そのため、この例では下記のキーの順で検索を試み、最初の結果を返します。

activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank

更にモデルが継承を使用したものである場合、メッセージは継承チェーンを辿って辿って検索されます。

例えば、Userから継承したAdminモデルがあるかもしれません。

class Admin < User
  validates :name, presence: true
end

この場合、Activeレコードは下記の順でメッセージを検索します。

activerecord.errors.models.admin.attributes.name.blank
activerecord.errors.models.admin.blank
activerecord.errors.models.user.attributes.name.blank
activerecord.errors.models.user.blank
activerecord.errors.messages.blank
errors.attributes.name.blank
errors.messages.blank

このように様々なエラーメッセージを、モデルの継承チェーン、属性、モデル、またはデフォルトスコープの異なる場所で、 特別な翻訳を提供することが出来ます。

5.1.2 エラーメッセージ補完

翻訳されるモデル名、属性名、値は、常に補完の利用することが出来ます。

そこで例えば、デフォルトのエラーメッセージ"cannot be blank"を属性名を使用して、 "Please fill in your %{attribute}"のように変更することが可能です。

countが利用可能であれば、それが提供されると複数形にするための使用が可能になります。

検証 オプション メッセージ 補完
confirmation - :confirmation -
acceptance - :accepted -
presence - :blank -
absence - :present -
length :within, :in :too_short count
length :within, :in :too_long count
length :is :wrong_length count
length :minimum :too_short count
length :maximum :too_long count
uniqueness - :taken -
format - :invalid -
inclusion - :inclusion -
exclusion - :exclusion -
associated - :invalid -
numericality - :not_a_number -
numericality :greater_than :greater_than count
numericality :greater_than_or_equal_to :greater_than_or_equal_to count
numericality :equal_to :equal_to count
numericality :less_than :less_than count
numericality :less_than_or_equal_to :less_than_or_equal_to count
numericality :only_integer :not_an_integer -
numericality :odd :odd -
numericality :even :even -
5.1.3 Activeレコードのerror_messages_forヘルパーでの翻訳

もし、Activeレコードのerror_messages_forヘルパーを使用してるのであれば、 あなたはこれに翻訳を追加したいと考えるでしょう。

Railsには、下記の翻訳が含まれています。

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:"

このヘルパーを使用するには、Gemfileにgem 'dynamic_form'の行を追加して、 DynamicFormのGemをインストールする必要があります。

5.2 Action MalerのEメールの件名の翻訳

もしmailメソッドに件名(subject)を渡さないと、 Action Mailerはこの翻訳を見つけようと試みます。 検索の実行には、キーの構成に>mailer_scope<.>action_name<.subjectのパターンが使用されます。

# user_mailer.rb
class UserMailer < ActionMailer::Base
  def welcome(user)
    #...
  end
end
en:
  user_mailer:
    welcome:
      subject: "Welcome to Rails Guides!"

5.3 I18nサポートが提供する、その他の組み込みメソッドの概要

Railsはフォーマット文字列とそれ以外のフォーマット情報のようにして、 固定文字とその他のローカライゼーションを1組として、ヘルパーメソッド上で使用します。 下記に簡単な概要を示します。

5.3.1 Actionビューヘルパーメソッド
  • distance_of_time_in_wordsはその結果の翻訳と複数形化、秒・分・時間の補完を行います。 詳細は、datetime.distance_in_wordsを参照してください。
  • datetime_selectselect_monthは、 よくある月を選択するセレクトタグの月の名前を翻訳するのに使用されます。 翻訳するには、date.month_namesを参照してください。 datetime_selectはまた、 date.order(明示的にオプションを渡すこと無く)から、 orderオプションも探します。 全ての日時を選択するヘルパーは、該当するものがあれば、 datetime.promptsスコープ内の翻訳を使用して、 プロンプト(未選択時に表示される文字?)を翻訳します。
  • number_to_currencynumber_with_precisionnumber_to_percentagenumber_with_delimiternumber_to_human_sizeヘルパーは、 numberスコープ内に設定された数値フォーマットを使用します。
5.3.2 Activeモデルメソッド
  • model_name.humanhuman_attribute_nameは、 activerecord.modelsスコープが利用可能であれば、 モデル名と属性名のための翻訳を使用します。 これらはまた、先ほどの"エラーメッセージスコープ"で説明した継承されたクラス名(例えば、単一テーブル継承(STI)のために使用)をサポートします。
  • ActiveModel::Errors#generate_message(Activeモデルの検証に使用されますが、手動でも使用されるかもしれません。)は、 model_name.humanhuman_attribute_name(前述を参照)を使用します。 これは、エラーメッセージの翻訳と、先程の"エラーメッセージスコープ"で説明された継承されたクラス名の翻訳のサポートも行います。
  • ActiveModel::Errors#full_messagesは、 errors.formatから検索された区切り指定を使用して、 属性名をエラーメッセージに差し込みます。(デフォルトは"%{attribute} %{message}"です。)
5.3.3 Active Supportメソッド

Array#to_sentenceは、 support.arrayスコープ内で与えられるフォーマット設定を使用します。

6. I18nセットアップのカスタマイズ

6.1 異なるバックエンドの使用

幾つかの理由からActive Supportに同伴されたSimple Backendは、 Ruby on Rails33のために"出来るだけシンプルにして動作させる"という事のみを行い、 これは英語だけは動作することを保証し、また言語を英語に非常に近づけるという副作用を伴います。(翻訳に自信なし) また、Simple Backendは翻訳の読みに関しては優れていますが、フォーマットを動的に格納することが出来ません。

ただし、これがあなたの足枷になるという意味ではありません。 RubyのI18nのGemは、Simple backendをあなたのニーズをより満たしてくれる、 他のものに入れ替えることを非常に簡単に行ってくれます。 例えば、GlobalizeのStatic backendに入れ替えるには次のようにします。

I18n.backend = Globalize::Backend::Static.new

また、複数のバックエンドを一緒にチェインするChain backendも使用することが出来ます。 これはSimple backendを使用した標準の翻訳を使用したいが、 アプリケーションのカスタム翻訳がデータベースまたは他のbackendに格納されているといったケースで便利です。 例えば、Active Record backendの使用と、(デフォルトの)Simple backendへのフォールバックが可能です。

I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)

6.2 別の例外ハンドラの使用

I18nのAPIは、予期せぬ状態で発生する内容に沿って、 backendによって発生させられる下記の例外を定義します。

#要求されたキーの翻訳が見つからなかった
MissingTranslationData

#I18n.localeへ設定された値が不正(例えば、nil)
InvalidLocale

#countオプションが渡されたが、
#複数形にするための適切な翻訳データでは無い
InvalidPluralizationData

#翻訳は補完のための引数を期待したが渡されなかった
MissingInterpolationArgument

#予約された補完変数名が翻訳に含まれている
#(例えば、デフォルトのスコープのone)
ReservedInterpolationKey

#backendはI18n.load_pathへ追加されたファイルタイプを
#扱い方を知らない
UnknownFileType

I18nのAPIはバックエンドでスローされた際に、これらの例外全てをキャッチし、 default_exception_handlerメソッドにそれらが渡します。 ます。 このメソッドはMissingTranslationDataを除く全ての例外を再発生させます。 MissingTranslationData例外がキャッチされた場合、 見つからないキー/スコープを含む例外のエラーメッセージの文字列を返します。

これは開発段階で翻訳が欠落していたとしても、ビューの描画が望まれるといった理由のためです。

ただし、あなたは別のコンテキストで、この振る舞いを変更したいと考えるかもしれません。 例えば、デフォルトの例外のハンドリングでは、自動テスト中に簡単に欠落した翻訳のキャッチをさせてくれません。 これを行うために、異なる例外ハンドラを指定する事が可能です。 指定された例外ハンドラはI18nモジュール上のメソッドか、#callメソッドを持つクラスでなければなりません。

module I18n
  class JustRaiseExceptionHandler < ExceptionHandler
    def call(exception, locale, key, options)
      if exception.is_a?(MissingTranslation)
        raise exception.to_exception
      else
        super
      end
    end
  end
end

I18n.exception_handler = I18n::JustRaiseExceptionHandler.new

これはMissingTranslationData例外のみを、 デフォルトの例外ハンドラへ他の全ての入力を渡して再発生させます。

ただし、I18n::Backend::Pluralizationを使用しているのであれば、 このハンドラもI18n::MissingTranslationData: translation missing: en.i18n.plural.rule例外を発生させ、 英語ロケールのためのデフォルトの複数形ルールへのフォールバックは通常は無視されるはずです。(翻訳に自信なし) これを避けるために、翻訳キーへの追加チェックを使用することが出来ます。

if exception.is_a?(MissingTranslation) && key.to_s != 'i18n.plural.rule'
  raise exception.to_exception
else
  super
end

デフォルトの望ましくない振る舞いのその他の例に、 #tメソッド(#translateも同様)を提供するRailsのTranslationHelperがあります。(翻訳に自信なし) MissingTranslationData例外がこのコンテキスト内で発生すると、 ヘルパーはtranslation_missingのCSSクラス付きのspanでこのメッセージを囲みます。

これを行うため、たとえ:raiseオプションの設定よって例外ハンドラが定義されていたとしても、 ヘルパーはI18n#translateで例外を発生させることを強制します。

#バックエンドから常に例外を再発生
I18n.t :foo, raise: true

注釈

1  Wikipediaから引用。
"国際化(Internationalization)は、エンジニアリングによる変更無しで、 様々な言語と地域に適応可能なソフトウェア・アプリケーション設計のプロセスです。 ローカライゼーション(Localization)は、特定のリージョン(地方)または言語に、 ロケール固有のコンポーネントと翻訳テキストを追加することで、ソフトウェアを適応させるプロセスです。"

2  他のbackendは、別のフォーマットの使用を許可または必要とするかもしれません。 例えば、GetTextのbackendはGetTextの読み込みを可能にしてくれます。

3  これらの理由の1つに、I18n機能を必要としないアプリケーションに不必要な物を読み込ませたくないという考えがあり、 そのため可能な限りシンプルにI18nを保持する必要がありました。 その他に、存在する全ての言語のI18nに関連する全ての問題を、 1つの実装で全て解決するという事は事実上不可能であるという理由があります。 そのため、実装全体を状況に応じて簡単に変更可能とする解決策を取ることにしました。 また、これにより簡単にカスタム機能や拡張を試すことが出来ます。

 Back to top

© 2010 - 2017 STUDIO KINGDOM