web_translate_it 3.2.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2344f55bed19e435f8334c66229e58266ff1efe3cf54e9e81f4d17287f42a93d
4
- data.tar.gz: 89c6ea08bcc87a43741c8419d5de32e2441f896f12e8338762c15cf84759ab5e
3
+ metadata.gz: 660b650bae69f2f4a4282acb69b7abc72296b7e352660adaf3c1047a3bac04ce
4
+ data.tar.gz: acf8cc2ef71f6a897d400cd99793b86c33d28672b8aa6049b49d1f3b3a2f3c16
5
5
  SHA512:
6
- metadata.gz: c4a9b5b8f2874c7779eedb0d413d826a1d9bccbd8e9db33834ab82a0b8b22b2fb719bf412e7339cb05880018ddc072ee6e70a370824f3fc96f7d6982a2f8396f
7
- data.tar.gz: ad5b96143c805efa324ea6119af80dd24517c2e41691b451920934a3deea7115385c13f859ccff7d314c034680f64b95033aba839b1bc4728ff09aa885395a00
6
+ metadata.gz: dc29fee26eb5eb394705c81975adcf21f03dbe3ca7918d0e9a622a791ef0163ee9c7aa00f88d9c608eea462b5a75e8b9e4c7d530af276cfa3449ab7824ef53ec
7
+ data.tar.gz: 148e6238f00cb1cd2030a8a9f13c1b155ed82c26baac67f6933d86da68463998a8f91f144339f8d538512fe7eee0ec47cab15b23a2de8bdab27df48f77176b76
data/bin/wti CHANGED
@@ -42,11 +42,12 @@ when 'pull'
42
42
  wti pull [filename] - Pull target language file(s)
43
43
  [options] are:
44
44
  BANNER
45
- opt :locale, 'ISO code of locale(s) to pull, space-separated', type: :string
46
- opt :all, 'Pull all files'
47
- opt :force, 'Force pull (bypass conditional requests to WTI)'
48
- opt :config, 'Path to a configuration file', short: '-c', default: '.wti'
49
- opt :debug, 'Display debug information'
45
+ opt :locale, 'ISO code of locale(s) to pull, space-separated', type: :string
46
+ opt :all, 'Pull all files'
47
+ opt :force, 'Force pull (bypass conditional requests to WTI)'
48
+ opt :threads, 'Number of threads for parallel downloads', type: :integer, default: 10
49
+ opt :config, 'Path to a configuration file', short: '-c', default: '.wti'
50
+ opt :debug, 'Display debug information'
50
51
  end
51
52
  when 'push'
52
53
  Optimist.options do
data/history.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## Version 3.2.3 / 2026-04-09
2
+
3
+ * Handle HTTP 429 (rate limit) errors: add `RateLimitError` class, retry with backoff respecting the `Retry-After` header, and fix garbled error messages when the response body is not JSON.
4
+ * Add `--threads N` option to `wti pull` to control the number of concurrent download threads. Defaults to 10. Use `--threads 1` for sequential pulls.
5
+ * Modernize code for Ruby 3.0+: use `match?` instead of `!~`, `start_with?`/`delete_prefix` instead of index slicing, array difference instead of `reject`, and remove redundant `|| nil` fallbacks.
6
+
1
7
  ## Version 3.2.2 / 2026-03-03
2
8
 
3
9
  * Replace O(n²) string concatenation loop in `StringUtil#backward_truncate` with `String#ljust`.
@@ -9,9 +9,9 @@ module WebTranslateIt
9
9
  def initialize(params = {}, connection: nil)
10
10
  params = params.transform_keys(&:to_s)
11
11
  self.connection = connection
12
- self.id = params['id'] || nil
13
- self.created_at = params['created_at'] || nil
14
- self.updated_at = params['updated_at'] || nil
12
+ self.id = params['id']
13
+ self.created_at = params['created_at']
14
+ self.updated_at = params['updated_at']
15
15
  self.translations = params['translations'] || []
16
16
  self.new_record = true
17
17
  assign_attributes(params)
@@ -29,7 +29,7 @@ module WebTranslateIt
29
29
  end
30
30
 
31
31
  def valid_request?(env)
32
- env['PATH_INFO'] !~ /\.(js|css|jpeg|jpg|gif|png|woff)$/
32
+ !env['PATH_INFO'].match?(/\.(js|css|jpeg|jpg|gif|png|woff)$/)
33
33
  end
34
34
 
35
35
  end
@@ -29,7 +29,7 @@ module WebTranslateIt
29
29
 
30
30
  def pull_files(files) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength, Naming/PredicateMethod
31
31
  time = Time.now
