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
54
apps/app_seeds/test/services/locale_store_test.dart
Normal file
54
apps/app_seeds/test/services/locale_store_test.dart
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:tane/i18n/strings.g.dart';
|
||||
import 'package:tane/security/secret_store.dart';
|
||||
import 'package:tane/services/locale_store.dart';
|
||||
|
||||
/// In-memory [SecretStore] for tests.
|
||||
class FakeSecretStore implements SecretStore {
|
||||
final Map<String, String> _data = {};
|
||||
@override
|
||||
Future<String?> read(String key) async => _data[key];
|
||||
@override
|
||||
Future<void> write(String key, String value) async => _data[key] = value;
|
||||
}
|
||||
|
||||
void main() {
|
||||
// setLocale/useDeviceLocale touch the Flutter locale, which needs a binding.
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
late FakeSecretStore secrets;
|
||||
late LocaleStore store;
|
||||
|
||||
setUp(() {
|
||||
secrets = FakeSecretStore();
|
||||
store = LocaleStore(secrets);
|
||||
});
|
||||
|
||||
test('no choice saved yet → follows the device (null)', () async {
|
||||
expect(await store.saved(), isNull);
|
||||
});
|
||||
|
||||
test('picking Asturian persists it and applies it', () async {
|
||||
await store.setLocale(AppLocale.ast);
|
||||
|
||||
expect(await store.saved(), AppLocale.ast);
|
||||
expect(LocaleSettings.currentLocale, AppLocale.ast);
|
||||
// Asturian is modelled as its real `ast` code so the schema stays honest;
|
||||
// Material chrome borrows Spanish elsewhere (see app.dart).
|
||||
expect(AppLocale.ast.languageCode, 'ast');
|
||||
});
|
||||
|
||||
test('choosing "system language" forgets the explicit choice', () async {
|
||||
await store.setLocale(AppLocale.es);
|
||||
expect(await store.saved(), AppLocale.es);
|
||||
|
||||
await store.useDeviceLocale();
|
||||
expect(await store.saved(), isNull);
|
||||
});
|
||||
|
||||
test('an unknown stored tag reads as "follow the device", never throws',
|
||||
() async {
|
||||
await secrets.write('tane.locale', 'zz-ZZ-removed-in-a-later-build');
|
||||
expect(await store.saved(), isNull);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue