From 6a2f37bc135e884d076c09bbf27d96ead2f45e24 Mon Sep 17 00:00:00 2001 From: Tomas Stanik Date: Mon, 8 Jun 2020 14:02:45 +0200 Subject: [PATCH 01/13] Separate localization by context --- lib/poeditor/commands/pull_command.rb | 1 + lib/poeditor/configuration.rb | 9 ++++- lib/poeditor/core.rb | 58 +++++++++++++++++++-------- 3 files changed, 49 insertions(+), 19 deletions(-) diff --git a/lib/poeditor/commands/pull_command.rb b/lib/poeditor/commands/pull_command.rb index 8a2af67..5dcf118 100644 --- a/lib/poeditor/commands/pull_command.rb +++ b/lib/poeditor/commands/pull_command.rb @@ -43,6 +43,7 @@ def get_configuration(argv) type: get_or_raise(yaml, "type"), tags: yaml["tags"], filters: yaml["filters"], + contexts: yaml["contexts"], languages: get_or_raise(yaml, "languages"), language_alias: yaml["language_alias"], path: get_or_raise(yaml, "path"), diff --git a/lib/poeditor/configuration.rb b/lib/poeditor/configuration.rb index de65e81..ddbacce 100644 --- a/lib/poeditor/configuration.rb +++ b/lib/poeditor/configuration.rb @@ -16,6 +16,9 @@ class Configuration # @return [Array] Filters by 'translated', 'untranslated', 'fuzzy', 'not_fuzzy', 'automatic', 'not_automatic', 'proofread', 'not_proofread' (optional) attr_accessor :filters + # @return [Array] Contexts + attr_accessor :contexts + # @return [Array] The languages codes attr_accessor :languages @@ -28,8 +31,8 @@ class Configuration # @return [Hash{Sting => String}] The path replacements attr_accessor :path_replace - def initialize(api_key:, project_id:, type:, tags:nil, - filters:nil, languages:, language_alias:nil, + def initialize(api_key:, project_id:, type:, tags:nil, filters:nil, + contexts:, languages:, language_alias:nil, path:, path_replace:nil) @api_key = from_env(api_key) @project_id = from_env(project_id.to_s) @@ -37,6 +40,7 @@ def initialize(api_key:, project_id:, type:, tags:nil, @tags = tags || [] @filters = filters || [] + @contexts = contexts @languages = languages @language_alias = language_alias || {} @@ -58,6 +62,7 @@ def to_s "type" => self.type, "tags" => self.tags, "filters" => self.filters, + "contexts" => self.contexts, "languages" => self.languages, "language_alias" => self.language_alias, "path" => self.path, diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index 26e252c..96e99ea 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -37,17 +37,11 @@ def pull() UI.puts " - Exporting '#{language}'" content = self.export(:api_key => @configuration.api_key, :project_id => @configuration.project_id, + :contexts => @configuration.contexts, :language => language, :type => @configuration.type, :tags => @configuration.tags, :filters => @configuration.filters) - write(language, content) - - for alias_to, alias_from in @configuration.language_alias - if language == alias_from - write(alias_to, content) - end - end end end @@ -61,11 +55,11 @@ def pull() # @param filters [Array] # # @return Downloaded translation content - def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) + def export(api_key:, project_id:, contexts:, language:, type:, tags:nil, filters:nil) options = { "id" => project_id, "language" => convert_to_poeditor_language(language), - "type" => type, + "type" => "json", "tags" => (tags || []).join(","), "filters" => (filters || []).join(","), } @@ -87,9 +81,40 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) content.gsub!(/(%(\d+\$)?)@/, '\1s') # %@ -> %s end - unless content.end_with? "\n" - content += "\n" + json = JSON.parse content + json.group_by{ |json| json['context'] }.each do |context, json| + case type + when "apple_strings" + content = appleStrings(json) + when "android_strings" + content = androidStrings(json) + end + + write(context, language, content) + + for alias_to, alias_from in @configuration.language_alias + if language == alias_from + write(context, alias_to, content) + end + end end + + end + + def appleStrings(json) + content = "" + json.each { |item| + content << "\"#{item["term"]}\" = #{item["definition"].dump};\n" + } + return content + end + + def androidStrings(json) + content = "\n\n" + json.each { |item| + content << " #{item["definition"].dump}\n" + } + content << "\n" return content end @@ -104,8 +129,8 @@ def convert_to_poeditor_language(language) end # Write translation file - def write(language, content) - path = path_for_language(language) + def write(context, language, content) + path = path_for_context_language(context, language).delete_prefix("/") unless File.exist?(path) raise POEditor::Exception.new "#{path} doesn't exist" end @@ -113,13 +138,12 @@ def write(language, content) UI.puts " #{"\xe2\x9c\x93".green} Saved at '#{path}'" end - def path_for_language(language) + def path_for_context_language(context, language) if @configuration.path_replace[language] - @configuration.path_replace[language] + @configuration.path_replace[language].gsub("{CONTEXT}", context) else - @configuration.path.gsub("{LANGUAGE}", language) + @configuration.path.gsub("{CONTEXT}", context).gsub("{LANGUAGE}", language) end end - end end From 747ac7bbb389eb9de1532d1842d1018e53845f56 Mon Sep 17 00:00:00 2001 From: Tomas Stanik Date: Tue, 9 Jun 2020 15:30:32 +0200 Subject: [PATCH 02/13] Define context path, add tests --- lib/poeditor/commands/pull_command.rb | 3 +- lib/poeditor/configuration.rb | 23 ++++--- lib/poeditor/core.rb | 40 +++++++++--- test/test.rb | 2 +- test/test_core.rb | 94 ++++++++++++++++++++++----- 5 files changed, 126 insertions(+), 36 deletions(-) diff --git a/lib/poeditor/commands/pull_command.rb b/lib/poeditor/commands/pull_command.rb index 5dcf118..a0ddbab 100644 --- a/lib/poeditor/commands/pull_command.rb +++ b/lib/poeditor/commands/pull_command.rb @@ -43,11 +43,12 @@ def get_configuration(argv) type: get_or_raise(yaml, "type"), tags: yaml["tags"], filters: yaml["filters"], - contexts: yaml["contexts"], languages: get_or_raise(yaml, "languages"), language_alias: yaml["language_alias"], path: get_or_raise(yaml, "path"), path_replace: yaml["path_replace"], + context_path: yaml["context_path"], + context_path_replace: yaml["context_path_replace"] ) end diff --git a/lib/poeditor/configuration.rb b/lib/poeditor/configuration.rb index ddbacce..e41096c 100644 --- a/lib/poeditor/configuration.rb +++ b/lib/poeditor/configuration.rb @@ -16,9 +16,6 @@ class Configuration # @return [Array] Filters by 'translated', 'untranslated', 'fuzzy', 'not_fuzzy', 'automatic', 'not_automatic', 'proofread', 'not_proofread' (optional) attr_accessor :filters - # @return [Array] Contexts - attr_accessor :contexts - # @return [Array] The languages codes attr_accessor :languages @@ -31,21 +28,30 @@ class Configuration # @return [Hash{Sting => String}] The path replacements attr_accessor :path_replace - def initialize(api_key:, project_id:, type:, tags:nil, filters:nil, - contexts:, languages:, language_alias:nil, - path:, path_replace:nil) + # @return [String] The context path template + attr_accessor :context_path + + # @return [Hash{Sting => String}] The context path replacements + attr_accessor :context_path_replace + + def initialize(api_key:, project_id:, type:, tags:nil, + filters:nil, languages:, language_alias:nil, + path:, path_replace:nil, + context_path:nil, context_path_replace:nil) @api_key = from_env(api_key) @project_id = from_env(project_id.to_s) @type = type @tags = tags || [] @filters = filters || [] - @contexts = contexts @languages = languages @language_alias = language_alias || {} @path = path @path_replace = path_replace || {} + + @context_path = context_path + @context_path_replace = context_path_replace || {} end def from_env(value) @@ -62,11 +68,12 @@ def to_s "type" => self.type, "tags" => self.tags, "filters" => self.filters, - "contexts" => self.contexts, "languages" => self.languages, "language_alias" => self.language_alias, "path" => self.path, "path_replace" => self.path_replace, + "context_path" => self.context_path, + "context_path_replace" => self.context_path_replace, } YAML.dump(values)[4..-2] .each_line diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index 96e99ea..1607568 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -37,7 +37,6 @@ def pull() UI.puts " - Exporting '#{language}'" content = self.export(:api_key => @configuration.api_key, :project_id => @configuration.project_id, - :contexts => @configuration.contexts, :language => language, :type => @configuration.type, :tags => @configuration.tags, @@ -55,7 +54,7 @@ def pull() # @param filters [Array] # # @return Downloaded translation content - def export(api_key:, project_id:, contexts:, language:, type:, tags:nil, filters:nil) + def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) options = { "id" => project_id, "language" => convert_to_poeditor_language(language), @@ -82,7 +81,12 @@ def export(api_key:, project_id:, contexts:, language:, type:, tags:nil, filters end json = JSON.parse content - json.group_by{ |json| json['context'] }.each do |context, json| + groups = json.group_by { |json| json['context'] } + groups.each do |context, json| + if context != nil && context != "" && @configuration.context_path == nil + next # if context path is not defined, skip saving context strings + end + case type when "apple_strings" content = appleStrings(json) @@ -98,13 +102,13 @@ def export(api_key:, project_id:, contexts:, language:, type:, tags:nil, filters end end end - end def appleStrings(json) content = "" json.each { |item| - content << "\"#{item["term"]}\" = #{item["definition"].dump};\n" + definition = item["definition"] + content << "\"#{item["term"]}\" = \"#{definition}\";\n" } return content end @@ -112,7 +116,8 @@ def appleStrings(json) def androidStrings(json) content = "\n\n" json.each { |item| - content << " #{item["definition"].dump}\n" + definition = item["definition"] + content << " \"#{definition}\"\n" } content << "\n" return content @@ -130,7 +135,12 @@ def convert_to_poeditor_language(language) # Write translation file def write(context, language, content) - path = path_for_context_language(context, language).delete_prefix("/") + path = path_for_context_language(context, language) + + unless path != nil + raise POEditor::Exception.new "Undefined context path" + end + unless File.exist?(path) raise POEditor::Exception.new "#{path} doesn't exist" end @@ -139,10 +149,20 @@ def write(context, language, content) end def path_for_context_language(context, language) - if @configuration.path_replace[language] - @configuration.path_replace[language].gsub("{CONTEXT}", context) + if context == nil || context == "" + if @configuration.path_replace[language] + path = @configuration.path_replace[language] + else + path = @configuration.path.gsub("{LANGUAGE}", language) + end else - @configuration.path.gsub("{CONTEXT}", context).gsub("{LANGUAGE}", language) + if @configuration.context_path_replace[language] + path = @configuration.context_path_replace[language].gsub("{CONTEXT}", context) + elsif @configuration.context_path + path = @configuration.context_path.gsub("{LANGUAGE}", language).gsub("{CONTEXT}", context) + else + return nil + end end end end diff --git a/test/test.rb b/test/test.rb index 089c401..b63e360 100644 --- a/test/test.rb +++ b/test/test.rb @@ -1,7 +1,7 @@ require "minitest/autorun" require "webmock/minitest" -require "helper" +require_relative "helper" require_relative "../lib/poeditor" class Test < Minitest::Test diff --git a/test/test_core.rb b/test/test_core.rb index 0795eaf..90dd057 100644 --- a/test/test_core.rb +++ b/test/test_core.rb @@ -8,24 +8,56 @@ def clean def setup clean() - for language in ["en", "ko", "ja", "zh", "zh-Hans", "zh-Hant"] + + contexts = ["context1", "context2"] + ios_languages = ["en", "ja", "ko", "nl", "zh", "zh-Hans", "zh-Hant"] + android_languages = ["en", "ja", "ko", "nl", "zh", "zh-rCN", "zh-rTW"] + base_language = "en" + + # iOS + for language in ios_languages FileUtils.mkdir_p("TestProj/#{language}.lproj") File.write("TestProj/#{language}.lproj/Localizable.strings", "") + for context in contexts + FileUtils.mkdir_p("TestProj/#{context}/#{language}.lproj") + File.write("TestProj/#{context}/#{language}.lproj/Localizable.strings", "") + end end - for language in ["en", "ko", "ja", "zh", "zh-rCN", "zh-rTW"] - if language == "en" - dirname = "TestProj/values" - else - dirname = "TestProj/values-#{language}" + + # Android + for language in android_languages + if language == base_language + FileUtils.mkdir_p("TestProj/values") + File.write("TestProj/values/strings.xml", "") + else + FileUtils.mkdir_p("TestProj/values-#{language}") + File.write("TestProj/values-#{language}/strings.xml", "") + end + for context in contexts + if language == base_language + FileUtils.mkdir_p("TestProj/#{context}/values") + File.write("TestProj/#{context}/values/strings.xml", "") + else + FileUtils.mkdir_p("TestProj/#{context}/values-#{language}") + File.write("TestProj/#{context}/values-#{language}/strings.xml", "") + end end - FileUtils.mkdir_p(dirname) - File.write("#{dirname}/strings.xml", "") end - stub_api_export "en", %{"greeting" = "Hi, %s!";} - stub_api_export "ko", %{"greeting" = "%s님 안녕하세요!";} - stub_api_export "zh-CN", %{"greeting" = "Simplified 你好, %s!";} - stub_api_export "zh-TW", %{"greeting" = "Traditional 你好, %s!";} + stub_api_export "en", %{[ + {"term": "greeting", "definition": "Hi, %s!", "context": ""}, + {"term": "welcome", "definition": "Welcome!", "context": ""}, + {"term": "welcome", "definition": "Welcome to context1!", "context": "context1"}, + {"term": "welcome", "definition": "Welcome to context2!", "context": "context2"} + ]} + stub_api_export "nl", %{[ + {"term": "welcome", "definition": "Welkom!", "context": ""}, + {"term": "welcome", "definition": "Welkom bij context1!", "context": "context1"}, + {"term": "welcome", "definition": "Welkom bij context2!", "context": "context2"} + ]} + stub_api_export "ko", %{[{"term": "greeting", "definition": "%s님 안녕하세요!", "context": ""}]} + stub_api_export "zh-CN", %{[{"term": "greeting", "definition": "Simplified 你好, %s!", "context": ""}]} + stub_api_export "zh-TW", %{[{"term": "greeting", "definition": "Traditional 你好, %s!", "context": ""}]} end def teardown @@ -33,9 +65,9 @@ def teardown clean() end - def get_client(type:, - languages:, language_alias:nil, - path:, path_replace:nil) + def get_client(type:, languages:, language_alias:nil, + path:, path_replace:nil, + context_path:nil, context_path_replace:nil) configuration = POEditor::Configuration.new( :api_key => "TEST", :project_id => 12345, @@ -44,8 +76,10 @@ def get_client(type:, :filters => nil, :languages => languages, :language_alias => language_alias, - :path_replace => path_replace, :path => path, + :path_replace => path_replace, + :context_path => context_path, + :context_path_replace => context_path_replace ) POEditor::Core.new(configuration) end @@ -114,4 +148,32 @@ def test_pull_path_replace File.read("TestProj/values/strings.xml") end + def test_context + client = get_client( + :type => "android_strings", + :languages => ["en", "nl"], + :path => "TestProj/values-{LANGUAGE}/strings.xml", + :path_replace => {"en" => "TestProj/values/strings.xml"}, + :context_path => "TestProj/{CONTEXT}/values-{LANGUAGE}/strings.xml", + :context_path_replace => {"en" => "TestProj/{CONTEXT}/values/strings.xml"} + ) + client.pull() + + assert_match /Welcome!/, File.read("TestProj/values/strings.xml") + assert_match /Welcome to context1!/, File.read("TestProj/context1/values/strings.xml") + assert_match /Welcome to context2!/, File.read("TestProj/context2/values/strings.xml") + + assert_match /Welkom!/, File.read("TestProj/values-nl/strings.xml") + assert_match /Welkom bij context1!/, File.read("TestProj/context1/values-nl/strings.xml") + assert_match /Welkom bij context2!/, File.read("TestProj/context2/values-nl/strings.xml") + + assert(!/Welcome!/.match(File.read("TestProj/values-nl/strings.xml"))) + assert(!/Welcome!/.match(File.read("TestProj/context1/values/strings.xml"))) + assert(!/Welcome!/.match(File.read("TestProj/context1/values-nl/strings.xml"))) + + assert(!/Welkom!/.match(File.read("TestProj/values/strings.xml"))) + assert(!/Welkom!/.match(File.read("TestProj/context1/values/strings.xml"))) + assert(!/Welkom!/.match(File.read("TestProj/context1/values-nl/strings.xml"))) + end + end From 8329370abc41539f4c15a488bd951db6a88635f8 Mon Sep 17 00:00:00 2001 From: Tomas Stanik Date: Tue, 9 Jun 2020 15:44:49 +0200 Subject: [PATCH 03/13] Escape quotes in definition --- lib/poeditor/core.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index 1607568..5d9ac52 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -107,7 +107,7 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) def appleStrings(json) content = "" json.each { |item| - definition = item["definition"] + definition = item["definition"].gsub("\"", "\\\"") content << "\"#{item["term"]}\" = \"#{definition}\";\n" } return content @@ -116,7 +116,7 @@ def appleStrings(json) def androidStrings(json) content = "\n\n" json.each { |item| - definition = item["definition"] + definition = item["definition"].gsub("\"", "\\\"") content << " \"#{definition}\"\n" } content << "\n" From 969d3c3922294184191daef8a2c4ce6dd1f70c3b Mon Sep 17 00:00:00 2001 From: Tomas Stanik Date: Tue, 9 Jun 2020 17:27:20 +0200 Subject: [PATCH 04/13] Add support for placeholders --- lib/poeditor/core.rb | 52 ++++++++++++++++++++++++++++++++++++++++++-- test/test_core.rb | 39 ++++++++++++++++++++++++++------- 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index 5d9ac52..0041222 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -82,9 +82,23 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) json = JSON.parse content groups = json.group_by { |json| json['context'] } + placeholderItems = [] groups.each do |context, json| - if context != nil && context != "" && @configuration.context_path == nil - next # if context path is not defined, skip saving context strings + if context == "" + json.each { |item| + definition = item["definition"] + if definition =~ /\$([a-z_]+)/ + placeholderItems << item + end + } + end + end + groups.each do |context, json| + if context != "" + if @configuration.context_path == nil + next # if context path is not defined, skip saving context strings + end + copyPlaceholderItems(placeholderItems, context, json) end case type @@ -104,6 +118,40 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) end end + # Copy items with replaced placeholders to context json + # + # @param items [JSON] + # @param context String + # @param contextJson JSON + # + def copyPlaceholderItems(items, context, contextJson) + items.each { |item| + term = item["term"] + definition = item["definition"].gsub(/\$([a-z_]+)/) { |placeholder| + definitionForPlaceholder(placeholder, contextJson) + } + + if !contextJson.find { |e| e["term"] == term } + newItem = { + "term" => term, + "definition" => definition, + "context" => context + } + contextJson << newItem + end + } + end + + def definitionForPlaceholder(placeholder, contextJson) + term = placeholder.delete_prefix("$") + contextJson.each { |item| + if item["term"] == term + return item["definition"] + end + } + return placeholder + end + def appleStrings(json) content = "" json.each { |item| diff --git a/test/test_core.rb b/test/test_core.rb index 90dd057..7c32f9c 100644 --- a/test/test_core.rb +++ b/test/test_core.rb @@ -47,13 +47,19 @@ def setup stub_api_export "en", %{[ {"term": "greeting", "definition": "Hi, %s!", "context": ""}, {"term": "welcome", "definition": "Welcome!", "context": ""}, - {"term": "welcome", "definition": "Welcome to context1!", "context": "context1"}, - {"term": "welcome", "definition": "Welcome to context2!", "context": "context2"} + {"term": "welcome", "definition": "Welcome to App 1!", "context": "context1"}, + {"term": "welcome", "definition": "Welcome to App 2!", "context": "context2"}, + {"term": "thank_you", "definition": "Thank you for downloading $app_name.", "context": ""}, + {"term": "app_name", "definition": "App 1 in EN", "context": "context1"}, + {"term": "app_name", "definition": "App 2 in EN", "context": "context2"} ]} stub_api_export "nl", %{[ {"term": "welcome", "definition": "Welkom!", "context": ""}, - {"term": "welcome", "definition": "Welkom bij context1!", "context": "context1"}, - {"term": "welcome", "definition": "Welkom bij context2!", "context": "context2"} + {"term": "welcome", "definition": "Welkom bij App 1!", "context": "context1"}, + {"term": "welcome", "definition": "Welkom bij App 2!", "context": "context2"}, + {"term": "thank_you", "definition": "Bedankt voor het downloaden van $app_name.", "context": ""}, + {"term": "app_name", "definition": "App 1 in NL", "context": "context1"}, + {"term": "app_name", "definition": "App 2 in NL", "context": "context2"} ]} stub_api_export "ko", %{[{"term": "greeting", "definition": "%s님 안녕하세요!", "context": ""}]} stub_api_export "zh-CN", %{[{"term": "greeting", "definition": "Simplified 你好, %s!", "context": ""}]} @@ -160,12 +166,12 @@ def test_context client.pull() assert_match /Welcome!/, File.read("TestProj/values/strings.xml") - assert_match /Welcome to context1!/, File.read("TestProj/context1/values/strings.xml") - assert_match /Welcome to context2!/, File.read("TestProj/context2/values/strings.xml") + assert_match /Welcome to App 1!/, File.read("TestProj/context1/values/strings.xml") + assert_match /Welcome to App 2!/, File.read("TestProj/context2/values/strings.xml") assert_match /Welkom!/, File.read("TestProj/values-nl/strings.xml") - assert_match /Welkom bij context1!/, File.read("TestProj/context1/values-nl/strings.xml") - assert_match /Welkom bij context2!/, File.read("TestProj/context2/values-nl/strings.xml") + assert_match /Welkom bij App 1!/, File.read("TestProj/context1/values-nl/strings.xml") + assert_match /Welkom bij App 2!/, File.read("TestProj/context2/values-nl/strings.xml") assert(!/Welcome!/.match(File.read("TestProj/values-nl/strings.xml"))) assert(!/Welcome!/.match(File.read("TestProj/context1/values/strings.xml"))) @@ -176,4 +182,21 @@ def test_context assert(!/Welkom!/.match(File.read("TestProj/context1/values-nl/strings.xml"))) end + def test_placeholders + client = get_client( + :type => "android_strings", + :languages => ["en", "nl"], + :path => "TestProj/values-{LANGUAGE}/strings.xml", + :path_replace => {"en" => "TestProj/values/strings.xml"}, + :context_path => "TestProj/{CONTEXT}/values-{LANGUAGE}/strings.xml", + :context_path_replace => {"en" => "TestProj/{CONTEXT}/values/strings.xml"} + ) + client.pull() + + assert_match /Thank you for downloading App 1 in EN./, File.read("TestProj/context1/values/strings.xml") + assert_match /Thank you for downloading App 2 in EN./, File.read("TestProj/context2/values/strings.xml") + assert_match /Bedankt voor het downloaden van App 1 in NL./, File.read("TestProj/context1/values-nl/strings.xml") + assert_match /Bedankt voor het downloaden van App 2 in NL./, File.read("TestProj/context2/values-nl/strings.xml") + end + end From 78526676954455fb9578e3853f79a3e41d445439 Mon Sep 17 00:00:00 2001 From: Tomas Stanik Date: Mon, 22 Jun 2020 09:12:28 +0200 Subject: [PATCH 05/13] Export plural forms --- lib/poeditor/commands/pull_command.rb | 4 +- lib/poeditor/configuration.rb | 14 ++- lib/poeditor/core.rb | 131 ++++++++++++++++++++++---- test/test_core.rb | 27 +++--- 4 files changed, 142 insertions(+), 34 deletions(-) diff --git a/lib/poeditor/commands/pull_command.rb b/lib/poeditor/commands/pull_command.rb index a0ddbab..ec43c7e 100644 --- a/lib/poeditor/commands/pull_command.rb +++ b/lib/poeditor/commands/pull_command.rb @@ -1,9 +1,7 @@ module POEditor class PullCommand def run(argv) - UI.puts "Reading configuration" configuration = get_configuration(argv) - UI.puts configuration client = POEditor::Core.new(configuration) client.pull() end @@ -46,8 +44,10 @@ def get_configuration(argv) languages: get_or_raise(yaml, "languages"), language_alias: yaml["language_alias"], path: get_or_raise(yaml, "path"), + path_plural: yaml["path_plural"], path_replace: yaml["path_replace"], context_path: yaml["context_path"], + context_path_plural: yaml["context_path_plural"], context_path_replace: yaml["context_path_replace"] ) end diff --git a/lib/poeditor/configuration.rb b/lib/poeditor/configuration.rb index e41096c..be878d8 100644 --- a/lib/poeditor/configuration.rb +++ b/lib/poeditor/configuration.rb @@ -25,19 +25,25 @@ class Configuration # @return [String] The path template attr_accessor :path + # @return [String] The plural path template + attr_accessor :path_plural + # @return [Hash{Sting => String}] The path replacements attr_accessor :path_replace # @return [String] The context path template attr_accessor :context_path + # @return [String] The plural context path template + attr_accessor :context_path_plural + # @return [Hash{Sting => String}] The context path replacements attr_accessor :context_path_replace def initialize(api_key:, project_id:, type:, tags:nil, filters:nil, languages:, language_alias:nil, - path:, path_replace:nil, - context_path:nil, context_path_replace:nil) + path:, path_plural: nil, path_replace:nil, + context_path:nil, context_path_plural:nil, context_path_replace:nil) @api_key = from_env(api_key) @project_id = from_env(project_id.to_s) @type = type @@ -48,9 +54,11 @@ def initialize(api_key:, project_id:, type:, tags:nil, @language_alias = language_alias || {} @path = path + @path_plural = path_plural || {} @path_replace = path_replace || {} @context_path = context_path + @context_path_plural = context_path_plural || {} @context_path_replace = context_path_replace || {} end @@ -71,8 +79,10 @@ def to_s "languages" => self.languages, "language_alias" => self.language_alias, "path" => self.path, + "path_plural" => self.path_plaural, "path_replace" => self.path_replace, "context_path" => self.context_path, + "context_path_plural" => self.context_path_plural, "context_path_replace" => self.context_path_replace, } YAML.dump(values)[4..-2] diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index 0041222..91cdf08 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -87,7 +87,7 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) if context == "" json.each { |item| definition = item["definition"] - if definition =~ /\$([a-z_]+)/ + if definition =~ /\$([a-z_]{3,})/ placeholderItems << item end } @@ -103,17 +103,17 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) case type when "apple_strings" - content = appleStrings(json) - when "android_strings" - content = androidStrings(json) - end + singularContent = singularAppleStrings(json) + write(context, language, singularContent, :singular) - write(context, language, content) - - for alias_to, alias_from in @configuration.language_alias - if language == alias_from - write(context, alias_to, content) + if @configuration.path_plural != {} + pluralContent = pluralAppleStrings(json) + write(context, language, pluralContent, :plural) end + when "android_strings" + content = androidStrings(json) + path = path_for_context_language(context, language) + write(context, language, content, :singular) end end end @@ -127,7 +127,7 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) def copyPlaceholderItems(items, context, contextJson) items.each { |item| term = item["term"] - definition = item["definition"].gsub(/\$([a-z_]+)/) { |placeholder| + definition = item["definition"].gsub(/\$([a-z_]{3,})/) { |placeholder| definitionForPlaceholder(placeholder, contextJson) } @@ -152,25 +152,94 @@ def definitionForPlaceholder(placeholder, contextJson) return placeholder end - def appleStrings(json) + def singularAppleStrings(json) content = "" json.each { |item| - definition = item["definition"].gsub("\"", "\\\"") - content << "\"#{item["term"]}\" = \"#{definition}\";\n" + term = item["term"] + definition = item["definition"] + if definition.instance_of? String + value = definition.gsub("\"", "\\\"") + content << "\"#{term}\" = \"#{value}\";\n" + end } return content end + def pluralAppleStrings(json) + content = " + + + +" + json.each { |item| + term = item["term"] + definition = item["definition"] + if definition.instance_of? Hash + content << " + #{term} + + NSStringLocalizedFormatKey + %\#@VARIABLE@ + VARIABLE + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d" + ["zero", "one", "two", "few", "many", "other"].each { |form| + if definition[form] != nil + value = definition[form].gsub("\"", "\\\"") + content << " + #{form} + #{value}" + else + content << " + #{form} + " + end + } + content << " + + " + end + } + content << " + +" + return content + end + def androidStrings(json) content = "\n\n" json.each { |item| - definition = item["definition"].gsub("\"", "\\\"") - content << " \"#{definition}\"\n" + definition = item["definition"] + if definition.instance_of? String + value = definition.gsub("\"", "\\\"").gsub("&", "&") + content << " \"#{value}\"\n" + else + content << " \n" + ["zero", "one", "two", "few", "many", "other"].each { |form| + pluralItem = androidPluralItem(definition, form) + if pluralItem != nil + content << pluralItem + end + } + content << " \n" + end } content << "\n" return content end + def androidPluralItem(definition, form) + if definition[form] != nil + value = definition[form].gsub("\"", "\\\"").gsub("&", "&") + return " \"#{value}\"\n" + else + return nil + end + end + def convert_to_poeditor_language(language) if language.downcase.match(/zh.+(hans|cn)/) 'zh-CN' @@ -181,9 +250,23 @@ def convert_to_poeditor_language(language) end end + def write(context, language, content, plurality) + write_content_to_path(context, language, content, plurality) + for alias_to, alias_from in @configuration.language_alias + if language == alias_from + write_content_to_path(context, alias_to, content, plurality) + end + end + end + # Write translation file - def write(context, language, content) - path = path_for_context_language(context, language) + def write_content_to_path(context, language, content, plurality) + case plurality + when :singular + path = path_for_context_language(context, language) + when :plural + path = path_plural_for_context_language(context, language) + end unless path != nil raise POEditor::Exception.new "Undefined context path" @@ -213,5 +296,17 @@ def path_for_context_language(context, language) end end end + + def path_plural_for_context_language(context, language) + if context == nil || context == "" + path = @configuration.path_plural.gsub("{LANGUAGE}", language) + else + if @configuration.context_path_plural + path = @configuration.context_path_plural.gsub("{LANGUAGE}", language).gsub("{CONTEXT}", context) + else + return nil + end + end + end end end diff --git a/test/test_core.rb b/test/test_core.rb index 7c32f9c..0bef02e 100644 --- a/test/test_core.rb +++ b/test/test_core.rb @@ -18,9 +18,10 @@ def setup for language in ios_languages FileUtils.mkdir_p("TestProj/#{language}.lproj") File.write("TestProj/#{language}.lproj/Localizable.strings", "") + File.write("TestProj/#{language}.lproj/Localizable.stringsdict", "") for context in contexts - FileUtils.mkdir_p("TestProj/#{context}/#{language}.lproj") - File.write("TestProj/#{context}/#{language}.lproj/Localizable.strings", "") + File.write("TestProj/#{language}.lproj/#{context}.strings", "") + File.write("TestProj/#{language}.lproj/#{context}.stringsdict", "") end end @@ -50,16 +51,16 @@ def setup {"term": "welcome", "definition": "Welcome to App 1!", "context": "context1"}, {"term": "welcome", "definition": "Welcome to App 2!", "context": "context2"}, {"term": "thank_you", "definition": "Thank you for downloading $app_name.", "context": ""}, - {"term": "app_name", "definition": "App 1 in EN", "context": "context1"}, - {"term": "app_name", "definition": "App 2 in EN", "context": "context2"} + {"term": "app_name", "definition": "App 1 in 🇬🇧", "context": "context1"}, + {"term": "app_name", "definition": "App 2 in 🇬🇧", "context": "context2"} ]} stub_api_export "nl", %{[ {"term": "welcome", "definition": "Welkom!", "context": ""}, {"term": "welcome", "definition": "Welkom bij App 1!", "context": "context1"}, {"term": "welcome", "definition": "Welkom bij App 2!", "context": "context2"}, {"term": "thank_you", "definition": "Bedankt voor het downloaden van $app_name.", "context": ""}, - {"term": "app_name", "definition": "App 1 in NL", "context": "context1"}, - {"term": "app_name", "definition": "App 2 in NL", "context": "context2"} + {"term": "app_name", "definition": "App 1 in 🇳🇱", "context": "context1"}, + {"term": "app_name", "definition": "App 2 in 🇳🇱", "context": "context2"} ]} stub_api_export "ko", %{[{"term": "greeting", "definition": "%s님 안녕하세요!", "context": ""}]} stub_api_export "zh-CN", %{[{"term": "greeting", "definition": "Simplified 你好, %s!", "context": ""}]} @@ -72,8 +73,8 @@ def teardown end def get_client(type:, languages:, language_alias:nil, - path:, path_replace:nil, - context_path:nil, context_path_replace:nil) + path:, path_plural:nil, path_replace:nil, + context_path:nil, context_path_plural:nil, context_path_replace:nil) configuration = POEditor::Configuration.new( :api_key => "TEST", :project_id => 12345, @@ -83,8 +84,10 @@ def get_client(type:, languages:, language_alias:nil, :languages => languages, :language_alias => language_alias, :path => path, + :path_plural => path_plural, :path_replace => path_replace, :context_path => context_path, + :context_path_plural => context_path_plural, :context_path_replace => context_path_replace ) POEditor::Core.new(configuration) @@ -193,10 +196,10 @@ def test_placeholders ) client.pull() - assert_match /Thank you for downloading App 1 in EN./, File.read("TestProj/context1/values/strings.xml") - assert_match /Thank you for downloading App 2 in EN./, File.read("TestProj/context2/values/strings.xml") - assert_match /Bedankt voor het downloaden van App 1 in NL./, File.read("TestProj/context1/values-nl/strings.xml") - assert_match /Bedankt voor het downloaden van App 2 in NL./, File.read("TestProj/context2/values-nl/strings.xml") + assert_match /Thank you for downloading App 1 in 🇬🇧./, File.read("TestProj/context1/values/strings.xml") + assert_match /Thank you for downloading App 2 in 🇬🇧./, File.read("TestProj/context2/values/strings.xml") + assert_match /Bedankt voor het downloaden van App 1 in 🇳🇱./, File.read("TestProj/context1/values-nl/strings.xml") + assert_match /Bedankt voor het downloaden van App 2 in 🇳🇱./, File.read("TestProj/context2/values-nl/strings.xml") end end From cbfffe73d47cc2a9aafce252b9a72b54ce271f78 Mon Sep 17 00:00:00 2001 From: Tomas Stanik Date: Wed, 12 Aug 2020 09:55:07 +0200 Subject: [PATCH 06/13] Skip missing files --- lib/poeditor/core.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index 91cdf08..8d6b154 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -272,11 +272,12 @@ def write_content_to_path(context, language, content, plurality) raise POEditor::Exception.new "Undefined context path" end - unless File.exist?(path) - raise POEditor::Exception.new "#{path} doesn't exist" + if File.exist?(path) + File.write(path, content) + UI.puts " #{"\xe2\x9c\x93".green} Saved at '#{path}'" + else + UI.puts " #{"\xe2\x9c\x97".red} File not found '#{path}'" end - File.write(path, content) - UI.puts " #{"\xe2\x9c\x93".green} Saved at '#{path}'" end def path_for_context_language(context, language) From 6e34db0bba1b6cff35634621312aa1dacdaefc7e Mon Sep 17 00:00:00 2001 From: Tomas Stanik Date: Wed, 11 Nov 2020 15:28:11 +0100 Subject: [PATCH 07/13] Generate kotlin constants --- lib/poeditor/commands/pull_command.rb | 1 + lib/poeditor/configuration.rb | 9 +++++-- lib/poeditor/core.rb | 36 ++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/lib/poeditor/commands/pull_command.rb b/lib/poeditor/commands/pull_command.rb index ec43c7e..4971688 100644 --- a/lib/poeditor/commands/pull_command.rb +++ b/lib/poeditor/commands/pull_command.rb @@ -41,6 +41,7 @@ def get_configuration(argv) type: get_or_raise(yaml, "type"), tags: yaml["tags"], filters: yaml["filters"], + header: yaml["header"], languages: get_or_raise(yaml, "languages"), language_alias: yaml["language_alias"], path: get_or_raise(yaml, "path"), diff --git a/lib/poeditor/configuration.rb b/lib/poeditor/configuration.rb index be878d8..c0d97a3 100644 --- a/lib/poeditor/configuration.rb +++ b/lib/poeditor/configuration.rb @@ -15,6 +15,9 @@ class Configuration # @return [Array] Filters by 'translated', 'untranslated', 'fuzzy', 'not_fuzzy', 'automatic', 'not_automatic', 'proofread', 'not_proofread' (optional) attr_accessor :filters + + # @return [Hash{Sting => String}] Header (optional) + attr_accessor :header # @return [Array] The languages codes attr_accessor :languages @@ -40,8 +43,8 @@ class Configuration # @return [Hash{Sting => String}] The context path replacements attr_accessor :context_path_replace - def initialize(api_key:, project_id:, type:, tags:nil, - filters:nil, languages:, language_alias:nil, + def initialize(api_key:, project_id:, type:, tags:nil, filters:nil, + header:nil, languages:, language_alias:nil, path:, path_plural: nil, path_replace:nil, context_path:nil, context_path_plural:nil, context_path_replace:nil) @api_key = from_env(api_key) @@ -49,6 +52,7 @@ def initialize(api_key:, project_id:, type:, tags:nil, @type = type @tags = tags || [] @filters = filters || [] + @header = header @languages = languages @language_alias = language_alias || {} @@ -76,6 +80,7 @@ def to_s "type" => self.type, "tags" => self.tags, "filters" => self.filters, + "header" => self.header, "languages" => self.languages, "language_alias" => self.language_alias, "path" => self.path, diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index 8d6b154..2ca7a5b 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -40,7 +40,8 @@ def pull() :language => language, :type => @configuration.type, :tags => @configuration.tags, - :filters => @configuration.filters) + :filters => @configuration.filters, + :header => @configuration.header) end end @@ -52,9 +53,10 @@ def pull() # @param type [String] # @param tags [Array] # @param filters [Array] + # @param header [String] # # @return Downloaded translation content - def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) + def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil, header:nil) options = { "id" => project_id, "language" => convert_to_poeditor_language(language), @@ -76,7 +78,7 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) case type when "apple_strings" content.gsub!(/(%(\d+\$)?)s/, '\1@') # %s -> %@ - when "android_strings" + when "android_strings", "kotlin_strings" content.gsub!(/(%(\d+\$)?)@/, '\1s') # %@ -> %s end @@ -114,6 +116,10 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil) content = androidStrings(json) path = path_for_context_language(context, language) write(context, language, content, :singular) + when "kotlin_strings" + content = kotlinStrings(json, header) + path = path_for_context_language(context, language) + write(context, language, content, :singular) end end end @@ -215,21 +221,39 @@ def androidStrings(json) definition = item["definition"] if definition.instance_of? String value = definition.gsub("\"", "\\\"").gsub("&", "&") - content << " \"#{value}\"\n" + content << " \"#{value}\"\n" else - content << " \n" + content << " \n" ["zero", "one", "two", "few", "many", "other"].each { |form| pluralItem = androidPluralItem(definition, form) if pluralItem != nil content << pluralItem end } - content << " \n" + content << " \n" end } content << "\n" return content end + + def kotlinStrings(json, header) + content = "" + if header != nil + content << "#{header}\n\n" + end + content << "object Strings {\n" + json.each { |item| + content << " val #{snakeCaseToCamelCase(item["term"])} = \"#{item["term"]}\".localized()\n" + } + content << "}\n" + return content + end + + def snakeCaseToCamelCase(text) + words = text.split('_') + return words[0] + words[1..-1].collect(&:capitalize).join + end def androidPluralItem(definition, form) if definition[form] != nil From 82ec64fb7038430db53e304c5ae9fa97d3857b6c Mon Sep 17 00:00:00 2001 From: Tomas Stanik Date: Thu, 26 Nov 2020 15:29:50 +0100 Subject: [PATCH 08/13] Initialize strings by lazy --- lib/poeditor/core.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index 2ca7a5b..5a896b9 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -240,9 +240,16 @@ def androidStrings(json) def kotlinStrings(json, header) content = "" if header != nil - content << "#{header}\n\n" + content << "#{header}\n" end - content << "object Strings {\n" + content << " +class Strings { + companion object { + val Strings by lazy { + Strings() + } + }\n +" json.each { |item| content << " val #{snakeCaseToCamelCase(item["term"])} = \"#{item["term"]}\".localized()\n" } From 06e126a093a0b4676fed64bd8bb94c57e97d097e Mon Sep 17 00:00:00 2001 From: Tomas Stanik Date: Thu, 26 Nov 2020 17:18:17 +0100 Subject: [PATCH 09/13] Ignore untranslated strings --- lib/poeditor/core.rb | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index 5a896b9..e2d91b7 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -219,18 +219,20 @@ def androidStrings(json) content = "\n\n" json.each { |item| definition = item["definition"] - if definition.instance_of? String - value = definition.gsub("\"", "\\\"").gsub("&", "&") - content << " \"#{value}\"\n" - else - content << " \n" - ["zero", "one", "two", "few", "many", "other"].each { |form| - pluralItem = androidPluralItem(definition, form) - if pluralItem != nil - content << pluralItem - end - } - content << " \n" + if definition != nil + if definition.instance_of? String + value = definition.gsub("\"", "\\\"").gsub("&", "&") + content << " \"#{value}\"\n" + else + content << " \n" + ["zero", "one", "two", "few", "many", "other"].each { |form| + pluralItem = androidPluralItem(definition, form) + if pluralItem != nil + content << pluralItem + end + } + content << " \n" + end end } content << "\n" From d8f8a23ebc00fc69f3fa64ffa32c9b7cca9fe012 Mon Sep 17 00:00:00 2001 From: Tomas Stanik Date: Wed, 3 Mar 2021 18:06:53 +0100 Subject: [PATCH 10/13] Load strings separately --- lib/poeditor/core.rb | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index e2d91b7..13d167d 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -244,16 +244,13 @@ def kotlinStrings(json, header) if header != nil content << "#{header}\n" end - content << " -class Strings { - companion object { - val Strings by lazy { - Strings() - } - }\n + content << "import kotlin.native.concurrent.ThreadLocal + +@ThreadLocal +object Strings { " json.each { |item| - content << " val #{snakeCaseToCamelCase(item["term"])} = \"#{item["term"]}\".localized()\n" + content << " val #{snakeCaseToCamelCase(item["term"])} by lazy { \"#{item["term"]}\".localized() }\n" } content << "}\n" return content From edf5aa09c4dfead151ed048a80b3ebda4ad44396 Mon Sep 17 00:00:00 2001 From: Grigory Avdyushin Date: Tue, 9 Mar 2021 17:04:34 +0100 Subject: [PATCH 11/13] Fix invalid stringdicts --- lib/poeditor/core.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index 13d167d..31a3295 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -172,8 +172,7 @@ def singularAppleStrings(json) end def pluralAppleStrings(json) - content = " - + content = " " From e561c33a1cd6d3b138458fb46d28a56387f93bdd Mon Sep 17 00:00:00 2001 From: Grigory Avdyushin Date: Tue, 9 Mar 2021 19:49:05 +0100 Subject: [PATCH 12/13] Added plurals for Kaluga quantity --- lib/poeditor/core.rb | 55 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/lib/poeditor/core.rb b/lib/poeditor/core.rb index 31a3295..aa6b4d0 100644 --- a/lib/poeditor/core.rb +++ b/lib/poeditor/core.rb @@ -86,7 +86,7 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil, heade groups = json.group_by { |json| json['context'] } placeholderItems = [] groups.each do |context, json| - if context == "" + if context == "" json.each { |item| definition = item["definition"] if definition =~ /\$([a-z_]{3,})/ @@ -96,7 +96,7 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil, heade end end groups.each do |context, json| - if context != "" + if context != "" if @configuration.context_path == nil next # if context path is not defined, skip saving context strings end @@ -120,6 +120,10 @@ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil, heade content = kotlinStrings(json, header) path = path_for_context_language(context, language) write(context, language, content, :singular) + if @configuration.path_plural != {} + pluralContent = pluralKotlinStrings(json, header) + write(context, language, pluralContent, :plural) + end end end end @@ -134,7 +138,7 @@ def copyPlaceholderItems(items, context, contextJson) items.each { |item| term = item["term"] definition = item["definition"].gsub(/\$([a-z_]{3,})/) { |placeholder| - definitionForPlaceholder(placeholder, contextJson) + definitionForPlaceholder(placeholder, contextJson) } if !contextJson.find { |e| e["term"] == term } @@ -222,7 +226,7 @@ def androidStrings(json) if definition.instance_of? String value = definition.gsub("\"", "\\\"").gsub("&", "&") content << " \"#{value}\"\n" - else + else content << " \n" ["zero", "one", "two", "few", "many", "other"].each { |form| pluralItem = androidPluralItem(definition, form) @@ -237,31 +241,58 @@ def androidStrings(json) content << "\n" return content end - + def kotlinStrings(json, header) content = "" if header != nil - content << "#{header}\n" + content << "#{header}\n\n" end - content << "import kotlin.native.concurrent.ThreadLocal + content << "import com.splendo.kaluga.resources.localized +import kotlin.native.concurrent.ThreadLocal @ThreadLocal object Strings { " json.each { |item| - content << " val #{snakeCaseToCamelCase(item["term"])} by lazy { \"#{item["term"]}\".localized() }\n" + term = item["term"] + definition = item["definition"] + if definition.instance_of? String + content << " val #{snakeCaseToCamelCase(term)} by lazy { \"#{term}\".localized() }\n" + end } content << "}\n" return content end - + + def pluralKotlinStrings(json, header) + content = "" + if header != nil + content << "#{header}\n\n" + end + content << "import com.splendo.kaluga.resources.quantity +import kotlin.native.concurrent.ThreadLocal + +@ThreadLocal +object Plurals { +" + json.each { |item| + term = item["term"] + definition = item["definition"] + if definition.instance_of? Hash + content << " fun #{snakeCaseToCamelCase(term)}(value: Int): String { return \"#{term}\".quantity(value) }\n" + end + } + content << "}\n" + return content + end + def snakeCaseToCamelCase(text) - words = text.split('_') - return words[0] + words[1..-1].collect(&:capitalize).join + words = text.split('_') + return words[0] + words[1..-1].collect(&:capitalize).join end def androidPluralItem(definition, form) - if definition[form] != nil + if definition[form] != nil value = definition[form].gsub("\"", "\\\"").gsub("&", "&") return " \"#{value}\"\n" else From 0fbe5d9ca3710718fc58ca982f6365ad36844b5a Mon Sep 17 00:00:00 2001 From: Grigory Avdyushin Date: Wed, 10 Mar 2021 08:05:33 +0100 Subject: [PATCH 13/13] Update README.md --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index a085a64..1622022 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ Command line application for [POEditor](https://poeditor.com). $ [sudo] gem install poeditor-cli ``` +Install from given repo and branch: + +```console +$ [sudo] gem specific_install https://github.com/splendo/poeditor-cli -b kotlin-plurals +``` + ## Usage A single command will do almost everything for you. @@ -121,6 +127,30 @@ path_replace: en: myapp/src/main/res/values/strings.xml ``` +* Multiplatform project + +For Kotlin and [Kaluga](https://github.com/splendo/kaluga) + + ```yaml + api_key: $POEDITOR_API_KEY + project_id: $POEDITOR_PROJECT_ID + type: kotlin_strings + languages: [en] + + header: "package my.project.models" + path: 'shared/src/commonMain/kotlin/my/project/models/Strings.kt' + path_plural: 'shared/src/commonMain/kotlin/my/project/models/Plurals.kt' + ``` + +Where header is your package for generated objects. + +Usage: + + ```kotlin + val myString = Strings.myAwesomeString + val myPlural = Plurals.myAwesomePlural(someIntValue) + ``` + * Projects using gettext ```yaml