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: 日本語
本格的に翻訳作業を始めたときに整理がつきやすいよう、コントローラごとに翻訳ファイルを作ることにしました。
これで、簡単に日本語と英語の間でロケールを切り替えることができるようになりました。

本日はここまで。
