From 22a851b7e29d2a9ebb88f1af09a9b2e4e738994c Mon Sep 17 00:00:00 2001 From: flemming-it Date: Fri, 8 May 2026 13:43:17 +0200 Subject: [PATCH] fix(studio): doctor i18n + light-mode AppBar contrast (v0.33.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two reports: 1. "In Diagnose ist viel nicht übersetzt, wie pending, chain etc." The Doctor summary strip (Modules / Approvals / Audit / Services tiles) and the modules panel still rendered English wire labels: "pending", "chain", "declared", "loaded", "empty", "attention", "No pending approvals", "No host services declared", and the matching "{n} modules · {m} capabilities" / "{n} approvals awaiting review" strings. Fix: 13 new ARB keys covering the summary tiles, modules panel, and services panel; doctor.dart now reads them via AppLocalizations. German operators no longer see English labels on Diagnose. 2. "Im Light mode ist der kontrast zur überschrift falsch, store ist weiß auf weiß." The AppBar title rendered white-on-white in light mode. Cause: `appBarTheme.titleTextStyle = textTheme.headlineSmall` passed a TextStyle built fresh from GoogleFonts.inter(...) with `color: null`. Material's "merge foregroundColor at draw time" path didn't always populate it — depended on build configuration. The colour fell through to whatever the surrounding DefaultTextStyle had, which on the light-mode AppBar was the surface colour. Fix: bake the foreground colour straight into the title style via `copyWith(color: scheme.onSurface)`. Also pin `iconTheme: IconThemeData(color: scheme.onSurface)` so actions-row icons get the same treatment defensively. Signed-off-by: flemming-it --- lib/l10n/app_de.arb | 30 ++++++++++ lib/l10n/app_en.arb | 30 ++++++++++ lib/l10n/app_localizations.dart | 96 ++++++++++++++++++++++++++++++ lib/l10n/app_localizations_de.dart | 63 ++++++++++++++++++++ lib/l10n/app_localizations_en.dart | 63 ++++++++++++++++++++ lib/main.dart | 2 +- lib/pages/doctor.dart | 42 ++++++++----- lib/theme/theme.dart | 10 +++- pubspec.yaml | 2 +- 9 files changed, 319 insertions(+), 19 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index d21e106..18c8b71 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -392,6 +392,36 @@ "@approvalsExpiresInHours": { "placeholders": { "hours": { "type": "int" } } }, "doctorTitle": "Diagnose", + "doctorSummaryModules": "Module", + "doctorSummaryApprovals": "Freigaben", + "doctorSummaryAudit": "Protokoll", + "doctorSummaryServices": "Dienste", + "doctorSummaryCapabilities": "{n} Capabilities", + "@doctorSummaryCapabilities": { "placeholders": { "n": { "type": "int" } } }, + "doctorSummaryPending": "offen", + "doctorSummaryChain": "{verified}/{total} Chain", + "@doctorSummaryChain": { + "placeholders": { + "verified": { "type": "int" }, + "total": { "type": "int" } + } + }, + "doctorSummaryDeclared": "deklariert", + "doctorModulesPanelSummary": "{n} Module · {m} Capabilities", + "@doctorModulesPanelSummary": { + "placeholders": { + "n": { "type": "int" }, + "m": { "type": "int" } + } + }, + "doctorApprovalsNone": "Keine offenen Freigaben", + "doctorApprovalsCount": "{n, plural, =1{1 Freigabe wartet auf Prüfung} other{{n} Freigaben warten auf Prüfung}}", + "@doctorApprovalsCount": { "placeholders": { "n": { "type": "int" } } }, + "doctorApprovalsAttentionPill": "wichtig", + "doctorServicesEmpty": "Keine Host-Dienste deklariert", + "doctorServicesEmptyHint": "in ~/.fai/config.yaml unter services: hinzufügen", + "doctorPillLoaded": "geladen", + "doctorPillEmpty": "leer", "doctorRecheckTooltip": "Erneut prüfen", "doctorEventLogSection": "Audit-Log", "doctorModulesApprovalsSection": "Module & Freigaben", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9958d33..5186dcf 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -393,6 +393,36 @@ "@approvalsExpiresInHours": { "placeholders": { "hours": { "type": "int" } } }, "doctorTitle": "Doctor", + "doctorSummaryModules": "Modules", + "doctorSummaryApprovals": "Approvals", + "doctorSummaryAudit": "Audit", + "doctorSummaryServices": "Services", + "doctorSummaryCapabilities": "{n} capabilities", + "@doctorSummaryCapabilities": { "placeholders": { "n": { "type": "int" } } }, + "doctorSummaryPending": "pending", + "doctorSummaryChain": "{verified}/{total} chain", + "@doctorSummaryChain": { + "placeholders": { + "verified": { "type": "int" }, + "total": { "type": "int" } + } + }, + "doctorSummaryDeclared": "declared", + "doctorModulesPanelSummary": "{n} modules · {m} capabilities", + "@doctorModulesPanelSummary": { + "placeholders": { + "n": { "type": "int" }, + "m": { "type": "int" } + } + }, + "doctorApprovalsNone": "No pending approvals", + "doctorApprovalsCount": "{n, plural, =1{1 approval awaiting review} other{{n} approvals awaiting review}}", + "@doctorApprovalsCount": { "placeholders": { "n": { "type": "int" } } }, + "doctorApprovalsAttentionPill": "attention", + "doctorServicesEmpty": "No host services declared", + "doctorServicesEmptyHint": "add to ~/.fai/config.yaml under services:", + "doctorPillLoaded": "loaded", + "doctorPillEmpty": "empty", "doctorRecheckTooltip": "Re-check", "doctorEventLogSection": "Event log", "doctorModulesApprovalsSection": "Modules & approvals", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 4e2a321..d5a1d54 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1730,6 +1730,102 @@ abstract class AppLocalizations { /// **'Doctor'** String get doctorTitle; + /// No description provided for @doctorSummaryModules. + /// + /// In en, this message translates to: + /// **'Modules'** + String get doctorSummaryModules; + + /// No description provided for @doctorSummaryApprovals. + /// + /// In en, this message translates to: + /// **'Approvals'** + String get doctorSummaryApprovals; + + /// No description provided for @doctorSummaryAudit. + /// + /// In en, this message translates to: + /// **'Audit'** + String get doctorSummaryAudit; + + /// No description provided for @doctorSummaryServices. + /// + /// In en, this message translates to: + /// **'Services'** + String get doctorSummaryServices; + + /// No description provided for @doctorSummaryCapabilities. + /// + /// In en, this message translates to: + /// **'{n} capabilities'** + String doctorSummaryCapabilities(int n); + + /// No description provided for @doctorSummaryPending. + /// + /// In en, this message translates to: + /// **'pending'** + String get doctorSummaryPending; + + /// No description provided for @doctorSummaryChain. + /// + /// In en, this message translates to: + /// **'{verified}/{total} chain'** + String doctorSummaryChain(int verified, int total); + + /// No description provided for @doctorSummaryDeclared. + /// + /// In en, this message translates to: + /// **'declared'** + String get doctorSummaryDeclared; + + /// No description provided for @doctorModulesPanelSummary. + /// + /// In en, this message translates to: + /// **'{n} modules · {m} capabilities'** + String doctorModulesPanelSummary(int n, int m); + + /// No description provided for @doctorApprovalsNone. + /// + /// In en, this message translates to: + /// **'No pending approvals'** + String get doctorApprovalsNone; + + /// No description provided for @doctorApprovalsCount. + /// + /// In en, this message translates to: + /// **'{n, plural, =1{1 approval awaiting review} other{{n} approvals awaiting review}}'** + String doctorApprovalsCount(int n); + + /// No description provided for @doctorApprovalsAttentionPill. + /// + /// In en, this message translates to: + /// **'attention'** + String get doctorApprovalsAttentionPill; + + /// No description provided for @doctorServicesEmpty. + /// + /// In en, this message translates to: + /// **'No host services declared'** + String get doctorServicesEmpty; + + /// No description provided for @doctorServicesEmptyHint. + /// + /// In en, this message translates to: + /// **'add to ~/.fai/config.yaml under services:'** + String get doctorServicesEmptyHint; + + /// No description provided for @doctorPillLoaded. + /// + /// In en, this message translates to: + /// **'loaded'** + String get doctorPillLoaded; + + /// No description provided for @doctorPillEmpty. + /// + /// In en, this message translates to: + /// **'empty'** + String get doctorPillEmpty; + /// No description provided for @doctorRecheckTooltip. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index b59bf66..234ed67 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -978,6 +978,69 @@ class AppLocalizationsDe extends AppLocalizations { @override String get doctorTitle => 'Diagnose'; + @override + String get doctorSummaryModules => 'Module'; + + @override + String get doctorSummaryApprovals => 'Freigaben'; + + @override + String get doctorSummaryAudit => 'Protokoll'; + + @override + String get doctorSummaryServices => 'Dienste'; + + @override + String doctorSummaryCapabilities(int n) { + return '$n Capabilities'; + } + + @override + String get doctorSummaryPending => 'offen'; + + @override + String doctorSummaryChain(int verified, int total) { + return '$verified/$total Chain'; + } + + @override + String get doctorSummaryDeclared => 'deklariert'; + + @override + String doctorModulesPanelSummary(int n, int m) { + return '$n Module · $m Capabilities'; + } + + @override + String get doctorApprovalsNone => 'Keine offenen Freigaben'; + + @override + String doctorApprovalsCount(int n) { + String _temp0 = intl.Intl.pluralLogic( + n, + locale: localeName, + other: '$n Freigaben warten auf Prüfung', + one: '1 Freigabe wartet auf Prüfung', + ); + return '$_temp0'; + } + + @override + String get doctorApprovalsAttentionPill => 'wichtig'; + + @override + String get doctorServicesEmpty => 'Keine Host-Dienste deklariert'; + + @override + String get doctorServicesEmptyHint => + 'in ~/.fai/config.yaml unter services: hinzufügen'; + + @override + String get doctorPillLoaded => 'geladen'; + + @override + String get doctorPillEmpty => 'leer'; + @override String get doctorRecheckTooltip => 'Erneut prüfen'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 0d94baf..7632cae 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -992,6 +992,69 @@ class AppLocalizationsEn extends AppLocalizations { @override String get doctorTitle => 'Doctor'; + @override + String get doctorSummaryModules => 'Modules'; + + @override + String get doctorSummaryApprovals => 'Approvals'; + + @override + String get doctorSummaryAudit => 'Audit'; + + @override + String get doctorSummaryServices => 'Services'; + + @override + String doctorSummaryCapabilities(int n) { + return '$n capabilities'; + } + + @override + String get doctorSummaryPending => 'pending'; + + @override + String doctorSummaryChain(int verified, int total) { + return '$verified/$total chain'; + } + + @override + String get doctorSummaryDeclared => 'declared'; + + @override + String doctorModulesPanelSummary(int n, int m) { + return '$n modules · $m capabilities'; + } + + @override + String get doctorApprovalsNone => 'No pending approvals'; + + @override + String doctorApprovalsCount(int n) { + String _temp0 = intl.Intl.pluralLogic( + n, + locale: localeName, + other: '$n approvals awaiting review', + one: '1 approval awaiting review', + ); + return '$_temp0'; + } + + @override + String get doctorApprovalsAttentionPill => 'attention'; + + @override + String get doctorServicesEmpty => 'No host services declared'; + + @override + String get doctorServicesEmptyHint => + 'add to ~/.fai/config.yaml under services:'; + + @override + String get doctorPillLoaded => 'loaded'; + + @override + String get doctorPillEmpty => 'empty'; + @override String get doctorRecheckTooltip => 'Re-check'; diff --git a/lib/main.dart b/lib/main.dart index 85d4747..73d57b4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,7 +26,7 @@ import 'widgets/widgets.dart'; /// Studio's own build version. Bump on every UI commit so the /// running app self-identifies — visible in the sidebar header /// and quick-glance proof that you're seeing the current build. -const String kStudioVersion = '0.33.0'; +const String kStudioVersion = '0.33.1'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/pages/doctor.dart b/lib/pages/doctor.dart index 89cf83d..79b2929 100644 --- a/lib/pages/doctor.dart +++ b/lib/pages/doctor.dart @@ -143,19 +143,20 @@ class _SummaryStrip extends StatelessWidget { @override Widget build(BuildContext context) { + final l = AppLocalizations.of(context)!; return Row( children: [ _StatTile( - label: 'Modules', + label: l.doctorSummaryModules, value: snapshot.moduleCount.toString(), - subtitle: '${snapshot.capabilityCount} capabilities', + subtitle: l.doctorSummaryCapabilities(snapshot.capabilityCount), icon: Icons.extension_outlined, ), const SizedBox(width: FaiSpace.md), _StatTile( - label: 'Approvals', + label: l.doctorSummaryApprovals, value: snapshot.pendingApprovals.toString(), - subtitle: 'pending', + subtitle: l.doctorSummaryPending, icon: Icons.inbox_outlined, tone: snapshot.pendingApprovals > 0 ? FaiPillTone.warning @@ -163,10 +164,12 @@ class _SummaryStrip extends StatelessWidget { ), const SizedBox(width: FaiSpace.md), _StatTile( - label: 'Audit', + label: l.doctorSummaryAudit, value: snapshot.chainHealthy ? '✓' : '⚠', - subtitle: - '${snapshot.eventChainVerified}/${snapshot.eventChainTotal} chain', + subtitle: l.doctorSummaryChain( + snapshot.eventChainVerified, + snapshot.eventChainTotal, + ), icon: Icons.shield_outlined, tone: snapshot.chainHealthy ? FaiPillTone.success @@ -174,9 +177,9 @@ class _SummaryStrip extends StatelessWidget { ), const SizedBox(width: FaiSpace.md), _StatTile( - label: 'Services', + label: l.doctorSummaryServices, value: snapshot.services.length.toString(), - subtitle: 'declared', + subtitle: l.doctorSummaryDeclared, icon: Icons.dns_outlined, ), ], @@ -639,6 +642,7 @@ class _ModulesPanel extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); + final l = AppLocalizations.of(context)!; return FaiCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -652,12 +656,17 @@ class _ModulesPanel extends StatelessWidget { ), const SizedBox(width: FaiSpace.sm), Text( - '${snapshot.moduleCount} modules · ${snapshot.capabilityCount} capabilities', + l.doctorModulesPanelSummary( + snapshot.moduleCount, + snapshot.capabilityCount, + ), style: theme.textTheme.bodyMedium, ), const Spacer(), FaiPill( - label: snapshot.moduleCount > 0 ? 'loaded' : 'empty', + label: snapshot.moduleCount > 0 + ? l.doctorPillLoaded + : l.doctorPillEmpty, tone: snapshot.moduleCount > 0 ? FaiPillTone.success : FaiPillTone.neutral, @@ -675,14 +684,14 @@ class _ModulesPanel extends StatelessWidget { const SizedBox(width: FaiSpace.sm), Text( snapshot.pendingApprovals == 0 - ? 'No pending approvals' - : '${snapshot.pendingApprovals} approval${snapshot.pendingApprovals == 1 ? '' : 's'} awaiting review', + ? l.doctorApprovalsNone + : l.doctorApprovalsCount(snapshot.pendingApprovals), style: theme.textTheme.bodyMedium, ), const Spacer(), if (snapshot.pendingApprovals > 0) FaiPill( - label: 'attention', + label: l.doctorApprovalsAttentionPill, tone: FaiPillTone.warning, ), ], @@ -701,6 +710,7 @@ class _ServicesPanel extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); + final l = AppLocalizations.of(context)!; if (snapshot.services.isEmpty) { return FaiCard( child: Row( @@ -712,14 +722,14 @@ class _ServicesPanel extends StatelessWidget { ), const SizedBox(width: FaiSpace.sm), Text( - 'No host services declared', + l.doctorServicesEmpty, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), const Spacer(), Text( - 'add to ~/.fai/config.yaml under services:', + l.doctorServicesEmptyHint, style: FaiTheme.mono( size: 11, color: theme.colorScheme.onSurfaceVariant, diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index 506f1d6..ed68f2b 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -154,10 +154,18 @@ class FaiTheme { appBarTheme: AppBarTheme( backgroundColor: scheme.surface, foregroundColor: scheme.onSurface, + iconTheme: IconThemeData(color: scheme.onSurface), elevation: 0, scrolledUnderElevation: 0, centerTitle: false, - titleTextStyle: textTheme.headlineSmall, + // Bake the foreground colour straight into the title + // style. `headlineSmall` is built from GoogleFonts.inter + // with no color slot, and Material's "merge foreground + // colour at draw time" path leaves it null in some + // light-mode builds — the title rendered white-on-white. + titleTextStyle: textTheme.headlineSmall?.copyWith( + color: scheme.onSurface, + ), toolbarHeight: 64, ), cardTheme: CardThemeData( diff --git a/pubspec.yaml b/pubspec.yaml index 1973096..88aada3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: fai_studio description: "F∆I Studio — desktop GUI for the F∆I hub" publish_to: 'none' -version: 0.33.0 +version: 0.33.1 environment: sdk: ^3.11.0-200.1.beta