lokalise_manager 6.0.0 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +1 -0
- data/lib/lokalise_manager/global_config.rb +6 -1
- data/lib/lokalise_manager/task_definitions/base.rb +57 -26
- data/lib/lokalise_manager/task_definitions/exporter.rb +37 -24
- data/lib/lokalise_manager/task_definitions/importer.rb +92 -31
- data/lib/lokalise_manager/version.rb +1 -1
- data/lib/lokalise_manager.rb +35 -16
- data/lokalise_manager.gemspec +1 -0
- metadata +17 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b058f5acea2966c452c6ad194fbc4e82777c4624d58526f2fde8cc1b81ce0f16
|
4
|
+
data.tar.gz: 133c9fbd8e7d41ca827719e407ecf40612374c3ef5f39602994d4bbe9fa2ab15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0403ce7ce14ff83d4b2b3e0688350f666a29e6e8d50f4bb224a2c048247f74998569328859e911c2f833d5a7f9d7c1ccba736c94b5c6e2aaf1f4b70c2833c1ca
|
7
|
+
data.tar.gz: 6d4dc48b94da3882706c5c30f6600e899b427fce7e5a7c6ea9e192c9c62c2eadbe2d3228c73d8d49b3a3ed6023b6681dac54f2c58c6124aac147a6588267acf9
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 6.1.1 (20-Feb-2025)
|
4
|
+
|
5
|
+
* Prevent error swallowing in rare cases
|
6
|
+
|
7
|
+
## 6.1.0 (19-Feb-2025)
|
8
|
+
|
9
|
+
* Added support for `import_async` option (default to `false`). When enabled, the [import process will happen in the background](https://developers.lokalise.com/reference/download-files-async) and the gem will use exponential backoff to wait for its completion according to the `max_retries_import` option.
|
10
|
+
|
3
11
|
## 6.0.0 (29-Nov-2024)
|
4
12
|
|
5
13
|
* **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.
|
data/README.md
CHANGED
@@ -146,6 +146,7 @@ importer = LokaliseManager.importer api_token: '1234abc',
|
|
146
146
|
|
147
147
|
* `import_safe_mode` (`boolean`) — default to `false`. When this option is enabled, the import task will check whether the directory set with `locales_path` is empty or not. If it is not empty, you will be prompted to continue.
|
148
148
|
* `max_retries_import` (`integer`) — this option is introduced to properly handle Lokalise API rate limiting. If the HTTP status code 429 (too many requests) has been received, this gem will apply an exponential backoff mechanism with a very simple formula: `2 ** retries`. If the maximum number of retries has been reached, a `RubyLokaliseApi::Error::TooManyRequests` exception will be raised and the operation will be halted.
|
149
|
+
* `import_async` (`boolean`) — default to `false`. Runs the [import in the background](https://developers.lokalise.com/reference/download-files-async) on Lokalise. Uses exponential backoff to wait for completion, based on `max_retries_import`. Useful only for large projects.
|
149
150
|
|
150
151
|
### Export config
|
151
152
|
|
@@ -10,7 +10,7 @@ module LokaliseManager
|
|
10
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
|
-
:raise_on_export_fail
|
13
|
+
:raise_on_export_fail, :import_async
|
14
14
|
|
15
15
|
# Yield self to block for configuration
|
16
16
|
def config
|
@@ -84,6 +84,11 @@ module LokaliseManager
|
|
84
84
|
@import_safe_mode.nil? ? false : @import_safe_mode
|
85
85
|
end
|
86
86
|
|
87
|
+
# Return whether import should be performed asynchronously
|
88
|
+
def import_async
|
89
|
+
@import_async.nil? ? false : @import_async
|
90
|
+
end
|
91
|
+
|
87
92
|
# Return whether to skip file export based on a lambda condition
|
88
93
|
def skip_file_export
|
89
94
|
@skip_file_export || ->(_) { false }
|
@@ -5,30 +5,43 @@ require 'pathname'
|
|
5
5
|
|
6
6
|
module LokaliseManager
|
7
7
|
module TaskDefinitions
|
8
|
-
# Base class for LokaliseManager task definitions
|
9
|
-
#
|
8
|
+
# Base class for LokaliseManager task definitions.
|
9
|
+
#
|
10
|
+
# Provides shared functionality for Importer and Exporter classes, including:
|
11
|
+
# - API client management.
|
12
|
+
# - Configuration merging.
|
13
|
+
# - File validation helpers.
|
14
|
+
# - Exponential backoff for retrying failed API requests.
|
10
15
|
class Base
|
11
16
|
using LokaliseManager::Utils::HashUtils
|
12
17
|
|
13
18
|
attr_accessor :config
|
14
19
|
|
15
|
-
#
|
20
|
+
# Defines exceptions that should trigger a retry with exponential backoff.
|
16
21
|
#
|
17
|
-
#
|
18
|
-
#
|
22
|
+
# - `JSON::ParserError`: Occurs when the API responds with non-JSON content (e.g., HTML due to rate limits).
|
23
|
+
# - `RubyLokaliseApi::Error::TooManyRequests`: Raised when too many requests are sent in a short period.
|
24
|
+
EXCEPTIONS = [JSON::ParserError, RubyLokaliseApi::Error::TooManyRequests].freeze
|
25
|
+
|
26
|
+
# Initializes a new task object with merged global and custom configurations.
|
27
|
+
#
|
28
|
+
# @param custom_opts [Hash] Custom configuration options specific to the task.
|
29
|
+
# @param global_config [Object] The global configuration object.
|
19
30
|
def initialize(custom_opts = {}, global_config = LokaliseManager::GlobalConfig)
|
20
31
|
merged_opts = merge_configs(global_config, custom_opts)
|
21
32
|
@config = build_config_class(merged_opts)
|
22
33
|
end
|
23
34
|
|
24
|
-
# Retrieves or
|
35
|
+
# Retrieves or initializes the Lokalise API client based on the current configuration.
|
25
36
|
#
|
26
|
-
# @return [RubyLokaliseApi::Client] Lokalise API client.
|
37
|
+
# @return [RubyLokaliseApi::Client] An instance of the Lokalise API client.
|
27
38
|
def api_client
|
28
39
|
@api_client ||= create_api_client
|
29
40
|
end
|
30
41
|
|
31
|
-
# Resets API client
|
42
|
+
# Resets the API client, clearing cached instances.
|
43
|
+
#
|
44
|
+
# Useful when switching authentication tokens or handling connection issues.
|
32
45
|
def reset_api_client!
|
33
46
|
::RubyLokaliseApi.reset_client!
|
34
47
|
::RubyLokaliseApi.reset_oauth2_client!
|
@@ -37,7 +50,9 @@ module LokaliseManager
|
|
37
50
|
|
38
51
|
private
|
39
52
|
|
40
|
-
# Creates a Lokalise API client based on configuration.
|
53
|
+
# Creates a new Lokalise API client instance based on the configuration.
|
54
|
+
#
|
55
|
+
# @return [RubyLokaliseApi::Client] The initialized API client.
|
41
56
|
def create_api_client
|
42
57
|
client_opts = [config.api_token, config.additional_client_opts]
|
43
58
|
client_method = config.use_oauth2_token ? :oauth2_client : :client
|
@@ -46,6 +61,13 @@ module LokaliseManager
|
|
46
61
|
end
|
47
62
|
|
48
63
|
# Merges global and custom configurations.
|
64
|
+
#
|
65
|
+
# - Extracts all global config values.
|
66
|
+
# - Merges them with custom options using a deep merge strategy.
|
67
|
+
#
|
68
|
+
# @param global_config [Object] The global configuration object.
|
69
|
+
# @param custom_opts [Hash] The custom configuration options.
|
70
|
+
# @return [Hash] The merged configuration.
|
49
71
|
def merge_configs(global_config, custom_opts)
|
50
72
|
primary_opts = global_config
|
51
73
|
.singleton_methods
|
@@ -58,13 +80,19 @@ module LokaliseManager
|
|
58
80
|
primary_opts.deep_merge(custom_opts)
|
59
81
|
end
|
60
82
|
|
61
|
-
#
|
83
|
+
# Constructs a configuration object from a hash of options.
|
84
|
+
#
|
85
|
+
# Uses a struct to provide attribute-style access to settings.
|
86
|
+
#
|
87
|
+
# @param all_opts [Hash] The merged configuration options.
|
88
|
+
# @return [Struct] A configuration object.
|
62
89
|
def build_config_class(all_opts)
|
63
|
-
|
64
|
-
config_klass.new(all_opts)
|
90
|
+
Struct.new(*all_opts.keys, keyword_init: true).new(all_opts)
|
65
91
|
end
|
66
92
|
|
67
|
-
#
|
93
|
+
# Validates required configuration options.
|
94
|
+
#
|
95
|
+
# @raise [LokaliseManager::Error] If required configurations are missing.
|
68
96
|
def check_options_errors!
|
69
97
|
errors = []
|
70
98
|
errors << 'Project ID is not set!' if config.project_id.nil? || config.project_id.empty?
|
@@ -72,36 +100,39 @@ module LokaliseManager
|
|
72
100
|
raise LokaliseManager::Error, errors.join(' ') unless errors.empty?
|
73
101
|
end
|
74
102
|
|
75
|
-
# Checks if
|
103
|
+
# Checks if a file has a valid extension based on the configuration.
|
76
104
|
#
|
77
|
-
# @param raw_path [String, Pathname]
|
78
|
-
# @return [Boolean]
|
105
|
+
# @param raw_path [String, Pathname] The file path to check.
|
106
|
+
# @return [Boolean] `true` if the file has a valid extension, `false` otherwise.
|
79
107
|
def proper_ext?(raw_path)
|
80
108
|
path = raw_path.is_a?(Pathname) ? raw_path : Pathname.new(raw_path)
|
81
109
|
config.file_ext_regexp.match? path.extname
|
82
110
|
end
|
83
111
|
|
84
|
-
# Extracts the
|
112
|
+
# Extracts the subdirectory and filename from a given path.
|
85
113
|
#
|
86
114
|
# @param entry [String] The file path.
|
87
|
-
# @return [Array
|
115
|
+
# @return [Array<Pathname, Pathname>] An array containing the subdirectory and filename.
|
88
116
|
def subdir_and_filename_for(entry)
|
89
117
|
Pathname.new(entry).split
|
90
118
|
end
|
91
119
|
|
92
|
-
# Constructs a project identifier
|
120
|
+
# Constructs a Lokalise project identifier that may include a branch.
|
93
121
|
#
|
94
|
-
#
|
122
|
+
# If a branch is specified, the project ID is formatted as `project_id:branch`.
|
123
|
+
#
|
124
|
+
# @return [String] The formatted project identifier.
|
95
125
|
def project_id_with_branch
|
96
126
|
config.branch.to_s.strip.empty? ? config.project_id.to_s : "#{config.project_id}:#{config.branch}"
|
97
127
|
end
|
98
128
|
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
|
103
|
-
|
104
|
-
#
|
129
|
+
# Executes a block with exponential backoff for handling API rate limits and temporary failures.
|
130
|
+
#
|
131
|
+
# Retries the operation for a defined number of attempts, doubling the wait time after each failure.
|
132
|
+
#
|
133
|
+
# @param max_retries [Integer] Maximum number of retries before giving up.
|
134
|
+
# @yield The operation to retry.
|
135
|
+
# @return [Object] The result of the block if successful.
|
105
136
|
def with_exp_backoff(max_retries)
|
106
137
|
return unless block_given?
|
107
138
|
|
@@ -4,14 +4,21 @@ require 'base64'
|
|
4
4
|
|
5
5
|
module LokaliseManager
|
6
6
|
module TaskDefinitions
|
7
|
-
#
|
7
|
+
# Handles exporting translation files from a local project to Lokalise.
|
8
8
|
class Exporter < Base
|
9
9
|
# Maximum number of concurrent uploads to avoid exceeding Lokalise API rate limits.
|
10
10
|
MAX_THREADS = 6
|
11
11
|
|
12
|
-
|
12
|
+
ProcessResult = Struct.new(:success, :process, :path, :error, keyword_init: true)
|
13
|
+
|
14
|
+
# Exports translation files to Lokalise in batches to optimize performance.
|
15
|
+
#
|
16
|
+
# - Validates configuration.
|
17
|
+
# - Gathers translation files from the project directory.
|
18
|
+
# - Uploads files to Lokalise in parallel, respecting API rate limits.
|
19
|
+
# - Handles errors and ensures failed uploads are reported.
|
13
20
|
#
|
14
|
-
# @return [Array] An array of process
|
21
|
+
# @return [Array] An array of process results for each uploaded file.
|
15
22
|
def export!
|
16
23
|
check_options_errors!
|
17
24
|
|
@@ -28,10 +35,10 @@ module LokaliseManager
|
|
28
35
|
|
29
36
|
private
|
30
37
|
|
31
|
-
#
|
38
|
+
# Uploads a group of files in parallel using threads.
|
32
39
|
#
|
33
|
-
# @param files_group [Array]
|
34
|
-
# @return [Array] Array of
|
40
|
+
# @param files_group [Array] List of file path pairs (full and relative).
|
41
|
+
# @return [Array] Array of results from the upload process.
|
35
42
|
def parallel_upload(files_group)
|
36
43
|
files_group.map do |file_data|
|
37
44
|
Thread.new { do_upload(*file_data) }
|
@@ -44,34 +51,34 @@ module LokaliseManager
|
|
44
51
|
def raise_on_fail(thread)
|
45
52
|
return if thread.success
|
46
53
|
|
47
|
-
raise thread.error
|
54
|
+
raise thread.error
|
48
55
|
end
|
49
56
|
|
50
|
-
#
|
57
|
+
# Uploads a single file to Lokalise.
|
58
|
+
#
|
59
|
+
# Uses exponential backoff to retry failed uploads.
|
51
60
|
#
|
52
|
-
# @param f_path [Pathname] Full path
|
53
|
-
# @param r_path [Pathname] Relative path
|
54
|
-
# @return [Struct]
|
61
|
+
# @param f_path [Pathname] Full file path.
|
62
|
+
# @param r_path [Pathname] Relative file path within the project.
|
63
|
+
# @return [Struct] Struct containing upload status, process details, and error (if any).
|
55
64
|
def do_upload(f_path, r_path)
|
56
|
-
proc_klass = Struct.new(:success, :process, :path, :error, keyword_init: true)
|
57
|
-
|
58
65
|
process = with_exp_backoff(config.max_retries_export) do
|
59
66
|
api_client.upload_file(project_id_with_branch, opts(f_path, r_path))
|
60
67
|
end
|
61
68
|
|
62
|
-
|
69
|
+
ProcessResult.new(success: true, process: process, path: f_path)
|
63
70
|
rescue StandardError => e
|
64
|
-
|
71
|
+
ProcessResult.new(success: false, path: f_path, error: e)
|
65
72
|
end
|
66
73
|
|
67
|
-
# Prints a
|
74
|
+
# Prints a message indicating that the export process is complete.
|
68
75
|
def print_completion_message
|
69
76
|
$stdout.puts 'Task complete!'
|
70
77
|
end
|
71
78
|
|
72
|
-
#
|
79
|
+
# Collects all translation files that match export criteria.
|
73
80
|
#
|
74
|
-
# @return [Array]
|
81
|
+
# @return [Array] List of [Pathname, Pathname] pairs (full and relative paths).
|
75
82
|
def all_files
|
76
83
|
loc_path = Pathname.new(config.locales_path)
|
77
84
|
|
@@ -84,11 +91,13 @@ module LokaliseManager
|
|
84
91
|
end
|
85
92
|
end
|
86
93
|
|
87
|
-
#
|
94
|
+
# Constructs upload options for a file.
|
95
|
+
#
|
96
|
+
# Reads and encodes the file content in Base64 before sending it to Lokalise.
|
88
97
|
#
|
89
|
-
# @param full_p [Pathname] Full path
|
98
|
+
# @param full_p [Pathname] Full file path.
|
90
99
|
# @param relative_p [Pathname] Relative path within the project.
|
91
|
-
# @return [Hash]
|
100
|
+
# @return [Hash] Upload options including encoded content, filename, and language.
|
92
101
|
def opts(full_p, relative_p)
|
93
102
|
content = File.read(full_p).strip
|
94
103
|
|
@@ -99,10 +108,14 @@ module LokaliseManager
|
|
99
108
|
}.merge(config.export_opts)
|
100
109
|
end
|
101
110
|
|
102
|
-
#
|
111
|
+
# Determines if a file meets the criteria for export.
|
112
|
+
#
|
113
|
+
# - Must be a valid file (not a directory).
|
114
|
+
# - Must match the allowed file extensions.
|
115
|
+
# - Must not be explicitly skipped by `skip_file_export`.
|
103
116
|
#
|
104
|
-
# @param full_path [Pathname] Full path
|
105
|
-
# @return [Boolean]
|
117
|
+
# @param full_path [Pathname] Full file path.
|
118
|
+
# @return [Boolean] `true` if the file should be uploaded, `false` otherwise.
|
106
119
|
def file_matches_criteria?(full_path)
|
107
120
|
full_path.file? && proper_ext?(full_path) &&
|
108
121
|
!config.skip_file_export.call(full_path)
|
@@ -6,13 +6,16 @@ require 'fileutils'
|
|
6
6
|
|
7
7
|
module LokaliseManager
|
8
8
|
module TaskDefinitions
|
9
|
-
#
|
10
|
-
# and importing them into the specified project directory.
|
9
|
+
# Handles downloading translation files from Lokalise and importing them into the project directory.
|
11
10
|
class Importer < Base
|
12
|
-
# Initiates the import process
|
13
|
-
# downloading files, and processing them. Outputs task completion status.
|
11
|
+
# Initiates the translation import process.
|
14
12
|
#
|
15
|
-
#
|
13
|
+
# - Validates configuration.
|
14
|
+
# - Ensures safe mode conditions are met.
|
15
|
+
# - Downloads translation files.
|
16
|
+
# - Extracts and processes the downloaded files.
|
17
|
+
#
|
18
|
+
# @return [Boolean] Returns `true` if the import completes successfully, `false` if cancelled.
|
16
19
|
def import!
|
17
20
|
check_options_errors!
|
18
21
|
|
@@ -21,7 +24,7 @@ module LokaliseManager
|
|
21
24
|
return false
|
22
25
|
end
|
23
26
|
|
24
|
-
open_and_process_zip
|
27
|
+
open_and_process_zip(download_bundle)
|
25
28
|
|
26
29
|
$stdout.print('Task complete!') unless config.silent_mode
|
27
30
|
true
|
@@ -29,47 +32,96 @@ module LokaliseManager
|
|
29
32
|
|
30
33
|
private
|
31
34
|
|
32
|
-
#
|
35
|
+
# Retrieves the download URL of the translation files.
|
36
|
+
#
|
37
|
+
# If `import_async` is enabled, initiates an asynchronous download process.
|
38
|
+
#
|
39
|
+
# @return [String] The URL of the downloaded translation bundle.
|
40
|
+
def download_bundle
|
41
|
+
return download_files.bundle_url unless config.import_async
|
42
|
+
|
43
|
+
process = download_files_async
|
44
|
+
process.details['download_url'] || process.details[:download_url]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Downloads translation files from Lokalise using a synchronous request.
|
48
|
+
#
|
49
|
+
# Handles retries and errors using exponential backoff.
|
33
50
|
#
|
34
|
-
# @return [Hash]
|
51
|
+
# @return [Hash] The response from Lokalise API containing download details.
|
35
52
|
def download_files
|
36
|
-
|
37
|
-
|
53
|
+
fetch_with_retry { api_client.download_files(project_id_with_branch, config.import_opts) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Initiates an asynchronous download request for translation files.
|
57
|
+
#
|
58
|
+
# Waits for the process to complete before proceeding.
|
59
|
+
#
|
60
|
+
# @return [QueuedProcess] The completed async download process object.
|
61
|
+
def download_files_async
|
62
|
+
process = fetch_with_retry { api_client.download_files_async(project_id_with_branch, config.import_opts) }
|
63
|
+
wait_for_async_download(process.process_id)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Waits for an asynchronous translation file download process to finish.
|
67
|
+
#
|
68
|
+
# Uses exponential backoff for polling the process status.
|
69
|
+
#
|
70
|
+
# @param process_id [String] The ID of the asynchronous process.
|
71
|
+
# @return [QueuedProcess] The process object when completed successfully.
|
72
|
+
# @raise [LokaliseManager::Error] If the process fails or takes too long.
|
73
|
+
def wait_for_async_download(process_id)
|
74
|
+
(config.max_retries_import + 1).times do |i|
|
75
|
+
sleep 2**i
|
76
|
+
process = reload_process(process_id)
|
77
|
+
|
78
|
+
case process.status
|
79
|
+
when 'failed' then raise LokaliseManager::Error, 'Asynchronous download process failed'
|
80
|
+
when 'finished' then return process
|
81
|
+
end
|
38
82
|
end
|
39
|
-
|
40
|
-
raise
|
83
|
+
|
84
|
+
raise LokaliseManager::Error, "Asynchronous download process timed out after #{config.max_retries_import} tries"
|
85
|
+
end
|
86
|
+
|
87
|
+
# Retrieves the latest status of an asynchronous download process.
|
88
|
+
#
|
89
|
+
# @param process_id [String] The process ID to check.
|
90
|
+
# @return [QueuedProcess] The process object with updated status.
|
91
|
+
def reload_process(process_id)
|
92
|
+
api_client.queued_process project_id_with_branch, process_id
|
41
93
|
end
|
42
94
|
|
43
|
-
#
|
95
|
+
# Extracts and processes files from a ZIP archive.
|
44
96
|
#
|
45
97
|
# @param path [String] The URL or local path to the ZIP archive.
|
46
98
|
def open_and_process_zip(path)
|
47
99
|
Zip::File.open_buffer(open_file_or_remote(path)) do |zip|
|
48
100
|
zip.each { |entry| process_entry(entry) if proper_ext?(entry.name) }
|
49
101
|
end
|
50
|
-
rescue StandardError => e
|
51
|
-
raise e.class, "Error processing ZIP file: #{e.message}"
|
52
102
|
end
|
53
103
|
|
54
|
-
#
|
55
|
-
#
|
104
|
+
# Extracts data from a ZIP entry and writes it to the correct directory.
|
105
|
+
#
|
106
|
+
# - Extracts file content.
|
107
|
+
# - Determines the appropriate subdirectory and filename.
|
108
|
+
# - Writes the processed file.
|
56
109
|
#
|
57
110
|
# @param zip_entry [Zip::Entry] The ZIP entry to process.
|
58
111
|
def process_entry(zip_entry)
|
59
112
|
data = data_from(zip_entry)
|
60
|
-
|
61
|
-
|
62
|
-
FileUtils.mkdir_p full_path
|
113
|
+
full_path = File.join(config.locales_path, *subdir_and_filename_for(zip_entry.name))
|
114
|
+
FileUtils.mkdir_p File.dirname(full_path)
|
63
115
|
|
64
|
-
File.write(
|
65
|
-
rescue StandardError => e
|
66
|
-
raise e.class, "Error processing entry #{zip_entry.name}: #{e.message}"
|
116
|
+
File.write(full_path, config.translations_converter.call(data), mode: 'w+:UTF-8')
|
67
117
|
end
|
68
118
|
|
69
|
-
#
|
70
|
-
#
|
119
|
+
# Checks whether the import should proceed under safe mode constraints.
|
120
|
+
#
|
121
|
+
# If `import_safe_mode` is enabled, the target directory must be empty,
|
122
|
+
# or the user must explicitly confirm continuation.
|
71
123
|
#
|
72
|
-
# @return [Boolean]
|
124
|
+
# @return [Boolean] `true` if the import should proceed, `false` otherwise.
|
73
125
|
def proceed_when_safe_mode?
|
74
126
|
return true unless config.import_safe_mode && !Dir.empty?(config.locales_path.to_s)
|
75
127
|
|
@@ -78,21 +130,30 @@ module LokaliseManager
|
|
78
130
|
$stdin.gets.strip.upcase == 'Y'
|
79
131
|
end
|
80
132
|
|
81
|
-
# Opens a local file or a remote
|
133
|
+
# Opens a local file or downloads a remote file.
|
82
134
|
#
|
83
|
-
# @param path [String] The
|
84
|
-
# @return [IO]
|
135
|
+
# @param path [String] The file path (local or URL).
|
136
|
+
# @return [IO] An IO object for reading the file.
|
85
137
|
def open_file_or_remote(path)
|
86
138
|
uri = URI.parse(path)
|
87
139
|
uri.scheme&.start_with?('http') ? uri.open : File.open(path)
|
88
140
|
end
|
89
141
|
|
90
|
-
#
|
142
|
+
# Reads and processes data from a ZIP file entry.
|
91
143
|
#
|
92
|
-
# @param zip_entry [Zip::Entry] The ZIP entry
|
144
|
+
# @param zip_entry [Zip::Entry] The ZIP entry containing translation data.
|
145
|
+
# @return [String] The extracted file content.
|
93
146
|
def data_from(zip_entry)
|
94
147
|
config.translations_loader.call zip_entry.get_input_stream.read
|
95
148
|
end
|
149
|
+
|
150
|
+
# Executes a block with exponential backoff for retrying failed operations.
|
151
|
+
#
|
152
|
+
# @yield The operation to retry.
|
153
|
+
# @return [Object] The result of the successful operation.
|
154
|
+
def fetch_with_retry(&block)
|
155
|
+
with_exp_backoff(config.max_retries_import, &block)
|
156
|
+
end
|
96
157
|
end
|
97
158
|
end
|
98
159
|
end
|
data/lib/lokalise_manager.rb
CHANGED
@@ -6,33 +6,52 @@ require 'yaml'
|
|
6
6
|
loader = Zeitwerk::Loader.for_gem
|
7
7
|
loader.setup
|
8
8
|
|
9
|
-
# The LokaliseManager module provides
|
10
|
-
# files
|
11
|
-
# by providing a straightforward interface to instantiate importers and exporters.
|
9
|
+
# The `LokaliseManager` module provides a high-level interface for importing and exporting
|
10
|
+
# translation files between a Ruby project and the Lokalise TMS.
|
12
11
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
12
|
+
# This module simplifies interactions with the Lokalise API by exposing two factory methods:
|
13
|
+
# - `importer` → Instantiates an importer for fetching translations from Lokalise.
|
14
|
+
# - `exporter` → Instantiates an exporter for uploading translations to Lokalise.
|
15
|
+
#
|
16
|
+
# ## Example Usage:
|
17
|
+
#
|
18
|
+
# ```ruby
|
19
|
+
# importer = LokaliseManager.importer(api_token: '1234abc', project_id: '123.abc')
|
20
|
+
# exporter = LokaliseManager.exporter(api_token: '1234abc', project_id: '123.abc')
|
21
|
+
#
|
22
|
+
# importer.import!
|
23
|
+
# exporter.export!
|
24
|
+
# ```
|
18
25
|
#
|
19
26
|
module LokaliseManager
|
20
27
|
class << self
|
21
|
-
#
|
28
|
+
# Instantiates an importer for retrieving translation files from Lokalise.
|
22
29
|
#
|
23
|
-
# @param custom_opts [Hash] Custom options for the importer (e.g.,
|
24
|
-
# @param global_config [Object]
|
25
|
-
# @return [LokaliseManager::TaskDefinitions::Importer] An instance
|
30
|
+
# @param custom_opts [Hash] Custom options for the importer (e.g., `api_token`, `project_id`).
|
31
|
+
# @param global_config [Object] The global configuration object (defaults to `LokaliseManager::GlobalConfig`).
|
32
|
+
# @return [LokaliseManager::TaskDefinitions::Importer] An `Importer` instance for downloading translations.
|
33
|
+
#
|
34
|
+
# ## Example:
|
35
|
+
# ```ruby
|
36
|
+
# importer = LokaliseManager.importer(api_token: 'xyz', project_id: '456.abc')
|
37
|
+
# importer.import!
|
38
|
+
# ```
|
26
39
|
#
|
27
40
|
def importer(custom_opts = {}, global_config = LokaliseManager::GlobalConfig)
|
28
41
|
LokaliseManager::TaskDefinitions::Importer.new custom_opts, global_config
|
29
42
|
end
|
30
43
|
|
31
|
-
#
|
44
|
+
# Instantiates an exporter for uploading translation files to Lokalise.
|
45
|
+
#
|
46
|
+
# @param custom_opts [Hash] Custom options for the exporter (e.g., `api_token`, `project_id`).
|
47
|
+
# @param global_config [Object] The global configuration object (defaults to `LokaliseManager::GlobalConfig`).
|
48
|
+
# @return [LokaliseManager::TaskDefinitions::Exporter] An `Exporter` instance for uploading translations.
|
32
49
|
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
50
|
+
# ## Example:
|
51
|
+
# ```ruby
|
52
|
+
# exporter = LokaliseManager.exporter(api_token: 'xyz', project_id: '456.abc')
|
53
|
+
# exporter.export!
|
54
|
+
# ```
|
36
55
|
#
|
37
56
|
def exporter(custom_opts = {}, global_config = LokaliseManager::GlobalConfig)
|
38
57
|
LokaliseManager::TaskDefinitions::Exporter.new custom_opts, global_config
|
data/lokalise_manager.gemspec
CHANGED
@@ -23,6 +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 'base64', '~> 0.2.0'
|
26
27
|
spec.add_dependency 'ruby-lokalise-api', '~> 9.3'
|
27
28
|
spec.add_dependency 'rubyzip', '~> 2.3'
|
28
29
|
spec.add_dependency 'zeitwerk', '~> 2.4'
|
metadata
CHANGED
@@ -1,15 +1,28 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lokalise_manager
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.
|
4
|
+
version: 6.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ilya Krukowski
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-02-20 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: base64
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 0.2.0
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.2.0
|
13
26
|
- !ruby/object:Gem::Dependency
|
14
27
|
name: ruby-lokalise-api
|
15
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -223,7 +236,6 @@ licenses:
|
|
223
236
|
- MIT
|
224
237
|
metadata:
|
225
238
|
rubygems_mfa_required: 'true'
|
226
|
-
post_install_message:
|
227
239
|
rdoc_options: []
|
228
240
|
require_paths:
|
229
241
|
- lib
|
@@ -238,8 +250,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
238
250
|
- !ruby/object:Gem::Version
|
239
251
|
version: '0'
|
240
252
|
requirements: []
|
241
|
-
rubygems_version: 3.5
|
242
|
-
signing_key:
|
253
|
+
rubygems_version: 3.6.5
|
243
254
|
specification_version: 4
|
244
255
|
summary: Lokalise integration for Ruby
|
245
256
|
test_files: []
|