From ca01fd2ec0ab596f232c962fa73eb9d68500861a Mon Sep 17 00:00:00 2001 From: flemming-it Date: Fri, 17 Jul 2026 23:25:50 +0200 Subject: [PATCH] fix(l10n): consistent formal address across the German catalog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Store search, security panel, System-AI fix hint and the approvals payload hint addressed the operator informally while the rest of the app uses Sie — the panel flagged the break on every second screen. All DE strings now use the formal address, and a guard test rejects any future informal form in app_de.arb. Signed-off-by: flemming-it --- lib/l10n/app_de.arb | 8 +++--- lib/l10n/app_localizations_de.dart | 8 +++--- test/l10n_formality_test.dart | 43 ++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 test/l10n_formality_test.dart diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 5947d39..43b1427 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -285,7 +285,7 @@ "channelsEnableAutostart": "Autostart bei Anmeldung aktivieren", "channelsDisableAutostart": "Autostart deaktivieren", "storeSearchHint": "Module suchen — Name, Tagline, Tag, Fähigkeit…", - "storeAskHint": "Was brauchst du? Versuch's mit „ein Modul, das tabellarische Daten liest\" oder tippe einfach einen Begriff.", + "storeAskHint": "Was brauchen Sie? Zum Beispiel „ein Modul, das tabellarische Daten liest\" — oder geben Sie einfach einen Begriff ein.", "storeAskSubmit": "Suchen", "storeAskAi": "KI fragen", "storeAskClear": "Frage zurücksetzen", @@ -1074,7 +1074,7 @@ "approvalsPillRejected": "abgelehnt", "approvalsPillExpired": "abgelaufen", "approvalsPayloadPreview": "ZU PRÜFENDE DATEN", - "approvalsNoPayload": "Keine Daten zum Prüfen angehängt. Der system.approval-Schritt des Flows bestimmt über sein \"show:\"-Feld, was angezeigt wird — setze es, um die Daten hinter dieser Entscheidung sichtbar zu machen.", + "approvalsNoPayload": "Keine Daten zum Prüfen angehängt. Der system.approval-Schritt des Flows bestimmt über sein \"show:\"-Feld, was angezeigt wird — setzen Sie es, um die Daten hinter dieser Entscheidung sichtbar zu machen.", "approvalsNoDataConfirmTitle": "Ohne Prüfdaten freigeben?", "approvalsNoDataConfirmBody": "Dieser Flow hat bewusst keine Prüfdaten hinterlegt (kein \"show:\" am Freigabe-Schritt). Sie können trotzdem freigeben — entscheiden dann aber, ohne die Daten hinter dieser Entscheidung gesehen zu haben.", "approvalsNoDataConfirmAction": "Trotzdem freigeben", @@ -1558,7 +1558,7 @@ "settingsIntegrationsPanelTitle": "Integrationen", "settingsIntegrationsPanelBody": "Externe Tool-Server, deren Capabilities der Hub föderiert. MCP-Server bieten Tool-Endpunkte; n8n-Endpunkte stellen gehostete Workflows bereit.", "settingsSecurityPanelTitle": "Sicherheit & Zugangsdaten", - "settingsSecurityPanelBody": "Tokens, die der Hub für dich verwahrt. Liegen in ~/.chain/ mit Modus 0600; werden nie als Telemetrie versendet.", + "settingsSecurityPanelBody": "Tokens, die der Hub für Sie verwahrt. Liegen in ~/.chain/ mit Modus 0600; werden nie als Telemetrie versendet.", "settingsMaintenancePanelTitle": "Wartung", "settingsMaintenancePanelBody": "Destruktive Aktionen, abgesichert durch explizite Bestätigungs-Dialoge. Nutzen z.B. beim Re-Pilot-Testen oder Debuggen.", "tooltipMoveUp": "Nach oben", @@ -1640,7 +1640,7 @@ }, "sysAiFixDisabled": "Richte die System-KI in den Einstellungen (Cmd+,) → Bereich System-KI ein.", "sysAiFixEnvMissing": "Die Umgebungsvariable für den API-Schlüssel ist leer. Setzen Sie sie in Ihrer Shell und starten Sie den Daemon neu.", - "sysAiFixNetwork": "Der Hub erreicht den konfigurierten Endpunkt nicht. Läuft dein Anbieter?", + "sysAiFixNetwork": "Der Hub erreicht den konfigurierten Endpunkt nicht. Läuft Ihr Anbieter?", "sysAiFixHttp": "Der Anbieter hat einen Status außerhalb 2xx geliefert. Prüfen Sie Endpunkt, Modellnamen und API-Schlüssel.", "sysAiFixEmptyResponse": "Der Anbieter hat keinen Inhalt geliefert. Probiere ein anderes Modell oder formuliere den Prompt um.", "channelSwitchOk": "Aktiver Kanal auf \"{name}\" umgeschaltet. Daemon neu gestartet.", diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index d5b65a8..136c1b6 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -714,7 +714,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get storeAskHint => - 'Was brauchst du? Versuch\'s mit „ein Modul, das tabellarische Daten liest\" oder tippe einfach einen Begriff.'; + 'Was brauchen Sie? Zum Beispiel „ein Modul, das tabellarische Daten liest\" — oder geben Sie einfach einen Begriff ein.'; @override String get storeAskSubmit => 'Suchen'; @@ -1963,7 +1963,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get approvalsNoPayload => - 'Keine Daten zum Prüfen angehängt. Der system.approval-Schritt des Flows bestimmt über sein \"show:\"-Feld, was angezeigt wird — setze es, um die Daten hinter dieser Entscheidung sichtbar zu machen.'; + 'Keine Daten zum Prüfen angehängt. Der system.approval-Schritt des Flows bestimmt über sein \"show:\"-Feld, was angezeigt wird — setzen Sie es, um die Daten hinter dieser Entscheidung sichtbar zu machen.'; @override String get approvalsNoDataConfirmTitle => 'Ohne Prüfdaten freigeben?'; @@ -2697,7 +2697,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get settingsSecurityPanelBody => - 'Tokens, die der Hub für dich verwahrt. Liegen in ~/.chain/ mit Modus 0600; werden nie als Telemetrie versendet.'; + 'Tokens, die der Hub für Sie verwahrt. Liegen in ~/.chain/ mit Modus 0600; werden nie als Telemetrie versendet.'; @override String get settingsMaintenancePanelTitle => 'Wartung'; @@ -2851,7 +2851,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get sysAiFixNetwork => - 'Der Hub erreicht den konfigurierten Endpunkt nicht. Läuft dein Anbieter?'; + 'Der Hub erreicht den konfigurierten Endpunkt nicht. Läuft Ihr Anbieter?'; @override String get sysAiFixHttp => diff --git a/test/l10n_formality_test.dart b/test/l10n_formality_test.dart new file mode 100644 index 0000000..de1ae34 --- /dev/null +++ b/test/l10n_formality_test.dart @@ -0,0 +1,43 @@ +// German strings address the operator as "Sie" — never "Du". +// The usertest panel flagged Du/Sie breaks (store search, flow +// empty states) as a recurring trust issue for the regulated +// audience, so this guard scans the DE catalog for unambiguous +// second-person-singular forms. Progress messages in the first +// person ("Starte …" = the app announcing its own action) stay +// legal; only forms that address the reader are rejected. + +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('DE catalog contains no informal (Du) address', () { + final raw = File('lib/l10n/app_de.arb').readAsStringSync(); + final map = jsonDecode(raw) as Map; + // Pronouns + possessives + common 2nd-person-singular verb + // forms. Word-bounded so "individuell" or "Bedingungen" + // never match. + final informal = RegExp( + r"\b(du|dich|dir|dein|deine|deiner|deinem|deinen|deins|" + r"brauchst|tippst|willst|kannst|hast|bist|machst|siehst|" + r"klickst|wählst|öffnest|startest|versuchst|versuch's)\b", + caseSensitive: false, + ); + final offenders = []; + map.forEach((key, value) { + if (key.startsWith('@') || value is! String) return; + final m = informal.firstMatch(value); + if (m != null) { + offenders.add('$key: "${m.group(0)}" in "$value"'); + } + }); + expect( + offenders, + isEmpty, + reason: + 'German UI strings must use the formal Sie address:\n' + '${offenders.join('\n')}', + ); + }); +}