32
- results, n_threads = Concurrency.concurrent_batch(files) do |batch|
32
+ results, n_threads = Concurrency.concurrent_batch(files, max_threads: command_options.threads) do |batch|
33
33
  with_connection do |conn|
34
34
  batch.map do |file|
35
35
  result = file.fetch(conn, command_options.force)
@@ -37,7 +37,7 @@ module WebTranslateIt
37
37
  if command_options.locale
38
38
  warn_unknown_locales(command_options.locale.split)
39
39
  elsif command_options.target
40
- configuration.target_locales.reject { |locale| locale == configuration.source_locale }
40
+ configuration.target_locales - [configuration.source_locale]
41
41
  else
42
42
  [configuration.source_locale]
43
43
  end
@@ -16,16 +16,16 @@ module WebTranslateIt
16
16
 
17
17
  protected
18
18
 
19
- def assign_attributes(params) # rubocop:todo Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
20
- self.key = params['key'] || nil
21
- self.plural = params['plural'] || nil
22
- self.type = params['type'] || nil
23
- self.dev_comment = params['dev_comment'] || nil
24
- self.word_count = params['word_count'] || nil
25
- self.status = params['status'] || nil
26
- self.category = params['category'] || nil
27
- self.labels = params['labels'] || nil
28
- self.file = params['file'] || nil
19
+ def assign_attributes(params) # rubocop:todo Metrics/AbcSize
20
+ self.key = params['key']
21
+ self.plural = params['plural']
22
+ self.type = params['type']
23
+ self.dev_comment = params['dev_comment']
24
+ self.word_count = params['word_count']
25
+ self.status = params['status']
26
+ self.category = params['category']
27
+ self.labels = params['labels']
28
+ self.file = params['file']
29
29
  end
30
30
 
31
31
  def parse_translation_response(json)
@@ -13,8 +13,8 @@ module WebTranslateIt
13
13
  protected
14
14
 
15
15
  def assign_attributes(params)
16
- self.text = params['text'] || nil
17
- self.description = params['description'] || nil
16
+ self.text = params['text']
17
+ self.description = params['description']
18
18
  end
19
19
 
20
20
  def parse_translation_response(json)
@@ -4,18 +4,27 @@ module WebTranslateIt
4
4
 
5
5
  module Concurrency
6
6
 
7
- # Execute a block with automatic retry on Timeout::Error.
7
+ # Execute a block with automatic retry on Timeout::Error and RateLimitError.
8
8
  # Returns the block's return value on success, or re-raises after retries are exhausted.
9
9
  def self.with_retries(retries: 3, delay: 5)
10
10
  yield
11
11
  rescue Timeout::Error
12
- puts "Request timeout. Will retry in #{delay} seconds."
13
- if (retries -= 1).positive?
14
- sleep(delay)
15
- retry
16
- end
17
- raise
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)
18
26
  end
27
+ private_class_method :log_retry
19
28
 
20
29
  # Process items in parallel using a thread pool.
21
30
  # Yields each batch (array of items) to the block; collects return values.
@@ -2,6 +2,17 @@
2
2
 
3
3
  module WebTranslateIt
4
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
+
5
16
  module HttpResponse
6
17
 
7
18
  STATUS_LABELS = {
@@ -31,15 +42,24 @@ module WebTranslateIt
31
42
 
32
43
  def self.raise_on_error!(response)
33
44
  code = response.code.to_i
34
- if code >= 400 && code < 500
35
- raise "Error: #{MultiJson.load(response.body)['error']}"
36
- elsif code == 500
37
- raise 'Error: Server temporarily unavailable (Error 500).'
38
- elsif code == 503
39
- raise 'Error: Locked (another import in progress)'
40
- end
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
41
49
  end
42
- private_class_method :raise_on_error!
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
43
63
 
44
64
  end
45
65
 
@@ -25,8 +25,8 @@ class StringUtil
25
25
  end
26
26
 
27
27
  def self.array_to_columns(array)
28
- if array[0][0] == '*'
29
- "*#{backward_truncate(array[0][1..])} | #{array[1]} #{array[2]}\n"
28
+ if array[0].start_with?('*')
29
+ "*#{backward_truncate(array[0].delete_prefix('*'))} | #{array[1]} #{array[2]}\n"
30
30
  else
31
31
  " #{backward_truncate(array[0])} | #{array[1]} #{array[2]}\n"
32
32
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: web_translate_it
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.2
4
+ version: 3.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edouard Briere
@@ -113,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
113
  - !ruby/object:Gem::Version
114
114
  version: '0'
115
115
  requirements: []
116
- rubygems_version: 3.6.9
116
+ rubygems_version: 4.0.6
117
117
  specification_version: 4
118
118
  summary: A CLI tool to sync locale files with WebTranslateIt.com.
119
119
  test_files: []