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 配置 MaterialApp
import '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.yaml
flutter: 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 使用 Provider
void main() { runApp( ChangeNotifierProvider( create: (context) => LocaleProvider(), child: const MyApp(), ), ); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return Consumer<LocaleProvider>( 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.of<LocaleProvider>(context); return DropdownButton<Locale>( 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 Widget
Directionality( 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 Consumer<LocaleProvider>( 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 支持- 处理从右到左的语言
- 格式化- 日期、时间、数字的本地化格式化
通过合理的国际化设计,可以让应用程序更好地服务于全球用户。
