Skip to content

Custom CSV import and export tasks

lorint edited this page Jan 10, 2020 · 10 revisions

These custom tasks for CSV import and export were originally posted by @lacco in https://github.com/glebm/i18n-tasks/pull/95.

The code below is also available as a gem: https://github.com/halostatue/i18n-tasks-csv.

CSV format

The first column contains the key. The second column contains the value in the base locale. The subsequent columns contain the values in the other locales (one column per locale). The first row of the CSV is the header row.

Example:

key en de
app.greeting Hello Hallo
app.farewell Goodbye Tschüss

Installation

Add CSV configuration to config/i18n-tasks.yml:

# config/i18n-tasks.yml
<% require './lib/i18n_csv_tasks' %>
  
csv:
  export:
    - ["faqs.*", "tmp/i18n-export/faqs.csv"]
    - "tmp/i18n-export/main.csv"
  import:
    - tmp/i18n-export/main.csv
    - tmp/i18n-export/faqs.csv

Put implementation in lib/i18n_csv_tasks.rb:

# lib/i18n_csv_tasks.rb
require 'i18n/tasks/commands'
require 'csv'
 
module I18nCsvTasks
  include ::I18n::Tasks::Command::Collection
  cmd :csv_export, desc: 'export translations to CSV'
  def csv_export(opts = {})
    translations_by_path = {}
    router = I18n::Tasks::Data::Router::PatternRouter.new(nil, write: i18n.config["csv"]["export"])
 
    i18n.locales.each do |locale|
      router.route(locale, i18n.data_forest) do |path, nodes|
        translations_by_path[path] ||= {}
        translations_by_path[path][locale] ||= {}
 
        nodes.leaves do |node|
          translations_by_path[path][locale][node.full_key(root: false)] = node.value
        end
      end
    end
 
    translations_by_path.each do |(path, translations_by_locale)|
      FileUtils.mkdir_p(File.dirname(path))
 
      CSV.open(path, "wb") do |csv|
        csv << (["key"] + i18n.locales)
 
        translations_by_locale[i18n.base_locale].keys.each do |key|
          values = i18n.locales.map do |locale|
            translations_by_locale[locale][key]
          end
          csv << values.unshift(key)
        end
      end
    end
  end
 
  cmd :csv_import, desc: 'import translations from CSV'
  def csv_import(opts = {})
    i18n.config["csv"]["import"].each do |file|
      csv = open(file).read.force_encoding('UTF-8')
 
      translations = []
      CSV.parse(csv, headers: true) do |row|
        key = row["key"]
        next unless key
 
        i18n.locales.each do |locale|
          raise "Locale missing for key #{key}! (locales in app: #{locales} / locales in file: #{row.headers.inspect})" unless row.has_key?(locale)
          translations << [[locale, key].join("."), row[locale]]
        end
      end

      # # Uncomment this in order to attempt to recognise entries that are likely symbols pointing to
      # # other translations, intended to be rendered in your .yml with a preceding colon:
      # translations.map! { |t| [t.first, (t.last&.=~ /^(?=.*\.)[a-z0-9_\.]+$/) ? t.last.to_sym : t.last] }
      i18n.data.merge! I18n::Tasks::Data::Tree::Siblings.from_flat_pairs(translations)
    end
  end
end
I18n::Tasks.add_commands I18nCsvTasks

Usage

$ i18n-tasks csv-import
$ i18n-tasks csv-export