简介
国际化(i18n)是应用程序适配不同语言和地区的过程。Flutter 提供了完整的国际化支持,可以轻松实现多语言切换。
基本配置
添加依赖
dependencies:
flutter_localizations:
sdk: flutter
intl: ^0.18.1
配置 MaterialApp
MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
AppLocalizations.delegate,
],
supportedLocales: [
Locale('en', ''), // English
Locale('zh', ''), // Chinese
Locale('es', ''), // Spanish
],
locale: _currentLocale, // 当前语言
)
实现本地化
定义翻译
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 Map<String, Map<String, String>> _localizedValues = {
'en': {
'title': 'My App',
'hello': 'Hello',
'settings': 'Settings',
'language': 'Language',
'theme': 'Theme',
},
'zh': {
'title': '我的应用',
'hello': '你好',
'settings': '设置',
'language': '语言',
'theme': '主题',
},
'es': {
'title': 'Mi Aplicación',
'hello': 'Hola',
'settings': 'Ajustes',
'language': 'Idioma',
'theme': 'Tema',
},
};
String get title => _localizedValues[locale.languageCode]!['title']!;
String get hello => _localizedValues[locale.languageCode]!['hello']!;
String get settings => _localizedValues[locale.languageCode]!['settings']!;
String get language => _localizedValues[locale.languageCode]!['language']!;
String get theme => _localizedValues[locale.languageCode]!['theme']!;
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
bool isSupported(Locale locale) {
return ['en', 'zh', 'es'].contains(locale.languageCode);
}
@override
Future<AppLocalizations> load(Locale locale) async {
return AppLocalizations(locale);
}
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
语言切换
语言管理
class LanguageProvider extends ChangeNotifier {
Locale _locale = Locale('en');
Locale get locale => _locale;
void setLocale(Locale locale) {
if (!AppLocalizations.delegate.isSupported(locale)) return;
_locale = locale;
notifyListeners();
}
// 获取当前语言名称
String getCurrentLanguageName() {
switch (_locale.languageCode) {
case 'en':
return 'English';
case 'zh':
return '中文';
case 'es':
return 'Español';
default:
return 'English';
}
}
}
语言持久化
class LanguagePreferences {
static const String key = 'language_code';
static Future<void> setLanguageCode(String code) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(key, code);
}
static Future<String> getLanguageCode() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(key) ?? 'en';
}
}
完整示例
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => LanguageProvider(),
child: Consumer<LanguageProvider>(
builder: (context, languageProvider, child) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
AppLocalizations.delegate,
],
supportedLocales: [
Locale('en', ''),
Locale('zh', ''),
Locale('es', ''),
],
locale: languageProvider.locale,
home: HomePage(),
);
},
),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context);
final languageProvider = Provider.of<LanguageProvider>(context);
return Scaffold(
appBar: AppBar(
title: Text(localizations.title),
),
body: ListView(
children: [
ListTile(
title: Text(localizations.language),
subtitle: Text(languageProvider.getCurrentLanguageName()),
onTap: () => _showLanguageDialog(context),
),
Divider(),
ListTile(
title: Text(localizations.hello),
),
],
),
);
}
void _showLanguageDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(AppLocalizations.of(context).language),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: Text('English'),
onTap: () {
Provider.of<LanguageProvider>(context, listen: false)
.setLocale(Locale('en'));
Navigator.pop(context);
},
),
ListTile(
title: Text('中文'),
onTap: () {
Provider.of<LanguageProvider>(context, listen: false)
.setLocale(Locale('zh'));
Navigator.pop(context);
},
),
ListTile(
title: Text('Español'),
onTap: () {
Provider.of<LanguageProvider>(context, listen: false)
.setLocale(Locale('es'));
Navigator.pop(context);
},
),
],
),
);
},
);
}
}
最佳实践
- 文案模板用命名占位符(
"{name} 购买了 {item}"),别字符串拼接:德语、日语的语序和中文不一样,按中文语序拼死的句子翻译过去就是病句。 - 复数用
Intl.plural,别写三元表达式:中文没有复数变化,但俄语有 3 种复数形式、阿拉伯语有 6 种——count == 1 ? 'item' : 'items'这种写法一上多语言就崩。 - 日期、数字、货币一律走 intl 的
DateFormat/NumberFormat:别手拼"2024-01-01",美国人看不懂,德国人的小数点还是逗号。
注意事项
- 翻译文本长度:德语比英语长 30% 左右,按钮、Tab 写死宽度必溢出;用
Flexible、softWrap、FittedBox,别写死120.0这种宽度——每加一种语言都要跑一遍 UI 走查。 - 中文特有坑:“5 分钟前”这种相对时间别直译英文模板(
"5 minutes ago"翻成”5 分钟之前”读着别扭),用timeago包或自己写一套符合中文习惯的表达。 - RTL 语言(阿拉伯语、希伯来语):布局基本自动镜像,但
EdgeInsets要用EdgeInsetsDirectional——left/right写死会导致镜像错乱;自定义绘制和绝对定位的角标要手动处理。 - 切语言会重建整个
MaterialApp(locale 变化整树 rebuild):这是预期行为不是 bug——别在语言切换时做耗时操作,也别指望切语言后页面滚动位置还在。
实战案例:只做中文也要加 flutter_localizations
旅行类应用 是个只做中文的电商 App,但它的 pubspec 里照样有 flutter_localizations。为什么?因为这不是”做多语言才加”的依赖,而是”用了 Material 组件就得加”——GlobalMaterialLocalizations.delegate 管的是日期选择器、TextField 长按菜单这些系统文案的本地化,不加的话,中文手机上弹出的日期选择器照样显示英文月份。我最早也以为单语言 App 可以省掉它,直到在一个项目里看到英文的”Cancel/OK”按钮才去补。trade-off 在文案管理上:文案少的时候(几十条),直接写个 Map 放代码里最省事,别急着上 arb;文案一多、或者以后要接翻译平台,再迁到 flutter_intl + arb 工具链。过早引入 arb 工具链,改一条文案要跑一遍生成,对小团队是纯负担。先让 App 跑起来,再谈规范化。
总结
通过合理使用 Flutter 的国际化支持,可以轻松实现应用的多语言支持。注意在实现过程中要考虑文本适配和布局问题。
