routesの設定と言語選択の優先順位 の変更点

更新


SIZE(22){COLOR(RED){このページの内容は非常に古いです(Rails 1.x.x)。最新の Rails では洗練された国際化の機構が標準で入っているため、下記は読むだけ無駄な内容になっています。}}

[[ソフトウェア/rails/言語ネゴシエーション]]

#contents

* 多言語化したビューの作成 [#b2db2fc3]

今作ったビューを削除して、日本語版と英語版を作成。

まだ rails に手を入れていないので、そのままではエラーになる。

 LANG:console
 $ rm app/views/test/index.html.erb
 $ echo "Test" > app/views/test/index.html.en.erb
 $ echo "テスト" > app/views/test/index.html.ja.erb
 $ wget -O- http://localhost:3000/test
  エラー 404: Not Found

これを正しく表示できるようにするのが目標なのだが、
その前に rails に言語ファイル選択の優先順位を教える必要がある。

* config/routes.rb の設定 [#w47da434]

多言語化後は、各ページに対して

 http://localhost:3000/test
 http://localhost:3000/test.ja
 http://localhost:3000/test/index
 http://localhost:3000/test/index.ja
 http://localhost:3000/test/index/0
 http://localhost:3000/test/index/0.ja
 http://localhost:3000/test/index/0.html
 http://localhost:3000/test/index/0.html.ja

などの形でアクセスできるようにする。

+ .ja などの言語指定がない場合
-- ブラウザからも指定が無ければサーバー側の優先順位で表示言語を選ぶ
-- ブラウザが優先順位を指定していれば、その順で表示言語を選ぶ
+ .ja などの言語指定があれば、その言語で表示する
+ 一旦言語指定を行ったら、以降は言語指定がない場合にサーバーやブラウザの優先順位を無視して、
直前に行った言語指定と同じ言語で表示する
+ 表示したい言語のテンプレートファイルが無ければ、ある物を使って表示する

3. は、明示的に表示言語を選択できるようにするためのもの。


受け入れ可能な言語のリストは config/routes.rb の先頭で

 ENV['RAILS_ACCEPTABLE_LANGUAGES'] ||= 'ja|en'

として設定する事にする。

サーバー側の優先順位はここでの指定順で決まる物とする。~
これ以外の言語が指定されても無視することにする。

route の設定は、

 map.connect ':controller/:action/:id.:rails_language', 
                     :requirements => { :rails_language => /ja|en/ }

のようになる。/^(ja|en)$/ ではなく /ja|en/ とするのが正しいらしい。

以下がコード。

config/routes.rb
 LANG:ruby(linenumber)
 ENV['RAILS_ACCEPTABLE_LANGUAGES'] ||= 'ja|en'
 
 ActionController::Routing::Routes.draw do |map|
 
   lang_regexp= Regexp.new( ENV['RAILS_ACCEPTABLE_LANGUAGES'] )
 
   map.connect ':controller.:rails_language', :format => 'html', :action => 'index',
                       :requirements => { :rails_language => lang_regexp }
   map.connect ':controller/:action.:rails_language', :format => 'html',
                       :requirements => { :rails_language => lang_regexp }
   map.connect ':controller/:action/:id.:rails_language', :format => 'html',
                       :requirements => { :rails_language => lang_regexp }
   map.connect ':controller/:action/:id', :format => 'html'
 
   map.connect ':controller/:action/:id.:format.:rails_language',
                       :requirements => { :rails_language => lang_regexp }
   map.connect ':controller/:action/:id.:format',
                       :defaults => { :action => 'index', :format => 'html' }
 
 end

** テスト [#s18f0fb5]

 LANG:console
 $ (restart script/server)
 $ jed app/controllers/test_controller.rb
  class TestController < ApplicationController
    def index
      render :text => params[:rails_language].inspect + "\n"
    end
  end
 $ wget -qO- http://localhost:3000/test
  nil
 $ wget -qO- http://localhost:3000/test.ja
  "ja"
 $ wget -O- http://localhost:3000/test.html.ja
  404: Not Found
 $ wget -qO- http://localhost:3000/test/index
  nil
 $ wget -qO- http://localhost:3000/test/index.ja
  "ja"
 $ wget -O- http://localhost:3000/test/index.html.ja
  404: Not Found
 $ wget -qO- http://localhost:3000/test/index/0.html
  nil
 $ wget -qO- http://localhost:3000/test/index/0.html.ja
  "ja"

言語指定を正しく行えている事が分かる。

* 言語が指定されていない時を含めた言語選択の優先順位 [#k5f2df0a]

優先順位は以下のようにする。

+ 指定された言語 (指定されていれば)
+ 前回指定された言語 (もしあれば cookie に保存しておく)
+ ブラウザから送られる Accept-Language (もしあれば)
+ RAILS_ACCEPTABLE_LANGUAGES に記述された言語(記述された順)

この順で提供可能な言語(作成されている言語テンプレート)を検索し、
見つかった物を使って表示する。

優先順位を決定するコードは以下の通り:

config/environment.rb の末尾に
 LANG:ruby(linenumber)
 class ActionController::AbstractRequest
 
   def self.acceptable_languages
     @acceptable_languages ||=
       ENV['RAILS_ACCEPTABLE_LANGUAGES'].split('|').map {|l| l.to_sym}
   end
 
   def self.acceptable_language?(l)
     acceptable_languages.include?(l.to_sym)
   end
 
 end
 
 class ActionController::AbstractRequest
 
   # will be set by ActionView::Base._pick_template
   attr :language, true
 
   def accepts_languages!(language=nil)
     @accepts_languages= [
         language ? language.to_sym : nil,
         ( cookie = cookies['rails_language'] and l = cookie.first and 
             ActionController::AbstractRequest.acceptable_language?(l) ) ? l.to_sym : nil,
         ( @env['HTTP_ACCEPT_LANGUAGE'] || '' ).split(",").collect {|l|
             lsym= l.split(/;|\-/,2)[0].downcase.to_sym
             ActionController::AbstractRequest.acceptable_language?(lsym) ? lsym : nil
         },
         ActionController::AbstractRequest.acceptable_languages
     ].flatten.compact.uniq
   end
 
   def accepts_languages
     @accepts_languages || accepts_languages!
   end
 
 end
 
 class ActionController::Base
 
 #  before_filter :set_language
 #    fails for some unknown reason.
 #  following is a workaround.
 
   def perform_action_with_set_rails_language
     set_rails_language
     perform_action_without_set_rails_language
   end
   alias_method_chain :perform_action, :set_rails_language
 
 protected
   def set_rails_language
     rails_language= params[:rails_language]
     if rails_language && ActionController::AbstractRequest.acceptable_language?(rails_language)
       cookies['rails_language']= rails_language
       request.accepts_languages!(rails_language)
     end
   end
 end

ブラウザから送られる Accept ヘッダーを解釈して、
ブラウザの受け入れ可能な Mime::Type の配列にして返す関数が
ActionController::AbstractRequest::accepts
という名前であるのに習って、
Accept-Language を解釈して配列にして返す関数を accepts_languages とした。

一旦指定された言語選択は、ActionController::Base.set_rails_language にて
cookies['rails_language'] に保存される。

クッキーの書き換えを行った場合には、accepts_languages を更新する必要がある。
このための関数が accepts_languages! 。

set_rails_language を before_fileter に登録したかったのだけれど、
なぜかうまく行かなかったので、perform_action の前に無理矢理はさんだ。

実際にどの言語で送信されるかは、優先順位に従ってテンプレートファイルを検索し、
初めて見つかった物が何であるかによって決まる。

request.language はこの実際のテンプレートファイルの言語を表すプロパティで、
後から ActionView::Base._pick_template によって設定される事になる。

** テスト [#h4a02e5d]

 LANG:console
 $ jed app/controllers/test_controller.rb
  class TestController < ApplicationController
    def index
      render :text => request.accepts_languages.inspect + "\n"
    end
  end
 $ (restart script/server)
 $ wget -qO- http://localhost:3000/test
  [:ja, :en]
 $ wget -qO- --header="Accept-Language:ja" http://localhost:3000/test
  [:ja, :en]
 $ wget -qO- --header="Accept-Language:en" http://localhost:3000/test
  [:en, :ja]
 $ wget -qO- http://localhost:3000/test
  [:ja, :en]
 $ wget -qO- http://localhost:3000/test.en
  [:en, :ja]
 $ alias wgetc="wget -qO- --load-cookies=tmp/wget.cookie --save-cookies=tmp/wget.cookie --keep-session-cookies"
 $ cat tmp/wget.cookie 
  cat: tmp/wget.cookie: そのようなファイルやディレクトリはありません
 $ wgetc http://localhost:3000/test
  [:ja, :en]
 $ cat tmp/wget.cookie
  # HTTP cookie file.
  # Edit at your own risk.
  
 $ wgetc http://localhost:3000/test.en
  [:en, :ja]
 $ cat tmp/wget.cookie
  # HTTP cookie file.
  # Edit at your own risk.
  
  localhost:3000  FALSE   /       FALSE   0       _negotiation_session    BAh7BiIKZm...
  localhost:3000  FALSE   /       FALSE   0       rails_language  en
 $ wgetc http://localhost:3000/test
  [:en, :ja]
 $ wgetc http://localhost:3000/test.ja
  [:ja, :en]
 $ wgetc http://localhost:3000/test
  [:ja, :en]

正しく切り換えができている事が分かる。

ACCEPT_LANGUAGE や rails_language, cookie['rails_language'] 
に不正な言語指定が送られてもはじくようになっている。

[[#### 前へ>ソフトウェア/rails/言語ネゴシエーション/negotiation という名前のアプリケーションの作成]]~
[[#### 上へ>ソフトウェア/rails/言語ネゴシエーション]]~
[[#### 次へ>ソフトウェア/rails/言語ネゴシエーション/言語が指定されていない時を含めた言語選択の優先順位]]~

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