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 小时前
Fluter InteractiveViewer 与ScrollView滑动冲突问题解决
flutter
叽哥12 小时前
Flutter Riverpod上手指南
android·flutter·ios
BG1 天前
Flutter 简仿Excel表格组件介绍
flutter
zhangmeng1 天前
FlutterBoost在iOS26真机运行崩溃问题
flutter·app·swift
恋猫de小郭1 天前
对于普通程序员来说 AI 是什么?AI 究竟用的是什么?
前端·flutter·ai编程
卡尔特斯1 天前
Flutter A GlobalKey was used multipletimes inside one widget'schild list.The ...
flutter
w_y_fan1 天前
Flutter 滚动组件总结
前端·flutter
醉过才知酒浓1 天前
Flutter Getx 的页面传参
flutter
火柴就是我2 天前
flutter 之真手势冲突处理
android·flutter
Speed1232 天前
`mockito` 的核心“打桩”规则
flutter·dart