feat(studio): app-wide i18n via flutter_localizations (v0.24.0)
Replaces the Store-only DE/EN toggle with an app-wide one parked in the sidebar footer next to the theme button. Pressing it flips every translated string at once: nav labels, page titles, common buttons, the bilingual store-index content. Implementation: - Adds `flutter_localizations` + `intl` to pubspec, plus `flutter.generate: true` so `flutter gen-l10n` runs in the build pipeline. - ARB sources at `lib/l10n/app_en.arb` and `app_de.arb`. The EN file is the template; DE carries the German strings. Initial coverage: navigation, common buttons, page titles, channels / store / audit / modules / approvals headers, hub-unreachable copy, MCP + n8n panel headers + hints. Rest of the UI strings are still English-literal — those fall in incrementally as we touch each surface. - Generated `AppLocalizations` lives at `lib/l10n/app_localizations*.dart` (regenerated via `flutter gen-l10n` on every ARB edit). - `StudioAppState` gains `localeNotifier` alongside `modeNotifier`; persisted via SharedPreferences key `locale.code`. - Sidebar `_LanguageToggle` reads/writes through the notifier. The Store's per-page locale state is gone: `_locale` now reads `Localizations.localeOf(context) .languageCode`, so the bilingual store-index content follows the global setting without a second toggle. - `_NavPage.label` becomes `_NavPage.id` + `labelOf(context)`; Cmd+K palette and Sidebar both read the localized label. Out of scope this iteration: localizing the remaining ~80% of UI strings (Settings dialog labels, Store search hint, error messages). Those land incrementally — the i18n infrastructure now means each is a one-line ARB edit + one call-site swap. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
d2a6ed32e2
commit
ee83eb851a
16 changed files with 1440 additions and 67 deletions
|
|
@ -26,9 +26,15 @@ class _StorePageState extends State<StorePage> {
|
|||
String _category = '';
|
||||
String _status = '';
|
||||
bool _installedOnly = false;
|
||||
String _locale = 'en';
|
||||
late Future<List<StoreItem>> _future;
|
||||
|
||||
/// Active UI locale, read from the app-wide MaterialApp
|
||||
/// `locale` setting. Drives which side of the bilingual
|
||||
/// store-index entries renders (taglineEn vs taglineDe,
|
||||
/// descriptionEn vs descriptionDe). The sidebar's
|
||||
/// language toggle flips this for the whole app.
|
||||
String get _locale => Localizations.localeOf(context).languageCode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -97,13 +103,10 @@ class _StorePageState extends State<StorePage> {
|
|||
appBar: AppBar(
|
||||
title: const Text('Store'),
|
||||
actions: [
|
||||
// Tiny DE/EN toggle — the store-index ships bilingual
|
||||
// copy and operators in regulated environments often
|
||||
// share screens with non-English colleagues.
|
||||
_LocaleToggle(
|
||||
value: _locale,
|
||||
onChanged: (v) => setState(() => _locale = v),
|
||||
),
|
||||
// The DE/EN toggle now lives in the sidebar footer
|
||||
// and flips the entire app's locale. The Store
|
||||
// detail-sheet content follows automatically because
|
||||
// _locale reads from `Localizations.localeOf`.
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh, size: 18),
|
||||
tooltip: 'Reload',
|
||||
|
|
@ -421,29 +424,6 @@ class _ChoiceChip extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class _LocaleToggle extends StatelessWidget {
|
||||
final String value;
|
||||
final ValueChanged<String> onChanged;
|
||||
const _LocaleToggle({required this.value, required this.onChanged});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: FaiSpace.sm),
|
||||
child: Tooltip(
|
||||
message: value == 'en'
|
||||
? 'Showing English copy. Click to switch to German.'
|
||||
: 'Deutsche Beschreibung — Click for English.',
|
||||
child: TextButton.icon(
|
||||
onPressed: () => onChanged(value == 'en' ? 'de' : 'en'),
|
||||
icon: const Icon(Icons.translate, size: 16),
|
||||
label: Text(value.toUpperCase()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Responsive grid — wraps to as many columns as the viewport
|
||||
/// allows. Cards are click-to-expand; install is also a top-
|
||||
/// level action so single-click installs are still possible.
|
||||
|
|
@ -893,7 +873,9 @@ class _StoreDetailSheet extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
||||
late String _locale = widget.locale;
|
||||
/// Locale comes from MaterialApp via `Localizations.localeOf`;
|
||||
/// the per-sheet state from before is no longer needed.
|
||||
String get _locale => Localizations.localeOf(context).languageCode;
|
||||
bool _busy = false;
|
||||
String? _toast;
|
||||
Future<({String errorKind, String text, String sourceUrl})>? _docsFuture;
|
||||
|
|
@ -1085,10 +1067,9 @@ class _StoreDetailSheetState extends State<_StoreDetailSheet> {
|
|||
],
|
||||
),
|
||||
),
|
||||
_LocaleToggle(
|
||||
value: _locale,
|
||||
onChanged: (v) => setState(() => _locale = v),
|
||||
),
|
||||
// Locale follows the app-wide setting
|
||||
// from the sidebar; the detail sheet
|
||||
// does not host its own toggle anymore.
|
||||
],
|
||||
),
|
||||
const SizedBox(height: FaiSpace.lg),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue