Flutter了解之入门篇15(国际化)

目录

  1. 国际化Material组件库
  2. 国际化开发人员的UI(Localizations)

国际化:支持多种语言。为应用程序支持的每种语言设置本地化值,如文本(语言差异)、布局(阅读方向差异)、图片(国旗)。

默认情况下,flutterSDK(为了尽可能小而且简单)中的组件仅提供美国英语值的本地化资源(文本)。为Material组件库添加其他语言的支持,须添加一个名为“flutter_localizations”的包依赖,然后在MaterialApp中进行一些配置。
/*
flutter_localizations 包(Material组件库的本地化实现)包含:
  GlobalMaterialLocalizations和GlobalWidgetsLocalizations的本地化接口的多语言实现。
*/
Material组件库和开发人员的UI都需要进行国际化:
  1. Material组件库
    比如:日历组件默认在任何环境下都会以英文显示,所以需要国际化。
    需要依赖flutter_localizations包。
  2. 开发人员的UI。
    需要实现Localizations。
/*
iOS需要在info.plist中添加Localizations项(在其中添加语言,默认有一个英文)
*/

简介

  1. 获取当前区域Locale
Locale类(包括语言和国家两个标志)用来标识用户的语言环境。
Locale('zh', 'CN') // 中文简体

获取应用的当前区域Locale

// Localizations 组件一般位于widget树中其它业务组件的顶部,它的作用是定义区域Locale以及设置子树依赖的本地化资源。 
// 如果系统的语言环境发生变化,WidgetsApp将创建一个新的Localizations 组件并重建它,这样子树中通过Localizations.localeOf(context) 获取的Locale就会更新。
Locale myLocale = Localizations.localeOf(context);
// 返回:zh_CN
myLocale.toString()
  1. 监听系统语言切换
当更改系统语言设置时,APP中的Localizations组件会重新构建,Localizations.localeOf(context) 获取的Locale就会更新,最终界面会重新build达到切换语言的效果。
因为Localizations组件内部使用了InheritedWidget ,当子组件的build函数引用了InheritedWidget时会创建对InheritedWidget的隐式依赖关系,因此当InheritedWidget发生更改时(即Localizations的Locale设置发生更改时)将重建所有依赖它的子组件。
这个过程是隐式完成的,并没有主动去监听系统语言切换。

但是有时需要在系统语言发生改变时做一些事,比如系统语言切换为一种APP不支持的语言时,需要设置一个默认的语言,这时就需要 监听locale改变事件。

可以通过MaterialApp的以下2种方法来监听locale改变的事件

方法1. localeResolutionCallback回调函数:
  Locale Function(Locale locale, Iterable<Locale> supportedLocales)

方法2. localeListResolutionCallback(推荐)回调函数:
  // 和localeResolutionCallback唯一的不同就在第一个参数类型
  Locale Function(List<Locale> locales, Iterable<Locale> supportedLocales)

说明:
  1. locale:当前手机系统设置的语言区域。
    当应用启动时或用户动态改变系统语言设置时此locale即为系统的当前locale。
    如果locale为null,则表示Flutter未能获取到设备的Locale信息,所以在使用locale之前一定要先判空。
    注意:当开发者手动指定APP的locale时(MaterialApp的locale属性),那么此locale参数代表开发者指定的locale,将忽略系统locale,不再会因为设备语言改变而发生变化。
  2. supportedLocales:当前应用支持的locale列表,MaterialApp通过supportedLocales属性注册的。
  3. 返回值:一个Locale(应用最终使用的Locale)
    通常会在不支持的语言区域时返回一个默认的Locale(默认使用语言)。

  4. locales
    用户在较新的Android系统手机设置中可以设置一个语言列表,应用通常的处理方式就是按照列表的顺序依次尝试加载相应的Locale,如果某一种语言加载成功则会停止。
    在Flutter中,应该优先使用localeListResolutionCallback,不必担心Android系统的差异性,如果在低版本的Android系统中,Flutter会自动处理这种情况,这时Locale列表只会包含一项。
  1. 引用本地化值

