poeditor-pull 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b0f1ce426ca44d45275e5dfea07c7196dd55e65b
4
+ data.tar.gz: 9b72ddf1d38c7ba1b3862a7edf9552ebc2921a03
5
+ SHA512:
6
+ metadata.gz: f98cb2d867e2982a0047c5e0b60b42a989d325a3373f025945527654528b19a4933351e73e9caf396b0bc33ff647707212dc5ab8f0a67879460328e16c07ad7c
7
+ data.tar.gz: b4a9c22b645bd2af056b483e61d79f233ea9e665bc517c13c3e7e8265add5641a053b704d1599683143efc65ea30e33eaad48c5f779d04b9087100b58b44f3ee
@@ -0,0 +1,10 @@
1
+ This tool has been forked from https://github.com/StyleShare/poeditor-cli (v.0.5.0) and adjusted.
2
+
3
+ List of changes introduced to source codebase:
4
+
5
+ 1. Changed format of input config file: YAML -> JSON
6
+ 2. Added: creating missing directory structures on the fly in case new translations appear while pulling
7
+ 3. Added: logic filtering out languages with no translations
8
+ 4. Added: support for project main dir definition and relative translations paths in config
9
+ 5. Added: formatting paths with target/flavor name based on {TARGET_NAME} as parameter.
10
+ 6. Implemented language-region combination support [Android] and Chinese language coding support
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Suyeol Jeon (xoul.kr)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,49 @@
1
+ POEditor scripts have been provided in order to speed up process of pulling new translations into mobile projects.
2
+ They are written in Ruby, support both mobile platforms: Android, iOS and are intendend to be shared across them.
3
+
4
+ There are 2 ways to use provided functionality:
5
+
6
+ 1. Instantiating `PullCommand` class instance directly in your Ruby code and calling its methods accordingly to API.
7
+ 2. Installing Ruby gem: `[sudo] gem install poeditor-pull` and calling `poeditor-pull` from command line with arguments and treating it as a black box with no need to write any line in Ruby to take advantage of it.
8
+
9
+ Both ways require preparing and providing POEditor config JSON file that defines project setup as shown in the example below:
10
+
11
+ ```json
12
+ {
13
+ "api_key": "$POEDITOR_API_KEY",
14
+ "project_id": 11111,
15
+ "type": "android_strings",
16
+ "languages": [
17
+ "en-us",
18
+ "en-gb",
19
+ "es",
20
+ "pt-Br"
21
+ ],
22
+ "filters": [
23
+ "translated"
24
+ ],
25
+ "path": "path/to/your/project/src/main/res/values-{LANGUAGE}/po_strings.xml",
26
+ "path_replace": {
27
+ "en-us": "path/to/your/project/src/main/res/values/po_strings.xml"
28
+ }
29
+ }
30
+ ```
31
+
32
+ `"api_key"` can be defined directly in JSON or defined as environment variable: `POEDITOR_API_KEY`
33
+
34
+ `"path"` and `"path_replace"` paths can be defined as both: absolute and relative. If they are relative
35
+ you also need to define project main directory while initializing `PullCommand` class or calling
36
+ `poeditor-pull` script with `-p` parameter.
37
+
38
+ ```
39
+ Usage: poeditor-pull [options]
40
+ -c, --config-path CONFIG_PATH Path to your POEditor JSON config
41
+ -p, --project-path PROJECT_PATH Path to your project's main directory [optional]
42
+ ```
43
+
44
+ JSON supports defitions based on POEDitor v2 API like: `type, tags, filters` etc. For further information regarding that visit:
45
+ https://poeditor.com/docs/api and https://github.com/StyleShare/poeditor-cli (keep in mind YAML -> JSON change)
46
+
47
+ As it's stated in `CHANGELOG` - scripts are based on POEditor Ruby CLI tool: https://github.com/StyleShare/poeditor-cli
48
+ and support everything what's supported in v.0.5.0. In addition to that there were new functionalites implemented.
49
+ For more information regarding introduced adjustments please review `CHANGELOG` file.
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # If path to project main directory is missing then paths in JSON config are considered to be absolute
4
+ # and are used directly. In other case target path is a result of concatenation of project main dir path
5
+ # and relative paths in JSON config.
6
+
7
+ require 'fileutils'
8
+ require 'optparse'
9
+ require 'PullCommand'
10
+ require 'UI'
11
+ require 'Version'
12
+
13
+ include POEditor
14
+
15
+ options = {}
16
+ OptionParser.new do |parser|
17
+ parser.on_tail('-v', '--version', 'Show version') do
18
+ puts POEditor::VERSION
19
+ exit
20
+ end
21
+ parser.on('-c', '--config-path=CONFIG_PATH',
22
+ 'Path to POEditor JSON config') do
23
+ |v| options[:config_path] = v
24
+ end
25
+ parser.on('-p', '--project-path=PROJECT_PATH',
26
+ 'Path to project main directory [optional]') do
27
+ |v| options[:project_path] = v
28
+ end
29
+ end.parse!
30
+
31
+ json_config_path = options[:config_path]
32
+ project_path = options[:project_path]
33
+ if project_path == nil
34
+ project_path = ""
35
+ end
36
+ if json_config_path.nil? || json_config_path.empty?
37
+ abort("Path to POEditor JSON config is required to be non-empty.")
38
+ end
39
+
40
+ UI.enabled = true
41
+ poEditor_pull_command = PullCommand.new(project_path)
42
+ poEditor_pull_command.run_from_path(json_config_path)
@@ -0,0 +1,56 @@
1
+ module POEditor
2
+ class Configuration
3
+ # @return [String] POEditor API key
4
+ # @see https://poeditor.com/account/api POEditor API Access
5
+ attr_accessor :api_key
6
+
7
+ # @return [String] POEditor project ID
8
+ attr_accessor :project_id
9
+
10
+ # @return [String] Export file type (po, apple_strings, android_strings)
11
+ attr_accessor :type
12
+
13
+ # @return [Array<String>] Tag filters (optional)
14
+ attr_accessor :tags
15
+
16
+ # @return [Array<String>] Filters by 'translated', 'untranslated', 'fuzzy', 'not_fuzzy', 'automatic', 'not_automatic', 'proofread', 'not_proofread' (optional)
17
+ attr_accessor :filters
18
+
19
+ # @return [Array<String>] The languages codes
20
+ attr_accessor :languages
21
+
22
+ # @return [Hash{Sting => String}] The languages aliases
23
+ attr_accessor :language_alias
24
+
25
+ # @return [String] The path template
26
+ attr_accessor :path
27
+
28
+ # @return [Hash{Sting => String}] The path replacements
29
+ attr_accessor :path_replace
30
+
31
+ def initialize(api_key:, project_id:, type:, tags:nil,
32
+ filters:nil, languages:, language_alias:nil,
33
+ path:, path_replace:nil)
34
+ @api_key = from_env(api_key)
35
+ @project_id = from_env(project_id.to_s)
36
+ @type = type
37
+ @tags = tags || []
38
+ @filters = filters || []
39
+
40
+ @languages = languages
41
+ @language_alias = language_alias || {}
42
+
43
+ @path = path
44
+ @path_replace = path_replace || {}
45
+ end
46
+
47
+ def from_env(value)
48
+ if value.start_with?("$")
49
+ key = value[1..-1]
50
+ ENV[key]
51
+ else
52
+ value
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,140 @@
1
+ require "json"
2
+ require "net/http"
3
+ require 'fileutils'
4
+ require 'colorize'
5
+
6
+ module POEditor
7
+ class Core
8
+
9
+ POEDITOR_BASE_URL = "https://api.poeditor.com/v2/"
10
+
11
+ # @return [POEditor::Configuration] The configuration for export
12
+ attr_accessor :configuration
13
+
14
+ # @param configuration [POEditor::Configuration]
15
+ def initialize(configuration)
16
+ unless configuration.is_a? Configuration
17
+ raise POEditor::Exception.new \
18
+ "`configuration` should be an `Configuration`"
19
+ end
20
+ @configuration = configuration
21
+ end
22
+
23
+ # Request POEditor API
24
+ #
25
+ # @param action [String]
26
+ # @param api_token [String]
27
+ # @param options [Hash{Sting => Object}]
28
+ #
29
+ # @return [Net::HTTPResponse] The response object of API request
30
+ #
31
+ # @see https://poeditor.com/api_reference/ POEditor API Reference
32
+ def api(action, api_token, options={})
33
+ uri = URI(POEDITOR_BASE_URL + "#{action}")
34
+ options["api_token"] = api_token
35
+ return Net::HTTP.post_form(uri, options)
36
+ end
37
+
38
+ # Pull translations
39
+ def pull()
40
+ UI.puts "\nExport translations"
41
+ for language in @configuration.languages
42
+ UI.puts " - Exporting '#{language}'"
43
+ content = self.export(:api_key => @configuration.api_key,
44
+ :project_id => @configuration.project_id,
45
+ :language => language,
46
+ :type => @configuration.type,
47
+ :tags => @configuration.tags,
48
+ :filters => @configuration.filters)
49
+ write(language, content)
50
+
51
+ for alias_to, alias_from in @configuration.language_alias
52
+ if language == alias_from
53
+ write(alias_to, content)
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ # Export translation for specific language
60
+ #
61
+ # @param api_key [String]
62
+ # @param project_jd [String]
63
+ # @param language [String]
64
+ # @param type [String]
65
+ # @param tags [Array<String>]
66
+ # @param filters [Array<String>]
67
+ #
68
+ # @return Downloaded translation content
69
+ def export(api_key:, project_id:, language:, type:, tags:nil, filters:nil)
70
+ options = {
71
+ "id" => project_id,
72
+ "language" => convert_to_poeditor_language(language),
73
+ "type" => type,
74
+ "tags" => (tags || []).join(","),
75
+ "filters" => (filters || []).join(","),
76
+ }
77
+ response = self.api("projects/export", api_key, options)
78
+ data = JSON(response.body)
79
+ unless data["response"]["status"] == "success"
80
+ code = data["response"]["code"]
81
+ message = data["response"]["message"]
82
+ raise POEditor::Exception.new "#{message} (#{code})"
83
+ end
84
+
85
+ download_uri = URI(data["result"]["url"])
86
+ content = Net::HTTP.get(download_uri)
87
+
88
+ case type
89
+ when "apple_strings"
90
+ content.gsub!(/(%(\d+\$)?)s/, '\1@') # %s -> %@
91
+ when "android_strings"
92
+ content.gsub!(/(%(\d+\$)?)@/, '\1s') # %@ -> %s
93
+ end
94
+
95
+ unless content.end_with? "\n"
96
+ content += "\n"
97
+ end
98
+ return content
99
+ end
100
+
101
+ def convert_to_poeditor_language(language)
102
+ language = language.downcase
103
+ android_region_language_regexp = /(?<=[a-z]{2}-)[r](?=[a-z]{2})/i
104
+ chinese_regions_regexp = /(zh-)\K(hans|hant)/i
105
+ if language =~ android_region_language_regexp
106
+ return language.gsub(android_region_language_regexp, "")
107
+ elsif language =~ chinese_regions_regexp
108
+ return language.gsub(chinese_regions_regexp, {'hans' => 'cn', 'hant' => 'tw'})
109
+ end
110
+ language
111
+ end
112
+
113
+ # Write translation file
114
+ def write(language, content)
115
+ if content.to_s.strip.empty?
116
+ UI.puts " #{"\xe2\x9c\x95".red} Ignoring language: #{language} because there are no any translations."
117
+ return
118
+ end
119
+ path = path_for_language(language)
120
+ if path.to_s.empty?
121
+ raise POEditor::Exception.new "#{path} doesn't exist"
122
+ end
123
+ dirname = File.dirname(path)
124
+ unless File.directory?(dirname)
125
+ FileUtils.mkdir_p(dirname)
126
+ end
127
+ File.write(path, content)
128
+ UI.puts " #{"\xe2\x9c\x93".green} Saved at '#{path}'"
129
+ end
130
+
131
+ def path_for_language(language)
132
+ if @configuration.path_replace[language]
133
+ @configuration.path_replace[language]
134
+ else
135
+ @configuration.path.gsub("{LANGUAGE}", language)
136
+ end
137
+ end
138
+
139
+ end
140
+ end
@@ -0,0 +1,4 @@
1
+ module POEditor
2
+ class Exception < Exception
3
+ end
4
+ end
@@ -0,0 +1,108 @@
1
+ require_relative 'UI'
2
+ require_relative 'Core'
3
+ require_relative 'Configuration'
4
+ require_relative 'Exception'
5
+
6
+ module POEditor
7
+ class PullCommand
8
+
9
+ # @param project_path [String] Path of project main directory [optional] -
10
+ # if present then target paths would be created
11
+ # by concatenating it with paths in JSON config
12
+ #
13
+ def initialize(project_path = "")
14
+ @project_path = project_path
15
+ end
16
+
17
+ # Pulls POEditor translations based on passed POEditor JSON config and target name
18
+ #
19
+ # @param config [JSON] POEditor JSON config
20
+ #
21
+ # @param target_name [String] Target name for translations [optional] -
22
+ # if absent paths would not be parameterized
23
+ #
24
+ def run(config, target_name = "")
25
+ pull_translations(create_configuration(config, target_name))
26
+ end
27
+
28
+ # Pulls POEditor translations based on POEditor JSON config read
29
+ # from passed path and target name
30
+ #
31
+ # @param config_path [String] Path to POEditor config
32
+ #
33
+ # @param target_name [String] Target name for translations [optional] -
34
+ # if absent paths would not be parameterized
35
+ #
36
+ def run_from_path(config_path, target_name = "")
37
+ pull_translations(get_configuration(config_path, target_name))
38
+ end
39
+
40
+ private
41
+
42
+ # Pulls POEditor translations based on configuration
43
+ #
44
+ # @param configuration [Configuration] Configuration object
45
+ #
46
+ def pull_translations(configuration)
47
+ client = POEditor::Core.new(configuration)
48
+ client.pull()
49
+ end
50
+
51
+ # Returns {#POEditor::Configuration} based on POEditor JSON config and target name
52
+ #
53
+ # @param config_path [String] Path to POEditor config
54
+ #
55
+ # @param target_name [String] Target name for translations
56
+ #
57
+ # @return [POEditor::Configuration] The export configuration
58
+ def get_configuration(config_path, target_name)
59
+ UI.puts "Reading configuration"
60
+ unless File.exist?(config_path)
61
+ raise POEditor::Exception.new %{\
62
+ Configuration file doesn't exist: #{config_path}.
63
+ Specify the path using '--config'.\
64
+ }
65
+ end
66
+ json_config = JSON(File.read(config_path))
67
+ create_configuration(json_config, target_name)
68
+ end
69
+
70
+ def create_configuration(json_config, target_name)
71
+ translations_path = File.join(@project_path, path_for_target(get_or_raise(json_config, "path"), target_name))
72
+ path_replace_array = json_config["path_replace"]
73
+ if path_replace_array != nil
74
+ path_replace_array.each { |language, language_filepath| path_replace_array[language] = File.join(@project_path,
75
+ path_for_target(language_filepath, target_name)) }
76
+ end
77
+ Configuration.new(
78
+ api_key: get_or_raise(json_config, "api_key"),
79
+ project_id: get_or_raise(json_config, "project_id"),
80
+ type: get_or_raise(json_config, "type"),
81
+ tags: json_config["tags"],
82
+ filters: json_config["filters"],
83
+ languages: get_or_raise(json_config, "languages"),
84
+ language_alias: json_config["language_alias"],
85
+ path: translations_path,
86
+ path_replace: path_replace_array,
87
+ )
88
+ end
89
+
90
+ # Returns the value of specified key from the given json instance. Raise
91
+ # exception when there's no key in the json.
92
+ #
93
+ # @param json_config [JSON]
94
+ # @param key [String]
95
+ #
96
+ # @return The value for the specified key from json
97
+ # @raise [POEditor::Exception]
98
+ def get_or_raise(json_config, key)
99
+ json_config[key] or raise POEditor::Exception.new \
100
+ "Missing configuration key: '#{key}'"
101
+ end
102
+
103
+ def path_for_target(path, target_name)
104
+ path.gsub("{TARGET_NAME}", target_name)
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,17 @@
1
+ module POEditor
2
+ class UI
3
+ @@enabled = false
4
+
5
+ def self.enabled
6
+ @@enabled
7
+ end
8
+
9
+ def self.enabled=enabled
10
+ @@enabled = enabled
11
+ end
12
+
13
+ def self.puts(*args)
14
+ Kernel.puts args.map { |x| x.to_s }.join(" ") if @@enabled
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module POEditor
2
+ VERSION = "1.0.3"
3
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: poeditor-pull
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Suyeol Jeon
8
+ - Konrad Stanik - Viacom
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-07-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: colorize
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.8'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.8'
28
+ description: This is a command line tool for pulling translations from POEditor.
29
+ email:
30
+ executables:
31
+ - poeditor-pull
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - CHANGELOG
36
+ - LICENSE
37
+ - README.md
38
+ - bin/poeditor-pull
39
+ - lib/Configuration.rb
40
+ - lib/Core.rb
41
+ - lib/Exception.rb
42
+ - lib/PullCommand.rb
43
+ - lib/UI.rb
44
+ - lib/Version.rb
45
+ homepage:
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '2.1'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 2.5.2.3
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: POEditor Pull CLI
69
+ test_files: []