Cookie を使ったロケールの切り替え
2009/01/04
前回では、サンプルアプリケーション asagao を国際化(i18n)するための第一歩を踏み出しました。
今回は、ロケールを切り替える機能を加えましょう。
まず、app/controllers/application.rb
に AVAILABLE_LOCALES
という新たな定数を定義します。
class ApplicationController < ActionController::Base AVAILABLE_LOCALES = %w(en ja) # Pick a unique cookie name to distinguish our session data from others' session :session_key => '_asagao_session_id' (省略)
値は、'en' と 'ja' という 2 つの文字列からなる配列です。%w(en ja)
という書き方は括弧の内側の文字列を空白文字で分割して配列にする特別な表記法です。
続いて、シングルトン・リソース locale
のためのコントローラ locales
を作ります。
> ruby script/generate controller locales show
このコントローラでユーザーのロケールを切り替えることにします。
config/routes.rb
を編集して、locale
リソースを登録します。
(省略) # マイアカウントコントローラ map.resource :account # マイロケールコントローラ map.resource :locale # 基本のURLパターン map.connect ':controller/:action/:id.:format' map.connect ':controller/:action/:id' end
ついでに、Rails 2.0 で現れた新しい書き方でトップページへのルーティングを指定することにしましょう。
map.connect '', :controller => 'main'
を、次のように書き換えます。
map.root :controller => 'main'
こうすることで、root_path
というメソッドが使えるようになります。
テスト駆動で実装していきましょう。
仕様の要点は次の通り:
- ユーザーの現在のロケールは、クッキー変数
my_locale
に文字列として格納する。 GET /locale
で現在のロケールとその他のロケールのリストを表示する。- 各ロケールは HTML リンクであり、ユーザーがそのひとつをクリックすると
/locale
に対してlocale
パラメータを PUT する。 PUT /locale
はクッキー変数my_locale
を書き換える。PUT /locale
には、ロケール選択ページの直前に表示されていたページの URL をreferer
パラメータで渡し、処理が終わったらその URL にリダイレクトする。
test/functionals/locales_controller_test.rb
を開いてください。
require 'test_helper' class LocalesControllerTest < ActionController::TestCase # Replace this with your real tests. test "the truth" do assert true end end
Rails 2.2 式の新しいテストメソッドの書き方です。これは、次のように書くのと同じです。
require 'test_helper' class LocalesControllerTest < ActionController::TestCase # Replace this with your real tests. def test_the_truth assert true end end
少し読みやすくなったかもしれません。
では、仕様をテストに翻訳します。
require 'test_helper' class LocalesControllerTest < ActionController::TestCase # 現在の自分のロケールを表示する。 test "should show my locale" do get :show assert_response :success assert_equal 'ja', assigns(:my_locale) end # 現在の自分のロケールを変更する。 # referer パラメータが指定されていなければ、トップページにリダイレクトする。 test "should change my locale" do put :update, { :locale => 'en' } assert_equal ['en'], cookies['my_locale'] assert_redirected_to root_path end # 現在の自分のロケールを変更する。 # referer パラメータが指定されていれば、その URL にリダイレクトする。 test "should change my locale with referer" do url = 'http://example.com/main/news' put :update, { :locale => 'en', :referer => url } assert_equal ['en'], cookies['my_locale'] assert_redirected_to url end end
ポイントは、機能テストにおけるクッキーの表現方法です。cookies['my_locale']
は OK ですが、cookies[:my_locale]
は NG です。
また、cookies['my_locale']
は配列を返します。そこで比較対象は 'en'
ではなく、['en']
となっています。
テストを実行して、ちゃんと失敗することを確認します。
Loaded suite test/functional/locales_controller_test Started EF Finished in 0.069264 seconds. 1) Error: test_should_change_my_locale(LocalesControllerTest): ActionController::UnknownAction: No action responded to update. Actions: exception, exception=, rescue_action_without_handler translation missing: ja, support, array, sentence_connector show (省略) 2) Failure: test_should_show_my_locale(LocalesControllerTest) [test/functional/locales_controller_test.rb:8:in `test_should_show_my_locale' /usr/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in `__send__' /usr/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in `run']: <"ja"> expected but was <nil>.
続いて、locales
コントローラを実装します。
class LocalesController < ApplicationController def show if AVAILABLE_LOCALES.include?(cookies[:my_locale]) @my_locale = cookies[:my_locale] else @my_locale = I18n.default_locale.to_s end end def update if AVAILABLE_LOCALES.include?(params[:locale]) cookies[:my_locale] = params[:locale] end if params[:referer] redirect_to params[:referer] else redirect_to root_path end end end
ポイントは、配列(Array)のインスタンスメソッド include?
の使い方です。
if cookies[:my_locale] == 'en' || cookies[:my_locale] == 'ja'
と書く代わりに、次のように書いています。将来、ロケールの個数が増えるかもしれませんので、こう書くべきです。
if AVAILABLE_LOCALES.include?(cookies[:my_locale])
テストが成功することを確認します。
> ruby -Itest test/functional/locales_controller_test.rb Loaded suite test/functional/locales_controller_test Started ... Finished in 0.086196 seconds. 3 tests, 6 assertions, 0 failures, 0 errors
次に、HTML テンプレートを作成します。app/views/locales/show.html.erb
を開きます。
Rails 2.0 から HTML テンプレートの拡張子は .html.erb
となりました。
.erb
の部分は、テンプレートエンジンの名前を示しており、ERB 以外のテンプレートエンジンが利用できることを示唆しています。
次のように修正します。
<h1><%= t('title.switch_language') %></h1> <p><%= t('locales.select_language') %></p> <ul> <% ApplicationController::AVAILABLE_LOCALES.each do |locale| -%> <li> <%= link_to_unless( locale == I18n.locale.to_s, t('locales.language_name.' + locale), { :locale => locale, :referer => request.env['HTTP_REFERER'] }, { :method => :put }) do "<strong>#{t('locales.language_name.' + locale)}</strong>" end %> </li> <% end -%> </ul>
app/views/shared/_menu_bar.rhtml
を次のように修正します。
<% menu_items = [ { :link => { :controller => '/main', :action => 'index' }, :name => t('title.top') }, { :link => { :controller => '/main', :action => 'activities' }, :name => t('title.our_activities') }, { :link => { :controller => '/main', :action => 'news'}, :name => t('title.news') }, { :link => { :controller => '/blog_entries', :action => 'index' }, :name => t('title.blog') }, { :link => locale_path, :name => t('title.switch_language') } ] if @current_user menu_items << { :link => { :controller => '/members', :action => 'index', :sort => nil, :group_id => nil }, :name => t('title.members') } end if @current_user and @current_user.administrator? menu_items << { :link => { :controller => '/admin/main', :action => 'index' }, :name => t('title.administration') } end -%> <div id='menu_bar'> <% menu_items.each_with_index do |item, index| -%> <% if index > 0 %>&;&;&;|&;&;&;<% end -%> <%= menu_link_to item -%> <% end -%> </div>
cookies[:my_locale]
の値に従って言語を切り替えるように、app/controllers/application.rb
を修正します。
class ApplicationController < ActionController::Base AVAILABLE_LOCALES = %w(en ja) # Pick a unique cookie name to distinguish our session data from others' session :session_key => '_asagao_session_id' before_filter :select_locale before_filter :resume_session private # ロケールの選択 def select_locale if AVAILABLE_LOCALES.include?(cookies[:my_locale]) I18n.locale = cookies[:my_locale] end end (省略)
最後に、翻訳ファイルを作成します。
config/locales/titles_en.yml
en: title: news: News top: TOP our_activities: Our Activities news: News blog: Blog switch_language: Switch Language members: Members administration: Administration
config/locales/titles_ja.yml
ja: title: top: TOP our_activities: 私たちの活動 news: ニュース blog: ブログ switch_language: 言語の切り替え members: 会員名簿 administration: 管理ページ
config/locales/locales_en.yml
en: locales: select_language: Please select your preferred language below. language_name: en: English ja: Japanese
config/locales/locales_en.yml
ja: locales: select_language: このサイトで使用する言語を次の中から選択してください。 language_name: en: 英語 ja: 日本語
本格的に翻訳作業を始めたときに整理がつきやすいよう、コントローラごとに翻訳ファイルを作ることにしました。
これで、簡単に日本語と英語の間でロケールを切り替えることができるようになりました。
本日はここまで。