通过Localizations.of(context,type)来引用本地化值。
Localizations组件用于加载和查找应用当前语言下的本地化值或资源。

本地化值由Localizations组件的LocalizationsDelegates列表加载。 每个委托必须定义一个异步load() 方法,以生成封装了一系列本地化值的对象。通常这些对象为每个本地化值定义一个方法。

在大型应用程序中,不同模块或Package可能会与自己的本地化值捆绑在一起, 这就是为什么要用Localizations组件 管理对象表的原因。 要使用由LocalizationsDelegate的load方法之一产生的对象,可以指定一个BuildContext和对象的类型来找到它。
例如:Material组件库的本地化字符串由MaterialLocalizations类定义,此类的实例由MaterialApp类提供的LocalizationDelegate创建。通过如下方式获取到:
Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);

这个特殊的Localizations.of()表达式会经常使用,所以MaterialLocalizations类提供了一个便捷方法:
static MaterialLocalizations of(BuildContext context) {
  return Localizations.of<MaterialLocalizations>(context, MaterialLocalizations);
}
使用:MaterialLocalizations.of(context).国际化字段名  

1. 国际化Material组件库

第一步: 在pubspec.yaml文件中添加flutter_localizations依赖包(支持十几种语言),并下载

dependencies:
  flutter_localizations:
    sdk: flutter

第二步:配置MaterialApp(指定localizationsDelegates和supportedLocales)

import 'package:flutter_localizations/flutter_localizations.dart';
new MaterialApp(
 localizationsDelegates: [
   // 本地化的代理类
   GlobalMaterialLocalizations.delegate,
   GlobalWidgetsLocalizations.delegate,
 ],
 supportedLocales: [  // 应用支持的语言
    const Locale('en', 'US'), // 美国英语(美国地区的英文)
    const Locale('zh', 'CN'), // 中文简体
    // 其它Locales
  ],
  // ...
)

说明:
  1. 多数应用程序都是通过MaterialApp(对WidgetsApp进行了包装)为入口,低级别的WidgetsApp为入口时也可以使用相同的类和逻辑进行国际化。与MaterialApp类为入口的应用不同, 对基于WidgetsApp类为入口的应用程序进行国际化时,不需要GlobalMaterialLocalizations.delegate。
  2. localizationsDelegates列表中的元素是生成本地化值集合的工厂。
    每个委托必须定义一个异步load() 方法,以生成封装了一系列本地化值的对象。
    GlobalMaterialLocalizations.delegate为Material组件库提供的本地化的字符串和其他值,它可以使Material 组件支持多语言。GlobalMaterialLocalizations.delegate是一个产生GlobalMaterialLocalizations实例的LocalizationsDelegate。
    GlobalWidgetsLocalizations.delegate定义组件默认的文本方向,从左到右或从右到左。
  3. supportedLocales接收一个Locale数组,表示应用支持的语言列表。
    当没有精确匹配(语言和地区同时匹配)时,使用语言。
    如果没有匹配则使用supportedLocales列表项的第一个。

2. 国际化开发人员的UI(Localizations)

示例

第一步:实现Localizations资源类(提供本地化资源值,如文本)

// Locale资源类 
// 会根据当前的语言(获取本地化资源值)返回不同的文本。可以将所有需要支持多语言的文本都在此类中定义,该类的实例会在Delegate类的load方法中创建。

class DemoLocalizations {
  DemoLocalizations(this.isZh);
  // 是否为中文
  bool isZh = false;

  // 为了使用方便,定义一个静态方法
  static DemoLocalizations of(BuildContext context) {
    // MaterialApp组件内部嵌套了Localizations组件,通过第三步配置MaterialApp的localizationsDelegates,会将DemoLocalizationsDelegate传给Localizations组件
    // 获取DemoLocalizations实例
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
  }

