Today, I am open sourcing my Ruby LocaleTranslator; the translator uses google’s translator API to translate a primary seed locale into various other languages. This eases the creation of multi-lingual sites. Not only can the LocaleTranslator translate your main seed locale into different languages but it can also recursively merge in differences, this comes in handy if you have hand-optimized your translated locales.

Viva Localization!

My projects that use the LocaleTranslator; UploadBooth, PasteBooth and ShrinkBooth.

LocaleTranslator Examples

en.yml


site:
  hello_world: Hello World!
  home: Home
  statement: Localization should be simple!

Batch Conversion of your English locale.

#!/opt/local/bin/ruby

require 'monkey-patches.rb'
require 'locale_translator.rb'

en_yml = YAML::load(File.open('en.yml'))

[:de,:ru].each do |lang|
  lang_yml = LocaleTranslator.translate(en_yml,
    :to=>lang,
    :html=>true,
    :key=>'GOOGLE API KEY')
  f = File.new("#{lang.to_s.downcase}.yml","w")
  f.puts(lang_yml.ya2yaml(:syck_compatible => true))
  f.close
  p "Translated to #{lang.to_s}"
end

Merge in new locale keys from your English Locale into your already translated Russian locale.

#!/opt/local/bin/ruby

require 'monkey-patches.rb'
require 'locale_translator.rb'

en_yml = YAML::load(File.open('en.yml'))
ru_yml = YAML::load(File.open('ru.yml'))

ru_new_yml = LocaleTranslator.translate(en_yml,
  :to=>:ru,
  :html=>true,
  :merge=>ru_yml,
  :key=>'GOOGLE API KEY')
puts ru_new_yml.ya2yaml(:syck_compatible => true)

The Implementation Code

Support Monkey Patches

monkey-patches.rb

class Hash
  def to_list
    h2l(self)
  end
  
  def diff(hash)
    hsh = {}
    this = self
    hash.each do |k,v| 
      if v.kind_of?Hash and this.key?k
        tmp = this[k].diff(v)
        hsh[k] = tmp if tmp.size > 0 
      else
        hsh[k] = v unless this.key?k 
      end
    end
    hsh
  end
  
  def merge_r(hash)
    hsh = {}
    this = self
    hash.each do |k,v| 
      if v.kind_of?Hash
        hsh[k] = this[k].merge_r(v)
      else
        hsh[k] = v
      end
    end
    self.merge(hsh)
  end
  
  private 
    def h2l(hash)
      list = []
      hash.each {|k,v| list = (v.kind_of?Hash) ? list.merge_with_dups(h2l(v)) : list << v }
      list
    end
end

class Array
  def chunk(p=2)
    return [] if p.zero?
    p_size = (length.to_f / p).ceil
    [first(p_size), *last(length - p_size).chunk(p - 1)]
  end
  def to_hash(hash)
    l2h(hash,self)
  end
  def merge(arr)
    self | arr
  end
  def merge_with_dups(arr)
    temp = []
    self.each {|a| temp << a }
    arr.each {|a| temp << a }
    temp
  end
  def merge!(arr)
    temp = self.clone
    self.clear
    temp.each {|a| self << a }
    arr.each {|a| self << a unless temp.include?a }
    true
  end
  def merge_with_dups!(arr)
    temp = self.clone
    self.clear
    temp.each {|a| self << a }
    arr.each {|a| self << a }
    true
  end
  private
    def l2h(hash,lst)
      hsh = {}
      hash.each {|k,v| hsh[k] = (v.kind_of?Hash) ? l2h(v,lst) : lst.shift }
      hsh
    end
end

The LocaleTranslator Implementation

You need the ya2yaml and easy_translate gems. Ya2YAML can export locales in UTF-8 unlike the standard yaml implementation that can only export in binary for non-standard ascii.

locale-translator.rb

$KCODE = 'UTF8' if RUBY_VERSION < '1.9.0'
require 'rubygems'
require 'ya2yaml'
require 'yaml'
require 'easy_translate'

class LocaleTranslator
  def self.translate(text,opts)
    opts[:to] = [opts[:to]] if opts[:to] and !opts[:to].kind_of?Array
    
    if opts[:merge].kind_of?Hash and text.kind_of?Hash
      diff = opts[:merge].diff(text)
      diff_hsh = LocaleTranslator.translate(diff,:to=>opts[:to],:html=>true)
      return opts[:merge].merge_r(diff_hsh)
    end
    
    if text.kind_of?Hash
      t_arr = text.to_list
      t_arr = t_arr.first if t_arr.size == 1
      tout_arr = LocaleTranslator.translate(t_arr,:to=>opts[:to],:html=>true)
      tout_arr = [tout_arr] if tout_arr.kind_of?String
      tout_arr.to_hash(text)
    elsif text.kind_of?Array
      if text.size > 50
        out = []
        text.chunk.each {|l| out.merge_with_dups!(EasyTranslate.translate(l,opts).first) }
        out
      else
        text = text.first if text.size == 1
        EasyTranslate.translate(text,opts).first
      end
    else
      EasyTranslate.translate(text,opts).first
    end
  end
end