天天看點

Flutter學習記錄——21.應用國際化處理

文章目錄

  • ​​1.實作應用國際化​​
  • ​​2.使用插件庫實作應用國際化​​

國際化就是讓我們的應用支援多種語言,例如運作在國内的使用中文簡體、在港澳台的使用繁體字、美國的使用英文、日本的使用者顯示的是日文等等類似場景,也可以把國際化稱為本地化處理。Flutter 本身的 API 是支援國際化處理的,當然也可以用官方提供的插件庫來實作。

那麼這節課我們将介紹 Flutter 中應用國際化處理的基本使用詳解,并配合一些案例。

1.實作應用國際化

如果我們的應用想提供多種語言模式,那麼就需要進行國際化處理,Flutter 本身是支援國際化處理的。

在 Flutter 中使用國際化一般要配合 MaterialApp 或 WidgetsApp 的國際化屬性localizationsDelegates 和 supportedLocales。并且在 pubspec.yaml 配置 flutter_localizations 的一個單獨包。截至 2017 年 10 月,該軟體包支援 15 種語言(來源于官方)。

接下來我們看下 Flutter 實作國際化的步驟。

首先需要配置 pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  // 添加國際化包
  flutter_localizations:
    sdk:      

接下來在使用的頁面導入包:

import 'package:flutter_localizations/flutter_localizations.dart';
使用 MaterialApp 或 WidgetsApp 的屬性來配置:

class LocalizationsSamplesState extends State<LocalizationsSamples> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        // 這裡要自己實作一個Localizations.delegate
      ],
      supportedLocales: [
        const Locale('en', 'US'), // English
        const Locale('zh', 'CN'), // Chinese
        // ... 其他語言支援
      ],
      home: getBody(),
    );
  }
...
}      

supportedLocales 裡定義的是語種,Locale 來定義語言語種,參數包括語言和國家兩個标志。

接下來我們需要自己實作一個 GlobalMaterialLocalizations,本地化需要 Localizations 和Delegate 兩個類才可以,我們先實作 Localizations:

import 'package:flutter/widgets.dart';

class PageLocalizations {
  final Locale locale;
  PageLocalizations(this.locale);

  static Map<String, Map<String, String>> _localizedValues = {
    'en': {
      'task title': 'Flutter Demo',
      'titlebar title': 'Flutter Demo Home Page',
      'click tip': 'You have pushed the button this many times:',
      'inc': 'Increment'
    },
    'zh': {
      'task title': 'Flutter 示例',
      'titlebar title': 'Flutter 示例首頁面',
      'click tip': '你一共點選了這麼多次按鈕:',
      'inc': '增加'
    }
  };

  get taskTitle {
    return _localizedValues[locale.languageCode]['task title'];
  }

  get titleBarTitle {
    return _localizedValues[locale.languageCode]['titlebar title'];
  }

  get clickTop {
    return _localizedValues[locale.languageCode]['click tip'];
  }

  get inc {
    return _localizedValues[locale.languageCode]['inc'];
  }

  static PageLocalizations of(BuildContext context) {
    return Localizations.of(context, PageLocalizations);
  }
}      

接下來實作 Delegate:

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_samples/samples/pageLocalizations.dart';

class GlobalPagesLocalizations
    extends LocalizationsDelegate<PageLocalizations> {
  const GlobalPagesLocalizations();

  // 是否支援某個語言
  @override
  bool isSupported(Locale locale) {
    return ['en', 'zh'].contains(locale.languageCode);
  }

    // 加載對應的語言資源,自動調用
  @override
  Future<PageLocalizations> load(Locale locale) {
    return new SynchronousFuture<PageLocalizations>(
        new PageLocalizations(locale));
  }

  // 重新加載
  @override
  bool shouldReload(LocalizationsDelegate<PageLocalizations> old) {
    return false;
  }

  static GlobalPagesLocalizations delegate = const GlobalPagesLocalizations();
}
最後調用使用即可:

class LocalizationsSamples extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return LocalizationsSamplesState();
  }
}

class LocalizationsSamplesState extends State<LocalizationsSamples> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalPagesLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('en', 'US'), // English
        const Locale('zh', 'CN'), // Chinese
        // ... 其他語言支援
      ],
      home: WelcomePage(),
    );
  }
}

class WelcomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return WelcomeState();
  }
}

class WelcomeState extends State<WelcomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Localizations'),
        primary: true,
      ),
      body: Column(
        children: <Widget>[
          // 調用國際化後的屬性資源
          Text(PageLocalizations.of(context).taskTitle,)
        ],
      ),
    );
  }
}      

可以看到調用的方式是:​

​PageLocalizations.of(context).taskTitle​

​。

這樣,當我們的手機在切換語言環境時,應用便會自動顯示目前語言環境下的字元資源,達到國際化目的。

2.使用插件庫實作應用國際化

接下來我們看下通過插件庫來實作的應用國際化的用法,如 intl 和 flutter_i18n 插件。這兩個插件庫都是 Flutter 官方的,這兩個基本原理是一樣的,隻不過 flutter_i18n 是自動化執行了将 arb檔案轉為 dart 的等操作。那麼這裡直接以 flutter_i18n 插件為例給大家講解通過插件庫實作應用國際化。這裡需要使用 Android Studio 安裝一個 Flutter i18n 插件:

Flutter學習記錄——21.應用國際化處理

Flutter i18n 插件可以幫助我們簡化手動編寫 arb 和生成 dart 這個過程。其原理是通過 arb 檔案來自動生成所需要的代碼。

插件安裝完後悔自動出現一個按鈕,按這個按鈕就可以自動根據項目根目錄的 res 裡的 arb 檔案來自動在 lib/generated 生成名字叫 i18n.dart 的 dart 檔案。

