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:
vjrj 2026-07-10 15:43:33 +02:00
parent beb29915d8
commit 9bf5c5cfbc
10 changed files with 695 additions and 15 deletions

View file

@ -21,6 +21,7 @@ import '../services/auto_backup_store.dart';
import '../services/export_import_service.dart';
import '../services/file_picker_file_service.dart';
import '../services/file_service.dart';
import '../services/locale_store.dart';
import '../services/ocr/label_text_extractor.dart';
import '../services/ocr/ocr_language.dart';
import '../services/ocr/tesseract_label_extractor.dart';
@ -61,9 +62,18 @@ Future<void> configureDependencies() async {
// contacted here (local-first the social layer only enriches).
final socialService = await SocialService.fromRootSeedHex(rootSeedHex);
// Seed the bundled species catalog (idempotent) before the UI opens.
// Seed the bundled species catalog before the UI opens but only once per
// catalog version. Parsing the ~1.5 MB catalog and scanning the species table
// on every launch was the main startup jank; the version marker skips both
// when this install already holds the current catalog.
const catalogVersionKey = 'species_catalog_version';
final speciesRepository = SpeciesRepository(database, idGen: IdGen());
await speciesRepository.seedBundled(await loadBundledSpecies());
await speciesRepository.seedBundledIfNeeded(
version: speciesCatalogVersion,
readVersion: () => secretStore.read(catalogVersionKey),
writeVersion: (v) => secretStore.write(catalogVersionKey, v),
loadSeeds: loadBundledSpecies,
);
final varietyRepository = VarietyRepository(
database,
@ -90,6 +100,7 @@ Future<void> configureDependencies() async {
..registerSingleton<FileService>(fileService)
..registerSingleton<LabelTextExtractor>(labelExtractor)
..registerSingleton<OnboardingStore>(OnboardingStore(secretStore))
..registerSingleton<LocaleStore>(LocaleStore(secretStore))
..registerSingleton<SocialService>(socialService)
..registerSingleton<SocialSettings>(SocialSettings(secretStore))
..registerSingleton<OfferOutbox>(OfferOutbox(secretStore))