lokalise_manager 5.1.0 → 5.1.2
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 +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
|
[![Maintainability](https://api.codeclimate.com/v1/badges/9b682367a274ee3dcdee/maintainability)](https://codeclimate.com/github/bodrovis/lokalise_manager/maintainability)
|
7
7
|
![Downloads total](https://img.shields.io/gem/dt/lokalise_manager)
|
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
|