lokalise_manager 1.1.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +62 -0
- data/README.md +37 -5
- data/lib/lokalise_manager/global_config.rb +12 -1
- data/lib/lokalise_manager/task_definitions/base.rb +10 -4
- data/lib/lokalise_manager/task_definitions/exporter.rb +36 -12
- data/lib/lokalise_manager/task_definitions/importer.rb +2 -2
- data/lib/lokalise_manager/utils/array_utils.rb +27 -0
- data/lib/lokalise_manager/utils/hash_utils.rb +28 -0
- data/lib/lokalise_manager/version.rb +1 -1
- data/lib/lokalise_manager.rb +3 -0
- data/lokalise_manager.gemspec +4 -1
- data/spec/lib/lokalise_manager/global_config_spec.rb +10 -0
- data/spec/lib/lokalise_manager/task_definitions/base_spec.rb +1 -1
- data/spec/lib/lokalise_manager/task_definitions/exporter_spec.rb +71 -28
- data/spec/lib/lokalise_manager/task_definitions/importer_spec.rb +43 -11
- data/spec/lib/utils/array_utils_spec.rb +18 -0
- data/spec/lib/utils/hash_utils_spec.rb +13 -0
- data/spec/support/vcr.rb +3 -1
- metadata +13 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3a7da094cb7939fd6826c89afbbbc525dff59b553a23fba7b49ed38046e68b9
|
4
|
+
data.tar.gz: c0a323464c864d12042f126d765a483adc128a4397604d012cdeadee31003fcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79c0b5a93767b5ffa8cd55dbf6591a90cafbb586043e8e75ca040761b8fa882882971783358e6d342afab96e298d34c67ca5d414d376c909fa564e8ad03d2466
|
7
|
+
data.tar.gz: ed2ad8e59fb6e15db8ba7c60de48e808c2eab0ff94b8f287c54bb2e618e1c930d5d3244a2483a6e61e69b7094943a05db59ea54ef32fc3596ef344f5956553ee
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,67 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 2.1.0 (27-Jan-22)
|
4
|
+
|
5
|
+
* **Breaking change**: `export!` will now return an array of objects responding to the following methods:
|
6
|
+
+ `success` — usually returns `true` (to learn more, check documentation for the `:raise_on_export_fail` option below)
|
7
|
+
+ `process` — returns an object (an instance of the `Lokalise::Resources::QueuedProcess`) representing a [queued background process](https://lokalise.github.io/ruby-lokalise-api/api/queued-processes) as uploading is done in the background on Lokalise. You can use this object to check the process status (whether the uploading is completed or not).
|
8
|
+
+ `path` — returns an instance of the `Pathname` class which represent the file being uploaded.
|
9
|
+
* Here's an example:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
def uploaded?(process)
|
13
|
+
5.times do # try to check the status 5 times
|
14
|
+
process = process.reload_data # load new data
|
15
|
+
return(true) if process.status == 'finished' # return true is the upload has finished
|
16
|
+
sleep 1 # wait for 1 second, adjust this number with regards to the upload size
|
17
|
+
end
|
18
|
+
|
19
|
+
false # if all 5 checks failed, return false (probably something is wrong)
|
20
|
+
end
|
21
|
+
|
22
|
+
processes = exporter.export!
|
23
|
+
puts "Checking status for the #{processes[0].path} file"
|
24
|
+
uploaded? processes[0].process
|
25
|
+
```
|
26
|
+
|
27
|
+
* Introduced a new option `raise_on_export_fail` (`boolean`) which is `true` by default. When this option is enabled, LokaliseManager will re-raise any exceptions that happened during the file uploading. When this option is disabled, the exporting process will continue even if something goes wrong. In this case you'll probably need to check the result yourself and make the necessary actions. For example:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
processes = exporter.export!
|
31
|
+
|
32
|
+
processes.each do |proc_data|
|
33
|
+
if proc_data.success
|
34
|
+
# Everything is good, the uploading is queued
|
35
|
+
puts "#{proc_data.path} is sent to Lokalise!"
|
36
|
+
process = proc_data.process
|
37
|
+
puts "Current process status is #{process.status}"
|
38
|
+
else
|
39
|
+
# Something bad has happened
|
40
|
+
puts "Could not send #{proc_data.path} to Lokalise"
|
41
|
+
puts "Error #{proc_data.error.class}: #{proc_data.error.message}"
|
42
|
+
# Or you could re-raise this exception:
|
43
|
+
# raise proc_data.error.class
|
44
|
+
end
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
## 2.0.0 (27-Jan-22)
|
49
|
+
|
50
|
+
* `export!` method is now taking advantage of multi-threading (as Lokalise API allows to send requests in parallel since January 2022)
|
51
|
+
* Test with Ruby 3.1.0
|
52
|
+
* Other minor fixes
|
53
|
+
|
54
|
+
## 1.2.1 (26-Nov-21)
|
55
|
+
|
56
|
+
* Use refinements instead of monkey patching to add hash methods
|
57
|
+
* Don't use `OpenStruct` anymore to store config opts
|
58
|
+
* Minor fixes
|
59
|
+
|
60
|
+
## 1.2.0 (26-Oct-21)
|
61
|
+
|
62
|
+
* Add a new option `:silent_mode` which is `false` by default. When silent mode is enabled, no debug info will be printed out to `$stdout`. The only exception are the "safe mode" messages — you'll still be prompted to continue if the target directory is not empty.
|
63
|
+
* Use `#deep_merge` instead of a simple merge when processing options.
|
64
|
+
|
3
65
|
## 1.1.0 (25-Oct-21)
|
4
66
|
|
5
67
|
* Add a new option `:use_oauth2_token` which is `false` by default. When enabled, you'll be able to provide a token obtained via [OAuth 2 flow](https://docs.lokalise.com/en/articles/5574713-oauth-2) rather than generated via Lokalise profile. The token should still be provided via the `:api_token` option:
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# LokaliseManager
|
2
2
|
|
3
3
|
![Gem](https://img.shields.io/gem/v/lokalise_manager)
|
4
|
-
|
4
|
+
![CI](https://github.com/bodrovis/lokalise_manager/actions/workflows/ci.yml/badge.svg)
|
5
5
|
[![Test Coverage](https://codecov.io/gh/bodrovis/lokalise_manager/graph/badge.svg)](https://codecov.io/gh/bodrovis/lokalise_manager)
|
6
6
|
![Downloads total](https://img.shields.io/gem/dt/lokalise_manager)
|
7
7
|
|
@@ -15,6 +15,8 @@ If you are looking for a Rails integration, please check [lokalise_rails](https:
|
|
15
15
|
|
16
16
|
This gem requires Ruby 2.5+. You will also need to [setup a Lokalise account](https://app.lokalise.com/signup) and create a [translation project](https://docs.lokalise.com/en/articles/1400460-projects). Finally, you will need to generate a [read/write API token](https://docs.lokalise.com/en/articles/1929556-api-tokens) at your Lokalise profile.
|
17
17
|
|
18
|
+
Alternatively, you can utilize a token obtained via OAuth 2 flow. When using such a token, you'll have to set `:use_oauth2_token` option to `true` (see below).
|
19
|
+
|
18
20
|
### Installation
|
19
21
|
|
20
22
|
Add the gem to your `Gemfile`:
|
@@ -63,7 +65,13 @@ To upload your translation files from a local directory (defaults to `locales/`)
|
|
63
65
|
processes = exporter.export!
|
64
66
|
```
|
65
67
|
|
66
|
-
`processes` will contain an array of
|
68
|
+
`processes` will contain an array of objects responding to the following methods:
|
69
|
+
|
70
|
+
* `success` — usually returns `true` (to learn more, check documentation for the `:raise_on_export_fail` option below)
|
71
|
+
* `process` — returns an object (an instance of the `Lokalise::Resources::QueuedProcess`) representing a [queued background process](https://lokalise.github.io/ruby-lokalise-api/api/queued-processes) as uploading is done in the background on Lokalise.
|
72
|
+
* `path` — returns an instance of the `Pathname` class which represent the file being uploaded.
|
73
|
+
|
74
|
+
You can perform periodic checks to read the status of the process. Here's a very simple example:
|
67
75
|
|
68
76
|
```ruby
|
69
77
|
def uploaded?(process)
|
@@ -77,7 +85,8 @@ def uploaded?(process)
|
|
77
85
|
end
|
78
86
|
|
79
87
|
processes = exporter.export!
|
80
|
-
|
88
|
+
puts "Checking status for the #{processes[0].path} file"
|
89
|
+
uploaded? processes[0].process
|
81
90
|
```
|
82
91
|
|
83
92
|
Please don't forget that Lokalise API has rate limiting and you cannot send more than six requests per second.
|
@@ -92,6 +101,7 @@ Please don't forget that Lokalise API has rate limiting and you cannot send more
|
|
92
101
|
* `locales_path` (`string`) — path to the directory with your translation files. Defaults to `"#{Dir.getwd}/locales"`.
|
93
102
|
* `branch` (`string`) — Lokalise project branch to use. Defaults to `""` (no branch is provided).
|
94
103
|
* `timeouts` (`hash`) — set [request timeouts for the Lokalise API client](https://lokalise.github.io/ruby-lokalise-api/additional_info/customization#setting-timeouts). By default, requests have no timeouts: `{open_timeout: nil, timeout: nil}`. Both values are in seconds.
|
104
|
+
* `silent_mode` (`boolean`) — whether you would like to output debugging information to `$stdout`. By default, after a task is performed, a short notification message will be printed out to the terminal. When set to `false`, notifications won't be printed. Please note that currently `import_safe_mode` has higher priority. Even if you enable `silent_mode`, and the `import_safe_mode` is enabled as well, you will be prompted to confirm the import operation if the target directory is not empty.
|
95
105
|
|
96
106
|
### Import config
|
97
107
|
|
@@ -159,7 +169,29 @@ In this case the `export_opts` will have `detect_icu_plurals` set to `true` and
|
|
159
169
|
c.skip_file_export = ->(file) { f.split[1].to_s.include?('fr') }
|
160
170
|
```
|
161
171
|
|
162
|
-
* `max_retries_export` (`integer`) — this option is introduced to properly handle Lokalise API rate limiting. If the HTTP status code 429 (too many requests) has been received, LokaliseRails will apply an exponential backoff mechanism with a very simple formula: `2 ** retries` (initially `retries` is `0`). If the maximum number of retries has been reached, a `Lokalise::Error::TooManyRequests` exception will be raised and the export operation will be halted. By default,
|
172
|
+
* `max_retries_export` (`integer`) — this option is introduced to properly handle Lokalise API rate limiting. If the HTTP status code 429 (too many requests) has been received, LokaliseRails will apply an exponential backoff mechanism with a very simple formula: `2 ** retries` (initially `retries` is `0`). If the maximum number of retries has been reached, a `Lokalise::Error::TooManyRequests` exception will be raised and the export operation will be halted. By default, LokaliseManager will make up to `5` retries which potentially means `1 + 2 + 4 + 8 + 16 + 32 = 63` seconds of waiting time. If the `max_retries_export` is less than `1`, LokaliseRails will not perform any retries and give up immediately after receiving error 429.
|
173
|
+
* `raise_on_export_fail` (`boolean`) — default is `true`. When this option is enabled, LokaliseManager will re-raise any exceptions that happened during the file uploading. In other words, if any uploading thread raised an exception, your exporting process will exit with an exception. Suppose, you are uploading 12 translation files; these files will be split in 2 groups with 6 files each, and each group will be uploaded in parallel (using threads). However, suppose some exception happens when uploading the first group. By default this exception will be re-raised for the whole process and the script will never try to upload the second group. If you would like to continue uploading even if an exception happened, set the `raise_on_export_fail` to `false`. In this case the `export!` method will return an array with scheduled processes and with information about processes that were not successfully scheduled. This information is represented as an object with three methods: `path` (contains an instance of the `Pathname` class which says which file could not be uploaded), `error` (the actual exception), and `success` (returns `false`). So, you can use the following snippet to check your processes:
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
processes = exporter.export!
|
177
|
+
|
178
|
+
processes.each do |proc_data|
|
179
|
+
if proc_data.success
|
180
|
+
# Everything is good, the uploading is queued
|
181
|
+
puts "#{proc_data.path} is sent to Lokalise!"
|
182
|
+
process = proc_data.process
|
183
|
+
puts "Current process status is #{process.status}"
|
184
|
+
else
|
185
|
+
# Something bad has happened
|
186
|
+
puts "Could not send #{proc_data.path} to Lokalise"
|
187
|
+
puts "Error #{proc_data.error.class}: #{proc_data.error.message}"
|
188
|
+
# Or you could re-raise this exception:
|
189
|
+
# raise proc_data.error.class
|
190
|
+
end
|
191
|
+
end
|
192
|
+
```
|
193
|
+
|
194
|
+
* For example, you could collect all the files that were uploaded successfully, re-create the exporter object with the `skip_file_export` option (skipping all files that were successfully imported), and re-run the whole exporting process once again.
|
163
195
|
|
164
196
|
### Config to work with formats other than YAML
|
165
197
|
|
@@ -268,4 +300,4 @@ importer.import!
|
|
268
300
|
|
269
301
|
## License
|
270
302
|
|
271
|
-
Copyright (c) [Lokalise team](http://lokalise.com), [Ilya Bodrov](http://bodrovis.tech). License type is [MIT](https://github.com/bodrovis/lokalise_manager/blob/master/LICENSE).
|
303
|
+
Copyright (c) [Lokalise team](http://lokalise.com), [Ilya Bodrov](http://bodrovis.tech). License type is [MIT](https://github.com/bodrovis/lokalise_manager/blob/master/LICENSE).
|
@@ -7,13 +7,24 @@ module LokaliseManager
|
|
7
7
|
attr_writer :import_opts, :import_safe_mode, :export_opts, :locales_path,
|
8
8
|
:file_ext_regexp, :skip_file_export, :branch, :timeouts,
|
9
9
|
:translations_loader, :translations_converter, :lang_iso_inferer,
|
10
|
-
:max_retries_export, :max_retries_import, :use_oauth2_token
|
10
|
+
:max_retries_export, :max_retries_import, :use_oauth2_token, :silent_mode,
|
11
|
+
:raise_on_export_fail
|
11
12
|
|
12
13
|
# Main interface to provide configuration options
|
13
14
|
def config
|
14
15
|
yield self
|
15
16
|
end
|
16
17
|
|
18
|
+
# When enabled, will re-raise any exception that happens during file exporting
|
19
|
+
def raise_on_export_fail
|
20
|
+
@raise_on_export_fail || true
|
21
|
+
end
|
22
|
+
|
23
|
+
# When enabled, won't print any debugging info to $stdout
|
24
|
+
def silent_mode
|
25
|
+
@silent_mode || false
|
26
|
+
end
|
27
|
+
|
17
28
|
# When enabled, will use OAuth 2 Lokalise client and will require to provide a token obtained via OAuth 2 flow
|
18
29
|
# rather than via Lokalise profile
|
19
30
|
def use_oauth2_token
|
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
require 'ruby-lokalise-api'
|
4
4
|
require 'pathname'
|
5
|
-
require 'ostruct'
|
6
5
|
|
7
6
|
module LokaliseManager
|
8
7
|
module TaskDefinitions
|
9
8
|
class Base
|
9
|
+
using LokaliseManager::Utils::HashUtils
|
10
|
+
|
10
11
|
attr_accessor :config
|
11
12
|
|
12
13
|
# Creates a new importer or exporter. It accepts custom config and merges it
|
@@ -23,7 +24,11 @@ module LokaliseManager
|
|
23
24
|
opts[reader.to_sym] = global_config.send(reader)
|
24
25
|
end
|
25
26
|
|
26
|
-
|
27
|
+
all_opts = primary_opts.deep_merge(custom_opts)
|
28
|
+
|
29
|
+
config_klass = Struct.new(*all_opts.keys, keyword_init: true)
|
30
|
+
|
31
|
+
@config = config_klass.new all_opts
|
27
32
|
end
|
28
33
|
|
29
34
|
# Creates a Lokalise API client
|
@@ -33,12 +38,13 @@ module LokaliseManager
|
|
33
38
|
client_opts = [config.api_token, {enable_compression: true}.merge(config.timeouts)]
|
34
39
|
client_method = config.use_oauth2_token ? :oauth_client : :client
|
35
40
|
|
36
|
-
@api_client
|
41
|
+
@api_client = ::Lokalise.send(client_method, *client_opts)
|
37
42
|
end
|
38
43
|
|
39
44
|
# Resets API client
|
40
45
|
def reset_api_client!
|
41
|
-
Lokalise.reset_client!
|
46
|
+
::Lokalise.reset_client!
|
47
|
+
::Lokalise.reset_oauth_client!
|
42
48
|
@api_client = nil
|
43
49
|
end
|
44
50
|
|
@@ -5,6 +5,10 @@ require 'base64'
|
|
5
5
|
module LokaliseManager
|
6
6
|
module TaskDefinitions
|
7
7
|
class Exporter < Base
|
8
|
+
using LokaliseManager::Utils::ArrayUtils
|
9
|
+
|
10
|
+
MAX_THREADS = 6
|
11
|
+
|
8
12
|
# Performs translation file export to Lokalise and returns an array of queued processes
|
9
13
|
#
|
10
14
|
# @return [Array]
|
@@ -12,31 +16,50 @@ module LokaliseManager
|
|
12
16
|
check_options_errors!
|
13
17
|
|
14
18
|
queued_processes = []
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
+
|
20
|
+
all_files.in_groups_of(MAX_THREADS) do |files_group|
|
21
|
+
parallel_upload(files_group).each do |thr|
|
22
|
+
raise_on_fail(thr) if config.raise_on_export_fail
|
23
|
+
|
24
|
+
queued_processes.push thr
|
25
|
+
end
|
19
26
|
end
|
20
27
|
|
21
|
-
$stdout.print
|
28
|
+
$stdout.print('Task complete!') unless config.silent_mode
|
22
29
|
|
23
30
|
queued_processes
|
24
31
|
end
|
25
32
|
|
26
33
|
private
|
27
34
|
|
35
|
+
def parallel_upload(files_group)
|
36
|
+
files_group.compact.map do |file_data|
|
37
|
+
do_upload(*file_data)
|
38
|
+
end.map(&:value)
|
39
|
+
end
|
40
|
+
|
41
|
+
def raise_on_fail(thread)
|
42
|
+
raise(thread.error.class, "Error while trying to upload #{thread.path}: #{thread.error.message}") unless thread.success
|
43
|
+
end
|
44
|
+
|
28
45
|
# Performs the actual file uploading to Lokalise. If the API rate limit is exceeed,
|
29
46
|
# applies exponential backoff
|
30
47
|
def do_upload(f_path, r_path)
|
31
|
-
|
32
|
-
|
48
|
+
proc_klass = Struct.new(:success, :process, :path, :error, keyword_init: true)
|
49
|
+
|
50
|
+
Thread.new do
|
51
|
+
process = with_exp_backoff(config.max_retries_export) do
|
52
|
+
api_client.upload_file project_id_with_branch, opts(f_path, r_path)
|
53
|
+
end
|
54
|
+
proc_klass.new success: true, process: process, path: f_path
|
55
|
+
rescue StandardError => e
|
56
|
+
proc_klass.new success: false, path: f_path, error: e
|
33
57
|
end
|
34
58
|
end
|
35
59
|
|
36
|
-
#
|
37
|
-
def
|
38
|
-
|
39
|
-
|
60
|
+
# Gets translation files from the specified directory
|
61
|
+
def all_files
|
62
|
+
files = []
|
40
63
|
loc_path = config.locales_path
|
41
64
|
Dir["#{loc_path}/**/*"].sort.each do |f|
|
42
65
|
full_path = Pathname.new f
|
@@ -45,8 +68,9 @@ module LokaliseManager
|
|
45
68
|
|
46
69
|
relative_path = full_path.relative_path_from Pathname.new(loc_path)
|
47
70
|
|
48
|
-
|
71
|
+
files << [full_path, relative_path]
|
49
72
|
end
|
73
|
+
files
|
50
74
|
end
|
51
75
|
|
52
76
|
# Generates export options
|
@@ -14,13 +14,13 @@ module LokaliseManager
|
|
14
14
|
check_options_errors!
|
15
15
|
|
16
16
|
unless proceed_when_safe_mode?
|
17
|
-
$stdout.print
|
17
|
+
$stdout.print('Task cancelled!') unless config.silent_mode
|
18
18
|
return false
|
19
19
|
end
|
20
20
|
|
21
21
|
open_and_process_zip download_files['bundle_url']
|
22
22
|
|
23
|
-
$stdout.print
|
23
|
+
$stdout.print('Task complete!') unless config.silent_mode
|
24
24
|
true
|
25
25
|
end
|
26
26
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Taken from https://github.com/rails/rails/blob/6bfc637659248df5d6719a86d2981b52662d9b50/activesupport/lib/active_support/core_ext/array/grouping.rb
|
4
|
+
|
5
|
+
module LokaliseManager
|
6
|
+
module Utils
|
7
|
+
module ArrayUtils
|
8
|
+
refine Array do
|
9
|
+
def in_groups_of(number, fill_with = nil, &block)
|
10
|
+
if number.to_i <= 0
|
11
|
+
raise ArgumentError,
|
12
|
+
"Group size must be a positive integer, was #{number.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
if fill_with == false
|
16
|
+
collection = self
|
17
|
+
else
|
18
|
+
padding = (number - (size % number)) % number
|
19
|
+
collection = dup.concat(Array.new(padding, fill_with))
|
20
|
+
end
|
21
|
+
|
22
|
+
collection.each_slice(number, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Taken from https://github.com/rails/rails/blob/83217025a171593547d1268651b446d3533e2019/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
|
4
|
+
|
5
|
+
module LokaliseManager
|
6
|
+
module Utils
|
7
|
+
module HashUtils
|
8
|
+
refine Hash do
|
9
|
+
def deep_merge(other_hash, &block)
|
10
|
+
dup.deep_merge!(other_hash, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Same as +deep_merge+, but modifies +self+.
|
14
|
+
def deep_merge!(other_hash, &block)
|
15
|
+
merge!(other_hash) do |key, this_val, other_val|
|
16
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
17
|
+
this_val.deep_merge(other_val, &block)
|
18
|
+
elsif block
|
19
|
+
yield(key, this_val, other_val)
|
20
|
+
else
|
21
|
+
other_val
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/lokalise_manager.rb
CHANGED
data/lokalise_manager.gemspec
CHANGED
@@ -32,7 +32,10 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_development_dependency 'rspec', '~> 3.6'
|
33
33
|
spec.add_development_dependency 'rubocop', '~> 1.0'
|
34
34
|
spec.add_development_dependency 'rubocop-performance', '~> 1.5'
|
35
|
-
spec.add_development_dependency 'rubocop-rspec', '~> 2.
|
35
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 2.6'
|
36
36
|
spec.add_development_dependency 'simplecov', '~> 0.16'
|
37
37
|
spec.add_development_dependency 'vcr', '~> 6.0'
|
38
|
+
spec.metadata = {
|
39
|
+
'rubygems_mfa_required' => 'true'
|
40
|
+
}
|
38
41
|
end
|
@@ -14,6 +14,16 @@ describe LokaliseManager::GlobalConfig do
|
|
14
14
|
fake_class.project_id = '123.abc'
|
15
15
|
end
|
16
16
|
|
17
|
+
it 'is possible to set raise_on_export_fail' do
|
18
|
+
allow(fake_class).to receive(:raise_on_export_fail=).with(false)
|
19
|
+
fake_class.raise_on_export_fail = false
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'is possible to set silent_mode' do
|
23
|
+
allow(fake_class).to receive(:silent_mode=).with(true)
|
24
|
+
fake_class.silent_mode = true
|
25
|
+
end
|
26
|
+
|
17
27
|
it 'is possible to set use_oauth2_token' do
|
18
28
|
allow(fake_class).to receive(:use_oauth2_token=).with(true)
|
19
29
|
fake_class.use_oauth2_token = true
|
@@ -24,7 +24,7 @@ describe LokaliseManager::TaskDefinitions::Base do
|
|
24
24
|
specify '.reset_client!' do
|
25
25
|
expect(described_object.api_client).to be_an_instance_of(Lokalise::Client)
|
26
26
|
described_object.reset_api_client!
|
27
|
-
current_client = described_object.instance_variable_get
|
27
|
+
current_client = described_object.instance_variable_get :@api_client
|
28
28
|
expect(current_client).to be_nil
|
29
29
|
end
|
30
30
|
|
@@ -24,26 +24,52 @@ describe LokaliseManager::TaskDefinitions::Exporter do
|
|
24
24
|
|
25
25
|
describe '.export!' do
|
26
26
|
it 'sends a proper API request and handles rate limiting' do
|
27
|
-
process =
|
28
|
-
|
29
|
-
|
27
|
+
process = nil
|
28
|
+
|
29
|
+
VCR.use_cassette('upload_files_multiple') do
|
30
|
+
expect(-> { process = described_object.export!.first.process }).to output(/complete!/).to_stdout
|
31
|
+
end
|
30
32
|
|
31
33
|
expect(process.project_id).to eq(project_id)
|
32
34
|
expect(process.status).to eq('queued')
|
33
35
|
end
|
34
36
|
|
35
37
|
it 'handles too many requests' do
|
38
|
+
allow(described_object.config).to receive(:max_retries_export).and_return(1)
|
39
|
+
allow(described_object).to receive(:sleep).and_return(0)
|
40
|
+
|
41
|
+
fake_client = instance_double('Lokalise::Client')
|
42
|
+
allow(fake_client).to receive(:token).with(any_args).and_return('fake_token')
|
43
|
+
allow(fake_client).to receive(:upload_file).with(any_args).and_raise(Lokalise::Error::TooManyRequests)
|
44
|
+
allow(described_object).to receive(:api_client).and_return(fake_client)
|
45
|
+
|
46
|
+
expect(-> { described_object.export! }).to raise_error(Lokalise::Error::TooManyRequests, /Gave up after 1 retries/i)
|
47
|
+
|
48
|
+
expect(described_object).to have_received(:sleep).exactly(6).times
|
49
|
+
expect(described_object).to have_received(:api_client).at_least(12).times
|
50
|
+
expect(fake_client).to have_received(:upload_file).exactly(12).times
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'handles too many requests but does not re-raise anything when raise_on_export_fail is false' do
|
54
|
+
allow(described_object.config).to receive(:max_retries_export).and_return(1)
|
55
|
+
allow(described_object.config).to receive(:raise_on_export_fail).and_return(false)
|
36
56
|
allow(described_object).to receive(:sleep).and_return(0)
|
37
57
|
|
38
58
|
fake_client = instance_double('Lokalise::Client')
|
59
|
+
allow(fake_client).to receive(:token).with(any_args).and_return('fake_token')
|
39
60
|
allow(fake_client).to receive(:upload_file).with(any_args).and_raise(Lokalise::Error::TooManyRequests)
|
40
61
|
allow(described_object).to receive(:api_client).and_return(fake_client)
|
62
|
+
processes = []
|
63
|
+
expect(-> { processes = described_object.export! }).not_to raise_error
|
41
64
|
|
42
|
-
expect(
|
65
|
+
expect(processes[0].path.to_s).to include('en_0')
|
66
|
+
expect(processes[0].success).to be false
|
67
|
+
expect(processes[1].error.class).to eq(Lokalise::Error::TooManyRequests)
|
68
|
+
expect(processes.count).to eq(7)
|
43
69
|
|
44
|
-
expect(described_object).to have_received(:sleep).exactly(
|
45
|
-
expect(described_object).to have_received(:api_client).
|
46
|
-
expect(fake_client).to have_received(:upload_file).exactly(
|
70
|
+
expect(described_object).to have_received(:sleep).exactly(7).times
|
71
|
+
expect(described_object).to have_received(:api_client).at_least(14).times
|
72
|
+
expect(fake_client).to have_received(:upload_file).exactly(14).times
|
47
73
|
end
|
48
74
|
end
|
49
75
|
end
|
@@ -58,10 +84,23 @@ describe LokaliseManager::TaskDefinitions::Exporter do
|
|
58
84
|
end
|
59
85
|
|
60
86
|
describe '.export!' do
|
87
|
+
it 'sends a proper API request but does not output anything when silent_mode is enabled' do
|
88
|
+
allow(described_object.config).to receive(:silent_mode).and_return(true)
|
89
|
+
|
90
|
+
process = nil
|
91
|
+
|
92
|
+
VCR.use_cassette('upload_files') do
|
93
|
+
expect(-> { process = described_object.export!.first.process }).not_to output(/complete!/).to_stdout
|
94
|
+
end
|
95
|
+
|
96
|
+
expect(process.status).to eq('queued')
|
97
|
+
expect(described_object.config).to have_received(:silent_mode).at_most(1).times
|
98
|
+
end
|
99
|
+
|
61
100
|
it 'sends a proper API request' do
|
62
101
|
process = VCR.use_cassette('upload_files') do
|
63
102
|
described_object.export!
|
64
|
-
end.first
|
103
|
+
end.first.process
|
65
104
|
|
66
105
|
expect(process.project_id).to eq(project_id)
|
67
106
|
expect(process.status).to eq('queued')
|
@@ -70,11 +109,16 @@ describe LokaliseManager::TaskDefinitions::Exporter do
|
|
70
109
|
it 'sends a proper API request when a different branch is provided' do
|
71
110
|
allow(described_object.config).to receive(:branch).and_return('develop')
|
72
111
|
|
73
|
-
|
112
|
+
process_data = VCR.use_cassette('upload_files_branch') do
|
74
113
|
described_object.export!
|
75
114
|
end.first
|
76
115
|
|
77
116
|
expect(described_object.config).to have_received(:branch).at_most(2).times
|
117
|
+
expect(process_data.success).to be true
|
118
|
+
expect(process_data.path.to_s).to include('en.yml')
|
119
|
+
|
120
|
+
process = process_data.process
|
121
|
+
expect(process).to be_an_instance_of(Lokalise::Resources::QueuedProcess)
|
78
122
|
expect(process.project_id).to eq(project_id)
|
79
123
|
expect(process.status).to eq('queued')
|
80
124
|
end
|
@@ -93,9 +137,9 @@ describe LokaliseManager::TaskDefinitions::Exporter do
|
|
93
137
|
end
|
94
138
|
end
|
95
139
|
|
96
|
-
describe '
|
140
|
+
describe '#all_files' do
|
97
141
|
it 'yield proper arguments' do
|
98
|
-
expect
|
142
|
+
expect(described_object.send(:all_files).first).to include(
|
99
143
|
Pathname.new(path),
|
100
144
|
Pathname.new(relative_name)
|
101
145
|
)
|
@@ -165,30 +209,29 @@ describe LokaliseManager::TaskDefinitions::Exporter do
|
|
165
209
|
end
|
166
210
|
end
|
167
211
|
|
168
|
-
describe '
|
169
|
-
it '
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
]
|
212
|
+
describe '#each_file' do
|
213
|
+
it 'returns all files' do
|
214
|
+
files = described_object.send(:all_files)
|
215
|
+
expect(files[0]).to include(
|
216
|
+
Pathname.new(path),
|
217
|
+
Pathname.new(relative_name)
|
218
|
+
)
|
219
|
+
expect(files[1]).to include(
|
220
|
+
Pathname.new(path_ru),
|
221
|
+
Pathname.new(filename_ru)
|
179
222
|
)
|
180
223
|
end
|
181
224
|
|
182
|
-
it 'does not
|
225
|
+
it 'does not return files that have to be skipped' do
|
183
226
|
allow(described_object.config).to receive(:skip_file_export).twice.and_return(
|
184
227
|
->(f) { f.split[1].to_s.include?('ru') }
|
185
228
|
)
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
]
|
229
|
+
files = described_object.send(:all_files)
|
230
|
+
expect(files[0]).to include(
|
231
|
+
Pathname.new(path),
|
232
|
+
Pathname.new(relative_name)
|
191
233
|
)
|
234
|
+
expect(files.count).to eq(1)
|
192
235
|
|
193
236
|
expect(described_object.config).to have_received(:skip_file_export).twice
|
194
237
|
end
|
@@ -9,12 +9,16 @@ describe LokaliseManager::TaskDefinitions::Importer do
|
|
9
9
|
let(:loc_path) { described_object.config.locales_path }
|
10
10
|
let(:project_id) { ENV['LOKALISE_PROJECT_ID'] }
|
11
11
|
let(:local_trans) { "#{Dir.getwd}/spec/fixtures/trans.zip" }
|
12
|
-
let(:faulty_trans) { "#{Dir.getwd}/spec/fixtures/faulty_trans.zip" }
|
13
12
|
|
14
|
-
describe '
|
13
|
+
describe '#open_and_process_zip' do
|
15
14
|
it 're-raises errors during file processing' do
|
16
|
-
|
17
|
-
|
15
|
+
entry = double
|
16
|
+
allow(entry).to receive(:name).and_return('fail.yml')
|
17
|
+
allow(described_object).to receive(:data_from).with(entry).and_raise(EncodingError)
|
18
|
+
expect(-> { described_object.send(:process!, entry) }).
|
19
|
+
to raise_error(EncodingError, /Error when trying to process fail\.yml/)
|
20
|
+
|
21
|
+
expect(described_object).to have_received(:data_from)
|
18
22
|
end
|
19
23
|
|
20
24
|
it 're-raises errors during file opening' do
|
@@ -23,7 +27,7 @@ describe LokaliseManager::TaskDefinitions::Importer do
|
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
26
|
-
describe '
|
30
|
+
describe '#download_files' do
|
27
31
|
it 'returns a proper download URL' do
|
28
32
|
response = VCR.use_cassette('download_files') do
|
29
33
|
described_object.send :download_files
|
@@ -103,15 +107,31 @@ describe LokaliseManager::TaskDefinitions::Importer do
|
|
103
107
|
end
|
104
108
|
|
105
109
|
it 'runs import successfully' do
|
106
|
-
result =
|
107
|
-
|
110
|
+
result = nil
|
111
|
+
|
112
|
+
VCR.use_cassette('import') do
|
113
|
+
expect(-> { result = described_object.import! }).to output(/complete!/).to_stdout
|
108
114
|
end
|
109
115
|
|
110
116
|
expect(result).to be true
|
111
117
|
|
112
|
-
expect(count_translations).to eq(
|
113
|
-
expect_file_exist loc_path, '
|
114
|
-
expect_file_exist loc_path, '
|
118
|
+
expect(count_translations).to eq(24)
|
119
|
+
expect_file_exist loc_path, 'en_1.yml'
|
120
|
+
expect_file_exist loc_path, 'ru_2.yml'
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'runs import successfully but does not provide any output when silent_mode is enabled' do
|
124
|
+
allow(described_object.config).to receive(:silent_mode).and_return(true)
|
125
|
+
result = nil
|
126
|
+
|
127
|
+
VCR.use_cassette('import') do
|
128
|
+
expect(-> { result = described_object.import! }).not_to output(/complete!/).to_stdout
|
129
|
+
end
|
130
|
+
|
131
|
+
expect(result).to be true
|
132
|
+
expect_file_exist loc_path, 'en_1.yml'
|
133
|
+
expect_file_exist loc_path, 'ru_2.yml'
|
134
|
+
expect(described_object.config).to have_received(:silent_mode).at_most(1).times
|
115
135
|
end
|
116
136
|
end
|
117
137
|
|
@@ -157,9 +177,21 @@ describe LokaliseManager::TaskDefinitions::Importer do
|
|
157
177
|
it 'import halts when a user chooses not to proceed' do
|
158
178
|
allow(safe_mode_obj).to receive(:download_files).at_most(0).times
|
159
179
|
allow($stdin).to receive(:gets).and_return('N')
|
160
|
-
expect(-> { safe_mode_obj.import! }).to output(/
|
180
|
+
expect(-> { safe_mode_obj.import! }).to output(/cancelled/).to_stdout
|
181
|
+
|
182
|
+
expect(safe_mode_obj).not_to have_received(:download_files)
|
183
|
+
expect($stdin).to have_received(:gets)
|
184
|
+
expect(count_translations).to eq(1)
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'import halts when a user chooses not to proceed and debug info is not printed out when silent_mode is enabled' do
|
188
|
+
allow(safe_mode_obj.config).to receive(:silent_mode).and_return(true)
|
189
|
+
allow(safe_mode_obj).to receive(:download_files).at_most(0).times
|
190
|
+
allow($stdin).to receive(:gets).and_return('N')
|
191
|
+
expect(-> { safe_mode_obj.import! }).not_to output(/cancelled/).to_stdout
|
161
192
|
|
162
193
|
expect(safe_mode_obj).not_to have_received(:download_files)
|
194
|
+
expect(safe_mode_obj.config).to have_received(:silent_mode)
|
163
195
|
expect($stdin).to have_received(:gets)
|
164
196
|
expect(count_translations).to eq(1)
|
165
197
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe LokaliseManager::Utils::ArrayUtils do
|
4
|
+
using described_class
|
5
|
+
let(:arr) { (1..8).to_a }
|
6
|
+
|
7
|
+
describe '#in_groups_of' do
|
8
|
+
it 'raises an exception when the number is less than 1' do
|
9
|
+
expect(-> { arr.in_groups_of(-1) }).to raise_error(ArgumentError)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'uses collection itself if fill_with is false' do
|
13
|
+
enum = arr.in_groups_of(5, false)
|
14
|
+
enum.next
|
15
|
+
expect(enum.next.count).to eq(3)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe LokaliseManager::Utils::HashUtils do
|
4
|
+
using described_class
|
5
|
+
let(:h1) { {a: 100, b: 200, c: {c1: 100}} }
|
6
|
+
let(:h2) { {b: 250, c: {c1: 200}} }
|
7
|
+
|
8
|
+
specify '#deep_merge' do
|
9
|
+
result = h1.deep_merge(h2) { |_key, this_val, other_val| this_val + other_val }
|
10
|
+
expect(result[:b]).to eq(450)
|
11
|
+
expect(result[:c][:c1]).to eq(300)
|
12
|
+
end
|
13
|
+
end
|
data/spec/support/vcr.rb
CHANGED
@@ -6,6 +6,8 @@ VCR.configure do |c|
|
|
6
6
|
c.ignore_hosts 'codeclimate.com'
|
7
7
|
c.hook_into :faraday
|
8
8
|
c.cassette_library_dir = File.join(File.dirname(__FILE__), '..', 'fixtures', 'vcr_cassettes')
|
9
|
-
c.filter_sensitive_data('<LOKALISE_TOKEN>')
|
9
|
+
c.filter_sensitive_data('<LOKALISE_TOKEN>') do |_i|
|
10
|
+
ENV.fetch('LOKALISE_API_TOKEN')
|
11
|
+
end
|
10
12
|
c.configure_rspec_metadata!
|
11
13
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lokalise_manager
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ilya Bodrov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-lokalise-api
|
@@ -128,14 +128,14 @@ dependencies:
|
|
128
128
|
requirements:
|
129
129
|
- - "~>"
|
130
130
|
- !ruby/object:Gem::Version
|
131
|
-
version: 2.
|
131
|
+
version: '2.6'
|
132
132
|
type: :development
|
133
133
|
prerelease: false
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
135
135
|
requirements:
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
|
-
version: 2.
|
138
|
+
version: '2.6'
|
139
139
|
- !ruby/object:Gem::Dependency
|
140
140
|
name: simplecov
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -187,6 +187,8 @@ files:
|
|
187
187
|
- lib/lokalise_manager/task_definitions/base.rb
|
188
188
|
- lib/lokalise_manager/task_definitions/exporter.rb
|
189
189
|
- lib/lokalise_manager/task_definitions/importer.rb
|
190
|
+
- lib/lokalise_manager/utils/array_utils.rb
|
191
|
+
- lib/lokalise_manager/utils/hash_utils.rb
|
190
192
|
- lib/lokalise_manager/version.rb
|
191
193
|
- lokalise_manager.gemspec
|
192
194
|
- spec/lib/lokalise_manager/global_config_spec.rb
|
@@ -194,6 +196,8 @@ files:
|
|
194
196
|
- spec/lib/lokalise_manager/task_definitions/exporter_spec.rb
|
195
197
|
- spec/lib/lokalise_manager/task_definitions/importer_spec.rb
|
196
198
|
- spec/lib/lokalise_manager_spec.rb
|
199
|
+
- spec/lib/utils/array_utils_spec.rb
|
200
|
+
- spec/lib/utils/hash_utils_spec.rb
|
197
201
|
- spec/spec_helper.rb
|
198
202
|
- spec/support/file_manager.rb
|
199
203
|
- spec/support/spec_addons.rb
|
@@ -201,7 +205,8 @@ files:
|
|
201
205
|
homepage: https://github.com/bodrovis/lokalise_manager
|
202
206
|
licenses:
|
203
207
|
- MIT
|
204
|
-
metadata:
|
208
|
+
metadata:
|
209
|
+
rubygems_mfa_required: 'true'
|
205
210
|
post_install_message:
|
206
211
|
rdoc_options: []
|
207
212
|
require_paths:
|
@@ -217,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
217
222
|
- !ruby/object:Gem::Version
|
218
223
|
version: '0'
|
219
224
|
requirements: []
|
220
|
-
rubygems_version: 3.
|
225
|
+
rubygems_version: 3.3.5
|
221
226
|
signing_key:
|
222
227
|
specification_version: 4
|
223
228
|
summary: Lokalise integration for Ruby
|
@@ -227,6 +232,8 @@ test_files:
|
|
227
232
|
- spec/lib/lokalise_manager/task_definitions/exporter_spec.rb
|
228
233
|
- spec/lib/lokalise_manager/task_definitions/importer_spec.rb
|
229
234
|
- spec/lib/lokalise_manager_spec.rb
|
235
|
+
- spec/lib/utils/array_utils_spec.rb
|
236
|
+
- spec/lib/utils/hash_utils_spec.rb
|
230
237
|
- spec/spec_helper.rb
|
231
238
|
- spec/support/file_manager.rb
|
232
239
|
- spec/support/spec_addons.rb
|