  // Locale相关值,title为应用标题
  String get title {
    return isZh ? "Flutter应用" : "Flutter APP";
  }
  //... 其它的值  
}

/*
方式2

class DemoLocalizations {
  DemoLocalizations(this.locale);
  final Locale locale;
  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
  }
  static Map<String, Map<String, String>> _localizedValues = {
    'en': {
      'title': 'Hello World',
    },
    'es': {
      'title': 'Hola Mundo',
    },
  };
  String get title {
    return _localizedValues[locale.languageCode]['title'];
  }
}
*/

第二步:实现Delegate类(在Locale改变时会从DemoLocalizations中加载新的本地化资源值)

// Locale代理类
// Delegate类需要继承自LocalizationsDelegate类并实现相应的接口。有一个load方法。

class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations> {
  const DemoLocalizationsDelegate();

  // 是否支持某个Local
  @override
  bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode);

  // Flutter会调用此类加载相应的Locale资源类
  @override
  Future<DemoLocalizations> load(Locale locale) {
    print("$locale");
    return SynchronousFuture<DemoLocalizations>(
        DemoLocalizations(locale.languageCode == "zh")
    );
  }

  // shouldReload的返回值决定当Localizations组件重新build时,是否调用load方法重新加载Locale资源。一般情况下,Locale资源只应该在Locale切换时加载一次,不需要每次在Localizations重新build时都加载,所以返回false即可。
  // 事实上,无论shouldReload返回true还是false,每当Locale改变时Flutter都会再调用load方法加载新的Locale。
  @override
  bool shouldReload(DemoLocalizationsDelegate old) => false;

  static DemoLocalizationsDelegate delegate = const DemoLocalizationsDelegate();
}

/*
class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations> {
  const DemoLocalizationsDelegate();
  @override
  bool isSupported(Locale locale) => ['en', 'es'].contains(locale.languageCode);
  @override
  Future<DemoLocalizations> load(Locale locale) {
    return new SynchronousFuture<DemoLocalizations>(new DemoLocalizations(locale));
  }
  @override
  bool shouldReload(DemoLocalizationsDelegate old) => false;
  static DemoLocalizationsDelegate delegate = const DemoLocalizationsDelegate();
}
*/

第三步:配置MaterialApp的localizationsDelegates

在MaterialApp或WidgetsApp的localizationsDelegates列表中添加Delegate实例即可完成注册

localizationsDelegates: [
 // 本地化的代理类
 GlobalMaterialLocalizations.delegate,
 GlobalWidgetsLocalizations.delegate,
 // 注册我们的Delegate
 DemoLocalizationsDelegate(), // 或DemoLocalizationsDelegate.delegate
],

第四步:在Widget中使用本地化资源值

return Scaffold(
  appBar: AppBar(
    // 使用Locale title  
    title: Text(DemoLocalizations.of(context).title),
  ),
  ... //省略无关代码
 )

完整代码如下

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter/foundation.dart' show SynchronousFuture;

void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.yellow,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home:MyHomePage(),
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        DemoLocalizationsDelegate.delegate,
      ],
      supportedLocales: [
        const Locale('zh', 'CH'),
        const Locale('en', 'US'),
      ],
    );
  }
}
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: new Text(DemoLocalizations.of(context).title),
      ),
      body: new Center(
        child: new Text(DemoLocalizations.of(context).content),
      ),
    );
  }
}


class DemoLocalizations {
  DemoLocalizations(this.locale);
  final Locale locale;
  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
  }
  static Map<String, Map<String, String>> _localizedValues = {
    'en': {
      'title': 'Home',
      'content': 'Hello World'
    },
    'zh': {
      'title': '首页',
      'content': '世界 你好'
    },
  };
  String get title {
    return _localizedValues[locale.languageCode]['title'];
  }
  String get content {
    return _localizedValues[locale.languageCode]['content'];
  }
}
class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations> {
  const DemoLocalizationsDelegate();
  @override
  bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode);
  @override
  Future<DemoLocalizations> load(Locale locale) {
    return new SynchronousFuture<DemoLocalizations>(new DemoLocalizations(locale));
  }
  @override
  bool shouldReload(DemoLocalizationsDelegate old) => false;
  static DemoLocalizationsDelegate delegate = const DemoLocalizationsDelegate();
}
中文

