Skip to content

Commit

Permalink
Add plural localizations
Browse files Browse the repository at this point in the history
  • Loading branch information
greshilov authored and biodranik committed Aug 7, 2021
1 parent 792b249 commit 38f1761
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 5 deletions.
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
7 changes: 7 additions & 0 deletions lib/twine/formatters/android.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Formatters
class Android < Abstract
include Twine::Placeholders

SUPPORTS_PLURAL = true
LANG_CODES = Hash[
'zh' => 'zh-Hans',
'zh-CN' => 'zh-Hans',
Expand Down Expand Up @@ -116,6 +117,12 @@ def key_value_pattern
"\t<string name=\"%{key}\">%{value}</string>"
end

def format_plural_keys(key, plural_hash)
result = "\t<plurals name=\"#{key}\">\n"
result += plural_hash.map{|quantity,value| "\t#{' ' * 2}<item quantity=\"#{quantity}\">#{value}</item>"}.join("\n")
result += "\n\t</plurals>"
end

def gsub_unless(text, pattern, replacement)
text.gsub(pattern) do |match|
match_start_position = Regexp.last_match.offset(0)[0]
Expand Down
4 changes: 4 additions & 0 deletions lib/twine/formatters/apple.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ def format_key(key)
def format_value(value)
escape_quotes(value)
end

def should_include_definition(definition, lang)
return !definition.is_plural? && super
end
end
end
end
Expand Down
68 changes: 68 additions & 0 deletions lib/twine/formatters/apple_plural.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
module Twine
module Formatters
class ApplePlural < Apple
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 = "<!--\n * Apple Stringsdict File\n * Generated by Twine #{Twine::VERSION}\n * Language: #{lang} -->\n"
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 = "#{tab(2)}<key>#{key}</key>\n"
result += "#{tab(2)}<dict>\n"
result += "#{tab(4)}<key>NSStringLocalizedFormatKey</key>\n#{tab(4)}<string>\%\#@value@</string>\n"
result += "#{tab(4)}<key>value</key>\n#{tab(4)}<dict>\n"
result += "#{tab(6)}<key>NSStringFormatSpecTypeKey</key>\n#{tab(6)}<string>NSStringPluralRuleType</string>\n"
result += "#{tab(6)}<key>NSStringFormatValueTypeKey</key>\n#{tab(6)}<string>d</string>\n"
result += plural_hash.map{|quantity,value| "#{tab(6)}<key>#{quantity}</key>\n#{tab(6)}<string>#{value}</string>"}.join("\n")
result += "\n#{tab(4)}</dict>\n#{tab(2)}</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 tab(level)
' ' * level
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
8 changes: 8 additions & 0 deletions lib/twine/output_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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
35 changes: 31 additions & 4 deletions lib/twine/twine_file.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
module Twine
class TwineDefinition
PLURAL_KEYS = %w(zero one two few many other)

attr_reader :key
attr_accessor :comment
attr_accessor :tags
attr_reader :translations
attr_reader :plural_translations
attr_reader :is_plural
attr_accessor :reference
attr_accessor :reference_key

Expand All @@ -12,6 +16,7 @@ def initialize(key)
@comment = nil
@tags = nil
@translations = {}
@plural_translations = {}
end

def comment
Expand Down Expand Up @@ -50,6 +55,16 @@ def translation_for_lang(lang)

return translation
end

def plural_translation_for_lang(lang)
if @plural_translations.has_key? lang
@plural_translations[lang].dup
end
end

def is_plural?
!@plural_translations.empty?
end
end

class TwineSection
Expand Down Expand Up @@ -137,11 +152,12 @@ def read(path)
parsed = true
end
else
match = /^([^=]+)=(.*)$/.match(line)
match = /^([^:=]+)(?::([^=]+))?=(.*)$/.match(line)
if match
key = match[1].strip
value = match[2].strip

plural_key = match[2].to_s.strip
value = match[3].strip

value = value[1..-2] if value[0] == '`' && value[-1] == '`'

case key
Expand All @@ -155,7 +171,18 @@ def read(path)
if !@language_codes.include? key
add_language_code(key)
end
current_definition.translations[key] = value
# Providing backward compatibility
# for formatters without plural support
if plural_key.empty? || plural_key == 'other'
current_definition.translations[key] = value
end
if !plural_key.empty?
if !TwineDefinition::PLURAL_KEYS.include? plural_key
warn("Unknown plural key #{plural_key}")
next
end
(current_definition.plural_translations[key] ||= {})[plural_key] = value
end
end
parsed = true
end
Expand Down

0 comments on commit 38f1761

Please sign in to comment.