当Flutter应用程序的应用范围超越单一市场时,语言支持就成为一项至关重要的需求。一个设计良好的应用程序应该能让用户无论处于何种地区都能自然地使用它,它能自动适应用户的语言设置,同时仍允许用户自行进行语言切换。

本文提供了一份全面且以实际开发为导向的指南,介绍了如何利用Flutter的本地化系统、《intl》包以及Bloc状态管理库,在Flutter应用程序中支持多种语言。我们将支持英语、法语和西班牙语,实现自动语言检测功能,并允许用户通过设置手动切换语言;同时,我们还会探讨如何利用人工智能技术来实现文本翻译的自动化。

目录

先决条件

在继续学习之前,您需要掌握以下概念:

  • Dart编程语言:变量、类、函数以及空值安全机制

  • Flutter基础知识:组件、`BuildContext`以及组件树结构

  • 状态管理基础:熟悉Bloc或类似的模式

  • 终端使用方法:如何运行Flutter命令行工具

如果您之前有过使用Flutter组件和构建基本应用程序架构的经验,那么您就已经为后续的学习打下了良好的基础。

为什么在Flutter应用程序中本地化如此重要

本地化是指将应用程序适配成不同语言和地区版本的过程。这一过程不仅仅涉及简单的文本翻译,还关系到应用的可用性、用户的信任度以及整体使用体验。从技术角度来看,本地化面临诸多挑战:文本需要在运行时动态加载,当语言设置发生变化时用户界面必须立即更新,用户的语言偏好设置需要在不同会话之间保持一致,而且当系统不支持某种语言时,还需要能够优雅地处理相应的兼容问题。

Flutter的本地化框架结合了`intl`库和Bloc模式,能够高效且可靠地解决这些挑战。

Flutter本地化架构概述

Flutter的本地化功能主要基于以下三个核心理念来构建:

  1. ARB文件作为翻译后文本的权威存储来源

  2. 代码生成机制,用于确保对翻译内容的类型安全访问

  3. 根据用户设置的语言环境动态重建组件树结构

在运行时,当前激活的`Locale`设置会决定使用哪份翻译文件。当语言环境发生变化时,Flutter会自动重新构建相关的组件。

如何配置依赖项

请将所需的依赖项添加到您的`pubspec.yaml`文件中:

dependencies:
  flutter:
    sdk: flutter

  flutter_localizations:
    sdk: flutter

  intl: ^0.20.2
  flutter_bloc: ^8.1.3
  arb_translate: ^1.1.0

若要启用代码生成功能,请添加以下配置:

flutter:
  generate: true

这会指示 Flutter 从 ARB 文件中生成本地化类。

如何定义支持的语言

