Merging Rails’ i18n files

Lately I’ve been in a hassle to translate our application Quassum into English. My collegues continously modify the german i18n file for Rails, then the old structure of the other language files are left unmodified. Doing this by hand is close to impossible.

So what I came up with was a rake task that merges a give structure (YAML source file) into an existing target structure while overwriting every key that changed and adding new keys. Old keys and values are left as they are.

I didn’t find anything on the internet doing this task, so I hope this task will save you the time I spent last saturday. Good luck!

$KCODE = 'UTF8'
require 'ya2yaml'

namespace :quassum do
  desc "Copy all missing lang identifiers from [src].yml to [dest].yml"
  task :fill_lang do

    if ENV['src'].blank? or ENV['dest'].blank?
      puts "Give all parameters: src and dest. " +
        "E.g.: rake quassum:fill_lang src='de' dest='en'"
      exit
    end
    src_path = File.join(RAILS_ROOT,'config','locales', "#{ENV['src']}.yml")
    dest_path = File.join(RAILS_ROOT,'config','locales', "#{ENV['dest']}.yml")

    unless File.readable?(src_path)
      puts "File #{src_path} not readable!"
      exit
    end

    unless File.exist?(dest_path)
      puts "File #{dest_path} does not exist. Creating it..."
      File.new(dest_path, "w")
    end

    # We assume that the src file is correct...
    yaml_src = YAML::load_file(src_path)
    struct_src = yaml_src[ENV['src']]

    # ...but the src not necessarily. So, in case, we create a new lang file:
    @yaml_dest = YAML::load_file(dest_path)
    @yaml_dest ||= Hash.new
    @struct_dest = @yaml_dest[ENV['dest']]
    @struct_dest ||= Hash.new

    # merge all unknown changes to the dest struct:
    merge_recursively struct_src

    @yaml_dest[ENV['dest']] = @struct_dest
    File.open(dest_path, "w") do |file|
      file.puts @yaml_dest.ya2yaml
    end

    puts File.new(dest_path).read
    puts "Everything done."
  end

  #
  # SOME HELPER FUNCTIONS
  #
  def merge_recursively(pairs, parents = [])
    pairs.each_pair do |k,v|
      # copy the parents path and add the current element key:
      current_path = Array.new(parents) << k
      if v.is_a?(Hash)
        merge_recursively(v, current_path)
      else
        ensure_yaml_contains(@struct_dest, current_path, v.to_s)
      end
    end
  end

  def ensure_yaml_contains(element, path, val)
    #puts "    ensure_yaml_contains(#{element}, #{path}, #{val})"

    if path.length == 1
      if element[path.first].is_a?(Hash) and not val.blank?
        puts "Hash found instead of key. In favor of '#{val}' '#{element.to_s}' will be deleted!"
        element[path.first] = val
      end
      if element[path.first].nil?
        puts "Missing key '#{path.first}'! Pre-filled for editing with: '#{val}'. element was #{element.class}"
        element[path.first] = val + " [[TODO]]  "
      end
      # if none of these replaced anything - we're fine, the key exists!
      # :-)
    elsif path.length > 1
      # walk down if possible:
      if element[path.first].is_a?(Hash) and element.has_key?(path.first)
        ensure_yaml_contains(element[path.first], path[1..-1], val)
      else
        # We don't have a hash in the dest file, so we create the hash
        # and fill in all subsequent children:
        element[path.first] = Hash.new
        ensure_yaml_contains(element[path.first], path[1..-1], val)
      end
    else
      puts "REPLACING FAILED"
      # should stop here
    end
  end

end

Tags: , , ,

Leave a Reply