ソフトウェア/rails/国際化

(3621d) 更新


公開メモ

最新の 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>       # エスケープされる → &lt;b&gt;welcome!&lt;/b&gt;
<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");

参照:https://github.com/fnando/i18n-js

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などをつけて順番を選べるようにしておくと都合が良い。 辞書順に上から読み込まれて行くので、内容がコンフリクトした場合は数値が大きいほうの設定が使われる様になる。」
    • フォルダーに分けている場合の読み込み順はどうなるだろう?

コメント





Counter: 11428 (from 2010/06/03), today: 2, yesterday: 1