Rails 国際化(i18n)API

RubyのI18n(国際化・多言語化: internationalizationの略)gemはRuby on Rails 2.2以降からRailsに同梱されています。このgemは、アプリケーションの文言を英語以外の別の1つの言語に翻訳する機能や、多言語サポート機能を簡単かつ拡張可能な方式で導入するためのフレームワークを提供します。

アプリケーションの「国際化」プロセスといえば、使われるすべての文言やロケール固有の要素 (日付や通貨フォーマットなど) の抽象化までの作業を指すのが普通です。一方、「ローカライズ(localization)」とは、具体的な翻訳方法を提供したり、そのためのフォーマットを提供したりすることを指します。1

Railsアプリケーションを国際化するプロセスでは、以下を行う必要があります。

  • i18nを確実にサポートすること。
  • ロケール辞書の置き場所をRailsに指示すること。
  • ロケールの設定・保存・切替方法をRailsに指示すること。

Railsアプリケーションをローカライズするプロセスでは、おそらく以下の3つの作業が必要となるでしょう。

  • Railsのデフォルトロケールの差し替えまたはロケールの追加。日付や時刻のフォーマット、月の呼称、Active Recordモデル名などが対象。
  • アプリケーションで使われる文字列を抽象化し、キーで検索できる辞書に保存する。フラッシュメッセージやビュー内の固定テキストなどが対象。
  • 作成された辞書を別の場所に保存する。

本ガイドではI18n APIをひととおり概観しており、Railsアプリケーションを国際化する方法を一から解説するチュートリアルも収録しています。

このガイドの内容:

  • Ruby on RailsにおけるI18nのしくみ
  • RESTfulなアプリケーションを正しく国際化するためのさまざまな方法
  • I18n機能を使ってActive RecordのエラーやAction Mailerのメールタイトルを翻訳する方法
  • その他アプリケーションの翻訳に使えるツールの紹介

RubyのI18nフレームワークでは、Railsアプリケーションの国際化/ローカライズに必要な手段がすべて提供されています。もちろん、さまざまなプラグインや拡張機能を導入してそれ以外の機能を追加しても構いません。詳しくはrails-i18n gemを参照してください。

1 Ruby on RailsにおけるI18nのしくみ

国際化は複雑な問題です。自然言語には、たとえば単数形と複数形の規則のような多くの違いがあり、すべての問題を一度に解決する魔法のようなツールを提供するのは非常に困難です。このため、Rails I18n APIでは以下の点に絞り込んで問題の解決を図っています。

  • 基本的に英語およびそれに近い言語に対するサポートを提供する
  • それ以外の言語についてもあらゆる要素をカスタマイズおよび拡張可能にする

問題解決の一環として、Railsフレームワーク内のすべての静的文字列(Active Recordのバリデーションメッセージ、時刻や日付のフォーマットなど)の 国際化部分は既に完了しています。従って、Railsアプリケーションのこれらの部分の「ローカライズ」とは、欲しい言語の文字列について翻訳済みの値(訳文)を定義することを指します。

アプリケーションのコンテンツ(ブログ記事の翻訳など)の保存方法や更新方法をローカライズする場合は、モデルのコンテンツを翻訳するを参照してください。

1.1 ライブラリのアーキテクチャ概観

以上のことから、RubyのI18nのgemは以下の2つに分けられます。

  • i18nフレームワークのpublic API : ライブラリの動作を定義するpublicメソッドを持つRubyモジュール
  • 上記メソッドを実装する、デフォルトのシンプルな(あえてこう呼びます)バックエンド

I18nのユーザーとしては、I18nモジュールのパブリックなメソッドにだけアクセスするのが筋ですが、バックエンドの機能についても知っておくと何かと便利です。

Rails備え付けの「シンプルな」バックエンドを、より高性能なものに置き換えることもできます。たとえば訳文データをリレーショナル・データベースやGetText(訳注: Unixの国際化ライブラリ)辞書などに保存できます。詳しくは後述のバックエンドを切り替えるを参照してください。

1.2 I18nのpublic API

I18n APIで最も重要なメソッドを以下に示します。

translate # 訳文を参照する
localize  # DateオブジェクトやTimeオブジェクトを現地のフォーマットに変換する

上のメソッドにはそれぞれ#t#lというエイリアスメソッドがあるので、以下のように簡潔に書けます。

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

以下の属性については読み取り属性と書き込み属性が使えます。

load_path                 # カスタム訳文ファイルの場所を示す
locale                    # 現在のロケールの取得と設定
default_locale            # デフォルトロケールの取得と設定
available_locales         # アプリケーションでの利用が許されるロケールの許可リスト
enforce_available_locales # ロケールの許可リスト化を強制するかどうか(true/false)
exception_handler         # 異なるexception_handlerを使う
backend                   # 異なるバックエンドを使う

次の章では、シンプルな手順でRailsアプリケーションを一から国際化してみましょう。

2 Railsアプリケーションを国際化向けに設定する

RailsアプリケーションでI18nのサポートを導入および実行するには、いくつかの手順を実施するだけで済みます。

2.1 I18nモジュールを設定する

Railsは、設定より規約という思想に基づいて、十分に吟味されたデフォルト設定をアプリケーションに対して行います。i18nは文字列のデフォルト訳文を適切に提供します。別の訳文が必要な場合は上書きできます。

config/locales以下にあるすべての.rbファイルと.ymlファイルは、自動的に訳文読み込みパスに追加されます。

上記ディレクトリにあるデフォルトのen.ymlロケールファイルには、サンプルとなる原文・訳文ペアが含まれています。

en:
  hello: "Hello world"

上の例は、「:enというロケールでは、"hello"というキーは"Hello world"という文字列に対応付けられる」という意味です。Rails内部の文字列はすべてこのように国際化されています。具体例については、Active Modelのバリデーションメッセージ一覧(activemodel/lib/active_model/locale/en.ymlファイル)やActive Supportの日付時刻フォーマット一覧(activesupport/lib/active_support/locale/en.yml ファイル)を参照してください。デフォルトの(シンプルな)バックエンドの訳文は、YAMLや標準のRubyハッシュに保存できます。

