module Aigu class IosImporter DICT_DICT_OPEN_REGEX = /^\s*<dict>\s*$/ DICT_DICT_CLOSE_REGEX = /^\s*<\/dict>\s*$/ DICT_KEY_REGEX = /^\s*<key>(?<text>.*)<\/key>\s*$/ DICT_STRING_REGEX = /^(?<left>\s*<string>)(?<text>.*)(?<right><\/string>\s*)$/ def initialize(opts = {}) @input_file = opts[:'input-file'] @output_directory = opts[:'output-directory'] @locale = opts[:locale] end def process! puts "Generating IOS strings files in `#{@output_directory}` based on Accent-generated `#{@input_file}` file" puts '---' parse_json strings, dict = split_dict write_strings_files(strings) write_stringsdict_file(dict) puts '---' puts 'Done' end protected def parse_json json = File.read(@input_file) @object = JSON.parse(json) end def split_dict strings = {} dict = {} @object.each_pair do |key, value| match_data = key.match(/^__@DICT__.*/) if match_data dict[key] = value else strings[key] = value end end [strings, dict] end def write_strings_files(content) @locale || @locale = 'en' file_path = File.join(@output_directory, "#{@locale}.lproj", 'Localizable.strings') puts "Generating #{file_path}" FileUtils.mkdir_p(File.dirname(file_path)) File.open(file_path, 'w+:bom|utf-8') do |file| file << format_strings_file(content) end end def format_strings_file(content) file_content = '' content.each_pair do |key, value| file_content << '"' << key << '" = "' << replace_string_interpolations(value) << '";' << "\n" end file_content end # This takes a string and replaces iOS interpolations to Android interpolations # so that it is reusable across platforms. # Example: 'iOS interpolation %@ or %1$@' => 'Android interpolation %s or %1$s'. def replace_string_interpolations(string) string.gsub(/%([0-9]+\$)?@/, '%\\1s') end def write_stringsdict_file(content) # uses same logic as exporter to parse current file & update in memory # read file file_path = File.join(@output_directory, "#{@locale}.lproj", 'Localizable.stringsdict') puts "Updating #{file_path}" file_content = File.open(file_path, 'rb:bom|utf-8').read # in memory update file_content = update_stringsdict_content(file_content, content) # write back File.open(file_path, 'w+:bom|utf-8') do |file| file << file_content end end # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting def update_stringsdict_content(file_content, new_hash) # Custom parsing in order to keep the file as-is, with any comments and spaces that dev used new_content = '' in_root_dict = false bundle_key = nil string_key = nil in_string_dict = false plural_key = nil file_content.each_line do |line| if line.match(DICT_DICT_OPEN_REGEX) # <dict> if !in_root_dict in_root_dict = true elsif string_key in_string_dict = true end # dict open lines, keep as is new_content << line elsif line.match(DICT_DICT_CLOSE_REGEX) # </dict> if in_string_dict in_string_dict = false string_key = nil plural_key = nil elsif bundle_key bundle_key = nil else in_root_dict = false end # dict close lines, keep as is new_content << line elsif matched_data = line.match(DICT_KEY_REGEX) # <key>..</key> if in_root_dict if bundle_key if string_key plural_key = matched_data[:text] else string_key = matched_data[:text] end else bundle_key = matched_data[:text] end end # key lines, keep as is new_content << line elsif matched_data = line.match(DICT_STRING_REGEX) # <string>..</string> if string_key && !in_string_dict string_key = nil # useless string lines, keep as is new_content << line elsif in_string_dict && plural_key if %w(zero one other).include? plural_key hash_key = "__@DICT__#{bundle_key}__@STRING__#{string_key}__@#{plural_key.upcase}" # replace value, keeps other parts of the line as is new_content << matched_data[:left] << new_hash[hash_key].encode(xml: :text) << matched_data[:right] else # useless string lines, keep as is new_content << line end else # useless string lines, keep as is new_content << line end else # unknown line, keep as is new_content << line end end new_content end # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting end end