require 'fileutils' require 'yaml' require 'active_support/inflector' require 'active_support/core_ext/object' $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'google_translate_dictionary' require 'google_spreadsheet' require 'config' module I18n module Migrations class Migrator def config @config ||= Config.new.read! end def new_migration(name) name = name.parameterize(separator: '_') file_name = "#{Time.now.strftime('%Y%m%d%H%M')}_#{name.downcase.gsub(' ', '_')}.rb" unless Dir.exist?(config.migration_dir) puts "Creating migration directory #{config.migration_dir} because it didn't exist." FileUtils.mkdir_p(config.migration_dir) end File.open(File.join(config.migration_dir, file_name), 'w') do |f| f << <<-CONTENTS require 'i18n-migrations' class #{name.camelcase} < I18n::Migrations::Migration def change # add('foo.bar', 'The foo of the bar') end end CONTENTS end puts "Wrote new migration to #{file_name}" end def migrate(locale_or_all = 'all') each_locale(locale_or_all) do |locale| update_locale_info(locale) do |data, notes| migrate_locale(locale, data, notes) end end end def rollback(locale_or_all) each_locale(locale_or_all) do |locale| update_locale_info(locale) do |data, notes| rollback_locale(locale, data, notes) end end end def pull(locale_or_all) each_locale(locale_or_all) do |locale| next if locale == config.main_locale sheet = GoogleSpreadsheet.new(locale).sheet pull_locale(locale, sheet) migrate(locale) end end def push(locale_or_all) each_locale(locale_or_all) do |locale| next if locale == config.main_locale sheet = GoogleSpreadsheet.new(locale).sheet pull_locale(locale, sheet) migrate(locale) push_locale(locale, sheet) end end def new_locale(new_locale) dictionary = GoogleTranslateDictionary.new(config.google_translate_api_key, config.main_locale, new_locale) new_data, new_notes = {}, {} read_locale_data(config.main_locale).each do |key, term| new_data[key] = dictionary.lookup(term) new_notes[key] = '[autotranslated]' print '.'.green end write_locale_data_and_notes(new_locale, new_data, new_notes) end def version each_locale do |locale| puts "#{locale}: #{locale_versions(read_locale_data(locale)).last}" end end private def update_locale_info(locale) data, notes = read_locale_data_and_notes(locale) yield data, notes write_locale_data_and_notes(locale, data, notes) end def read_locale_data_and_notes(locale) data = read_locale_data(locale) notes = locale == config.main_locale ? {} : read_locale_from_file(locale, "../#{locale}_notes.yml") [data, notes] end def read_locale_data(locale) read_locale_from_file(locale, "#{locale}.yml") end def write_locale_data_and_notes(locale, data, notes) write_locale_to_file(locale, "#{locale}.yml", data) write_locale_to_file(locale, "../#{locale}_notes.yml", notes) unless locale == config.main_locale end def pull_locale(locale, sheet) puts "Pulling #{locale}" data = {} notes = {} count = 0 (2..sheet.num_rows).each do |row| key, value, note = sheet[row, 1], sheet[row, 3], sheet[row, 4] if key.present? assign_complex_key(data, key.split('.'), value.present? ? value : '') if note.present? assign_complex_key(notes, key.split('.'), note) end count += 1 print '.' end end write_locale_data_and_notes(locale, data, notes) write_locale_remote_version(locale, data) puts "\n#{count} keys" end def write_locale_remote_version(locale, data) write_locale_to_file(locale, "../#{locale}_remote_version.yml", { 'VERSION' => locale_versions(data) }) end def push_locale(locale, sheet) main_data = read_locale_data(config.main_locale) data, notes = read_locale_data_and_notes(locale) row = 2 puts "Pushing #{locale}" main_data.each do |key, value| sheet[row, 1] = key sheet[row, 2] = value sheet[row, 3] = data[key] sheet[row, 4] = notes[key] row += 1 print '.' end sheet.synchronize write_locale_remote_version(locale, data) puts "\n#{main_data.keys.length} keys" end def migrate_locale(locale, data, notes) missing_versions = all_versions - locale_versions(data) if missing_versions.empty? puts "#{locale}: up-to-date" return end puts "#{locale}: Migrating #{missing_versions.join(', ')}" missing_versions.each do |version| migrate_locale_to_version(locale, data, notes, version, :up) end end def rollback_locale(locale, data, notes) last_version = locale_versions(data).last if last_version == nil puts "#{locale}: no more migrations to roll back" return end puts "#{locale}: Rolling back #{last_version}" raise "Can't find #{last_version}.rb to rollback" unless all_versions.include?(last_version) migrate_locale_to_version(locale, data, notes, last_version, :down) end def migrate_locale_to_version(locale, data, notes, version, direction) filename = File.join(config.migration_dir, "#{version}.rb") require filename migration_class_name = version.gsub(/^\d{12}_/, '').camelcase dictionary = GoogleTranslateDictionary.new(config.google_translate_api_key, config.main_locale, locale) migration = begin migration_class_name.constantize.new(locale, data, notes, dictionary, direction) rescue raise "Couldn't load migration #{migration_class_name} in #{filename}" end migration.change if direction == :up data['VERSION'] = (locale_versions(data) + [version]).join("\n") else data['VERSION'] = (locale_versions(data) - [version]).join("\n") end end def locale_versions(data) (data['VERSION'] && data['VERSION'].split("\n")) || [] end def read_locale_from_file(locale, filename) filename = File.join(config.locales_dir, filename) begin hash = {} add_to_hash(hash, YAML.load(File.read(filename))[locale.to_s]) hash rescue puts "Error loading #{filename}" raise end end def write_locale_to_file(locale, filename, hash) # we have to go from flat keys -> values to a hash that contains other hashes complex_hash = {} hash.keys.sort.each do |key| value = hash[key] assign_complex_key(complex_hash, key.split('.'), value.present? ? value : '') end File.open(File.join(config.locales_dir, filename), 'w') do |file| file << { locale => complex_hash }.to_yaml end end def assign_complex_key(hash, key, value) if key.length == 0 # should never get here elsif key.length == 1 hash[key[0]] = value else hash[key[0]] ||= {} assign_complex_key(hash[key[0]], key[1..-1], value) end end # flattens new_hash and adds it to hash def add_to_hash(hash, new_hash, prefix = []) return unless new_hash new_hash.each do |key, value| if value.is_a?(Hash) add_to_hash(hash, value, prefix + [key]) else hash[(prefix + [key]).join('.')] = value end end end def each_locale(locale = 'all') (locale == 'all' ? all_locales : [locale]).each do |l| yield l end end def all_locales [config.main_locale] + config.other_locales end def all_versions Dir[config.migration_dir + '/*.rb'].map { |name| File.basename(name).gsub('.rb', '') } end end end end