phraseapp_updater 3.2.0 → 3.3.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e8639a81e55d83a36d03854f2c970737ee9754266e629ef2bad1d9e65c53826
4
- data.tar.gz: '078928d80c10deb59f9a293fbd7135a2d9b4b52479809985b5c4cf9835332131'
3
+ metadata.gz: 5d137755330d9711327e6731a7b4ae1fc701ec705ce719055bca7479dcd3579a
4
+ data.tar.gz: ab2839920b873ed264759eadf926d6b9df837b511b163f1f18e4ef777bcfc820
5
5
  SHA512:
6
- metadata.gz: 1e2927b0dc2e87ba9ec5c64c8d0393a58624ce7e754183d556e1e165bb51fed61e0cac138a8e9bb9b46e2f97b74628ff75e2d24190fad30a68a1124d1425dee9
7
- data.tar.gz: 23f2339b569cbe5f6237d1f23c89259153ac80c79830a02861ec740c5c077384a8794aadad4d462fca4d95e46c04e4b145aff09f1bf5d1bc89c1b8f44e38f805
6
+ metadata.gz: afe9e8266f248e39c52095a762a64a9e1823bf94f825a4cbe7d166946892cd1c171252d2218b3dce0c47c6dd6e4cfe224c826a5ffea812d8f3011f07991c5034
7
+ data.tar.gz: ba81fe027f6016fc759e72b5cf27c88e1c31ca7e08e1580dd4c5b110ecaa83dcd6f603ec686978b21071fbb5bc9e02e5967e32a1a102612a5a16448094c3158a
@@ -9,6 +9,14 @@ class PhraseAppUpdaterCLI < Thor
9
9
  class_option :file_format, type: :string, default: 'json', desc: 'Filetype of localization files.'
10
10
  class_option :verbose, type: :boolean, default: false, desc: 'Verbose output'
11
11
 
12
+ # Options that mirror the PhraseApp API (https://developers.phrase.com/api/#post-/projects)
13
+ PHRASEAPP_CREATE_PROJECT_OPTIONS = {
14
+ zero_plural_form_enabled: {
15
+ type: :boolean,
16
+ desc: 'Displays the input fields for the \'ZERO\' plural form for every key as well although only some languages require the \'ZERO\' explicitly.'
17
+ },
18
+ }
19
+
12
20
  desc 'setup <locale_path>',
13
21
  'Create a new PhraseApp project, initializing it with locale files at <locale_path>. the new project ID is printed to STDOUT'
14
22
  method_option :phraseapp_api_key, type: :string, required: true, desc: 'PhraseApp API key.'
@@ -16,16 +24,23 @@ class PhraseAppUpdaterCLI < Thor
16
24
  method_option :parent_commit, type: :string, required: true, desc: 'git commit hash of initial locales'
17
25
  method_option :remove_orphans, type: :boolean, default: true, desc: 'Remove keys not in the uploaded default locale'
18
26
 
27
+ PHRASEAPP_CREATE_PROJECT_OPTIONS.each do |name, params|
28
+ method_option(name, **params)
29
+ end
30
+
19
31
  def setup(locales_path)
20
32
  validate_readable_path!('locales', locales_path)
21
33
 
22
34
  handle_errors do
35
+ phraseapp_opts = options.slice(*PHRASEAPP_CREATE_PROJECT_OPTIONS.keys)
36
+
23
37
  updater, project_id = PhraseAppUpdater.for_new_project(
24
38
  options[:phraseapp_api_key],
25
39
  options[:phraseapp_project_name],
26
40
  options[:file_format],
27
41
  options[:parent_commit],
28
- verbose: options[:verbose])
42
+ verbose: options[:verbose],
43
+ **phraseapp_opts)
29
44
 
30
45
  updater.upload_directory(locales_path, remove_orphans: options[:remove_orphans])
31
46
 
@@ -4,6 +4,7 @@ require 'phraseapp_updater/locale_file'
4
4
  require 'phraseapp_updater/index_by'
5
5
  require 'uri'
6
6
  require 'phrase'
7
+ require 'concurrent'
7
8
  require 'parallel'
8
9
  require 'tempfile'
9
10
 
@@ -25,10 +26,15 @@ class PhraseAppUpdater
25
26
  @locale_file_class = locale_file_class
26
27
  end
27
28
 
28
- def create_project(name, parent_commit)
29
+ # @param [Hash] opts Options to be passed to the {https://developers.phrase.com/api/#post-/projects PhraseApp API}
30
+ def create_project(name, parent_commit, **opts)
29
31
  params = Phrase::ProjectCreateParameters.new(
30
- name: name,
31
- main_format: @locale_file_class.phraseapp_type)
32
+ # Merges name and main_format into opts to prevent overriding these properties
33
+ opts.merge(
34
+ name: name,
35
+ main_format: @locale_file_class.phraseapp_type
36
+ )
37
+ )
32
38
 
33
39
  project = phraseapp_request(Phrase::ProjectsApi, :project_create, params)
34
40
 
@@ -91,38 +97,60 @@ class PhraseAppUpdater
91
97
  end
92
98
  end
93
99
 
94
- # Empirically, PhraseApp fails to parse the uploaded files when uploaded in
95
- # parallel. Give it a better chance by uploading them one at a time.
96
100
  def upload_files(locale_files, default_locale:)
