You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从.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'),
            )
          ],
        ),
      ),
    );
  }
}

核心问题分析

  1. 路径错误:getApplicationDocumentsDirectory()获取的是应用的私有文档目录,而项目的lib/l10n/属于源码目录,运行时无法直接修改(安装包内文件为只读)。
  2. 工具使用场景错误:generateFromArb是开发阶段的命令行工具,不能在Flutter运行时直接调用,它用于预生成本地化文件,而非动态更新。
  3. 版本不兼容: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 15:39:58