i18n-tasks 1.0.15 → 1.1.0

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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +85 -13
  3. data/Rakefile +4 -4
  4. data/bin/i18n-tasks +3 -3
  5. data/config/locales/en.yml +6 -0
  6. data/config/locales/ru.yml +7 -0
  7. data/i18n-tasks.gemspec +28 -41
  8. data/lib/i18n/tasks/base_task.rb +19 -19
  9. data/lib/i18n/tasks/cli.rb +37 -30
  10. data/lib/i18n/tasks/command/collection.rb +4 -4
  11. data/lib/i18n/tasks/command/commander.rb +5 -5
  12. data/lib/i18n/tasks/command/commands/check_prism.rb +126 -0
  13. data/lib/i18n/tasks/command/commands/data.rb +33 -33
  14. data/lib/i18n/tasks/command/commands/eq_base.rb +3 -3
  15. data/lib/i18n/tasks/command/commands/health.rb +6 -5
  16. data/lib/i18n/tasks/command/commands/interpolations.rb +14 -3
  17. data/lib/i18n/tasks/command/commands/meta.rb +6 -6
  18. data/lib/i18n/tasks/command/commands/missing.rb +25 -25
  19. data/lib/i18n/tasks/command/commands/tree.rb +33 -33
  20. data/lib/i18n/tasks/command/commands/usages.rb +24 -24
  21. data/lib/i18n/tasks/command/dsl.rb +1 -1
  22. data/lib/i18n/tasks/command/option_parsers/enum.rb +5 -5
  23. data/lib/i18n/tasks/command/option_parsers/locale.rb +4 -4
  24. data/lib/i18n/tasks/command/options/common.rb +16 -16
  25. data/lib/i18n/tasks/command/options/data.rb +18 -18
  26. data/lib/i18n/tasks/command/options/locales.rb +32 -32
  27. data/lib/i18n/tasks/commands.rb +14 -12
  28. data/lib/i18n/tasks/concurrent/cache.rb +1 -1
  29. data/lib/i18n/tasks/concurrent/cached_value.rb +1 -1
  30. data/lib/i18n/tasks/configuration.rb +22 -21
  31. data/lib/i18n/tasks/console_context.rb +11 -11
  32. data/lib/i18n/tasks/data/adapter/json_adapter.rb +1 -1
  33. data/lib/i18n/tasks/data/adapter/yaml_adapter.rb +5 -5
  34. data/lib/i18n/tasks/data/file_formats.rb +3 -3
  35. data/lib/i18n/tasks/data/file_system.rb +5 -5
  36. data/lib/i18n/tasks/data/file_system_base.rb +26 -26
  37. data/lib/i18n/tasks/data/language_names.rb +202 -0
  38. data/lib/i18n/tasks/data/router/conservative_router.rb +3 -3
  39. data/lib/i18n/tasks/data/router/isolating_router.rb +19 -19
  40. data/lib/i18n/tasks/data/router/pattern_router.rb +5 -5
  41. data/lib/i18n/tasks/data/tree/node.rb +27 -27
  42. data/lib/i18n/tasks/data/tree/nodes.rb +10 -10
  43. data/lib/i18n/tasks/data/tree/siblings.rb +20 -20
  44. data/lib/i18n/tasks/data/tree/traversal.rb +5 -5
  45. data/lib/i18n/tasks/data.rb +4 -4
  46. data/lib/i18n/tasks/html_keys.rb +2 -2
  47. data/lib/i18n/tasks/ignore_keys.rb +9 -9
  48. data/lib/i18n/tasks/interpolations.rb +21 -1
  49. data/lib/i18n/tasks/key_pattern_matching.rb +8 -8
  50. data/lib/i18n/tasks/logging.rb +2 -1
  51. data/lib/i18n/tasks/missing_keys.rb +24 -8
  52. data/lib/i18n/tasks/plural_keys.rb +6 -4
  53. data/lib/i18n/tasks/references.rb +4 -4
  54. data/lib/i18n/tasks/reports/base.rb +18 -14
  55. data/lib/i18n/tasks/reports/terminal.rb +64 -47
  56. data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +3 -3
  57. data/lib/i18n/tasks/scanners/ast_matchers/default_i18n_subject_matcher.rb +3 -3
  58. data/lib/i18n/tasks/scanners/ast_matchers/message_receivers_matcher.rb +10 -10
  59. data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +1 -1
  60. data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +69 -10
  61. data/lib/i18n/tasks/scanners/file_scanner.rb +5 -5
  62. data/lib/i18n/tasks/scanners/files/caching_file_finder.rb +3 -3
  63. data/lib/i18n/tasks/scanners/files/caching_file_finder_provider.rb +3 -3
  64. data/lib/i18n/tasks/scanners/files/caching_file_reader.rb +2 -2
  65. data/lib/i18n/tasks/scanners/files/file_finder.rb +8 -8
  66. data/lib/i18n/tasks/scanners/files/file_reader.rb +1 -1
  67. data/lib/i18n/tasks/scanners/local_ruby_parser.rb +8 -8
  68. data/lib/i18n/tasks/scanners/occurrence_from_position.rb +1 -1
  69. data/lib/i18n/tasks/scanners/pattern_mapper.rb +7 -7
  70. data/lib/i18n/tasks/scanners/pattern_scanner.rb +20 -20
  71. data/lib/i18n/tasks/scanners/pattern_with_scope_scanner.rb +8 -8
  72. data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +8 -1
  73. data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +101 -61
  74. data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +169 -105
  75. data/lib/i18n/tasks/scanners/relative_keys.rb +8 -8
  76. data/lib/i18n/tasks/scanners/results/key_occurrences.rb +3 -3
  77. data/lib/i18n/tasks/scanners/results/occurrence.rb +14 -10
  78. data/lib/i18n/tasks/scanners/ruby_ast_call_finder.rb +1 -1
  79. data/lib/i18n/tasks/scanners/ruby_key_literals.rb +6 -6
  80. data/lib/i18n/tasks/scanners/ruby_parser_factory.rb +1 -1
  81. data/lib/i18n/tasks/scanners/ruby_scanner.rb +225 -0
  82. data/lib/i18n/tasks/scanners/scanner.rb +2 -2
  83. data/lib/i18n/tasks/scanners/scanner_multiplexer.rb +1 -1
  84. data/lib/i18n/tasks/split_key.rb +4 -4
  85. data/lib/i18n/tasks/stats.rb +3 -3
  86. data/lib/i18n/tasks/translation.rb +5 -5
  87. data/lib/i18n/tasks/translators/base_translator.rb +40 -14
  88. data/lib/i18n/tasks/translators/deepl_translator.rb +17 -14
  89. data/lib/i18n/tasks/translators/google_translator.rb +169 -25
  90. data/lib/i18n/tasks/translators/openai_translator.rb +34 -23
  91. data/lib/i18n/tasks/translators/watsonx_translator.rb +16 -16
  92. data/lib/i18n/tasks/translators/yandex_translator.rb +8 -8
  93. data/lib/i18n/tasks/unused_keys.rb +1 -1
  94. data/lib/i18n/tasks/used_keys.rb +32 -33
  95. data/lib/i18n/tasks/version.rb +1 -1
  96. data/lib/i18n/tasks.rb +17 -17
  97. data/templates/config/i18n-tasks.yml +12 -0
  98. data/templates/minitest/i18n_test.rb +3 -3
  99. data/templates/rspec/i18n_spec.rb +7 -7
  100. metadata +25 -185
  101. data/lib/i18n/tasks/scanners/prism_scanner.rb +0 -83
  102. data/lib/i18n/tasks/scanners/ruby_ast_scanner.rb +0 -145