I18nライブラリでは、Englishデフォルトのロケールとして扱います。デフォルトのロケールに他の言語を指定しなかった場合は、訳文の検索に:enが使われます。

いくつかの議論を経た結果、Railsのi18nライブラリではロケールキーの扱いについて 実用に則したアプローチ を採用しています。つまり、:en:plのようないわゆるロケール(言語)部分のみをロケールキーとして採用しました。:en-US:en-GBのような、言語と地域(あるいは方言)を分離した表記法は、ロケールキーとしては使われていません。 実際、国際化されたアプリケーションの多くは、:cs:th:es(それぞれチェコ語、タイ語、スペイン語)のような言語部分のみをロケール表記として採用しています。しかし、同じ言語グループに属していても、地域による違いが重要になることもあります。端的な例として、:en-USの通貨記号は$(ドル)ですが、:en-GBの通貨記号は£(ポンド)です。上のように、このような地域ごとの違いを言語から分離することについては何も問題はありません。この場合に必要なのは、"English - United Kingdom"というロケールを:en-GB辞書に追加することだけです。

訳文読み込みパス (I18n.load_path) はファイルへのパスの配列であり、自動的に読み込まれます。このパスを設定することで、訳文のディレクトリ構造やファイル命名スキームをカスタマイズできます。

I18のバックエンドは、訳文が初めて参照されるときに遅延読み込みを行います。これにより、訳文を既に公開した後でもバックエンドを他のものに差し替えることができます。

config/application.rbでは次のように、デフォルトのロケールを変更したり訳文読み込みパスを設定したりできます。

config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb, yml}')]
config.i18n.default_locale = :de

読み込みパスは、訳文の参照前に指定する必要があります。config/application.rbのイニシャライザのデフォルトロケールを変更するには次のようにします。

# config/initializers/locale.rbファイルの内容:

# I18nライブラリに訳文の探索場所を指示する
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb, yml}')]

# アプリケーションでの利用を許可するロケールのリストを渡す
I18n.available_locales = [:en, :pt]

# ロケールを:en以外に変更する
I18n.default_locale = :pt

ただし、アプリケーションのi18nで設定する代わりにI18n.load_pathsに直接追加すると、外部gem由来の訳文はオーバーライドされません

2.2 リクエスト間でロケールを管理する

ローカライズされたアプリケーションは、将来複数ロケールのサポートが必要になるかもしれません。これを行うためには、各リクエストの冒頭にロケールを設定して、リクエストが持続する間はすべての文字列が指定のロケールで翻訳されるようにしておくべきです。

I18n.locale=I18n.with_localeを使わない場合、すべての訳文でデフォルトのロケールが使われます。

I18n.localeの設定がすべてのコントローラで一貫していないと、同じスレッドやプロセスによって処理される今後のリクエストにI18n.localeが漏出する可能性があります。たとえば、あるPOSTリクエストでI18n.locale = :esを実行すると、ロケールを設定していないコントローラで以後のすべてのリクエストに効いてしまいます。こうした理由から、I18n.locale =の代わりに、漏出が発生しないI18n.with_localeを利用することもできます。

ロケールはApplicationControlleraround_actionで設定できます。

around_action :switch_locale

def switch_locale(&action)
  locale = params[:locale] || I18n.default_locale
  I18n.with_locale(locale, &action)
end

