| 1234567891011121314151617181920212223242526272829303132333435363738394041424344 |
- 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<String, Map<String, String>> _dictionaries = {};
- static Future<void> preloadAll() async {
- for (final code in supportedLocales) {
- final raw = await rootBundle.loadString('assets/lang/$code.json');
- _dictionaries[code] = Map<String, String>.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<String, String> 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');
- }
|