97
- is_default = ->(l) { l.locale_name == default_locale }
101
+ locale_files = locale_files.sort_by(&:locale_name)
102
+ default_locale_file = locale_files.detect { |l| l.locale_name == default_locale }
103
+ locale_files.delete(default_locale_file) if default_locale_file
98
104
 
99
- # Ensure the locales all exist
100
- STDERR.puts('Creating locales')
101
105
  known_locales = fetch_locales.index_by(&:name)
106
+
107
+ # Phraseapp appears to use to use the first file uploaded to resolve conflicts
108
+ # between pluralized and non-pluralized keys. Upload and verify the canonical
109
+ # default locale first before uploading translated locales.
110
+ if default_locale_file
111
+ unless known_locales.has_key?(default_locale_file.locale_name)
112
+ STDERR.puts("Creating default locale (#{default_locale_file})")
113
+ create_locale(default_locale_file.locale_name, default: true)
114
+ end
115
+
116
+ STDERR.puts("Uploading default locale (#{default_locale_file})")
117
+ upload_id = upload_file(default_locale_file)
118
+
119
+ successful_default_upload = verify_uploads({ upload_id => default_locale_file })
120
+ else
121
+ STDERR.puts("No upload for default locale (#{default_locale})")
122
+ end
123
+
124
+ # Ensure the locales all exist
125
+ STDERR.puts('Creating translation locales')
102
126
  threaded_request(locale_files) do |locale_file|
103
127
  unless known_locales.has_key?(locale_file.locale_name)
104
- create_locale(locale_file.locale_name, default: is_default.(locale_file))
128
+ create_locale(locale_file.locale_name, default: false)
105
129
  end
106
130
  end
107
131
 
108
- # Upload the files in a stable order, ensuring the default locale is first.
109
- locale_files.sort! do |a, b|
110
- next -1 if is_default.(a)
111
- next 1 if is_default.(b)
132
+ uploads = Concurrent::Hash.new
112
133
 
113
- a.locale_name <=> b.locale_name
134
+ threaded_request(locale_files) do |locale_file|
135
+ STDERR.puts("Uploading #{locale_file}")
136
+ upload_id = upload_file(locale_file)
137
+ uploads[upload_id] = locale_file
114
138
  end
115
139
 
116
- uploads = {}
140
+ successful_uploads = verify_uploads(uploads)
117
141
 
118
- uploads = locale_files.to_h do |locale_file|
119
- STDERR.puts("Uploading #{locale_file}")
120
- upload_id = upload_file(locale_file)
121
- [upload_id, locale_file]
142
+ if default_locale_file
143
+ successful_uploads = successful_uploads.merge(successful_default_upload)
122
144
  end
123
145
 
124
- # Validate the uploads, retrying failures as necessary
125
- successful_upload_ids = {}
146
+ successful_uploads
147
+ end
148
+
149
+ # Given a map of {upload_id => locale_file} pairs, use the upload_show
150
+ # API to verify that they're complete, and re-upload them if they failed.
151
+ # Return a map of locale name to upload id.
152
+ def verify_uploads(uploads)
153
+ successful_upload_ids = Concurrent::Hash.new
126
154
 
127
155
  STDERR.puts('Verifying uploads...')
128
156
  until uploads.empty?
@@ -152,6 +180,7 @@ class PhraseAppUpdater
152
180
  successful_upload_ids
153
181
  end
154
182
 
183
+
155
184
  def remove_keys_not_in_uploads(upload_ids)
156
185
  threaded_request(upload_ids) do |upload_id|
157
186
  STDERR.puts "Removing keys not in upload #{upload_id}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class PhraseAppUpdater
4
- VERSION = '3.2.0'
4
+ VERSION = '3.3.1'
5
5
  end
@@ -10,9 +10,9 @@ require 'phraseapp_updater/yml_config_loader'
10
10
  class PhraseAppUpdater
11
11
  using IndexBy
12
12
 
13
- def self.for_new_project(phraseapp_api_key, phraseapp_project_name, file_format, parent_commit, verbose: false)
13
+ def self.for_new_project(phraseapp_api_key, phraseapp_project_name, file_format, parent_commit, verbose: false, **phraseapp_opts)
14
14
  api = PhraseAppAPI.new(phraseapp_api_key, nil, LocaleFile.class_for_file_format(file_format))
15
- project_id = api.create_project(phraseapp_project_name, parent_commit)
15
+ project_id = api.create_project(phraseapp_project_name, parent_commit, **phraseapp_opts)
16
16
  return self.new(phraseapp_api_key, project_id, file_format, verbose: verbose), project_id
17
17
  end
18
18
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phraseapp_updater
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - iKnow Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-04 00:00:00.000000000 Z
11
+ date: 2024-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '1.23'
97
+ - !ruby/object:Gem::Dependency
98
+ name: concurrent-ruby
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.0.2
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.0.2
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: bundler
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -193,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
207
  - !ruby/object:Gem::Version
194
208
  version: '0'
195
209
  requirements: []
196
- rubygems_version: 3.3.26
210
+ rubygems_version: 3.3.27
197
211
  signing_key:
198
212
  specification_version: 4
199
213
  summary: A three-way differ for PhraseApp projects.