lokalise_manager 5.1.1 → 6.0.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 +17 -1
- data/README.md +1 -1
- data/lib/lokalise_manager/error.rb +1 -1
- data/lib/lokalise_manager/global_config.rb +4 -4
- data/lib/lokalise_manager/task_definitions/base.rb +36 -26
- data/lib/lokalise_manager/task_definitions/exporter.rb +35 -38
- data/lib/lokalise_manager/task_definitions/importer.rb +6 -17
- data/lib/lokalise_manager/version.rb +1 -1
- data/lokalise_manager.gemspec +2 -2
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0df6efdcebd1ef7b1f3992a14c997d56859553cf0e1b089d43fe496f479bb1e
|
4
|
+
data.tar.gz: 762127da9ea96e1b5ad532a5217d0f06556b3d9f61b65f722f79a50a406bba23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42c3990642e4598cc746f1f991d57bceb1793b0de6f8a9ff8b7a33d42194c47a9d84243ee045658a4925dda13d2b6ee63bb0464bd48c8094951b60c2b83e8c9b
|
7
|
+
data.tar.gz: 156793b3c527be7e88fa4790db675f98958ba80a2be717259f4e3fca937a1369b9660210d8778b727cce1249f9237873507c6800e88eec33d40aac3bf9b42c8a
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,22 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
##
|
3
|
+
## 6.0.0 (29-Nov-2024)
|
4
|
+
|
5
|
+
* **Breaking change**: rename the `timeouts` config method to `additional_client_opts`. It has the same usage but now enables you to set both client timeouts and override the API host to send requests to.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
additional_client_opts: {
|
9
|
+
open_timeout: 100,
|
10
|
+
timeout: 500,
|
11
|
+
api_host: 'http://example.com/api'
|
12
|
+
}
|
13
|
+
```
|
14
|
+
|
15
|
+
## 5.1.2 (01-Nov-2024)
|
16
|
+
|
17
|
+
* Update dependencies
|
18
|
+
|
19
|
+
## 5.1.1 (10-May-2024)
|
4
20
|
|
5
21
|
* Update documentation, minor code fixes
|
6
22
|
|
data/README.md
CHANGED
@@ -108,7 +108,7 @@ Please don't forget that Lokalise API has rate limiting and you cannot send more
|
|
108
108
|
* `project_id` (`string`, required) — Lokalise project ID. You must have import/export permissions in the specified project.
|
109
109
|
* `locales_path` (`string`) — path to the directory with your translation files. Defaults to `"#{Dir.getwd}/locales"`.
|
110
110
|
* `branch` (`string`) — Lokalise project branch to use. Defaults to `""` (no branch is provided).
|
111
|
-
* `
|
111
|
+
* `additional_client_opts` (`hash`) — set [request timeouts and API host for the Lokalise client](https://lokalise.github.io/ruby-lokalise-api/additional_info/customization). By default, requests have no timeouts and the API host is not overriden: `{open_timeout: nil, timeout: nil, api_host: nil}`. Timeout values are in seconds.
|
112
112
|
* `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.
|
113
113
|
|
114
114
|
### Import config
|
@@ -7,7 +7,7 @@ module LokaliseManager
|
|
7
7
|
class << self
|
8
8
|
attr_accessor :api_token, :project_id
|
9
9
|
attr_writer :import_opts, :import_safe_mode, :export_opts, :locales_path,
|
10
|
-
:file_ext_regexp, :skip_file_export, :branch, :
|
10
|
+
:file_ext_regexp, :skip_file_export, :branch, :additional_client_opts,
|
11
11
|
:translations_loader, :translations_converter, :lang_iso_inferer,
|
12
12
|
:max_retries_export, :max_retries_import, :use_oauth2_token, :silent_mode,
|
13
13
|
:raise_on_export_fail
|
@@ -42,9 +42,9 @@ module LokaliseManager
|
|
42
42
|
@branch || ''
|
43
43
|
end
|
44
44
|
|
45
|
-
# Return API
|
46
|
-
def
|
47
|
-
@
|
45
|
+
# Return additional API client options
|
46
|
+
def additional_client_opts
|
47
|
+
@additional_client_opts || {}
|
48
48
|
end
|
49
49
|
|
50
50
|
# Return the max retries for export
|
@@ -5,9 +5,8 @@ require 'pathname'
|
|
5
5
|
|
6
6
|
module LokaliseManager
|
7
7
|
module TaskDefinitions
|
8
|
-
# Base class for LokaliseManager task definitions,
|
9
|
-
#
|
10
|
-
# client interactions and configuration merging.
|
8
|
+
# Base class for LokaliseManager task definitions, providing common methods and logic
|
9
|
+
# for importer and exporter classes. Handles API client interactions and configuration merging.
|
11
10
|
class Base
|
12
11
|
using LokaliseManager::Utils::HashUtils
|
13
12
|
|
@@ -18,31 +17,15 @@ module LokaliseManager
|
|
18
17
|
# @param custom_opts [Hash] Custom configurations for specific tasks.
|
19
18
|
# @param global_config [Object] Reference to the global configuration.
|
20
19
|
def initialize(custom_opts = {}, global_config = LokaliseManager::GlobalConfig)
|
21
|
-
|
22
|
-
|
23
|
-
.filter { |m| m.to_s.end_with?('=') }
|
24
|
-
.each_with_object({}) do |method, opts|
|
25
|
-
reader = method.to_s.delete_suffix('=')
|
26
|
-
opts[reader.to_sym] = global_config.send(reader)
|
27
|
-
end
|
28
|
-
|
29
|
-
all_opts = primary_opts.deep_merge(custom_opts)
|
30
|
-
|
31
|
-
config_klass = Struct.new(*all_opts.keys, keyword_init: true)
|
32
|
-
|
33
|
-
@config = config_klass.new all_opts
|
20
|
+
merged_opts = merge_configs(global_config, custom_opts)
|
21
|
+
@config = build_config_class(merged_opts)
|
34
22
|
end
|
35
23
|
|
36
|
-
#
|
24
|
+
# Retrieves or creates a Lokalise API client based on configuration.
|
37
25
|
#
|
38
26
|
# @return [RubyLokaliseApi::Client] Lokalise API client.
|
39
27
|
def api_client
|
40
|
-
|
41
|
-
|
42
|
-
client_opts = [config.api_token, config.timeouts]
|
43
|
-
client_method = config.use_oauth2_token ? :oauth2_client : :client
|
44
|
-
|
45
|
-
@api_client = ::RubyLokaliseApi.send(client_method, *client_opts)
|
28
|
+
@api_client ||= create_api_client
|
46
29
|
end
|
47
30
|
|
48
31
|
# Resets API client
|
@@ -54,15 +37,42 @@ module LokaliseManager
|
|
54
37
|
|
55
38
|
private
|
56
39
|
|
40
|
+
# Creates a Lokalise API client based on configuration.
|
41
|
+
def create_api_client
|
42
|
+
client_opts = [config.api_token, config.additional_client_opts]
|
43
|
+
client_method = config.use_oauth2_token ? :oauth2_client : :client
|
44
|
+
|
45
|
+
::RubyLokaliseApi.public_send(client_method, *client_opts)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Merges global and custom configurations.
|
49
|
+
def merge_configs(global_config, custom_opts)
|
50
|
+
primary_opts = global_config
|
51
|
+
.singleton_methods
|
52
|
+
.select { |m| m.to_s.end_with?('=') }
|
53
|
+
.each_with_object({}) do |method, opts|
|
54
|
+
reader = method.to_s.delete_suffix('=')
|
55
|
+
opts[reader.to_sym] = global_config.public_send(reader)
|
56
|
+
end
|
57
|
+
|
58
|
+
primary_opts.deep_merge(custom_opts)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Builds a config class with the given options.
|
62
|
+
def build_config_class(all_opts)
|
63
|
+
config_klass = Struct.new(*all_opts.keys, keyword_init: true)
|
64
|
+
config_klass.new(all_opts)
|
65
|
+
end
|
66
|
+
|
57
67
|
# Checks and validates task options, raising errors if configurations are missing.
|
58
68
|
def check_options_errors!
|
59
69
|
errors = []
|
60
70
|
errors << 'Project ID is not set!' if config.project_id.nil? || config.project_id.empty?
|
61
71
|
errors << 'Lokalise API token is not set!' if config.api_token.nil? || config.api_token.empty?
|
62
|
-
raise LokaliseManager::Error, errors.join(' ')
|
72
|
+
raise LokaliseManager::Error, errors.join(' ') unless errors.empty?
|
63
73
|
end
|
64
74
|
|
65
|
-
#
|
75
|
+
# Checks if the file has the correct extension based on the configuration.
|
66
76
|
#
|
67
77
|
# @param raw_path [String, Pathname] Path to check.
|
68
78
|
# @return [Boolean] True if the extension matches, false otherwise.
|
@@ -91,7 +101,7 @@ module LokaliseManager
|
|
91
101
|
# Until this is fixed, we revert to this quick'n'dirty solution.
|
92
102
|
EXCEPTIONS = [JSON::ParserError, RubyLokaliseApi::Error::TooManyRequests].freeze
|
93
103
|
|
94
|
-
#
|
104
|
+
# Handles retries with exponential backoff for specific exceptions.
|
95
105
|
def with_exp_backoff(max_retries)
|
96
106
|
return unless block_given?
|
97
107
|
|
@@ -15,13 +15,9 @@ module LokaliseManager
|
|
15
15
|
def export!
|
16
16
|
check_options_errors!
|
17
17
|
|
18
|
-
queued_processes =
|
19
|
-
|
20
|
-
|
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
|
18
|
+
queued_processes = all_files.each_slice(MAX_THREADS).flat_map do |files_group|
|
19
|
+
parallel_upload(files_group).tap do |threads|
|
20
|
+
threads.each { |thr| raise_on_fail(thr) if config.raise_on_export_fail }
|
25
21
|
end
|
26
22
|
end
|
27
23
|
|
@@ -36,17 +32,19 @@ module LokaliseManager
|
|
36
32
|
#
|
37
33
|
# @param files_group [Array] Group of files to be uploaded.
|
38
34
|
# @return [Array] Array of threads handling the file uploads.
|
39
|
-
|
40
35
|
def parallel_upload(files_group)
|
41
36
|
files_group.map do |file_data|
|
42
37
|
Thread.new { do_upload(*file_data) }
|
43
38
|
end.map(&:value)
|
44
39
|
end
|
45
40
|
|
41
|
+
# Raises an error if a file upload thread failed.
|
42
|
+
#
|
43
|
+
# @param thread [Struct] The result of the file upload thread.
|
46
44
|
def raise_on_fail(thread)
|
47
45
|
return if thread.success
|
48
46
|
|
49
|
-
raise
|
47
|
+
raise thread.error.class, "Error while trying to upload #{thread.path}: #{thread.error.message}"
|
50
48
|
end
|
51
49
|
|
52
50
|
# Performs the actual upload of a file to Lokalise.
|
@@ -56,56 +54,55 @@ module LokaliseManager
|
|
56
54
|
# @return [Struct] A struct with the success status, process details, and any error information.
|
57
55
|
def do_upload(f_path, r_path)
|
58
56
|
proc_klass = Struct.new(:success, :process, :path, :error, keyword_init: true)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
63
|
-
proc_klass.new(success: true, process: process, path: f_path)
|
64
|
-
rescue StandardError => e
|
65
|
-
proc_klass.new(success: false, path: f_path, error: e)
|
57
|
+
|
58
|
+
process = with_exp_backoff(config.max_retries_export) do
|
59
|
+
api_client.upload_file(project_id_with_branch, opts(f_path, r_path))
|
66
60
|
end
|
61
|
+
|
62
|
+
proc_klass.new(success: true, process: process, path: f_path)
|
63
|
+
rescue StandardError => e
|
64
|
+
proc_klass.new(success: false, path: f_path, error: e)
|
67
65
|
end
|
68
66
|
|
69
67
|
# Prints a completion message to standard output.
|
70
68
|
def print_completion_message
|
71
|
-
$stdout.
|
69
|
+
$stdout.puts 'Task complete!'
|
72
70
|
end
|
73
71
|
|
74
|
-
#
|
72
|
+
# Retrieves all translation files from the specified directory.
|
73
|
+
#
|
74
|
+
# @return [Array] Array of [Pathname, Pathname] pairs representing full and relative paths.
|
75
75
|
def all_files
|
76
|
-
loc_path = config.locales_path
|
77
|
-
Dir["#{loc_path}/**/*"].filter_map do |f|
|
78
|
-
full_path = Pathname.new f
|
76
|
+
loc_path = Pathname.new(config.locales_path)
|
79
77
|
|
80
|
-
|
81
|
-
|
82
|
-
|
78
|
+
Dir["#{loc_path}/**/*"].filter_map do |file|
|
79
|
+
full_path = Pathname.new(file)
|
80
|
+
next unless file_matches_criteria?(full_path)
|
83
81
|
|
82
|
+
relative_path = full_path.relative_path_from(loc_path)
|
84
83
|
[full_path, relative_path]
|
85
84
|
end
|
86
85
|
end
|
87
86
|
|
88
|
-
# Generates
|
87
|
+
# Generates options for file upload to Lokalise.
|
89
88
|
#
|
90
|
-
# @
|
91
|
-
# @param
|
92
|
-
# @
|
89
|
+
# @param full_p [Pathname] Full path to the file.
|
90
|
+
# @param relative_p [Pathname] Relative path within the project.
|
91
|
+
# @return [Hash] Options for the Lokalise API upload.
|
93
92
|
def opts(full_p, relative_p)
|
94
|
-
content = File.read
|
93
|
+
content = File.read(full_p).strip
|
95
94
|
|
96
|
-
|
97
|
-
data: Base64.strict_encode64(content
|
98
|
-
filename: relative_p,
|
95
|
+
{
|
96
|
+
data: Base64.strict_encode64(content),
|
97
|
+
filename: relative_p.to_s,
|
99
98
|
lang_iso: config.lang_iso_inferer.call(content, full_p)
|
100
|
-
}
|
101
|
-
|
102
|
-
initial_opts.merge config.export_opts
|
99
|
+
}.merge(config.export_opts)
|
103
100
|
end
|
104
101
|
|
105
|
-
# Checks whether the specified file
|
102
|
+
# Checks whether the specified file meets the criteria for upload.
|
106
103
|
#
|
107
|
-
# @
|
108
|
-
# @
|
104
|
+
# @param full_path [Pathname] Full path to the file.
|
105
|
+
# @return [Boolean] True if the file matches criteria, false otherwise.
|
109
106
|
def file_matches_criteria?(full_path)
|
110
107
|
full_path.file? && proper_ext?(full_path) &&
|
111
108
|
!config.skip_file_export.call(full_path)
|
@@ -6,9 +6,8 @@ require 'fileutils'
|
|
6
6
|
|
7
7
|
module LokaliseManager
|
8
8
|
module TaskDefinitions
|
9
|
-
# The Importer class
|
10
|
-
# and importing them into the specified project directory.
|
11
|
-
# which provides shared functionality and configuration management.
|
9
|
+
# The Importer class handles downloading translation files from Lokalise
|
10
|
+
# and importing them into the specified project directory.
|
12
11
|
class Importer < Base
|
13
12
|
# Initiates the import process by checking configuration, ensuring safe mode conditions,
|
14
13
|
# downloading files, and processing them. Outputs task completion status.
|
@@ -46,11 +45,7 @@ module LokaliseManager
|
|
46
45
|
# @param path [String] The URL or local path to the ZIP archive.
|
47
46
|
def open_and_process_zip(path)
|
48
47
|
Zip::File.open_buffer(open_file_or_remote(path)) do |zip|
|
49
|
-
zip.each
|
50
|
-
next unless proper_ext?(entry.name)
|
51
|
-
|
52
|
-
process_entry(entry)
|
53
|
-
end
|
48
|
+
zip.each { |entry| process_entry(entry) if proper_ext?(entry.name) }
|
54
49
|
end
|
55
50
|
rescue StandardError => e
|
56
51
|
raise e.class, "Error processing ZIP file: #{e.message}"
|
@@ -80,8 +75,7 @@ module LokaliseManager
|
|
80
75
|
|
81
76
|
$stdout.puts "The target directory #{config.locales_path} is not empty!"
|
82
77
|
$stdout.print 'Enter Y to continue: '
|
83
|
-
|
84
|
-
answer.to_s.strip == 'Y'
|
78
|
+
$stdin.gets.strip.upcase == 'Y'
|
85
79
|
end
|
86
80
|
|
87
81
|
# Opens a local file or a remote URL using the provided path, safely handling different path schemes.
|
@@ -89,13 +83,8 @@ module LokaliseManager
|
|
89
83
|
# @param path [String] The path to the file, either a local path or a URL.
|
90
84
|
# @return [IO] Returns an IO object for the file.
|
91
85
|
def open_file_or_remote(path)
|
92
|
-
|
93
|
-
|
94
|
-
if parsed_path&.scheme&.include?('http')
|
95
|
-
parsed_path.open
|
96
|
-
else
|
97
|
-
File.open path
|
98
|
-
end
|
86
|
+
uri = URI.parse(path)
|
87
|
+
uri.scheme&.start_with?('http') ? uri.open : File.open(path)
|
99
88
|
end
|
100
89
|
|
101
90
|
# Loads translations from the ZIP file.
|
data/lokalise_manager.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.extra_rdoc_files = ['README.md']
|
24
24
|
spec.require_paths = ['lib']
|
25
25
|
|
26
|
-
spec.add_dependency 'ruby-lokalise-api', '~> 9.
|
26
|
+
spec.add_dependency 'ruby-lokalise-api', '~> 9.3'
|
27
27
|
spec.add_dependency 'rubyzip', '~> 2.3'
|
28
28
|
spec.add_dependency 'zeitwerk', '~> 2.4'
|
29
29
|
|
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_development_dependency 'rubocop', '~> 1.0'
|
34
34
|
spec.add_development_dependency 'rubocop-performance', '~> 1.5'
|
35
35
|
spec.add_development_dependency 'rubocop-rake', '~> 0.6'
|
36
|
-
spec.add_development_dependency 'rubocop-rspec', '~>
|
36
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 3.0'
|
37
37
|
spec.add_development_dependency 'simplecov', '~> 0.16'
|
38
38
|
spec.add_development_dependency 'simplecov-lcov', '~> 0.8'
|
39
39
|
spec.add_development_dependency 'webmock', '~> 3.18'
|
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: 6.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ilya Krukowski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-lokalise-api
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '9.
|
19
|
+
version: '9.3'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '9.
|
26
|
+
version: '9.3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rubyzip
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,14 +142,14 @@ dependencies:
|
|
142
142
|
requirements:
|
143
143
|
- - "~>"
|
144
144
|
- !ruby/object:Gem::Version
|
145
|
-
version: '
|
145
|
+
version: '3.0'
|
146
146
|
type: :development
|
147
147
|
prerelease: false
|
148
148
|
version_requirements: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
150
|
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
|
-
version: '
|
152
|
+
version: '3.0'
|
153
153
|
- !ruby/object:Gem::Dependency
|
154
154
|
name: simplecov
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -238,7 +238,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
238
238
|
- !ruby/object:Gem::Version
|
239
239
|
version: '0'
|
240
240
|
requirements: []
|
241
|
-
rubygems_version: 3.5.
|
241
|
+
rubygems_version: 3.5.23
|
242
242
|
signing_key:
|
243
243
|
specification_version: 4
|
244
244
|
summary: Lokalise integration for Ruby
|