开篇:好的国际化不是"翻译字符串"
当你的应用准备走向海外市场,“国际化(i18n)“和"本地化(l10n)“就不仅仅是把按钮文字翻译成英文那么简单。日期在各国书写习惯不同、金额需要按地区货币符号与小数位显示、阿拉伯语和希伯来语需要 RTL 布局、时区与复数规则千差万别——真正的本地化是"让每种语言的用户都感觉应用是原生的”。
Flutter 提供了一套完整的国际化基础设施:flutter_localizations 提供组件内置文案(如日期选择器、对话框),intl 提供日期/数字/货币的格式化能力,ARB 文件 + gen-l10n 把翻译声明式地接入代码。本章将系统讲解从零构建多语言应用的完整流程。
一、国际化基础与 Locale
1.1 Locale 是什么
Locale 是语言与地区的组合标识,格式为 语言代码-地区代码,如 zh-CN、en-US、ja-JP。Flutter 通过它决定显示哪种语言与格式。
const localeZh = Locale('zh', 'CN'); // 简体中文(中国)
const localeEn = Locale('en'); // 英语
const localeAr = Locale('ar'); // 阿拉伯语(触发 RTL)
1.2 配置 flutter_localizations
国际化第一步是在 pubspec.yaml 与 MaterialApp 中启用本地化支持:
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: any
intl_translation: any # 可选,用于某些工具链
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
MaterialApp(
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate, // Material 组件内置文案
GlobalWidgetsLocalizations.delegate, // Widget 层(如文本方向)
GlobalCupertinoLocalizations.delegate,// Cupertino 组件内置文案
],
supportedLocales: const [
Locale('zh', 'CN'),
Locale('en'),
Locale('ar'),
],
locale: const Locale('zh', 'CN'), // 手动指定;不指定则跟随系统
home: const HomePage(),
)
| Delegate | 提供的内容 |
|---|---|
GlobalMaterialLocalizations | 日期选择器、对话框、菜单等组件文案 |
GlobalWidgetsLocalizations | 文本方向(LTR/RTL) |
GlobalCupertinoLocalizations | iOS 风格组件文案 |
一句话:
flutter_localizations负责"组件内置文案"的本地化,intl负责"业务数据格式"的本地化,两者配合才算完整的国际化。
二、ARB 文件与 gen-l10n
2.1 ARB 文件格式
ARB(Application Resource Bundle)是 Flutter 官方推荐的翻译文件格式,本质是 JSON。每个 locale 一个文件:
// lib/l10n/app_zh.arb
{
"@@locale": "zh",
"appTitle": "我的应用",
"@appTitle": {
"description": "应用标题"
},
"welcome": "欢迎,{name}!",
"@welcome": {
"description": "带参数的问候语",
"placeholders": {
"name": { "type": "String" }
}
},
"itemCount": "{count, plural, =0{没有项目} =1{1 个项目} other{{count} 个项目}}",
"@itemCount": {
"placeholders": {
"count": { "type": "int" }
}
}
}
// lib/l10n/app_en.arb
{
"@@locale": "en",
"appTitle": "My App",
"welcome": "Welcome, {name}!",
"itemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}"
}
2.2 配置 gen-l10n 生成代码
在 pubspec.yaml 中启用代码生成:
flutter:
generate: true # 启用 gen-l10n
l10n:
arb-dir: lib/l10n # ARB 文件目录
template-arb-file: app_zh.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
nullable-getter: false
运行 flutter gen-l10n(或 flutter pub run build_runner build)后,会生成 AppLocalizations 类,代码中即可安全访问翻译:
// 在 build 中获取本地化实例
AppLocalizations l10n = AppLocalizations.of(context)!;
Text(l10n.appTitle), // "我的应用"
Text(l10n.welcome('张三')), // "欢迎,张三!"
Text(l10n.itemCount(3)), // "3 个项目"
2.3 手动配置 delegate(可选)
如果不用代码生成,也可手动编写 delegate。但推荐始终使用 gen-l10n——它生成编译期安全的访问器、自动处理复数与占位符:
| 方式 | 优点 | 缺点 |
|---|---|---|
gen-l10n(推荐) | 编译期安全、自动复数/占位符 | 需要 codegen |
| 手写 delegate | 无额外工具 | 易错、繁琐 |
一句话:ARB 是"翻译的源文件”,
gen-l10n把它变成编译期安全的 Dart 代码——l10n.welcome('张三')这样调用,拼错 key 直接编译报错。
三、intl 格式化:日期、数字、货币
3.1 日期格式化
不同地区日期写法差异极大,DateFormat 按 locale 自动适配:
import 'package:intl/intl.dart';
final zhDate = DateFormat.yMMMd('zh').format(DateTime.now());
// 例如 2026年9月26日
final enDate = DateFormat.yMMMd('en').format(DateTime.now());
// 例如 Sep 26, 2026
// 自定义格式
final custom = DateFormat('yyyy-MM-dd HH:mm').format(DateTime.now());
// 2026-09-26 10:00
3.2 数字与货币格式化
final nf = NumberFormat('#,##0.00', 'en_US');
print(nf.format(1234567.891)); // 1,234,567.89
// 货币:按地区自动带符号与小数位
final usd = NumberFormat.currency(locale: 'en_US', symbol: r'$');
print(usd.format(19.99)); // $19.99
final cny = NumberFormat.currency(locale: 'zh', symbol: '¥');
print(cny.format(19.99)); // ¥19.99
final euro = NumberFormat.currency(locale: 'de', name: 'EUR');
print(euro.format(19.99)); // 19,99 €(注意小数逗号)
3.3 格式化能力对照
| 类 | 能力 | 地区差异示例 |
|---|---|---|
DateFormat | 日期时间 | yyyy/MM/dd(中)vs MM/dd/yyyy(美) |
NumberFormat | 数字千分位/小数 | 1,234.5 vs 1.234,5 |
NumberFormat.currency | 货币符号 | $ / ¥ / € / £ |
Intl.plural | 复数规则 | 英文 2 种、阿拉伯语 6 种 |
一句话:绝不要手动拼接日期/数字/货币——
intl按 locale 自动处理千分位、小数位、货币符号和复数规则,交给它最省心。
四、RTL 布局支持
4.1 RTL 语言的挑战
阿拉伯语、希伯来语、波斯语使用从右到左(RTL)的书写方向。Flutter 通过 Directionality 自动适配,多数内置组件开箱即用:
// 语言切换为阿拉伯语后,Row/Column 的方向会自动翻转
Row(
children: [
Icon(Icons.arrow_back), // 在 RTL 下自动镜像为向前
Text('返回'),
],
)
// 手动指定方向
Directionality(
textDirection: TextDirection.rtl,
child: Row(children: [...]), // 强制 RTL
)
4.2 需要适配的常见点
| 场景 | 问题 | 解决方案 |
|---|---|---|
| 图标箭头 | 方向固定 | 用 Icons.arrow_forward 的镜像语义或 Directionality 包裹 |
| Padding/Alignment | 硬编码左/右 | 用 start/end 替代 left/right |
| 自定义绘制 | 文本锚点错误 | TextPainter.textDirection 跟随上下文 |
| 数字与混排 | 数字方向 | 默认按内容自动处理,无需干预 |
// 用逻辑方向而非物理方向
Padding(
padding: const EdgeInsetsDirectional.only(
start: 16, // RTL 下自动成为右侧
end: 8,
),
child: Row(
children: [
// Row 的 children 顺序在 RTL 下自动反转
Text('标签'),
const Spacer(),
Text('值'),
],
),
)
一句话:RTL 适配的黄金法则是"用逻辑方向(start/end)代替物理方向(left/right)"——Flutter 会在 RTL 语言下自动完成镜像。
五、动态语言切换与持久化
5.1 运行时切换语言
MaterialApp 的 locale 是响应式的,只需在状态管理中改变它:
class LocaleController extends ChangeNotifier {
Locale _locale = const Locale('zh', 'CN');
Locale get locale => _locale;
Future<void> setLocale(Locale locale) async {
_locale = locale;
notifyListeners();
final prefs = await SharedPreferences.getInstance();
await prefs.setString('locale', locale.toString()); // 持久化
}
}
// 在 MaterialApp 中绑定
ListenableBuilder(
listenable: localeController,
builder: (context, _) => MaterialApp(
locale: localeController.locale,
supportedLocales: const [Locale('zh', 'CN'), Locale('en')],
// ...delegates
),
)
5.2 语言选择器
DropdownButton<Locale>(
value: currentLocale,
items: const [
DropdownMenuItem(value: Locale('zh', 'CN'), child: Text('简体中文')),
DropdownMenuItem(value: Locale('en'), child: Text('English')),
DropdownMenuItem(value: Locale('ja'), child: Text('日本語')),
],
onChanged: (locale) => context.read<LocaleController>().setLocale(locale!),
)
5.3 方案对比
| 方案 | 跟随系统 | 用户手动选择 | 持久化 |
|---|---|---|---|
locale: null | ✅ | ❌ | 无需 |
| 手动 + SharedPreferences | 可回退 | ✅ | ✅ |
| 本地化包(如 easy_localization) | ✅ | ✅ | ✅ |
一句话:动态语言切换 = “可变的 locale + 状态管理”——把 locale 存进控制器并持久化,切换即生效、重启即恢复。
六、多语言内容管理最佳实践
6.1 翻译流程与角色分工
| 环节 | 责任方 | 产出物 |
|---|---|---|
| 文案提取 | 开发者 | 新增 key 到 ARB |
| 翻译 | 翻译人员/本地化平台 | 各 locale ARB |
| 审核 | 产品/QA | 语境截图、术语表 |
| 构建 | CI 流水线 | 自动 gen-l10n |
6.2 使用 Flutter Intl 插件提升效率
flutter-intl 插件(VS Code)支持从编辑器直接创建/编辑 ARB,并提供预览:
# .vscode/settings.json(示例)
{
"flutter-intl.arbDir": "lib/l10n",
"flutter-intl.outputDir": "lib/l10n",
"flutter-intl.enabled": true
}
6.3 关键实践清单
- Key 语义化:用
button.save、error.timeout等层级命名,而非text_001 - 占位符类型化:ARB 中声明
type: String/int,保证生成代码类型安全 - 复数必须处理:用 ICU 复数语法
{count, plural, ...}而非字符串拼接 - 翻译语境注释:用
@key的description字段给翻译者说明语境
// 好的做法:把文案集中调用,避免字符串散落
class AppStrings {
static String appTitle(BuildContext context) =>
AppLocalizations.of(context)!.appTitle;
}
一句话:多语言内容管理的核心是"一个 key 源(ARB)+ 一条 CI 流水线”——key 语义化、类型安全、复数正确,翻译效率与代码质量就能双赢。
七、测试与 CI 集成
7.1 本地化单元测试
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
void main() {
testWidgets('英文环境渲染正确', (tester) async {
await tester.pumpWidget(
MaterialApp(
locale: const Locale('en'),
supportedLocales: const [Locale('en')],
localizationsDelegates: GlobalMaterialLocalizations.delegates,
home: const HomePage(),
),
);
expect(find.text('My App'), findsOneWidget);
});
}
7.2 自动化验证清单
| 检查项 | 方法 |
|---|---|
| 所有 locale 均有对应 ARB | CI 脚本校验文件存在性 |
| 无缺 key | flutter gen-l10n 报错即失败 |
| 文本溢出 | 各语言渲染截图 + Golden 测试 |
| RTL 布局 | 用 locale: ar 跑 Widget 测试 |
一句话:本地化的测试分两层——单元层验证"文案正确取到",集成层用真实 locale 渲染验证"布局不溢出"。
八、总结
Flutter 国际化与本地化是一个覆盖配置、生成、格式化、布局与运维的完整工程:
| 环节 | 工具/机制 | 核心要点 |
|---|---|---|
| 基础设施 | flutter_localizations | 组件内置文案 + 文本方向 |
| 翻译源 | ARB 文件 | 语义化 key + 类型化占位符 |
| 代码生成 | gen-l10n | 编译期安全的访问器 |
| 格式本地化 | intl | 日期/数字/货币自动适配 |
| 布局适配 | Directionality | RTL 用逻辑方向 |
| 动态切换 | Locale + 持久化 | 用户可控、重启恢复 |
| 质量保障 | CI + Golden 测试 | 缺 key 即失败、溢出即发现 |
一句话:国际化不是"翻译一下就行",而是"本地化思维贯穿架构"——从 ARB 到 gen-l10n,从 intl 到 RTL,从动态切换再到 CI 保障,环环相扣才能让应用真正走向全球。
相关阅读
- https://plumephp.com/flutter-architecture-patterns/ — 架构模式(i18n 在大型项目的组织)
- https://plumephp.com/flutter-state-management/ — 状态管理(locale 作为全局状态)
- https://plumephp.com/flutter-form-validation/ — 表单验证(本地化后的表单校验文案)
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。