@@ -1,29 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/translators/base_translator'
3
+ require "cgi"
4
+ require "net/http"
5
+ require "uri"
6
+ require "json"
7
+ require "i18n/tasks/translators/base_translator"
8
+
9
+ # https://cloud.google.com/translate/docs/reference/rest/v2/translate
4
10
 
5
11
  module I18n::Tasks::Translators
6
12
  class GoogleTranslator < BaseTranslator
7
- NEWLINE_PLACEHOLDER = '<br id=i18n />'
13
+ API_ENDPOINT = "https://translation.googleapis.com/language/translate/v2"
14
+ MAX_TEXTS_PER_REQUEST = 128
15
+ MAX_CHARS_PER_REQUEST = 30_000
16
+
17
+ class Error < StandardError; end
18
+
19
+ class RateLimitError < Error; end
20
+
21
+ class QuotaExceededError < Error; end
22
+
8
23
  def initialize(*)
9
- begin
10
- require 'easy_translate'
11
- rescue LoadError
12
- raise ::I18n::Tasks::CommandError, "Add gem 'easy_translate' to your Gemfile to use this command"
13
- end
14
24
  super
15
25
  end
16
26
 
17
27
  protected
18
28
 
19
29
  def translate_values(list, **options)
30
+ html = options[:html].present?
20
31
  result = restore_newlines(
21
- EasyTranslate.translate(
22
- replace_newlines_with_placeholder(list, options[:html]),
23
- options,
24
- format: options[:html] ? :html : :text
32
+ translate(
33
+ texts: replace_newlines_with_placeholder(list),
34
+ api_key: api_key,
35
+ to: options[:to],
36
+ from: options[:from],
37
+ html:
25
38
  ),
26
- options[:html]
39
+ html:
27
40
  )
28
41
 
29
42
  @progress_bar.progress += result.size
@@ -40,15 +53,15 @@ module I18n::Tasks::Translators
40
53
  end
41
54
 
42
55
  def options_for_html
43
- { html: true }
56
+ {html: true}
44
57
  end
45
58
 
46
59
  def options_for_plain
47
- { format: 'text' }
60
+ {format: "text"}
48
61
  end
49
62
 
50
63
  def no_results_error_message
51
- I18n.t('i18n_tasks.google_translate.errors.no_results')
64
+ I18n.t("i18n_tasks.google_translate.errors.no_results")
52
65
  end
53
66
 
54
67
  private
@@ -59,19 +72,17 @@ module I18n::Tasks::Translators
59
72
  # fallback with deprecation warning
60
73
  if @i18n_tasks.translation_config[:api_key]
61
74
  warn_deprecated(
62
- 'Please rename Google Translate API Key from `api_key` to `google_translate_api_key`.'
75
+ "Please rename Google Translate API Key from `api_key` to `google_translate_api_key`."
63
76
  )
64
77
  key ||= translation_config[:api_key]
65
78
  end
66
- fail ::I18n::Tasks::CommandError, I18n.t('i18n_tasks.google_translate.errors.no_api_key') if key.blank?
79
+ fail ::I18n::Tasks::CommandError, I18n.t("i18n_tasks.google_translate.errors.no_api_key") if key.blank?
67
80
 
68
81
  key
69
82
  end
70
83
  end
71
84
 
72
- def replace_newlines_with_placeholder(list, html)
73
- return list unless html
74
-
85
+ def replace_newlines_with_placeholder(list)
75
86
  list.map do |value|
76
87
  value.gsub(/\n(\s*)/) do
77
88
  "<Z__#{::Regexp.last_match(1)&.length || 0}>"
@@ -79,13 +90,146 @@ module I18n::Tasks::Translators
79
90
  end
80
91
  end
81
92
 
82
- def restore_newlines(translations, html)
83
- return translations unless html
84
-
85
- translations.map do |translation|
93
+ def restore_newlines(translations, html:)
94
+ restored = translations.map do |translation|
86
95
  translation.gsub(/<Z__(\d+)>/) do
87
- "\n#{' ' * ::Regexp.last_match(1).to_i}"
96
+ "\n#{" " * ::Regexp.last_match(1).to_i}"
97
+ end
98
+ end
99
+
100
+ # Need to unescape if translating HTML content
101
+ html ? restored.map { |t| CGI.unescapeHTML(t) } : restored
102
+ end
103
+
104
+ # @param texts [Array<String>] texts to translate
105
+ # @param api_key [String] Google Translate API key (required)
106
+ # @param to [String] target language code (required)
107
+ # @param format [Symbol] :text or :html (required)
108
+ # @param from [String] source language code
109
+ # @return [Array<String>] translated texts
110
+ def translate(texts:, api_key:, to:, html:, from: nil)
111
+ texts = Array(texts)
112
+
113
+ return [] if texts.empty?
114
+
115
+ raise ArgumentError, "api_key is required" if api_key.nil? || api_key.empty?
116
+
117
+ raise ArgumentError, "target language (:to) is required" if to.nil? || to.empty?
118
+
119
+ # Split into batches if needed
120
+ batches = batch_texts(texts)
121
+
122
+ batches.flat_map do |batch|
123
+ translate_batch(texts: batch, api_key:, to:, from:, html:)
124
+ end
125
+ end
126
+
127
+ def batch_texts(texts)
128
+ batches = []
129
+ current_batch = []
130
+ current_chars = 0
131
+
132
+ texts.each do |text|
133
+ text_length = text.to_s.length
134
+
135
+ # If adding this text would exceed limits, start new batch
136
+ if current_batch.size >= MAX_TEXTS_PER_REQUEST ||
137
+ (current_chars + text_length > MAX_CHARS_PER_REQUEST && current_batch.any?)
138
+ batches << current_batch
139
+ current_batch = []
140
+ current_chars = 0
88
141
  end
142
+
143
+ current_batch << text
144
+ current_chars += text_length
145
+ end
146
+
147
+ batches << current_batch if current_batch.any?
148
+ batches
149
+ end
150
+
151
+ def translate_batch(texts:, api_key:, to:, from:, html:)
152
+ uri = URI(API_ENDPOINT)
153
+ # Provide API key via query string as expected by v2 API
154
+ uri.query = URI.encode_www_form(key: api_key)
155
+
156
+ body = {
157
+ q: texts,
158
+ target: to,
159
+ format: html ? "html" : "text"
160
+ }
161
+
162
+ # Add source language if specified
163
+ if from
164
+ body[:source] = from
165
+ end
166
+
167
+ response = make_request(uri, body)
168
+ parse_response(response, texts.size)
169
+ end
170
+
171
+ def make_request(uri, body)
172
+ http = Net::HTTP.new(uri.host, uri.port)
173
+ http.use_ssl = true
174
+ http.read_timeout = 60
175
+ http.open_timeout = 30
176
+
177
+ request = Net::HTTP::Post.new(uri.request_uri)
178
+ request["Content-Type"] = "application/json"
179
+ request["Accept"] = "application/json"
180
+ request["User-Agent"] = "i18n-tasks/GoogleTranslateApi"
181
+ request.body = JSON.generate(body)
182
+
183
+ response = http.request(request)
184
+
185
+ handle_error_response(response) unless response.is_a?(Net::HTTPSuccess)
186
+ response
187
+ end
188
+
189
+ def parse_response(response, expected_count)
190
+ data = JSON.parse(response.body)
191
+
192
+ unless data["data"] && data["data"]["translations"]
193
+ raise Error, "Unexpected API response format"
194
+ end
195
+
196
+ translations = data["data"]["translations"].map do |translation|
197
+ translation["translatedText"]
198
+ end
199
+
200
+ if translations.size != expected_count
201
+ raise Error, "Expected #{expected_count} translations, got #{translations.size}"
202
+ end
203
+
204
+ translations
205
+ rescue JSON::ParserError => e
206
+ raise Error, "Failed to parse API response: #{e.message}"
207
+ end
208
+
209
+ def handle_error_response(response)
210
+ begin
211
+ error_data = JSON.parse(response.body)
212
+ error_message = error_data.dig("error", "message") || "Unknown error"
213
+ error_code = error_data.dig("error", "code")
214
+ rescue JSON::ParserError
215
+ error_message = response.body
216
+ error_code = response.code
217
+ end
218
+
219
+ status = response.code.to_i
220
+ diagnostic = "(status=#{status}, code=#{error_code})"
221
+ case status
222
+ when 400
223
+ raise Error, "Bad request #{diagnostic}: #{error_message}"
224
+ when 401, 403
225
+ raise Error, "Authentication/authorization failed #{diagnostic}: #{error_message}. " \
226
+ "Ensure the Cloud Translation API v2 is enabled, billing is active, and the key is unrestricted for this API."
227
+ when 429
228
+ raise RateLimitError, "Rate limit exceeded #{diagnostic}: #{error_message}"
229
+ when 503
230
+ raise QuotaExceededError, "Quota exceeded #{diagnostic}: #{error_message}"
231
+ else
232
+ raise Error, "API error #{diagnostic}: #{error_message}"
89
233
  end
90
234
  end
91
235
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/translators/base_translator'
4
- require 'active_support/core_ext/string/filters'
3
+ require "i18n/tasks/translators/base_translator"
4
+ require "active_support/core_ext/string/filters"
5
5
 
6
6
  module I18n::Tasks::Translators
7
7
  class OpenAiTranslator < BaseTranslator
8
+ include ::I18n::Tasks::Data::LanguageNames
9
+
8
10
  # max allowed texts per request
9
11
  BATCH_SIZE = 50
10
12
  DEFAULT_SYSTEM_PROMPT = <<~PROMPT.squish
@@ -26,7 +28,7 @@ module I18n::Tasks::Translators
26
28
 
27
29
  def initialize(*)
28
30
  begin
29
- require 'openai'
31
+ require "openai"
30
32
  rescue LoadError
31
33
  raise ::I18n::Tasks::CommandError, "Add gem 'ruby-openai' to your Gemfile to use this command"
32
34
  end
@@ -49,7 +51,7 @@ module I18n::Tasks::Translators
49
51
  end
50
52
 
51
53
  def no_results_error_message
52
- I18n.t('i18n_tasks.openai_translate.errors.no_results')
54
+ I18n.t("i18n_tasks.openai_translate.errors.no_results")
53
55
  end
54
56
 
55
57
  private
@@ -61,29 +63,39 @@ module I18n::Tasks::Translators
61
63
  def api_key
62
64
  @api_key ||= begin
63
65
  key = @i18n_tasks.translation_config[:openai_api_key]
64
- fail ::I18n::Tasks::CommandError, I18n.t('i18n_tasks.openai_translate.errors.no_api_key') if key.blank?
66
+ fail ::I18n::Tasks::CommandError, I18n.t("i18n_tasks.openai_translate.errors.no_api_key") if key.blank?
65
67
 
66
68
  key
67
69
  end
68
70
  end
69
71
 
70
72
  def model
71
- @model ||= @i18n_tasks.translation_config[:openai_model].presence || 'gpt-4o-mini'
73
+ @model ||= @i18n_tasks.translation_config[:openai_model].presence || "gpt-4o-mini"
74
+ end
75
+
76
+ def temperature
77
+ @temperature ||= @i18n_tasks.translation_config[:openai_temperature].presence || 0.0
78
+ end
79
+
80
+ def system_prompt(to_locale)
81
+ prompt = if locale_prompts[to_locale].present?
82
+ locale_prompts[to_locale]
83
+ else
84
+ @i18n_tasks.translation_config[:openai_system_prompt].presence || DEFAULT_SYSTEM_PROMPT
85
+ end
86
+
87
+ prompt.concat("\n#{JSON_FORMAT_INSTRUCTIONS_SYSTEM_PROMPT}")
72
88
  end
73
89
 
74
- def system_prompt
75
- @system_prompt ||=
76
- (@i18n_tasks.translation_config[:openai_system_prompt].presence || DEFAULT_SYSTEM_PROMPT)
77
- .concat("\n#{JSON_FORMAT_INSTRUCTIONS_SYSTEM_PROMPT}")
78
- @system_prompt
90
+ def locale_prompts
91
+ @locale_prompts ||= @i18n_tasks.translation_config[:openai_locale_prompts] || {}
79
92
  end
80
93
 
81
94
  def translate_values(list, from:, to:)
82
95
  results = []
83
96
 
84
97
  list.each_slice(BATCH_SIZE) do |batch|
85
- translations = translate(batch, from, to)
86
- result = JSON.parse(translations)
98
+ result = translate(batch, from, to)
87
99
  results << result
88
100
 
89
101
  @progress_bar.progress += result.size
@@ -97,33 +109,32 @@ module I18n::Tasks::Translators
97
109
  parameters: {
98
110
  model: model,
99
111
  messages: build_messages(values, from, to),
100
- temperature: 0.0,
101
- response_format: { type: 'json_object' }
112
+ temperature: temperature,
113
+ response_format: {type: "json_object"}
102
114
  }
103
115
  )
