icon 《基础 Ruby on Rails》的示例程序asagao与Rails2.2相适应

基于Cookie的区域的切换

上一章中,迈出了将示例应用 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' 两个字符串组成的数组。%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

接下来,用Rails2.0中的新写法指定到首页的路由。

  map.connect '', :controller => 'main'

将此做如下改写:

  map.root :controller => 'main'

这样一来,就可以使用方法 root_path 了。


用测试驱动进行实际操作吧。

要点如下:

  • 在cookie变量 my_locale 将用户现在的区域存为字符串。
  • GET /locale 表示现在区域和其他区域的列表。
  • 各个区域都为 HTML 链接,用户点击其中一个链接,向 /locale PUT locale 参数。
  • PUT /locale 改写cookie变量 my_locale
  • PUT /locale 中,通过 referer 参数指向区域选择页面前一页的 URL,处理完成后重定向至该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

要点是功能测试里存在的 cookie 的表现方式。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

从Rails2.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: 中文

为了翻译开始时更容易整理,我们决定给每个控制台建翻译文件。


这样就可以简单地在中文和英语间进行区域转换了。

翻訳されたasagaoのメニューバー

今天就到这儿吧。

(2009/01/04)