Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
792b249
Modifications for Organic Maps
Zverik Mar 30, 2018
38f1761
Add plural localizations
greshilov Mar 30, 2018
649967b
Fixed plural escaping
biodranik Aug 7, 2021
13f5b7b
Fixed Hebrew language code for Android
biodranik Jan 11, 2022
96e0e2c
Fixed invalid stringdicts XML
biodranik Jan 12, 2022
6cd569f
Added regional dialect fallback to generic language (#3)
dvdmrtnz Mar 23, 2022
ccf5f38
Add rexml dependency
dvdmrtnz Apr 13, 2022
6917052
Merged with upstream twine changes
biodranik Dec 22, 2022
50e7eb9
Fixed generic language fallback
biodranik Aug 31, 2022
1a140cb
Properly extract language from json files
biodranik Dec 22, 2022
c9bcdcd
Merge pull request #5 from organicmaps/fixes
biodranik Dec 22, 2022
457c5bb
Do not print Twine version in generated files
biodranik Dec 23, 2022
b5d723c
Replace Android %s with iOS %@ in generated strings
biodranik May 18, 2023
f724a8f
Remove twine version from apple plurals file
biodranik Mar 4, 2024
a9a97d1
Use custom om suffix for twine version
biodranik Mar 4, 2024
860c79f
Sort plurals forms in CLDR order
dwaynebailey Aug 13, 2024
9c6143e
Remove Android Twine header
dwaynebailey Aug 7, 2024
4cfda06
Remove Apple Twine headers
dwaynebailey Aug 8, 2024
1aeee66
Consistent space indents for Android strings
dwaynebailey Aug 7, 2024
e7215cc
Break serialising strings on \n for readabilty
dwaynebailey Aug 15, 2024
ebe50c3
Use tab indents in iPhone plural stringdict files
dwaynebailey Aug 8, 2024
89dd0a6
Add magic Android newlines
dwaynebailey Aug 13, 2024
d095749
Use space indentation for Twine file
dwaynebailey Sep 18, 2024
523e187
Output developer language after meta tags
dwaynebailey Sep 18, 2024
e9386d1
Keep derivatives of dev language close to the top
dwaynebailey Sep 18, 2024
a1d5145
Order meta data as comment, tags, ref
dwaynebailey Sep 18, 2024
c9abfdf
Insert space before each translation block
dwaynebailey Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion documentation/assets/formatter.graffle
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@

\f0\fs36 \cf0 /**\
* Apple Strings File\
* Generated by Twine 0.8.1\
* Generated by Twine\
* Language: en\
*/\
\
Expand Down
6 changes: 0 additions & 6 deletions documentation/formatters.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ If we map the _input_ for each method to the example file, it looks like this
As stated at the beginning, the output produced by a formatter depends on the formatter. The output of the Apple formatter would for example be

```
/**
* Apple Strings File
* Generated by Twine 0.8.1
* Language: en
*/

/********** General **********/

"yes" = "Yes";
Expand Down
1 change: 1 addition & 0 deletions lib/twine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Error < StandardError
require 'twine/formatters/abstract'
require 'twine/formatters/android'
require 'twine/formatters/apple'
require 'twine/formatters/apple_plural'
require 'twine/formatters/django'
require 'twine/formatters/flash'
require 'twine/formatters/gettext'
Expand Down
20 changes: 19 additions & 1 deletion lib/twine/formatters/abstract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module Twine
module Formatters
class Abstract
SUPPORTS_PLURAL = false
LANGUAGE_CODE_WITH_OPTIONAL_REGION_CODE = "[a-z]{2}(?:-[A-Za-z]{2})?"

attr_accessor :twine_file
Expand Down Expand Up @@ -139,7 +140,13 @@ def format_section(section, lang)
end

def format_definition(definition, lang)
[format_comment(definition, lang), format_key_value(definition, lang)].compact.join
formatted_definition = [format_comment(definition, lang)]
if self.class::SUPPORTS_PLURAL && definition.is_plural?
formatted_definition << format_plural(definition, lang)
else
formatted_definition << format_key_value(definition, lang)
end
formatted_definition.compact.join
end

def format_comment(definition, lang)
Expand All @@ -150,10 +157,21 @@ def format_key_value(definition, lang)
key_value_pattern % { key: format_key(definition.key.dup), value: format_value(value.dup) }
end

def format_plural(definition, lang)
plural_hash = definition.plural_translation_for_lang(lang)
if plural_hash
format_plural_keys(definition.key.dup, plural_hash)
end
end

def key_value_pattern
raise NotImplementedError.new("You must implement key_value_pattern in your formatter class.")
end

def format_plural_keys(key, plural_hash)
raise NotImplementedError.new("You must implement format_plural_keys in your formatter class.")
end

def format_key(key)
key
end
Expand Down
35 changes: 30 additions & 5 deletions lib/twine/formatters/android.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ module Formatters
class Android < Abstract
include Twine::Placeholders

SUPPORTS_PLURAL = true
LANG_CODES = Hash[
'zh' => 'zh-Hans',
'zh-CN' => 'zh-Hans',
'zh-HK' => 'zh-Hant',
# See https://developer.android.com/reference/java/util/Locale#legacy-language-codes
'iw' => 'he',
'in' => 'id',
'ji' => 'yi'
]

def format_name
'android'
end
Expand All @@ -33,7 +44,10 @@ def determine_language_given_path(path)
# see http://developer.android.com/guide/topics/resources/providing-resources.html#AlternativeResources
match = /^values-([a-z]{2}(-r[a-z]{2})?)$/i.match(segment)

return match[1].sub('-r', '-') if match
if match
lang = match[1].sub('-r', '-')
return LANG_CODES.fetch(lang, lang)
end
end
end

Expand Down Expand Up @@ -82,7 +96,7 @@ def read(io, lang)
end

def format_header(lang)
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Android Strings File -->\n<!-- Generated by Twine #{Twine::VERSION} -->\n<!-- Language: #{lang} -->"
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
end

def format_sections(twine_file, lang)
Expand All @@ -94,15 +108,25 @@ def format_sections(twine_file, lang)
end

def format_section_header(section)
"\t<!-- SECTION: #{section.name} -->"
"#{space(4)}<!-- SECTION: #{section.name} -->"
end

def format_comment(definition, lang)
"\t<!-- #{definition.comment.gsub('--', '—')} -->\n" if definition.comment
"#{space(4)}<!-- #{definition.comment.gsub('--', '—')} -->\n" if definition.comment
end

def key_value_pattern
"\t<string name=\"%{key}\">%{value}</string>"
"#{space(4)}<string name=\"%{key}\">%{value}</string>"
end

def format_plural_keys(key, plural_hash)
result = "#{space(4)}<plurals name=\"#{key}\">\n"
result += plural_hash.map{|quantity,value| "#{space(8)}<item quantity=\"#{quantity}\">#{escape_value(value)}</item>"}.join("\n")
result += "\n#{space(4)}</plurals>"
end

def space(level)
' ' * level
end

def gsub_unless(text, pattern, replacement)
Expand Down Expand Up @@ -136,6 +160,7 @@ def escape_value(value)
angle_bracket = /<(?!(\/?(b|em|i|cite|dfn|big|small|font|tt|s|strike|del|u|super|sub|ul|li|br|div|span|p|a|\!\[CDATA)))/
end
value = gsub_unless(value, angle_bracket, '&lt;') { |substring| substring =~ inside_cdata }
value = gsub_unless(value, '\n', "\n\\n") { |substring| substring =~ inside_cdata }

# escape non resource identifier @ signs (http://developer.android.com/guide/topics/resources/accessing-resources.html#ResourcesFromXml)
resource_identifier_regex = /@(?!([a-z\.]+:)?[a-z+]+\/[a-zA-Z_]+)/ # @[<package_name>:]<resource_type>/<resource_name>
Expand Down
12 changes: 8 additions & 4 deletions lib/twine/formatters/apple.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module Twine
module Formatters
class Apple < Abstract
include Twine::Placeholders

def format_name
'apple'
end
Expand Down Expand Up @@ -63,10 +65,6 @@ def read(io, lang)
end
end

def format_header(lang)
"/**\n * Apple Strings File\n * Generated by Twine #{Twine::VERSION}\n * Language: #{lang}\n */"
end

def format_section_header(section)
"/********** #{section.name} **********/\n"
end
Expand All @@ -84,8 +82,14 @@ def format_key(key)
end

def format_value(value)
# Replace Android's %s with iOS %@
value = convert_placeholders_from_android_to_twine(value)
escape_quotes(value)
end

def should_include_definition(definition, lang)
return !definition.is_plural? && super
end
end
end
end
Expand Down
72 changes: 72 additions & 0 deletions lib/twine/formatters/apple_plural.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
module Twine
module Formatters
class ApplePlural < Apple
include Twine::Placeholders

SUPPORTS_PLURAL = true

def format_name
'apple-plural'
end

def extension
'.stringsdict'
end

def default_file_name
'Localizable.stringsdict'
end

def format_footer(lang)
footer = "</dict>\n</plist>"
end

def format_file(lang)
result = super
result += format_footer(lang)
end

def format_header(lang)
header = "<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>\n"
header += "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
header += "<plist version=\"1.0\">\n<dict>"
end

def format_section_header(section)
"<!-- ********** #{section.name} **********/ -->\n"
end

def format_plural_keys(key, plural_hash)
result = "\t<key>#{key}</key>\n"
result += "\t<dict>\n"
result += "\t\t<key>NSStringLocalizedFormatKey</key>\n"
result += "\t\t<string>\%\#@value@</string>\n"
result += "\t\t<key>value</key>\n"
result += "\t\t<dict>\n"
result += "\t\t\t<key>NSStringFormatSpecTypeKey</key>\n"
result += "\t\t\t<string>NSStringPluralRuleType</string>\n"
result += "\t\t\t<key>NSStringFormatValueTypeKey</key>\n"
result += "\t\t\t<string>d</string>\n"
# Replace Android's %s with iOS %@
result += plural_hash.map{|quantity,value| "\t\t\t<key>#{quantity}</key>\n\t\t\t<string>#{convert_placeholders_from_android_to_twine(value)}</string>"}.join("\n")
result += "\n"
result += "\t\t</dict>\n"
result += "\t</dict>\n"
end

def format_comment(definition, lang)
"<!-- #{definition.comment.gsub('--', '—')} -->\n" if definition.comment
end

def read(io, lang)
raise NotImplementedError.new("Reading \".stringdict\" files not implemented yet")
end

def should_include_definition(definition, lang)
return definition.is_plural? && definition.plural_translation_for_lang(lang)
end
end
end
end

Twine::Formatters.formatters << Twine::Formatters::ApplePlural.new
2 changes: 1 addition & 1 deletion lib/twine/formatters/django.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def format_file(lang)

def format_header(lang)
# see https://www.gnu.org/software/trans-coord/manual/gnun/html_node/PO-Header.html for details
"# Django Strings File\n# Generated by Twine #{Twine::VERSION}\n# Language: #{lang}\nmsgid \"\"\nmsgstr \"\"\n\"Content-Type: text/plain; charset=UTF-8\\n\""
"# Django Strings File\n# Generated by Twine\n# Language: #{lang}\nmsgid \"\"\nmsgstr \"\"\n\"Content-Type: text/plain; charset=UTF-8\\n\""
end

def format_section_header(section)
Expand Down
2 changes: 1 addition & 1 deletion lib/twine/formatters/flash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def format_sections(twine_file, lang)
end

def format_header(lang)
"## Flash Strings File\n## Generated by Twine #{Twine::VERSION}\n## Language: #{lang}"
"## Flash Strings File\n## Generated by Twine\n## Language: #{lang}"
end

def format_section_header(section)
Expand Down
2 changes: 1 addition & 1 deletion lib/twine/formatters/jquery.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def default_file_name
end

def determine_language_given_path(path)
match = /^.+-([^-]{2})\.json$/.match File.basename(path)
match = /^.+([a-z]{2}-[A-Z]{2})\.json$/.match File.basename(path)
return match[1] if match

return super
Expand Down
2 changes: 1 addition & 1 deletion lib/twine/formatters/tizen.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def read(io, lang)
end

def format_header(lang)
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Tizen Strings File -->\n<!-- Generated by Twine #{Twine::VERSION} -->\n<!-- Language: #{lang} -->"
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Tizen Strings File -->\n<!-- Generated by Twine -->\n<!-- Language: #{lang} -->"
end

def format_sections(twine_file, lang)
Expand Down
15 changes: 14 additions & 1 deletion lib/twine/output_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ def fallback_languages(language)
'zh-TW' => 'zh-Hant' # if we don't have a zh-TW translation, try zh-Hant before en
}

[fallback_mapping[language], default_language].flatten.compact
# Regional dialect fallbacks to generic language (for example: 'es-MX' to 'es' instead of default 'en').
if language.match(/([a-zA-Z]{2})-[a-zA-Z]+/)
generic_language = language.gsub(/([a-zA-Z])-[a-zA-Z]+/, '\1')
end

[fallback_mapping[language], generic_language, default_language].flatten.compact
end

def process(language)
Expand All @@ -43,6 +48,14 @@ def process(language)
new_definition = definition.dup
new_definition.translations[language] = value

if definition.is_plural?
# If definition is plural, but no translation found -> create
# Then check 'other' key
if !(new_definition.plural_translations[language] ||= {}).key? 'other'
new_definition.plural_translations[language]['other'] = value
end
end

new_section.definitions << new_definition
result.definitions_by_key[new_definition.key] = new_definition
end
Expand Down
Loading