英文

当要支持的语言不是两种而是8种甚至20几种时,如果为每个文本属性都要分别去判断到底是哪种Locale从而获取相应语言的文本将会是一件非常复杂的事。而且通常情况下翻译人员并不是开发人员,能不能像i18n或l10n标准那样可以将翻译单独保存为一个arb文件交由翻译人员去翻译,翻译好之后开发人员再通过工具将arb文件转为代码呢。

解决:使用Intl包

使用Intl包的好处:
  1. 轻松实现国际化
  2. 将字符串文本分离成单独的文件,方便开发人员和翻译人员分工协作。

2. 使用Intl包

第一步:添加依赖、创建必要目录

添加依赖

dependencies:
  #...省略无关项
  intl: ^0.15.7 
dev_dependencies:
   #...省略无关项
  intl_translation: ^0.17.2

说明:
  1. intl_translation包主要包含了一些工具,它在开发阶段主要主要的作用是从代码中提取要国际化的字符串到单独的arb文件和根据arb文件生成对应语言的dart代码。
  2. intl包主要是引用和加载intl_translation生成后的dart代码。

创建必要目录

在项目根目录下创建一个l10n-arb目录,该目录保存接下来通过intl_translation命令生成的arb文件。
在lib目录下创建一个l10n的目录,该目录用于保存从arb文件生成的dart代码文件。

/*
arb文件示例(通过intl_translation命令自动生成)JSON格式:
{
  "@@last_modified": "2020-09-23T12:54:51.602843",
  "@@locale":"zh_CH",
  "title": "Flutter应用",
  "@title": {
    "description": "Title for the Demo application",
    "type": "text",
    "placeholders": {}
  }
 }
*/

第二步:实现Localizations类(添加需要国际化的属性)和Delegate类

在lib/l10n目录下新建一个“localization_intl.dart”的文件

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'messages_all.dart'; // 该文件会由intl_translation命令生成(从arb文件生成的dart代码)

// 可以在DemoLocalizations类中添加需要国际化的属性或方法
class DemoLocalizations {
  static Future<DemoLocalizations> load(Locale locale) {
    final String name = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
    final String localeName = Intl.canonicalizedLocale(name);
    // intl_translation命令会生成对应的方法
    // initializeMessages()用来加载翻译的字符串
    return initializeMessages(localeName).then((b) {
      Intl.defaultLocale = localeName;
      return new DemoLocalizations();
    });
  }

  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
  }
  
  // 添加属性
  String get title {
    return Intl.message(  // Intl.message用来查找
      'Flutter APP',
      name: 'title',
      desc: 'Title for the Demo application',
    );
  }
/*
  // 添加方法
  String helloTitle(String name) {
    return Intl.message(  // Intl.message用来查找
      'hello $name',
      name: 'helloTitle',
      desc: 'helloTitle',
      args:[name],
    );
  }
*/
  // Intl.plural方法可以在howMany值不同时输出不同的提示信息
  remainingEmailsMessage(int howMany) => Intl.plural(howMany,
      zero: 'There are no emails left',
      one: 'There is $howMany email left',
      other: 'There are $howMany emails left',
      name: "remainingEmailsMessage",
      args: [howMany],
      desc: "How many emails remain after archiving.",
      examples: const {'howMany': 110, 'userName': 'Fred'});
}


// Locale代理类
class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations> {
  const DemoLocalizationsDelegate();