上の例では、ロケールをURLクエリパラメータで設定しているところを示しています(http://example.com/books?locale=ptなど)。この方法では、http://localhost:3000?locale=ptでポルトガル語のローカライズを出力し、http://localhost:3000?locale=deでドイツ語のローカライズを出力するようになります。

ロケールの設定にはさまざまな方法を使えます。

2.2.1 ドメイン名を元にロケールを設定する

その他のオプションの1つとして、アプリケーションが実行されているドメイン名に基いてロケールを設定することもできます。たとえば、www.example.comにアクセスした場合は英語ロケール(デフォルト)にし、www.example.esにアクセスした場合はスペイン語にするという具合です。従って、ここでは「トップレベルドメイン名」を使ってロケールを設定します。この方法には以下のような多くの利点があります。

  • ロケールがURLの一部として明確に示される。
  • ユーザーはそのWebページが何語で表示されるのかをすぐ理解できる。
  • Railsでの実装がかなり楽になる。
  • 検索エンジンは、このようにドメイン別に異なる言語のコンテンツが置かれ、しかもドメイン名同士に関連性があるものを優先的に扱っているふしがある。

このように設定するには、ApplicationControllerで以下のように実装します。

around_action :switch_locale

def switch_locale(&action)
  locale = extract_locale_from_tld || I18n.default_locale
  I18n.with_locale(locale, &action)
end

# トップレベルドメインからロケールを取得する、なければ+nil+を返す
# この動作をローカルPCで行なうためには、
#   127.0.0.1 application.com
#   127.0.0.1 application.it
#   127.0.0.1 application.pl
# /etc/hostsファイルに上のように記述する必要がある
def extract_locale_from_tld
  parsed_locale = request.host.split('.').last
  I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end

ほぼ同じ方法で、サブドメインを使ってロケールを設定することもできます。

# リクエストのサブドメインからロケールを取り出す (http://it.application.local:3000のような形式)
# この動作をローカルPCで行なうためには
#   127.0.0.1 gr.application.local
# /etc/hostsファイルに上のように記述する必要がある
def extract_locale_from_subdomain
  parsed_locale = request.subdomains.first
  I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end

アプリケーションにロケール切り替えメニューを取り付ける場合は、以下のような記述が使えるでしょう。

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

ここではAPP_CONFIG[:deutsch_website_url]の部分にhttp://www.application.deのような値を設定するとします。

ドメイン名からロケールを取得する方法には前述のメリットがありますが、ドメイン名が変わったときにローカライズ内容まで変えるわけにいかない事情や、変えたくないような事情もあるでしょう。そのような場合に最適な解決法は、URL params(つまりリクエストパス)にロケールコードを含めることでしょう。

2.2.2 URL paramsを元にロケールを設定する

最も一般的なロケールの設定方法と受け渡し方法は、既に最初の例でI18n.with_locale(params[:locale], &action) around_action を使ったのと同じように、ロケールをURL paramsに含めることでしょう。この場合、www.example.com/books?locale=jawww.example.com/ja/booksのようなURLを使えます。

この方法では、ドメイン名からロケールを取得するのとほぼ同じメリットを得られます。つまり、アプリケーションはRESTfulになり、World Wide Webの他の部分にも調和します。ただし、少しだけ実装を追加する必要があります。

paramsからロケールを取り出して設定するのは案外簡単です。ロケールをすべてのURLに含めてリクエスト経由で渡せばよい のです。ただし、たとえばlink_to(books_url(locale: I18n.locale))のような形式ですべてのURLにロケールオプションを直接含める方法はかなり面倒になるので、実用的ではありません。

Railsには、こういうときのためにApplicationController#default_url_optionsに「URLの動的な決定を集中制御する」ためのインフラが備わっています。これを使うことで、url_forや、これに依存するヘルパーメソッドの"デフォルト値"を設定できます(この場合、default_url_optionsを実装してオーバーライドする必要があります)。

ApplicationControllerにも同様の設定を含めることが可能です。

# app/controllers/application_controller.rb
def default_url_options
  { locale: I18n.locale }
end

上のようにすることで、url_forに依存するすべてのヘルパーメソッド(root_pathroot_urlなどの名前付きメソッドやbooks_pathbooks_urlなどのリソースルーティングを持つヘルパー)では自動的にロケール情報がクエリ文字列に含まれるようになります。たとえばhttp://localhost:3001/?locale=jaのような形式になります。

これで要件は満たされるでしょう。これによってURLが読みにくくなるようなことはほとんどありませんが、ロケール情報がURLの末尾にぶらさがることになるのも確かです。さらにアプリケーションの設計面から見れば、ロケールはアプリケーションドメインの他の部分よりも構造上の上位に位置するのだから、ロケール情報もURLの上位に置くべきという考え方もあります。

その場合は、URLをhttp://www.example.com/en/books(英語ロケールを読み込む)や http://www.example.com/nl/books(オランダ語ロケールを読み込む)のような形式にするとよいでしょう。これは、"default_url_optionsの設定をオーバーライドする" 手法で達成できます。以下のようにルーティングでscope 設定する必要があります。

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

これでbooks_pathメソッドを呼び出すと、デフォルトロケールが"/en/books"のようにURLに現れます。http://localhost:3001/nl/booksのようなURLの場合はオランダ語ロケールが読み込まれ、その後books_pathを呼び出すと、ロケールが反映された"/nl/books"が返されます。

default_url_optionsの戻り値はリクエストごとにキャッシュされるため、対応するI18n.localeを繰り返しごとに設定するループ内では、ロケールセレクタ内のURLがヘルパー呼び出しによって生成されません。この方法ではなく、I18n.localeは変更せずにヘルパーに明示的に:localeオプションを渡すか、request.original_fullpathを編集してください。

ルーティングでロケールを強制したくない場合は、以下のように、丸かっこで示したオプションのパススコープを使うこともできます。

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

上記のようにロケールを必須でない設定にすることで、http://localhost:3001/booksのようにロケールを含まないURLを使ってもRouting Errorは生じなくなります。これは、ロケールの指定がなければデフォルトロケールを使うようにしたい場合に便利です。

ただし上記の方法を使う場合、アプリケーションのホームページやダッシュボードが設置される、いわゆる「ルートURL」については特別な注意が必要です。たとえば、http://localhost:3001/nlのようなURLを指定しても機能しません。これは、routes.rbにおけるroot to: "books#index"宣言ではロケールが考慮されないからです(原理上"root" URLは1つのアプリケーションに1つしかありません)。

この問題を回避するには、たとえば以下のようにURLをマッピングする必要があります。

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

このルーティング宣言が他のルーティングを「食べてしまう」ことのないよう、ルーティング宣言の順序に十分ご注意ください(この記述はroot :toの直前に置くことも可能です)。

routing_filterroute_translatorなど、ルーティングに対してこのような形でシンプルに動作するさまざまなgemがあります。

2.2.3 ロケールをユーザーが自由に設定する

アプリケーションの認証済みユーザーに、アプリケーションのインターフェイスからロケールをユーザー好みに設定させることができます。このアプローチでは、ユーザーが選択したロケール設定をデータベースに保存しておき、それを用いてユーザーからの認証済みリクエストごとにロケールを設定します。

around_action :switch_locale

def switch_locale(&action)
  locale = current_user.try(:locale) || I18n.default_locale
  I18n.with_locale(locale, &action)
end
2.2.4 暗黙のロケールを選択する

リクエストにロケールが明示的に設定されていない場合(上のいずれかの方法などで)、望ましいロケールをアプリケーションが推測すべきです。

2.2.4.1 Languageヘッダーからロケールを推測する

Accept-Language HTTPヘッダーは、リクエストへのレスポンスで使いたい言語を示します。ブラウザはユーザーの言語設定に基づいてこのヘッダーの値を設定し、ロケール推測時に使える適切な第一候補とします。

Accept-Languageヘッダーを使った簡単な実装は、たとえば以下のような感じになります。

def switch_locale(&action)
  logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}"
  locale = extract_locale_from_accept_language_header
  logger.debug "* Locale set to '#{locale}'"
  I18n.with_locale(locale, &action)
end

private
  def extract_locale_from_accept_language_header
    request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
  end

実際には、この信頼性を実現するのにより堅固なコードが必要です。Iain Hackerのhttp_accept_languageライブラリやRyan TomaykoのlocaleRackミドルウェアがこの問題へのソリューションを提供しています。

2.2.4.2 IP地理情報からロケールを推測する

リクエストを送信するクライアントのIPアドレスは、クライアントの地理上の位置やロケールを推測するのに使えます。GeoLite2 Countryなどのサービスや、geocoderなどのgemは、このアプローチの実装に利用できます。

