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

(5384d) 更新


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

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

多言語化したビューの作成

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

まだ 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 の設定

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

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

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

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

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

テスト

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"

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

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

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

  1. 指定された言語 (指定されていれば)
  2. 前回指定された言語 (もしあれば cookie に保存しておく)
  3. ブラウザから送られる Accept-Language (もしあれば)
  4. 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 によって設定される事になる。

テスト

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'] に不正な言語指定が送られてもはじくようになっている。

#### 前へ?
#### 上へ
#### 次へ?


Counter: 4622 (from 2010/06/03), today: 2, yesterday: 0