diff --git a/lib/twine/formatters/abstract.rb b/lib/twine/formatters/abstract.rb index 30f9ab7..8e4e657 100644 --- a/lib/twine/formatters/abstract.rb +++ b/lib/twine/formatters/abstract.rb @@ -132,12 +132,13 @@ module Twine end def format_file(strings, lang) - result = format_header(lang) + "\n" + header = format_header(lang) + result = "" + result += header + "\n" if header result += format_sections(strings, lang) end def format_header(lang) - raise NotImplementedError.new("You must implement format_header in your formatter class.") end def format_sections(strings, lang) @@ -165,21 +166,24 @@ module Twine result += rows.join end - def format_row(row, lang) - value = row.translated_string_for_lang(lang) - - return nil unless value - - result = "" - if row.comment - comment = format_comment(row.comment) - result += comment + "\n" if comment - end - - result += key_value_pattern % { key: format_key(row.key.dup), value: format_value(value.dup) } + def row_pattern + "%{comment}%{key_value}" end - def format_comment(comment) + def format_row(row, lang) + return nil unless row.translated_string_for_lang(lang) + + result = row_pattern.scan(/%\{([a-z_]+)\}/).flatten + result.map! { |element| send("format_#{element}".to_sym, row, lang) } + result.flatten.join + end + + def format_comment(row, lang) + end + + def format_key_value(row, lang) + value = row.translated_string_for_lang(lang) + key_value_pattern % { key: format_key(row.key.dup), value: format_value(value.dup) } end def key_value_pattern @@ -194,6 +198,10 @@ module Twine value end + def escape_quotes(text) + text.gsub('"', '\\\\"') + end + def write_file(path, lang) encoding = @options[:output_encoding] || 'UTF-8' diff --git a/lib/twine/formatters/android.rb b/lib/twine/formatters/android.rb index e4212c3..ff32e7f 100644 --- a/lib/twine/formatters/android.rb +++ b/lib/twine/formatters/android.rb @@ -105,8 +105,8 @@ module Twine "\t" end - def format_comment(comment) - "\t" + def format_comment(row, lang) + "\t\n" if row.comment end def key_value_pattern @@ -114,11 +114,10 @@ module Twine end def format_value(value) - value = value.dup # Android enforces the following rules on the values # 1) apostrophes and quotes must be escaped with a backslash + value = escape_quotes(value) value.gsub!("'", "\\\\'") - value.gsub!('"', '\\\\"') # 2) HTML escape the string value = CGI.escapeHTML(value) # 3) fix substitutions (e.g. %s/%@) diff --git a/lib/twine/formatters/apple.rb b/lib/twine/formatters/apple.rb index 87fb1f4..9810a9f 100644 --- a/lib/twine/formatters/apple.rb +++ b/lib/twine/formatters/apple.rb @@ -98,8 +98,8 @@ module Twine "\"%{key}\" = \"%{value}\";\n" end - def format_comment(comment) - "/* #{comment.gsub('*/', '* /')} */" + def format_comment(row, lang) + "/* #{row.comment.gsub('*/', '* /')} */\n" if row.comment end def format_key(key) @@ -109,11 +109,6 @@ module Twine def format_value(value) escape_quotes(value) end - - def escape_quotes(text) - text.gsub('"', '\\\\"') - end - end end end diff --git a/lib/twine/formatters/django.rb b/lib/twine/formatters/django.rb index 981f4d7..cbadf73 100644 --- a/lib/twine/formatters/django.rb +++ b/lib/twine/formatters/django.rb @@ -90,53 +90,43 @@ module Twine end end - def write_file(path, lang) - default_lang = @strings.language_codes[0] - encoding = @options[:output_encoding] || 'UTF-8' - File.open(path, "w:#{encoding}") do |f| - f.puts "##\n # Django Strings File\n # Generated by Twine #{Twine::VERSION}\n # Language: #{lang}\n " - @strings.sections.each do |section| - printed_section = false - section.rows.each do |row| - if row.matches_tags?(@options[:tags], @options[:untagged]) - f.puts '' - if !printed_section - if section.name && section.name.length > 0 - f.print "#--------- #{section.name} ---------#\n\n" - end - printed_section = true - end - - basetrans = row.translated_string_for_lang(default_lang) + def format_file(strings, lang) + @default_lang = strings.language_codes[0] + super + end - key = row.key - key = key.gsub('"', '\\\\"') + def format_header(lang) + "##\n # Django Strings File\n # Generated by Twine #{Twine::VERSION}\n # Language: #{lang}\n" + end - value = row.translated_string_for_lang(lang, default_lang) - if value - value = value.gsub('"', '\\\\"') + def format_section_header(section) + "#--------- #{section.name} ---------#\n" + end - comment = row.comment + def row_pattern + "%{comment}%{base_translation}%{key_value}" + end - if comment - comment = comment.gsub('"', '\\\\"') - end + def format_base_translation(row, lang) + base_translation = row.translations[@default_lang] + "# base translation: \"#{base_translation}\"\n" if base_translation + end - if comment && comment.length > 0 - f.print "#. #{comment} \n" - end + def key_value_pattern + "msgid \"%{key}\"\n" + + "msgstr \"%{value}\"\n" + end - if basetrans && basetrans.length > 0 - f.print "# base translation: \"#{basetrans}\"\n" - end + def format_comment(row, lang) + "#. #{escape_quotes(row.comment)}\n" if row.comment + end - f.print "msgid \"#{key}\"\n" - f.print "msgstr \"#{value}\"\n" - end - end - end - end - end + def format_key(key) + escape_quotes(key) + end + + def format_value(value) + escape_quotes(value) end end end diff --git a/lib/twine/formatters/flash.rb b/lib/twine/formatters/flash.rb index e36c403..9b62314 100644 --- a/lib/twine/formatters/flash.rb +++ b/lib/twine/formatters/flash.rb @@ -51,7 +51,7 @@ module Twine match = /((?:[^"\\]|\\.)+)\s*=\s*((?:[^"\\]|\\.)*)/.match(line) if match key = match[1] - value = match[2] + value = match[2].strip value.gsub!(/\{[0-9]\}/, '%@') set_translation_for_key(key, lang, value) if last_comment @@ -70,40 +70,25 @@ module Twine end end - def write_file(path, lang) - default_lang = @strings.language_codes[0] - encoding = @options[:output_encoding] || 'UTF-8' - File.open(path, "w:#{encoding}") do |f| - f.puts "## Flash Strings File\n## Generated by Twine #{Twine::VERSION}\n## Language: #{lang}\n" - @strings.sections.each do |section| - printed_section = false - section.rows.each do |row| - if row.matches_tags?(@options[:tags], @options[:untagged]) - f.puts '' - if !printed_section - if section.name && section.name.length > 0 - f.print "## #{section.name} ##\n\n" - end - printed_section = true - end + def format_header(lang) + "## Flash Strings File\n## Generated by Twine #{Twine::VERSION}\n## Language: #{lang}" + end - key = row.key - value = row.translated_string_for_lang(lang, default_lang) - if value - placeHolderNumber = -1 - value = value.gsub(/%[d@]/) { placeHolderNumber += 1; '{%d}' % placeHolderNumber } - - comment = row.comment - if comment && comment.length > 0 - f.print "# #{comment}\n" - end + def format_section_header(section) + "## #{section.name} ##\n" + end - f.print "#{key}=#{value}" - end - end - end - end - end + def format_comment(row, lang) + "# #{row.comment}\n" if row.comment + end + + def key_value_pattern + "%{key}=%{value}" + end + + def format_value(value) + placeHolderNumber = -1 + value.gsub(/%[d@]/) { placeHolderNumber += 1; '{%d}' % placeHolderNumber } end end end diff --git a/lib/twine/formatters/gettext.rb b/lib/twine/formatters/gettext.rb index d76c4c3..0ac4f5a 100644 --- a/lib/twine/formatters/gettext.rb +++ b/lib/twine/formatters/gettext.rb @@ -60,50 +60,43 @@ module Twine end end - def write_file(path, lang) - default_lang = @strings.language_codes[0] - encoding = @options[:output_encoding] || 'UTF-8' - File.open(path, "w:#{encoding}") do |f| - f.puts "msgid \"\"\nmsgstr \"\"\n\"Language: #{lang}\\n\"\n\"X-Generator: Twine #{Twine::VERSION}\\n\"\n\n" - @strings.sections.each do |section| - printed_section = false - section.rows.each do |row| - if row.matches_tags?(@options[:tags], @options[:untagged]) - if !printed_section - f.puts '' - if section.name && section.name.length > 0 - section_name = section.name.gsub('--', '—') - f.puts "# SECTION: #{section_name}" - end - printed_section = true - end + def format_file(strings, lang) + @default_lang = strings.language_codes[0] + super + end - basetrans = row.translated_string_for_lang(default_lang) + def format_header(lang) + "msgid \"\"\nmsgstr \"\"\n\"Language: #{lang}\\n\"\n\"X-Generator: Twine #{Twine::VERSION}\\n\"\n" + end - if basetrans - key = row.key - key = key.gsub('"', '\\\\"') + def format_section_header(section) + "# SECTION: #{section.name}" + end - comment = row.comment - if comment - comment = comment.gsub('"', '\\\\"') - end + def row_pattern + "%{comment}%{key}%{base_translation}%{value}" + end - if comment && comment.length > 0 - f.print "#. \"#{comment}\"\n" - end + def format_row(row, lang) + return nil unless row.translated_string_for_lang(@default_lang) - f.print "msgctxt \"#{key}\"\nmsgid \"#{basetrans}\"\n" - value = row.translated_string_for_lang(lang) - if value - value = value.gsub('"', '\\\\"') - end - f.print "msgstr \"#{value}\"\n\n" - end - end - end - end - end + super + end + + def format_comment(row, lang) + "#. \"#{escape_quotes(row.comment)}\"\n" if row.comment + end + + def format_key(row, lang) + "msgctxt \"#{row.key.dup}\"\n" + end + + def format_base_translation(row, lang) + "msgid \"#{row.translations[@default_lang]}\"\n" + end + + def format_value(row, lang) + "msgstr \"#{row.translated_string_for_lang(lang)}\"\n" end end end diff --git a/lib/twine/formatters/jquery.rb b/lib/twine/formatters/jquery.rb index cf6ab0a..4988816 100644 --- a/lib/twine/formatters/jquery.rb +++ b/lib/twine/formatters/jquery.rb @@ -45,46 +45,45 @@ module Twine end end + def format_file(strings, lang) + "{\n#{super}\n}" + end + + def format_sections(strings, lang) + sections = strings.sections.map { |section| format_section(section, lang) } + sections.join(",\n\n") + end + + def format_section_header(section) + end + + def format_section(section, lang) + rows = section.rows.dup + + rows.map! { |row| format_row(row, lang) } + rows.compact! # remove nil entries + rows.join(",\n") + end + + def key_value_pattern + "\"%{key}\":\"%{value}\"" + end + + def format_key(key) + escape_quotes(key) + end + + def format_value(value) + escape_quotes(value) + end + def write_file(path, lang) begin require "json" rescue LoadError raise Twine::Error.new "You must run 'gem install json' in order to read or write jquery-localize files." end - - printed_string = false - default_lang = @strings.language_codes[0] - encoding = @options[:output_encoding] || 'UTF-8' - File.open(path, "w:#{encoding}") do |f| - f.print "{" - - @strings.sections.each_with_index do |section, si| - printed_section = false - section.rows.each_with_index do |row, ri| - if row.matches_tags?(@options[:tags], @options[:untagged]) - if printed_string - f.print ",\n" - end - - if !printed_section - f.print "\n" - printed_section = true - end - - key = row.key - key = key.gsub('"', '\\\\"') - - value = row.translated_string_for_lang(lang, default_lang) - value = value.gsub('"', '\\\\"') - - f.print "\"#{key}\":\"#{value}\"" - printed_string = true - end - end - end - f.puts "\n}" - - end + super end end end diff --git a/lib/twine/formatters/tizen.rb b/lib/twine/formatters/tizen.rb index d020c77..9453c8c 100644 --- a/lib/twine/formatters/tizen.rb +++ b/lib/twine/formatters/tizen.rb @@ -111,64 +111,45 @@ module Twine end end - def write_file(path, lang) - default_lang = nil - if DEFAULT_LANG_CODES.has_key?(lang) - default_lang = DEFAULT_LANG_CODES[lang] - end - File.open(path, 'w:UTF-8') do |f| - f.puts "\n\n\n" - f.write '' - @strings.sections.each do |section| - printed_section = false - section.rows.each do |row| - if row.matches_tags?(@options[:tags], @options[:untagged]) - if !printed_section - f.puts '' - if section.name && section.name.length > 0 - section_name = section.name.gsub('--', '—') - f.puts "\t" - end - printed_section = true - end + def format_header(lang) + "\n\n\n" + end - key = row.key + def format_sections(strings, lang) + result = '' + + result += super + "\n" - value = row.translated_string_for_lang(lang, default_lang) - if !value && !@options[:exclude_untranslated] - value = row.translated_string_for_lang(@strings.language_codes[0]) - end + result += '' + end - if value # if values is nil, there was no appropriate translation, so let Tizen handle the defaulting - value = String.new(value) # use a copy to prevent modifying the original + def format_section_header(section) + "\t" + end - # Tizen enforces the following rules on the values - # 1) apostrophes and quotes must be escaped with a backslash - value.gsub!('\'', '\\\\\'') - value.gsub!('"', '\\\\"') - # 2) HTML escape the string - value = CGI.escapeHTML(value) - # 3) fix substitutions (e.g. %s/%@) - value = androidify_substitutions(value) - # 4) replace beginning and end spaces with \0020. Otherwise Tizen strips them. - value.gsub!(/\A *| *\z/) { |spaces| '\u0020' * spaces.length } + def format_comment(row, lang) + "\t\n" if row.comment + end - comment = row.comment - if comment - comment = comment.gsub('--', '—') - end + def key_value_pattern + "\t%{value}" + end - if comment && comment.length > 0 - f.puts "\t\n" - end - f.puts "\t#{value}" - end - end - end - end + def format_key(key) + key.upcase + end - f.puts '' - end + def format_value(value) + value = escape_quotes(value) + # Tizen enforces the following rules on the values + # 1) apostrophes and quotes must be escaped with a backslash + value.gsub!("'", "\\\\'") + # 2) HTML escape the string + value = CGI.escapeHTML(value) + # 3) fix substitutions (e.g. %s/%@) + value = androidify_substitutions(value) + # 4) replace beginning and end spaces with \0020. Otherwise Tizen strips them. + value.gsub(/\A *| *\z/) { |spaces| '\u0020' * spaces.length } end end end diff --git a/test/fixtures/formatter_django.po b/test/fixtures/formatter_django.po new file mode 100644 index 0000000..895f0bb --- /dev/null +++ b/test/fixtures/formatter_django.po @@ -0,0 +1,28 @@ +## + # Django Strings File + # Generated by Twine 0.7.0 + # Language: en + + +#--------- Section 1 ---------# + +#. comment key1 +# base translation: "value1-english" +msgid "key1" +msgstr "value1-english" + +# base translation: "value2-english" +msgid "key2" +msgstr "value2-english" + + +#--------- Section 2 ---------# + +# base translation: "value3-english" +msgid "key3" +msgstr "value3-english" + +#. comment key4 +# base translation: "value4-english" +msgid "key4" +msgstr "value4-english" diff --git a/test/fixtures/formatter_flash.properties b/test/fixtures/formatter_flash.properties new file mode 100644 index 0000000..a623196 --- /dev/null +++ b/test/fixtures/formatter_flash.properties @@ -0,0 +1,15 @@ +## Flash Strings File +## Generated by Twine 0.7.0 +## Language: en + +## Section 1 ## + +# comment key1 +key1=value1-english +key2=value2-english + +## Section 2 ## + +key3=value3-english +# comment key4 +key4=value4-english diff --git a/test/fixtures/formatter_gettext.po b/test/fixtures/formatter_gettext.po index b89adc0..c2bda5c 100644 --- a/test/fixtures/formatter_gettext.po +++ b/test/fixtures/formatter_gettext.po @@ -24,4 +24,3 @@ msgstr "value3-english" msgctxt "key4" msgid "value4-english" msgstr "value4-english" - diff --git a/test/test_formatters.rb b/test/test_formatters.rb index efd6bd7..6727329 100644 --- a/test/test_formatters.rb +++ b/test/test_formatters.rb @@ -200,3 +200,43 @@ class TestTizenFormatter < FormatterTest end end + +class TestDjangoFormatter < FormatterTest + def setup + super Twine::Formatters::Django + end + + def test_read_file_format + @formatter.read_file fixture('formatter_django.po'), 'en' + + 1.upto(4) do |i| + assert_equal "value#{i}-english", @strings.strings_map["key#{i}"].translations['en'] + end + end + + def test_write_file_output_format + formatter = Twine::Formatters::Django.new @twine_file, {} + formatter.write_file @output_path, 'en' + assert_equal content('formatter_django.po'), output_content + end +end + +class TestFlashFormatter < FormatterTest + def setup + super Twine::Formatters::Flash + end + + def test_read_file_format + @formatter.read_file fixture('formatter_flash.properties'), 'en' + + 1.upto(4) do |i| + assert_equal "value#{i}-english", @strings.strings_map["key#{i}"].translations['en'] + end + end + + def test_write_file_output_format + formatter = Twine::Formatters::Flash.new @twine_file, {} + formatter.write_file @output_path, 'en' + assert_equal content('formatter_flash.properties'), output_content + end +end