Ruby on Rails 中文 Wiki
Gibberish

Gibberish 本地化插件学习

本文来自:http://pilipala.javaeye.com/blog/106389

Gibberish是基于rails框架下的一个语言本地化插件, beast项目使用它进行语言本地化。

Gibberish代码不多,也很容易理解,但是很好地实现了语言本地化的功能,读了以后感觉有些收获就写了这篇文档。

一、安装与配置

在rails项目下, 执行

ruby script/plugin install svn://errtheblog.com/svn/plugins/gibberish  

在rails项目下,建立 lang 目录,并创建相应语言文件,例如:

en.yml 为英语文件,在其中输入:
login: Login

zh.yml 为中文文件,在其中输入:
login: 登录

这样即可,如果有新的需要本地化字符串,也按同样格式写入两个文件即可。

二、使用方法

通过运行 ruby script/console , 查看一下Gibberish的使用方法。


#初始化加载 
>> Gibberish.load_languages! 
=> [:zh, :en] 

#显示 login 
>> "login"[:login] 
=> "Login" 

#更改语言为zh 

>> Gibberish.current_language = :zh 
=> :zh 

>> "login"[:login] 
=> "登录" 

稍微复杂的例子是可以写成如下格式

en.yml: 
welcome: "Welcome {name}!" 

zh:yml: 
welcome: "{name},欢迎来访! " 

执行: 
"welcome"[:welcome,"Mike"]

在不同语言设置时,会显示:

Welcome Mike! 

Mike,欢迎来访!

三、源码学习

1、初始化加载


def load_languages!   
   language_files.each do |file|    
      key = File.basename(file, '.*').to_sym   
      @@languages[key] = YAML.load_file(file).symbolize_keys   
    end  
    languages   
end  

def load_languages!
   language_files.each do |file| 
      key = File.basename(file, '.*').to_sym
      @@languages[key] = YAML.load_file(file).symbolize_keys
    end
    languages
end

简单地load 相应语言文件yml至 @@languages[key] 中。

2 翻译字符串:

首先, 执行:

String.send :include, Gibberish::StringExt

当Gibberish::String Ext被别的类include时会调用到函数:

           
def self.included(base)   
  base.class_eval do  
    alias :brackets :[]   
    alias_method_chain :brackets, :translation  
    alias :[] :brackets  
  end  
end  

def self.included(base)
  base.class_eval do
    alias :brackets :[]
    alias_method_chain :brackets, :translation
    alias :[] :brackets
  end
end

其中:alias_method_chain 大概实现如下:

       
def alias_method_chain(target, feature)   
  alias_method "#{target}without#{feature}", target   
  alias_method target, "#{target}with#{feature}"  
end  

def alias_method_chain(target, feature)
  alias_method "#{target}without#{feature}", target
  alias_method target, "#{target}with#{feature}" 
end

所以,再执行String#[]时,会调用到函数 brackets_with_translation

       
def brackets_with_translation(*args)   
      args = [underscore.tr(' ', '_').to_sym] if args.empty?   
      return brackets_without_translation(*args) unless args.first.is_a? Symbol  
      Gibberish.translate(self, args.shift, *args)   
    end  

def brackets_with_translation(*args)
      args = [underscore.tr(' ', '_').to_sym] if args.empty?
      return brackets_without_translation(*args) unless args.first.is_a? Symbol
      Gibberish.translate(self, args.shift, *args)
    end

当[]中第一个参数不是一个Symbol 还会执行以前的[],否则就执行Gibberish.translate

四、使用细节

已经读过源码对一些使用细节有了更清楚的了解:

1、可以通过Gibberish::Localize#add_reserved_key添加保留字,如:

>>Gibberish.add_reserved_key(:aaa, :bbb) 
>> "bbb"[:bbb] 
=> nil

2、对于“string”[:string] 如果未找到定义的,会返回”string”

3、“string”[] 相当于 “string”[:string]

4、 类似welcome: “Welcome {name}!” 这种功能是通过下面代码实现的

string.gsub(/\{\w+\}/) { args.shift }  

所以,它只是把大括号里面的内容,按[]中的参数顺序替换而已。

比如,语言文件定义如下:

hello: hi,{name}! how are you, {name}? are you fine, {name}?

在调用时,应该是

“hello”[:hello, "Mike", "Mike", "Mike"]

而不是

“hello”[:hello, "Mike"]

另外,感觉每次更换语言,还需要set current_language 不太好。
因为,有时不同的用户会选用不同的语言,他们如果同时使用,就比较麻烦。
类似这样的调用似乎更好

"welcome"[:zh, "Mike"]

当然,实现上述功能也非常容易。

五、回顾

ruby的Mixin功能强大,可以很容易在一个Class中,加入另一个module的功能,简单这样即可:

class ClassA   
    include ModuleA   
end  

而ruby还有永不关闭的类的说法,我们可以在任何时候修改以往的类库来实现想要的功能。
在这时通常会需要重载以往已经实现的函数,而在Gibberish中就重载了String#[].

在这种情况下通常都有很固定的模式:

1、def self.included(base) 该函数会在module被别的类Mixin时候执行。

2、通过 base.class_eval 执行动态代码

3、使用 alias_method_chain :brackets, :translation

将原有函数命名为 brackets_without_translation

定义新函数 brackets_with_translation完成重载。