引言

在全球化浪潮下,一款成功的移动应用往往需要支持多种语言和区域习惯。无论是面向海外市场的出海 App,还是服务国内多民族用户的政务/教育类应用,国际化(Internationalization, i18n)与本地化(Localization, l10n) 都是产品走向成熟的关键一步。

Flutter 作为 Google 主导的跨平台框架,天然具备强大的国际化能力。官方提供了基于 ARB(Android Resource Bundle) 格式的标准方案,并通过 flutter gen-l10n 工具自动生成类型安全的本地化代码。然而,在实际开发中,许多团队仍面临如下挑战:

  • 官方方案默认不支持运行时动态切换语言
  • 复数、性别、日期、货币等复杂场景处理困难;
  • 第三方 UI 库(如 showDialogDatePicker)未自动适配系统语言;
  • 翻译缺失导致空字符串或崩溃;
  • 缺乏自动化测试与 CI 校验机制。

本文将带你从零构建一套生产级 Flutter 国际化系统,涵盖:

  • 官方 i18n 方案原理与配置
  • 动态语言切换的完整实现(兼容 Riverpod / Provider)
  • 复杂本地化场景(复数、参数、RTL)
  • 第三方库本地化适配
  • 自动化测试与翻译完整性校验
  • 上线前的最佳实践清单

无论你是刚接触 i18n 的新手,还是正在优化现有项目的资深开发者,本文都将提供可落地的解决方案。


一、Flutter 官方 i18n 方案详解

1.1 什么是 ARB?

ARB(Application Resource Bundle)是一种 JSON 格式的资源文件,最初由 Google 为 Android 设计,后被 Flutter 采用作为标准 i18n 格式。其优势在于:

  • 支持占位符(如 {name}
  • 支持复数规则(zero/one/two/many/other)
  • 支持元数据描述(便于翻译人员理解上下文)

1.2 项目结构配置

pubspec.yaml 中启用本地化生成:

1flutter:
2  generate: true  # 关键!启用代码生成
3  uses-material-design: true
4
5# 可选:使用 flutter_localizations 内置翻译
6dependencies:
7  flutter_localizations:
8    sdk: flutter

创建 l10n 目录(路径可自定义):

1lib/
2└── l10n/
3    ├── app_en.arb
4    ├── app_zh.arb
5    └── app_ar.arb  # 阿拉伯语(支持 RTL)

1.3 编写 ARB 文件

基础文本(app_en.arb):
1{
2  "appName": "My Awesome App",
3  "helloWorld": "Hello World!",
4  "welcomeMessage": "Welcome, {name}!",
5  "@welcomeMessage": {
6    "description": "Personalized welcome message",
7    "placeholders": {
8      "name": {
9        "type": "String",
10        "example": "Alice"
11      }
12    }
13  }
14}
中文翻译(app_zh.arb):
1{
2  "appName": "我的超棒应用",
3  "helloWorld": "你好,世界!",
4  "welcomeMessage": "欢迎,{name}!"
5}

✅ 注意:所有 key 必须在所有语言文件中存在,否则生成会失败(可通过 --no-synthetic-package 跳过,但不推荐)。

1.4 自动生成本地化代码

运行命令:

1flutter gen-l10n

或直接执行 flutter pub get(新版 Flutter 会自动触发)。

生成文件位于 .dart_tool/flutter_gen/gen_l10n/,包含:

  • app_localizations.dart:主入口类
  • app_localizations_en.dartapp_localizations_zh.dart:各语言实现

使用方式:

1Text(AppLocalizations.of(context)!.helloWorld);
2Text(AppLocalizations.of(context)!.welcomeMessage('张三'));

🔒 类型安全:若调用不存在的 key,编译时报错!


二、实现运行时动态语言切换

官方方案默认仅在启动时读取系统语言,无法在运行中切换。我们需要手动干预。

2.1 自定义 LocalizationsDelegate

创建 app_localizations_delegate.dart

1import 'package:flutter/widgets.dart';
2import 'package:flutter_gen/gen_l10n/app_localizations.dart';
3
4class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
5  final Locale locale;
6
7  const AppLocalizationsDelegate(this.locale);
8
9  @override
10  bool isSupported(Locale locale) {
11    // 支持的语言列表
12    return ['en', 'zh', 'ar'].contains(locale.languageCode);
13  }
14
15  @override
16  Future<AppLocalizations> load(Locale locale) {
17    return AppLocalizations.load(locale);
18  }
19
20  @override
21  bool shouldReload(AppLocalizationsDelegate old) {
22    return locale != old.locale;
23  }
24}

