如何从.arb文件重新生成AppLocalization?遇intl工具调用问题
动态下载翻译并重新生成AppLocalization的问题
我需要从指定URL下载翻译数据,覆盖项目中的.arb文件,然后重新生成包含新文本的AppLocalization。尝试用intl、intl_translation和arbify实现时,无法调用generateFromArb或GenerateLocalization方法,还遇到了插件版本兼容问题。
相关代码及配置
1. TranslationService 代码
import 'dart:io'; import 'package:http/http.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter/services.dart'; // import 'package:intl_translation/extract_messages.dart'; // import 'package:intl_translation/generate_localized.dart'; // import 'package:intl_translation/visitors/interpolation_visitor.dart'; // import 'package:intl_translation/visitors/message_finding_visitor.dart'; // import 'package:intl_translation/visitors/plural_gender_visitor.dart'; class TranslationService { static final TranslationService _singleton = TranslationService._internal(); factory TranslationService() { return _singleton; } TranslationService._internal(); Future<void> downloadAndBuildTranslations(String langCode, String url) async { try { final response = await get(Uri.parse(url)); final translations = jsonDecode(response.body) as Map<String, dynamic>; print('funkcja dziala'); //print(response.body); final directory = await getApplicationDocumentsDirectory(); final path = directory.path + '/lib/l10n/app_$langCode.arb'; final arbFile = File(path); // final arbFile = File( // '/data/user/0/com.zareklamy/app_flutter/lib/l10n/app_${langCode}.arb'); // final directory = await getApplicationDocumentsDirectory(); // print(directory.path); to plugin, którym mogłem sprawdzić dokładny path do aplikacji, możliwe że ten path trzeba przekleić zamiast wpisywać na sztywno if (arbFile.existsSync()) { print('${langCode} istnieje'); //sprawdza czy plik istnieje } else { print('${langCode} nie istnieje'); } final Map<String, dynamic> data = { //narazie tylko ta lista dla testów, wystarczy zamienić na dole z data, na arbFile docelowo "login_1": "Actual 1", "login_2": "Actual 2", "login_3": "Actual 3", "login_4": "Actual 4", "login_5": "Actual 5", "login_6": "Actual 6" }; final sink = arbFile.openWrite(); sink.write('{\n'); data.forEach((key, value) { //tutaj if (key != data.keys.last) { //i tutaj sink.write('"$key": "${value.toString()}",\n'); } else { sink.write('"$key": "${value.toString()}"\n'); } }); sink.write('}\n'); await sink.close(); final updatedFile = await File( '/data/user/0/com.zareklamy/app_flutter/lib/l10n/app_${langCode}.arb') .readAsString(); print('Updated file content: $updatedFile'); } catch (e) { print('Error downloading or building translations: $e'); } } Future<void> rebuildLocalization() async { final arbFiles = await rootBundle.loadString('AssetManifest.json'); final filesMap = json.decode(arbFiles) as Map<String, dynamic>; final arbFileNames = filesMap.keys .where((fileName) => fileName.contains('/l10n/') && fileName.endsWith('.arb')) .toList(); // Wygenerowanie pliku AppLocalizations await generateFromArb( arbFileNames: arbFileNames, outputDir: 'lib/l10n/', templateArbFile: 'lib/l10n/app_en.arb', outputClass: 'AppLocalizations', deferredLoading: false, header: '// This file is automatically generated by the Flutter tool. Do not modify.\n', ); } }
2. pubspec.yaml 配置
dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter provider: ^6.0.5 another_stepper: ^1.1.6 step_progress_indicator: ^1.0.2 file_picker: ^5.2.5 flutter_html: ^3.0.0-alpha.3 cupertino_icons: ^1.0.2 dot_navigation_bar: ^1.0.1+4 currency_picker: ^2.0.16 country_picker: ^2.0.20 syncfusion_flutter_charts: ^20.4.41 country_state_city_pro: ^0.0.3 dropdown_button2: ^1.9.2 flutter_cupertino_date_picker: ^1.0.26+2 flutter_local_notifications: ^13.0.0 intl: ^0.17.0 gap: ^2.0.1 flutter_switch: ^0.3.2 flutter_rating_bar: ^4.0.1 percent_indicator: ^4.2.2 top_snackbar_flutter: ^3.0.0+1 email_validator: "^2.1.16" url_launcher: ^6.1.8 flutter_svg: ^1.1.6 http: ^0.13.5 fl_country_code_picker: ^0.1.1 flutter_secure_storage: ^7.0.1 shared_preferences: ^2.1.0 path_provider: ^2.0.14 new_flutter_tapjoy: ^0.0.5 new_flutter_inbrain: ^1.0.5 flutter_pollfish: ^4.1.3 bitlabs: ^2.0.1 new_flutter_fairbid: ^2.0.2 share_plus: ^6.3.1 new_flutter_ironsource_x: ^1.2.0+3 flutter_svg_provider: ^1.0.3 fluttertoast: ^8.2.1 cs_fyber_ad: ^0.0.6 flutter_ayetstudios: ^1.0.0 flutter_inbrain: ^1.3.0+5 freezed: ^2.1.0 freezed_annotation: ^2.2.0 json_annotation: ^4.7.0 intl_translation: ^0.18.0 new_flutter_ayetstudios: ^1.1.3 dev_dependencies: flutter_test: sdk: flutter flutter_launcher_icons: ^0.11.0 flutter_native_splash: ^2.2.16 build_runner: ^2.3.3 json_serializable: ^6.4.0
3. TestUrl.dart 代码
class TestURL extends StatefulWidget { const TestURL({Key? key}) : super(key: key); @override State<TestURL> createState() => _TestURLState(); } class _TestURLState extends State<TestURL> { final translationService = TranslationService(); @override void initState() { super.initState(); _initializeMessages(); } Future<void> _initializeMessages() async { try { final languages = { 'en': 'https://www.paidwork.com/language?code=en-us', 'de': 'https://www.paidwork.com/language?code=de', 'fr': 'https://www.paidwork.com/language?code=fr', // add more languages and translation URLs here }; for (final langCode in languages.keys) { final url = languages[langCode]!; await translationService.downloadAndBuildTranslations(langCode, url); } } catch (e) { print('Error downloading or building translations: $e'); } } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(AppLocalizations.of(context).login_1), Text(AppLocalizations.of(context).login_2), Text(AppLocalizations.of(context).login_3), Text(AppLocalizations.of(context).login_4), Text(AppLocalizations.of(context).login_5), ElevatedButton( onPressed: () async { try { final langCode = 'en'; final url = 'https://www.paidwork.com/language?code=$langCode'; await translationService.downloadAndBuildTranslations( langCode, url); setState(() {}); } catch (e) { print('Error downloading or building translations: $e'); } }, child: Text('Test function'), ), ElevatedButton( onPressed: () { setState(() {}); }, child: Text('Reset state'), ) ], ), ), ); } }
核心问题分析
- 路径错误:
getApplicationDocumentsDirectory()获取的是应用的私有文档目录,而项目的lib/l10n/属于源码目录,运行时无法直接修改(安装包内文件为只读)。 - 工具使用场景错误:
generateFromArb是开发阶段的命令行工具,不能在Flutter运行时直接调用,它用于预生成本地化文件,而非动态更新。 - 版本不兼容:
intl: ^0.17.0和intl_translation: ^0.18.0版本不匹配,intl_translation 0.18.x必须搭配intl 0.18.x使用。
可行解决方案
方案1:运行时动态加载翻译(推荐)
放弃生成AppLocalization类,改为动态加载arb内容,通过自定义LocalizationsDelegate实现:
1. 定义动态本地化类
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class DynamicAppLocalizations { final Map<String, String> translations; DynamicAppLocalizations(this.translations); static DynamicAppLocalizations of(BuildContext context) { return Localizations.of<DynamicAppLocalizations>(context, DynamicAppLocalizations)!; } String translate(String key) { return translations[key] ?? key; } } class DynamicLocalizationsDelegate extends LocalizationsDelegate<DynamicAppLocalizations> { final Map<String, Map<String, String>> _translations; final Locale locale; DynamicLocalizationsDelegate(this._translations, this.locale); @override bool isSupported(Locale locale) => _translations.containsKey(locale.languageCode); @override Future<DynamicAppLocalizations> load(Locale locale) async { return DynamicAppLocalizations(_translations[locale.languageCode] ?? {}); } @override bool shouldReload(covariant LocalizationsDelegate<DynamicAppLocalizations> old) => true; }
2. 修改TranslationService下载逻辑
// 在TranslationService中添加 Future<Map<String, Map<String, String>>> downloadAllTranslations() async { final languages = { 'en': 'https://www.paidwork.com/language?code=en-us', 'de': 'https://www.paidwork.com/language?code=de', 'fr': 'https://www.paidwork.com/language?code=fr', }; final Map<String, Map<String, String>> allTranslations = {}; for (final langCode in languages.keys) { final response = await get(Uri.parse(languages[langCode]!)); final data = jsonDecode(response.body) as Map<String, dynamic>; allTranslations[langCode] = data.map((k, v) => MapEntry(k, v.toString())); } return allTranslations; }
3. 在TestUrl中使用动态本地化
class _TestURLState extends State<TestURL> { final translationService = TranslationService(); Map<String, Map<String, String>> _translations = {}; Locale _currentLocale = const Locale('en'); @override void initState() { super.initState(); _initializeMessages(); } Future<void> _initializeMessages() async { _translations = await translationService.downloadAllTranslations(); setState(() {}); } @override Widget build(BuildContext context) { return MaterialApp( locale: _currentLocale, localizationsDelegates: [ DynamicLocalizationsDelegate(_translations, _currentLocale), GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: _translations.keys.map((code) => Locale(code)).toList(), home: Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(DynamicAppLocalizations.of(context).translate('login_1')), Text(DynamicAppLocalizations.of(context).translate('login_2')), Text(DynamicAppLocalizations.of(context).translate('login_3')), Text(DynamicAppLocalizations.of(context).translate('login_4')), Text(DynamicAppLocalizations.of(context).translate('login_5')), ElevatedButton( onPressed: () async { _translations = await translationService.downloadAllTranslations(); setState(() {}); }, child: const Text('更新翻译'), ), ], ), ), ), ); } }
方案2:开发阶段批量更新(仅适用于预生成)
如果只需要在开发时下载arb并生成本地化文件,可编写独立Dart脚本:
1. 调整pubspec依赖版本
dependencies: intl: ^0.18.1 intl_translation: ^0.18.1
2. 创建更新脚本update_translations.dart
import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; import 'package:intl_translation/generate_localized.dart'; void main() async { final languages = { 'en': 'https://www.paidwork.com/language?code=en-us', 'de': 'https://www.paidwork.com/language?code=de', 'fr': 'https://www.paidwork.com/language?code=fr', }; // 下载并保存arb文件到lib/l10n/ for (final langCode in languages.keys) { final response = await http.get(Uri.parse(languages[langCode]!)); final data = jsonDecode(response.body); final file = File('lib/l10n/app_$langCode.arb'); await file.writeAsString(jsonEncode(data)); } // 生成AppLocalizations await generateFromArb( arbFileNames: languages.keys.map((code) => 'lib/l10n/app_$code.arb').toList(), outputDir: 'lib/l10n/', templateArbFile: 'lib/l10n/app_en.arb', outputClass: 'AppLocalizations', deferredLoading: false, ); }
3. 运行脚本
dart run update_translations.dart
关键注意事项
- 运行时无法修改Flutter源码目录的文件,动态加载是唯一可行的运行时更新方案。
- intl和intl_translation版本必须严格匹配,建议使用相同大版本。
- 动态加载方案如需处理复数、性别等复杂本地化场景,可结合intl的
MessageFormat实现。
内容的提问来源于stack exchange,提问作者Maciej Wawryszuk
相关产品推荐
相关产品推荐

