Flutter中如何像Android一样根据Locale选择对应本地化文件(非ARB格式)
Flutter中如何像Android一样根据Locale选择对应本地化文件(非ARB格式)
我之前把Android项目迁移到Flutter时,也遇到过一模一样的需求——不想局限于官方推荐的ARB文件做本地化,希望延续Android那种按Locale对应文件夹存放资源的逻辑,其实Flutter完全可以实现,下面是具体的实现思路和步骤:
一、先规划你的资源文件结构
完全模仿Android的raw文件夹结构,在Flutter的assets目录下创建对应Locale的子文件夹:
assets/ ├─ raw/ # 默认资源文件夹(对应Android的raw) │ ├─ app_data.json │ └─ tutorial_audio.mp3 ├─ raw-de/ # 德语资源文件夹(对应Android的raw-de) │ ├─ app_data.json │ └─ tutorial_audio.mp3 ├─ raw-fr/ # 法语资源文件夹(对应Android的raw-fr) │ ├─ app_data.json │ └─ tutorial_audio.mp3 └─ raw-zh-CN/ # 中文简体资源文件夹(如果需要区分地区) ├─ app_data.json └─ tutorial_audio.mp3
二、在pubspec.yaml中声明所有资源
要确保Flutter能识别这些资源,在pubspec.yaml的flutter节点下添加资源声明,用通配符可以更灵活地覆盖所有Locale文件夹:
flutter: assets: - assets/raw/ # 声明默认资源 - assets/raw-*/ # 通配符匹配所有以raw-开头的Locale文件夹
三、实现根据Locale加载资源的工具类
封装一个工具类来处理Locale匹配、资源加载和 fallback 逻辑,这样代码更复用,也更清晰:
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class LocalizedAssetLoader { final BuildContext context; LocalizedAssetLoader(this.context); // 加载文本类资源(如JSON、TXT) Future<String> loadStringAsset(String filename) async { final currentLocale = Localizations.localeOf(context); // 定义资源路径的优先级:完整Locale(如zh-CN)> 语言码(如zh)> 默认资源 final possiblePaths = [ 'assets/raw-${currentLocale.toString()}/$filename', 'assets/raw-${currentLocale.languageCode}/$filename', 'assets/raw/$filename', ]; // 依次尝试加载路径,找到第一个可用的资源 for (final path in possiblePaths) { try { return await rootBundle.loadString(path); } catch (_) { // 当前路径加载失败,继续尝试下一个 continue; } } // 所有路径都失败时抛出异常 throw Exception('无法加载本地化资源:$filename'); } // 加载二进制类资源(如音频、图片) Future<ByteData> loadBinaryAsset(String filename) async { final currentLocale = Localizations.localeOf(context); final possiblePaths = [ 'assets/raw-${currentLocale.toString()}/$filename', 'assets/raw-${currentLocale.languageCode}/$filename', 'assets/raw/$filename', ]; for (final path in possiblePaths) { try { return await rootBundle.load(path); } catch (_) { continue; } } throw Exception('无法加载二进制本地化资源:$filename'); } }
四、在业务代码中使用工具类
在有BuildContext的地方,直接实例化工具类并加载资源即可:
// 示例:加载JSON资源 Future<void> loadAppData() async { final loader = LocalizedAssetLoader(context); try { final jsonContent = await loader.loadStringAsset('app_data.json'); // 处理JSON内容 print('加载到本地化数据:$jsonContent'); } catch (e) { print('资源加载失败:$e'); } } // 示例:加载音频资源 Future<void> loadTutorialAudio() async { final loader = LocalizedAssetLoader(context); try { final audioData = await loader.loadBinaryAsset('tutorial_audio.mp3'); // 播放音频或做其他处理 } catch (e) { print('音频加载失败:$e'); } }
五、拓展:支持手动切换Locale
如果你的App支持用户手动切换语言(而不是仅跟随系统Locale),只需要修改工具类的Locale来源即可——比如用状态管理(Provider/Riverpod)保存当前选中的Locale,替换Localizations.localeOf(context):
// 假设用Riverpod管理当前Locale final currentLocaleProvider = StateProvider<Locale>((ref) => const Locale('en')); // 修改工具类的构造方法,接收Locale参数 class LocalizedAssetLoader { final Locale currentLocale; LocalizedAssetLoader(this.currentLocale); // 后续的load方法中,直接使用this.currentLocale即可 Future<String> loadStringAsset(String filename) async { final possiblePaths = [ 'assets/raw-${currentLocale.toString()}/$filename', 'assets/raw-${currentLocale.languageCode}/$filename', 'assets/raw/$filename', ]; // ... 其余逻辑不变 } } // 使用时从状态管理中获取Locale final currentLocale = ref.watch(currentLocaleProvider); final loader = LocalizedAssetLoader(currentLocale);
这种方式完全复刻了Android的资源组织逻辑,不需要依赖ARB文件,非常适合从Android迁移过来的项目,能最大程度保持资源结构的一致性。
内容来源于stack exchange