104
116
 
105
- translations = response.dig('choices', 0, 'message', 'content')
106
- error = response['error']
117
+ translations = response.dig("choices", 0, "message", "content")
118
+ error = response["error"]
107
119
 
108
120
  fail "AI error: #{error}" if error.present?
109
121
 
110
122
  # Extract the array from the JSON object response
111
- result = JSON.parse(translations)
112
- result['translations'].to_json
123
+ JSON.parse(translations)["translations"]
113
124
  end
114
125
 
115
126
  def build_messages(values, from, to)
116
127
  [
117
128
  {
118
- role: 'system',
119
- content: format(system_prompt, from: from, to: to)
129
+ role: "system",
130
+ content: format(system_prompt(to), from: language_name(from), to: language_name(to))
120
131
  },
121
132
  {
122
- role: 'user',
133
+ role: "user",
123
134
  content: "Translate this array: \n\n\n"
124
135
  },
125
136
  {
126
- role: 'user',
137
+ role: "user",
127
138
  content: values.to_json
128
139
  }
129
140
  ]
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/translators/base_translator'
4
- require 'active_support/core_ext/string/filters'
3
+ require "i18n/tasks/translators/base_translator"
4
+ require "active_support/core_ext/string/filters"
5
5
 
6
6
  module I18n::Tasks::Translators
