Ruby 和 Rails 的国际化与本地化技术

Ruby 和 Rails 的国际化与本地化技术

杨 波 ( bob.yang.dev@gmail.com), 技术经理, 结信网络有限公司

简介:  在本教程中,您将通过两个例子循序渐进地学习使用 Ruby-Gettext 进行 Ruby 和 Rails 程序国际化和本地化开发的相关方法和知识。通过学习,您可以掌握国际化 Ruby、Rails 程序的技术,为您进一步了解和使用 Ruby-Gettext 打下良好的基础。

发布日期:  2007 年 11 月 05 日 
级别: 中级 

访问情况 4830 次浏览 
建议: 0 (添加评论)

1 star 2 stars 3 stars 4 stars 5 stars  平均分 (共 10 个评分 )

开始之前

关于本教程

在本教程中,您将学习:

  • Gettext 的概述和 Ruby-Gettext 包的安装;
  • 第一个国际化的 Ruby 程序 hello_i18n;
  • 如何创建 POT 文件,翻译 PO 文件,并最终生成 MO 文件;
  • 如何国际化和本地化一个 Rails 应用程序;

目标

通过本教程的学习,您不仅可以了解 Gettext 的基础知识,而且可以掌握如何使用 Ruby-Gettext 包对 Ruby 程序和 Rails 程序进行国际化和本地化的技术。

先决条件

这里假设读者已经掌握了常用的 Ruby 程序开发技术以及初步的 Rails 程序开发技术,知道 Rails 中的数据库迁移(migration)的概念,知道 Ruby Gems 包管理系统的相关知识,知道常用的 MySQL 数据库使用知识,知道常用的 Linux 命令。

系统需求

本文所有示例均在 Linux 系统中测试完成。您需要一台能运行 Linux 或者 Windows 系统的机器,除此之外您还需要一些工具才能试用本教程中的代码。所有这些工具都可以免费下载:(参见 参考资源):

  • Ruby 1.8.6;
  • RubyGems 0.9.2;
  • Rails 1.2.3;
  • gettext gem 1.9.0;
  • MySQL 4.1;
  • Firefox;

问题描述

我们在开发 Rails 或者 Ruby 程序过程中经常会面临这样的问题,即如何让开发出来的程序支持多国语言,而不只是英语或者中文;以及如何让 Rails 的英文提示都变成中文,甚至是其它语言的文字。比如,当使用 Rails 的校验机制对用户输入的表单数据进行校验时,我们希望呈现给用户的错误提示全部是中文的而不是中英文混杂的,甚至最好能根据用户浏览器的语言偏好信息来选择页面使用的语言文字。

有很多种方法能够实现这种需求,但是比较完整和系统的方法是使用 Ruby-Gettext 包来实现 Rails 程序、Ruby 程序的国际化和本地化。Ruby-Gettext 是 Masao Mutoh 开发的一个利用 GNU gettext 库实现的 Ruby 国际化程序库(更多信息请参见 参考资源)。图 1 是用 Ruby-Gettext 对 Rails 进行国际化(I18n)和本地化(L10n)的一个示例截屏,其中展示了其对中文的支持效果。


图 1. 用 Ruby-Gettext 进行中文本地化后的 Rails 程序效果(摘自 Masao Mutoh 的 Ruby-Gettext 文档)
图 1. 用 Ruby-Gettext 进行中文本地化后的 Rails 程序效果(摘自 Masao Mutoh 的 Ruby-Gettext 文档)  

可以看到用 Ruby-Gettext 做出来的 Rails 本地化(中文)非常完整。在本教程的后续部分您将看到,让它支持其它语言也是十分简单。不需要对代码进行修改,只需把要翻译的内容交给翻译者,然后把翻译后的文件放入系统中即可完成本地化(L10n)工作。下面让我们一起来探索如何使用 Ruby-Gettext 轻松进行国际化和本地化的开发!

常用术语和概念

首先来了解一些本教程中将要用到的术语,以及和国际化与本地化相关的基本概念。

常用术语

区域(locale):对某个国家的文化习惯的一种正式描述,与该国家或语言有关的翻译被称作这种语言或者国家的区域。

