最近刚把公司项目的国际化搞完,顺便记录下Flutter项目国际化的几个步骤,来供大家参考。
App 项目为什么需要国际化?
公司项目的国际化需求来得很突然,就是因为要参加国外的展会,方便展示给客户使用App的操作步骤。正常情况下国际化无非是通过将应用程序本地化为不同的语言和地区,可以使应用在全球范围内更具吸引力,从而扩大市场覆盖范围。当你的应用能够以用户熟悉和舒适的语言呈现时,吸引更多的用户成为可能,同时也提升用户的使用体验,或者是遵守当地法律法规及政策的要求。
国际化需要做哪些准备?
以下是示例项目所在的电脑的开发环境。
joe@wf ~ % flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[!] Flutter (Channel unknown, 3.7.1, on macOS 13.6 22G120 darwin-arm64 (Rosetta), locale zh-Hans-CN)
Dart SDK
joe@wf ~ % dart --version
Dart SDK version: 2.19.1 (stable) (Tue Jan 31 12:25:35 2023 +0000) on "macos_arm64
示例项目的 pubspec.yaml
中的 environment
配置:
environment:
sdk: ">=2.17.0 <3.0.0"
在你的 pubspec.yaml
文件中添加 intl
和 intl_utils
依赖:
dev_dependencies:
intl: ^0.17.0
intl_utils: ^2.4.0
使用 intl_utils
第三方工具的目的是给官方 Dart Intl
库生成样板代码,并为 Dart
代码中的键添加自动完成的功能。
flutter_intl:
enabled: true
class_name: S
main_locale: en
output_dir: lib/language/generated
arb_dir: lib/language/l10n
-
output_dir
:模板代码生成路径; -
arb_dir
:是.arb
文件存放的路径。
每次编辑 .arb
文件保存后会自动生成模板代码保存到 output_dir
路径下面。
运行 flutter pub get
指令后,自动生成的文件目录如下:
根据需要创建 .arb
文件编辑好内容后,保存文件就会在 lib/language/generated
目录下自动生成模板代码,下面以 intl_zh.arb
和 intl_en.arb
为例来修改。
修改项目入口的Widget
在 mian()
中的第一个 Widget
-> build
函数中添加如下3个地方。
void main() {
runApp(App());
}
class App extends StatelessWidget {
App({Key? key}) : super(key: key)
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter 技术实践',
theme: lightTheme,
darkTheme: darkTheme,
themeMode: ThemeMode.system,
onGenerateRoute: MyRoutes.router.generator,
initialRoute: MyRoutes.root,
debugShowCheckedModeBanner: false,
supportedLocales: S.delegate.supportedLocales, // 1
localizationsDelegates: const [ // 2
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
localeResolutionCallback:
(Locale? locale, Iterable<Locale> supportedLocales) { // 3
Locale currentLocale =
Locale.fromSubtags(languageCode: locale?.languageCode ?? "zh");
return supportedLocales.contains(currentLocale)
? currentLocale
: const Locale.fromSubtags(languageCode: "zh");
},
);
}
}
这里的localeResolutionCallback: (Locale? locale, Iterable<Locale> supportedLocales)
函数是设备系统或者浏览器语言环境发生改变的时候的回调,其中 locale
参数是改变后的语言,supportedLocales
是项目中所有支持的语言环境,如果本地未找到支持的语言就默认显示中文,也就是 languageCode: "zh"
。
使用 S.of(context).{key} 替换项目中文案
这里的 key 就是我们在 .arb 的文件定义的 key。
{
"nav_tab": "Tab Pages",
"nav_news": "News List",
"nav_route": "Route Navigation And Parameter Transfer",
"nav_completer": "Use Completer And Compute in Flutter",
"nav_extension": "Chrome Extension",
"tab_home": "Home",
"tab_search": "Search",
"tab_favor": "Favor",
"tab_setting": "Setting"
}
intl_utils
帮我们生成了模板代码,使用的时候有代码提示。
如何在应用内部切换语言
在应用内部切换语言的时候,就需要用到全局的状态管理,如Provider,当应用内部收到切换语言的操作后,会通知所有依赖它的 Widget 进行刷新,达到显示更换后的语言,我们也来实现一下。
在 pubspec.yaml
中引入 provider
依赖:
dependencies:
provider: ^6.1.2
创建类 AppLanguageProvider
继承自 ChangeNotifier
,用来记录当前的应用内的语言,以及当执行切换语言的操作时,调用 notifyListeners()
来刷新。
import 'package:flutter/cupertino.dart';
enum LanguageCode { zh, en }
class AppLanguageProvider extends ChangeNotifier {
LanguageCode languageCode = LanguageCode.zh;
changeLanguage(LanguageCode languageCode) {
this.languageCode = languageCode;
notifyListeners();
}
}
将 AppLanguageProvider
挂在项目入口的 Widget
来作为管理全局语言环境状态,并在 Builder
回调中监听当前的 languageCode
的值(context.watch<AppLanguageProvider>().languageCode
),再根据当前的 languageCode
值获得到的 Locale
赋值给 MaterialApp -> locale
属性。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// 第一步
ChangeNotifierProvider(create: (_) {
return AppLanguageProvider();
}),
],
builder: (BuildContext context, Widget? child) {
// 第二步
LanguageCode languageCode =
context.watch<AppLanguageProvider>().languageCode;
return MaterialApp(
title: 'Flutter 技术实践',
theme: lightTheme,
darkTheme: darkTheme,
themeMode: ThemeMode.system,
onGenerateRoute: MyRoutes.router.generator,
initialRoute: MyRoutes.root,
debugShowCheckedModeBanner: false,
supportedLocales: S.delegate.supportedLocales,
// 第三步
locale: Locale.fromSubtags(languageCode: languageCode.name),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
localeResolutionCallback:
(Locale? locale, Iterable<Locale> supportedLocales) {
Locale currentLocale =
Locale.fromSubtags(languageCode: locale?.languageCode ?? "zh");
return supportedLocales.contains(currentLocale)
? currentLocale
: const Locale.fromSubtags(languageCode: "zh");
},
);
},
);
}
}
最后,在切换语言操作的地方执行如下代码:
Provider.of<AppLanguageProvider>(context, listen: false).changeLanguage(LanguageCode.zh);
最终效果:
好了,以上就是Flutter项目国际化的全部实现过程,示例代码和查看效果在 https://flutter.nnxkcloud.com 上,另外,如果有更好的实现方式,也欢迎留言交流一下。