7
7
  class WatsonxTranslator < BaseTranslator
@@ -40,7 +40,7 @@ module I18n::Tasks::Translators
40
40
  end
41
41
 
42
42
  def no_results_error_message
43
- I18n.t('i18n_tasks.watsonx_translate.errors.no_results')
43
+ I18n.t("i18n_tasks.watsonx_translate.errors.no_results")
44
44
  end
45
45
 
46
46
  private
@@ -52,7 +52,7 @@ module I18n::Tasks::Translators
52
52
  def api_key
53
53
  @api_key ||= begin
54
54
  key = @i18n_tasks.translation_config[:watsonx_api_key]
55
- fail ::I18n::Tasks::CommandError, I18n.t('i18n_tasks.watsonx_translate.errors.no_api_key') if key.blank?
55
+ fail ::I18n::Tasks::CommandError, I18n.t("i18n_tasks.watsonx_translate.errors.no_api_key") if key.blank?
56
56
 
57
57
  key
58
58
  end
@@ -63,7 +63,7 @@ module I18n::Tasks::Translators
63
63
  project_id = @i18n_tasks.translation_config[:watsonx_project_id]
64
64
  if project_id.blank?
65
65
  fail ::I18n::Tasks::CommandError,
66
- I18n.t('i18n_tasks.watsonx_translate.errors.no_project_id')
66
+ I18n.t("i18n_tasks.watsonx_translate.errors.no_project_id")
67
67
  end