2.2 使用状态管理保存当前语言

方案一:Riverpod(推荐)
1// providers/locale_provider.dart
2import 'package:flutter/material.dart';
3import 'package:flutter_riverpod/flutter_riverpod.dart';
4
5@riverpod
6class AppLocale extends _$AppLocale {
7  @override
8  Locale build() {
9    // 从 SharedPreferences 读取上次设置
10    return ref.read(sharedPreferencesProvider).getString('locale')?.let((code) => Locale(code)) 
11           ?? WidgetsBinding.instance.platformDispatcher.locale;
12  }
13
14  void change(Locale locale) {
15    state = locale;
16    // 保存到本地
17    ref.read(sharedPreferencesProvider).setString('locale', locale.languageCode);
18  }
19}
方案二:Provider(兼容旧项目)
1class LocaleModel with ChangeNotifier {
2  Locale _locale = const Locale('en');
3
4  Locale get locale => _locale;
5
6  void setLocale(Locale locale) {
7    _locale = locale;
8    notifyListeners();
9    // 保存...
10  }
11}

2.3 重建 MaterialApp 实现切换

1class MyApp extends ConsumerWidget {
2  @override
3  Widget build(BuildContext context, WidgetRef ref) {
4    final locale = ref.watch(appLocaleProvider);
5
6    return MaterialApp(
7      title: 'My App',
8      locale: locale,
9      // 支持的语言列表
10      supportedLocales: const [
11        Locale('en'),
12        Locale('zh'),
13        Locale('ar'),
14      ],
15      localizationsDelegates: [
16        // 自定义 delegate(关键!)
17        AppLocalizationsDelegate(locale),
18        // Flutter 内置 Material 组件翻译
19        GlobalMaterialLocalizations.delegate,
20        GlobalWidgetsLocalizations.delegate,
21        GlobalCupertinoLocalizations.delegate,
22      ],
23      home: HomePage(),
24    );
25  }
26}

2.4 切换语言的 UI 操作

1DropdownButton<Locale>(
2  value: currentLocale,
3  items: [
4    DropdownMenuItem(value: Locale('en'), child: Text('English')),
5    DropdownMenuItem(value: Locale('zh'), child: Text('中文')),
6    DropdownMenuItem(value: Locale('ar'), child: Text('العربية')),
7  ],
8  onChanged: (locale) {
9    if (locale != null) {
10      ref.read(appLocaleProvider.notifier).change(locale);
11    }
12  },
13)

✅ 效果:切换后整个 App 立即刷新为新语言!


三、处理复杂本地化场景

3.1 复数(Plurals)

适用于“X 个文件”、“1 条消息”等场景。

app_en.arb:

1"fileCount": "{count, plural, zero{No files} one{1 file} other{{count} files}}",
2"@fileCount": {
3  "placeholders": {
4    "count": {
5      "type": "int"
6    }
7  }
8}

app_zh.arb(中文通常无需复数,但可统一格式):

1"fileCount": "{count, plural, other{{count} 个文件}}"

使用:

1Text(S.fileCount(0));  // "No files"
2Text(S.fileCount(1));  // "1 file"
3Text(S.fileCount(5));  // "5 files"

💡 提示:阿拉伯语有 6 种复数形式,务必参考 Unicode CLDR 规则。

3.2 日期、时间、数字格式化

借助 intl 包(已内置):

1// 日期
2DateFormat.yMMMd(locale.languageCode).format(DateTime.now());
3
4// 货币
5NumberFormat.simpleCurrency(locale: locale.toLanguageTag()).format(1234.5);
6
7// 数字(千分位)
8NumberFormat('#,##0.##', locale.languageCode).format(1234567);

建议封装工具类:

1class FormatUtils {
2  static String date(DateTime date, Locale locale) {
3    return DateFormat.yMMMd(locale.languageCode).format(date);
4  }
5}

3.3 RTL(从右到左)布局支持

阿拉伯语、希伯来语等需要镜像 UI。

