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

Easy Localization API翻译不稳定问题排查求助

问题排查:Flutter easy_localization 切换语言不稳定

我在Flutter项目中使用easy_localization 3.0.3和easy_localization_loader 2.0.1,通过自定义的SmartNetworkAssetLoader从API获取翻译文件,但切换语言时翻译功能时常不稳定,无法完整翻译整个应用。请帮忙排查该问题是SmartNetworkAssetLoader代码存在问题,还是库本身的问题?


自定义资源加载器:SmartNetworkAssetLoader

class SmartNetworkAssetLoader extends AssetLoader {
  final Function localeUrl;

  final Duration timeout;

  final String assetsPath;

  final Duration localCacheDuration;

  SmartNetworkAssetLoader(
      {required this.localeUrl,
        this.timeout = const Duration(seconds: 30),
        required this.assetsPath,
        this.localCacheDuration = const Duration(days: 1)});

  @override
  Future<Map<String, dynamic>> load(String localePath, Locale locale) async {
    var string = '';
     print("localPath $localePath");
     print("locale $locale");
    // 尝试加载本地已保存的翻译文件
    if (await localTranslationExists(locale.toString())) {
      string = await loadFromLocalFile(locale.toString());
    }

    // 无本地缓存或加载失败,检查网络并下载文件
    if (string == '' && await isInternetConnectionAvailable()) {
      string = await loadFromNetwork(locale.toString());
    }

    // 缓存过期或无网络,但优先使用本地缓存而非资产文件
    if (string == '' &&
        await localTranslationExists(locale.toString(),
            ignoreCacheDuration: false)) {
      string = await loadFromLocalFile(locale.toString());
    }

    // 仍无内容则加载本地资产文件
    if (string == '') {
      String localeString = locale.toString();

      // 将下划线替换为连字符
      String convertedLocale = localeString.replaceAll('_', '-');
      string = await rootBundle
          .loadString('$assetsPath/$convertedLocale.json');
    }

    // 返回解析后的JSON
    return json.decode(string);
  }

  Future<bool> localeExists(String localePath) => Future.value(true);

  Future<bool> isInternetConnectionAvailable() async {
    try{
      ConnectivityResult connectivityResult =
      await (Connectivity().checkConnectivity());
      if (connectivityResult == ConnectivityResult.none) return false;

      final result = await Future.any(
          [InternetAddress.lookup('google.com'), Future.delayed(timeout)]);

      if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
        return true;
      }

    }catch(e)
    {
     print(e);
    }
    return false;
  }

  Future<String> loadFromNetwork(String localeName) async {
    String url = localeUrl(localeName);
     print("loccaleName$localeName");
     print("url $url");
    url = '$url$localeName';
     print("final Url $url");
    try {
      final response =
      await Future.any([http.get(Uri.parse(url)), Future.delayed(timeout)]);

      if (response != null && response.statusCode == 200) {
        var content = utf8.decode(response.bodyBytes);
        log("content1234 $content");
        try {
          var jsonResponse = json.decode(content);
          if (jsonResponse != null && jsonResponse['data'] != null) {
            // 仅提取"data"字段内容
            var dataContent = jsonResponse['data'];
            // 转换回JSON字符串
            var dataContentString = json.encode(dataContent);
            print("data content: $dataContentString");
            print("localeName $localeName");
            // 保存翻译内容
            await saveTranslation(localeName, dataContentString);
            return dataContentString;
          }
        } catch (e) {
          showFalseSnackbar("$e");
          print("Error decoding JSON: $e");
        }
      }else{
        showFalseSnackbar("response null or ${response.statusCode}");
      }
    } catch (e) {
      showFalseSnackbar("$e");
      print(e.toString());
    }

    return '';
  }

  Future<bool> localTranslationExists(String localeName,
      {bool ignoreCacheDuration = false}) async {
    var translationFile = await getFileForLocale(localeName);

    if (!await translationFile.exists()) {
      return false;
    }

    // 不检查文件时效
    if (!ignoreCacheDuration) {
      var difference =
      DateTime.now().difference(await translationFile.lastModified());

      if (difference > (localCacheDuration)) {
        return false;
      }
    }

    return true;
  }

  Future<String> loadFromLocalFile(String localeName) async {
    return await (await getFileForLocale(localeName)).readAsString();
  }

  Future<void> saveTranslation(String localeName, String content) async {
    try{
      var file = File(await getFilenameForLocale(localeName));
      await file.create(recursive: true);
      return print('saved');
    }catch(e)
    {
     print("error creating file $e");
    }

  }

  Future<String> get _localPath async {
    final directory = await paths.getTemporaryDirectory();

    return directory.path;
  }

  Future<String> getFilenameForLocale(String localeName) async {
    return '${await _localPath}/translations/$localeName.json';
  }

  Future<File> getFileForLocale(String localeName) async {
    return File(await getFilenameForLocale(localeName));
  }
}

