lokalise_manager 5.1.0 → 5.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +10 -5
- data/lib/lokalise_manager/error.rb +4 -2
- data/lib/lokalise_manager/global_config.rb +21 -22
- data/lib/lokalise_manager/task_definitions/base.rb +49 -41
- data/lib/lokalise_manager/task_definitions/exporter.rb +53 -44
- data/lib/lokalise_manager/task_definitions/importer.rb +33 -43
- data/lib/lokalise_manager/version.rb +1 -1
- data/lib/lokalise_manager.rb +16 -16
- 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: 109786a5056b1b17efbc2214df611dcbef3b774169f04b71a1d5ee3f531bc514
|
4
|
+
data.tar.gz: 250e0e838b6d3166e3e9fa5135e6417af1bf5b853719712ddecbef181015ee48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1206ab988a7545b40838b3e053d2e5af54ef3b2bd8bf63a4d48e8d4544d8b005c4e74f6b4549010f8263b5a1c469abbe9a09b9371a43627c0ecfdfc4b76390e4
|
7
|
+
data.tar.gz: 90887ab6f6b8b5d5d05688ca20a8e0e0d6afe195a4b3edee1a6021fc5f481f830fa5be856b60fe95c0ddbe07eb6deaecfb5ff29744187b601cc2b4506dfaa4e8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 5.1.2 (01-Nov-2024)
|
4
|
+
|
5
|
+
* Update dependencies
|
6
|
+
|
7
|
+
## 5.1.1 (10-May-2024)
|
8
|
+
|
9
|
+
* Update documentation, minor code fixes
|
10
|
+
|
3
11
|
## 5.1.0 (09-Feb-2024)
|
4
12
|
|
5
13
|
* Handle rare case when the server returns HTML instead of JSON which happens when too many requests are sent
|
data/README.md
CHANGED
@@ -6,17 +6,22 @@
|
|
6
6
|
[](https://codeclimate.com/github/bodrovis/lokalise_manager/maintainability)
|
7
7
|

|
8
8
|
|
9
|
-
|
9
|
+
The LokaliseManager gem provides seamless integration with [Lokalise](http://lokalise.com), enabling easy exchange of translation files between your Ruby project and the Lokalise translation management system (TMS). It leverages the [ruby-lokalise-api](https://lokalise.github.io/ruby-lokalise-api) to send and manage APIv2 requests.
|
10
10
|
|
11
|
-
|
11
|
+
For integration directly with Rails applications, refer to [lokalise_rails](https://github.com/bodrovis/lokalise_rails), which offers a suite of Rake tasks specifically designed for importing and exporting translation files.
|
12
12
|
|
13
|
-
## Getting
|
13
|
+
## Getting Started
|
14
14
|
|
15
15
|
### Requirements
|
16
16
|
|
17
|
-
|
17
|
+
- **Ruby version**: Ruby 3.0 or higher is required.
|
18
|
+
- **Lokalise account**: You must have an active [Lokalise account](https://app.lokalise.com/signup).
|
19
|
+
- **Project setup**: Create a [translation project](https://docs.lokalise.com/en/articles/1400460-projects) within your Lokalise account.
|
20
|
+
- **API token**: Obtain a read/write [API token](https://docs.lokalise.com/en/articles/1929556-api-tokens) from your Lokalise profile.
|
18
21
|
|
19
|
-
|
22
|
+
### Optional
|
23
|
+
|
24
|
+
- **OAuth 2 token**: If you prefer using an OAuth 2 token instead of a standard API token, set the `:use_oauth2_token` option to `true` in your configuration settings.
|
20
25
|
|
21
26
|
### Installation
|
22
27
|
|
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module LokaliseManager
|
4
|
-
#
|
4
|
+
# The Error class provides a custom exception type for the LokaliseManager,
|
5
|
+
# allowing the library to raise specific errors that can be easily identified
|
6
|
+
# and handled separately from other StandardError exceptions in Ruby.
|
5
7
|
class Error < StandardError
|
6
8
|
# Initializes a new Error object
|
7
9
|
def initialize(message = '')
|
8
|
-
super
|
10
|
+
super
|
9
11
|
end
|
10
12
|
end
|
11
13
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module LokaliseManager
|
4
|
-
#
|
4
|
+
# GlobalConfig provides a central place to manage configuration settings for LokaliseManager.
|
5
|
+
# It allows setting various operational parameters such as API tokens, paths, and behavior modifiers.
|
5
6
|
class GlobalConfig
|
6
7
|
class << self
|
7
8
|
attr_accessor :api_token, :project_id
|
@@ -11,58 +12,57 @@ module LokaliseManager
|
|
11
12
|
:max_retries_export, :max_retries_import, :use_oauth2_token, :silent_mode,
|
12
13
|
:raise_on_export_fail
|
13
14
|
|
14
|
-
#
|
15
|
+
# Yield self to block for configuration
|
15
16
|
def config
|
16
17
|
yield self
|
17
18
|
end
|
18
19
|
|
19
|
-
#
|
20
|
+
# Return whether to raise on export failure
|
20
21
|
def raise_on_export_fail
|
21
|
-
@raise_on_export_fail
|
22
|
+
@raise_on_export_fail.nil? ? true : @raise_on_export_fail
|
22
23
|
end
|
23
24
|
|
24
|
-
#
|
25
|
+
# Return whether debugging information is suppressed
|
25
26
|
def silent_mode
|
26
27
|
@silent_mode || false
|
27
28
|
end
|
28
29
|
|
29
|
-
#
|
30
|
-
# rather than via Lokalise profile
|
30
|
+
# Return whether to use OAuth2 tokens instead of regular API tokens
|
31
31
|
def use_oauth2_token
|
32
32
|
@use_oauth2_token || false
|
33
33
|
end
|
34
34
|
|
35
|
-
#
|
35
|
+
# Return the path to locales
|
36
36
|
def locales_path
|
37
37
|
@locales_path || "#{Dir.getwd}/locales"
|
38
38
|
end
|
39
39
|
|
40
|
-
#
|
40
|
+
# Return the project branch
|
41
41
|
def branch
|
42
42
|
@branch || ''
|
43
43
|
end
|
44
44
|
|
45
|
-
#
|
45
|
+
# Return API request timeouts
|
46
46
|
def timeouts
|
47
47
|
@timeouts || {}
|
48
48
|
end
|
49
49
|
|
50
|
-
#
|
50
|
+
# Return the max retries for export
|
51
51
|
def max_retries_export
|
52
52
|
@max_retries_export || 5
|
53
53
|
end
|
54
54
|
|
55
|
-
#
|
55
|
+
# Return the max retries for import
|
56
56
|
def max_retries_import
|
57
57
|
@max_retries_import || 5
|
58
58
|
end
|
59
59
|
|
60
|
-
#
|
60
|
+
# Return the regex for file extensions
|
61
61
|
def file_ext_regexp
|
62
62
|
@file_ext_regexp || /\.ya?ml\z/i
|
63
63
|
end
|
64
64
|
|
65
|
-
#
|
65
|
+
# Return import options with defaults
|
66
66
|
def import_opts
|
67
67
|
@import_opts || {
|
68
68
|
format: 'ruby_yaml',
|
@@ -74,33 +74,32 @@ module LokaliseManager
|
|
74
74
|
}
|
75
75
|
end
|
76
76
|
|
77
|
-
#
|
77
|
+
# Return export options
|
78
78
|
def export_opts
|
79
79
|
@export_opts || {}
|
80
80
|
end
|
81
81
|
|
82
|
-
#
|
82
|
+
# Return whether import should check if target is empty
|
83
83
|
def import_safe_mode
|
84
84
|
@import_safe_mode.nil? ? false : @import_safe_mode
|
85
85
|
end
|
86
86
|
|
87
|
-
#
|
87
|
+
# Return whether to skip file export based on a lambda condition
|
88
88
|
def skip_file_export
|
89
89
|
@skip_file_export || ->(_) { false }
|
90
90
|
end
|
91
91
|
|
92
|
+
# Load translations from raw data
|
92
93
|
def translations_loader
|
93
|
-
@translations_loader || ->(raw_data) { YAML.safe_load
|
94
|
+
@translations_loader || ->(raw_data) { YAML.safe_load(raw_data) }
|
94
95
|
end
|
95
96
|
|
96
|
-
#
|
97
|
+
# Convert raw translation data to YAML format
|
97
98
|
def translations_converter
|
98
99
|
@translations_converter || ->(raw_data) { YAML.dump(raw_data).gsub('\\\\n', '\n') }
|
99
100
|
end
|
100
101
|
|
101
|
-
#
|
102
|
-
# The lambda expects to accept the raw contents of the translation file
|
103
|
-
# and the full path to the file (instance of the `Pathname` class)
|
102
|
+
# Infer language ISO code from translation file
|
104
103
|
def lang_iso_inferer
|
105
104
|
@lang_iso_inferer || ->(data, _path) { YAML.safe_load(data)&.keys&.first }
|
106
105
|
end
|
@@ -5,41 +5,27 @@ require 'pathname'
|
|
5
5
|
|
6
6
|
module LokaliseManager
|
7
7
|
module TaskDefinitions
|
8
|
-
# Base class for LokaliseManager task definitions
|
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.
|
9
10
|
class Base
|
10
11
|
using LokaliseManager::Utils::HashUtils
|
11
12
|
|
12
13
|
attr_accessor :config
|
13
14
|
|
14
|
-
#
|
15
|
-
# with the global config (custom config take precendence)
|
15
|
+
# Initializes a new task object by merging custom and global configurations.
|
16
16
|
#
|
17
|
-
# @param custom_opts [Hash]
|
18
|
-
# @param global_config [Object]
|
17
|
+
# @param custom_opts [Hash] Custom configurations for specific tasks.
|
18
|
+
# @param global_config [Object] Reference to the global configuration.
|
19
19
|
def initialize(custom_opts = {}, global_config = LokaliseManager::GlobalConfig)
|
20
|
-
|
21
|
-
|
22
|
-
.filter { |m| m.to_s.end_with?('=') }
|
23
|
-
.each_with_object({}) do |method, opts|
|
24
|
-
reader = method.to_s.delete_suffix('=')
|
25
|
-
opts[reader.to_sym] = global_config.send(reader)
|
26
|
-
end
|
27
|
-
|
28
|
-
all_opts = primary_opts.deep_merge(custom_opts)
|
29
|
-
|
30
|
-
config_klass = Struct.new(*all_opts.keys, keyword_init: true)
|
31
|
-
|
32
|
-
@config = config_klass.new all_opts
|
20
|
+
merged_opts = merge_configs(global_config, custom_opts)
|
21
|
+
@config = build_config_class(merged_opts)
|
33
22
|
end
|
34
23
|
|
35
|
-
#
|
24
|
+
# Retrieves or creates a Lokalise API client based on configuration.
|
36
25
|
#
|
37
|
-
# @return [RubyLokaliseApi::Client]
|
26
|
+
# @return [RubyLokaliseApi::Client] Lokalise API client.
|
38
27
|
def api_client
|
39
|
-
|
40
|
-
client_method = config.use_oauth2_token ? :oauth2_client : :client
|
41
|
-
|
42
|
-
@api_client = ::RubyLokaliseApi.send(client_method, *client_opts)
|
28
|
+
@api_client ||= create_api_client
|
43
29
|
end
|
44
30
|
|
45
31
|
# Resets API client
|
@@ -51,41 +37,63 @@ module LokaliseManager
|
|
51
37
|
|
52
38
|
private
|
53
39
|
|
54
|
-
#
|
55
|
-
|
56
|
-
|
40
|
+
# Creates a Lokalise API client based on configuration.
|
41
|
+
def create_api_client
|
42
|
+
client_opts = [config.api_token, config.timeouts]
|
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
|
+
|
67
|
+
# Checks and validates task options, raising errors if configurations are missing.
|
57
68
|
def check_options_errors!
|
58
69
|
errors = []
|
59
70
|
errors << 'Project ID is not set!' if config.project_id.nil? || config.project_id.empty?
|
60
71
|
errors << 'Lokalise API token is not set!' if config.api_token.nil? || config.api_token.empty?
|
61
|
-
|
62
|
-
raise(LokaliseManager::Error, errors.join(' ')) if errors.any?
|
72
|
+
raise LokaliseManager::Error, errors.join(' ') unless errors.empty?
|
63
73
|
end
|
64
74
|
|
65
|
-
# Checks
|
75
|
+
# Checks if the file has the correct extension based on the configuration.
|
66
76
|
#
|
67
|
-
# @
|
68
|
-
# @
|
77
|
+
# @param raw_path [String, Pathname] Path to check.
|
78
|
+
# @return [Boolean] True if the extension matches, false otherwise.
|
69
79
|
def proper_ext?(raw_path)
|
70
80
|
path = raw_path.is_a?(Pathname) ? raw_path : Pathname.new(raw_path)
|
71
81
|
config.file_ext_regexp.match? path.extname
|
72
82
|
end
|
73
83
|
|
74
|
-
#
|
84
|
+
# Extracts the directory and filename from a given path.
|
75
85
|
#
|
76
|
-
# @
|
77
|
-
# @
|
86
|
+
# @param entry [String] The file path.
|
87
|
+
# @return [Array] Contains [Pathname, Pathname] representing the directory and filename.
|
78
88
|
def subdir_and_filename_for(entry)
|
79
89
|
Pathname.new(entry).split
|
80
90
|
end
|
81
91
|
|
82
|
-
#
|
92
|
+
# Constructs a project identifier string that may include a branch.
|
83
93
|
#
|
84
|
-
# @return [String]
|
94
|
+
# @return [String] Project identifier potentially including the branch.
|
85
95
|
def project_id_with_branch
|
86
|
-
|
87
|
-
|
88
|
-
"#{config.project_id}:#{config.branch}"
|
96
|
+
config.branch.to_s.strip.empty? ? config.project_id.to_s : "#{config.project_id}:#{config.branch}"
|
89
97
|
end
|
90
98
|
|
91
99
|
# In rare cases the server might return HTML instead of JSON.
|
@@ -93,7 +101,7 @@ module LokaliseManager
|
|
93
101
|
# Until this is fixed, we revert to this quick'n'dirty solution.
|
94
102
|
EXCEPTIONS = [JSON::ParserError, RubyLokaliseApi::Error::TooManyRequests].freeze
|
95
103
|
|
96
|
-
#
|
104
|
+
# Handles retries with exponential backoff for specific exceptions.
|
97
105
|
def with_exp_backoff(max_retries)
|
98
106
|
return unless block_given?
|
99
107
|
|
@@ -4,96 +4,105 @@ require 'base64'
|
|
4
4
|
|
5
5
|
module LokaliseManager
|
6
6
|
module TaskDefinitions
|
7
|
-
#
|
7
|
+
# Class to handle 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
|
+
# Exports translation files to Lokalise and handles any necessary concurrency and error checking.
|
13
13
|
#
|
14
|
-
# @return [Array]
|
14
|
+
# @return [Array] An array of process statuses for each file uploaded.
|
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
|
|
28
|
-
|
24
|
+
print_completion_message unless config.silent_mode
|
29
25
|
|
30
26
|
queued_processes
|
31
27
|
end
|
32
28
|
|
33
29
|
private
|
34
30
|
|
31
|
+
# Handles parallel uploads of a group of files, utilizing threading.
|
32
|
+
#
|
33
|
+
# @param files_group [Array] Group of files to be uploaded.
|
34
|
+
# @return [Array] Array of threads handling the file uploads.
|
35
35
|
def parallel_upload(files_group)
|
36
36
|
files_group.map do |file_data|
|
37
|
-
do_upload(*file_data)
|
37
|
+
Thread.new { do_upload(*file_data) }
|
38
38
|
end.map(&:value)
|
39
39
|
end
|
40
40
|
|
41
|
+
# Raises an error if a file upload thread failed.
|
42
|
+
#
|
43
|
+
# @param thread [Struct] The result of the file upload thread.
|
41
44
|
def raise_on_fail(thread)
|
42
45
|
return if thread.success
|
43
46
|
|
44
|
-
raise
|
47
|
+
raise thread.error.class, "Error while trying to upload #{thread.path}: #{thread.error.message}"
|
45
48
|
end
|
46
49
|
|
47
|
-
# Performs the actual file
|
48
|
-
#
|
50
|
+
# Performs the actual upload of a file to Lokalise.
|
51
|
+
#
|
52
|
+
# @param f_path [Pathname] Full path to the file.
|
53
|
+
# @param r_path [Pathname] Relative path of the file within the project.
|
54
|
+
# @return [Struct] A struct with the success status, process details, and any error information.
|
49
55
|
def do_upload(f_path, r_path)
|
50
56
|
proc_klass = Struct.new(:success, :process, :path, :error, keyword_init: true)
|
51
57
|
|
52
|
-
|
53
|
-
|
54
|
-
api_client.upload_file project_id_with_branch, opts(f_path, r_path)
|
55
|
-
end
|
56
|
-
proc_klass.new success: true, process: process, path: f_path
|
57
|
-
rescue StandardError => e
|
58
|
-
proc_klass.new success: false, path: f_path, error: e
|
58
|
+
process = with_exp_backoff(config.max_retries_export) do
|
59
|
+
api_client.upload_file(project_id_with_branch, opts(f_path, r_path))
|
59
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)
|
60
65
|
end
|
61
66
|
|
62
|
-
#
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
full_path = Pathname.new f
|
67
|
+
# Prints a completion message to standard output.
|
68
|
+
def print_completion_message
|
69
|
+
$stdout.puts 'Task complete!'
|
70
|
+
end
|
67
71
|
|
68
|
-
|
72
|
+
# Retrieves all translation files from the specified directory.
|
73
|
+
#
|
74
|
+
# @return [Array] Array of [Pathname, Pathname] pairs representing full and relative paths.
|
75
|
+
def all_files
|
76
|
+
loc_path = Pathname.new(config.locales_path)
|
69
77
|
|
70
|
-
|
78
|
+
Dir["#{loc_path}/**/*"].filter_map do |file|
|
79
|
+
full_path = Pathname.new(file)
|
80
|
+
next unless file_matches_criteria?(full_path)
|
71
81
|
|
82
|
+
relative_path = full_path.relative_path_from(loc_path)
|
72
83
|
[full_path, relative_path]
|
73
84
|
end
|
74
85
|
end
|
75
86
|
|
76
|
-
# Generates
|
87
|
+
# Generates options for file upload to Lokalise.
|
77
88
|
#
|
78
|
-
# @
|
79
|
-
# @param
|
80
|
-
# @
|
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.
|
81
92
|
def opts(full_p, relative_p)
|
82
|
-
content = File.read
|
93
|
+
content = File.read(full_p).strip
|
83
94
|
|
84
|
-
|
85
|
-
data: Base64.strict_encode64(content
|
86
|
-
filename: relative_p,
|
95
|
+
{
|
96
|
+
data: Base64.strict_encode64(content),
|
97
|
+
filename: relative_p.to_s,
|
87
98
|
lang_iso: config.lang_iso_inferer.call(content, full_p)
|
88
|
-
}
|
89
|
-
|
90
|
-
initial_opts.merge config.export_opts
|
99
|
+
}.merge(config.export_opts)
|
91
100
|
end
|
92
101
|
|
93
|
-
# Checks whether the specified file
|
102
|
+
# Checks whether the specified file meets the criteria for upload.
|
94
103
|
#
|
95
|
-
# @
|
96
|
-
# @
|
104
|
+
# @param full_path [Pathname] Full path to the file.
|
105
|
+
# @return [Boolean] True if the file matches criteria, false otherwise.
|
97
106
|
def file_matches_criteria?(full_path)
|
98
107
|
full_path.file? && proper_ext?(full_path) &&
|
99
108
|
!config.skip_file_export.call(full_path)
|
@@ -6,11 +6,13 @@ require 'fileutils'
|
|
6
6
|
|
7
7
|
module LokaliseManager
|
8
8
|
module TaskDefinitions
|
9
|
-
# Importer class
|
9
|
+
# The Importer class handles downloading translation files from Lokalise
|
10
|
+
# and importing them into the specified project directory.
|
10
11
|
class Importer < Base
|
11
|
-
#
|
12
|
+
# Initiates the import process by checking configuration, ensuring safe mode conditions,
|
13
|
+
# downloading files, and processing them. Outputs task completion status.
|
12
14
|
#
|
13
|
-
# @return [Boolean]
|
15
|
+
# @return [Boolean] Returns true if the import completes successfully, false if cancelled.
|
14
16
|
def import!
|
15
17
|
check_options_errors!
|
16
18
|
|
@@ -27,10 +29,9 @@ module LokaliseManager
|
|
27
29
|
|
28
30
|
private
|
29
31
|
|
30
|
-
# Downloads files from Lokalise using
|
31
|
-
# Utilizes exponential backoff if "too many requests" error is received
|
32
|
+
# Downloads translation files from Lokalise, handling retries and errors using exponential backoff.
|
32
33
|
#
|
33
|
-
# @return [Hash]
|
34
|
+
# @return [Hash] Returns the response from Lokalise API containing download details.
|
34
35
|
def download_files
|
35
36
|
with_exp_backoff(config.max_retries_import) do
|
36
37
|
api_client.download_files project_id_with_branch, config.import_opts
|
@@ -39,67 +40,56 @@ module LokaliseManager
|
|
39
40
|
raise e.class, "There was an error when trying to download files: #{e.message}"
|
40
41
|
end
|
41
42
|
|
42
|
-
# Opens ZIP archive
|
43
|
+
# Opens a ZIP archive from a given path and processes each entry if it matches the required file extension.
|
43
44
|
#
|
44
|
-
# @param path [String]
|
45
|
+
# @param path [String] The URL or local path to the ZIP archive.
|
45
46
|
def open_and_process_zip(path)
|
46
47
|
Zip::File.open_buffer(open_file_or_remote(path)) do |zip|
|
47
|
-
|
48
|
+
zip.each { |entry| process_entry(entry) if proper_ext?(entry.name) }
|
48
49
|
end
|
49
50
|
rescue StandardError => e
|
50
|
-
raise
|
51
|
+
raise e.class, "Error processing ZIP file: #{e.message}"
|
51
52
|
end
|
52
53
|
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# Processes ZIP entry by reading its contents and creating the corresponding translation file
|
65
|
-
def process!(zip_entry)
|
66
|
-
data = data_from zip_entry
|
67
|
-
subdir, filename = subdir_and_filename_for zip_entry.name
|
68
|
-
full_path = "#{config.locales_path}/#{subdir}"
|
54
|
+
# Processes a single ZIP entry by extracting data, determining the correct directory structure,
|
55
|
+
# and writing the data to the appropriate file.
|
56
|
+
#
|
57
|
+
# @param zip_entry [Zip::Entry] The ZIP entry to process.
|
58
|
+
def process_entry(zip_entry)
|
59
|
+
data = data_from(zip_entry)
|
60
|
+
subdir, filename = subdir_and_filename_for(zip_entry.name)
|
61
|
+
full_path = File.join(config.locales_path, subdir)
|
69
62
|
FileUtils.mkdir_p full_path
|
70
63
|
|
71
|
-
File.
|
72
|
-
f.write config.translations_converter.call(data)
|
73
|
-
end
|
64
|
+
File.write(File.join(full_path, filename), config.translations_converter.call(data), mode: 'w+:UTF-8')
|
74
65
|
rescue StandardError => e
|
75
|
-
raise e.class, "Error
|
66
|
+
raise e.class, "Error processing entry #{zip_entry.name}: #{e.message}"
|
76
67
|
end
|
77
68
|
|
78
|
-
#
|
69
|
+
# Determines if the import should proceed based on the safe mode setting and the content of the target directory.
|
70
|
+
# In safe mode, the directory must be empty, or the user must confirm continuation.
|
79
71
|
#
|
80
|
-
# @return [Boolean]
|
72
|
+
# @return [Boolean] Returns true if the import should proceed, false otherwise.
|
81
73
|
def proceed_when_safe_mode?
|
82
74
|
return true unless config.import_safe_mode && !Dir.empty?(config.locales_path.to_s)
|
83
75
|
|
84
76
|
$stdout.puts "The target directory #{config.locales_path} is not empty!"
|
85
77
|
$stdout.print 'Enter Y to continue: '
|
86
|
-
|
87
|
-
answer.to_s.strip == 'Y'
|
78
|
+
$stdin.gets.strip.upcase == 'Y'
|
88
79
|
end
|
89
80
|
|
90
|
-
# Opens a local file or a remote URL using the provided
|
81
|
+
# Opens a local file or a remote URL using the provided path, safely handling different path schemes.
|
91
82
|
#
|
92
|
-
# @
|
83
|
+
# @param path [String] The path to the file, either a local path or a URL.
|
84
|
+
# @return [IO] Returns an IO object for the file.
|
93
85
|
def open_file_or_remote(path)
|
94
|
-
|
95
|
-
|
96
|
-
if parsed_path&.scheme&.include?('http')
|
97
|
-
parsed_path.open
|
98
|
-
else
|
99
|
-
File.open path
|
100
|
-
end
|
86
|
+
uri = URI.parse(path)
|
87
|
+
uri.scheme&.start_with?('http') ? uri.open : File.open(path)
|
101
88
|
end
|
102
89
|
|
90
|
+
# Loads translations from the ZIP file.
|
91
|
+
#
|
92
|
+
# @param zip_entry [Zip::Entry] The ZIP entry to process.
|
103
93
|
def data_from(zip_entry)
|
104
94
|
config.translations_loader.call zip_entry.get_input_stream.read
|
105
95
|
end
|
data/lib/lokalise_manager.rb
CHANGED
@@ -6,34 +6,34 @@ require 'yaml'
|
|
6
6
|
loader = Zeitwerk::Loader.for_gem
|
7
7
|
loader.setup
|
8
8
|
|
9
|
-
# LokaliseManager
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# exporter = LokaliseManager.exporter api_token: '1234abc', project_id: '123.abc'
|
13
|
-
#
|
14
|
-
# Use the instantiated objects to import or export your translation files:
|
9
|
+
# The LokaliseManager module provides functionalities to import and export translation
|
10
|
+
# files to and from the Lokalise TMS. It simplifies interactions with the Lokalise API
|
11
|
+
# by providing a straightforward interface to instantiate importers and exporters.
|
15
12
|
#
|
13
|
+
# Example:
|
14
|
+
# importer = LokaliseManager.importer(api_token: '1234abc', project_id: '123.abc')
|
15
|
+
# exporter = LokaliseManager.exporter(api_token: '1234abc', project_id: '123.abc')
|
16
16
|
# importer.import!
|
17
17
|
# exporter.export!
|
18
18
|
#
|
19
19
|
module LokaliseManager
|
20
20
|
class << self
|
21
|
-
#
|
22
|
-
#
|
21
|
+
# Creates an importer object for downloading translation files from Lokalise.
|
22
|
+
#
|
23
|
+
# @param custom_opts [Hash] Custom options for the importer (e.g., API token and project ID).
|
24
|
+
# @param global_config [Object] Global configuration settings, defaults to LokaliseManager::GlobalConfig.
|
25
|
+
# @return [LokaliseManager::TaskDefinitions::Importer] An instance of the importer.
|
23
26
|
#
|
24
|
-
# @return [LokaliseManager::TaskDefinitions::Importer]
|
25
|
-
# @param custom_opts [Hash]
|
26
|
-
# @param global_config [Object]
|
27
27
|
def importer(custom_opts = {}, global_config = LokaliseManager::GlobalConfig)
|
28
28
|
LokaliseManager::TaskDefinitions::Importer.new custom_opts, global_config
|
29
29
|
end
|
30
30
|
|
31
|
-
#
|
32
|
-
#
|
31
|
+
# Creates an exporter object for uploading translation files to Lokalise.
|
32
|
+
#
|
33
|
+
# @param custom_opts [Hash] Custom options for the exporter (e.g., API token and project ID).
|
34
|
+
# @param global_config [Object] Global configuration settings, defaults to LokaliseManager::GlobalConfig.
|
35
|
+
# @return [LokaliseManager::TaskDefinitions::Exporter] An instance of the exporter.
|
33
36
|
#
|
34
|
-
# @return [LokaliseManager::TaskDefinitions::Exporter]
|
35
|
-
# @param custom_opts [Hash]
|
36
|
-
# @param global_config [Object]
|
37
37
|
def exporter(custom_opts = {}, global_config = LokaliseManager::GlobalConfig)
|
38
38
|
LokaliseManager::TaskDefinitions::Exporter.new custom_opts, global_config
|
39
39
|
end
|
data/lokalise_manager.gemspec
CHANGED
@@ -27,13 +27,13 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_dependency 'rubyzip', '~> 2.3'
|
28
28
|
spec.add_dependency 'zeitwerk', '~> 2.4'
|
29
29
|
|
30
|
-
spec.add_development_dependency 'dotenv', '~>
|
30
|
+
spec.add_development_dependency 'dotenv', '~> 3.0'
|
31
31
|
spec.add_development_dependency 'rake', '~> 13.0'
|
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
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: 5.1.
|
4
|
+
version: 5.1.2
|
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-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-lokalise-api
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '3.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '3.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rake
|
71
71
|
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.22
|
242
242
|
signing_key:
|
243
243
|
specification_version: 4
|
244
244
|
summary: Lokalise integration for Ruby
|