<body><script type="text/javascript"> function setAttributeOnload(object, attribute, val) { if(window.addEventListener) { window.addEventListener('load', function(){ object[attribute] = val; }, false); } else { window.attachEvent('onload', function(){ object[attribute] = val; }); } } </script> <div id="navbar-iframe-container"></div> <script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script> <script type="text/javascript"> gapi.load("gapi.iframes:gapi.iframes.style.bubble", function() { if (gapi.iframes && gapi.iframes.getContext) { gapi.iframes.getContext().openChild({ url: 'https://www.blogger.com/navbar.g?targetBlogID\x3d4684235500622716427\x26blogName\x3dCaiwangqin\x27s+blog\x26publishMode\x3dPUBLISH_MODE_HOSTED\x26navbarType\x3dBLUE\x26layoutType\x3dCLASSIC\x26searchRoot\x3dhttp://blog.caiwangqin.com/search\x26blogLocale\x3dzh_CN\x26v\x3d2\x26homepageUrl\x3dhttp://blog.caiwangqin.com/\x26vt\x3d3393395200455623441', where: document.getElementById("navbar-iframe-container"), id: "navbar-iframe" }); } }); </script>

Caiwangqin's blog

Focus on Cloud Service, Smart Hardware, Architecture, Technic and beyond…

使用ruby gettext实现RoR应用的多语言化——Step by Step

2007年1月22日星期一


今晚看到虫眼世界的Rails学习笔记,之前也曾读过gettext实现RoR应用的多语言化的文章,感觉他这篇写得比较耐心,转发分享.


1. 安装ruby gettext
使用gem安装:


代码





  1. gem install gettext  


render_code();
gem会列出可以安装的gettext的版本。注意,如果是在Windows下安装的话,一定要选择后面带“(mswin32)”的选项,否则会有编译错误。2. 修改config/environment.rb文件:
在文件头上加入:


代码





  1. $KCODE=’u’  
  2.   

  3. require ’jcode’  
  4. require ’gettext/rails’  


render_code();3. 修改app/controllers/application.rb文件
在文件头加入


代码





  1. require ’gettext/rails’  


render_code();在ApplicationController的类声明后加入对init_gettext的调用。参数是一个字符串,一般取项目名称。这是后面要说到的po和mo文件的文件名。这里假设取’project’。


代码





  1. init_gettext ’project’  


render_code();4. 修改Rakefile
加入以下内容:


代码





  1. desc “Update pot/po files to match new version.”   
  2. task :updatepo do  

  3.   MY_APP_TEXT_DOMAIN = “project”   
  4.   MY_APP_VERSION     = “project 0.0.1″   

  5.   GetText.update_pofiles(MY_APP_TEXT_DOMAIN,   
  6.                          Dir.glob(“{app,lib}/**/*.{rb,rhtml}”),   

  7.                          MY_APP_VERSION)  
  8. end  

  9.   
  10. desc “Create mo-files for L10n”   

  11. task :makemo do  
  12.   GetText.create_mofiles(true“po”“locale”)  

  13. end    


render_code();5. 修改rb和rhtml文件,用“_()”调用代替裸字符串。
无论是在rb还是rhtml文件中,都通过“_()”函数得到多语言化的字符串。
比如controller:


代码





  1. class BlogController < ApplicationController  
  2.    :  

  3.    :  
  4.   def create  

  5.     @article = Article.new(params[:article])  
  6.     if @article.save  

  7.       flash[:notice] = _(’Article was successfully created.’)  #Here!  
  8.       redirect_to :action => ’list’  

  9.     else  
  10.       render :action => ’new‘  

  11.     end  
  12.   end  

  13.    :  
  14.    :  

  15. end  


render_code();
_()函数的参数可以是任意的英文句子,而不需要象在Java应用中一样,遵循properties的命名规则。这样对英文版来说,基本没有额外的工作。model中validation的错误信息:


代码





  1. validates_presence_of :title, :message => N_(“can’t be empty!”)  


render_code();
注意,这里要用”N_()”,而不是“_()”。
作为主语的属性名称不用写,gettext会自动将属性名加在开头。否则,可以用”%{fn}”代入属性名。gettext会查询数据库,自动产 生属性名称的字符串,产生规则是:”类名|属性名”。如果属性名是由下划线分隔的多个单词,则将下划线替换为空格,第一个单词的首字母大写,其余单词全部 小写。
例如User类的name属性,对应的是”User|Name”,”created_at”属性对应”User|Created at”。
在任意场合可以根据该规则使用多语言化的属性名,最常见的是在表头和表单中的标签。例如在rhtml文件中可以写:


代码





  1. <tr>  
  2.     <td><%= _(”User|Name”) %></td>  

  3.     <td><%= text_field ”user”, ”name” %></td>  
  4. </tr>  


render_code();在错误信息中还可以用“%d”代表validates_*中的某些参数,例如:


代码





  1. alidates_length_of :description, :minimum => 10, :message => N_(“%{fn} is too short (min is %d characters)”)  


render_code();在view的rhtml文件中, 所有需要多语言化的文本都要用“_()”代替,比如:


