module Aigu class IosExporter PROP_LINE_REGEX = /^\s*"(?.+)"\s?=\s?"(?.*)";\s$/ DICT_DICT_OPEN_REGEX = /^\s*\s*$/ DICT_DICT_CLOSE_REGEX = /^\s*<\/dict>\s*$/ DICT_KEY_REGEX = /^\s*(?.*)<\/key>\s*$/ DICT_STRING_REGEX = /^\s*(?.*)<\/string>\s*$/ def initialize(opts = {}) @output_file = opts[:'output-file'] @input_directory = opts[:'input-directory'] @locale = opts[:locale] @ignore = opts[:ignore] end def process! puts "Generating Accent JSON file `#{@output_file}` based on IOS strings files in `#{@input_directory}` directory" if @ignore print 'Ignoring ' puts @ignore.join(', ') end puts '---' build_output write_json_file puts '---' puts 'Done' end protected def build_output @output = {} @locale || @locale = 'en' pattern = File.join(@input_directory, "#{@locale}.lproj", 'Localizable.strings') filepath = Dir[pattern].first assign_output_from_file!(filepath) pattern = File.join(@input_directory, "#{@locale}.lproj", 'Localizable.stringsdict') filepath = Dir[pattern].first assign_output_from_file!(filepath) @output end def assign_output_from_file!(filepath) if filepath puts "Processing #{filepath}" file_content = File.open(filepath, 'rt').read @output.merge! parse_strings_file(file_content) else puts 'Strings file not found' end end def parse_strings_file(file_content) file_content.each_line.each_with_object({}) do |line, string_hash| match_data = line.match(PROP_LINE_REGEX) if match_data string_hash[match_data[:key]] = replace_string_interpolations(match_data[:value]) end end end # This takes a string and replaces Android interpolations to iOS interpolations. # Note that %s works on iOS but it doesn’t work with NSString because they’re objects. # To make it work with objects, we need to convert all %s to %@. # Example: 'Android interpolation %s or %1$s' => 'iOS interpolation %@ or %1$@'. def replace_string_interpolations(string) string.gsub(/%([0-9]+\$)?s/, '%\\1@') end # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting def parse_stringsdict_file(file_content) # Quite custom parsing to reuse same logic on importer # Assume a clean structure of file to KISS in_root_dict = false bundle_key = nil string_key = nil in_string_dict = false plural_key = nil file_content.each_line.each_with_object({}) do |line, string_hash| if line.match(DICT_DICT_OPEN_REGEX) # if !in_root_dict in_root_dict = true elsif string_key in_string_dict = true end elsif line.match(DICT_DICT_CLOSE_REGEX) # 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 elsif matched_data = line.match(DICT_KEY_REGEX) # .. 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 elsif matched_data = line.match(DICT_STRING_REGEX) # .. if string_key && !in_string_dict string_key = nil 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}" string_hash[hash_key] = cleanup_input_xml(matched_data[:text]) end end end end end # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting def write_json_file file_path = @output_file puts "Generating #{file_path}" FileUtils.mkdir_p(File.dirname(file_path)) File.open(file_path, 'w+') do |file| file << JSON.pretty_generate(JSON.parse(@output.to_json)) end end # Try to fix issue def cleanup_input_xml(xml) replace_html_name(xml, 'quot', '"') replace_html_name(xml, 'amp', '&') replace_html_name(xml, 'apos', "'") replace_html_name(xml, 'lt', '<') replace_html_name(xml, 'gt', '>') xml end def replace_html_name(xml, html_name, character) xml.gsub!(/&#{html_name};/, character) end end end