phraseapp_updater 3.2.0 → 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
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.