feat(i18n): add Asturian (asturianu) as a language
Adds Asturianu, translated in full (345 strings), plus the wiring to make a minority language work end to end — the OS language picker is an unreliable way to reach it, so the in-app choice must be explicit and remembered. - Model Asturian as the real `ast` locale (honest, international-by-design), not a fake `es-AST` region — slang rejects a 3-letter region anyway. - Flutter ships no Material/Cupertino/intl localizations for `ast`, so map the framework chrome to Spanish (`materialLocaleFor`); the app's own text stays Asturian via slang. Fixes an "Invalid locale ast" crash when the auto-backup date was formatted with intl. - Persist the picked language in the keystore (LocaleStore) and restore it at startup so it survives restarts and wins over the device locale. - Settings: add the Asturianu tile; compare the full AppLocale so `es` and `ast` aren't both marked selected. Tests: LocaleStore round-trip, locale switch + Spanish-chrome fallback, and a regression for the intl date crash under Asturian.
This commit is contained in:
parent
beb29915d8
commit
9bf5c5cfbc
10 changed files with 695 additions and 15 deletions
|
|
@ -1,23 +1,30 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../di/injector.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
import '../services/export_import_service.dart';
|
||||
import '../services/locale_store.dart';
|
||||
import 'backup_section.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// Basic settings: app language, backup (export/import) and an "about"
|
||||
/// section. [exportImport] is injectable so widget tests can pass a fake;
|
||||
/// the app resolves it lazily from the service locator.
|
||||
/// section. [exportImport] and [localeStore] are injectable so widget tests can
|
||||
/// pass fakes; the app resolves them lazily from the service locator.
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
const SettingsScreen({this.exportImport, super.key});
|
||||
const SettingsScreen({this.exportImport, this.localeStore, super.key});
|
||||
|
||||
final ExportImportService? exportImport;
|
||||
final LocaleStore? localeStore;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.t;
|
||||
final current = TranslationProvider.of(context).flutterLocale.languageCode;
|
||||
// Compare the full locale (not just the language code) so `es` and the
|
||||
// Asturian `es-AST` — which share the `es` language — are told apart.
|
||||
final current = TranslationProvider.of(context).locale;
|
||||
void pick(AppLocale locale) =>
|
||||
(localeStore ?? getIt<LocaleStore>()).setLocale(locale);
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(t.menu.settings)),
|
||||
body: ListView(
|
||||
|
|
@ -25,23 +32,28 @@ class SettingsScreen extends StatelessWidget {
|
|||
_SectionHeader(t.settings.language),
|
||||
_LanguageTile(
|
||||
label: t.settings.langEs,
|
||||
selected: current == 'es',
|
||||
onTap: () => LocaleSettings.setLocale(AppLocale.es),
|
||||
selected: current == AppLocale.es,
|
||||
onTap: () => pick(AppLocale.es),
|
||||
),
|
||||
_LanguageTile(
|
||||
label: t.settings.langAst,
|
||||
selected: current == AppLocale.ast,
|
||||
onTap: () => pick(AppLocale.ast),
|
||||
),
|
||||
_LanguageTile(
|
||||
label: t.settings.langEn,
|
||||
selected: current == 'en',
|
||||
onTap: () => LocaleSettings.setLocale(AppLocale.en),
|
||||
selected: current == AppLocale.en,
|
||||
onTap: () => pick(AppLocale.en),
|
||||
),
|
||||
_LanguageTile(
|
||||
label: t.settings.langPt,
|
||||
selected: current == 'pt',
|
||||
onTap: () => LocaleSettings.setLocale(AppLocale.pt),
|
||||
selected: current == AppLocale.pt,
|
||||
onTap: () => pick(AppLocale.pt),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.smartphone_outlined),
|
||||
title: Text(t.settings.systemLanguage),
|
||||
onTap: () => LocaleSettings.useDeviceLocale(),
|
||||
onTap: () => (localeStore ?? getIt<LocaleStore>()).useDeviceLocale(),
|
||||
),
|
||||
const Divider(),
|
||||
_SectionHeader(t.backup.title),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue