15 — Flutter Implementation Guide (i18n Module)
- 1. Contract recap (from source)
- 2. Integration strategy: ARB + remote overlay
- 3. Locale specifics
- 4. Cache & refresh
- 5. Notifications copy (planned wiring)
- 6. Testing
- 7. File checklist
Maps the backend i18n contract onto the Flutter app. Architecture baseline per 00-shared/11_Flutter_App_Architecture.md; state per 13_State_Management.md.
1. Contract recap (from source)
- Catalog endpoint:
GET /api/v1/i18n/messages?locale=→ flatMap<String,String>(i18n.controller.ts:13-17); fallback English (i18n.service.ts:47-49). - Locales:
en, hi, ta, te, kn, ml, gu, mr, bn(i18n.service.ts:11-21); only en+hi shipped today. - Interpolation:
{name}regex\{(\w+)\}(i18n.service.ts:42-44); missing params stay literal{name}. - Fallback chain on server: locale catalog → en → key.
2. Integration strategy: ARB + remote overlay
flowchart LR
A[flutter_localizations] --> B[Localizations app]
B --> C[ARB assets<br/>en.arb, hi.arb]
C --> D[AppStrings<br/>generated: .tr / .of context]
E[GET /i18n/messages?locale] --> F[RemoteCatalog<br/>Map<String,String>]
F --> G[MessageLookup override]
G --> B
- Ship ARB files for offline + hot-reload dev (
flutter gen-l10n). - Add a
RemoteLookupimplementingGlobalWidgetsLocalizations/AppLocalizationsDelegatefallback chain: ARB first, remote overlay second, key literal last — mirroring server order (i18n.service.ts:40: key literal). - Fetch via
CatalogCubit(13 §2): fetch on launch after locale resolve; cache per locale withshared_preferences; version hash (planned) header triggers refetch. - Locale resolution:
WidgetsBinding.instance.platformDispatcher.locale→ match againstSUPPORTED_LOCALES-equivalent constant; fallbacken.
Key → lookup mapping
Backend keys are dotted (errors.resourceNotFound). ARB keys must be
camelCase — map once in a generated KeyMap: errors.resourceNotFound →
errorsResourceNotFound. Never duplicate strings; the ARB file holds
source values and the remote catalog overrides at runtime.
Interpolation mapping
String tr(String key, [Map<String, Object?>? params]) {
var t = remote[key] ?? arb(key) ?? key; // order mirrors server
params?.forEach((k, v) => t = t.replaceAll('{$k}', v.toString()));
return t;
}
- Regex parity: server uses
\{(\w+)\}(i18n.service.ts:42); implement the same on the client so{days}behaves identically offline and online. - Missing param → literal
{days}(matches serveri18n.service.ts:43).
3. Locale specifics
| Item | Guide |
|---|---|
intl setup | Intl.defaultLocale = resolved locale; MaterialApp(localizationsDelegates: ...) |
| Plurals | leave.requested uses {days} with English "(s)" hack — replace with proper pluralization via ARB plural when the key is re-worked (planned) |
| Numbers/dates | use NumberFormat/DateFormat per locale, never hardcode digits in translation values |
| Direction | textDirection from locale tag (all current locales LTR; RTL (forward-looking)) |
| Fonts | no forced fontFamily for Indic text; system fallback (07 §8); test Devanagari/Tamil at 2.0 scale |
| IME | translation-adjacent inputs: TextCapitalization.none, autocorrect off |
4. Cache & refresh
final CatalogCubit catalogCubit; // 13 §2
// launch:
final locale = await resolveLocale(); // platform → whitelist → en
await catalogCubit.fetch(locale); // offline: cached map
// locale switch (forward-looking):
onLocaleChanged(catalogCubit.setLocale); // rebuild Localizations
- Cache key:
catalog_<locale>; TTL 24 h + version-hash refetch (planned); delta push (forward-looking).
5. Notifications copy (planned wiring)
Server notification titles are hardcoded English today
(notifications.handler.ts:16-31); planned: move to keys, e.g.
notification.passwordReset, interpolated server-side per recipient locale
(12_API_Mapping.md §3). Client just renders title/body from the
Notification document (schemas/notification.schema.ts:22-26).
6. Testing
- Golden: catalog parity test — for every key in
en.arb, assertremoteCatalogcontains it (guards missing-key drift; CI script (planned) perIMPLEMENTATION_PLAN.md:845). - Widget test:
tr('leave.requested', {'days': 3})renders both en/hi. flutter test+npm run typecheck+npm run lintbefore merge.
7. File checklist
| File | Purpose |
|---|---|
l10n.yaml | gen-l10n config, en/hi templates |
lib/l10n/app_en.arb, app_hi.arb | shipped locales |
lib/l10n/app_ta.arb … bn | (planned) as catalogs ship server-side |
lib/i18n/key_map.dart | dotted → camelCase map |
lib/i18n/catalog_cubit.dart | fetch/cache/refresh (13 §2) |
lib/i18n/remote_lookup.dart | ARB→remote→key fallback |