根据本指南,该应用程序将支持以下语言:

  • 英语(en

  • 法语(fr

  • 西班牙语(es

这些本地化设置会以集中式方式进行配置,并在整个应用程序中得到使用。

如何使用 ARB 文件添加本地化文本

Flutter 使用 应用资源包(ARB) 文件来存储本地化字符串。每种支持的语言都有对应的 ARB 文件。

英语 – app_en.arb

{
  "@@locale": "en",
  "enter_email_address_to_reset": "请输入您的电子邮件地址以进行重置"
}

法语 – app_fr.arb

{
  "@@locale": "fr",
  "enter_email_address_to_reset": "请输入您的电子邮件地址以进行重置"
}

西班牙语 – app_es.arb

{
  "@@locale": "es",
  "enter_email_address_to_reset": >请输入您的电子邮件地址以进行重置
}

所有键的值在各个文件中都必须保持一致,只有具体语言对应的值才会有所不同。

如何生成本地化代码

在终端中运行以下命令:

flutter gen-l10n

Flutter 会生成一个强类型的本地化类,该文件通常位于:

.dart_tool/flutter_gen/gen_l10n/app_localizations.dart

这个文件提供了诸如以下的获取方法:

AppLocalizations.of(context)!.enter_email_address_to_reset

如何为 MaterialApp 配置本地化功能

必须为 MaterialApp 组件配置相应的本地化代理和支持的语言:

MaterialApp(
  localizationsDelegates: const [
    AppLocalizations.delegate,
    GlobalMaterialLocalizationsdelegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: const [
    Locale('en'),
    Locale('fr'),
    Locale('es'),
  ],
  locale: state.locale,
  home: const MyHomePage(),
)

locale属性由Bloc控制,因此可以在运行时动态对其进行更新。

自动检测用户设备的语言设置

Flutter通过PlatformDispatcher来获取设备的区域设置信息。我们可以利用这一机制自动选择最适合当前设备的语言版本。

void detectLanguageAndSet() {
  Locale deviceLocale = PlatformDispatcher.instance.locale;

  Locale selectedLocale = AppLocalizations.supportedLocales.firstWhere(
    (supported) => supported.languageCode == deviceLocale.languageCode,
    orElse: () => const Locale('en'),
  );

  print('当前使用的区域设置为:如何使用Bloc来管理本地化功能

Bloc为管理整个应用程序范围内的区域设置变更提供了一种可靠且便于测试的方式。

本地化状态类

AppLocalizationState {
  final Locale locale;
  const App LocalizationState(this.locale);
}

本地化事件类

abstract AppLocalizationEvent {}

SetLocale App LocalizationEvent {
  final Locale locale;
  SetLocale({required this.locale});
}

本地化处理模块

AppLocalizationBloc
    Bloc<App LocalizationEvent, AppLocalizationState>> {
  AppLocalizationBloc()
      : super('en'))) {
    如何在组件中显示本地化文本

一旦完成了本地化配置,使用翻译后的文本就非常简单了:

Text(
  AppLocalizations.of(context)!.enter_email_address_to_reset,
  style: getRegularStyle(
    color: Colors.white,
    fontSize: FontSize.s16,
  ),
)

AppLocalizations.of(context)!.enter_email_address_to_reset会从生成的本地化资源中获取当前应用程序语言设置对应的本地化字符串enter_email_address_to_reset。系统会根据当前激活的语言设置自动选择正确的翻译版本。

通过设置菜单切换语言

用户应该始终能够手动覆盖系统自动检测的语言设置。

ListTile(
  title: const Text('French'),
  onTap: () {
    context.read().add(
      SetLocale(locale: const Locale('fr')),
    );
  },
)

这个ListTile会显示文本“French”,当用户点击它时,它会触发AppLocalizationBloc,通过发送SetLocale事件将应用程序的语言设置更改为法语('fr'),并且这种语言设置会在下次启动应用程序时保持不变。

如何为本地化文本添加参数

在实际应用中,文本很少是静态不变的。消息内容通常会包含一些动态值,比如用户名、数量、日期或价格等。Flutter的本地化系统借助intl库,能够以类型安全的方式支持带参数的字符串

参数的定义位置

参数是与本地化文本一起存储在ARB文件中的。每个包含参数的消息由两部分组成:一部分是包含占位符的文本字符串,另一部分是描述这些占位符的元数据。

示例:带参数的文本

假设我们想要显示一条包含用户名称的问候信息。

英语版本 – app_en.arb

{
  "@@locale": "en",
  "greetingMessage": "Hello {username}!",
  "@greetingMessage": {
    "description": "显示在主屏幕上的问候信息",
    "placeholders": {
      "username": {
        "type": "String"
      }
    }
  }
}

这定义了一条针对英语的参数化本地化消息,其表示方式为"@@locale": "en"。键"greetingMessage"中包含字符串"Hello {username}!",其中{username}是一个占位符,在运行时会动态替换为用户的名称。"@greetingMessage"条目为这条消息提供了元数据,包括一段说明该字符串会在首页显示的描述,以及一个"placeholders"部分,该部分指明"username"的数据类型为String。当应用程序运行时,这种结构能够使消息动态显示出来——例如,如果用户名是"Alice",那么显示的消息就会是"Hello Alice!"

法语 – app_fr.arb

{
  "@@locale": "fr",
  "greetingMessage": "Bonjour {username} !"
}

西班牙语 – app_es.arb

{
  "@@locale": "es",
  "greetingMessage": "¡Hola {username}!"
}

这个占位符名称{username}必须在所有的ARB文件中保持一致

生成的Dart API

运行以下命令后:

flutter gen-l10n

Flutter会生成一个强类型的方法,而不仅仅是一个简单的获取器函数:

String greetingMessage(String username)

这样就可以避免运行时错误,并确保编译时的安全性。

如何在组件中使用参数化字符串

Text(
  AppLocalizations.of(context)!.greetingMessage('Tony'),
)

如果将语言设置为法语,输出结果将会是:

Bonjour Tony !

复数形式与数量表达

另一个常见的本地化需求就是复数形式的处理。不同语言在表示数量时存在很大差异,如果在Dart中硬编码复数逻辑,很容易出现错误。

在ARB中定义复数形式消息

{
  "itemsCount": "{count, plural, =0{没有项目} =1{1个项目} other{{count}个项目}}",
  "@itemsCount": {
    "description": “用于显示项目数量”,
    "placeholders": {
      "count": {
        "type": "int"
      }
    }
  }
}

这定义了一个针对itemsCount值的复数形式字符串生成规则。字符串{count, plural, =0{没有项目} =1{1个项目} 其他{{count}个项目}会根据count的实际值动态变化:当count为0时,显示“没有项目”;当count为1时,显示“1个项目”;而对于其他所有值,则显示“{count}个项目”。元数据条目"@itemsCount"提供了对该字符串的描述,并指明了占位符count的数据类型为int

每种语言都可以定义自己特有的复数形式生成规则,同时它们都可以使用相同的键名。

使用复数形式字符串

Text(
  AppLocalizations.of(context)!.itemsCount(3),
)

Flutter会根据当前激活的语言设置,自动选择正确的复数形式来显示这些文本。

如何格式化日期、数字和货币

intl包还提供了能够识别不同语言环境的格式化工具。这些工具应该与本地化的字符串结合使用,而不能单独替代那些本地化字符串。

日期格式化示例

final formattedDate = DateFormat.yMMMMd(
  Localizations.localeOf(context).toString(),
).format(DateTime.now());
Text(
  AppLocalizations.of(context)!.lastLoginDate(formattedDate),
)

这样的处理方式能够确保语言设置和格式化规则都与用户的实际语言环境保持一致。

本地化数据流的处理过程

本地化功能的实现是通过一个明确的数据流来完成的。语言环境的识别和处理被视作应用程序的状态变化,而不是通过静态配置传递给MaterialApp的。

这一流程始于设备当前设置的语言环境,这个值是在程序启动时从操作系统层面获取的。虽然这个值代表了系统默认使用的语言和地区设置,但它并不会直接应用于用户界面。

相反,这个语言环境信息会先经过detectLanguageAndSet这一处理环节,然后才会应用应用程序特定的规则进行进一步处理。这一层通常负责处理语言环境的标准化处理以及备用方案的启用,比如将不支持的语言转换成系统支持的格式,或者从持久化存储中恢复用户选择的语言设置,同时还会根据产品的具体要求来限制可使用的翻译内容。

最终确定后的语言环境信息会被放入Localization Bloc中,这个模块成为了本地化状态信息的统一管理来源。通过集中管理语言环境相关的数据,应用程序能够支持运行时语言环境的动态变化,确保重新构建代码时的稳定性,并且使本地化逻辑与界面组件以及操作系统API保持解耦。

Localization Bloc会将处理后的语言环境信息传递给MaterialApplocale属性,而这个属性正是Flutter本地化系统与之交互的接口。一旦这个属性的值发生改变,就会触发Localizations作用域内的代码重新构建,从而确保所有依赖于该语言环境设置的组件都能使用当前激活的语言环境来显示相应的文本内容。

在系统的边缘,本地化组件会使用由flutter gen-l10n生成的本地化类。这些组件并不关心本地化设置是如何被选择或更新的,它们只会根据框架提供的本地化环境来做出相应的反应。

这种架构清晰地分离了以下各个部分:

  • 本地化检测功能

  • 业务逻辑与状态管理

  • 框架层面的本地化处理

  • 用户界面渲染

因此,本地化的实现方式更加明确、易于维护,并且能够与自动化翻译流程以及基于持续集成系统的本地化更新机制兼容。

本地化数据流

常见误区及避免方法

  1. 避免手动进行字符串拼接。例如,不要使用'Hello ' + name这种写法,而应该使用本地化模板。

  2. 在Dart中绝对不要硬编码复数处理逻辑。始终使用intl提供的复数处理功能来正确处理不同语言的需求。

  3. 避免在intl工具之外使用特定于某种语言的格式化代码。日期、数字和货币等内容的格式化应通过专门的本地化工具来完成。

  4. 在更新ARB文件后,务必重新生成本地化文件。这样才能确保应用程序能够显示最新的翻译内容。

如何利用AI实现自动化翻译

对于那些依赖ARB文件进行本地化的Flutter应用来说,随着应用规模的扩大,维护翻译内容的成本会越来越高。每添加一条新消息,都需手动将其同步到各个语言版本的文件中,这往往会导致键值对缺失、表述不一致或更新延迟等问题。而在那些没有使用翻译管理系统(TMS),而是直接将ARB文件保存在代码仓库中的项目中,这种问题会更加严重。

虽然许多TMS平台已经开始提供AI辅助翻译功能,但并非所有项目都会使用这些工具,尤其是小型团队、内部开发工具或个人项目。在这种情况下,开发者通常会手动将字符串复制到AI翻译工具中,然后再将翻译结果粘贴回ARB文件中,这种做法效率低下且难以扩展。

为了解决这一问题,Leen Code发布了arb_translate这个基于Dart的命令行工具,它可以利用大型语言模型自动完成ARB文件中缺失的翻译内容。

设计思路

arb_translate所采用的机制是与Flutter现有的本地化流程相兼容的,而不是取代现有的流程:

  • 英文版本的ARB文件仍然是最终参考依据。

  • 只有那些缺失的键值对才会被翻译成目标语言。

  • 生成后的结果会被保存为标准的ARB文件格式。

  • flutter gen-l10n工具仍然负责代码生成的工作。

这种设计使得该工具既适用于本地开发,也适合用于持续集成流程中,且不会引入新的运行时依赖项或本地化抽象层。

从整体流程来看,具体步骤如下:

  1. 解析基础语言(通常是英语)的ARB文件

  2. 检查目标语言版本的ARB文件中是否存在缺失的键值对

  3. 通过API将键值对发送给大语言模型

  4. 接收翻译后的字符串结果

  5. 更新或生成对应目标语言版本的ARB文件

  6. 运行`flutter gen-l10n`命令来重新生成本地化资源文件

基于Gemini的配置方法

若要使用Gemini进行ARB文件的翻译,需按照以下步骤操作:

  1. 生成Gemini API密钥
    点击此处获取Gemini API密钥

    Gemini API控制面板

  2. 安装相应的命令行工具:

dart pub global activate arb_translate
  1. 导出API密钥:
export ARB_TRANSLATE_API_KEY=your-api-key
  1. 从Flutter项目的根目录处运行该工具:
arb_translate

该工具会扫描现有的ARB文件,生成缺失的翻译内容,并将其保存回磁盘中。

对OpenAI/ChatGPT的支持

从版本1.0.0开始,`arb_translate`工具也支持OpenAI ChatGPT模型。这样一来,各团队既可以继续使用OpenAI提供的基础设施,也可以更换服务提供商,而无需更改原有的本地化工作流程。

  1. 生成OpenAI API密钥
    点击此处获取OpenAI API密钥

    OpenAI平台

  2. 安装该工具:

dart pub global activate arb_translate
  1. 导出API密钥:
export ARB_TRANSLATE_API_KEY=your-api-key
  1. 选择OpenAI作为翻译服务提供商:

可以通过`l10n.yaml`文件进行配置:

arb_translate-model-provider: open-ai

或者通过命令行工具指定:

arb.translate --model-provider open-ai
  1. 最后运行该工具即可开始翻译过程:
arb_translate

实际应用案例

这种方法并非旨在取代专业的翻译或审核流程。相反,它作为一种确定性的自动化层,能够:

  • 消除手动复制粘贴的工作流程

  • 确保ARB文件在结构上保持一致性

  • 使CI流程中能够自动生成翻译内容

  • 在需要时,支持在TMS系统中进行后续审核

对于那些内容量较大、且没有专用本地化平台的Flutter项目或团队来说,这种方法提供了一种实用且易于维护的解决方案。

最佳实践与注意事项

  1. 始终指定一个备用语言设置,以确保应用程序仍可正常使用。

  2. 避免将用户界面中的文本硬编码;应使用本地化资源。

  3. 为便于维护,使用语义明确且稳定的ARB键进行数据管理。

  4. 保存用户的的语言偏好设置,以提供一致的体验。

  5. 使用较长的翻译内容及多种语言环境来测试应用程序,以便发现布局或用户界面方面存在的问题。

结论

本地化是现代Flutter应用程序的基本要求。通过结合Flutter内置的本地化框架、《intl》包以及用于状态管理的Bloc库,你可以获得一个强大且可扩展的解决方案。

借助自动检测设备语言、运行时切换功能以及清晰的架构设计,你的应用程序能够在不牺牲可维护性的前提下实现全球范围内的使用。

参考资料

以下是一些关于Flutter本地化的官方参考链接:

Comments are closed.