国际化(i18n) 和本地化(l10n):i18n 和 l10n 分别是 internationalization 和 localization 的缩写。国际化是指让同一个程序能支持多个不同的区域,而本地化是指让程序能够支持用户所选择的区域。粗略地说,对于多语言的系统而言,国际化通常由开发人员实现,而本地化通常由翻译人员实现。

NLS(Native Language Support,本地语言支持):是指与国际化和本地化相关的所有活动或者产品功能,是指能够让一个程序支持多语言交互的能力。

Gettext:是一个 GNU 软件项目,是很多 GNU 程序实现多语言支持的基础,它提供了简单、统一的方式来使国际化、本地化工作尽量简单和尽量不影响原有的程序代码。(该项目主页,请参见 参考资源

包含翻译信息的文件

在对应用程序进行国际化和本地化的开发过程中,通常会使用到后缀分别为“.pot”、“.po”和“.mo”的 POT、PO 和 MO 文件,下面对这些文件进行解释。

“.po”文件中的字母缩写 PO 是 Portable Object(可移植对象)的意思,为的是与“.mo”文件区别,这里 MO 代表的是 Machine Object(机器对象)的意思。PO 文件适合于人的读写、编辑,因为它是一个纯文本文件,包含用于翻译的原始字符串和目标语言字符串。一个 PO 文件仅用于一种目标语言。如果一个 Package(软件包)支持多种语言,那么每种语言就需要一个 PO 文件与之对应,因此整个包将由一组 PO 文件组成。

以“.pot”结尾的文件是一种用于翻译的基本模板文件,它一般包含在软件的分发包中,是所有 PO 文件的格式模版。在后面的开发例子中,我们将把 POT 文件拷贝成 PO 文件,然后再编辑 PO 文件。每一种语言的 PO 文件都是从 POT 文件拷贝而来再进行翻译的,而不是直接修改 POT 文件。

“.mo”文件是用于机器读取的文件,它以二进制方式存储,只适合机器解读。它由 rmsgfmt (或 msgfmt)读取 PO 文件进行编译后生成。

国际化与本地化一个简单的 Ruby 程序

那么接下来,我们将对一个简单的 Ruby 程序国际化和本地化的实现过程进行探索,从而学习 Ruby-Gettext 的安装、使用以及工作原理,帮助我们循序渐进地学习 Rails 的国际化和本地化开发技术。下面是实现步骤:

  1. 安装 ruby-gettext gem 包;
  2. 编写 hello_i18n.rb 程序;
  3. 抽取代码中需要翻译的内容串,创建 POT 文件;
  4. 创建、翻译 PO 文件并创建 MO 文件;
  5. 运行本地化后的程序;

安装 ruby-gettext gem 包

运行安装命令(注:$ 符号是命令提示符,表示这是一条 shell 命令,不需要输入的):

$ gem install gettext

如果正常,会得到下面的提示,否则可能是网络或者 ruby gem 服务器暂时有问题,可以多试几次。在这里您可以选择不同平台的安装版本, 本文中,我使用的是 Linux 操作系统,所以我选择 2,如果您在 Windows 下,请选择 1。


清单 1. 安装 gettext gem 的提示
                    
Bulk updating Gem source index for: http://gems.rubyforge.org
Select which gem to install for your platform (i686-linux)
 1. gettext 1.9.0 (mswin32)
 2. gettext 1.9.0 (ruby)
 3. gettext 1.8.0 (mswin32)
 4. gettext 1.8.0 (ruby)
 5. Skip this gem
 6. Cancel installation
> 2
Building native extensions. This could take a while...
Successfully installed gettext-1.9.0

安装好 Ruby-Gettext gem 之后,可以用 ruby --version 命令来查看 Ruby 解释器的版本信息,本文使用的 Ruby 解释器的版本是 ruby 1.8.6 (2007-03-13 patchlevel 0) [i686-linux]。如果您的操作系统是 Windows,那么最好使用 ruby one click 安装包。可以用 gem --version 来查看 gem 的版本信息,我使用的 gem 版本是 0.9.2。下面检查一下 Ruby-Gettext 安装是否正确,执行 irb然后输入下列语句:


清单 2. 检查 Ruby-Gettext gem 是否正确安装
                    
irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'gettext'
=> true
irb(main):003:0> GetText
=> GetText

如果您的结果和上面一样,那么您已经正确地安装了 Ruby-Gettext gem。如果提示“没有定义 GetText 这个常量”,那么说明安装有问题,或者是使用了旧版本的 gem 和 ruby。对于可能的安装问题,您可以检查 <GEM_HOME>/gettext-1.9.0/ext/mkmf.log 文件中的内容,这个文件是您在安装 gettext 包时,编译 C 扩展代码的编译日志,如果编译失败,会在其中找到相应的一些提示。

对于旧版本的 rubygems,需要用 gem_require 指令来声明所使用的包,而新版本(>0.9.2)使用的方法却是先 require 'rubygems',然后 require 需要使用的 gem 管理的包。您可以根据您正在使用的 rubygems 版本选择合适的方式引用 gettext 包。

另外,您还可以在运行 ruby 命令时使用 ruby -rubygems <yourfile.rb> 的形式,这样在代码中就可以不使用 require 'rubygems' 语句来显示地加载 rubygems 包了,直接 require 'gettext' 即可。这样调整后,只要能正确装载 Ruby-Gettext gem 并可以找到 GetText 符号就可以。然后按照您的这个可行的方法修改下面代码中使用 Ruby-Gettext gem 的方法。

编写 hello_i18n.rb 文件

用您最喜欢的 Ruby 开发工具或者文本编辑工具创建一个文件 hello_i18n.rb,内容如下:


清单 3. hello_i18n.rb 代码
                    
require 'rubygems'
require 'gettext'
include GetText

bindtextdomain("hello_i18n")
print _("Hello I18N World\n")

注意 print _("Hello I18N World\n") 这个写法,其中的 _("Hello I18N World\n") 使这个字符串支持多种语言(即国际化)。在运行时,gettext 会根据系统的“地区”设置自动选择合适的语言文字替换此处的字符串。我们来运行一下这个程序:


清单 4. 运行没有本地化的 hello_i18n.rb
                    
$ ruby hello_i18n.rb
Hello I18N World

出现打印出来字符串,说明我们的国际化程序运行正常。下面,我们将开始进行本地化工作。

抽取代码中需要翻译的内容串,创建 POT 文件

在 常用术语和概念 一节中,我们已经介绍了 POT 文件,知道它是翻译文件的原始模板,包含了需要被翻译的内容。执行下面的命令:

$ rgettext hello_i18n.rb -o hello_i18n.pot

gettext 工具将会根据指定的文件名生成 POT 文件,其内容如下:


清单 5. hello_i18n.pot 的内容
                    
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2007-05-20 18:17+0800\n"
"PO-Revision-Date: 2007-05-20 18:17+0800\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#: hello_i18n.rb:7
msgid "Hello I18N World\n"
msgstr ""

翻译 PO 文件并创建 MO 文件

POT 文件是 PO 模板文件,即被翻译的原始文件,我们不能对其进行修改,而是将它拷贝为一个 PO 文件,让翻译人员在 PO 文件上进行翻译编辑工作。一种语言对应一个 PO 文件,如果您要进行多种语言的本地化工作,那么就需要拷贝多个 PO 文件给不同的翻译人员进行翻译。


清单 6. 拷贝 POT 文件到 PO 文件
                    
$ cp hello_i18n.pot hello_i18n.po

接下来,我们用文本编辑器打开 hello_i18n.po 文件进行翻译,需要注意的是,您的编辑器必须支持 UTF-8 编码方式,PO 文件的内容必须都用 UTF-8 来编码。


清单 7. 翻译后的 hello_i18n.po 文件
                    
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2007-05-20 19:22+0800\n"
"PO-Revision-Date: 2007-05-20 19:22+0800\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#: hello_i18n.rb:7
msgid "Hello I18N World\n"
msgstr "您好国际化和本地化的世界\n"
                

注意其中粗体的那一行,原来是空着的,现在被翻译为与 msgid 对应的中文内容。

接着我们需要用这个 PO 文件生成供程序读取的 MO 文件,以便 gettext 能够快速读取。因为我们的代码中函数bindtextdomain() 没有带上 MO 查找路径参数,所以,gettext 会在默认的路径中查找 MO 文件。一般会在 /usr/share/locale/#{lang}/LC_MESSAGES/ 或者 /usr/local/share/locale/#{lang}/LC_MESSAGES/ 中查找,因为 /usr/share/locale 中放置了很多系统使用的 MO 文件,为了方便调试和开发,我们选择把我们的 MO 文件放置在目录 /usr/local/share/locale/zh_CN/LC_MESSAGES/ 中。如果该目录不存在,我们可以用下面的命令创建它:


清单 8. 创建 MO 存放目录
                    
$ mkdir -p /usr/local/share/locale/zh_CN/LC_MESSAGES/

然后我们用 PO 文件创建 MO 文件:


清单 9. 创建 MO 文件
                    
$ rmsgfmt hello_i18n.po -o /usr/local/share/locale/zh_CN/LC_MESSAGES/hello_i18n.mo

rmsgfmt 是 ruby-gettext 提供的一个生成 MO 文件的程序,也可以使用 GNU 的 msgfmt 程序来生成 MO 文件。

如果您使用的是 Windows 系统,或者想知道具体的 MO 文件查找目录都有哪些,并且是以什么顺序查找的,可以在生成 MO 文件之前用 -d 选项运行,这时因为 Ruby-Gettext 找不到任何 MO 文件,所以会提示错误,并把 MO 文件查找目录按照先后顺序列出来。如下:


清单 10. 以调试方式运行 Ruby 程序以显示 MO 查找路径
                    
$ ruby -d hello_i18n.rb
Exception `LoadError' at /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27 
- no such file to load -- gettext
Bind the domain 'hello_i18n' to 'Object'. Current locale is 
#<Locale::Object:0xb78fbb20 @variant=nil, @charset="UTF-8", @orig_str="zh_CN.UTF-8", 
@script=nil, @country="CN", @modifier=nil, @language="zh">
MO file is not found in
  /usr/share/locale/zh_CN.UTF-8/LC_MESSAGES/hello_i18n.mo
  /usr/share/locale/zh_CN/LC_MESSAGES/hello_i18n.mo
  /usr/share/locale/zh/LC_MESSAGES/hello_i18n.mo
  /usr/local/share/locale/zh_CN.UTF-8/LC_MESSAGES/hello_i18n.mo
  /usr/local/share/locale/zh_CN/LC_MESSAGES/hello_i18n.mo
  /usr/local/share/locale/zh/LC_MESSAGES/hello_i18n.mo
  /usr/lib/ruby/gems/1.8/gems/gettext-1.9.0/data/locale/zh_CN.UTF-8/
	LC_MESSAGES/hello_i18n.mo
  /usr/lib/ruby/gems/1.8/gems/gettext-1.9.0/data/locale/zh_CN/LC_MESSAGES/hello_i18n.mo
  /usr/lib/ruby/gems/1.8/gems/gettext-1.9.0/data/locale/zh/LC_MESSAGES/hello_i18n.mo
  /usr/lib/ruby/gems/1.8/gems/gettext-1.9.0/data/locale/zh_CN.UTF-8/hello_i18n.mo
  /usr/lib/ruby/gems/1.8/gems/gettext-1.9.0/data/locale/zh_CN/hello_i18n.mo
  /usr/lib/ruby/gems/1.8/gems/gettext-1.9.0/data/locale/zh/hello_i18n.mo
  /usr/lib/ruby/gems/1.8/gems/gettext-1.9.0/locale/zh_CN.UTF-8/hello_i18n.mo
  /usr/lib/ruby/gems/1.8/gems/gettext-1.9.0/locale/zh_CN/hello_i18n.mo
  /usr/lib/ruby/gems/1.8/gems/gettext-1.9.0/locale/zh/hello_i18n.mo
Hello I18N World

上面显示了 gettext 查找 MO 的目录及其顺序。从高到低,先查找前面的目录,如果没有找到,再查找后面的目录。可以看到,最先查找的目录是“/usr/share/locale/zh_CN.UTF-8/LC_MESSAGES/”,因为在我的系统中,“区域(locale)”环境变量 $LANG 是 zh_CN.UTF-8。

运行本地化后的程序

现在,我们来查看一下运行结果。再次运行 Ruby 程序:


清单 11. 运行本地化后的程序
                    
$ ruby hello_i18n.rb
您好国际化和本地化的世界

到此,我们已经成功地国际化及本地化了一个简单的 Ruby 程序。

国际化与本地化一个 Rails Blog 应用程序

前面我们已经对一个简单的 Ruby 程序进行了国际化与本地化,了解了基本的开发过程和相关的概念。 在这一节中,我们将演示一个稍微复杂的场景,我们将利用 Rails 框架创建一个名为 Blog 的 Web 应用程序,然后我们用前面学习到的知识对其模型和视图模版进行国际化及本地化,并且在 Firefox 浏览器下演示其对不同区域语言的支持。

下面是我们的开发步骤:

  1. 创建数据库;
  2. 生成 Rails 应用程序框架代码;
  3. 配置 Rails 使用的数据库连接参数;
  4. 创建模型 article;
  5. 编写数据库迁移(migration)代码并执行数据库迁移操作,生成数据库结构;
  6. 设置 Ruby 解释器使用的编码方式和声明 Ruby-Gettext 相关的包;
  7. 创建一个脚手架模型和控制器;
  8. 初始化 Ruby-Gettext 包;
  9. 国际化模型;
  10. 本地化模型:创建 Rake 任务来完成抽取 POT 文件,然后翻译 PO 文件,最后创建 MO 文件的操作;
  11. 使用浏览器查看本地化后的效果;
  12. 国际化模板;

虽然看起来步骤很多,但是只有 8-12 是与国际化相关的。下面我们将逐一进行介绍。

创建数据库

创建一个 MySQL 数据库,版本需要在 4.1 以上,以支持数据库缺省字符集,本文我们使用 utf8 字符集编码。


清单 12. 创建数据库
                    
mysql> create database blog_development default character set utf8;
Query OK, 1 row affected (0.00 sec)

生成 Rails 应用程序框架代码

确认 Rails 的版本大于等于 1.2.3,然后用下面的命令创建一个 Rails 应用程序。


清单 13. 创建 Rails 程序框架
                    
rails blog

Rails 会在当前目录下创建一个项目目录 blog,这里面包含了 blog 应用程序的所有文件。使用 cd blog 命令进入到 blog 目录中,后面的步骤均在此目录下进行操作。

配置 Rails 使用的数据库连接参数

编辑 config/database.yml 文件,设置正确的数据库连接属性,如下:


清单 14. 数据库连接参数配置
                    
development:
  adapter: mysql
  database: blog_development
  username: root
  password:
  host: localhost

创建模型 article

使用下面的命令创建模型 article


清单 15. 创建模型 article
                    
$ ./script/generate model article
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/article.rb
      create  test/unit/article_test.rb
      create  test/fixtures/articles.yml
      create  db/migrate
      create  db/migrate/001_create_articles.rb

编写数据库迁移(migration)代码并执行数据库迁移操作,生成数据库结构和数据

编辑 migrate 文件“db/migrate/001_create_articles.rb”,创建相关的表和字段:


清单 16. 编辑 migrate 文件
                    
class CreateArticles < ActiveRecord::Migration
  def self.up
    create_table :articles do |t|
      t.column :title, :string, :default => ''
      t.column :description, :text
      t.column :lastupdate, :date
    end
  end

  def self.down
    drop_table :articles
  end
end

执行 migrate 操作,创建数据库表:


清单 17. 执行 migrate 操作,创建数据库结构
                    
$ rake db:migrate
== CreateArticles: migrating ========================
-- create_table(:articles)
   -> 0.0032s
== CreateArticles: migrated (0.0034s) ==================

Rails 将根据迁移代码在 MySQL 数据库中创建用于存储 article 模型数据的数据库表 articles。

设置 Ruby 解释器使用的编码方式和声明 Ruby-Gettext 相关的包

编辑文件 config/environment.rb,在第一行加入下面的语句:


清单 18. 设置解释器使用的编码方式并引入需要的包
                    
$KCODE = 'u'
require 'jcode'

同时,在最后一行加入下面的语句:

require 'gettext/rails'

创建脚手架模型和控制器,模型是 Article,控制器是 Blog


清单 19. 创建模型和控制器
                    
./script/generate scaffold Article Blog

初始化 Ruby-Gettext 包

编辑 app/controllers/application.rb,在 ApplicationController 类中加入一行代码,用于初始化 Ruby-Gettext 包:


清单 20. 初始化 Ruby-Gettext 包
                    
class ApplicationController < ActionController::Base
    ...
    init_gettext "blog"
end

这里的参数 blog 是 textdomain,也就是给一组相关的 pot/po/mo 文件起的一个共同的组名,和 bindtextdomain 中的参数 textdomain 含义一样。

下面是 init_gettext() 方法的 API 文档:

ActionController::Base.init_gettext(textdomain, options = {})

绑定 'textdomain' 到所有的 controllers/views/models 上。参数:

  • textdomain: 文本域;
  • options:
    • :charset - 输出字符集,缺省是 "UTF-8";
    • :content_type - 文档类型,缺省是 "text/html" ;
    • :locale_path - locale 目录的路径,缺省是 RAILS_ROOT 或者插件的根目录。

您也可以为每个 controller 单独地绑定一个“文本域”,如果这样就要在每个 controller 中加入下面的代码:

class BlogController < ApplicationController
  init_gettext "blog"
  :
  :
end

接下来,我们就可以像在 Ruby 程序中使用 ruby-gettext 提供的方法一样,在 Rails 的 model、ontroller、view、helper 中使用它们了。

国际化模型

让我们首先来国际化模型(model),以便让由模型产生的校验错误提示信息都是本地语言。下面我们将在 Article 模型中加入验证代码,编辑 app/controllers/application.rb,输入下列代码:


清单 21. 国际化模型
                    
class Article < ActiveRecord::Base
  # Simple 
  validates_presence_of :title
  validates_length_of :description, :minimum => 10

  # With messages (Use N_() here)
  validates_presence_of :title, :message => N_("%{fn} can't be empty!")
  validates_length_of :description, :minimum => 10, 
    :message => N_("%{fn} is too short (min is %d characters)")

  # Your own validations (Use _() instead of N_()).
  protected
  def validate
    unless title =~ /\A[A-Z]+\z/
      errors.add("title", _("%{fn} is not correct: %{title}") % {:title => title}) 
    end   
  end   
end

这里的验证代码与一般验证代码的不同之处在于使用了一些 Ruby-Gettext 提供的方法,如:

  • _( ) :是 GetText._(msgid) 方法,它会把 msgid 映射成为对应的翻译字符串,并返回该翻译串。比如:
    _(“to be translated”) # => “将被翻译”

  • N_( ) :是 getText.N_(msgid) 方法,它不进行翻译,只是提供一个位置让 GetText 知道有这样一个 msgid 存在,它返回 msgid 本身。

所以,_( ) 方法与 N_( ) 方法的主要区别就在于是否进行翻译并返回翻译后的内容。因为 Ruby-Gettext 对 validates_ 系列函数的底层实现进行了修改,所以,不需要在调用时进行消息(:message)的翻译,而只需要在此创建一个 msgid 即可,真正的转换工作 Ruby-Gettext 包会处理。而对于自己实现的验证方法,如在 errors 中增加的错误提示信息,如:

errors.add("title", _("%{fn} is not correct: %{title}") % {:title => title}) 

就需要立刻进行翻译,得到一个翻译后的字符串供方法调用使用。

另外,在上面的代码中还有 _("%{fn} is not correct: %{title}") % {:title => title} 的用法,这是 Ruby-Gettext 对 String 类的扩展,允许在字符串中使用命名参数,例子中的含义是把 {:title => title} 中的 title 变量值嵌入到 %{title} 这个位置。其中 %{fn} 是 Ruby-Gettext 的一个特殊命名参数,表示字段名,如在这里就是“title”,并且该字段名会被进行正确的翻译,如在本例中会被翻译为“标题”。

更多其它用法,比如复数情况的翻译等,请参考 Ruby-Gettext 的 API 文档说明。

本地化模型

在我们能看到最后的效果前,还需要进行本地化(翻译)工作。我们需要从代码中抽取出 pot 文件,然后翻译为 po 文件,最后创建供程序使用的二进制的 mo 文件。为使这个过程更加方便,我们利用 rake 来进行自动化。

首先添加一个 rake 任务,这是通过创建文件 lib/tasks/gettext.rake 并写入下面的内容来实现:


清单 22. rake 任务代码
                    
desc "Update pot/po files."
task :updatepo do
  require 'gettext/utils'
  GetText.update_pofiles("blog", Dir.glob("{app,lib,bin}/**/*.{rb,rhtml}"), "blog 1.0.0")
end

desc "Create mo-files"
task :makemo do
  require 'gettext/utils'
  GetText.create_mofiles(true, "po", "locale")
end

updatepo 任务中 update_pofiles 方法将在 blog/po 目录下生成 blog.pot 文件,执行下面命令:


清单 23. 执行抽取 POT 文件的 rake 命令
                    
$ rake updatepo
(in /home/yangbo/doc/我的作品/src/blog)
po/blog.pot
. done.

然后我们像在国际化 Ruby 程序时一样拷贝 pot 文件并生成 po 文件,并把 PO 文件放置到特定的目录的 po/zh_CN 下,其中 zh_CN 是根据本 PO 文件的目标语言而设定的,例如,要翻译成日语则放置在 po/jp 目录下。


清单 24. 拷贝生成
                    
$ cd po
$ mkdir zh_CN
cp blog.pot zh_CN/blog.po

翻译 zh_CN/blog.po 文件,注意要使用 utf-8 编码保存本文件。翻译后的文件内容如下:


清单 25. 翻译后的 PO 文件
                    
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: blog 1.0.0\n"
"POT-Creation-Date: 2007-05-26 13:12+0800\n"
"PO-Revision-Date: 2007-05-26 13:11+0800\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#: app/models/article.rb:-
msgid "article"
msgstr "文章"

#: app/models/article.rb:-
msgid "Article|Title"
msgstr "标题"

#: app/models/article.rb:-
msgid "Article|Description"
msgstr "描述"

#: app/models/article.rb:-
msgid "Article|Lastupdate"
msgstr "更新日期"

...

现在,可以用这个 PO 文件来生成最后需要的 MO 文件了。这个步骤也可以使用刚才写好的 Rake 任务,如下:


清单 26. 生成 MO 文件
                    
$ rake makemo
(in /home/yangbo/doc/我的作品/src/blog)
po/zh_CN/blog.po -> locale/zh_CN/LC_MESSAGES/blog.mo

查看最终效果

现在我们终于可以查看最后的效果了。下面我们将利用 Firefox 浏览器模拟一个简体中文用户访问 Blog 程序。我们需要设置浏览器的语言选项,以中文优先,比如在 Firefox 中如下图所示进行设置:


图 2. 设置 Firefox 的语言偏好选择
图 2. 设置 Firefox 的语言偏好选择  

在发送的 HTTP 请求中会有如下的 HTTP 头字段信息:

Accept-Language: zh-cn,en-gb;q=0.7,en;q=0.3 

于是 Rails 和 Ruby-Gettext 系统会根据该信息选择简体中文区域(locale)进行消息的本地化。设置区域的另一种办法是指定 Rails 程序只使用一种本地化语言,那么不论浏览器选择什么语言,Rails 程序都只返回该种语言的文字,可以这样设置:


清单 27. 固定只显示一种语言
                    
class ApplicationController < ActionController::Base
  GetText.locale = "zh_CN"
  init_gettext "blog"
end

为了看到最后的效果,我们用 ./script/server 命令启动 blog 程序的 WEBrick 服务器,打开浏览器,然后访问这个链接地址“http://localhost:3000/blog/new”,故意空着标题提交以便可以得到下面的错误提示页面:


图 3. 本地化后的模型校验错误提示页面
图 3. 本地化后的模型校验错误提示页面  

可以看到页面上还有部分文字没有被本地化,它们是视图(view)模板中的文字。接下来,我们再国际化 create 方法的模板,将上图中的英文字符串本地化。编辑 app/views/blog/new.rhtml,修改为下面的内容:


清单 28. 国际化 new 方法的模板
                    
<h1><%= _("New article") %></h1>
<% form_tag :action => 'create' do %>
  <%= render :partial => 'form' %>
  <%= submit_tag _("Create") %>
<% end %>
<%= link_to _('Back'), :action => 'list' %>

编辑 app/views/blog/_form.rhtml,改为下列内容:


清单 29. 国际化 form 嵌套模板
                    
<%= error_messages_for 'article' %>
<!--[form:article]-->
<p><label for="article_title"><%= _("Title") %></label><br/>
<%= text_field 'article', 'title'  %></p>

<p><label for="article_description"><%= _("Description") %></label><br/>
<%= text_area 'article', 'description'  %></p>

<p><label for="article_lastupdate"><%= _("Lastupdate") %></label><br/>
<%= date_select 'article', 'lastupdate'  %></p>
<!--[eoform:article]-->

然后执行 rake 任务更新 POT 模板和 PO 模板。


清单 30. 更新 POT、PO 文件
                    
$ rake updatepo
(in /home/yangbo/doc/我的作品/src/blog)
po/blog.pot
. done.
po/zh_CN/blog.po
. done.

您会发现 po/zh_CN/blog.po 文件被自动进行了新旧内容的合并,这个功能方便了翻译者对本地化内容的维护。使得我们不必从头再把所有已经翻译过的内容再翻译一次。编辑 blog.po 文件会发现多了下面的内容:


清单 31. PO 文件新增加的内容
                    
#: app/views/blog/new.rhtml:5
msgid "Create"
msgstr ""

#: app/views/blog/new.rhtml:8
msgid "Back"
msgstr ""

#: app/views/blog/_form.rhtml:7
#, fuzzy
msgid "Description"
msgstr "描述"

特别注意粗体的 fuzzy 一行,它表示 Description 在原来的 msgid 中已经存在,但原来的 msgid 是带有上下文方式的(也就是形如 msgid "Article|Description" 方式的 msgid),需要翻译者确认原来的翻译是否在这里也适用。确认后需要把 #, fuzzy 一行去掉,否则,该 msgid 的翻译内容不会生成到 mo 文件中去,最后的效果就是没有该 msgid 的本地化翻译内容与之对应。其余部分用之前的方法进行翻译,然后执行 rake 命令生成 MO 文件:


清单 32. 生成 MO 文件
                    
$ rake makemo
(in /home/yangbo/doc/我的作品/src/blog)
po/zh_CN/blog.po -> locale/zh_CN/LC_MESSAGES/blog.mo

运行 blog 应用程序,执行之前的创建文章操作,故意空着标题栏,得到了完全本地化的页面:


图 4. 最后完全国际化和本地化后的页面效果
图 4. 最后完全国际化和本地化后的页面效果  

小结

本教程介绍了如何使用 Ruby-Gettext 来进行 Ruby 程序和 Rails 程序的国际化与本地化开发。通过两个例子循序渐进地介绍了使用 Ruby-Gettext 进行 Ruby、Rails 程序国际化开发的方法和相关知识。通过阅读本文,读者可以掌握国际化 Ruby、Rails 程序的技术。为读者进一步了解、使用 Ruby-Gettext 打下良好的基础。

下载

描述 名字 大小 下载方法
本文示例代码:hello_i18n.zip hello_i18n.zip 2 KB HTTP
本文示例代码:myblog.zip myblog.zip 78 KB HTTP

关于下载方法的信息

参考资料

学习

获得产品和技术

讨论

关于作者

杨波,早期使用 VC++ 在 Windows 平台下做桌面软件产品开发,之后多年使用 Java 语言进行开发,主要从事电信增值业务开发。从 2006 年开始接触和使用 Ruby 和 Ruby on Rails 进行一些开发。目前是结信网络有限公司的技术经理,从事特定领域(彩铃)搜索引擎产品和自然语言处理方面的研发工作,也是一个 Ruby, Rails 的爱好者。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值