mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-12 16:52:51 +00:00
Use AssetManifest.loadFromAssetBundle instead of manually parsing the deprecated AssetManifest.json. Fix a broken localization key reference (queue.cameraWithIndex → queue.itemWithIndex). Replace the standalone scripts/validate_localizations.dart with proper flutter tests (11 tests across two groups): file integrity checks (directory exists, en.json present, valid JSON structure, language code file names, deep key-completeness across all locales) and t() lookup tests (nested resolution, missing-key fallback, parameter substitution, partial-path fallback). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
159 lines
5.1 KiB
Dart
159 lines
5.1 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
class LocalizationService extends ChangeNotifier {
|
|
static LocalizationService? _instance;
|
|
static LocalizationService get instance => _instance ??= LocalizationService._();
|
|
|
|
LocalizationService._();
|
|
|
|
String _currentLanguage = 'en';
|
|
Map<String, dynamic> _strings = {};
|
|
List<String> _availableLanguages = [];
|
|
|
|
String get currentLanguage => _currentLanguage;
|
|
List<String> get availableLanguages => _availableLanguages;
|
|
|
|
Future<void> init() async {
|
|
await _discoverAvailableLanguages();
|
|
await _loadSavedLanguage();
|
|
await _loadStrings();
|
|
}
|
|
|
|
Future<void> _discoverAvailableLanguages() async {
|
|
_availableLanguages = [];
|
|
|
|
try {
|
|
final assetManifest = await AssetManifest.loadFromAssetBundle(rootBundle);
|
|
final localizationAssets = assetManifest.listAssets()
|
|
.where((path) => path.startsWith('lib/localizations/') && path.endsWith('.json'))
|
|
.toList();
|
|
|
|
for (final assetPath in localizationAssets) {
|
|
try {
|
|
final jsonString = await rootBundle.loadString(assetPath);
|
|
final parsedJson = json.decode(jsonString);
|
|
|
|
if (parsedJson is Map && parsedJson.containsKey('language')) {
|
|
final languageCode = assetPath.split('/').last.replaceAll('.json', '');
|
|
_availableLanguages.add(languageCode);
|
|
debugPrint('Found localization: $languageCode');
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Failed to load localization file $assetPath: $e');
|
|
}
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Failed to load asset manifest: $e');
|
|
_availableLanguages = ['en'];
|
|
}
|
|
|
|
debugPrint('Available languages: $_availableLanguages');
|
|
}
|
|
|
|
Future<void> _loadSavedLanguage() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final savedLanguage = prefs.getString('language_code');
|
|
|
|
if (savedLanguage != null && _availableLanguages.contains(savedLanguage)) {
|
|
_currentLanguage = savedLanguage;
|
|
} else {
|
|
// Use system default or fallback to English
|
|
final systemLocale = Platform.localeName.split('_')[0];
|
|
if (_availableLanguages.contains(systemLocale)) {
|
|
_currentLanguage = systemLocale;
|
|
} else {
|
|
_currentLanguage = 'en';
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _loadStrings() async {
|
|
try {
|
|
final String jsonString = await rootBundle.loadString('lib/localizations/$_currentLanguage.json');
|
|
_strings = json.decode(jsonString);
|
|
} catch (e) {
|
|
// Fallback to English if the language file doesn't exist
|
|
if (_currentLanguage != 'en') {
|
|
try {
|
|
final String fallbackString = await rootBundle.loadString('lib/localizations/en.json');
|
|
_strings = json.decode(fallbackString);
|
|
} catch (e) {
|
|
debugPrint('Failed to load fallback language file: $e');
|
|
_strings = {};
|
|
}
|
|
} else {
|
|
debugPrint('Failed to load language file for $_currentLanguage: $e');
|
|
_strings = {};
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> setLanguage(String? languageCode) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
if (languageCode == null) {
|
|
// System default
|
|
await prefs.remove('language_code');
|
|
final systemLocale = Platform.localeName.split('_')[0];
|
|
_currentLanguage = _availableLanguages.contains(systemLocale) ? systemLocale : 'en';
|
|
} else {
|
|
await prefs.setString('language_code', languageCode);
|
|
_currentLanguage = languageCode;
|
|
}
|
|
|
|
await _loadStrings();
|
|
notifyListeners();
|
|
}
|
|
|
|
String t(String key, {List<String>? params}) =>
|
|
lookup(_strings, key, params: params);
|
|
|
|
/// Pure lookup function used by [t] and available for testing.
|
|
static String lookup(Map<String, dynamic> strings, String key,
|
|
{List<String>? params}) {
|
|
List<String> keys = key.split('.');
|
|
dynamic current = strings;
|
|
|
|
for (String k in keys) {
|
|
if (current is Map && current.containsKey(k)) {
|
|
current = current[k];
|
|
} else {
|
|
return key;
|
|
}
|
|
}
|
|
|
|
String result = current is String ? current : key;
|
|
|
|
if (params != null) {
|
|
for (int i = 0; i < params.length; i++) {
|
|
result = result.replaceFirst('{}', params[i]);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Get display name for a specific language code
|
|
Future<String> getLanguageDisplayName(String languageCode) async {
|
|
try {
|
|
final String jsonString = await rootBundle.loadString('lib/localizations/$languageCode.json');
|
|
final Map<String, dynamic> langData = json.decode(jsonString);
|
|
return langData['language']?['name'] ?? languageCode.toUpperCase();
|
|
} catch (e) {
|
|
return languageCode.toUpperCase(); // Fallback to language code
|
|
}
|
|
}
|
|
|
|
// Helper methods for common strings
|
|
String get appTitle => t('app.title');
|
|
String get settings => t('actions.settings');
|
|
String get tagNode => t('actions.tagNode');
|
|
String get download => t('actions.download');
|
|
String get edit => t('actions.edit');
|
|
String get cancel => t('actions.cancel');
|
|
String get ok => t('actions.ok');
|
|
} |