93 lines
3.4 KiB
Dart
93 lines
3.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
/// Single source of truth for app language (Group-2).
|
|
/// Drives BOTH systems together:
|
|
/// - ARB static words via MaterialApp.locale (AppLocalizations)
|
|
/// - wireframe field words via DynamicStrings.tr(key) backed by
|
|
/// assets/translations_dynamic/{lang}.json (free, offline, KBs).
|
|
/// Revert: remove provider wiring in main.dart; generated screens fall back
|
|
/// to English keys (DynamicStrings.tr returns readable fallback, never crash).
|
|
class LocaleProvider with ChangeNotifier {
|
|
static const _prefKey = 'app_locale_code';
|
|
static const supportedCodes = ['en', 'hi'];
|
|
|
|
Locale _locale = const Locale('en');
|
|
Locale get locale => _locale;
|
|
String get code => _locale.languageCode;
|
|
|
|
/// lang -> (key -> translated value). Embedded assets loaded once at startup.
|
|
final Map<String, Map<String, String>> _tables = {};
|
|
|
|
/// Server-supplied diff cache (future: portal-backend translations API).
|
|
/// Key format: "lang::key" -> value, persisted to SharedPreferences.
|
|
final Map<String, String> _serverDiff = {};
|
|
|
|
bool _ready = false;
|
|
bool get ready => _ready;
|
|
|
|
Future<void> init() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final saved = prefs.getString(_prefKey);
|
|
if (saved != null && supportedCodes.contains(saved)) {
|
|
_locale = Locale(saved);
|
|
}
|
|
_ready = true;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Called once from bootstrap with preloaded asset tables.
|
|
void seedTables(Map<String, Map<String, String>> tables) {
|
|
_tables.addAll(tables);
|
|
}
|
|
|
|
/// Merge server diff (new wireframe fields added after release).
|
|
/// Works offline afterwards because diff is persisted.
|
|
Future<void> updateFromServer(Map<String, String> words, String lang) async {
|
|
if (words.isEmpty) return;
|
|
final table = _tables.putIfAbsent(lang, () => {});
|
|
table.addAll(words);
|
|
for (final e in words.entries) {
|
|
_serverDiff['$lang::${e.key}'] = e.value;
|
|
}
|
|
final prefs = await SharedPreferences.getInstance();
|
|
// Persist each entry individually to survive restarts (small volume).
|
|
for (final e in words.entries) {
|
|
await prefs.setString('dyn_${lang}_${e.key}', e.value);
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> loadServerCache() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
for (final lang in supportedCodes) {
|
|
final table = _tables.putIfAbsent(lang, () => {});
|
|
for (final key in prefs.getKeys().where((k) => k.startsWith('dyn_${lang}_'))) {
|
|
final field = key.substring('dyn_${lang}_'.length);
|
|
final v = prefs.getString(key);
|
|
if (v != null) table[field] = v;
|
|
}
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> setLocale(String code) async {
|
|
if (!supportedCodes.contains(code)) return;
|
|
_locale = Locale(code);
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_prefKey, code);
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Runtime lookup for wireframe-driven strings.
|
|
/// Never throws: missing key -> English table -> pretty fallback.
|
|
String trDynamic(String key) {
|
|
final norm = key.trim().toLowerCase().replaceAll(RegExp(r'\s+'), '_');
|
|
final cur = _tables[code]?[norm];
|
|
if (cur != null && cur.isNotEmpty) return cur;
|
|
final en = _tables['en']?[norm];
|
|
if (en != null && en.isNotEmpty) return en;
|
|
return key.replaceAll('_', ' ');
|
|
}
|
|
}
|