68
68
 
69
69
  project_id
@@ -71,7 +71,7 @@ module I18n::Tasks::Translators
71
71
  end
72
72
 
73
73
  def model
74
- @model ||= @i18n_tasks.translation_config[:watsonx_model].presence || 'meta-llama/llama-3-2-90b-vision-instruct'
74
+ @model ||= @i18n_tasks.translation_config[:watsonx_model].presence || "meta-llama/llama-3-2-90b-vision-instruct"
75
75
  end
76
76
 
77
77
  def system_prompt
@@ -94,11 +94,11 @@ module I18n::Tasks::Translators
94
94
 
95
95
  def translate(values, from, to)
96
96
  prompt = [
97
- '<|eot_id|><|start_header_id|>system<|end_header_id|>',
97
+ "<|eot_id|><|start_header_id|>system<|end_header_id|>",
98
98
  format(system_prompt, from: from, to: to),
99
- '<|eot_id|><|start_header_id|>user<|end_header_id|>Translate this array:',
99
+ "<|eot_id|><|start_header_id|>user<|end_header_id|>Translate this array:",
100
100
  "<|eot_id|><|start_header_id|>user<|end_header_id|>#{values.to_json}",
101
- '<|eot_id|><|start_header_id|>assistant<|end_header_id|>'
101
+ "<|eot_id|><|start_header_id|>assistant<|end_header_id|>"
102
102
  ].join