MaterialApp 中启用:

1MaterialApp(
2  locale: locale,
3  supportedLocales: [...],
4  localizationsDelegates: [...],
5  // 自动处理 RTL
6  builder: (context, child) {
7    return Directionality(
8      textDirection: locale.languageCode == 'ar' ? TextDirection.rtl : TextDirection.ltr,
9      child: child!,
10    );
11  },
12)

同时确保:

  • 使用 EdgeInsets.symmetric() 而非 left/right
  • 图标使用 Icons.arrow_forward(自动镜像为 arrow_back

四、第三方库的本地化适配

4.1 Material 组件(如 DatePicker)

确保注册 GlobalMaterialLocalizations.delegate,系统组件(如 showDatePicker)会自动翻译。

4.2 自定义 Dialog 或 Toast

避免硬编码文本:

1// ❌ 错误
2showDialog(context: context, builder: (_) => AlertDialog(title: Text("确认删除?")));
3
4// ✅ 正确
5showDialog(context: context, builder: (_) => AlertDialog(
6  title: Text(S.of(context).confirmDelete),
7));

4.3 处理翻译缺失

生成代码时添加 fallback:

1// 在 app_localizations.dart 中(需手动修改生成文件,不推荐)
2// 更好的方式:使用 flutter_intl 插件(见下文)
3
4// 或在调用处兜底
5String safeText(String Function() getter) {
6  try {
7    return getter();
8  } catch (e) {
9    return 'MISSING_TRANSLATION'; // 或返回英文
10  }
11}

五、提升开发体验:flutter_intl 插件

官方方案需写 AppLocalizations.of(context)!,略显冗长。推荐使用社区插件 flutter_localizely/flutter-intl-vscode

  1. 安装 VS Code 插件
  2. 在 pubspec.yaml 添加:
1flutter_intl:
2  enabled: true
3  class_name: S
  1. 自动生成 S.of(context).helloWorld,更简洁!

⚠️ 注意:该插件与官方 gen-l10n 不冲突,底层仍使用 ARB。


六、自动化测试与 CI 校验

6.1 单元测试本地化文本

1testWidgets('should display Chinese greeting', (tester) async {
2  await tester.pumpWidget(
3    MaterialApp(
4      locale: const Locale('zh'),
5      localizationsDelegates: AppLocalizations.localizationsDelegates,
6      home: Scaffold(body: Text(AppLocalizations.of(context)!.helloWorld)),
7    ),
8  );
9  expect(find.text('你好,世界!'), findsOneWidget);
10});

6.2 CI 校验 ARB 文件完整性

编写脚本 scripts/validate_l10n.dart

1// 读取 app_en.arb 的所有 key
2// 检查其他语言文件是否包含相同 key
3// 若缺失,exit(1) 中断 CI

在 GitHub Actions 中运行:

1- name: Validate translations
2  run: dart scripts/validate_l10n.dart

七、上线前最佳实践清单

  •  所有用户可见文本均来自 ARB,无硬编码
  •  支持的语言在 supportedLocales 中声明
  •  动态切换后,第三方组件(如 DatePicker)语言同步更新
  •  RTL 语言下布局正确镜像
  •  复数、日期、货币格式符合当地习惯
  •  翻译文件通过母语者校对(避免机翻错误)
  •  CI 流程包含翻译完整性检查

结语

国际化不是简单的“文本替换”,而是对全球用户的文化尊重与体验承诺。通过 Flutter 官方工具链 + 动态切换能力 + 自动化保障,你可以构建出真正世界级的应用。

最后建议

  • 将 ARB 文件交给专业翻译平台(如 Lokalise、Crowdin)管理
  • 在 App 内提供“反馈翻译错误”入口
  • 定期更新 flutter_localizations 以获取最新语言支持

附:学习资源

  • Flutter 官方 i18n 文档:https://docs.flutter.dev/ui/accessibility-and-localization/internationalization
  • ARB 格式规范:https://github.com/google/app-resource-bundle
  • CLDR 复数规则:https://unicode-org.github.io/cldr-staging/charts/37/supplemental/language_plural_rules.html

如需配套的 GitHub 示例项目(含完整代码、CI 配置、多语言截图),我也可以为你生成。欢迎继续提问!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