Flutter - 国际化

环境

Flutter 3.29

macOS Sequoia 15.4.1

Xcode 16.3

配置

安装依赖包

默认情况下,Flutter只提供美式英语的本地化,可以通过flutter_localizations这个package来实现国际化。

创建flutter工程后执行

sh 复制代码
¥ flutter pub add flutter_localizations --sdk=flutter
¥ flutter pub add intl:any

执行后的pubspec.yaml文件的效果

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  flutter_localizations:
    sdk: flutter
  intl: any

安装对应的package

sh 复制代码
¥ flutter pub get 
pubspec.yaml文件

在 Flutter 项目的根目录中添加一个新的 yaml 文件,命名为 l10n.yaml,其内容如下:

yaml 复制代码
arb-dir: lib/common/localization/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart 
  1. 将 应用资源包 (.arb) 的输入路径指定为 ${FLUTTER_PROJECT}/lib/common/localization/l10n。
  2. 将英文的语言模板设定为 app_en.arb。
  3. 指定 Flutter 生成本地化内容到 app_localizations.dart 文件。

要在pubspec.yaml 文件中,设置generate为true,执行命令时才会生成对应的app_localizations.dart文件

yaml 复制代码
flutter:
    generate: true

指定这个是为了生成项目时能自动生成对应的.dart代码文件

${FLUTTER_PROJECT}/lib/common/localization/l10n 添加 app_en.arb 模板文件

json 复制代码
{
    "helloWorld": "Hello World!"
}

在同一目录下添加中文的模板文件 app_zh.arb

json 复制代码
{
    "helloWorld":"你好,世界"
}

执行命令生成.dart文件

sh 复制代码
¥ flutter run

运行报错

sh 复制代码
Synthetic package output (package:flutter_gen) is deprecated: https://flutter.dev/to/flutter-gen-deprecation. In a future release, synthetic-package will
default to `false` and will later be removed entirely.
Generating synthetic localizations package failed with 1 error:

Error: Attempted to generate localizations code without having the flutter: generate flag turned on.
Check pubspec.yaml and ensure that flutter: generate: true has been added and rebuild the project. Otherwise, the localizations source code will not be
importable.

有两个问题,一个是使用flutter_gen生成synthetic package 的方式已经过期,推荐使用在 Flutter 项目中指定的目录下生成国际化资源代码文件。该标志默认为 true,还有一个报错是因为pubspec.yaml没有设置允许自动生成代码

解决方案: l10n.yaml文件中追加设置不使用Synthetic package的方式生成国际化资源文件

yaml 复制代码
synthetic-package: false

解决方案: pubspec.yaml设置允许自动生成代码

yaml 复制代码
flutter:
  generate: true

重新运行 flutter run

报错消失,并在l10n目录下生成对应的.dart代码文件

打开 app_localizations.dart

dart 复制代码
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;

import 'app_localizations_en.dart';
import 'app_localizations_zh.dart';


abstract class AppLocalizations {
  AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());

  final String localeName;

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

  static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
    
  /// 国际化代理对象的集合
  /// 代理中实现支持哪些语言,是否重新加载
  static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
    delegate,
    /// Material风格的组件的国际化
    GlobalMaterialLocalizations.delegate,
    /// iOS风格的组件的国际化
    GlobalCupertinoLocalizations.delegate,
    /// Widget组件的国际化
    GlobalWidgetsLocalizations.delegate,
  ];

  /// A list of this localizations delegate's supported locales.
  /// 支持的语言列表
  static const List<Locale> supportedLocales = <Locale>[
    Locale('en'),
    Locale('zh')
  ];
  
  /// 国际化的词条
  String get helloWorld;
}

/// 自定义的词条的代理对象
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
  const _AppLocalizationsDelegate();

  @override
  Future<AppLocalizations> load(Locale locale) {
  /// 同步加载资源
    return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
  }

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

  @override
  /// Returns true if the resources for this delegate should be loaded again by calling the [load] method.

  /// 调用load方法后是否要重新加载
  bool shouldReload(_AppLocalizationsDelegate old) => false;
}

AppLocalizations lookupAppLocalizations(Locale locale) {


  // Lookup logic when only language code is specified.
  switch (locale.languageCode) {
    case 'en': return AppLocalizationsEn();
    case 'zh': return AppLocalizationsZh();
  }

  throw FlutterError(
    'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
    'an issue with the localizations generation tool. Please file an issue '
    'on GitHub with a reproducible sample app and the gen-l10n configuration '
    'that was used.'
  );
}

使用

场景: 系统的Widgets

使用系统的组件,比如日历

dart 复制代码
@override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "localization示例",
      /// 1. 设置代理对象列表
      /// 一般开发都会有业务相关的词条,会创建词条,根据上面的代码可以传AppLocalizations.localizationsDelegates
      /// 没有其它自定义词条时也可以
      /// GlobalMaterialLocalizations.delegate,
      /// GlobalWidgetsLocalizations.delegate,
      /// GlobalCupertinoLocalizations.delegate,
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      /// 2. 设置支持语言
      supportedLocales: [Locale('en'), Locale('zh')],
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: MyHomePage(title: 'home page'),
      locale: Locale("zh"), /// 设置应用是中文还是英文,默认跟随系统
    );
  }
dart 复制代码
...
children: <Widget>[
    CalendarDatePicker(
      initialDate: DateTime.now(),
      firstDate: DateTime(2020, 3, 30),
      lastDate: DateTime(2025, 6, 31),
      onDateChanged: (value) => {logger.d(value.toString())},
    ),
],
...

locale参数传Locale("en") 再次运行

场景: 自定义

实现业务过程中相关的自定义的词条