103
103
 
104
104
  response = translator.generate_text(
@@ -111,18 +111,18 @@ module I18n::Tasks::Translators
111
111
  repetition_penalty: 1
112
112
  }
113
113
  )
114
- response.dig('results', 0, 'generated_text')
114
+ response.dig("results", 0, "generated_text")
115
115
  end
116
116
  end
117
117
  end
118
118
 
119
119
  class WatsonxClient
120
- WATSONX_BASE_URL = 'https://us-south.ml.cloud.ibm.com/ml/'
121
- IBM_CLOUD_IAM_URL = 'https://iam.cloud.ibm.com/identity/token'
120
+ WATSONX_BASE_URL = "https://us-south.ml.cloud.ibm.com/ml/"
121
+ IBM_CLOUD_IAM_URL = "https://iam.cloud.ibm.com/identity/token"
122
122
 
123
123
  def initialize(key:)
124
124
  begin
125
- require 'faraday'
125
+ require "faraday"
126
126
  rescue LoadError
127
127
  raise ::I18n::Tasks::CommandError, "Add gem 'faraday' to your Gemfile to use this command"
128
128
  end
@@ -137,7 +137,7 @@ class WatsonxClient
137
137
  end
138
138
 
139
139
  def generate_text(**opts)
140
- @http.post('v1/text/generation?version=2024-05-20', **opts).body
140
+ @http.post("v1/text/generation?version=2024-05-20", **opts).body
141
141
  end
