附录C - 国际化配置
嗨,朋友!我是长安。
如果你的 App 需要支持多种语言,国际化(i18n)就是必不可少的功能。Flutter 提供了完善的国际化支持,让你的 App 轻松走向全球!
这一章,我会教你如何配置多语言支持,从简单方案到专业方案都会涉及!
🌍 国际化基础概念
什么是国际化?
- 国际化(i18n):让应用支持多种语言的能力
- 本地化(l10n):将应用翻译成特定语言的过程
Flutter 国际化方案
| 方案 | 复杂度 | 适用场景 |
|---|---|---|
| 手动管理 | ⭐ | 语言少、文本少 |
| flutter_localizations | ⭐⭐ | 官方方案,推荐 |
| intl 包 | ⭐⭐⭐ | 专业级,大型项目 |
| easy_localization | ⭐⭐ | 第三方,简单易用 |
| get(GetX) | ⭐⭐ | 已用 GetX 的项目 |
📦 方案一:flutter_localizations(官方推荐)
1. 添加依赖
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_localizations: # 添加这个
sdk: flutter
intl: ^0.19.0 # 国际化工具
2. 配置 MaterialApp
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
// 支持的语言列表
supportedLocales: const [
Locale('zh', 'CN'), // 简体中文
Locale('zh', 'TW'), // 繁体中文
Locale('en', 'US'), // 英文
Locale('ja', 'JP'), // 日文
],
// 本地化代理
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate, // Material 组件本地化
GlobalWidgetsLocalizations.delegate, // Widget 本地化
GlobalCupertinoLocalizations.delegate, // Cupertino 组件本地化
// AppLocalizations.delegate, // 自定义本地化(后面会加)
],
// 语言选择逻辑
localeResolutionCallback: (locale, supportedLocales) {
// 遍历支持的语言,找到匹配的
for (var supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale?.languageCode &&
supportedLocale.countryCode == locale?.countryCode) {
return supportedLocale;
}
}
// 默认返回第一个(中文)
return supportedLocales.first;
},
home: const HomePage(),
);
}
}
3. 创建本地化类
// lib/l10n/app_localizations.dart
import 'package:flutter/material.dart';
class AppLocalizations {
final Locale locale;
AppLocalizations(this.locale);
// 获取当前本地化实例
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
}
// 本地化代理
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
// 翻译字典
static final Map<String, Map<String, String>> _localizedValues = {
'en': {
'app_title': 'My App',
'home': 'Home',
'settings': 'Settings',
'profile': 'Profile',
'login': 'Login',
'logout': 'Logout',
'welcome': 'Welcome',
'hello_user': 'Hello, {name}!',
'items_count': '{count} items',
},
'zh': {
'app_title': '我的应用',
'home': '首页',
'settings': '设置',
'profile': '个人中心',
'login': '登录',
'logout': '退出登录',
'welcome': '欢迎',
'hello_user': '你好,{name}!',
'items_count': '{count} 个项目',
},
'ja': {
'app_title': 'マイアプリ',
'home': 'ホーム',
'settings': '設定',
'profile': 'プロフィール',
'login': 'ログイン',
'logout': 'ログアウト',
'welcome': 'ようこそ',
'hello_user': 'こんにちは、{name}さん!',
'items_count': '{count} アイテム',
},
};
// 获取翻译文本
String _translate(String key) {
return _localizedValues[locale.languageCode]?[key] ?? key;
}
// 常用文本 getter
String get appTitle => _translate('app_title');
String get home => _translate('home');
String get settings => _translate('settings');
String get profile => _translate('profile');
String get login => _translate('login');
String get logout => _translate('logout');
String get welcome => _translate('welcome');
// 带参数的翻译
String helloUser(String name) {
return _translate('hello_user').replaceAll('{name}', name);
}
String itemsCount(int count) {
return _translate('items_count').replaceAll('{count}', count.toString());
}
}
// 本地化代理
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
bool isSupported(Locale locale) {
return ['en', 'zh', 'ja'].contains(locale.languageCode);
}
Future<AppLocalizations> load(Locale locale) async {
return AppLocalizations(locale);
}
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
4. 注册并使用
// lib/main.dart
import 'l10n/app_localizations.dart';
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
AppLocalizations.delegate, // 添加自定义代理
],
supportedLocales: const [
Locale('zh', 'CN'),
Locale('en', 'US'),
Locale('ja', 'JP'),
],
home: const HomePage(),
);
}
}
// 页面中使用
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
// 获取本地化实例
final l10n = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(l10n.appTitle),
),
body: Column(
children: [
Text(l10n.welcome),
Text(l10n.helloUser('张三')),
Text(l10n.itemsCount(5)),
],
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: const Icon(Icons.home),
label: l10n.home,
),
BottomNavigationBarItem(
icon: const Icon(Icons.settings),
label: l10n.settings,
),
BottomNavigationBarItem(
icon: const Icon(Icons.person),
label: l10n.profile,
),
],
),
);
}
}
📁 方案二:intl 包 + ARB 文件(专业方案)
使用 .arb 文件管理翻译,更适合大型项目。
1. 添加依赖
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
intl: ^0.19.0
dev_dependencies:
intl_utils: ^2.8.5 # 可选,用于生成代码
# 启用代码生成
flutter:
generate: true
2. 配置 l10n.yaml
# l10n.yaml(项目根目录)
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
3. 创建 ARB 文件
// lib/l10n/app_en.arb
{
"@@locale": "en",
"appTitle": "My App",
"@appTitle": {
"description": "The title of the application"
},
"home": "Home",
"settings": "Settings",
"profile": "Profile",
"login": "Login",
"logout": "Logout",
"welcome": "Welcome",
"helloUser": "Hello, {name}!",
"@helloUser": {
"description": "Greeting with user name",
"placeholders": {
"name": {
"type": "String",
"example": "Tom"
}
}
},
"itemsCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
"@itemsCount": {
"description": "Number of items",
"placeholders": {
"count": {
"type": "int"
}
}
},
"lastLogin": "Last login: {date}",
"@lastLogin": {
"description": "Last login time",
"placeholders": {
"date": {
"type": "DateTime",
"format": "yMMMd"
}
}
}
}
// lib/l10n/app_zh.arb
{
"@@locale": "zh",
"appTitle": "我的应用",
"home": "首页",
"settings": "设置",
"profile": "个人中心",
"login": "登录",
"logout": "退出登录",
"welcome": "欢迎",
"helloUser": "你好,{name}!",
"itemsCount": "{count, plural, =0{没有项目} =1{1 个项目} other{{count} 个项目}}",
"lastLogin": "上次登录:{date}"
}
// lib/l10n/app_ja.arb
{
"@@locale": "ja",
"appTitle": "マイアプリ",
"home": "ホーム",
"settings": "設定",
"profile": "プロフィール",
"login": "ログイン",
"logout": "ログアウト",
"welcome": "ようこそ",
"helloUser": "こんにちは、{name}さん!",
"itemsCount": "{count, plural, =0{アイテムなし} =1{1 アイテム} other{{count} アイテム}}",
"lastLogin": "最終ログイン:{date}"
}
4. 生成代码
# 运行 Flutter 命令生成本地化代码
flutter gen-l10n
生成的文件会在 .dart_tool/flutter_gen/gen_l10n/ 目录。
5. 使用生成的本地化类
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.appTitle)),
body: Column(
children: [
Text(l10n.welcome),
Text(l10n.helloUser('张三')),
Text(l10n.itemsCount(0)), // "没有项目"
Text(l10n.itemsCount(1)), // "1 个项目"
Text(l10n.itemsCount(5)), // "5 个项目"
Text(l10n.lastLogin(DateTime.now())),
],
),
);
}
}
🚀 方案三:easy_localization(简单易用)
第三方包,配置简单,支持 JSON/YAML 文件。
1. 添加依赖
dependencies:
easy_localization: ^3.0.5
2. 创建翻译文件
assets/
└── translations/
├── en.json
├── zh-CN.json
└── ja.json
// assets/translations/en.json
{
"app_title": "My App",
"home": "Home",
"settings": "Settings",
"profile": "Profile",
"login": "Login",
"logout": "Logout",
"welcome": "Welcome",
"hello_user": "Hello, {}!",
"items_count": {
"zero": "No items",
"one": "1 item",
"other": "{} items"
},
"nav": {
"home": "Home",
"discover": "Discover",
"mine": "Mine"
}
}
// assets/translations/zh-CN.json
{
"app_title": "我的应用",
"home": "首页",
"settings": "设置",
"profile": "个人中心",
"login": "登录",
"logout": "退出登录",
"welcome": "欢迎",
"hello_user": "你好,{}!",
"items_count": {
"zero": "没有项目",
"one": "1 个项目",
"other": "{} 个项目"
},
"nav": {
"home": "首页",
"discover": "发现",
"mine": "我的"
}
}
3. 配置 pubspec.yaml
flutter:
assets:
- assets/translations/
4. 初始化并使用
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
runApp(
EasyLocalization(
supportedLocales: const [
Locale('en'),
Locale('zh', 'CN'),
Locale('ja'),
],
path: 'assets/translations',
fallbackLocale: const Locale('zh', 'CN'),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'app_title'.tr(), // 使用 .tr() 翻译
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('app_title'.tr()),
),
body: Column(
children: [
// 基本翻译
Text('welcome'.tr()),
// 带参数
Text('hello_user'.tr(args: ['张三'])),
// 复数形式
Text('items_count'.plural(0)), // "没有项目"
Text('items_count'.plural(1)), // "1 个项目"
Text('items_count'.plural(5)), // "5 个项目"
// 嵌套 key
Text('nav.home'.tr()),
// 切换语言按钮
ElevatedButton(
onPressed: () {
context.setLocale(const Locale('en'));
},
child: const Text('English'),
),
ElevatedButton(
onPressed: () {
context.setLocale(const Locale('zh', 'CN'));
},
child: const Text('中文'),
),
],
),
);
}
}
🎨 方案四:GetX 国际化
如果你已经在使用 GetX,可以直接用它的国际化功能。
1. 创建翻译类
// lib/l10n/translations.dart
import 'package:get/get.dart';
class AppTranslations extends Translations {
Map<String, Map<String, String>> get keys => {
'en_US': {
'app_title': 'My App',
'home': 'Home',
'settings': 'Settings',
'profile': 'Profile',
'login': 'Login',
'logout': 'Logout',
'welcome': 'Welcome',
'hello_user': 'Hello, @name!',
'items_count': '@count items',
},
'zh_CN': {
'app_title': '我的应用',
'home': '首页',
'settings': '设置',
'profile': '个人中心',
'login': '登录',
'logout': '退出登录',
'welcome': '欢迎',
'hello_user': '你好,@name!',
'items_count': '@count 个项目',
},
'ja_JP': {
'app_title': 'マイアプリ',
'home': 'ホーム',
'settings': '設定',
'profile': 'プロフィール',
'login': 'ログイン',
'logout': 'ログアウト',
'welcome': 'ようこそ',
'hello_user': 'こんにちは、@nameさん!',
'items_count': '@count アイテム',
},
};
}
2. 配置 GetMaterialApp
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'l10n/translations.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'app_title'.tr,
// 国际化配置
translations: AppTranslations(),
locale: const Locale('zh', 'CN'), // 默认语言
fallbackLocale: const Locale('en', 'US'), // 后备语言
home: const HomePage(),
);
}
}
3. 使用翻译
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('app_title'.tr),
),
body: Column(
children: [
// 基本翻译
Text('welcome'.tr),
// 带参数
Text('hello_user'.trParams({'name': '张三'})),
// 带参数(数字)
Text('items_count'.trParams({'count': '5'})),
// 切换语言
ElevatedButton(
onPressed: () {
Get.updateLocale(const Locale('en', 'US'));
},
child: const Text('English'),
),
ElevatedButton(
onPressed: () {
Get.updateLocale(const Locale('zh', 'CN'));
},
child: const Text('中文'),
),
ElevatedButton(
onPressed: () {
Get.updateLocale(const Locale('ja', 'JP'));
},
child: const Text('日本語'),
),
// 显示当前语言
Text('当前语言:${Get.locale}'),
],
),
);
}
}
🔄 动态切换语言
保存语言偏好
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/material.dart';
class LanguageService {
static const String _key = 'language_code';
// 保存语言设置
static Future<void> saveLanguage(Locale locale) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_key, '${locale.languageCode}_${locale.countryCode}');
}
// 读取语言设置
static Future<Locale?> getSavedLanguage() async {
final prefs = await SharedPreferences.getInstance();
final code = prefs.getString(_key);
if (code != null) {
final parts = code.split('_');
return Locale(parts[0], parts.length > 1 ? parts[1] : null);
}
return null;
}
}
语言选择页面
class LanguageSettingsPage extends StatelessWidget {
const LanguageSettingsPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('语言设置')),
body: ListView(
children: [
_buildLanguageTile(
context,
'简体中文',
const Locale('zh', 'CN'),
),
_buildLanguageTile(
context,
'繁體中文',
const Locale('zh', 'TW'),
),
_buildLanguageTile(
context,
'English',
const Locale('en', 'US'),
),
_buildLanguageTile(
context,
'日本語',
const Locale('ja', 'JP'),
),
_buildLanguageTile(
context,
'한국어',
const Locale('ko', 'KR'),
),
],
),
);
}
Widget _buildLanguageTile(
BuildContext context,
String title,
Locale locale,
) {
// 获取当前语言
final currentLocale = Localizations.localeOf(context);
final isSelected = currentLocale.languageCode == locale.languageCode &&
currentLocale.countryCode == locale.countryCode;
return ListTile(
title: Text(title),
trailing: isSelected ? const Icon(Icons.check, color: Colors.green) : null,
onTap: () async {
// 保存设置
await LanguageService.saveLanguage(locale);
// 切换语言(根据使用的方案选择对应方法)
// easy_localization:
// context.setLocale(locale);
// GetX:
// Get.updateLocale(locale);
// 其他方案可能需要重启 App 或使用状态管理
},
);
}
}
使用 Provider 管理语言状态
// lib/providers/locale_provider.dart
import 'package:flutter/material.dart';
import '../services/language_service.dart';
class LocaleProvider extends ChangeNotifier {
Locale _locale = const Locale('zh', 'CN');
Locale get locale => _locale;
LocaleProvider() {
_loadSavedLocale();
}
Future<void> _loadSavedLocale() async {
final savedLocale = await LanguageService.getSavedLanguage();
if (savedLocale != null) {
_locale = savedLocale;
notifyListeners();
}
}
Future<void> setLocale(Locale locale) async {
if (_locale == locale) return;
_locale = locale;
await LanguageService.saveLanguage(locale);
notifyListeners();
}
}
// main.dart
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => LocaleProvider(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
final localeProvider = context.watch<LocaleProvider>();
return MaterialApp(
locale: localeProvider.locale,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
AppLocalizations.delegate,
],
supportedLocales: const [
Locale('zh', 'CN'),
Locale('en', 'US'),
Locale('ja', 'JP'),
],
home: const HomePage(),
);
}
}
📅 日期和数字格式化
使用 intl 包格式化
import 'package:intl/intl.dart';
class FormatUtils {
// 日期格式化
static String formatDate(DateTime date, String locale) {
return DateFormat.yMMMd(locale).format(date);
// zh_CN: 2026年2月3日
// en_US: Feb 3, 2026
// ja_JP: 2026年2月3日
}
// 时间格式化
static String formatTime(DateTime time, String locale) {
return DateFormat.Hm(locale).format(time);
// 14:30
}
// 日期时间格式化
static String formatDateTime(DateTime dateTime, String locale) {
return DateFormat.yMMMd(locale).add_Hm().format(dateTime);
// zh_CN: 2026年2月3日 14:30
}
// 相对时间
static String formatRelativeTime(DateTime dateTime) {
final now = DateTime.now();
final diff = now.difference(dateTime);
if (diff.inDays > 365) {
return '${diff.inDays ~/ 365}年前';
} else if (diff.inDays > 30) {
return '${diff.inDays ~/ 30}个月前';
} else if (diff.inDays > 0) {
return '${diff.inDays}天前';
} else if (diff.inHours > 0) {
return '${diff.inHours}小时前';
} else if (diff.inMinutes > 0) {
return '${diff.inMinutes}分钟前';
} else {
return '刚刚';
}
}
// 数字格式化
static String formatNumber(num number, String locale) {
return NumberFormat.decimalPattern(locale).format(number);
// 1234567 -> 1,234,567 (en_US)
// 1234567 -> 1,234,567 (zh_CN)
}
// 货币格式化
static String formatCurrency(num amount, String locale, String currency) {
return NumberFormat.currency(
locale: locale,
symbol: currency,
).format(amount);
// 1234.50 -> $1,234.50 (en_US, $)
// 1234.50 -> ¥1,234.50 (zh_CN, ¥)
}
// 百分比格式化
static String formatPercent(double value, String locale) {
return NumberFormat.percentPattern(locale).format(value);
// 0.75 -> 75%
}
}
在 Widget 中使用
class FormattedDateWidget extends StatelessWidget {
const FormattedDateWidget({super.key});
Widget build(BuildContext context) {
final locale = Localizations.localeOf(context).toString();
final now = DateTime.now();
return Column(
children: [
Text('日期:${FormatUtils.formatDate(now, locale)}'),
Text('时间:${FormatUtils.formatTime(now, locale)}'),
Text('价格:${FormatUtils.formatCurrency(1234.5, locale, '¥')}'),
Text('数量:${FormatUtils.formatNumber(1234567, locale)}'),
],
);
}
}
📝 最佳实践
翻译文件管理
建议
- 使用 key 命名规范:
模块_功能_描述,如home_title、auth_login_button - 避免硬编码:所有用户可见文本都应该国际化
- 保持翻译同步:使用工具检查遗漏的翻译
- 使用复数形式:不要用
1 items这种错误形式 - 考虑文本长度:不同语言翻译后长度可能差异很大
文本长度处理
// 某些语言翻译后可能更长,要考虑溢出
Text(
'very_long_text'.tr(),
overflow: TextOverflow.ellipsis,
maxLines: 2,
)
// 按钮文字也要考虑
ElevatedButton(
onPressed: () {},
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text('submit_button'.tr()),
),
)
RTL(从右到左)语言支持
MaterialApp(
// 支持 RTL 语言(如阿拉伯语、希伯来语)
supportedLocales: const [
Locale('en'),
Locale('zh'),
Locale('ar'), // 阿拉伯语(RTL)
],
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
)
// 布局会自动调整方向
// 如果需要手动控制:
Directionality(
textDirection: TextDirection.rtl, // 或 TextDirection.ltr
child: YourWidget(),
)
📊 方案对比总结
| 特性 | flutter_localizations | intl + ARB | easy_localization | GetX |
|---|---|---|---|---|
| 官方支持 | ✅ | ✅ | ❌ | ❌ |
| 配置复杂度 | 中 | 高 | 低 | 低 |
| 代码生成 | ❌ | ✅ | ❌ | ❌ |
| 复数支持 | ❌ | ✅ | ✅ | ❌ |
| 热重载 | ✅ | ✅ | ✅ | ✅ |
| JSON 支持 | ❌ | ❌ | ✅ | ❌ |
| 动态切换 | 需手动 | 需手动 | ✅ | ✅ |
推荐选择
- 小型项目:easy_localization 或 GetX(简单快速)
- 中型项目:flutter_localizations + 手动管理
- 大型项目:intl + ARB(专业级,有代码生成)
💪 练习题
- 使用 easy_localization 实现中英文切换
- 使用 intl 包实现日期和货币的本地化显示
- 实现一个语言设置页面,保存用户的语言偏好
- 处理复数形式:「你有 N 条新消息」
🚀 下一步
掌握了国际化配置后,你可以前往 附录D - 权限处理,学习如何请求和管理应用权限!
由 编程指南 提供
