fix(l10n): consistent formal address across the German catalog
Some checks are pending
Security / Security check (push) Waiting to run

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 <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-07-17 23:25:50 +02:00
parent 3e1ce6e1fd
commit ca01fd2ec0
3 changed files with 51 additions and 8 deletions

View file

@ -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.",

View file

@ -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 =>

View file

@ -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<String, dynamic>;
// 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 = <String>[];
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')}',
);
});
}