ソフトウェア/rails/国際化
最新の Rails4 の国際化事情†
昔は非常に苦労しましたが > ソフトウェア/rails/言語ネゴシエーション
最近はかなりいろいろ便利になっているようですね。
ページ構成†
今回、ページ構成は以下のようにしました。
xxxx というページを以下の3つのアドレスで参照可能にします。
- http://www.example.com/xxxx
- http ヘッダーの Accept-Language を参照してユーザーに合わせた言語で表示する
- http://www.example.com/ja/xxxx
- 日本語で表示する
- http://www.example.com/en/xxxx
- 英語で表示する
routes の指定†
conf/routes.rb
LANG:ruby # coding: utf-8 MyApp::Application.routes.draw do # '/' は MyDefaultController.index へ root 'my_default#index' # 有効なロケールは en と ja locale_regexp= /en|ja/ # '/en', '/ja' は MyDefaultController.index へ get ':locale' => 'my_default#index', constraints: { locale: locale_regexp } # 省略可能な locale スコープを用意する scope "(:locale)", locale: locale_regexp do # some_resources には、'/some_resources/1', '/en/some_resources/1', # '/ja/some_resources/1' どれでもアクセス可能 resources :some_resources # 各種コントローラへマップする # これらも /, /en/, /ja/ のどれでもアクセスできる get ':controller(/:action(/:id))' end end
これで、例えば
- /some_resources/1
- /en/some_resources/1
- /ja/some_resources/1
すべてが SomeResourcesController にマップされます。
routes.rb で Redirect を使ってはいけない†
routes.rb で Redirect してしまうと、下記の default_url_options が反映されないために 望み通りの結果が得られません。
そこで一旦何らかの Controller の method に受けてから、 Controller 内で redirect_to することでこの問題を回避します。
routes.rb で
LANG:ruby get "cont01/some" => Redirect("cont01#other")
とする代わりに、
LANG:ruby get "cont01/some" => "cont01#some"
として、一旦 Cont01Controller.some に受けておき、
LANG:ruby class Cont01Controller < ApplicationController def some redirect_to action: :other end def other ... end end
のように Controller 内で redirect_to を呼びます。
I18n.locale の設定†
上記のルート設定を用いると、 /en/xxxx や /ja/xxxx でアクセスした場合には param[:locale] に 'en' や 'ja' が入りますので、その値を I18n.locale に代入することで、 正しいロケールを使って出力できるようになります。
/xxxx として、ロケールを直接指定しなかった場合 param[:locale] は nil になるので、かわりにブラウザから渡された Accept-Language ヘッダー情報にからロケールを抜き出して用いることで、 ユーザーの好みに合わせたロケールでページを表示できます。
Accept-Language ヘッダーの値はブラウザの言語設定の結果が反映されます。 つまり、ブラウザの設定で「日本語」が優先されている場合には 日本語で、「英語」が優先されている場合には英語で表示できることになります。
Accept-Language ヘッダー情報も指定されなかった場合には、 I18n.default_locale(デフォルト値は :en)を使います。
I18n.default_locale の値は config/application.rb あるいは config/initializers/i18n.rb などで自分で指定してもよいです。
app/controllers/application_controller.rb
LANG:ruby # coding: utf-8 class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception before_filter :set_locale private ###################### ロケール関連 def set_locale I18n.locale = params[:locale] || default_locale end def default_locale extract_locale_from_accept_language_header || I18n.default_locale end def extract_locale_from_accept_language_header ( request.env['HTTP_ACCEPT_LANGUAGE'] || '' ).scan(/^[a-z]{2}/).first end # 現在のロケールが自動判別ロケールと異なる場合、、 # リンク先に現在のロケールを追加する # 引数は必要???詳しくは次項を参照 def default_url_options(options = {}) if I18n.locale.to_sym == default_locale.to_sym super() else # キーは必ず Symbol で渡すべし # http://d.hatena.ne.jp/ux00ff/20120124/1327373780 { :locale => I18n.locale } end end end
default_url_options の指定†
/en/xxxx というページに /yyyy というページへのリンクを表示するとき、 その飛び先を /en/yyyy としておくと、訪問者は英語のページを読み進めることができます。 これが /xxxx へのリンクになっていると、急に日本語ページが表示されてしまうかもしれません。
そこで、上のコードのように default_url_options を定義することにより、 現在のロケールが自動判別されたロケールと異なる場合に、 ページ内に表示するリンクすべてに現在のロケールを追加します。
これで url からコントローラへ、コントローラから url へ、 locale を正しく保った上で両方向のマッピングができるようになりました。
各言語用の view を作成†
- app/views/my_default/index.en.html
- app/views/my_default/index.ja.html
- app/views/my_default/_form.en.html
- app/views/my_default/_form.ja.html
のように、日本語版と、英語版の2つのファイルを用意しておくと、 I18n.locale の指定に従って、適切な方が使われます。
render partial: "form" などで読み込む _form などのサブ view も、 上記のように en と ja の2つを用意しておけば適切な方が読み込まれます。
わざわざ2つ用意する必要のないファイルは、これまで通り
- app/view/my_default/the_page.html
としておけば、I18n.locale が en でも ja でも、このファイルが使われます。
各種メッセージの翻訳を作成†
I18n には ruby のコードから表示するメッセージの翻訳を行う機能があります。
この設定は通常、config/locales/ 以下に .yml ファイルとして置きます。
まず、config/locales/*/ にある *.yml ファイルをロケール設定ファイルとして システムへ読み込むため、以下の設定を行います。
config/application.rb
LANG:ruby config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s] config.i18n.default_locale = :ja # 上記を参照。必要に応じて設定する。
そして、
- config/locales/defaults/en.yml
- config/locales/defaults/ja.yml
- config/locales/models/some_model/en.yml
- config/locales/models/some_model/ja.yml
- config/locales/views/my_default/en.yml
- config/locales/views/my_default/ja.yml
などとして、各種訳語ファイルを作成します。
訳語ファイルの中身は yaml 形式で、 以下のようにキーと実際のメッセージのペアをたくさん格納した形になります。
LANG:yaml en: some_message: "This is an English message." ja: some_message: "これは日本語のメッセージです。"
上記設定があれば、view 内で
<%=t :some_message %>
とすることで、"This is an English message." または "これは日本語のメッセージです。" のどちらかが、適切に選ばれて表示されます。
詳しくはこちらが分かりやすかったです。
http://morizyun.github.io/blog/i18n-english-rails-ruby-many-languages/
http://tkyk.name/blog/2011/06/22/Rails-Ruby-rails3_i18n/ によれば、
新たにファイルを追加した場合はサーバを再起動しなければ有効にならないそうです。
設定ファイルの分割について†
上記では、設定ファイルを models, views などに分けていますが、 この分割に機能的な意味はありません。
Rails には、すべてが1つのファイルに書かれたのと同じように読み込まれるので、 ファイルへの分割やファイル名の付け方はあくまで開発者が管理しやすいように 行えばOKです。
ですので、例えば、
config/locales/views/my_default/en.yml
LANG:yaml en: my_default: index: title: "Title of app/views/my_default/index.html.erb" other: title: "Title of app/views/my_default/other.html.erb"
config/locales/models/user/ja.yml
LANG:yaml ja: activerecord: models: user: ユーザー attributes: user: login: "ログインネーム"
config/locales/models/user/en.yml
LANG:yaml en: activerecord: models: user: one: "User" other: "Users" attributes: user: login: "Handle"
などというように、ファイル名と namespace が異なっても何ら問題はありません。
上記の view や model の namespace は rails の作法に則った物ですので、 ファイル名を上記のように付けている限り、namespace とファイル名とは 必然的に一致しないことになります。
I18n.* の便利な機能†
スコープの指定方法†
いろいろな指定法があります。
- 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]
キーに空白や記号を含むような任意の文字列を使う場合には scope を別に指定できる形を使うのが便利そうです。
翻訳が存在しない場合の処理†
- I18n.t :missing, default: 'Not here'
- I18n.t :missing, default: [:also_missing, 'Not here']
文字列の挿入†
LANG:ruby I18n.backend.store_translations :en, thanks: 'Thanks %{name}!' I18n.translate :thanks, name: 'Jeremy' # => 'Thanks Jeremy!'
複数形への対応†
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'
Rails メッセージの翻訳†
既製の翻訳ファイル†
とりあえずここから必要なロケールのものを持ってきて config/locales/defaults などに置いておけば、 Rails からのメッセージの多くが自動で翻訳されます。
https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale
view ファイル名に依存したスコープを使った翻訳†
config/locales/views/books/en.yml
LANG:yaml es: books: index: title: "Title of app/views/books/index.html.erb"
の設定後に、app/views/books/index.html.erb にて . を前置して
<%= t '.title' %>
とすることで、"Title of app/views/books/index.html.erb" を取り出せます。
スコープは view ファイル名から作られるようです。 つまり、例えば app/views/some_resources/new.rb から render partial: "form" として app/views/some_resources/_form.rb を取り込んだ場合、 "some_resources.new.XXX" ではなく、"some_resources.form.XXX" が参照されます。
部分 view に付く、先頭の "_" は取り除かれるようです。
この機能は I18n.t には無く、view helper の機能です。
この機能を使う場合、翻訳が見付からない場合にもエラーにはならず、 先頭の . 以下の文字列が Humanize されて出力されます。 次のようにオプションに raise: true を付けることで、翻訳が見付からない場合にエラーにできます。 これにより View Helper が期待する正確なキー名を表示させることができます。
LANG:html <%= t(:some_key, raise: true) %>
html メッセージを表示†
翻訳に html タグを含む場合、普通に <%=t :message_id %> で表示したのでは タグがエスケープされてしまいます。ただし、メッセージキーを _html や .html で終わるように つけることで、このエスケープを抑止できます。
config/locales/en.yml
LANG:yaml en: welcome: <b>welcome!</b> hello_html: <b>hello!</b> title: html: <b>title!</b>
app/views/home/index.html.erb
LANG:html <div><%= t('welcome') %></div> # エスケープされる → <b>welcome!</b> <div><%= raw t('welcome') %></div> # エスケープされない <div><%= t('hello_html') %></div> # エスケープされない <div><%= t('title.html') %></div> # エスケープされない
次のリンク先にあるように、メッセージ全体を raw に渡す場合と異なり、
_html や .html に変数値を埋め込んだ場合、変数の中身はちゃんとエスケープしてくれるので、
うかつに raw を呼ぶのではなく、_html や .html の機能をきちんと活用するようにしましょう。
http://qiita.com/tnj/items/c9e893124c1b000b5355
この機能は I18n.t には無く、view helper の機能です。
ActiveRecord モデルの翻訳†
config/locales/models/user/ja.yml
LANG:yaml ja: activerecord: models: user: ユーザー attributes: user: login: "ログインネーム"
config/locales/models/user/en.yml
LANG:yaml en: activerecord: models: user: one: "User" other: "Users" attributes: user: login: "Handle"
この設定でかなりの部分 Rails が自動で面倒を見てくれますが、 view などで自分で表示する場合には、次のように参照します。
- User.model_name.human
- User.human_attribute_name("login")
ActiveModel モデルの翻訳†
http://tkyk.name/blog/2011/06/22/Rails-Ruby-rails3_i18n/
LANG:yaml ja: activemodel: models: my_form: "モデル名" attributes: my_form attr_name: "属性名"
のように、activerecord ではなく activemodel 配下になるそうです。
また、i18n_scope メソッドをオーバーライドすれば、 任意のスコープに翻訳を置くことができるようです。
バリデーションメッセージの翻訳†
LANG:ruby class User < ActiveRecord::Base validates :name, presence: true end
というバリデーションが失敗した場合のメッセージ ID は :blank なのだそうです。
http://guides.rubyonrails.org/i18n.html#translations-for-active-record-models
で、
activerecord.errors.models.user.attributes.name.blank activerecord.errors.models.user.blank activerecord.errors.messages.blank errors.attributes.name.blank errors.messages.blank
という ID がこの順に検索されます。
自分でメッセージを指定する時も、文字列ではなく Symbol を与えれば上記箇所が参照されるので、 例えば次のようなことができます。
ja.yml
LANG:yaml ja: activerecord: errors: models: my_model: 'There is a critical error!': > 致命的なエラーが発生しました
app/models/my_model.rb
LANG:ruby errors.add(:some_column, :'There is a critical error!')
ActiveModel::Errors#full_messages†
en: errors: format: "%{attribute} %{message}"
となっているのでこれを変えます。
error_messages_for で表示されるエラーメッセージ†
LANG:yaml 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:"
view helper の便利機能†
- distance_of_time_in_words
- distance_of_time_in_words_to_now
- time_ago_in_words
- number_to_currency
- number_with_precision
- number_to_percentage
- number_with_delimiter
- number_to_human_size
などで翻訳が使われます。
Action Mailer†
LANG:ruby # user_mailer.rb class UserMailer < ActionMailer::Base def welcome(user) #... end end
に対して、
en: user_mailer: welcome: subject: "Welcome to Rails Guides!"
が使われます。
他にも†
いろいろありますね。
http://guides.rubyonrails.org/i18n.html
Java Script の多言語化†
http://morizyun.github.io/blog/i18n-english-rails-ruby-many-languages/ より、
Gemfile
# javascriptのi18n対応 gem "i18n-js"
初期設定 (config/i18n-js.yml の作成)
LANG:console $ bundle install $ rake i18n:js:setup
app/assets/javascripts/application.js
LANG:javascript //= require i18n //= require i18n/translations
app/views/layouts/application.html.erb
LANG:html <script type="text/javascript"> I18n.defaultLocale = "<%= I18n.default_locale %>"; I18n.locale = "<%= I18n.locale %>"; </script>
config/i18n-js.yml
ja: hello: "こんにちは"
使用したい javascript 中にて、
LANG:javascript I18n.t("hello");
SEO 視点での国際化†
http://morizyun.github.io/blog/i18n-english-rails-ruby-many-languages/ より、
Google の記事が紹介されていました:
https://support.google.com/webmasters/answer/182192
要検討†
- http://qiita.com/awakia/items/cab830238bbfaa924f02
- config.i18n.available_locales = [:ja, :en] というのがある?
- 「YAMLは同じキーがあった場合は内容を上書きするので、00などをつけて順番を選べるようにしておくと都合が良い。 辞書順に上から読み込まれて行くので、内容がコンフリクトした場合は数値が大きいほうの設定が使われる様になる。」
- フォルダーに分けている場合の読み込み順はどうなるだろう?