一般に、このアプローチはlanguageヘッダーを用いる方法に比べて信頼性がかなり落ちるため、ほとんどのWebアプリケーションではおすすめできません。

2.2.5 セッションやcookieに含まれるロケールの保存について

開発者は、選択したロケールをセッションやcookieに保存したくなる誘惑にかられるかもしれません。しかしこれは行ってはいけません。ロケールは透過的にすべきであり、かつURLの一部に含めるべきです。そうすることで、ユーザーがWeb自体に対して抱く基本的な前提を崩さずに済みます。あなたがそのURLを知人に送れば、あなたが見ているのとまったく同じページとコンテンツを知人も見ることができます。この前提を表す重要な言い回しが「RESTfulである」ということです。RESTfulアプローチについて詳しくは、Stefan Tilkovの記事を参照してください。RESTfulというルールから外れる場合もありますが、それについては後述します。

3 国際化とローカライズ

以上でRuby on RailsアプリケーションのI18nサポートの初期化が完了しました。利用するロケールも設定され、リクエスト間でロケールを保存する方法も指定されました。

次に必要なのはアプリケーションの国際化です。国際化は、ロケール固有のあらゆる要素を抽象化することで行います。最後に必要なのはローカライズ(localization)です。ローカライズは、これらの抽象化に対して必要な訳文を提供することで行います。

次の例をご覧ください。

# config/routes.rb
Rails.application.routes.draw do
  root to: "home#index"
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

  around_action :switch_locale

  def switch_locale(&action)
    locale = params[:locale] || I18n.default_locale
    I18n.with_locale(locale, &action)
  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>

rails i18n demo untranslated

3.1 ローカライズされたコードを抽象化する

上のコードには英語にローカライズされた文字列が2つあり("Hello Flash"と"Hello World")、これらがユーザー側でレンダリングされます。このコードを国際化するには、Railsの#tヘルパーを使ってこれらの英文字列を置き換えます。この#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をキーに持つ訳文が見つからないというエラーメッセージが表示されます。

rails i18n demo translation missing

Railsはt (translate) ヘルパーメソッドを自動的にビューに追加するので、I18n.tのように書かずに済みます。さらに、このヘルパーは訳文が見つからない場合にエラーメッセージを <span class="translation_missing"> でラップして表示してくれます。

3.2 訳文を与えて文字列を国際化する

訳文がないので、訳文の辞書ファイルに追加しましょう。

# 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はまだ変更されていないので、訳文では:enロケールが使われ、レスポンスで英文の文字列が出力されます。

rails i18n demo translated to English

