From 6d6dac44f3d7139a7bcdb9dcc0f0d2fc62da32bb Mon Sep 17 00:00:00 2001 From: rsnetworkinginc Date: Sat, 22 Aug 2026 00:37:30 -0700 Subject: [PATCH] fix(i18n): remove duplicate locale keys and guard with a regression test (#2789) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(i18n): remove duplicate locale keys that shadow translations YAML resolves duplicate keys by silently letting the last occurrence win, so every duplicated key hides an earlier definition without any warning: - transactions/{en,de,nl,hu}.yml defined `transactions.merge_duplicate` twice: once as a stale flat string ("Yes, merge them") and once as the `success`/`failure` mapping the controllers actually read. The flat string is dead either way (the show view reads `transactions.show.merge_duplicate`), so drop it. - securities/fr.yml listed five providers (tiingo, eodhd, alpha_vantage, mfapi, binance_public) twice inside `securities.providers`. - settings/api_keys/hu.yml defined `settings.api_keys.created.usage_instructions_title` twice. - imports/en.yml defined `imports.create.ndjson_uploaded` twice. All removals keep the currently-winning value, so rendered output is unchanged; this only makes the files match what I18n already loads. Add a non-skipped I18nTest case that walks the raw Psych AST of every file under config/locales and fails on same-level duplicate keys, so shadowed translations can't sneak back in. Fixes #1506 Fixes #1502 * Fix linter / merge errors * fix(i18n): repair duplicate locale regression test --------- Signed-off-by: Juan José Mata Co-authored-by: rsnetworkinginc Co-authored-by: Juan José Mata Co-authored-by: Juan José Mata Co-authored-by: sure-admin --- config/locales/views/imports/en.yml | 1 - config/locales/views/securities/fr.yml | 5 -- config/locales/views/settings/api_keys/hu.yml | 1 - config/locales/views/transactions/de.yml | 1 - config/locales/views/transactions/en.yml | 1 - config/locales/views/transactions/hu.yml | 1 - config/locales/views/transactions/nl.yml | 1 - config/locales/views/transactions/uk.yml | 1 - test/i18n_test.rb | 49 +++++++++++++++++++ 9 files changed, 49 insertions(+), 12 deletions(-) diff --git a/config/locales/views/imports/en.yml b/config/locales/views/imports/en.yml index 1834927b7..be1676855 100644 --- a/config/locales/views/imports/en.yml +++ b/config/locales/views/imports/en.yml @@ -447,7 +447,6 @@ en: document_uploaded: Document uploaded successfully. document_upload_failed: We couldn't upload the document to the vector store. Please try again. invalid_ndjson_file_type: Invalid file type or format. Please upload a valid .ndjson or .json export file. - ndjson_uploaded: NDJSON file uploaded successfully. document_provider_not_configured: No vector store is configured for document uploads. show: finalize_upload: Please finalize your file upload. diff --git a/config/locales/views/securities/fr.yml b/config/locales/views/securities/fr.yml index 0afe46d6f..7591c0595 100644 --- a/config/locales/views/securities/fr.yml +++ b/config/locales/views/securities/fr.yml @@ -12,9 +12,4 @@ fr: tiingo: Tiingo twelve_data: Twelve Data yahoo_finance: Yahoo Finance - tiingo: Tiingo - eodhd: EODHD - alpha_vantage: Alpha Vantage - mfapi: MFAPI.in - binance_public: Binance moex_public: MOEX diff --git a/config/locales/views/settings/api_keys/hu.yml b/config/locales/views/settings/api_keys/hu.yml index ad0df2325..5d376caed 100644 --- a/config/locales/views/settings/api_keys/hu.yml +++ b/config/locales/views/settings/api_keys/hu.yml @@ -103,7 +103,6 @@ hu: created_label: "Létrehozva:" security_note_title: "Fontos biztonsági megjegyzés" security_note_body: "Ez az egyetlen alkalom, amikor az API-kulcsod megjelenik. Mindenképpen másold le most és tárold biztonságosan. Ha elveszíted, újat kell generálnod." - usage_instructions_title: "Az API-kulcs használata" key_name: "Név" permissions: "Jogosultságok" critical_warning_title: "⚠️ Kritikus: Mentsd el az API-kulcsodat most" diff --git a/config/locales/views/transactions/de.yml b/config/locales/views/transactions/de.yml index cacafb4f6..5c5846d32 100644 --- a/config/locales/views/transactions/de.yml +++ b/config/locales/views/transactions/de.yml @@ -97,7 +97,6 @@ de: mark_recurring_title: Wiederkehrende Transaktion potential_duplicate_title: Mögliches Duplikat erkannt potential_duplicate_description: Diese ausstehende Transaktion könnte mit der unten stehenden gebuchten Transaktion übereinstimmen. Wenn ja, führe sie zusammen, um Doppelzählung zu vermeiden. - merge_duplicate: Ja, zusammenführen keep_both: Nein, beide behalten transaction: pending: Ausstehend diff --git a/config/locales/views/transactions/en.yml b/config/locales/views/transactions/en.yml index 6f4162f0d..bd1a41606 100644 --- a/config/locales/views/transactions/en.yml +++ b/config/locales/views/transactions/en.yml @@ -127,7 +127,6 @@ en: mark_recurring_title: Recurring Transaction potential_duplicate_title: Possible duplicate detected potential_duplicate_description: This pending transaction may be the same as the posted transaction below. If so, merge them to avoid double-counting. - merge_duplicate: Yes, merge them keep_both: No, keep both split_parent_row: split_label: "Split" diff --git a/config/locales/views/transactions/hu.yml b/config/locales/views/transactions/hu.yml index 8eef07b97..69f03b4bc 100644 --- a/config/locales/views/transactions/hu.yml +++ b/config/locales/views/transactions/hu.yml @@ -123,7 +123,6 @@ hu: mark_recurring_title: Ismétlődő tranzakció potential_duplicate_title: Lehetséges duplikátum észlelve potential_duplicate_description: Ez a függőben lévő tranzakció megegyezhet az alábbi közzétett tranzakcióval. Ha igen, egyesítsd őket a kettős könyvelés elkerülése érdekében. - merge_duplicate: Igen, egyesítsd őket keep_both: Nem, mindkettőt tartsd meg split_parent_row: split_label: "Felosztva" diff --git a/config/locales/views/transactions/nl.yml b/config/locales/views/transactions/nl.yml index 6747ced06..4d2a3e168 100644 --- a/config/locales/views/transactions/nl.yml +++ b/config/locales/views/transactions/nl.yml @@ -71,7 +71,6 @@ nl: mark_recurring_title: Terugkerende Transactie potential_duplicate_title: Mogelijk duplicaat gedetecteerd potential_duplicate_description: Deze wachtende transactie kan hetzelfde zijn als de geposte transactie hieronder. Indien ja, voeg ze samen om dubbeltelling te voorkomen. - merge_duplicate: Ja, voeg ze samen keep_both: Nee, bewaar beide transaction: pending: Wachtend diff --git a/config/locales/views/transactions/uk.yml b/config/locales/views/transactions/uk.yml index 25485af7a..7f9aecf1e 100644 --- a/config/locales/views/transactions/uk.yml +++ b/config/locales/views/transactions/uk.yml @@ -122,7 +122,6 @@ uk: mark_recurring_title: Повторювана транзакція potential_duplicate_title: Виявлено можливий дублікат potential_duplicate_description: Ця очікувана транзакція може бути такою ж, як і опублікована транзакція нижче. Якщо так, об'єднайте їх, щоб уникнути подвійного підрахунку. - merge_duplicate: Так, об'єднати їх keep_both: Ні, залишити обидві split_parent_row: split_label: "Розділити" diff --git a/test/i18n_test.rb b/test/i18n_test.rb index 988089091..ce839345a 100644 --- a/test/i18n_test.rb +++ b/test/i18n_test.rb @@ -57,6 +57,26 @@ class I18nTest < ActiveSupport::TestCase assert_empty inconsistent_interpolations, error_message end + # YAML silently resolves duplicate keys by letting the last occurrence win, + # so a duplicated key shadows the earlier definition without any warning + # (see #1506 / #1502, where a stale `transactions.merge_duplicate` string + # shadowed — or was shadowed by — the `merge_duplicate.success/failure` + # mapping in several locales). Parse the raw YAML AST so duplicates can't + # sneak back in. + def test_no_duplicate_keys_within_locale_files + offenses = [] + + Dir[File.expand_path("../config/locales/**/*.yml", __dir__)].sort.each do |file| + Psych.parse_stream(File.read(file), filename: file).children.each do |doc| + offenses.concat(duplicate_key_offenses(doc.root, [], file)) + end + end + + assert_empty offenses, + "Duplicate keys found in locale files (the last occurrence silently wins):\n" \ + "#{offenses.map { |offense| " #{offense}" }.join("\n")}" + end + private def german_locale_paths @german_locale_paths ||= locale_paths.select { |path| path.basename.to_s.match?(/(^|[._-])de\.yml\z/) } @@ -65,4 +85,33 @@ class I18nTest < ActiveSupport::TestCase def locale_paths @locale_paths ||= GERMAN_COVERAGE_GLOBS.flat_map { |glob| Pathname.pwd.glob(glob) }.uniq end + + def duplicate_key_offenses(node, path, file) + offenses = [] + + case node + when Psych::Nodes::Mapping + first_definition_lines = {} + + node.children.each_slice(2) do |key_node, value_node| + if key_node.is_a?(Psych::Nodes::Scalar) + key_path = path + [ key_node.value ] + + if (first_line = first_definition_lines[key_node.value]) + offenses << "#{file}:#{key_node.start_line + 1} duplicate key `#{key_path.join(".")}` (first defined on line #{first_line})" + else + first_definition_lines[key_node.value] = key_node.start_line + 1 + end + + offenses.concat(duplicate_key_offenses(value_node, key_path, file)) + else + offenses.concat(duplicate_key_offenses(value_node, path, file)) + end + end + when Psych::Nodes::Sequence + node.children.each { |child| offenses.concat(duplicate_key_offenses(child, path, file)) } + end + + offenses + end end