Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ Lint/SelfAssignment:
RSpec/ContextWording:
Exclude:
- 'spec/commands/tree_commands_spec.rb'
- 'spec/deepl_translate_spec.rb'
- 'spec/google_translate_spec.rb'
- 'spec/locale_tree/siblings_spec.rb'
- 'spec/relative_keys_spec.rb'

Expand All @@ -39,7 +37,6 @@ RSpec/ExampleLength:
# DisallowedExamples: works
RSpec/ExampleWording:
Exclude:
- 'spec/deepl_translate_spec.rb'
- 'spec/google_translate_spec.rb'
- 'spec/relative_keys_spec.rb'

Expand Down
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ to use the Prism Scanner without Rails support.
- Loads environment variables via `dotenv` if available. [#395](https://github.com/glebm/i18n-tasks/issues/395)
- Adds CLI command `check_prism` to try the new parser out and see the differences in key detection.
- The Prism-based scanner supports candidate_keys for Rails translations, allowing relative translations in controllers to match either the key scoped to controller and action or only to the controller.
- Translation services now catch errors and save partial results [#642](https://github.com/glebm/i18n-tasks/issues/642)

## v1.0.15

Expand Down
25 changes: 22 additions & 3 deletions lib/i18n/tasks/translators/base_translator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,22 @@ def translate_forest(forest, from)

@progress_bar = ProgressBar.create(total: pairs.flatten.size, format: "%a <%B> %e %c/%C (%p%%)")

translated = translate_pairs(pairs, to: root.key, from: from)
begin
translated = translate_pairs(pairs, to: root.key, from: from)
rescue => e
warn "Translation for locale #{root.key} failed: #{e.message}"
# If translate_pairs raised, try to salvage any partial translations
# by attempting to translate each slice individually and collecting successes.
translated = []
pairs.group_by { |k_v| @i18n_tasks.html_key? k_v[0], from }.each do |_is_html, list_slice|
translated.concat(fetch_translations(list_slice, to: root.key, from: from))
rescue => e2
warn "Partial translation failed for locale #{root.key}: #{e2.message} - leaving keys untranslated"
# leave the original list_slice untranslated
translated.concat(list_slice)
end
end

result.merge! Data::Tree::Siblings.from_flat_pairs(translated)
end
end
Expand All @@ -40,6 +55,10 @@ def translate_pairs(list, opts)
list -= reference_key_vals
result = list.group_by { |k_v| @i18n_tasks.html_key? k_v[0], opts[:from] }.map do |is_html, list_slice|
fetch_translations(list_slice, opts.merge(is_html ? options_for_html : options_for_plain))
rescue => e
warn "Translation slice failed: #{e.message} - leaving slice untranslated"
# Return the original untranslated slice so already completed translations are preserved
list_slice
end.reduce(:+) || []
result.concat(reference_key_vals)
result.sort! { |a, b| key_pos[a[0]] <=> key_pos[b[0]] }
Expand Down Expand Up @@ -128,10 +147,10 @@ def replace_interpolations(value)
# @param [String] translated
# @return [String] 'hello, <round-trippable string>' => 'hello, %{name}'
def restore_interpolations(untranslated, translated)
return translated if untranslated !~ INTERPOLATION_KEY_RE
return translated if !INTERPOLATION_KEY_RE.match?(untranslated)

values = untranslated.scan(INTERPOLATION_KEY_RE)
translated.gsub(/#{Regexp.escape(UNTRANSLATABLE_STRING)}\d+/i) do |m|
translated.gsub(/#{Regexp.escape(UNTRANSLATABLE_STRING)}\d+/io) do |m|
values[m[UNTRANSLATABLE_STRING.length..].to_i]
end
rescue => e
Expand Down
58 changes: 58 additions & 0 deletions spec/translators/base_translator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

require "spec_helper"

RSpec.describe "Base Translator" do
let(:task) { I18n::Tasks::BaseTask.new }

# Create a fake translator that raises for html slices
let(:translator_class) do
Class.new(I18n::Tasks::Translators::BaseTranslator) do
def translate_values(list, **options)
if options[:html]
raise StandardError, "html translation failure"
end

# return translated values simply by appending `-es` for testing
list.map { |v| "#{v}-es" }
end

def options_for_translate_values(from:, to:, **options)
options.merge(from: from, to: to)
end

def options_for_html
{html: true}
end

def options_for_plain
{html: false}
end

def no_results_error_message
"no results"
end
end
end

it "preserves successful translations when a subsequent slice fails" do
translator = translator_class.new(task)

list = [
["common.plain", "Hello"],
["common.html.html", "<b>Hi</b>"]
]

result = translator.send(:translate_pairs, list, from: "en", to: "es")

# Find translated plain key
plain = result.assoc("common.plain")
expect(plain).not_to be_nil
expect(plain.last).to eq("Hello-es")

# HTML slice should have been left untranslated due to simulated failure
html = result.assoc("common.html.html")
expect(html).not_to be_nil
expect(html.last).to eq("<b>Hi</b>")
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
TestCodebase.teardown
end

context "command" do
context "with default" do
let(:task) { i18n_task }

it "works" do
it "translate-missing" do
skip "temporarily disabled on JRuby due to https://github.com/jruby/jruby/issues/4802" if RUBY_ENGINE == "jruby"
skip "DEEPL_AUTH_KEY env var not set" unless ENV["DEEPL_AUTH_KEY"]
in_test_app_dir do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@
TestCodebase.teardown
end

context "command" do
context "with default" do
let(:task) { i18n_task }

it "works" do
it "translate-missing" do
skip "GOOGLE_TRANSLATE_API_KEY env var not set" unless ENV["GOOGLE_TRANSLATE_API_KEY"]
skip "GOOGLE_TRANSLATE_API_KEY env var is empty" if ENV["GOOGLE_TRANSLATE_API_KEY"].empty?
in_test_app_dir do
Expand Down Expand Up @@ -113,7 +113,7 @@
TestCodebase.teardown
end

context "command" do
context "when translate-missing" do
let(:task) { i18n_task }

it "allows google to decide proper language from locale" do
Expand Down