dart 复制代码
...
return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(AppLocalizations.of(context)!.helloWorld),
      ),
...

效果同上,中文时显示你好,世界,英文时显示Hello World

场景: 切换语言

添加get依赖

pubspec.yaml 复制代码
get: 4.7.2
dart 复制代码
@override
  Widget build(BuildContext context) {
    /// 1.MaterialApp => GetMaterialApp
    return GetMaterialApp(
      title: "localization示例",
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: [Locale('en'), Locale('zh')],
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: MyHomePage(title: 'home page'),
      locale: Locale("en"),
    );
  }
dart 复制代码
/// 使用updateLocale来设置语言
if (Get.locale?.languageCode == "zh") {
    Get.updateLocale(Locale('en'));
} else {
    Get.updateLocale(Locale('zh'));
}

场景: 占位符,复数和选项

当需要再词条中接收变量时可以使用占位符

json 复制代码
"hello": "Hello {userName}"
"@hello": {
  "description": "A message with a single parameter",
  "placeholders": {
    "userName": {
      "type": "String",
      "example": "Bob"
    }
  }
}
json 复制代码
 "hello": "你好 {userName}",
 "@hello": {
    "description": "A message with a single parameter",
    "placeholders": {
        "userName": {
        "type": "String",
        "example": "张三"
        }
    }
 }
dart 复制代码
...
Text(AppLocalizations.of(context)!.hello("Programmer")),
...

你还可以使用数字占位符来指定多个值。不同的语言有不同的单词复数化形式。该语法还支持指定单词的复数化形式。

json 复制代码
"{countPlaceholder, plural, =0{message0} =1{message1} =2{message2} few{messageFew} many{messageMany} other{messageOther}}"

countPlaceholder: 变量名

plural: 固定搭配

=0{message0} =1{message1}...: 参数0时显示message0,参数1时显示message1

app_en.arb

json 复制代码
"people": "{count, plural, =0{no people} =1{person} other{people} }",
"@people": {
  "description": "A plural people",
  "placeholders": {
    "count": {
      "type": "num",
      "format": "compact"
    }
  }
}

app_zh.arb

json 复制代码
 "people": "{count, plural, =0{没有人} =1{一个人} other{人群}}",
"@people": {
  "description": "A plural people",
  "placeholders": {
    "count": {
      "type": "num",
      "format": "compact"
    }
  }
}

other的情况必须填写,不然会报

app_en.arb:people\] ICU Syntax Error: Plural expressions must have an "other" case. {count, plural, =0{no people} =1{person} few{some people} } \^ \[app_zh.arb:people\] ICU Syntax Error: Plural expressions must have an "other" case. {count, plural, =0{没有人} =1{一个人} few{一些人} } \^ Found syntax errors.

添加词条后可以在.arb文件中右键然后选择Generate Localizations 生成国际化的.dart文件

dart 复制代码
...
Text(AppLocalizations.of(context)!.people(0)),
Text(AppLocalizations.of(context)!.people(1)),
Text(AppLocalizations.of(context)!.people(2)),
...

也可以使用String占位符来表示选择一个值,类似词条使用case语句的感觉

json 复制代码
"{selectPlaceholder, select, case{message} ... other{messageOther}}"

selectPlaceholder: 变量名

select: 固定搭配

case...other: 情况1,情况2,其它情况

json 复制代码
"pronoun": "{gender, select, male{he} other{she}}",
"@pronoun": {
  "description": "A gendered message",
  "placeholders": {
    "gender": {
      "type": "String"
    }
  }
}
json 复制代码
"pronoun": "{gender, select, male{他} other{她}}",
"@pronoun": {
  "description": "A gendered message",
  "placeholders": {
    "gender": {
      "type": "String"
    }
  }
}
dart 复制代码
Text(AppLocalizations.of(context)!.pronoun('male')),
Text(AppLocalizations.of(context)!.pronoun('female')),

其它

避免语法解析

有时候符号(例如{})也可能是普通文本的部分,如果不想被解析为一种语法,可以在l10n.yaml中设置use-escaping

yaml 复制代码
use-escaping: true

启用后,解析器会忽略使用一对单引号包括的文字,如果在文字中又想使用单个单引号,需要使用成对的单引号进行转义。

配置l10n.yaml文件

通过l10n.yaml文件,可以配置gen-l10n工具,指定以下内容

  • 所有输入文件的位置
  • 所有输出文件的创建位置
  • 为本地化代理设置自定义的类名

可以在命令行运行 flutter gen-10n --help 获取命令的具体使用

详见Flutter 应用里的国际化

参考

  1. Flutter 应用里的国际化
  2. Localized messages are generated into source, not a synthetic package.
相关推荐
造梦师5 小时前
关于flutter中Scaffold.of(context).openEndDrawer();不生效问题
flutter
程序员老刘·5 小时前
Flutter 3.32 升级要点全解析
flutter·跨平台开发·客户端开发
Cao_Shixin攻城狮5 小时前
Flutter 3.32 新特性
flutter·flutter 3.32·flutter3.32
androidwork7 小时前
Kotlin与Flutter:跨平台开发的互补之道与实战指南
开发语言·flutter·kotlin
星释7 小时前
鸿蒙Flutter实战:23-混合开发详解-3-源码模式引入
flutter·harmonyos
星释14 小时前
鸿蒙Flutter实战:25-混合开发详解-5-跳转Flutter页面
flutter·harmonyos
星释1 天前
鸿蒙Flutter实战:21-混合开发详解-1-概述
flutter·harmonyos
北极象1 天前
Flutter 中 build 方法为何写在 StatefulWidget 的 State 类中
flutter
唔661 天前
网络图片的缓存和压缩
flutter