URLを変更して、海賊語のロケールを渡すと (http://localhost:3000?locale=pirate)、以下のように表示されます。

rails i18n demo translated to pirate

新しく追加したロケールファイルは、サーバーを再起動するまで反映されません。

訳文をSimpleStoreに保存する際、YAML(.yml)ファイルまたはRuby(.rb)ファイルのいずれかの形式を選べます。 YAMLは多くのRails開発者に好まれている形式です。ただし、YAMLには1つ大きな問題があります。YAMLはホワイトスペースや特殊文字による影響を非常に受けやすいため、アプリケーションが辞書を正しく読み込めないことがあります。 Rubyファイル形式を選んだ場合、問題があれば最初のリクエストの時点でアプリケーションがクラッシュするので問題を見つけやすいというメリットがあります(YAML辞書で原因不明の奇妙な問題が発生した場合は、その部分をRubyファイルに移動してみると問題が解決するかもしれません)。

訳文がYAMLファイルに保存されている場合は、一部のキーをエスケープしなければなりません。以下のキーでエスケープが必要です。

  • trueonyes
  • falseoffno

例(failure以下のキーがエスケープされていない):

# config/locales/en.yml
en:
  success:
    'true':  'True!'
    'on':    'On!'
    'false': 'False!'
  failure:
    true:    'True!'
    off:     'Off!'
    false:   'False!'
I18n.t 'success.true'  # => 'True!'
I18n.t 'success.on'    # => 'On!'
I18n.t 'success.false' # => 'False!'
I18n.t 'failure.false' # => Translation Missing
I18n.t 'failure.off'   # => Translation Missing
I18n.t 'failure.true'  # => Translation Missing

3.3 訳文に変数を渡す

アプリケーションの国際化を成功させるには、ローカライズされたコードを抽象化するときに、そのロケールの文法規則を間違えないよう注意することが肝心です。あるロケールの基礎となる文法規則(語順など)が、他のロケールでも同じとは限りません。

不適切な抽象化の例を以下に示します。この例では、訳文を構成する各部分の順序(ここでは通貨記号の表示位置)に関する思い込みがあります。Railsでは、以下のような場合に使えるnumber_to_currencyヘルパーが提供されています。

<!-- app/views/products/show.html.erb -->
<%= "#{t('currency')}#{@product.price}" %>
# config/locales/en.yml
en:
  currency: "$"
# config/locales/es.yml
es:
  currency: "€"

@product.priceが10の場合、スペイン語での正しい表記は「€10」ではなく「10 €」なのですが、こうした点が抽象化に含まれていません。

抽象化を正しく行うために、I18n gemには「変数の式展開」機能が含まれており、訳文定義で変数を使えるようにし、翻訳メソッドでそれらに値を渡せるようにします。

適切な抽象化の例を以下に示します。

<!-- app/views/products/show.html.erb -->
<%= t('product_price', price: @product.price) %>
# config/locales/en.yml
en:
  product_price: "$%{price}"
# config/locales/es.yml
es:
  product_price: "%{price} €"

この抽象化では、文法や約物(句読点や記号)がすべて定義自身の中で決定されているので、正しい訳文を得られます。

defaultキーワードおよびscopeキーワードは予約されているため、変数には使えません。利用するとI18n::ReservedInterpolationKey例外が発生します。 ある訳文で式展開変数が期待されているにもかかわらず、#translateに値が渡されない場合は、I18n::MissingInterpolationArgument例外が発生します。

3.4 日付・時刻フォーマットを追加する

今度はビューにタイムスタンプを追加して、日付・時刻のローカライズ機能のデモを行ってみます。時間のフォーマットをローカライズするには、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>

それでは、海賊語の訳文ファイルに時刻フォーマットを追加してみましょう(既にデフォルトロケールを英語から海賊語に切り替えているとします)。

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

出力結果は以下のようになります。

railsのi18nデモ: 時刻を海賊語にローカライズ

現時点では、I18nバックエンドが(少なくとも海賊語ロケールで)本当に正常に動作するには、日付・時刻フォーマットを追加する必要があるでしょう。もちろん、誰か親切な人が Railsのデフォルト文字列をすべて翻訳してくれた結果がネット上のどこかに既にあるかもしれません。ロケールファイルのアーカイブがあるかどうかについては、GitHubのrails-i18nリポジトリを探してみるとよいでしょう。運よくそうしたファイルが見つかれば、config/locales/ディレクトリに置くだけで即座に使えるようになります。

3.5 他のロケール向けの活用形ルールを設定する

Railsでは英語以外の言語についても単数形・複数形のような活用形を定義できます。複数の言語を対象とする活用形ルールはconfig/initializers/inflections.rbファイルで指定できます。このイニシャライザには英語の活用形の追加例が記載されており、他の言語でも同じ要領で活用形ルールを追加できます

3.6 ローカライズ済みビューテンプレート

アプリケーションにBooksControllerというコントローラがあるとします。このコントローラにあるindexアクションが実行されるとapp/views/books/index.html.erbテンプレートが実行されます。同じディレクトリに「スペイン語ローカライズ版」のindex.es.html.erbテンプレートファイルを置くと、ロケールが:esのときにこのテンプレートが表示に使われるようになります。ロケールがデフォルトに設定されている場合、汎用のデフォルトindex.html.erbビューテンプレートが使われます(今後のRailsでは、publicディレクトリなどに置かれたアセットを「自動的にローカライズする」機能が搭載されるかもしれません)。

ビューに大量の文章が含まれているような場合、これらを文単位に分解してYAML辞書やRuby辞書の訳文に置き換えると、扱いが面倒なうえ、訳文と訳文のつながりを滑らかにするのが難しくなることがあります。そのような場合は、上のようにロケールごとにビュー全体を切り替える機能を使うとよいでしょう。ただし、後でビューの訳文の一部を変更した場合、同じ変更をビュー内の他の訳文にも手動で反映しなければならなくなる点にご注意ください。

3.7 ロケールファイルの編成

I18nライブラリにデフォルトで同梱されるSimpleStoreを使う場合、辞書は平文テキストファイルとしてディスク上に保存されます。アプリケーションで使われるすべての訳文をロケールごとに1つのファイルに保存すると、サイズが大きくなったときに管理が困難になる可能性があります。このため、訳文ファイルを階層化してわかりやすく保存できるようになっています。

たとえば、config/localesディレクトリ以下を以下のように編成できます。

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

こうすることで、モデル名とモデル属性名をビュー内部のテキストから分離し、そして日付・時刻フォーマットなどすべてをデフォルトから分離できます。i18nライブラリ用の他のストアでは、別の方法で分離しているものもあります。

Railsのデフォルトのロケール読み込みメカニズムでは、ここで使っているようなネストした辞書に含まれるロケールファイルを読み込みません。そのため、Railsでこれらが読み込まれるようにするためには、以下のように明示的に指定する必要があります。

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

4 I18n API機能の概要

ここまででi18nライブラリに対する理解がかなり進み、基本的なRailsアプリケーションの国際化で必要となる要素についてはひととおり学べたはずです。ここから先の章では、各機能の詳細について説明します。

以後の章では、I18n.translateメソッドとtranslateビューヘルパーメソッドの両方を例にとって説明します(このビューヘルパーメソッドが提供する追加機能についても解説します)。

以下の機能について説明します。

  • 訳文の参照
  • データを訳文で式展開する(interpolation)
  • 訳文の複数形化
  • 安全なHTML変換(ビューヘルパーメソッドのみ)
  • 日付、数値、通貨などのローカライズ

4.1 訳文の参照

4.1.1 基本的な参照、スコープ、ネストしたキー

訳文を参照するキーには、シンボルと文字列のどちらでも使えます。したがって、以下の2つの呼び出しは等価です。

I18n.t :message
I18n.t 'message'

translateメソッドには:scopeオプションも渡せます。このオプションには、「名前空間」を指定するための追加キーを1つ以上含めることも、訳文キーのスコープを含めることもできます。

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

上のコードでは、Active Recordエラーメッセージの:record_invalidメッセージを参照しています。

さらに、キーとスコープにはドット区切りのキーも指定できます。

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

したがって、以下の4つの呼び出しはすべて等価です。

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

:defaultオプションが与えられると、訳文が見つからない場合にここで指定した値が返されます。

I18n.t :missing, default: '【訳文なし】'
# => '【訳文なし】'

:defaultオプションに与えられる値がシンボルの場合、キーとして使われ、訳文に置き換えられます。複数の値をデフォルトとして指定できます。複数の場合、最初に返された値が返されます。

例: 以下では最初に:missingというキーを訳文に置き換えようとし、続いて:also_missingというキーを置き換えようとします。ここではどちらからも結果を得られないので、「【訳文なし】」という文字列が返されます。

I18n.t :missing, default: [:also_missing,  '【訳文なし】']
# =>  '【訳文なし】'
4.1.3 バルク参照と名前空間参照

キーの配列を渡すことで、複数の訳文を一度に参照できます。

I18n.t [:odd, :even], scope: 'errors.messages'
# => ["奇数が必要です", "偶数が必要です"]

キーは、グループ化された訳文のハッシュに翻訳できます(ハッシュはネストする可能性があります)。たとえば以下のコードでは、「すべての」Active Recordエラーメッセージをハッシュとして受け取れます。

I18n.t 'errors.messages'
# => {:inclusion=>"がリストに含まれていません", :exclusion=> ... }

多数の訳文を持つハッシュで式展開(interpolation)を行いたい場合は、パラメータにdeep_interpolation: trueを渡す必要があります。以下の辞書があるとします。

en:
  welcome:
    title: "Welcome!"
    content: "Welcome to the %{app_name}"

この設定を行わない場合、ネストした式展開は以下のように無視されます。

I18n.t 'welcome', app_name: 'book store'
# => {:title=>"Welcome!", :content=>"Welcome to the %{app_name}"}

I18n.t 'welcome', deep_interpolation: true, app_name: 'book store'
# => {:title=>"Welcome!", :content=>"Welcome to the book store"}
4.1.4 遅延探索

Railsには、 ロケールを「ビュー」内部で参照するときに便利な方法が実装されています。以下のような辞書があるとします。

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

以下のように、app/views/books/index.html.erbビューテンプレート内部books.index.title値にアクセスできます。ドットが使われていることにご注目ください。

<%= t '.title' %>

パーシャルによる自動訳文スコープは、translateビューヘルパーメソッドでのみ使えます。

遅延探索はコントローラでも使えます。

en:
  books:
    create:
      success: Book created!

上は、たとえば次のようにflashメッセージを設定するのに便利です。

class BooksController < ApplicationController
  def create
    # ...
    redirect_to books_url, notice: t('.success')
  end
end

4.2 複数形化

英語を含む多くの言語では、ある名詞には単数形が1種類のみ、複数形も一種類のみがあります(例: "1 message"と"2 messages")。その他の言語(アラビア語日本語ロシア語 など多数)では文法がさまざまに異なっており、複数形の数が英語より多いものもあれば少ないものもあります。これらに対応するため、I18n APIでも柔軟性の高い複数形化機能を備えています。

:countという式展開変数には特殊な役割が与えられており、通常の訳文への式展開に使われるほかに、複数形化バックエンドによって定義される複数形化ルールに沿って適切な複数形を選択するのにも使われます。デフォルトでは、英語の複数形化ルールのみが適用されます。

I18n.backend.store_translations :en, inbox: {
  zero: 'no messages', # オプション
  one: 'one message',
  other: '%{count} messages'
}
I18n.translate :inbox, count: 2
# => '2 messages'

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

I18n.translate :inbox, count: 0
# => 'no messages'

ロケールが:enの場合の複数形化アルゴリズムは単純です。

lookup_key = :zero if count == 0 && entry.has_key?(:zero)
lookup_key ||= count == 1 ? :one : :other
entry[lookup_key]

つまり、ここでは:oneと表記されている訳語が単数形と見なされ、それ以外はすべて複数形と見なされます(ゼロも複数形と見なされます)。countがゼロで、かつ:zeroエントリが存在する場合は、:otherではなく:zeroエントリが用いられます。

キーの探索で、複数形のために適切なハッシュが返されない場合は、I18n::InvalidPluralizationData例外が発生します。

4.2.1 ロケール固有のルール

I18n gemにはPluralizationバックエンドが用意されており、これを用いてロケール固有のルールを有効にできます。これをSimpleというバックエンドにincludeし、複数形化のためのアルゴリズムをローカライズしたものをi18n.plural.ruleという形で訳文ストアに保存します。

I18n::Backend::Simple.include(I18n::Backend::Pluralization)
I18n.backend.store_translations :pt, i18n: { plural: { rule: lambda { |n| [0, 1].include?(n) ? :one : :other } } }
I18n.backend.store_translations :pt, apples: { one: 'one or none', other: 'more than one' }

I18n.t :apples, count: 0, locale: :pt
# => 'one or none'

あるいは、rails-i18nという別のgemを用いて、ロケール固有の複数形化ルールの完全なセットを提供する方法もあります。

4.3 ロケールの設定と受け渡し

ロケールは、擬似グローバルな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のデフォルトはI18n.default_localeであり、デフォルトはenです。デフォルトのロケールは以下のように設定できます。

I18n.default_locale = :de

4.4 安全なHTML変換

キー名をhtmlにするか、キー名の末尾に_htmlを追加すると「HTML safe」とマークされます。これらのキーをビューで使うと、その部分の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>

ただし式展開(interpolation)は必要に応じてエスケープされます。以下のyamlがあるとします。

en:
  welcome_html: "<b>Welcome %{username}!</b>"

ユーザーが設定したこのusernameは、安全に渡せます。

<%# これはsafeであり、必要に応じてエスケープされる %>
<%= t('welcome_html', username: @current_user.username) %>

逆に、安全(safe)とマークされた文字列はエスケープされずに「そのまま」式展開されますのでご注意ください。

安全なHTML変換への自動変換は、translateビューヘルパーメソッドでのみ利用可能です。

i18n demo html safe

4.5 Active Recordモデルで翻訳を行なう

Model.model_name.humanメソッドとModel.human_attribute_name(attribute)メソッドを使うことで、モデル名と属性名を透過的に参照できるようになります。

たとえば以下のような訳文があるとします。

en:
  activerecord:
    models:
      user: Customer
    attributes:
      user:
        login: "Handle"
      # Userの"login"属性は"Handle"という語に翻訳される

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

以下のように、モデル名を複数形にしたものを訳文に加えることもできます。

en:
  activerecord:
    models:
      user:
        one: Customer
        other: Customers

これにより、User.model_name.human(count: 2)は複数形の"Customers"を返します。count: 1またはparamsなしの場合、単数形の"Customer"が返されます。

指定のモデル内のネストした属性にアクセスする必要が生じた場合は、訳文ファイルのモデルレベルでそれらをmodel/属性の下でネストすべきです。

en:
  activerecord:
    attributes:
      user/role:
        admin: "Admin"
        contributor: "Contributor"

上はUser.human_attribute_name("role.admin")で"Admin"を返します。

ActiveModelincludeするクラスを使っており、かつActiveRecord::Baseを継承しない場合は、上述のキーパスのactiverecordactivemodelに置き換えてください。

4.5.1 エラーメッセージのスコープ

Active Recordのバリデーションエラーメッセージも、I18nで簡単に訳文に置き換えられます。Active Recordは、メッセージの訳文を置ける名前空間をいくつか提供しています。名前空間が複数あるのは、モデル・属性・バリデーションごとに異なるメッセージと訳文を提供できるようにするためです。また、単一テーブル継承(STI)も透過的に扱われます。

このしくみは、必要に応じて最適なメッセージを柔軟に選択できる強力な手段となります。

以下のUserモデルでは、name属性でバリデーションが行われています。

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

この場合、エラーメッセージのキーは:blankになります。Active Recordは名前空間からこのキーを参照します。

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 Recordは以下の順にメッセージを探索します。

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

以上のように、モデル継承チェインのさまざまな場所や、属性・モデル・デフォルトスコープで使われている多数のエラーメッセージに対して、専用の訳文を提供できます。

4.5.2 エラーメッセージ内での式展開

翻訳されたモデル名・属性名・値は、それぞれmodelattributevalueという名前でいつでも式展開に使えます。

したがって、たとえば"cannot be blank"というデフォルトのエラーメッセージの代わりに"Please fill in your %{attribute}"のように属性名を展開できます。

  • 以下の表で式展開の列にcountが記載されている行の項目では、countの値に応じて複数形化を行えます。
バリデーション 利用可能なオプション メッセージ 式展開
confirmation - :confirmation attribute
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 -
non-optional association - :required -
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 :other_than :other_than count
numericality :only_integer :not_an_integer -
numericality :in :in count
numericality :odd :odd -
numericality :even :even -

4.6 Action Mailerメールの件名を訳文に置き換える

mailメソッドに件名が渡されなかった場合、Action Mailerは既存の訳文の利用を試みます。<メーラーのスコープ>.<アクション名>.subjectというパターンでキーが構築されます。

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

訳文にパラメータを渡したい場合は、メーラー内でdefault_i18n_subjectメソッドを使います。

# user_mailer.rb
class UserMailer < ActionMailer::Base
  def welcome(user)
    mail(to: user.email, subject: default_i18n_subject(user: user.name))
  end
end
en:
  user_mailer:
    welcome:
      subject: "%{user}, welcome to Rails Guides!"

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

Railsでは、固定された文字列の他に、文字列のフォーマットやその他のフォーマット情報をいくつかのヘルパーで使っています。これらについて簡単に説明します。

4.7.1 Action Viewヘルパーメソッド
  • distance_of_time_in_words: 得られた結果を訳文に置き換えて複数形化し、秒・分・時などの数値を式展開します。訳文置き換えについて詳しくはdatetime.distance_in_wordsを参照してください。

  • datetime_selectselect_month: 月を月名に置き換え、生成されたselectタグに展開します。訳文置き換えについて詳しくはdate.month_namesを参照してください。datetime_selectは、date.orderの順序オプションも探します(明示的にオプションを渡さなかった場合)。すべての日付選択用ヘルパーは、datetime.promptsスコープに訳文がある場合は日付オプションを訳文に置き換えてからユーザーに表示します。

  • number_to_currencynumber_with_precisionnumber_to_percentagenumber_with_delimiter、およびnumber_to_human_sizeヘルパー: numberスコープに置かれている数値フォーマット設定を使います。

4.7.2 Active Modelのメソッド
  • model_name.humanhuman_attribute_name: activerecord.modelsスコープにモデル名と属性名の訳語があればそれを使います。これらのメソッドは、前述の「エラーメッセージのスコープ」で説明されているとおり、継承されたクラス名(単一テーブル継承で使う場合など)の訳語もサポートします。

  • ActiveModel::Errors#generate_message: model_name.humanhuman_attribute_name(上記参照)を使います。generate_messageはActive Modelのバリデーションで使われますが、手動で利用することもできます。このメソッドは、エラーメッセージの訳文への置換と、前述の「エラーメッセージのスコープ」で説明されている、継承されたクラス名の訳文への置き換えもサポートします。

  • ActiveModel::Errors#full_messages: エラーメッセージの冒頭に属性名を追加します。このとき、errors.formatで参照される区切り文字を使います(デフォルト: "%{attribute} %{message}")。デフォルトのフォーマットをカスタマイズするには、アプリのlocaleファイルをオーバーライドします。モデルごと、または属性ごとにフォーマットをカスタマイズする方法について詳しくは、config.active_model.i18n_customize_full_messageを参照してください。

4.7.3 Active Supportのメソッド
  • Array#to_sentence: support.arrayスコープで与えられたフォーマット設定を使います。

5 独自の訳文を保存する方法

Active Supportにデフォルトで装備されている「シンプルな」バックエンドを使っている場合、純粋なRubyファイル形式およびYAMLフォーマット2が使えます。

たとえば、訳文を提供するRubyハッシュは以下のような感じになります。

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

上と同等のYAMLファイルは以下のような感じになります。

pt:
  foo:
    bar: baz

どちらの場合も、トップレベルに置かれているのはロケール名です。:fooは名前空間のキー、:barは"baz"という訳文のキーです。

以下は、Active Supportで「実際に」使うen.yml訳文YAMLファイルの例です。

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

上の設定により、以下の4つのコードはいずれも: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で保存することをおすすめします。ただし、特殊な日付フォーマットを使う場合などは、ロケールデータの一部にlambdaを保存するためにRubyファイル形式を選ぶこともできます。

6 I18n設定をカスタマイズする

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

Active Supportで利用できる組み込みの「シンプル」バックエンドは、さまざまな理由により、Ruby on Railsでは「正常に動作する可能性の高い、最も単純な動作にのみ対応します」3。つまり、「シンプル」バックエンドで動作が保証されているのは、英語および英語に極めて近い言語ぐらいしかないということです。同様に、「シンプル」バックエンドは訳文の読み出しのみが可能であり、訳文を動的に任意のフォーマットで保存することはできません。

もちろん、この制限を突破できないということではありません。Ruby I18n gemを利用して「シンプル」バックエンド実装をより適切なものに差し替えることは可能です。これを行うには、I18n.backend=セッターにバックエンドのインスタンスを1つ渡します。

たとえば「シンプル」バックエンドを、複数のバックエンドをチェインできる「チェイン」バックエンドに置き換えることが考えられます。これは、基本的には「シンプル」バックエンドに保存された標準的な訳文を使いたいが、アプリケーションのカスタム訳文はデータベースなどの別のバックエンドに保存しておきたい、というような場合に便利です。

「チェイン」バックエンドを使うと、通常はActive Recordのバックエンドが使われ、フォールバック先(デフォルト)が「シンプル」バックエンドになります。

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

6.2 標準以外の例外ハンドラを使う

I18n APIでは以下の例外が定義されています。これらの例外は、予想外の条件が発生した場合にバックエンドによって発生します。

MissingTranslationData       # リクエストされたキーに対応する訳文が見つからない
InvalidLocale                # I18n.localeに設定されたロケールが無効 (nilなど)
InvalidPluralizationData     # countオプションが渡されたが訳文データが複数形化に対応していない
MissingInterpolationArgument # 訳文側で必要となる式展開用の引数が渡されなかった
ReservedInterpolationKey     # 訳文に含まれる式展開の変数名に予約済みの変数名が使われている(scopeやdefaultなどのいずれか1つ)
UnknownFileType              # I18n.load_pathに追加されたファイルの種類をバックエンドが判定できない

I18n APIはバックエンドでスローされた例外をすべてキャッチし、default_exception_handlerメソッドに渡します。このメソッドはすべての例外を再びraiseしますが、MissingTranslationDataのみ再raiseを行いません。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例外のみを再raiseし、それ以外のすべての入力をデフォルトの例外ハンドラに渡します。

ただし、I18n::Backend::Pluralizationを使っている場合、このハンドラはI18n::MissingTranslationData: translation missing: en.i18n.plural.rule例外もraiseします。通常この例外は無視され、英語ロケールのデフォルトの複数形化ルールにフォールバックします。この動作を回避するには、以下のように訳文キーを追加でチェックします。

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

デフォルトの動作があまり望ましくないもう1つの例として、RailsのTranslationHelperがあります。これは#tメソッド(および#translateメソッド)を提供します。このコンテキストでMissingTranslationData例外が発生すると、このヘルパーメソッドはCSSクラスtranslation_missingのspanタグでメッセージをラップします。

これを行なうために、このヘルパーは:raiseオプションの設定にかかわらずI18n#translateで強制的に例外を発生させます。

I18n.t :foo, raise: true # バックエンドの例外を常に再raiseする

7 モデルのコンテンツを翻訳する

本ガイドに記載されているI18n APIでは、主に「UI文字列」の翻訳を想定しています。モデルのコンテンツ(ブログ記事など)を翻訳する方法をお探しの場合は、別のソリューションを検討する必要があります。

そうした目的に使える以下のようなさまざまなgemがあります。

  • Mobility: 訳文テーブルやJSONカラム(PostgreSQL)など、訳文のさまざまな保存方法を提供します。
  • Traco: Rails 3やRails 4向けの翻訳可能なカラムを提供し、モデルのテーブル自身に保存します。

8 まとめ

ここまでの解説をお読みいただいたことで、Ruby on RailsでサポートされているI18nの概要を把握でき、アプリケーションを翻訳する準備が整うでしょう。

9 Rails I18nへの貢献について

Ruby on Rails 2.2から導入されたI18nサポートは、現在も進化し続けています。I18nプロジェクトは、Ruby on Railsの優れた開発規約に沿って進められています。つまり、機能をいきなりコアに導入するのではなく、最初はプラグインとして進化させ、アプリケーションで実地に使うことで改良を重ね、その後に最も広く一般的に利用可能なベストの機能を組み合わせたものだけを抽出して、Railsのコアに採用します。

ですから、Railsチームはすべての皆様にプラグインなどのライブラリに採り入れられた新しいアイディアや機能をどしどし試していただき、その結果をコミュニティで利用できるようにしていただければと思います(そのときはぜひメーリングリスト) でもお知らせください)。

