import 'dart:convert'; import 'package:flutter/services.dart' show rootBundle; import '../theme/locale_controller.dart'; /// Hand-rolled translation dictionary loaded from flat JSON files in /// `assets/lang/`, one per language — freely hand-editable, no codegen. /// All dictionaries are preloaded at startup since they're tiny, so [t] /// is a pure synchronous lookup with no async gap on language switch. class AppStrings { AppStrings._(); static const supportedLocales = ['en', 'ms', 'zh']; static final Map> _dictionaries = {}; static Future preloadAll() async { for (final code in supportedLocales) { final raw = await rootBundle.loadString('assets/lang/$code.json'); _dictionaries[code] = Map.from(json.decode(raw)); } } /// Looks up [key] in the active locale, falling back to English, then /// to the raw key itself — a missing translation is loud, not silent. static String t(String key) { final code = LocaleController.localeCode.value; return _dictionaries[code]?[key] ?? _dictionaries['en']?[key] ?? key; } /// Like [t], but replaces `{placeholder}` tokens with [params] — for /// strings with dynamic content (counts, timestamps, error messages). static String tp(String key, Map params) { var result = t(key); for (final entry in params.entries) { result = result.replaceAll('{${entry.key}}', entry.value); } return result; } /// Display-only translation for an MPOB class name (e.g. "Ripe"). /// The underlying data key (used for DB storage, color lookups, and /// tally maps) is never touched — this only affects what's shown on /// screen. static String className(String englishClassName) => t('class_$englishClassName'); }