代码





  1. <h1><%= _(’Editing article’) %></h1>  
  2.   

  3. <%= start_form_tag :action => ’update’, :id => @article %>  
  4.   <%= render_partial ’form’ %>  

  5.   <p><%= submit_tag _(’Edit’) %></p>  
  6. <%= end_form_tag %>  

  7. <p>  
  8. <%= link_to _(’Show’), :action => ’show’, :id => @article %> |  

  9. <%= link_to _(’Destroy’), {:action => ’destroy’, :id => @article}, :confirm => _(’Are you sure?’) %> |  
  10. <%= link_to _(’Back’), :action => ’list’ %>  

  11. </p>  


render_code();6. 准备po目录。
在项目的根目录下创建目录“po”。然后在“po”目录下为没一种语言创建一个目录,目录名是语言代码,语言代码参见:
http://www.w3.org/International/resource-index.html#lang
中文一般用“zh”。其实,如果选择语言的过程完全由应用自己控制的话,目录名不遵循标准也没有关系。


7. 生成pot和po文件。
执行rake任务


代码





  1. rake updatepo  


render_code();
gettext会检查项目load路径中的所有的rb和rhtml文件,搜集调用“_()”或“N_()”的所有地方的参数,生成后缀为pot的 文件,存放在po目录下,以及后缀为”po”的文件,在没一种语言的目录下各放一份。pot和po文件的文件名前缀就是前面调用 “init_gettext”时的参数。所以如果在application.rb中写”init_gettext ‘project’”的话,生成的文件就是“project.pot”和“project.po”。8. 翻译po文件。
po文件是文本文件,可以使用文本编辑器编辑,但是必须使用Unicode编码。所以请使用支持Unicode的编辑器。另一种方式是使用poedit。([url]http://www.poedit.org[/url)
在po文件中,没一个文本串的格式如下面的样子:


代码





  1. #: app/views/member/index.rhtml:49  
  2. msgid “submit”  

  3. msgstr “”     


render_code();第一行是该字符串出现的位置。如果出现过多次,则会有多行。不要删除或修改该行,否则会影响下面说的merge过程。
第二行msgid就是调用“_()”或“N_()”时的参数。
第三行的双引号中应该填入相应语言的文本。


9. 生成mo文件
执行rake任务


代码





  1. rake makemo  


render_code();
该任务会在项目的根目录下生成locale目录。下面的内容到时候自己看一下就可以了,不需要动的。mo文件是二进制文件,是gettext在运行时真正要用的。10. 选择语言。
gettext按一下顺序决定使用哪一种语言。
a. 调用GetText.bindtextdomain方法的参数
b. request参数lang
c. Cookie lang的值。
d. Http消息头中HTTP_ACCEPT_LANGUAGE的值。
e. 缺省值“en”(English)


如果让用户自己选择语言,那么只要在controller中用“cookies[’lang’]=”设置用户使用的语言代码就可以了。


如果要在view中强制使用某种语言的话,可以使用set_locale方法。由此甚至可以做到一个view中使用不用的语言。比如:


代码





  1. <% set_locale “fr” %>  
  2. <%=_(“Hello world”) %>  

  3. <% set_locale “en” %>  
  4. <%=_(“Hello world”) %>  


render_code();11. 递增开发
“rake updatepo”是支持递增开发的。也就是每次运行“rake updatepo”的时候,如果某种语言的目录下没有po文件,则生成新的po文件。如果已经有po文件了,则只是补充新发现的字符串,原来已经翻译过的 内容会得到保留,是一个”merge”的过程。 这个过程中会发生两中情况,分别在po文件中用特殊的方式标记。
一种是在msgid前插入一行“#:fuzzy”,比如:


代码





  1. #: app/views/member/index.rhtml:49  
  2. #: fuzzy  

  3. msgid “submit”  
  4. msgstr “”     


render_code();
这表明gettext在merget的过程中遇到某种冲突,需要翻译po文件的人来解决。不过究竟什么样的冲突会导致fuzzy,我现在也不是很清楚。好在这种情况并不多见。发生的使用可能会出现msgid和msgstr之间张冠李戴的情况。
解决冲突以后,一定要把“#: fuzze”这行删除,否则makemo的时候会出现警告。另一种情况是原来的某个字符串不再被用到,这种情况下gettext并不会删除这个字符串,而是在msgid前加入一行“#: deprecated”。如果这个字符串确实不再被用到了,则可以将这一行连同msgid和msgstr全部删除。否则只删除“#: deprecated”这行,而保留msgid, msgstr。


12. 多人开发时的协作。
gettext可以支持不同的模块使用不用的po文件,不过我还没试过。一般的应用,所有的文本放在一个po文件(一种语言一个)中也可以了。这 时如果多个开发人员都去维护po文件的话,在版本管理上可能比较麻烦。所以最好是由一个人统一负责“updatepo, 翻译,makemo”这三个步骤。在某种语言的po文件中找不到某个字符串的时候,gettext会直接显示msgid,这在开发时也够用了。


13. 参考资料:
gettext home page: http://www.yotabanana.com/hiki/ruby-gettext.html


Ruby-GetText-Package HOWTO for Ruby on Rails: http://www.yotabanana.com/hiki/ruby-gettext-howto-rails.html


Using Gettext To Translate Your Rails Application: http://manuals.rubyonrails.com/read/chapter/105


*: 本文中的部分例子代码来自上述参考资料。



Link to 使用ruby gettext实现RoR应用的多语言化——Step by Step


标签:

posted by Jesse Cai, 下午6:34

0 Comments:

发表评论

订阅 帖子评论 [Atom]

<< 主页