【Flutter 实战】国际化及App 内切换语言功能

2020-11-26 16:07:48 浏览数 (1)

老孟导读:本文介绍如何实现国际化以及实现 App 内切换语言功能。

使App支持国际化

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

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

代码语言:javascript复制
MaterialApp(
  title: 'Flutter IntlApp',
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  ...
)

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

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

代码语言:javascript复制
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'];
  }
}

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

设置用于加载语言的 Delegate

代码语言:javascript复制
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
  const AppLocalizationsDelegate();

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

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

  @override
  bool shouldReload(AppLocalizationsDelegate old) => false;
}

将此 Delegate 添加到 MaterialApp:

代码语言:javascript复制
MaterialApp(
  title: 'Flutter IntlApp',
  localizationsDelegates: [
    AppLocalizationsDelegate(),
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  home: _HomePage(),
)

使用:

代码语言:javascript复制
Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('国际化:${AppLocalizations.of(context).title}'),
         ],
          ),
        ],
      ),
    )

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

重点是这句:

代码语言:javascript复制
AppLocalizations.of(context).title

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

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

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

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

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

代码语言:javascript复制
static AppLocalizations of(BuildContext context) {
  return Localizations.of<AppLocalizations>(context, AppLocalizations);
}

Localizations.of 源代码:

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

关键在 _typeToResources :

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

widget.delegates 的类型是:

代码语言:javascript复制
/// This list collectively defines the localized resources objects that can
/// be retrieved with [Localizations.of].
final List<LocalizationsDelegate<dynamic>> delegates;

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

代码语言:javascript复制
Localizations.of<AppLocalizations>(context, AppLocalizations)

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

添加系统国际化支持

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

代码语言:javascript复制
Scaffold(
      appBar: AppBar(),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('国际化:${AppLocalizations.of(context).title}'),
        ],
      ),
    )

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

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

代码语言:javascript复制
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

MaterialApp 修改如下:

代码语言:javascript复制
MaterialApp(
  title: 'Flutter IntlApp',
  localizationsDelegates: [
    AppLocalizationsDelegate(),
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  home: _HomePage(),
)

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

添加应用程序 title 国际化

按照上面的方式国际化:

代码语言:javascript复制
MaterialApp(
  title: '${AppLocalizations.of(context).title}',
  localizationsDelegates: [
    AppLocalizationsDelegate(),
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('zh'),
    const Locale('en'),
  ],
  home: _HomePage(),
)

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

修改方式是使用 onGenerateTitle:

代码语言:javascript复制
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(),
)

系统语言为英文:

系统语言为中文:

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

设置默认语言

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

代码语言:javascript复制
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(),
)

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的用法如下:

代码语言:javascript复制
MaterialApp(
      supportedLocales: [
        Locale('zh'),
        Locale('en'),
      ],
      localeListResolutionCallback: (List<Locale> locales, Iterable<Locale> supportLocales){
        print('locales:$locales');
        print('supportLocales:$supportLocales');
      },     
)

输出如下:

代码语言:javascript复制
locales:[zh_Hans_CN, ja_JP, en_GB]
supportLocales:[zh, en]

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

代码语言:javascript复制
Locale myLocale = Localizations.localeOf(context);

应用程序内切换语言

应用程序实现切换语言功能只需将 MaterialApp 中 locale 属性作为一个变量,切换不同的 Locale 即可达到切换语言的目的。代码如下:

代码语言:javascript复制
class IntlApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MyApp();
  }
}



class MyApp extends StatefulWidget {

  @override
  MyAppState createState() => MyAppState();
}

class MyAppState extends State<MyApp> {

  static _AppSetting setting = _AppSetting();

  @override
  void initState() {
    super.initState();
    setting.changeLocale = (Locale locale) {
      setState(() {
        setting._locale = locale;
      });
    };
  }

  @override
  Widget build(BuildContext context) {
    return 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');
      },
      locale: setting._locale,
      localizationsDelegates: [
        AppLocalizationsDelegate(),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('zh'),
        const Locale('en'),
      ],
      home: _HomePage(),
    );
  }
}

_HomePage 代码:

代码语言:javascript复制
class _HomePage extends StatefulWidget {
  @override
  __HomePageState createState() => __HomePageState();
}

class __HomePageState extends State<_HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('${AppLocalizations.of(context).title}'),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              RaisedButton(
                child: Text('中文'),
                onPressed: () {
                  MyAppState.setting.changeLocale(Locale('zh'));
                },
              ),
              RaisedButton(
                child: Text('英文'),
                onPressed: () {
                  MyAppState.setting.changeLocale(Locale('en'));
                },
              ),
            ],
          ),
        ],
      ),
    );
  }
}

_AppSetting 代码:

代码语言:javascript复制
class _AppSetting {
  _AppSetting();

  Null Function(Locale locale) changeLocale;
  Locale _locale;
}

0 人点赞