142
142
 
143
143
  private
@@ -147,9 +147,9 @@ class WatsonxClient
147
147
  conn.use Faraday::Response::RaiseError
148
148
  conn.response :json
149
149
  conn.params = {
150
- grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
150
+ grant_type: "urn:ibm:params:oauth:grant-type:apikey",
151
151
  apikey: key
152
152
  }
153
- end.post.body['access_token']
153
+ end.post.body["access_token"]
154
154
  end
155
155
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'i18n/tasks/translators/base_translator'
3
+ require "i18n/tasks/translators/base_translator"
4
4
 
5
5
  module I18n::Tasks::Translators
6
6
  class YandexTranslator < BaseTranslator
7
7
  def initialize(*)
8
8
  begin
9
- require 'yandex-translator'
9
+ require "yandex-translator"
10
10
  rescue LoadError
11
11
  raise ::I18n::Tasks::CommandError, "Add gem 'yandex-translator' to your Gemfile to use this command"
12
12
  end
@@ -31,24 +31,24 @@ module I18n::Tasks::Translators
31
31
  end
32
32
 
33
33
  def options_for_html
34
- { format: 'html' }
34
+ {format: "html"}
35
35
  end
36
36
 
37
37
  def options_for_plain
38
- { format: 'plain' }
38
+ {format: "plain"}
39
39
  end
40
40
 
41
41
  def no_results_error_message
42
- I18n.t('i18n_tasks.yandex_translate.errors.no_results')
42
+ I18n.t("i18n_tasks.yandex_translate.errors.no_results")
43
43
  end
44
44
 
45
45
  private
46
46
 
47
47
  # Convert 'es-ES' to 'es'
48
48
  def to_yandex_compatible_locale(locale)
49
- return locale unless locale.include?('-')
49
+ return locale unless locale.include?("-")
50
50
 
51
- locale.split('-', 2).first
51
+ locale.split("-", 2).first
52
52
  end
53
53
 
54
54
  def translator
@@ -58,7 +58,7 @@ module I18n::Tasks::Translators
58
58
  def api_key
59
59
  @api_key ||= begin
60
60
  key = @i18n_tasks.translation_config[:yandex_api_key]
61
- fail ::I18n::Tasks::CommandError, I18n.t('i18n_tasks.yandex_translate.errors.no_api_key') if key.blank?
61
+ fail ::I18n::Tasks::CommandError, I18n.t("i18n_tasks.yandex_translate.errors.no_api_key") if key.blank?
62
62
 
63
63
  key
64
64
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
3
+ require "set"
4
4
 
5
5
  module I18n
6
6
  module Tasks