#!/usr/bin/env ruby require 'rubygems' require 'fileutils' require 'term/ansicolor' require 'tmpdir' require 'yaml' include Term::ANSIColor require 'babelyoda/options' require 'babelyoda/strings' # Main block def main $config = BabelYoda::Options.parse setup print_config prepare_folders init_lproj_dirs apply_incremental_translations gen_src_code_strings extract_untranslated_src_code_strings gen_src_xib_strings extract_untranslated_src_xib_strings localize_xibs_incrementally pack_tmp_lproj push_tmp_lproj FileUtils.rm_rf $config['tmp_dir'], :verbose => true success "All done!" end # Aux methods def setup global_config_filename = File.expand_path('~/.babelyoda') $config.merge!(YAML.load_file(global_config_filename)) if File.exist?(global_config_filename) config = YAML.load_file($config['rules']) config['tmp_dir'] = File.join Dir.tmpdir, "epl.babelyoda.#{$$}" config['src_dir'] = File.join config['tmp_dir'], 'src' config['startwd'] = Dir.getwd config['src_lang_lproj'] = config['src_lang'] + '.lproj' config['src_lang_lproj_dir'] = File.join(config['strings_folder'], config['src_lang_lproj']) config['src_lang_code_strings_file'] = File.join(config['src_lang_lproj_dir'], 'Localizable.strings') config['initial_strings_file'] = File.join(config['strings_folder'], 'Localizable.strings') $config.merge!(config) $config['products_folder'] = File.expand_path($config['products_folder']) $config['time_badge'] = Time.now.utc.strftime('%Y%m%d_%H%M') end def print_config ; status "CONFIG" ; y $config ; end def exe(cmd) ; putcmd cmd ; system cmd ; end def putcmd(cmd) ; print magenta, "CMD: #{cmd}", reset, "\n" ; end def status(msg) ; print blue, "--- #{msg} ---", reset, "\n" ; end def success(msg) ; print green, bold, 'SUCCESS: ', msg, reset, "\n" ; end def error(msg) ; print red, bold, 'ERROR: ', msg, reset, "\n" ; exit 1 ; end def escape_cmd_args(args) ; args.collect{ |arg| "'#{arg}'"}.join(' ') ; end # Globbers def all_languages result = Array.new result << $config['languages'] result << $config['src_lang'] result.flatten!.sort! end def all_lproj_dirs result = Array.new result << dst_lproj_dirs result << src_lproj_dir result.flatten!.sort! end def src_lproj_dir $config['src_lang_lproj_dir'] end def dst_lproj_dirs $config['languages'].collect{ |l| lproj_dir_for_lang(l) }.sort! end def lproj_dir_for_lang(lang) File.join($config['strings_folder'], "#{lang}.lproj") end def tmp_lproj_dir_for_lang(lang) File.join($config['tmp_dir'], "#{lang}.lproj") end def src_code_files $config['folders'].collect{ |dir| Dir.glob(File.join(dir, '**', '*.{c,h,m,hh,mm}')) }.flatten!.sort! end def dst_code_strings_files $config['languages'].collect{ |l| File.join(lproj_dir_for_lang(l), 'Localizable.strings') }.sort! end def all_code_strings_files result = Array.new result << dst_code_strings_files result << $config['src_lang_code_strings_file'] result.flatten!.sort! end def xib_files_for_lang(lang) $config['folders'].collect{ |dir| Dir.glob(File.join(dir, '**', lang + '.lproj', '*.xib')) }.flatten!.sort! end def dst_xib_files $config['languages'].collect{ |lang| xib_files_for_lang(lang) }.flatten!.sort! end def src_xib_files xib_files_for_lang($config['src_lang']) end def strings_files_for_lang(lang) $config['folders'].collect{ |dir| Dir.glob(File.join(dir, '**', lang + '.lproj', '*.strings')) }.flatten!.sort! end def all_strings_files all_languages.collect{ |lang| strings_files_for_lang(lang) }.flatten!.sort! end def strings_file_for_xib_lang(xib, lang) parts = File.split(xib) lang_split = File.split(parts[-2]) lang_split[-1] = lang + '.lproj' parts[-2] = File.join(lang_split) parts[-1] = parts[-1].slice(0, parts[-1].length - 4) + '.strings' result = File.join(parts) result end def xib_file_for_xib_lang(xib, lang) xib_filename = File.split(xib).last parts = File.split(xib) lang_split = File.split(parts[-2]) lang_split[-1] = lang + '.lproj' parts[-2] = File.join(lang_split) File.join(parts) end def strings_files_for_xib(xib) all_languages.each.collect { |lang| strings_file_for_xib_lang(xib, lang) } end # Globs all incremental translations for the given .strings file and language. def incremental_translations(file, lang) filename = File.split(file).last mask = "#{$config['prefix']}_*_*_#{lang}_#{filename}" Dir.glob(File.join($config['incremental_translations_folder'], mask)).sort! end # Processing def prepare_folders status "PREPARING FOLDERS" FileUtils.mkdir_p $config['incremental_translations_folder'], :verbose => true FileUtils.mkdir_p $config['old_xibs_folder'], :verbose => true FileUtils.mkdir_p File.join($config['tmp_dir'], 'zips'), :verbose => true make_tmp_lproj_dirs end def make_tmp_lproj_dirs FileUtils.mkdir_p tmp_lproj_dir_for_lang($config['src_lang']), :verbose => true FileUtils.mkdir_p $config['languages'].sort.collect{ |lang| tmp_lproj_dir_for_lang(lang) }, :verbose => true end def init_lproj_dirs status "PREPARING LPROJ FOLDERS" if File.exists?($config['initial_strings_file']) && File.exists?($config['src_lang_code_strings_file']) error "Both '#{$config['initial_strings_file']}' and '#{$config['src_lang_code_strings_file']}' exist!" end FileUtils.mkdir_p all_lproj_dirs, :verbose => true end # Iterate over all the .strings files and apply # all the translations found for each in the # incremental translations folder. def apply_incremental_translations all_languages.each do |lang| strings_files_for_lang(lang).each do |strings_file| status "APPLYING INCREMENTAL TRANSLATIONS FOR '#{strings_file}'" incremental_translations(strings_file, lang).each do |translation| combine_strings(translation, strings_file) end end end end # Generate .strings file into the temporary directory, # touch the target file so it's created if it didn't exist, # and then merge it with the new file, preserving any translations made. def gen_src_code_strings status "GENERATING STRINGS FILE FOR THE SOURCE LANGUAGE" genstrings src_code_files, $config['tmp_dir'] tmp_strings_file = File.join($config['tmp_dir'], 'Localizable.strings') merge_strings tmp_strings_file, all_code_strings_files end def genstrings(files, output_path) files_safe = escape_cmd_args(files) rc = exe "genstrings -o '#{output_path}' #{files_safe}" error "Failed to generate strings" unless rc strings_file = File.join(output_path, 'Localizable.strings') BabelYoda::StringsHelper.safe_init_strings_file strings_file end def gen_src_xib_strings status "GENERATING STRINGS FILES FOR THE SOURCE LANGUAGE XIBS" src_xib_files.each do |xib| tmp_strings_file = gen_xib_strings xib merge_strings tmp_strings_file, strings_files_for_xib(xib) end end def tmp_filename_for_xib_strings(xib) xib_filename = File.split(xib).last strings_filename = xib_filename.slice(0, xib.length - 4) + '.strings' File.join($config['tmp_dir'], strings_filename) end def gen_xib_strings(xib) strings = tmp_filename_for_xib_strings(xib) rc = exe "ibtool --generate-strings-file '#{strings}' '#{xib}'" error "Failed to generate strings for file '#{xib}'" unless rc BabelYoda::StringsHelper.safe_init_strings_file strings strings end def merge_strings(src_strings, dst_strings) targets = dst_strings.is_a?(String) ? [dst_strings] : dst_strings puts "DST_STRINGS: #{dst_strings}" targets.each do |target| status "MERGING STRINGS AT '#{src_strings}' INTO '#{target}'" FileUtils.mkdir_p File.split(target)[0], :verbose => true if File.exists? target rc = exe "wincent-strings-util --base '#{src_strings}' --merge '#{target}' --output '#{target}'" error "Failed to merge '#{src_strings}' into '#{target}'" unless rc else FileUtils.cp src_strings, target, :verbose => true end end end def extract_untranslated_src_code_strings tmp_strings_file = File.join($config['tmp_dir'], 'Localizable.strings') $config['languages'].sort.each do |lang| src_file = File.join(lproj_dir_for_lang(lang), 'Localizable.strings') dst_file = File.join(tmp_lproj_dir_for_lang(lang), mangled_name('Localizable.strings', lang)) status "EXTRACTING UNTRANSLATED STRINGS FROM '#{src_file}' TO '#{dst_file}'" rc = exe "wincent-strings-util --base '#{tmp_strings_file}' --extract '#{src_file}' --output '#{dst_file}'" error "Failed to extract untranslated string from '#{src_file}' into '#{dst_file}' based on '#{tmp_strings_file}'" unless rc end end def extract_untranslated_src_xib_strings src_xib_files.each do |xib| tmp_strings_file = tmp_filename_for_xib_strings(xib) $config['languages'].sort.each do |lang| src_file = strings_file_for_xib_lang(xib, lang) src_filename = File.split(src_file).last dst_file = File.join(tmp_lproj_dir_for_lang(lang), mangled_name(src_filename, lang)) status "EXTRACTING UNTRANSLATED STRINGS FROM '#{src_file}' TO '#{dst_file}'" rc = exe "wincent-strings-util --base '#{tmp_strings_file}' --extract '#{src_file}' --output '#{dst_file}'" error "Failed to extract untranslated string from '#{src_file}' into '#{dst_file}' based on '#{tmp_strings_file}'" unless rc end end end def combine_strings(incremental_strings, old_strings) status "COMBINING STRINGS FROM '#{incremental_strings}' INTO '#{old_strings}'" FileUtils.touch old_strings rc = exe "wincent-strings-util --base '#{old_strings}' --combine '#{incremental_strings}' --output '#{old_strings}'" error "Failed to combine '#{incremental_strings}' into '#{old_strings}'" unless rc end def pack_tmp_lproj # Copy the source language Localizable.strings into the temporary lproj folder. status "COPYING STRINGS FILE FOR '#{$config['src_lang']}'" FileUtils.cp $config['src_lang_code_strings_file'], File.join(tmp_lproj_dir_for_lang($config['src_lang']), mangled_name('Localizable.strings', $config['src_lang'])), :verbose => true src_xib_files.each do |xib| strings = strings_file_for_xib_lang(xib, $config['src_lang']) strings_filename = File.split(strings).last FileUtils.cp strings, File.join(tmp_lproj_dir_for_lang($config['src_lang']), mangled_name(strings_filename, $config['src_lang'])), :verbose => true end # Pack all the temporary lproj folders. all_languages.sort.each do |lang| status "PACKING RESOURCES FOR '#{lang}'" FileUtils.cd(tmp_lproj_dir_for_lang(lang), :verbose => true) do |dir| # Get rid of empty files Dir.glob(File.join(dir, '**', '*.strings')).flatten.sort.each do |tmp_strings_file| FileUtils.rm_rf(tmp_strings_file, :verbose => true) unless File.size?(tmp_strings_file) > 2 end rc = exe "zip -r -9 -y '../zips/#{zipname_for_lang(lang)}' *" error "Failed to pack resources for '#{lang}'" unless rc end end end def push_tmp_lproj # Move all zips from the temporary lproj folder into the products folder (e.g. ~/Desktop). FileUtils.mv Dir.glob(File.join($config['tmp_dir'], 'zips', '*.zip')), $config['products_folder'], :verbose => true end # Generates a mangled filename for the given file and language. def mangled_name(file, lang) "#{$config['prefix']}_#{$config['time_badge']}_#{lang}_#{file}" end # Genarates ZIP file name for the given language def zipname_for_lang(lang) "#{$config['prefix']}_#{$config['time_badge']}_#{lang}_Localization.zip" end def localize_xibs_incrementally # ibtool # --previous-file path_to_project/English.lproj/MainWindow.old.xib # old_dst_xib # --incremental-file path_to_project/fr.lproj/MainWindow.old.xib # old_src_xib # --strings-file path_to_strings/fr/MainWindow.strings # dst_strings # --localize-incremental # --write path_to_project/fr.lproj/MainWindow.xib # dst_xib # path_to_project/English.lproj/MainWindow.new.xib # src_xib $config['languages'].sort.each do |lang| puts "XIB-LANG: #{lang}" xib_files_for_lang(lang).each do |dst_xib| puts "XIB-DST: #{dst_xib}" dst_strings = dst_xib.slice(0, dst_xib.length - 4) + '.strings' src_xib = xib_file_for_xib_lang(dst_xib, $config['src_lang']) old_src_xib = old_xib_for_xib_lang(src_xib, $config['src_lang']) FileUtils.cp src_xib, old_src_xib, :preserve => true, :verbose => true FileUtils.cp src_xib, dst_xib, :preserve => true, :verbose => true old_dst_xib = old_xib_for_xib_lang(dst_xib, lang) FileUtils.cp dst_xib, old_dst_xib, :preserve => true, :verbose => true BabelYoda::StringsHelper.safe_init_strings_file dst_strings ncmd = ['ibtool', '--previous-file', "'#{old_dst_xib}'", '--incremental-file', "'#{old_src_xib}'", '--strings-file', "'#{dst_strings}'", '--localize-incremental', '--write', "#{dst_xib}", "#{src_xib}"].join(' ') rc = exe ncmd error "Failed to localize a XIB incrementally" unless rc end end end def xib_file_for_xib_lang(xib, lang) xib_filename = File.split(xib).last parts = File.split(xib) lang_split = File.split(parts[-2]) lang_split[-1] = lang + '.lproj' parts[-2] = File.join(lang_split) result = File.join(parts) unless lang == $config['src_lang'] FileUtils.cp xib_file_for_xib_lang(xib, $config['src_lang']), result, :preserve => true, :verbose => true end result end def mangled_old_xib_name(xib, lang) file = File.split(xib).last File.join($config['old_xibs_folder'], "#{$config['prefix']}_OLD_#{lang}_#{file}") end def old_xib_for_xib_lang(xib, lang) mangled_old_xib_filename = mangled_old_xib_name(xib, lang) end main exit 0