ソフトウェア/rails/国際化 のバックアップの現在との差分(No.1)

更新


  • 追加された行はこの色です。
  • 削除された行はこの色です。
[[公開メモ]]

#contents

* 最新の Rails4 の国際化事情 [#x101a8e7]

昔は非常に苦労したのだけれど > [[ソフトウェア/rails/言語ネゴシエーション]]~
最近はかなりいろいろ便利になっているみたい。
昔は非常に苦労しましたが > [[ソフトウェア/rails/言語ネゴシエーション]]~
最近はかなりいろいろ便利になっているようですね。

-http://guides.rubyonrails.org/i18n.html
-http://memo.yomukaku.net/entries/LXvSUpT

* ページ構成 [#zff8f713]

今回、ページ構成は以下のようにしました。

xxxx というページを以下の3つのアドレスで参照可能にします。

:http://www.example.com/xxxx| http ヘッダーの Accept-Language を参照してユーザーに合わせた言語で表示する
:http://www.example.com/ja/xxxx| 日本語で表示する
:http://www.example.com/en/xxxx| 英語で表示する

* routes の指定 [#g2a6e39d]

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 を使ってはいけない [#jcaea57f]

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 の設定 [#we580774]

上記のルート設定を用いると、
/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 の指定 [#p7b84a2d]

/en/xxxx というページに /yyyy というページへのリンクを表示するとき、
その飛び先を /en/yyyy としておくと、訪問者は英語のページを読み進めることができます。
これが /xxxx へのリンクになっていると、急に日本語ページが表示されてしまうかもしれません。

そこで、上のコードのように default_url_options を定義することにより、
現在のロケールが自動判別されたロケールと異なる場合に、
ページ内に表示するリンクすべてに現在のロケールを追加します。

これで url からコントローラへ、コントローラから url へ、
locale を正しく保った上で両方向のマッピングができるようになりました。

* 各言語用の view を作成 [#q90bd227]

-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 でも、このファイルが使われます。

* 各種メッセージの翻訳を作成 [#sdd4f0c8]

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/ によれば、~
新たにファイルを追加した場合はサーバを再起動しなければ有効にならないそうです。

** 設定ファイルの分割について [#ffe0a9de]

上記では、設定ファイルを 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.* の便利な機能 [#f32db6fd]

** スコープの指定方法 [#m2f7e931]

いろいろな指定法があります。

-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 を別に指定できる形を使うのが便利そうです。

** 翻訳が存在しない場合の処理 [#j203f419]

-I18n.t :missing, default: 'Not here'
-I18n.t :missing, default: [:also_missing, 'Not here']

*** 文字列の挿入 [#m4b6a61e]

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

*** 複数形への対応 [#n5cb033f]

 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 メッセージの翻訳 [#ic1c6855]

** 既製の翻訳ファイル [#ueeb94e1]

とりあえずここから必要なロケールのものを持ってきて
config/locales/defaults などに置いておけば、
Rails からのメッセージの多くが自動で翻訳されます。

https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale

** view ファイル名に依存したスコープを使った翻訳 [#s27a305e]

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 メッセージを表示 [#x12ca3eb]

翻訳に 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 モデルの翻訳 [#z6c3fc67]

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 モデルの翻訳 [#t34066c7]

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 メソッドをオーバーライドすれば、
任意のスコープに翻訳を置くことができるようです。

** バリデーションメッセージの翻訳 [#nfd184fa]

 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 [#rf3e1667]

 en:
   errors:
     format: "%{attribute} %{message}"

となっているのでこれを変えます。

** error_messages_for で表示されるエラーメッセージ [#yd186671]

 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 の便利機能 [#x2c279ab]

-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 [#i75ef4d3]

 LANG:ruby
 # user_mailer.rb
 class UserMailer < ActionMailer::Base
   def welcome(user)
     #...
   end
 end

に対して、

 en:
   user_mailer:
     welcome:
       subject: "Welcome to Rails Guides!"

が使われます。

** 他にも [#md86f5d3]

いろいろありますね。~
http://guides.rubyonrails.org/i18n.html

やりたいことは、
* Java Script の多言語化 [#l9a89212]

- 日本語、英語のページを作る
::http://my.server.com/xxxx> http ヘッダーの Accept-Language を参照してユーザーに合わせた言語で表示する
::http://my.server.com/ja/xxxx> 日本語で表示する
::http://my.server.com/en/xxxx> 英語で表示する
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 視点での国際化 [#k5fbed46]

http://morizyun.github.io/blog/i18n-english-rails-ruby-many-languages/ より、

Google の記事が紹介されていました:~
https://support.google.com/webmasters/answer/182192

* 要検討 [#k4ce2702]

- http://qiita.com/awakia/items/cab830238bbfaa924f02
-- config.i18n.available_locales = [:ja, :en] というのがある?
-- 「YAMLは同じキーがあった場合は内容を上書きするので、00などをつけて順番を選べるようにしておくと都合が良い。
辞書順に上から読み込まれて行くので、内容がコンフリクトした場合は数値が大きいほうの設定が使われる様になる。」
-- フォルダーに分けている場合の読み込み順はどうなるだろう?

* コメント [#aa1134e0]

#article_kcaptcha


Counter: 11426 (from 2010/06/03), today: 1, yesterday: 0