微信交流群

# 使App支持国际化

当应用程序支持不同语言的时候,就需要对应用程序进行国际化,当然国际化不仅仅指文字,也可以是布局图片等。Flutter 已经提供了组件来实现国际化,下面是实现国际化的步骤:

MaterialApp.supportedLocales 中添加支持的语言:

MaterialApp(
  title: 'Flutter IntlApp',
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  ...
)
1
2
3
4
5
6
7
8

比如上面的代码中,只支持英文和中文。

根据不同的语言获取不同的资源:

class AppLocalizations {
  final Locale locale;

  AppLocalizations(this.locale);

  static AppLocalizations of(BuildContext context) {
    return Localizations.of<AppLocalizations>(context, AppLocalizations);
  }

  static Map<String, Map<String, String>> _localizedValues = {
    'en': {
      'title': 'Hello World',
    },
    'zh': {
      'title': '你好',
    },
  };

  String get title {
    return _localizedValues[locale.languageCode]['title'];
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

这里只设置了一个文案,实际项目中建议不同语言存放在不同的文件中。

设置用于加载语言的 Delegate

class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
  const AppLocalizationsDelegate();

  
  bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode);

  
  Future<AppLocalizations> load(Locale locale) {
    return SynchronousFuture<AppLocalizations>(AppLocalizations(locale));
  }

  
  bool shouldReload(AppLocalizationsDelegate old) => false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

将此 Delegate 添加到 MaterialApp:

MaterialApp(
  title: 'Flutter IntlApp',
  localizationsDelegates: [
    AppLocalizationsDelegate(),
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  home: _HomePage(),
)
1
2
3
4
5
6
7
8
9
10
11

使用:

Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('国际化:${AppLocalizations.of(context).title}'),
         ],
          ),
        ],
      ),
    )
1
2
3
4
5
6
7
8
9
10

注意:Scaffold 不要添加 AppBar 数据,否则报错,具体原因下面会给出。

重点是这句:

AppLocalizations.of(context).title
1

此时,App会根据系统语言作为当前的语言。

# 系统是如何实现国际化的?

Flutter 的国际化是通过 Localizations 组件实现,上面没有用到 Localizations 组件啊,是的,App 中并没有直接使用,因为 MaterialApp 内部封装了此组件,通过 DevTools 可以查看:

Localizations 组件用于加载本地化资源、获取系统语言,Localizations 组件内部使用了 InheritedWidget 组件,当其属性即 Locale 发生变化时,其子组件将重建。

上面定义的 AppLocalizations 类内部的 of 方法:

static AppLocalizations of(BuildContext context) {
  return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
1
2
3

Localizations.of 源代码:

这段代码是获取 Type 类型(App 传入的类型为 AppLocalizations)的资源,看一下 resourcesFor 的源代码:

关键在 _typeToResources :

_typeToResources 是一个 Map 类型, _typeToResources 初始化数据的:

widget.delegates 的类型是:

/// This list collectively defines the localized resources objects that can
/// be retrieved with [Localizations.of].
final List<LocalizationsDelegate<dynamic>> delegates;
1
2
3

是否还记得 MaterialApp localizationsDelegates 属性,此 delegates 就是在 MaterialApp 中设置的值,到此我们理解了

Localizations.of<AppLocalizations>(context, AppLocalizations)
1

这句是如何获取 AppLocalizations 实例的,当然中间还有一些其他的判断,具体可自行查看源代码。

# 添加系统国际化支持

前面说到 Scaffold 不要添加 AppBar 数据,否则报错,填上看其异常信息:

Scaffold(
      appBar: AppBar(),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('国际化:${AppLocalizations.of(context).title}'),
        ],
      ),
    )
1
2
3
4
5
6
7
8
9

上面的异常效果不明显,看控制台的异常信息:

提示 MaterialLocalizations 找不到,MaterialLocalizations 是什么呢?其实它是系统组件的国际化资源,所以修复以上异常的方法是引入 MaterialLocalizations,在pubspec.yaml文件中添加包依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
1
2
3
4
5

MaterialApp 修改如下:

MaterialApp(
  title: 'Flutter IntlApp',
  localizationsDelegates: [
    AppLocalizationsDelegate(),
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  home: _HomePage(),
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

flutter_localizations 99%的概率会引入,但我们要知道这个并不是必须的。

# 添加应用程序 title 国际化

按照上面的方式国际化:

MaterialApp(
  title: '${AppLocalizations.of(context).title}',
  localizationsDelegates: [
    AppLocalizationsDelegate(),
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  home: _HomePage(),
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

直接异常了,因为此时使用的 context 是从 build 方法中传入的,而 Localizations 从 context 开始向上查找,国际化资源是在 MaterialApp 组件中的,所以无法找到 AppLocalizations。

修改方式是使用 onGenerateTitle:

MaterialApp(
  onGenerateTitle: (context) {
    return AppLocalizations.of(context).title;
  },
  localizationsDelegates: [
    AppLocalizationsDelegate(),
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  home: _HomePage(),
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

系统语言为英文:

系统语言为中文:

此方法只在 Android 上有效,iOS 上没有效果。

# 设置默认语言

如果 App 仅支持英文和中文,其他系统的语言也默认使用中文:

MaterialApp(
  onGenerateTitle: (context) {
    return AppLocalizations.of(context).title;
  },
  localeResolutionCallback:
      (Locale locale, Iterable<Locale> supportedLocales) {
    var result = supportedLocales
        .where((element) => element.languageCode == locale.languageCode);
    if (result.isNotEmpty) {
      return locale;
    }
    return Locale('zh');
  },
  localizationsDelegates: [
    AppLocalizationsDelegate(),
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  home: _HomePage(),
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

localeResolutionCallback 回调中 locale 参数表示当前系统语言,supportedLocales 表示支持的语言,即 MaterialApp.supportedLocales 设置的值。

通过这两个参数判断当然系统语言是否在支持的范围内,如果支持则返回系统语言,不支持则返回默认语言。

使用此方法也可以实现所有英语区域的国家使用英语,而国内、香港、澳门等使用中文。

# 监听系统语言切换

当更改系统语言设置时,Localizations 组件将会重新 build,而用户就看到了语言的切换,这个过程是系统完成的,代码并不需要主动去监听语言切换,但如果想监听语言切换可以通过 localeResolutionCallback 或 localeListResolutionCallback 回调来监听。通常情况下,使用localeListResolutionCallback,localeListResolutionCallback有两个参数: List<Locale> locales 和 Iterable<Locale> supportedLocales,在较新的Android系统中可以设置语言列表,List<Locale> locales就表示这个语言列表,

supportedLocales为当前应用支持的locale列表,是在MaterialApp中设置supportedLocales的值。localeListResolutionCallback返回一个Locale,此Locale表示最终使用的Locale,一般情况下在App不支持当前语言时返回一个默认值。localeListResolutionCallback的用法如下:

MaterialApp(
      supportedLocales: [
        Locale('zh'),
        Locale('en'),
      ],
      localeListResolutionCallback: (List<Locale> locales, Iterable<Locale> supportLocales){
        print('locales:$locales');
        print('supportLocales:$supportLocales');
      },     
)
1
2
3
4
5
6
7
8
9
10

输出如下:

locales:[zh_Hans_CN, ja_JP, en_GB]
supportLocales:[zh, en]
1
2

也可以通过如下代码获取当前系统语言:

Locale myLocale = Localizations.localeOf(context);
1