Rails国際化(I18n) API

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

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

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

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

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

このガイドの内容:

RubyのI18nフレームワークでは、Railsアプリケーションの国際化/ローカライズに必要な手段がすべて提供されています。もちろん、さまざまなプラグインや拡張機能を導入してそれ以外の機能を追加しても構いません。詳細についてはRubyのI18n Wikiを参照してください(訳注: 翻訳時点ではこのWebサイトにアクセスできませんでした)。

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

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

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

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

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

以上のことから、RubyのI18nのgemは2つに分割されています。

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

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

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

1.2 パブリックI18n API

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

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

上のメソッドにはそれぞれ#tと#lという別名メソッドがあるので、以下のように簡潔に書くことができます。

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

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

load_path         # カスタム訳文ファイルの場所を示す
locale            # 現在のロケールの取得と設定
default_locale    # デフォルトロケールの取得と設定
exception_handler # 異なるexception_handlerを使用する
backend           # 異なるバックエンドを使用する

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

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

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

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

Railsは、 設定より規則 の哲学に従い、十分に吟味されたデフォルト設定をアプリケーションに対して行います。設定を変更したければ、簡単に上書きできます。

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

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

en:
  hello: "Hello world"

上の例は、「:enというロケールでは、 hello というキーは Hello world という文字列に対応付けられる」という意味です。Rails内部の文字列はすべてこのように国際化されています。具体例については、Active Modelの検証(バリデーション)メッセージ一覧(activemodel/lib/active_model/locale/en.yml ファイル) や日付時刻フォーマット一覧 (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辞書に追加すればよいのです。RailsにはGlobalize3など多数のRails I18nプラグインがあり、実装に役立てることができるでしょう。

訳文読み込みパス (I18n.load_path) は、単に訳文ファイルへのパスをRubyの配列にしたものであり、ここで指定された訳文はアプリケーションで自動的に読み込まれて利用できるようになります。指定するディレクトリやファイル名の命名スキームは、わかりやすいものを自由に使用できます。

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

デフォルトの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ライブラリに指示するには、アプリケーション内の読み込みパスを指定します。パスはアプリケーション内のどこでも構いませんが、訳文への参照が実際に発生する前に読み込まれるようにだけはしておく必要があります。デフォルトのロケールも変更可能です。イニシャライザに以下を記述するのが最もシンプルです。

# 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を設定します。ここで指定したロケールはリクエスト全体を通じて保たれます。

しかし、1つのアプリケーションで 2つ以上のロケールをサポートしたい ことも当然あるでしょう。そのような場合、リクエストとリクエストの間でロケールを設定し、受け渡す必要があります。

このガイドを読んでいる方の多くは、ロケールを セッションcookie に保存すれば済むと考えていることでしょう。しかし これは行わないでください 。ロケールはセッションやcookieに依存しない透過的なものでなければなりませんし、URLの一部でなければなりません。Webを使用するユーザーはWebについてさまざまなことを前提としていますので、それに逆らうような設計を行うべきではありません。あなたがあるURLを知人に送ったとすると、知人がそのURLで参照できる内容はあなたが見たものとまったく同じページ、同じ内容でなければなりません。言い方を替えれば、アプリケーションをRESTfulにするということです。

設定部分 は簡単です。以下のような方法で、ApplicationControllerbefore_actionでロケールを設定できます。

before_action :set_locale

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

上の設定を行った場合、ロケールはURLクエリパラメータの一部として、http://example.com/books?locale=ptのような形式で渡す必要があります。(これはGoogleなどが行っているアプローチです) つまり、http://localhost:3000?locale=ptとするとポルトガル語のローカライズが読み込まれ、http://localhost:3000?locale=deとするとドイツ語のローカライズが読み込まれるといった具合です。ロケール情報を手動でURLに配置してページを再読み込みしたい場合は、ここから アプリケーションを国際化する の節まで読み飛ばして構いません。

もちろんほとんどの開発者は、アプリケーション全体でいちいちロケールをURLで指定して回るようなことはしたくないでしょうし、URLの形式をもっと違うものにしたいと思うこともあるでしょう (通常のhttp://example.com/pt/booksとするかhttp://example.com/books/enとするか、など)。他にどんなオプションがあるか見てみましょう。

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

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

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

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

before_action :set_locale

def set_locale
  I18n.locale = extract_locale_from_tld || I18n.default_locale
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['REQUEST_URI']}")

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

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

2.5 URL Paramsを元にロケールを設定する

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

このアプローチは、ドメイン名からロケールを取得するのとほぼ同じメリットを得ることができます。これによりアプリケーションはWorld Wide Webに則ってRESTfulを保つことができるからです。ただし、少しだけ実装を追加する必要があります。

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(options = {})
  { locale: I18n.locale }.merge options
end

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

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

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

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

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

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

# 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の直前に置くこともできます)

ルーティングに対してこのような形でシンプルに動作する、以下の2つのプラグインをチェックしてみてもよいかもしれません。Sven Fuchs作 routing_filter およびRaul Murciano作 translate_routes

2.6 クライアント提供情報を元にロケールを設定する

場合によっては、URLではなく、クライアントから送信される情報を元にロケールを設定したいこともあるでしょう。クライアントから送信される情報としては、ブラウザに設定されている優先言語設定、IPから推測される範囲での地理情報の他に、ユーザーがアプリケーションの画面でロケールを選択してプロファイルに保存したものなどがあります。このアプローチは、Webサイトよりも、Webベースのアプリケーションやサービスに向いています。上の囲み記事にある セッションcookie 、RESTfulアーキテクチャに関する記述を参照してください。

2.6.1 Accept-Languageを使用する

クライアントから送信される情報源の1つがAccept-Language HTTPヘッダーです。この情報は ブラウザで設定 されるのが普通ですが、 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 のようなプラグインや、RackミドルウェアのRyan Tomayko作 locale などを使用してもよいでしょう。

2.6.2 GeoIPなどの地域情報データベースを使用する

クライアントから送信される情報からロケールを取得する別の方法として、クライアントIPアドレスを地域にマッピングするデータベースを使用することもできます。GeoIP Lite Countryなどが代表的です。このコードのしくみは、上のコードと非常によく似ています。ユーザーのIPアドレスをこのデータベースに問い合わせ、国・地域・町などに応じた好みのロケールを取得します。

2.6.3 ユーザープロファイル

アプリケーションのロケールを設定 (そして上書き) するための手段をユーザーに提供することもできます。そのためのコードは、これまでのものと基本的にほとんど変わりません。ドロップダウンリストにロケールを表示し、ユーザーがそれを使用してロケールを設定したらデータベースに保存します。その後、そのロケール値をアプリケーションのロケールに設定します。

3 アプリケーションを国際化する

お疲れさまでした。以上でRuby on RailsアプリケーションのI18nサポートの初期化が完了しました。使用すべきロケールも設定され、リクエスト間でロケールを保存する方法も指定されました。いよいよここからが本格的な作業になります。

それでは、アプリケーションの 国際化 (ロケール固有の部分を抽象化する作業など) を行い、続いて ローカライズ (抽象化された箇所に訳文を提供する) を行いましょう。

アプリケーションには以下のようなコードがあるものとします。

# config/routes.rb
Rails.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>

rails i18n demo untranslated

3.1 訳文を追加する

上のコードには、 英語にローカライズされた文字列が2つ あります。このコードを国際化するには、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をキーに持つ訳文が見つからないというエラーメッセージが表示されます。

rails i18n demo translation missing

Railsはt(translate) ヘルパーメソッドを自動的にビューに追加してくれるので、I18n.tとフルスペルで書かなくても済みます。さらに、このヘルパーは訳文が見つからない場合にエラーメッセージを` でラップして表示してくれます。

続いて、辞書ファイルに訳文を追加しましょう (最もローカリゼーションらしい部分でしょう)。

# 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はデフォルトの英語を使用します。アプリケーションの表示は以下のようになります。

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ファイルに移動してみると問題が解決するかもしれません)

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 日付・時刻フォーマットを追加する

お疲れさまでした。今度はビューにタイムスタンプを追加し、 日付・時刻のローカライズ 機能が動作するところもお目にかけましょう。時間のフォーマットをローカライズするには、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 demo localized time to pirate

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

3.4 活用形ルールを設定する

Rails 4.0では、英語以外の言語についても単数形/複数形のような活用形を定義できます。config/initializers/inflections.rbには、複数言語向けの活用形ルールを置くことができます。このイニシャライザには、英語の場合の活用形の追加方法の例が記載されています。他の言語でも同じ要領で活用形ルールを追加できます

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

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

ビューに大量の文章が含まれているような場合、これらを文単位に分解してYAML辞書や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

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

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

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

訳文の管理に使用できるツールのリストについてはRails i18n Wikiを参照してください(訳注: このサイトは翻訳時点ではアクセスできませんでした)。

4 I18n API機能の概要

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

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

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

  • 訳文の参照
  • データを訳文に式展開(interpolate)する
  • 訳文の複数形化
  • 安全な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: :active_record
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: 'Not here'
# => 'Not here'

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

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

I18n.t :missing, default: [:also_missing, 'Not here']
# => 'Not here'
4.1.3 一括参照と名前空間参照

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

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

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

I18n.t 'activerecord.errors.messages'
# => {:inclusion=>"is not included in the list", :exclusion=> ... }
4.1.4 遅延参照(lazy lookup)

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

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

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

<%= t '.title' %>

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

4.2 式展開

訳文を抽象化する際、 変数の訳文内での展開(interpolate) が求められることがよくあります。このため、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種類のみ、複数形も一種類のみがあります (例: "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 (これはThread.currentを使用しており、Time.zoneなどに似ています) に設定したり、#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.5 安全なHTML変換

名前が'html'であるキー、および名前が'_html'で終わるキーは、「HTML safe」とマークされます。これらのキーをビューで使用すると、その部分のHTMLはエスケープされません (訳注: 原文には記載されていませんが、訳文に含まれる式展開%{}については、キー名に'html'が含まれていても式展開の内容はエスケープされており、その分安全が確保されています。なお「HTML safe」フラグそのものは「別途安全確認済みのはず」という意味であり、それに基いて表示の際にエスケープせずに生のままで表示されることになります。別途安全確認が行われていない場合その名とは逆に「安全ではなくなる」可能性があることにご注意ください)。

# 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ビューヘルパーメソッドでのみ利用可能です。

i18n demo html safe

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

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

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

en:
  activerecord:
    models:
      user: Dude
    attributes:
      user:
        login: "Handle"
      # "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またはparamsなしの場合、単数形の"Dude"が返されます。

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

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

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

以下のUserモデルでは、name属性で検証が1つ行われています。

class User < ActiveRecord::Base
  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.6.2 エラーメッセージ内での式展開

翻訳されたモデル名・属性名・値はいつでも式展開で利用することができます。

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

  • 以下の表で式展開の列にcountが記載されている行の項目では、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 -
4.6.3 Active Record error_messages_forヘルパー用メッセージの訳文

Active Recordの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:"

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

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

mailメソッドに件名が渡されなかった場合、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!"

4.8 I18nサポートを提供するその他のビルトインメソッドの概要

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

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

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

  • ActiveModel::Errors#full_messagesは、エラーメッセージの冒頭に属性名を追加します。このとき、errors.formatで参照される区切り文字を使用します。なお、デフォルトでは"%{属性} %{メッセージ}"の形式になります。

4.8.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を利用して、「シンプルな」バックエンド実装をよりふさわしいものに差し替えることができます。たとえば、GlobalizeのStaticバックエンドに差し替えることもできます。

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

さらに、Chainバックエンドを使用して、複数のバックエンドを連鎖させることもできます。これは、基本的には「シンプルな」バックエンドに保存された標準的な訳文を使用したいが、アプリケーションのカスタム訳文はデータベースなどの別のバックエンドに保存しておきたい、というような場合に便利です。たとえば、通常は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例外も生成します。通常この例外は無視され、英語ロケールのデフォルトの複数形化ルールにフォールバックします。この動作を回避するには、以下のように訳文キーを追加でチェックします。

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

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

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

I18n.t :foo, raise: true # always re-raises exceptions from the backend

7 まとめ

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

本ガイドに不足や誤りを見つけた場合は、お手数ですがissue tracker でチケットを切ってください。議論に参加したい、または質問がある場合は私たちのメーリングリストに登録してください。

8 Rails I18nへの貢献について

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

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

欲しいロケールがRuby on Railsの訳文データの例リポジトリにない場合、リポジトリをfork し、訳文をそこに追加いただいた後pull requestを送信してください。

9 リソース

  • rails-i18n.org - rails-i18nプロジェクトのトップページです。wikiにも有用なリソースが多数掲載されています。
  • Google group: rails-i18n - I18nプロジェクトのメーリングリストです。
  • GitHub: rails-i18n - I18nプロジェクトのコードリポジトリです。Rails用の訳文は訳文例 に多数掲載されています。これらの訳文は大半のアプリケーションで利用できるはずです。
  • GitHub: i18n - I18n gemのコードリポジトリです。
  • Lighthouse: rails-i18n - rails-i18nのイシュートラッキングはこちらです。
  • Lighthouse: i18n - i18n gemのイシュートラッキングはこちらです。

10 作者

本ガイドがお役に立ったのであれば、workingwithrails でこれらの作者を推奨いただければ幸いです(訳注: 翻訳時点ではこのサイトは引退したと表示されていました)。

11 脚注

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

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

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