Flutter學習記錄——21.應用國際化處理

我們可以右鍵建立新的需要支援的語言 arb 檔案。

Flutter學習記錄——21.應用國際化處理

我們分别看下 strings_en.arb、strings_zh_CN.arb、i18n.dart 檔案内容:

// i18n.dart檔案内容,這個是自動生成

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

// ignore_for_file: non_constant_identifier_names
// ignore_for_file: camel_case_types
// ignore_for_file: prefer_single_quotes

//This file is automatically generated. DO NOT EDIT, all your changes would be lost.
class S implements WidgetsLocalizations {
  const S();

  static const GeneratedLocalizationsDelegate delegate =
    GeneratedLocalizationsDelegate();

  static S of(BuildContext context) => Localizations.of<S>(context, S);

  @override
  TextDirection get textDirection => TextDirection.ltr;

  String get appName => "App Name";
  String get title => "My Title";
  String hello(String name) => "Hello $name";
}

class $zh_HK extends S {
  const $zh_HK();

  @override
  TextDirection get textDirection => TextDirection.ltr;

  @override
  String get appName => "應用名";
  @override
  String get title => "我的標題";
  @override
  String hello(String name) => "妳好${name}";
}

class $en extends S {
  const $en();
}

class $zh_CN extends S {
  const $zh_CN();

  @override
  TextDirection get textDirection => TextDirection.ltr;

  @override
  String get appName => "應用名";
  @override
  String get title => "我的标題";
  @override
  String hello(String name) => "你好${name}";
}

class GeneratedLocalizationsDelegate extends LocalizationsDelegate<S> {
  const GeneratedLocalizationsDelegate();

  List<Locale> get supportedLocales {
    return const <Locale>[
      Locale("zh", "HK"),
      Locale("en", ""),
      Locale("zh", "CN"),
    ];
  }

  LocaleListResolutionCallback listResolution({Locale fallback}) {
    return (List<Locale> locales, Iterable<Locale> supported) {
      if (locales == null || locales.isEmpty) {
        return fallback ?? supported.first;
      } else {
        return _resolve(locales.first, fallback, supported);
      }
    };
  }

  LocaleResolutionCallback resolution({Locale fallback}) {
    return (Locale locale, Iterable<Locale> supported) {
      return _resolve(locale, fallback, supported);
    };
  }

  Locale _resolve(Locale locale, Locale fallback, Iterable<Locale> supported) {
    if (locale == null || !isSupported(locale)) {
      return fallback ?? supported.first;
    }

    final Locale languageLocale = Locale(locale.languageCode, "");
    if (supported.contains(locale)) {
      return locale;
    } else if (supported.contains(languageLocale)) {
      return languageLocale;
    } else {
      final Locale fallbackLocale = fallback ?? supported.first;
      return fallbackLocale;
    }
  }

  @override
  Future<S> load(Locale locale) {
    final String lang = getLang(locale);
    if (lang != null) {
      switch (lang) {
        case "zh_HK":
          return SynchronousFuture<S>(const $zh_HK());
        case "en":
          return SynchronousFuture<S>(const $en());
        case "zh_CN":
          return SynchronousFuture<S>(const $zh_CN());
        default:
          // NO-OP.
      }
    }
    return SynchronousFuture<S>(const S());
  }

  @override
  bool isSupported(Locale locale) =>
    locale != null && supportedLocales.contains(locale);

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

String getLang(Locale l) => l == null
  ? null
  : l.countryCode != null && l.countryCode.isEmpty
    ? l.languageCode
    : l.toString();      

再看下 strings_en.arb、strings_zh_CN.arb 檔案内容:

// strings_en.arb
{
  "appName": "App Name",
  "hello": "Hello $name",
  "title": "My Title"
}
// strings_zn_CN.arb
{
  "appName": "應用名",
  "hello": "你好${name}",
  "title": "我的标題"
}      

可以看到我們也可以通過 $ 符号來進行傳遞動态值。

flutter_i18n 插件庫位址:https://pub.dev/packages/flutter_i18n。

首先我們需要引用這個庫:

dependencies:
  flutter_i18n: ^0.6.3      

在使用的地方導入庫:

import 'package:flutter_i18n/flutter_i18n.dart';      

然後我們看下使用的方式:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_app/generated/i18n.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

class LocalizationsSamples extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return LocalizationsSamplesState();
  }
}

class LocalizationsSamplesState extends State<LocalizationsSamples> {
      Locale _locale = const Locale('zh', 'CN');

  @override
  void initState() {
    super.initState();
    localeChange = (locale) {
      setState(() {
        _locale = locale;
      });
    };
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        // 配置delegate
        S.delegate,
      ],
      supportedLocales: [
        // 支援的語言
        const Locale('en', ''), // English
        const Locale('zh', 'CN'), // Chinese
        const Locale("zh", "HK"),
        // ... 其他語言支援
      ],
      // 我們也可以指定一種預設語言
      localeResolutionCallback:
          S.delegate.resolution(fallback: const Locale('en', '')),
      home: WelcomePage(),
    );
  }
}

class WelcomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return WelcomeState();
  }
}

class WelcomeState extends State<WelcomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Localizations'),
        primary: true,
      ),
      body: Column(
        children: <Widget>[
          // 調用國際化後的屬性資源
          Text(
            S.of(context).title,
          )
        ],
      ),
    );
  }
}      

主動切換語言:

FlutterI18n.refresh(context, Locale('en', ''));      
FlutterI18n.translate(buildContext, "your.key")

FlutterI18n.plural(buildContext, "select", 0)