Flutter 国际化与本地化实战指南 Flutter 国际化与本地化实战指南一、国际化概述国际化Internationalization简称i18n是指应用程序能够支持多种语言和地区的能力。本地化Localization简称l10n则是为特定地区或语言调整应用程序的过程。Flutter 提供了完整的国际化支持主要通过以下包实现flutter_localizations- 官方本地化支持intl- 国际化工具库二、配置国际化环境2.1 添加依赖dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter intl: ^0.18.1 dev_dependencies: flutter_test: sdk: flutter2.2 配置 MaterialAppimport package:flutter_localizations/flutter_localizations.dart; import package:intl/intl.dart; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); override Widget build(BuildContext context) { return MaterialApp( title: Flutter i18n Demo, localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [ Locale(en, US), // 英语 Locale(zh, CN), // 中文简体 Locale(ja, JP), // 日语 Locale(ko, KR), // 韩语 ], home: const HomePage(), ); } }三、创建国际化资源文件3.1 创建 arb 文件创建lib/l10n目录并添加以下文件app_en.arb- 英语资源{ locale: en, helloWorld: Hello World, welcome: Welcome to our app, greeting: Hello {name}, counter: {count, plural, 0{No items} 1{One item} other{{count} items}}, date: {date, date, short}, time: {time, time, short} }app_zh.arb- 中文资源{ locale: zh, helloWorld: 你好世界, welcome: 欢迎使用我们的应用, greeting: 你好 {name}, counter: {count, plural, 0{没有项目} 1{一个项目} other{{count} 个项目}}, date: {date, date, short}, time: {time, time, short} }app_ja.arb- 日语资源{ locale: ja, helloWorld: こんにちは世界, welcome: アプリへようこそ, greeting: こんにちは {name}, counter: {count, plural, 0{項目なし} 1{1つの項目} other{{count} 項目}}, date: {date, date, short}, time: {time, time, short} }3.2 配置 pubspec.yamlflutter: generate: true assets: - lib/l10n/3.3 生成代码运行以下命令生成国际化代码flutter pub get flutter pub run intl_utils:generate四、使用国际化字符串4.1 基础用法import package:flutter_gen/gen_l10n/app_localizations.dart; class HomePage extends StatelessWidget { const HomePage({super.key}); override Widget build(BuildContext context) { final l10n AppLocalizations.of(context)!; return Scaffold( appBar: AppBar(title: Text(l10n.helloWorld)), body: Center( child: Column( children: [ Text(l10n.welcome), Text(l10n.greeting(name: John)), ], ), ), ); } }4.2 复数处理Text(l10n.counter(count: 0)); // 没有项目 Text(l10n.counter(count: 1)); // 一个项目 Text(l10n.counter(count: 5)); // 5 个项目4.3 日期和时间格式化Text(l10n.date(date: DateTime.now())); Text(l10n.time(time: DateTime.now()));五、动态切换语言5.1 创建语言状态管理class LocaleProvider extends ChangeNotifier { Locale _locale const Locale(en); Locale get locale _locale; void setLocale(Locale locale) { _locale locale; notifyListeners(); } }5.2 使用 Providervoid main() { runApp( ChangeNotifierProvider( create: (context) LocaleProvider(), child: const MyApp(), ), ); } class MyApp extends StatelessWidget { const MyApp({super.key}); override Widget build(BuildContext context) { return ConsumerLocaleProvider( builder: (context, provider, child) { return MaterialApp( locale: provider.locale, localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: const [ Locale(en, US), Locale(zh, CN), Locale(ja, JP), ], home: const HomePage(), ); }, ); } }5.3 创建语言选择器class LanguageSelector extends StatelessWidget { const LanguageSelector({super.key}); override Widget build(BuildContext context) { final provider Provider.ofLocaleProvider(context); return DropdownButtonLocale( value: provider.locale, items: const [ DropdownMenuItem(value: Locale(en, US), child: Text(English)), DropdownMenuItem(value: Locale(zh, CN), child: Text(中文)), DropdownMenuItem(value: Locale(ja, JP), child: Text(日本語)), ], onChanged: (locale) { if (locale ! null) { provider.setLocale(locale); } }, ); } }六、处理 RTL 语言6.1 配置 RTL 支持MaterialApp( ... supportedLocales: const [ Locale(ar, SA), // 阿拉伯语 Locale(he, IL), // 希伯来语 ], );6.2 使用 Directionality WidgetDirectionality( textDirection: TextDirection.rtl, child: Text(مرحبا بالعالم), // 阿拉伯语 );6.3 自适应布局Row( textDirection: Directionality.of(context), children: [ Text(l10n.name), Text(l10n.value), ], );七、日期时间格式化7.1 基础格式化import package:intl/intl.dart; final now DateTime.now(); // 格式化日期 print(DateFormat.yMd().format(now)); // 12/31/2023 print(DateFormat(yyyy-MM-dd).format(now)); // 2023-12-31 // 格式化时间 print(DateFormat.jm().format(now)); // 11:59 PM print(DateFormat.Hms().format(now)); // 23:59:59 // 完整日期时间 print(DateFormat.yMMMd().format(now)); // Dec 31, 2023 print(DateFormat.yMMMEd().format(now)); // Sun, Dec 31, 20237.2 本地化日期时间// 根据当前语言环境格式化 print(DateFormat.yMd(Localizations.localeOf(context).languageCode).format(now));八、数字格式化8.1 基础用法import package:intl/intl.dart; final number 1234567.89; print(NumberFormat().format(number)); // 1,234,567.89 print(NumberFormat.currency().format(number)); // $1,234,567.89 print(NumberFormat.percent().format(0.75)); // 75%8.2 本地化数字final format NumberFormat.decimalPattern(Localizations.localeOf(context).languageCode); print(format.format(number));九、复数规则9.1 基础复数{ items: {count, plural, 0{no items} 1{one item} other{{count} items}} }9.2 复杂复数规则{ apples: {count, plural, zero{no apples} one{one apple} two{two apples} few{few apples} many{many apples} other{{count} apples}} }十、处理地区差异10.1 地区特定格式// 美国格式 print(DateFormat.yMd(en_US).format(now)); // 12/31/2023 // 欧洲格式 print(DateFormat.yMd(de_DE).format(now)); // 31.12.2023 // 中国格式 print(DateFormat.yMd(zh_CN).format(now)); // 2023/12/3110.2 货币格式// 美元 print(NumberFormat.currency(locale: en_US).format(100)); // $100.00 // 欧元 print(NumberFormat.currency(locale: de_DE).format(100)); // 100,00 € // 人民币 print(NumberFormat.currency(locale: zh_CN, symbol: ¥).format(100)); // ¥100.00十一、测试国际化11.1 单元测试test(English localization, () { final l10n AppLocalizationsEn(); expect(l10n.helloWorld, Hello World); expect(l10n.greeting(name: Test), Hello Test); }); test(Chinese localization, () { final l10n AppLocalizationsZh(); expect(l10n.helloWorld, 你好世界); expect(l10n.greeting(name: 测试), 你好 测试); });11.2 Widget 测试testWidgets(Localized text displays correctly, (tester) async { await tester.pumpWidget( MaterialApp( locale: const Locale(zh), localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], home: const HomePage(), ), ); expect(find.text(你好世界), findsOneWidget); });十二、最佳实践12.1 组织翻译文件lib/ ├── l10n/ │ ├── app_en.arb │ ├── app_zh.arb │ ├── app_ja.arb │ └── app_ko.arb ├── main.dart └── ...12.2 使用一致的命名规范{ homeTitle: 首页, homeSubtitle: 欢迎回来, btnSubmit: 提交, btnCancel: 取消, errorNetwork: 网络错误, successSave: 保存成功 }12.3 避免硬编码字符串// 错误 Text(Hello World); // 正确 Text(l10n.helloWorld);12.4 使用翻译管理工具Lokalise- 专业翻译管理平台Transifex- 开源翻译管理Crowdin- 企业级翻译管理十三、实战案例多语言应用class InternationalizedApp extends StatelessWidget { const InternationalizedApp({super.key}); override Widget build(BuildContext context) { return ConsumerLocaleProvider( builder: (context, provider, child) { return MaterialApp( locale: provider.locale, localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: const [ Locale(en, US), Locale(zh, CN), Locale(ja, JP), Locale(ko, KR), ], home: const HomePage(), ); }, ); } } class HomePage extends StatelessWidget { const HomePage({super.key}); override Widget build(BuildContext context) { final l10n AppLocalizations.of(context)!; return Scaffold( appBar: AppBar( title: Text(l10n.homeTitle), actions: const [LanguageSelector()], ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(l10n.welcome), const SizedBox(height: 20), Text(l10n.greeting(name: 用户)), const SizedBox(height: 20), Text(l10n.counter(count: 5)), const SizedBox(height: 20), Text(l10n.date(date: DateTime.now())), ], ), ), ); } }十四、总结Flutter 国际化涉及多个方面配置环境- 添加依赖和配置 MaterialApp创建资源文件- 使用 ARB 格式管理翻译生成代码- 使用 intl 工具生成本地化类使用翻译- 在 Widget 中引用本地化字符串动态切换- 实现语言切换功能RTL 支持- 处理从右到左的语言格式化- 日期、时间、数字的本地化格式化通过合理的国际化设计可以让应用程序更好地服务于全球用户。