自分の欲しいロケールや言語がRuby on Railsの訳文データのサンプルリポジトリにない場合、リポジトリをforkし、訳文をそこに追加してからプルリクエストを送信してください。

10 リソース

  • Google group: rails-i18n - I18nプロジェクトのメーリングリストです。
  • GitHub: rails-i18n - I18nプロジェクトのコードリポジトリです。Rails用の訳文は訳文例に多数掲載されています。これらの訳文は大半のアプリケーションで利用できるはずです。
  • GitHub: i18n - I18n gemのコードリポジトリです。

11 作者

12 脚注

1 あるいは、Wikipedia によれば「国際化とは、技術上の実装変更を伴わずに多数の言語や地域への適合を行なうための、ソフトウェアアプリケーションの設計プロセスである。ローカライズとは、ロケール固有のコンポーネントを追加したりテキストを翻訳したりすることによってソフトウェアを特定の言語や地域に適合させるプロセスである。」となっています。

2 他のバックエンドを使う場合、別のフォーマットが利用できる可能性もありますが、別のフォーマットが必須となる可能性もあります。たとえば、GetTextバックエンドはGetTextファイルを読み込めます。

3 理由の1つは、I18n機能を必要としないアプリケーションで不必要な読み込みを強要したくないためです。そのために、私たちチームはI18nを極力シンプルに保ち、あえて英語のみに絞り込んでいます。もう1つの理由は、既存のあらゆる言語で生じるあらゆる問題を一度に解決できるような万能のソリューションを実装するのは無理だからです。結局私たちのようなI18n開発者にできることといえば、実装全体をいつでも簡単に差し替えられるようにすることぐらいしかありません。I18nを差し替え可能にすることで、実験や拡張がずっとやりやすくなるというメリットも得られます。

フィードバックについて

Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。

原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨

本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。

Railsガイド運営チーム (@RailsGuidesJP)

支援・協賛

Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。

  1. Star
  2. このエントリーをはてなブックマークに追加