diff --git a/config/locales/doorkeeper.de.yml b/config/locales/doorkeeper.de.yml index 4c6cf9fcb..4dd84b1de 100644 --- a/config/locales/doorkeeper.de.yml +++ b/config/locales/doorkeeper.de.yml @@ -65,12 +65,15 @@ de: authorize: 'Autorisieren' deny: 'Ablehnen' error: + go_back: 'Zurück' title: 'Ein Fehler ist aufgetreten' new: title: 'Autorisierung erforderlich' prompt: '%{client_name} Zugriff auf dein Konto erlauben?' able_to: 'Diese Anwendung kann Folgendes tun' show: + authorization_code_label: 'Autorisierungscode:' + copy_instructions: 'Kopiere diesen Code und füge ihn in der Anwendung ein.' title: 'Autorisierungscode' form_post: title: 'Dieses Formular absenden' diff --git a/config/locales/models/coinstats_item/de.yml b/config/locales/models/coinstats_item/de.yml index d56bc04aa..ee9307f5e 100644 --- a/config/locales/models/coinstats_item/de.yml +++ b/config/locales/models/coinstats_item/de.yml @@ -6,7 +6,7 @@ de: importing_wallets: Wallets werden von CoinStats importiert... checking_configuration: Wallet-Konfiguration wird geprüft... wallets_need_setup: - one: "1 Wallet muss eingerichtet werden..." + one: "%{count} Wallet muss eingerichtet werden..." other: "%{count} Wallets müssen eingerichtet werden..." processing_holdings: Bestände werden verarbeitet... calculating_balances: Salden werden berechnet... diff --git a/config/locales/models/user/de.yml b/config/locales/models/user/de.yml index 5e3474af0..96a262371 100644 --- a/config/locales/models/user/de.yml +++ b/config/locales/models/user/de.yml @@ -4,8 +4,8 @@ de: attributes: user: email: E-Mail - family: Familie - family_id: Familie + family: "%{moniker}" + family_id: "%{moniker}" first_name: Vorname last_name: Nachname password: Passwort @@ -18,4 +18,3 @@ de: cannot_deactivate_admin_with_other_users: Admin kann das Konto nicht löschen, solange noch andere Benutzer vorhanden sind Bitte zuerst alle Mitglieder löschen profile_image: invalid_file_size: Dateigröße muss kleiner als %{max_megabytes}MB sein - diff --git a/config/locales/views/binance_items/de.yml b/config/locales/views/binance_items/de.yml index 391f67c30..288ff74cd 100644 --- a/config/locales/views/binance_items/de.yml +++ b/config/locales/views/binance_items/de.yml @@ -17,6 +17,7 @@ de: one: "%{count} Konto verfügbar" other: "%{count} Konten verfügbar" select_all: Alle auswählen + historical_import: Verlaufsimport import_selected: Ausgewählte importieren cancel: Abbrechen creating: Importiere... diff --git a/config/locales/views/enable_banking_items/de.yml b/config/locales/views/enable_banking_items/de.yml index 2b5b6da58..f31d16938 100644 --- a/config/locales/views/enable_banking_items/de.yml +++ b/config/locales/views/enable_banking_items/de.yml @@ -110,6 +110,7 @@ de: creating_accounts: Konten werden angelegt … header_subtitle: Wähl die passende Kontoart für deine importierten Konten historical_data_range: 'Zeitraum der Historie:' + psd2_savings_notice: 'Hinweis: Einige regulierte französische Sparkonten (Livret A, PEL, LEP, LDDS) sind über Open Banking (PSD2) nur eingeschränkt oder gar nicht verfügbar. Wenn ein Sparkonto fehlt, kannst du es manuell hinzufügen.' sync_start_date_help: Wähl, wie weit zurück die Transaktionshistorie geholt werden soll. Verfügbar sind maximal 2 Jahre. sync_start_date_label: 'Transaktionen synchronisieren ab:' title: Enable-Banking-Konten einrichten diff --git a/config/locales/views/goals/de.yml b/config/locales/views/goals/de.yml index d97858d92..a00cdca1d 100644 --- a/config/locales/views/goals/de.yml +++ b/config/locales/views/goals/de.yml @@ -94,11 +94,13 @@ de: heading: Archiviert chips: all: Alle + aria: Ziele nach Status filtern behind: Hinter Plan completed: Erreicht no_target_date: Offen on_track: Im Plan paused: Pausiert + chips_aria: Ziele nach Status filtern empty_filtered: Kein Ziel passt. goals_section: heading: Ziele @@ -152,6 +154,9 @@ de: pause: invalid_transition: Dieses Ziel lässt sich in seinem aktuellen Zustand nicht pausieren. success: Ziel pausiert. + perform_transition!: + invalid_transition: Ziel kann nicht in diesen Status wechseln. + success: Ziel aktualisiert. reopen: invalid_transition: Dieses Ziel lässt sich in seinem aktuellen Zustand nicht wieder öffnen. success: Ziel wieder geöffnet. diff --git a/config/locales/views/indexa_capital_items/de.yml b/config/locales/views/indexa_capital_items/de.yml index 184e8e8fc..0bd9b469d 100644 --- a/config/locales/views/indexa_capital_items/de.yml +++ b/config/locales/views/indexa_capital_items/de.yml @@ -42,12 +42,12 @@ de: fields: api_token: label: "API-Token" - description: "Ihr schreibgeschützter API-Token aus dem IndexaCapital Dashboard" + description: "Dein schreibgeschützter API-Token aus dem IndexaCapital Dashboard" placeholder_new: "API-Token hier einfügen" placeholder_update: "Neuen API-Token zum Aktualisieren eingeben" username: label: "Benutzername" - description: "Ihr IndexaCapital Benutzername/E-Mail" + description: "Dein IndexaCapital Benutzername/E-Mail" placeholder_new: "Benutzername hier einfügen" placeholder_update: "Neuen Benutzernamen zum Aktualisieren eingeben" document: @@ -57,7 +57,7 @@ de: placeholder_update: "Neue Dokument-ID zum Aktualisieren eingeben" password: label: "Passwort" - description: "Ihr IndexaCapital Passwort" + description: "Dein IndexaCapital Passwort" placeholder_new: "Passwort hier einfügen" placeholder_update: "Neues Passwort zum Aktualisieren eingeben" diff --git a/config/locales/views/invitation_mailer/de.yml b/config/locales/views/invitation_mailer/de.yml index 674ac4d53..a1e6c91fd 100644 --- a/config/locales/views/invitation_mailer/de.yml +++ b/config/locales/views/invitation_mailer/de.yml @@ -3,6 +3,6 @@ de: invitation_mailer: invite_email: accept_button: Einladung annehmen - body: "%{inviter} hat dich eingeladen, der Familie %{family} auf %{product_name} beizutreten." + body: "%{inviter} hat dich eingeladen, der %{family} %{moniker} auf %{product_name} beizutreten." expiry_notice: Diese Einladung läuft in %{days} Tagen ab. greeting: Willkommen bei %{product_name}! diff --git a/config/locales/views/invitations/de.yml b/config/locales/views/invitations/de.yml index a53e48439..03fa58187 100644 --- a/config/locales/views/invitations/de.yml +++ b/config/locales/views/invitations/de.yml @@ -24,5 +24,5 @@ de: role_label: Rolle role_member: Mitglied submit: Einladung senden - subtitle: Lad jemanden in deine %{moniker} ein + subtitle: Lad jemanden in dein %{moniker}-Konto auf %{product_name} ein title: Jemanden einladen diff --git a/config/locales/views/kraken_items/de.yml b/config/locales/views/kraken_items/de.yml index c16449ed1..341d28654 100644 --- a/config/locales/views/kraken_items/de.yml +++ b/config/locales/views/kraken_items/de.yml @@ -8,7 +8,7 @@ de: description: "Mit %{name} verknüpfen" create: default_name: Kraken - success: Erfolgreich mit Kraken verbunden. Ihr Börsenkonto wird synchronisiert. + success: Erfolgreich mit Kraken verbunden. Dein Börsenkonto wird synchronisiert. update: success: Kraken-Verbindung erfolgreich aktualisiert. destroy: @@ -66,7 +66,7 @@ de: status_never: Noch nie synchronisiert delete: Löschen no_accounts_title: Keine Konten gefunden - no_accounts_message: Ihr Kraken-Börsenkonto erscheint hier nach der Synchronisierung. + no_accounts_message: Dein Kraken-Börsenkonto erscheint hier nach der Synchronisierung. setup_needed: Konto bereit zum Import setup_description: Importier diese Kraken-Verbindung als Krypto-Börsenkonto. setup_action: Konto importieren diff --git a/config/locales/views/lunchflow_items/de.yml b/config/locales/views/lunchflow_items/de.yml index 3f7a7618d..ff02c4b7f 100644 --- a/config/locales/views/lunchflow_items/de.yml +++ b/config/locales/views/lunchflow_items/de.yml @@ -20,6 +20,7 @@ de: other: "%{count} Konten mit leerem Namen können nicht verknüpft werden" link_failed: Konten konnten nicht verknüpft werden no_accounts_selected: Bitte wähle mindestens ein Konto aus + no_api_key: Der Lunch-Flow-API-Key ist nicht eingerichtet. Richte ihn in den Einstellungen ein. partial_invalid: "%{created_count} Konto/Konten verknüpft, %{already_linked_count} waren bereits verknüpft, %{invalid_count} hatten ungültige Namen" partial_success: "%{created_count} Konto/Konten erfolgreich verknüpft. %{already_linked_count} Konto/Konten waren bereits verknüpft: %{already_linked_names}" success: @@ -50,6 +51,7 @@ de: link_accounts: Ausgewählte Konten verknüpfen no_accounts_found: Keine Konten gefunden. Bitte überprüfe deine API-Key-Konfiguration. no_api_key: Lunch‑Flow-API-Schlüssel ist nicht konfiguriert. Bitte konfiguriere ihn in den Einstellungen. + no_credentials_configured: Richte zuerst deine Lunch-Flow-Zugangsdaten in den Anbietereinstellungen ein. no_name_placeholder: "(Kein Name)" title: Lunch‑Flow-Konten auswählen select_existing_account: @@ -63,6 +65,7 @@ de: no_account_specified: Kein Konto angegeben no_accounts_found: Keine Lunch‑Flow-Konten gefunden. Bitte überprüfe deine API-Key-Konfiguration. no_api_key: Lunch‑Flow-API-Schlüssel ist nicht konfiguriert. Bitte konfiguriere ihn in den Einstellungen. + no_credentials_configured: Richte zuerst deine Lunch-Flow-Zugangsdaten in den Anbietereinstellungen ein. no_name_placeholder: "(Kein Name)" title: "%{account_name} mit Lunch‑Flow verknüpfen" link_existing_account: @@ -72,6 +75,7 @@ de: lunchflow_account_already_linked: Dieses Lunch‑Flow-Konto ist bereits mit einem anderen Konto verknüpft lunchflow_account_not_found: Lunch‑Flow-Konto nicht gefunden missing_parameters: Erforderliche Parameter fehlen + no_api_key: Der Lunch-Flow-API-Key ist nicht eingerichtet. Richte ihn in den Anbietereinstellungen ein. success: "%{account_name} erfolgreich mit Lunch‑Flow verknüpft" setup_accounts: account_type_label: "Kontotyp:" diff --git a/config/locales/views/merchants/de.yml b/config/locales/views/merchants/de.yml index 8a368cce3..53a67251a 100644 --- a/config/locales/views/merchants/de.yml +++ b/config/locales/views/merchants/de.yml @@ -19,9 +19,9 @@ de: merge: Händler zusammenführen title: Händler family_title: Händler deiner %{moniker} - family_empty: Noch keine Händler der Familie vorhanden + family_empty: Noch keine Händler der %{moniker} vorhanden provider_title: Anbieter-Händler - provider_empty: Noch keine Anbieter-Händler mit dieser Familie verbunden + provider_empty: Noch keine Anbieter-Händler mit dieser %{moniker} verbunden provider_read_only: Anbieter-Händler werden von deinen verbundenen Instituten synchronisiert. Sie lassen sich hier nicht bearbeiten. provider_info: Diese Händler wurden automatisch von deinen Bankverbindungen oder der KI erkannt. Du kannst sie bearbeiten, um deine eigene Kopie zu erstellen, oder sie entfernen, um sie von deinen Transaktionen zu trennen. unlinked_title: Kürzlich getrennt diff --git a/config/locales/views/questrade_items/de.yml b/config/locales/views/questrade_items/de.yml index 0575d953e..613e91b99 100644 --- a/config/locales/views/questrade_items/de.yml +++ b/config/locales/views/questrade_items/de.yml @@ -49,7 +49,11 @@ de: loading_message: Questrade-Konten werden geladen … loading_title: Wird geladen panel: + disconnect: Trennen + disconnect_confirm: Diese Questrade-Verbindung wirklich trennen? Dadurch werden alle synchronisierten Kontodaten entfernt. + eyebrow: Questrade field_descriptions: 'Erläuterung der Felder:' + help_text: Sure nutzt diese Zugangsdaten nur, um deine Questrade-Konten, Bestände und Aktivitäten zu synchronisieren. fields: api_server: description: Dein Questrade-API-Server @@ -68,6 +72,13 @@ de: setup_instructions: 'So richtest du es ein:' status_configured_html: Eingerichtet und einsatzbereit. Im Reiter Konten verwaltest und richtest du Konten ein. status_not_configured: Nicht eingerichtet + steps: + s1_html: Öffne dein Questrade-Dashboard. + s2: Erstelle oder kopiere deine API-Zugangsdaten. + s3_html: Füge den Refresh Token und den API-Server unten ein. + s4: Speichere die Verbindung. + s5: Starte einen Sync, um Konten zu finden. + s6: Richte die gefundenen Konten im Konten-Reiter ein. step_1: Öffne dein Questrade-Dashboard und hol dir deine Zugangsdaten step_2: Trag die Zugangsdaten unten ein und klick auf Speichern step_3: Nach erfolgreicher Verbindung richtest du im Reiter „Konten“ die neuen Konten ein @@ -115,15 +126,21 @@ de: all_accounts_already_linked: Alle Questrade-Konten sind bereits verknüpft api_error: 'API-Fehler: %{message}' balance_label: 'Saldo:' + buttons: + cancel: Abbrechen + link: Verknüpfen cancel: Abbrechen cancel_button: Abbrechen configure_name_in_provider: Import nicht möglich – hinterleg den Kontonamen in Questrade connect_hint: Verbinde ein Questrade-Konto, um den automatischen Abgleich zu nutzen. description: Wähl ein Questrade-Konto, das mit diesem Konto verknüpft werden soll. Transaktionen werden automatisch abgeglichen und Dubletten entfernt. + dialog_title: Mit einem Questrade-Konto verknüpfen header: Mit Questrade verknüpfen link_account: Konto verknüpfen link_button: Dieses Konto verknüpfen + linking_label: Verknüpft wird linking_to: 'Wird verknüpft mit:' + none_available: Keine unverknüpften Questrade-Konten verfügbar. no_account_specified: Kein Konto angegeben no_accounts: Keine unverknüpften Questrade-Konten gefunden. no_accounts_found: Keine Questrade-Konten gefunden. Prüf die Konfiguration deines API-Keys. @@ -132,9 +149,11 @@ de: no_name_placeholder: "(kein Name)" settings_link: Zu den Anbietereinstellungen subtitle: Ein Questrade-Konto wählen + page_title: Questrade-Konto verknüpfen title: "%{account_name} mit Questrade verknüpfen" setup_accounts: account_type_label: 'Kontoart:' + all_linked: Alle Questrade-Konten sind bereits eingerichtet. account_types: credit_card: Kreditkarte crypto: Krypto-Konto @@ -149,6 +168,10 @@ de: all_accounts_linked: Alle deine Questrade-Konten sind bereits eingerichtet. api_error: 'API-Fehler: %{message}' balance: Saldo + buttons: + cancel: Abbrechen + create: Konten anlegen + done: Fertig cancel: Abbrechen choose_account_type: 'Wähl für jedes Questrade-Konto die passende Kontoart:' create_accounts: Konten anlegen @@ -156,6 +179,8 @@ de: creating_accounts: Konten werden angelegt … fetch_failed: Konten ließen sich nicht abrufen historical_data_range: 'Zeitraum der Historie:' + dialog_title: Deine Questrade-Konten einrichten + import_as: Importieren als import_selected: Gewählte Konten importieren instructions: Wähl die Konten, die du aus Questrade importieren willst. Du kannst mehrere auswählen. no_accounts: Aus dieser Questrade-Verbindung gibt es keine unverknüpften Konten. @@ -163,6 +188,7 @@ de: no_api_key: Der Questrade-API-Key ist nicht eingerichtet. Prüf deine Verbindungseinstellungen. select_all: Alle auswählen subtitle: Wähl die passende Kontoart für deine importierten Konten + page_title: Questrade-Konten einrichten subtype_labels: credit_card: '' crypto: '' @@ -203,6 +229,10 @@ de: sync_start_date_help: Wähl, wie weit zurück die Transaktionshistorie geholt werden soll. sync_start_date_label: 'Transaktionen synchronisieren ab:' title: Questrade-Konten einrichten + types: + depository: Giro- oder Sparkonto + investment: Investmentkonto + skip: Überspringen setup_required: go_to_provider_settings: Zu den Anbietereinstellungen not_configured_description: Leg deine Questrade-Verbindung in den Anbietereinstellungen an und komm dann hierher zurück, um Konten zu verknüpfen. diff --git a/config/locales/views/registrations/de.yml b/config/locales/views/registrations/de.yml index f60f7d6ba..8f6427d4a 100644 --- a/config/locales/views/registrations/de.yml +++ b/config/locales/views/registrations/de.yml @@ -15,7 +15,7 @@ de: success: Du hast dich erfolgreich registriert new: invitation_message: "%{inviter} hat dich eingeladen als %{role} beizutreten" - join_family_title: "%{family} beitreten" + join_family_title: "%{family} %{moniker} beitreten" role_admin: Administrator role_guest: Gast role_member: Mitglied diff --git a/config/locales/views/reports/de.yml b/config/locales/views/reports/de.yml index b1953ad03..3effc1a76 100644 --- a/config/locales/views/reports/de.yml +++ b/config/locales/views/reports/de.yml @@ -2,6 +2,7 @@ de: reports: index: + period_aria: Berichtszeitraum title: Berichte subtitle: Umfassende Einblicke in deine finanzielle Situation export: CSV exportieren diff --git a/config/locales/views/settings/de.yml b/config/locales/views/settings/de.yml index 5f8c7829c..904931df0 100644 --- a/config/locales/views/settings/de.yml +++ b/config/locales/views/settings/de.yml @@ -23,7 +23,7 @@ de: payments: show: page_title: Zahlung - subscription_subtitle: Aktualisiere dein Abonnement und deine Zahlungsdetails + subscription_subtitle: Aktualisiere deine Kreditkartendaten subscription_title: Abonnement verwalten choose_level: Stufe wählen contributions_note: Beiträge zu %{product_name} erscheinen hier. @@ -177,6 +177,10 @@ de: encryption_warning: generate: 'Einen Satz erzeugst du mit: bin/rails db:encryption:init' intro: 'Sensible Daten wie API-Keys, Anbieter-Tokens, MFA-Geheimnisse und personenbezogene Angaben liegen unverschlüsselt auf der Platte. Setze diese Schlüssel in deinen Umgebungsvariablen oder Rails-Credentials, um die Verschlüsselung einzuschalten:' + keys: + - ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY + - ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY + - ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT title: Verschlüsselungsschlüssel fehlen settings_nav: accounts_label: Konten diff --git a/config/locales/views/shared/de.yml b/config/locales/views/shared/de.yml index 54d4b0788..a672888d1 100644 --- a/config/locales/views/shared/de.yml +++ b/config/locales/views/shared/de.yml @@ -34,6 +34,7 @@ de: message: Neue Daten verfügbar refresh: Aktualisieren transaction_tabs: + aria: Transaktionstyp expense: Ausgabe income: Einnahme transfer: Überweisung diff --git a/config/locales/views/simplefin_items/de.yml b/config/locales/views/simplefin_items/de.yml index 1e71dd8c6..68685f5b4 100644 --- a/config/locales/views/simplefin_items/de.yml +++ b/config/locales/views/simplefin_items/de.yml @@ -86,7 +86,7 @@ de: all_skipped: "Alle Konten wurden übersprungen. Es wurden keine Konten erstellt." no_accounts: "Keine Konten zum Einrichten." success: - one: "Ein SimpleFIN-Konto erfolgreich erstellt! Deine Transaktionen und Positionen werden im Hintergrund importiert." + one: "%{count} SimpleFIN-Konto erfolgreich erstellt! Deine Transaktionen und Positionen werden im Hintergrund importiert." other: "%{count} SimpleFIN-Konten erfolgreich erstellt! Deine Transaktionen und Positionen werden im Hintergrund importiert." stale_accounts_processed: "Veraltete Konten: %{deleted} gelöscht, %{moved} verschoben." stale_accounts_errors: diff --git a/config/locales/views/simplefin_items/en.yml b/config/locales/views/simplefin_items/en.yml index c5c1100b3..9e187c922 100644 --- a/config/locales/views/simplefin_items/en.yml +++ b/config/locales/views/simplefin_items/en.yml @@ -30,7 +30,7 @@ en: label: "SimpleFIN Setup Token:" placeholder: "Paste your SimpleFIN setup token here..." help_text: "The token should be a long string starting with letters and numbers" - title: Update SimpleFin Connection + title: Update SimpleFIN Connection header_subtitle: Get a new setup token to reconnect your SimpleFIN account connection_needs_update: "Your SimpleFIN connection needs to be updated:" step_1_html: "Visit SimpleFIN Bridge to create a new setup token" @@ -39,9 +39,9 @@ en: update: Update cancel: Cancel setup_accounts: - title: Set Up Your SimpleFin Accounts + title: Set Up Your SimpleFIN Accounts header_subtitle: Choose the correct account types for your imported accounts - choose_account_type: "Choose the correct account type for each SimpleFin account:" + choose_account_type: "Choose the correct account type for each SimpleFIN account:" account_type_checking_savings: Checking or Savings account_type_checking_savings_desc: Regular bank accounts account_type_credit_card: Credit Card @@ -53,7 +53,7 @@ en: account_type_other_asset: Other Asset account_type_other_asset_desc: Everything else transaction_history_title: "Transaction History:" - transaction_history_description_html: "SimpleFin typically provides 60-90 days of transaction history, depending on your bank. After initial setup, new transactions will sync automatically going forward. Historical data availability varies by institution and account type." + transaction_history_description_html: "SimpleFIN typically provides 60-90 days of transaction history, depending on your bank. After initial setup, new transactions will sync automatically going forward. Historical data availability varies by institution and account type." account_type_label: "Account Type:" create_accounts: Create Accounts creating_accounts: Creating Accounts... diff --git a/config/locales/views/valuations/de.yml b/config/locales/views/valuations/de.yml index 731553d60..b936c0481 100644 --- a/config/locales/views/valuations/de.yml +++ b/config/locales/views/valuations/de.yml @@ -14,6 +14,8 @@ de: valuations: Wert value: Wert new: + amount: Betrag + submit: Kontostand hinzufügen title: Neuer Kontostand show: amount: Betrag @@ -38,8 +40,8 @@ de: title: Saldo aktualisieren update: Aktualisieren confirmation_contents: - action_set: 'Neu:' - action_update: 'Geändert:' + action_set: gesetzt + action_update: aktualisiert account_balance: Kontostand asset_value: Vermögenswert balance: Saldo @@ -53,7 +55,7 @@ de: "on": am property_value: Immobilienwert recalculate_notice: Alle späteren Transaktionen und Salden werden auf Basis dieser %{change_or_update} neu berechnet. - this_will: Damit wird der Kontowert zum + this_will: Damit wird der Kontowert %{action_verb} am to: auf to_colon: 'auf:' total_account_value: Gesamtwert des Kontos diff --git a/config/locales/views/wise_items/de.yml b/config/locales/views/wise_items/de.yml index 296839bef..e313a6f5c 100644 --- a/config/locales/views/wise_items/de.yml +++ b/config/locales/views/wise_items/de.yml @@ -84,6 +84,7 @@ de: description: Wähl das Wise-Währungsguthaben, das mit diesem Konto verknüpft werden soll. Transaktionen werden automatisch abgeglichen. link_account: Guthaben verknüpfen no_accounts_found: Keine unverknüpften Wise-Guthaben gefunden. + not_found: Wise-Guthaben nicht gefunden. title: "%{account_name} mit Wise verknüpfen" select_profiles: already_connected_label: Bereits verbunden diff --git a/test/i18n_test.rb b/test/i18n_test.rb index 51e7f4807..3b2fd8ea5 100644 --- a/test/i18n_test.rb +++ b/test/i18n_test.rb @@ -1,13 +1,62 @@ require "i18n/tasks" +require "pathname" +require "set" +require "yaml" # We're currently skipping some i18n tests to speed up development. Eventually, we'll make a dedicated # project for getting i18n working. More details on that here: # https://github.com/maybe-finance/maybe/issues/1225 class I18nTest < ActiveSupport::TestCase + GERMAN_COVERAGE_GLOBS = [ + "config/locales/breadcrumbs/*.yml", + "config/locales/doorkeeper.*.yml", + "config/locales/mailers/**/*.yml", + "config/locales/models/**/*.yml", + "config/locales/views/**/*.yml" + ] + def setup @i18n = I18n::Tasks::BaseTask.new end + def test_german_locale_files_parse + german_locale_paths.each do |path| + assert_nothing_raised do + YAML.load_file(path, aliases: true) + end + end + end + + def test_german_app_locale_covers_english_keys + missing_keys = english_app_locale_leaves.keys.filter_map do |key| + key unless translation_present?(german_app_locale_tree, key) + end + + assert_empty missing_keys.sort, "Missing German i18n keys:\n#{missing_keys.sort.join("\n")}" + end + + def test_german_app_locale_interpolations_match_english + mismatches = english_app_locale_leaves.filter_map do |key, english_value| + next unless english_value.is_a?(String) + + german_value = translation_at(german_app_locale_tree, key) + next if german_value.nil? + + expected_interpolations = interpolation_keys(english_value) + german_values = + if german_value.is_a?(Hash) + flatten_leaves(german_value).values.grep(String) + else + [ german_value ] + end + + bad_values = german_values.reject { |value| interpolation_keys(value) == expected_interpolations } + "#{key}: expected #{expected_interpolations.to_a.sort}, got #{bad_values.map { |value| interpolation_keys(value).to_a.sort }.uniq}" if bad_values.any? + end + + assert_empty mismatches, "German i18n interpolation mismatches:\n#{mismatches.join("\n")}" + end + def test_no_missing_keys skip "Skipping missing keys test" missing_keys = @i18n.missing_keys(locales: [ :en ]) @@ -38,4 +87,74 @@ class I18nTest < ActiveSupport::TestCase "Please run `i18n-tasks check-consistent-interpolations' to show them" assert_empty inconsistent_interpolations, error_message end + + private + def english_app_locale_leaves + @english_app_locale_leaves ||= flatten_leaves(english_app_locale_tree) + end + + def english_app_locale_tree + @english_app_locale_tree ||= locale_tree("en") + end + + def german_app_locale_tree + @german_app_locale_tree ||= locale_tree("de") + end + + def german_locale_paths + @german_locale_paths ||= locale_paths.select { |path| path.basename.to_s.match?(/(^|[._-])de\.yml\z/) } + end + + def locale_tree(locale) + locale_paths.each_with_object({}) do |path, tree| + data = YAML.load_file(path, aliases: true) || {} + deep_merge!(tree, data.fetch(locale, {})) + end + end + + def locale_paths + @locale_paths ||= GERMAN_COVERAGE_GLOBS.flat_map { |glob| Pathname.pwd.glob(glob) }.uniq + end + + def deep_merge!(target, source) + source.each do |key, value| + if target[key].is_a?(Hash) && value.is_a?(Hash) + deep_merge!(target[key], value) + else + target[key] = value + end + end + + target + end + + def flatten_leaves(value, prefix = [], result = {}) + case value + when Hash + value.each { |key, child| flatten_leaves(child, prefix + [ key.to_s ], result) } + else + result[prefix.join(".")] = value + end + + result + end + + def translation_at(tree, key) + key.split(".").reduce(tree) do |current, segment| + return nil unless current.is_a?(Hash) && current.key?(segment) + + current[segment] + end + end + + def translation_present?(tree, key) + value = translation_at(tree, key) + return false if value.nil? + + true + end + + def interpolation_keys(value) + value.scan(/%\{([^}]+)\}/).flatten.to_set + end end