web_translate_it 3.2.1 → 3.2.3
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/bin/wti +8 -10
- data/history.md +35 -0
- data/lib/web_translate_it/api_resource.rb +137 -0
- data/lib/web_translate_it/auto_fetch.rb +1 -1
- data/lib/web_translate_it/commands/add.rb +58 -0
- data/lib/web_translate_it/commands/addlocale.rb +39 -0
- data/lib/web_translate_it/commands/base.rb +58 -0
- data/lib/web_translate_it/commands/diff.rb +71 -0
- data/lib/web_translate_it/commands/init.rb +112 -0
- data/lib/web_translate_it/commands/match.rb +30 -0
- data/lib/web_translate_it/commands/mv.rb +67 -0
- data/lib/web_translate_it/commands/pull.rb +62 -0
- data/lib/web_translate_it/commands/push.rb +50 -0
- data/lib/web_translate_it/commands/rm.rb +67 -0
- data/lib/web_translate_it/commands/rmlocale.rb +43 -0
- data/lib/web_translate_it/commands/status.rb +52 -0
- data/lib/web_translate_it/configuration.rb +31 -21
- data/lib/web_translate_it/connection.rb +30 -3
- data/lib/web_translate_it/project.rb +13 -72
- data/lib/web_translate_it/runner.rb +75 -0
- data/lib/web_translate_it/string.rb +26 -265
- data/lib/web_translate_it/term.rb +15 -255
- data/lib/web_translate_it/term_translation.rb +16 -69
- data/lib/web_translate_it/translation.rb +12 -50
- data/lib/web_translate_it/translation_base.rb +38 -0
- data/lib/web_translate_it/translation_file.rb +58 -109
- data/lib/web_translate_it/util/concurrency.rb +46 -0
- data/lib/web_translate_it/util/hash_util.rb +0 -2
- data/lib/web_translate_it/util/http_response.rb +66 -0
- data/lib/web_translate_it/util/prompt.rb +49 -0
- data/lib/web_translate_it/util/spinner.rb +51 -0
- data/lib/web_translate_it/util/string_util.rb +4 -10
- data/lib/web_translate_it/util.rb +0 -75
- data/lib/web_translate_it.rb +21 -3
- metadata +21 -4
- data/lib/web_translate_it/command_line.rb +0 -521
- data/lib/web_translate_it/util/hash_extensions.rb +0 -13
|
@@ -2,39 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module WebTranslateIt
|
|
4
4
|
|
|
5
|
-
class TermTranslation
|
|
5
|
+
class TermTranslation < TranslationBase
|
|
6
6
|
|
|
7
|
-
attr_accessor :
|
|
7
|
+
attr_accessor :description, :new_record, :term_id
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# Implementation Example:
|
|
12
|
-
#
|
|
13
|
-
# WebTranslateIt::TermTranslation.new({ :text => "Super!" })
|
|
14
|
-
#
|
|
15
|
-
# to instantiate a new TermTranslation.
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
def initialize(params = {})
|
|
19
|
-
params.stringify_keys!
|
|
20
|
-
self.id = params['id'] || nil
|
|
21
|
-
self.locale = params['locale'] || nil
|
|
22
|
-
self.text = params['text'] || nil
|
|
23
|
-
self.description = params['description'] || nil
|
|
24
|
-
self.status = params['status'] || nil
|
|
25
|
-
self.term_id = params['term_id'] || nil
|
|
26
|
-
self.new_record = true
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Update or Create a WebTranslateIt::TermTranslation
|
|
30
|
-
#
|
|
31
|
-
# Implementation Example:
|
|
32
|
-
#
|
|
33
|
-
# translation = WebTranslateIt::TermTranslation.new({ :term_id => "1234", :text => "Super!" })
|
|
34
|
-
# WebTranslateIt::Connection.new('secret_api_token') do
|
|
35
|
-
# translation.save
|
|
36
|
-
# end
|
|
37
|
-
#
|
|
9
|
+
def self.parent_resource_path = 'terms'
|
|
10
|
+
def parent_id = term_id
|
|
38
11
|
|
|
39
12
|
def save
|
|
40
13
|
new_record ? create : update
|
|
@@ -50,54 +23,28 @@ module WebTranslateIt
|
|
|
50
23
|
}
|
|
51
24
|
end
|
|
52
25
|
|
|
53
|
-
def to_json(*_args)
|
|
54
|
-
MultiJson.dump(to_hash)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
26
|
protected
|
|
58
27
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
request.body = to_json
|
|
28
|
+
def assign_attributes(params)
|
|
29
|
+
self.description = params['description']
|
|
30
|
+
self.term_id = params['term_id']
|
|
31
|
+
self.new_record = true
|
|
32
|
+
end
|
|
65
33
|
|
|
66
|
-
|
|
67
|
-
|
|
34
|
+
def create
|
|
35
|
+
Concurrency.with_retries do
|
|
36
|
+
raw = connection.post(translation_path, body: to_json)
|
|
37
|
+
response = JSON.parse(HttpResponse.handle_response(raw))
|
|
68
38
|
self.id = response['id']
|
|
69
39
|
self.new_record = false
|
|
70
40
|
return true
|
|
71
|
-
rescue Timeout::Error
|
|
72
|
-
puts 'Request timeout. Will retry in 5 seconds.'
|
|
73
|
-
if (tries -= 1).positive?
|
|
74
|
-
sleep(5)
|
|
75
|
-
retry
|
|
76
|
-
else
|
|
77
|
-
success = false
|
|
78
|
-
end
|
|
79
41
|
end
|
|
80
|
-
success
|
|
81
42
|
end
|
|
82
43
|
|
|
83
|
-
def update
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
request = Net::HTTP::Put.new("/api/projects/#{connection.api_key}/terms/#{id}/locales/#{locale}/translations/#{id}")
|
|
87
|
-
WebTranslateIt::Util.add_fields(request)
|
|
88
|
-
request.body = to_json
|
|
89
|
-
begin
|
|
90
|
-
Util.handle_response(connection.http_connection.request(request), true, true)
|
|
91
|
-
rescue Timeout::Error
|
|
92
|
-
puts 'Request timeout. Will retry in 5 seconds.'
|
|
93
|
-
if (tries -= 1).positive?
|
|
94
|
-
sleep(5)
|
|
95
|
-
retry
|
|
96
|
-
else
|
|
97
|
-
success = false
|
|
98
|
-
end
|
|
44
|
+
def update
|
|
45
|
+
Concurrency.with_retries do
|
|
46
|
+
HttpResponse.handle_response(connection.put("/api/projects/#{connection.api_key}/terms/#{id}/locales/#{locale}/translations/#{id}", body: to_json))
|
|
99
47
|
end
|
|
100
|
-
success
|
|
101
48
|
end
|
|
102
49
|
|
|
103
50
|
end
|
|
@@ -2,56 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module WebTranslateIt
|
|
4
4
|
|
|
5
|
-
class Translation
|
|
5
|
+
class Translation < TranslationBase
|
|
6
6
|
|
|
7
|
-
attr_accessor :
|
|
7
|
+
attr_accessor :created_at, :updated_at, :version, :string_id
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# Implementation Example:
|
|
12
|
-
#
|
|
13
|
-
# WebTranslateIt::Translation.new({ :string_id => "1234", :text => "Super!" })
|
|
14
|
-
#
|
|
15
|
-
# to instantiate a new Translation without any text.
|
|
16
|
-
#
|
|
17
|
-
|
|
18
|
-
def initialize(params = {}) # rubocop:todo Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
19
|
-
params.stringify_keys!
|
|
20
|
-
self.id = params['id'] || nil
|
|
21
|
-
self.locale = params['locale'] || nil
|
|
22
|
-
self.text = params['text'] || nil
|
|
23
|
-
self.status = params['status'] || 'status_unproofread'
|
|
24
|
-
self.created_at = params['created_at'] || nil
|
|
25
|
-
self.updated_at = params['updated_at'] || nil
|
|
26
|
-
self.version = params['version'] || nil
|
|
27
|
-
self.string_id = (params['string']['id'] if params['string'])
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# Save a WebTranslateIt::Translation
|
|
31
|
-
#
|
|
32
|
-
# Implementation Example:
|
|
33
|
-
#
|
|
34
|
-
# translation = WebTranslateIt::Translation.new({ :string_id => "1234", :text => "Super!" })
|
|
35
|
-
# WebTranslateIt::Connection.new('secret_api_token') do
|
|
36
|
-
# translation.save
|
|
37
|
-
# end
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
def save # rubocop:todo Metrics/MethodLength
|
|
41
|
-
tries ||= 3
|
|
42
|
-
request = Net::HTTP::Post.new("/api/projects/#{connection.api_key}/strings/#{string_id}/locales/#{locale}/translations")
|
|
43
|
-
WebTranslateIt::Util.add_fields(request)
|
|
44
|
-
request.body = to_json
|
|
45
|
-
begin
|
|
46
|
-
Util.handle_response(connection.http_connection.request(request), true, true)
|
|
47
|
-
rescue Timeout::Error
|
|
48
|
-
puts 'Request timeout. Will retry in 5 seconds.'
|
|
49
|
-
if (tries -= 1).positive?
|
|
50
|
-
sleep(5)
|
|
51
|
-
retry
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
9
|
+
def self.parent_resource_path = 'strings'
|
|
10
|
+
def parent_id = string_id
|
|
55
11
|
|
|
56
12
|
def to_hash
|
|
57
13
|
{
|
|
@@ -61,8 +17,14 @@ module WebTranslateIt
|
|
|
61
17
|
}
|
|
62
18
|
end
|
|
63
19
|
|
|
64
|
-
|
|
65
|
-
|
|
20
|
+
protected
|
|
21
|
+
|
|
22
|
+
def assign_attributes(params)
|
|
23
|
+
self.status ||= 'status_unproofread'
|
|
24
|
+
self.created_at = params['created_at']
|
|
25
|
+
self.updated_at = params['updated_at']
|
|
26
|
+
self.version = params['version']
|
|
27
|
+
self.string_id = params['string']['id'] if params['string']
|
|
66
28
|
end
|
|
67
29
|
|
|
68
30
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebTranslateIt
|
|
4
|
+
|
|
5
|
+
class TranslationBase
|
|
6
|
+
|
|
7
|
+
attr_accessor :id, :locale, :text, :status, :connection
|
|
8
|
+
|
|
9
|
+
def initialize(params = {})
|
|
10
|
+
params = params.transform_keys(&:to_s)
|
|
11
|
+
self.id = params['id']
|
|
12
|
+
self.locale = params['locale']
|
|
13
|
+
self.text = params['text']
|
|
14
|
+
self.status = params['status']
|
|
15
|
+
assign_attributes(params)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def save
|
|
19
|
+
Concurrency.with_retries do
|
|
20
|
+
HttpResponse.handle_response(connection.post(translation_path, body: to_json))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_json(*_args)
|
|
25
|
+
MultiJson.dump(to_hash)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
protected
|
|
29
|
+
|
|
30
|
+
def assign_attributes(_params); end
|
|
31
|
+
|
|
32
|
+
def translation_path
|
|
33
|
+
"/api/projects/#{connection.api_key}/#{self.class.parent_resource_path}/#{parent_id}/locales/#{locale}/translations"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
@@ -12,7 +12,22 @@ module WebTranslateIt
|
|
|
12
12
|
|
|
13
13
|
attr_accessor :id, :file_path, :locale, :api_key, :updated_at, :remote_checksum, :master_id, :fresh
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Result = Struct.new(:success, :output)
|
|
16
|
+
|
|
17
|
+
def self.from_api(project_file, api_key)
|
|
18
|
+
new(
|
|
19
|
+
project_file['id'],
|
|
20
|
+
project_file['name'],
|
|
21
|
+
project_file['locale_code'],
|
|
22
|
+
api_key,
|
|
23
|
+
updated_at: project_file['updated_at'],
|
|
24
|
+
remote_checksum: project_file['hash_file'],
|
|
25
|
+
master_id: project_file['master_project_file_id'],
|
|
26
|
+
fresh: project_file['fresh']
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(id, file_path, locale, api_key, updated_at: nil, remote_checksum: '', master_id: nil, fresh: nil)
|
|
16
31
|
self.id = id
|
|
17
32
|
self.file_path = file_path
|
|
18
33
|
self.locale = locale
|
|
@@ -35,9 +50,7 @@ module WebTranslateIt
|
|
|
35
50
|
# file.fetch # returns nothing, with a status 304 Not Modified
|
|
36
51
|
# file.fetch(true) # force to re-download the file, will return the content of the file with a 200 OK
|
|
37
52
|
#
|
|
38
|
-
def fetch(
|
|
39
|
-
success = true
|
|
40
|
-
tries ||= 3
|
|
53
|
+
def fetch(connection, force = false) # rubocop:todo Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
41
54
|
display = []
|
|
42
55
|
if fresh
|
|
43
56
|
display.push(file_path)
|
|
@@ -47,37 +60,22 @@ module WebTranslateIt
|
|
|
47
60
|
display.push "#{StringUtil.checksumify(local_checksum.to_s)}..#{StringUtil.checksumify(remote_checksum.to_s)}"
|
|
48
61
|
if !File.exist?(file_path) || force || (remote_checksum != local_checksum)
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
response = http_connection.request(request)
|
|
63
|
+
dir = File.dirname(file_path)
|
|
64
|
+
FileUtils.mkpath(dir) unless File.exist?(file_path) || dir == '.'
|
|
65
|
+
with_display(display) do
|
|
66
|
+
response = connection.get(api_url)
|
|
55
67
|
File.open(file_path, 'wb') { |file| file << response.body } if response.code.to_i == 200
|
|
56
|
-
|
|
57
|
-
rescue Timeout::Error
|
|
58
|
-
puts StringUtil.failure('Request timeout. Will retry in 5 seconds.')
|
|
59
|
-
if (tries -= 1).positive?
|
|
60
|
-
sleep(5)
|
|
61
|
-
retry
|
|
62
|
-
else
|
|
63
|
-
success = false
|
|
64
|
-
end
|
|
65
|
-
rescue StandardError
|
|
66
|
-
display.push StringUtil.failure("An error occured: #{$ERROR_INFO}")
|
|
67
|
-
success = false
|
|
68
|
+
response
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
else
|
|
71
72
|
display.push StringUtil.success('Skipped')
|
|
73
|
+
Result.new(true, display)
|
|
72
74
|
end
|
|
73
|
-
print StringUtil.array_to_columns(display)
|
|
74
|
-
success
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
def fetch_remote_content(
|
|
78
|
-
|
|
79
|
-
WebTranslateIt::Util.add_fields(request)
|
|
80
|
-
response = http_connection.request(request)
|
|
77
|
+
def fetch_remote_content(connection)
|
|
78
|
+
response = connection.get(api_url)
|
|
81
79
|
response.body if response.code.to_i == 200
|
|
82
80
|
end
|
|
83
81
|
|
|
@@ -92,13 +90,9 @@ module WebTranslateIt
|
|
|
92
90
|
#
|
|
93
91
|
# Note that the request might or might not eventually be acted upon, as it might be disallowed when processing
|
|
94
92
|
# actually takes place. This is due to the fact that language file imports are handled by background processing.
|
|
95
|
-
# rubocop:todo Metrics/PerceivedComplexity
|
|
96
|
-
# rubocop:todo Metrics/ParameterLists
|
|
97
|
-
# rubocop:todo Metrics/MethodLength
|
|
98
93
|
# rubocop:todo Metrics/AbcSize
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
tries ||= 3
|
|
94
|
+
# rubocop:todo Metrics/MethodLength
|
|
95
|
+
def upload(connection, merge: false, ignore_missing: false, label: nil, minor_changes: false, force: false, rename_others: false, destination_path: nil) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
|
|
102
96
|
display = []
|
|
103
97
|
display.push(file_path)
|
|
104
98
|
display.push "#{StringUtil.checksumify(local_checksum.to_s)}..#{StringUtil.checksumify(remote_checksum.to_s)}"
|
|
@@ -114,126 +108,68 @@ module WebTranslateIt
|
|
|
114
108
|
['file', file]
|
|
115
109
|
]
|
|
116
110
|
params += [['name', destination_path]] unless destination_path.nil?
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
request.set_form params, 'multipart/form-data'
|
|
120
|
-
display.push Util.handle_response(http_connection.request(request))
|
|
121
|
-
rescue Timeout::Error
|
|
122
|
-
puts StringUtil.failure('Request timeout. Will retry in 5 seconds.')
|
|
123
|
-
if (tries -= 1).positive? # rubocop:todo Metrics/BlockNesting
|
|
124
|
-
sleep(5)
|
|
125
|
-
retry
|
|
126
|
-
else
|
|
127
|
-
success = false
|
|
111
|
+
return with_display(display) do
|
|
112
|
+
connection.put(api_url) { |req| req.set_form params, 'multipart/form-data' }
|
|
128
113
|
end
|
|
129
|
-
rescue StandardError
|
|
130
|
-
display.push StringUtil.failure("An error occured: #{$ERROR_INFO}")
|
|
131
|
-
success = false
|
|
132
114
|
end
|
|
133
115
|
else
|
|
134
116
|
display.push StringUtil.success('Skipped')
|
|
135
117
|
end
|
|
136
|
-
puts StringUtil.array_to_columns(display)
|
|
137
118
|
else
|
|
138
|
-
|
|
119
|
+
display.push StringUtil.failure("Can't push #{file_path}. File doesn't exist locally.")
|
|
139
120
|
end
|
|
140
|
-
|
|
121
|
+
Result.new(true, display)
|
|
141
122
|
end
|
|
142
|
-
# rubocop:enable Metrics/AbcSize
|
|
143
|
-
# rubocop:enable Metrics/MethodLength
|
|
144
|
-
# rubocop:enable Metrics/ParameterLists
|
|
145
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
|
146
123
|
|
|
124
|
+
# rubocop:enable Metrics/MethodLength
|
|
125
|
+
# rubocop:enable Metrics/AbcSize
|
|
147
126
|
# Create a master language file to Web Translate It by performing a POST Request.
|
|
148
127
|
#
|
|
149
128
|
# Example of implementation:
|
|
150
129
|
#
|
|
151
130
|
# configuration = WebTranslateIt::Configuration.new
|
|
152
131
|
# file = TranslationFile.new(nil, file_path, nil, configuration.api_key)
|
|
153
|
-
# file.create # should respond the HTTP code 201 Created
|
|
132
|
+
# file.create(http_connection) # should respond the HTTP code 201 Created
|
|
154
133
|
#
|
|
155
134
|
# Note that the request might or might not eventually be acted upon, as it might be disallowed when processing
|
|
156
135
|
# actually takes place. This is due to the fact that language file imports are handled by background processing.
|
|
157
136
|
#
|
|
158
|
-
def create(
|
|
159
|
-
success = true
|
|
160
|
-
tries ||= 3
|
|
137
|
+
def create(connection) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
|
|
161
138
|
display = []
|
|
162
139
|
display.push file_path
|
|
163
140
|
display.push "#{StringUtil.checksumify(local_checksum.to_s)}..[ ]"
|
|
164
141
|
if File.exist?(file_path)
|
|
165
142
|
File.open(file_path) do |file|
|
|
166
|
-
params = [['name', file_path]]
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
WebTranslateIt::Util.add_fields(request)
|
|
170
|
-
request.set_form params, 'multipart/form-data'
|
|
171
|
-
display.push Util.handle_response(http_connection.request(request))
|
|
172
|
-
puts StringUtil.array_to_columns(display)
|
|
173
|
-
rescue Timeout::Error
|
|
174
|
-
puts StringUtil.failure('Request timeout. Will retry in 5 seconds.')
|
|
175
|
-
if (tries -= 1).positive?
|
|
176
|
-
sleep(5)
|
|
177
|
-
retry
|
|
178
|
-
else
|
|
179
|
-
success = false
|
|
143
|
+
params = [['name', file_path], ['file', file]]
|
|
144
|
+
return with_display(display) do
|
|
145
|
+
connection.post(api_url_for_create) { |req| req.set_form params, 'multipart/form-data' }
|
|
180
146
|
end
|
|
181
|
-
rescue StandardError
|
|
182
|
-
display.push StringUtil.failure("An error occured: #{$ERROR_INFO}")
|
|
183
|
-
success = false
|
|
184
147
|
end
|
|
185
148
|
else
|
|
186
|
-
|
|
149
|
+
display.push StringUtil.failure("File #{file_path} doesn't exist locally!")
|
|
187
150
|
end
|
|
188
|
-
|
|
151
|
+
Result.new(true, display)
|
|
189
152
|
end
|
|
190
153
|
|
|
191
154
|
# Delete a master language file from Web Translate It by performing a DELETE Request.
|
|
192
155
|
#
|
|
193
|
-
def delete(
|
|
194
|
-
success = true
|
|
195
|
-
tries ||= 3
|
|
156
|
+
def delete(connection)
|
|
196
157
|
display = []
|
|
197
158
|
display.push file_path
|
|
198
159
|
if File.exist?(file_path)
|
|
199
|
-
|
|
200
|
-
request = Net::HTTP::Delete.new(api_url_for_delete)
|
|
201
|
-
WebTranslateIt::Util.add_fields(request)
|
|
202
|
-
display.push Util.handle_response(http_connection.request(request))
|
|
203
|
-
puts StringUtil.array_to_columns(display)
|
|
204
|
-
rescue Timeout::Error
|
|
205
|
-
puts StringUtil.failure('Request timeout. Will retry in 5 seconds.')
|
|
206
|
-
if (tries -= 1).positive?
|
|
207
|
-
sleep(5)
|
|
208
|
-
retry
|
|
209
|
-
else
|
|
210
|
-
success = false
|
|
211
|
-
end
|
|
212
|
-
rescue StandardError
|
|
213
|
-
display.push StringUtil.failure("An error occured: #{$ERROR_INFO}")
|
|
214
|
-
success = false
|
|
215
|
-
end
|
|
160
|
+
with_display(display) { connection.delete(api_url_for_delete) }
|
|
216
161
|
else
|
|
217
|
-
|
|
162
|
+
display.push StringUtil.failure("Master file #{file_path} doesn't exist locally!")
|
|
163
|
+
Result.new(true, display)
|
|
218
164
|
end
|
|
219
|
-
success
|
|
220
165
|
end
|
|
221
166
|
|
|
222
167
|
def exists?
|
|
223
168
|
File.exist?(file_path)
|
|
224
169
|
end
|
|
225
170
|
|
|
226
|
-
def modified_remotely?
|
|
227
|
-
fetch == '200 OK'
|
|
228
|
-
end
|
|
229
|
-
|
|
230
171
|
protected
|
|
231
172
|
|
|
232
|
-
# Convenience method which returns the date of last modification of a language file.
|
|
233
|
-
def last_modification
|
|
234
|
-
File.mtime(File.new(file_path, 'r'))
|
|
235
|
-
end
|
|
236
|
-
|
|
237
173
|
# Convenience method which returns the URL of the API endpoint for a locale.
|
|
238
174
|
def api_url
|
|
239
175
|
"/api/projects/#{api_key}/files/#{id}/locales/#{locale}"
|
|
@@ -249,10 +185,23 @@ module WebTranslateIt
|
|
|
249
185
|
|
|
250
186
|
def local_checksum
|
|
251
187
|
Digest::SHA1.hexdigest(File.read(file_path))
|
|
252
|
-
rescue StandardError
|
|
188
|
+
rescue StandardError => _e
|
|
253
189
|
''
|
|
254
190
|
end
|
|
255
191
|
|
|
192
|
+
private
|
|
193
|
+
|
|
194
|
+
def with_display(display)
|
|
195
|
+
Concurrency.with_retries do
|
|
196
|
+
response = yield
|
|
197
|
+
display.push HttpResponse.status_label(response)
|
|
198
|
+
end
|
|
199
|
+
Result.new(true, display)
|
|
200
|
+
rescue StandardError => e
|
|
201
|
+
display.push StringUtil.failure("An error occured: #{e.message}")
|
|
202
|
+
Result.new(false, display)
|
|
203
|
+
end
|
|
204
|
+
|
|
256
205
|
end
|
|
257
206
|
|
|
258
207
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebTranslateIt
|
|
4
|
+
|
|
5
|
+
module Concurrency
|
|
6
|
+
|
|
7
|
+
# Execute a block with automatic retry on Timeout::Error and RateLimitError.
|
|
8
|
+
# Returns the block's return value on success, or re-raises after retries are exhausted.
|
|
9
|
+
def self.with_retries(retries: 3, delay: 5)
|
|
10
|
+
yield
|
|
11
|
+
rescue Timeout::Error
|
|
12
|
+
raise unless (retries -= 1).positive?
|
|
13
|
+
|
|
14
|
+
log_retry('Request timeout', delay)
|
|
15
|
+
retry
|
|
16
|
+
rescue RateLimitError => e
|
|
17
|
+
raise unless (retries -= 1).positive?
|
|
18
|
+
|
|
19
|
+
log_retry('Rate limited', e.retry_after || delay)
|
|
20
|
+
retry
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.log_retry(message, wait)
|
|
24
|
+
puts "#{message}. Will retry in #{wait} seconds."
|
|
25
|
+
sleep(wait)
|
|
26
|
+
end
|
|
27
|
+
private_class_method :log_retry
|
|
28
|
+
|
|
29
|
+
# Process items in parallel using a thread pool.
|
|
30
|
+
# Yields each batch (array of items) to the block; collects return values.
|
|
31
|
+
# Returns [results, n_threads] where results is a flat array of block return values.
|
|
32
|
+
def self.concurrent_batch(items, batch_size: 3, max_threads: 10, &block)
|
|
33
|
+
n_threads = [(items.size.to_f / batch_size).ceil, max_threads].min
|
|
34
|
+
n_threads = 1 if n_threads < 1
|
|
35
|
+
threads = items.each_slice((items.size.to_f / n_threads).ceil).filter_map do |slice|
|
|
36
|
+
next if slice.empty?
|
|
37
|
+
|
|
38
|
+
Thread.new(slice, &block)
|
|
39
|
+
end
|
|
40
|
+
results = threads.flat_map(&:value)
|
|
41
|
+
[results, n_threads]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebTranslateIt
|
|
4
|
+
|
|
5
|
+
class RateLimitError < StandardError
|
|
6
|
+
|
|
7
|
+
attr_reader :retry_after
|
|
8
|
+
|
|
9
|
+
def initialize(retry_after: nil)
|
|
10
|
+
@retry_after = retry_after
|
|
11
|
+
super("Rate limited#{", retry after #{retry_after}s" if retry_after}")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module HttpResponse
|
|
17
|
+
|
|
18
|
+
STATUS_LABELS = {
|
|
19
|
+
200 => 'OK',
|
|
20
|
+
201 => 'Created',
|
|
21
|
+
202 => 'Accepted',
|
|
22
|
+
304 => 'Not Modified'
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
def self.handle_response(response)
|
|
26
|
+
raise_on_error!(response)
|
|
27
|
+
response.body
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.status_label(response)
|
|
31
|
+
raise_on_error!(response)
|
|
32
|
+
label = STATUS_LABELS[response.code.to_i]
|
|
33
|
+
StringUtil.success(label || 'OK')
|
|
34
|
+
rescue RuntimeError => e
|
|
35
|
+
StringUtil.failure(e.message)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.add_fields(request)
|
|
39
|
+
request.add_field('User-Agent', "wti v#{Util.version}")
|
|
40
|
+
request.add_field('Content-Type', 'application/json')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.raise_on_error!(response)
|
|
44
|
+
code = response.code.to_i
|
|
45
|
+
raise_on_rate_limit!(response) if code == 429
|
|
46
|
+
raise "Error: #{error_message(response)}" if code >= 400 && code < 500
|
|
47
|
+
raise 'Error: Server temporarily unavailable (Error 500).' if code == 500
|
|
48
|
+
raise 'Error: Locked (another import in progress)' if code == 503
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.raise_on_rate_limit!(response)
|
|
52
|
+
retry_after = response['Retry-After']&.to_i
|
|
53
|
+
raise RateLimitError.new(retry_after: retry_after)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.error_message(response)
|
|
57
|
+
MultiJson.load(response.body)['error']
|
|
58
|
+
rescue StandardError
|
|
59
|
+
response.body.to_s
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private_class_method :raise_on_error!, :raise_on_rate_limit!, :error_message
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebTranslateIt
|
|
4
|
+
|
|
5
|
+
module Prompt
|
|
6
|
+
|
|
7
|
+
# Ask a question. Returns a true for yes, false for no, default for nil.
|
|
8
|
+
def self.ask_yes_no(question, default = nil) # rubocop:todo Metrics/MethodLength
|
|
9
|
+
qstr = case default
|
|
10
|
+
when nil
|
|
11
|
+
'yn'
|
|
12
|
+
when true
|
|
13
|
+
'Yn'
|
|
14
|
+
else
|
|
15
|
+
'yN'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
result = nil
|
|
19
|
+
|
|
20
|
+
while result.nil?
|
|
21
|
+
result = ask("#{question} [#{qstr}]")
|
|
22
|
+
result = case result
|
|
23
|
+
when /^[Yy].*/
|
|
24
|
+
true
|
|
25
|
+
when /^[Nn].*/
|
|
26
|
+
false
|
|
27
|
+
when '', nil
|
|
28
|
+
default
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Ask a question. Returns an answer.
|
|
36
|
+
def self.ask(question, default = nil)
|
|
37
|
+
question += " (Default: #{default})" unless default.nil?
|
|
38
|
+
print("#{question} ")
|
|
39
|
+
$stdout.flush
|
|
40
|
+
|
|
41
|
+
result = $stdin.gets
|
|
42
|
+
result&.chomp!
|
|
43
|
+
result = default if result.nil? || (result == '')
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|