  // 是否支持某个Local
  @override
  bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode);

  // Flutter会调用此类加载相应的Locale资源类
  @override
  Future<DemoLocalizations> load(Locale locale) {
    return  DemoLocalizations.load(locale);
  }

  // 当Localizations Widget重新build时,是否调用load重新加载Locale资源.
  @override
  bool shouldReload(DemoLocalizationsDelegate old) => false;

  static DemoLocalizationsDelegate delegate = const DemoLocalizationsDelegate();
}

第三步: 生成arb文件、根据arb文件生成dart代码

生成arb文件

提取localization_intl.dart代码中的字符串到一个arb文件(通intl_translation包的工具),运行如下命令:
flutter pub pub run intl_translation:extract_to_arb --output-dir=l10n-arb lib/l10n/localization_intl.dart

运行此命令后,会将之前通过Intl包的API标识的属性和字符串提取到“l10n-arb/intl_messages.arb”文件中,看看其内容:
{
  "@@last_modified": "2020-09-23T12:54:51.602843",
  "title": "Flutter APP",
  "@title": {
    "description": "Title for the Demo application",
    "type": "text",
    "placeholders": {}
  },
  "remainingEmailsMessage": "{howMany,plural, =0{There are no emails left}=1{There is {howMany} email left}other{There are {howMany} emails left}}",
  "@remainingEmailsMessage": {
    "description": "How many emails remain after archiving.",
    "type": "text",
    "placeholders": {
      "howMany": {
        "example": 110
      }
    }
  }
}

// 中文简体:intl_zh_Hans_CN.arb
// const Locale.fromSubtags(languageCode: 'zh',scriptCode: 'Hans',countryCode: 'CN'),
这个是默认的Locale资源文件,如果现在要支持中文大陆,只需要在该文件同级目录创建一个"intl_zh_CN.arb"文件,然后将"intl_messages.arb"的内容拷贝到"intl_zh_CN.arb"文件,接下来将英文翻译为中文即可,翻译后的"intl_zh_CN.arb"文件内容如下:
{
  "@@last_modified": "2018-12-10T15:46:20.897228",
  "@@locale":"zh_CN",
  "title": "Flutter应用",
  "@title": {
    "description": "Title for the Demo application",
    "type": "text",
    "placeholders": {}
  },
  "remainingEmailsMessage": "{howMany,plural, =0{没有未读邮件}=1{有{howMany}封未读邮件}other{有{howMany}封未读邮件}}",
  "@remainingEmailsMessage": {
    "description": "How many emails remain after archiving.",
    "type": "text",
    "placeholders": {
      "howMany": {
        "example": 42
      }
    }
  }
}

必须要翻译title和remainingEmailsMessage字段,description是该字段的说明,通常给翻译人员看,代码中不会用到。

注意:
    1. 如果某个特定的arb中缺失某个属性,那么应用将会加载默认的arb文件(intl_messages.arb)中的相应属性,这是Intl的托底策略。
    2. 每次运行提取命令时,intl_messages.arb都会根据代码重新生成,但其他arb文件不会,所以当要添加新的字段或方法时,其他arb文件是增量的,不用担心会覆盖。
    3. arb文件是标准的。通常会将arb文件交给翻译人员,当他们完成翻译后,再通过下面的步骤根据arb文件生成最终的dart代码。

生成dart代码

根据arb生成dart文件:
flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/l10n/localization_intl.dart l10n-arb/intl_*.arb

在首次运行时会在"lib/l10n"目录下生成多个文件,对应多种Locale,这些代码便是最终要使用的dart代码。

优化第三步

在根目录下创建一个intl.sh的脚本,内容为:
flutter pub pub run intl_translation:extract_to_arb --output-dir=l10n-arb lib/l10n/localization_intl.dart
flutter pub pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/l10n/localization_intl.dart l10n-arb/intl_*.arb

授予执行权限:
chmod +x intl.sh

执行intl.sh
./intl.sh

完整代码

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'l10n/localization_intl.dart';