main.dart 初始化代码

runApp((EasyLocalization(
        supportedLocales:[
        Locale('en', 'US'),
        Locale('es', 'ES')
         ],
        path: 'assets/translations',
        fallbackLocale: const Locale('en', 'US'),
        assetLoader:  SmartNetworkAssetLoader(
          assetsPath: 'assets/translations',
          localCacheDuration: const Duration(days: 1),
          localeUrl: (String localeName) => Constants.appLangUrl,
          timeout: const Duration(seconds: 30),
        ),
        child: const MyApp())));

语言切换下拉按钮及相关逻辑

下拉按钮代码

DropdownButton<String>(
                              value: state.selectedValue,
                              onChanged: (newValue) async {
                                context.read<LoginBloc>().add(DropDownButtonClickedEvent(newValue!));
                                await changeLanguageHandler(context, newValue);
                              },
                              items: state.list
                                  ?.map<DropdownMenuItem<String>>((element) {
                                return DropdownMenuItem<String>(
                                  value: element,
                                  child: Text(
                                    element,
                                    style: const TextStyle(
                                      color: Colors.white,
                                      fontSize: 18,
                                      fontWeight: FontWeight.w400,
                                    ),
                                  ),
                                );
                              }).toList(),
                              icon: Image.asset(
                                "assets/images/arrowdown.png",
                                width: 16.w,
                                height: 7.h,
                              ),
                            ),

语言切换处理函数

Future<void> changeLanguageHandler( BuildContext context, String language) async {
            try {
      await LocalizationChecker.changeLanguage(context, language);
      setState(() {});
     
    } catch (error) {
      print("error $error");
    }
  }

LocalizationChecker 类

class LocalizationChecker {
  static changeLanguage(BuildContext context, String language) {
    // Locale? currentLocal = EasyLocalization.of(context)!.currentLocale;
    print("inside localization");
    if (language == 'Spanish') {
      EasyLocalization.of(context)!.setLocale(const Locale('es', 'ES'));
    } else {
      EasyLocalization.of(context)!.setLocale(const Locale('en', 'US'));
    }
  }
}

问题排查与修复建议

1. SmartNetworkAssetLoader 代码的核心问题

  • 缓存未写入内容:saveTranslation方法仅创建了文件,但没有将content写入文件,导致本地缓存始终为空。每次切换语言都需要重新请求网络或 fallback 到资产文件,直接引发不稳定。
    修复代码:
    Future<void> saveTranslation(String localeName, String content) async {
      try{
        var file = File(await getFilenameForLocale(localeName));
        await file.create(recursive: true);
        await file.writeAsString(content); // 新增:写入翻译内容
        print('saved');
      }catch(e)
      {
       print("error creating file $e");
      }
    }
    
  • 网络超时处理漏洞:使用Future.any处理超时后,若触发超时,response会是null,但代码直接访问response.statusCode会引发空指针异常,导致网络加载流程中断。
    修复:
    final result = await Future.any([http.get(Uri.parse(url)), Future.delayed(timeout, () => null)]);
    if (result is http.Response && result.statusCode == 200) {
      // 后续解析逻辑
    }
    
  • Locale格式一致性问题:Locale('en','US').toString()返回en_US,但加载本地资产时替换为en-US,缓存文件命名使用en_US,需确保API返回的locale格式与缓存命名一致,否则会导致缓存无法命中。

2. 语言切换逻辑的潜在问题

  • 异步操作未等待:setLocale是异步方法,但LocalizationChecker.changeLanguage是同步方法,未等待切换完成就调用setState,会导致UI更新不及时,出现部分未翻译内容。
    修复:
    class LocalizationChecker {
      static Future<void> changeLanguage(BuildContext context, String language) async {
        print("inside localization");
        if (language == 'Spanish') {
          await EasyLocalization.of(context)!.setLocale(const Locale('es', 'ES'));
        } else {
          await EasyLocalization.of(context)!.setLocale(const Locale('en', 'US'));
        }
      }
    }
    
  • Widget未监听Locale变化:确保所有需要翻译的Widget都使用EasyLocalization的tr方法,若使用状态管理(如Bloc),需在Locale变化时触发状态更新。

3. 库本身的可能性

easy_localization 3.0.3属于稳定版本,若上述问题修复后仍不稳定,可尝试:

  • 升级到最新兼容版本,排查是否存在已知的Locale切换Bug
  • 检查项目中是否存在多个EasyLocalization实例,或Context获取错误的情况

内容的提问来源于stack exchange,提问作者Nns_ninteyFIve

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 02:47:04