void main() {
  runApp(MyApp());
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.yellow,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home:MyHomePage(),
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        DemoLocalizationsDelegate.delegate,
      ],
      supportedLocales: [
        const Locale('zh', 'CH'),
        const Locale('en', 'US'),
      ],
    );
  }
}
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: new Text(DemoLocalizations.of(context).title),
      ),
      body: new Center(
        child: new Text(DemoLocalizations.of(context).content),
      ),
    );
  }
}

localization_intl.dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'messages_all.dart'; // intl_translation从arb文件生成的dart代码

class DemoLocalizations {
  static Future<DemoLocalizations> load(Locale locale) {
    final String name = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
    final String localeName = Intl.canonicalizedLocale(name);
    // initializeMessages()方法和"messages_all.dart"文件一样,是同时生成的。
    // initializeMessages()用来加载翻译的字符串
    return initializeMessages(localeName).then((b) {
      Intl.defaultLocale = localeName;
      return new DemoLocalizations();
    });
  }
  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
  }

  // Intl.message用来查找
  String get title {
    return Intl.message(
      'Home',
      name: 'title',
      desc: 'Title for the Demo application',
    );
  }
  String get content {
    return Intl.message(
      'Hello world',
      name: 'content',
      desc: 'Content for the Demo application',
    );
  }
  remainingEmailsMessage(int howMany) => Intl.plural(howMany,
      zero: 'There are no emails left',
      one: 'There is $howMany email left',
      other: 'There are $howMany emails left',
      name: "remainingEmailsMessage",
      args: [howMany],
      desc: "How many emails remain after archiving.",
      examples: const {'howMany': 110, 'userName': 'Fred'});
}

// Locale代理类
class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations> {
  const DemoLocalizationsDelegate();
  //是否支持某个Local
  @override
  bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode);
  // Flutter会调用此类加载相应的Locale资源类
  @override
  Future<DemoLocalizations> load(Locale locale) {
    return  DemoLocalizations.load(locale);
  }
  // 当Localizations Widget重新build时,是否调用load重新加载Locale资源.
  @override
  bool shouldReload(DemoLocalizationsDelegate old) => false;
  static DemoLocalizationsDelegate delegate = const DemoLocalizationsDelegate();
}

常见问题

  1. 默认语言区域不对

在一些非大陆行货渠道买的一些Android和iOS设备,会出现默认的Locale不是中文简体的情况。这属于正常现象,但是为了防止设备获取的Locale与实际的地区不一致,所有的支持多语言的APP都必须提供一个手动选择语言的入口。

  1. 对应用标题进行国际化

MaterialApp有一个title属性,用于指定APP的标题。在Android系统中,APP的标题会出现在任务管理器中。所以也需要对title进行国际化。

但是问题是很多国际化的配置都是在MaterialApp上设置的,无法在构建MaterialApp时通过Localizations.of来获取本地化资源,如:
MaterialApp(
  title: DemoLocalizations.of(context).title, //不能正常工作!
  localizationsDelegates: [
    // 本地化的代理类
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    DemoLocalizationsDelegate() // 设置Delegate
  ],
);
运行后,DemoLocalizations.of(context).title会报错,这里DemoLocalizations.of(context)会返回null,这里的context找不到MaterialApp,继而找不到DemoLocalizationsDelegate。


只需要设置一个onGenerateTitle回调即可:
MaterialApp(
  onGenerateTitle: (context){
    // 此时context在Localizations的子树中
    return DemoLocalizations.of(context).title;
  },
  localizationsDelegates: [
    DemoLocalizationsDelegate(),
    ...
  ],
);
  1. 为英语系的国家指定同一个locale

英语系的国家非常多,如美国、英国、澳大利亚等,这些英语系国家虽然说的都是英语,但也会有一些区别。如果APP只想提供一种英语(如美国英语)供所有英语系国家使用则在localeListResolutionCallback中来做兼容。

localeListResolutionCallback:
    (List<Locale> locales, Iterable<Locale> supportedLocales) {
  // 判断当前locale是否为英语系国家,如果是直接返回Locale('en', 'US')     
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342