关于 Flutter 项目的国际化,只需要做对这几步

最近刚把公司项目的国际化搞完,顺便记录下Flutter项目国际化的几个步骤,来供大家参考。

App 项目为什么需要国际化?

公司项目的国际化需求来得很突然,就是因为要参加国外的展会,方便展示给客户使用App的操作步骤。正常情况下国际化无非是通过将应用程序本地化为不同的语言和地区,可以使应用在全球范围内更具吸引力,从而扩大市场覆盖范围。当你的应用能够以用户熟悉和舒适的语言呈现时,吸引更多的用户成为可能,同时也提升用户的使用体验,或者是遵守当地法律法规及政策的要求。

国际化需要做哪些准备?

以下是示例项目所在的电脑的开发环境。

joe@wf ~ % flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[!] Flutter (Channel unknown, 3.7.1, on macOS 13.6 22G120 darwin-arm64 (Rosetta), locale zh-Hans-CN)

Dart SDK

joe@wf ~ % dart --version
Dart SDK version: 2.19.1 (stable) (Tue Jan 31 12:25:35 2023 +0000) on "macos_arm64

示例项目的 pubspec.yaml 中的 environment 配置:

environment:
  sdk: ">=2.17.0 <3.0.0"

在你的 pubspec.yaml 文件中添加 intlintl_utils 依赖:

dev_dependencies:
  intl: ^0.17.0
  intl_utils: ^2.4.0

使用 intl_utils 第三方工具的目的是给官方 Dart Intl 库生成样板代码,并为 Dart 代码中的键添加自动完成的功能。

flutter_intl:
  enabled: true
  class_name: S
  main_locale: en
  output_dir: lib/language/generated
  arb_dir: lib/language/l10n
  • output_dir:模板代码生成路径;
  • arb_dir:是 .arb 文件存放的路径。

每次编辑 .arb 文件保存后会自动生成模板代码保存到 output_dir 路径下面。

运行 flutter pub get 指令后,自动生成的文件目录如下:

image.png

根据需要创建 .arb 文件编辑好内容后,保存文件就会在 lib/language/generated 目录下自动生成模板代码,下面以 intl_zh.arbintl_en.arb 为例来修改。

image.png

修改项目入口的Widget

mian() 中的第一个 Widget -> build 函数中添加如下3个地方。

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  App({Key? key}) : super(key: key)

  @override
  Widget build(BuildContext context) {
     return MaterialApp(
      title: 'Flutter 技术实践',
      theme: lightTheme,
      darkTheme: darkTheme,
      themeMode: ThemeMode.system,
      onGenerateRoute: MyRoutes.router.generator,
      initialRoute: MyRoutes.root,
      debugShowCheckedModeBanner: false,
      supportedLocales: S.delegate.supportedLocales, // 1
      localizationsDelegates: const [ // 2
        S.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      localeResolutionCallback:
          (Locale? locale, Iterable<Locale> supportedLocales) { // 3
        Locale currentLocale =
            Locale.fromSubtags(languageCode: locale?.languageCode ?? "zh");
        return supportedLocales.contains(currentLocale)
            ? currentLocale
            : const Locale.fromSubtags(languageCode: "zh");
      },
    );
  }
}

这里的localeResolutionCallback: (Locale? locale, Iterable<Locale> supportedLocales)函数是设备系统或者浏览器语言环境发生改变的时候的回调,其中 locale 参数是改变后的语言,supportedLocales 是项目中所有支持的语言环境,如果本地未找到支持的语言就默认显示中文,也就是 languageCode: "zh"

使用 S.of(context).{key} 替换项目中文案

这里的 key 就是我们在 .arb 的文件定义的 key。

{
  "nav_tab": "Tab Pages",
  "nav_news": "News List",
  "nav_route": "Route Navigation And Parameter Transfer",
  "nav_completer": "Use Completer And Compute in Flutter",
  "nav_extension": "Chrome Extension",
  "tab_home": "Home",
  "tab_search": "Search",
  "tab_favor": "Favor",
  "tab_setting": "Setting"
}

intl_utils 帮我们生成了模板代码,使用的时候有代码提示。

image.png

如何在应用内部切换语言

在应用内部切换语言的时候,就需要用到全局的状态管理,如Provider,当应用内部收到切换语言的操作后,会通知所有依赖它的 Widget 进行刷新,达到显示更换后的语言,我们也来实现一下。

pubspec.yaml 中引入 provider 依赖:

dependencies:
  provider: ^6.1.2

创建类 AppLanguageProvider 继承自 ChangeNotifier,用来记录当前的应用内的语言,以及当执行切换语言的操作时,调用 notifyListeners() 来刷新。

import 'package:flutter/cupertino.dart';

enum LanguageCode { zh, en }

class AppLanguageProvider extends ChangeNotifier {
  LanguageCode languageCode = LanguageCode.zh;

  changeLanguage(LanguageCode languageCode) {
    this.languageCode = languageCode;
    notifyListeners();
  }
}

AppLanguageProvider 挂在项目入口的 Widget 来作为管理全局语言环境状态,并在 Builder 回调中监听当前的 languageCode 的值(context.watch<AppLanguageProvider>().languageCode),再根据当前的 languageCode 值获得到的 Locale 赋值给 MaterialApp -> locale 属性。

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

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
   return MultiProvider(
      providers: [
        // 第一步
        ChangeNotifierProvider(create: (_) {
          return AppLanguageProvider();
        }),
      ],
      builder: (BuildContext context, Widget? child) {
        // 第二步
        LanguageCode languageCode =
            context.watch<AppLanguageProvider>().languageCode;
        return MaterialApp(
          title: 'Flutter 技术实践',
          theme: lightTheme,
          darkTheme: darkTheme,
          themeMode: ThemeMode.system,
          onGenerateRoute: MyRoutes.router.generator,
          initialRoute: MyRoutes.root,
          debugShowCheckedModeBanner: false,
          supportedLocales: S.delegate.supportedLocales,
          // 第三步
          locale: Locale.fromSubtags(languageCode: languageCode.name),
          localizationsDelegates: const [
            S.delegate,
            GlobalMaterialLocalizations.delegate,
            GlobalWidgetsLocalizations.delegate,
            GlobalCupertinoLocalizations.delegate,
          ],
          localeResolutionCallback:
              (Locale? locale, Iterable<Locale> supportedLocales) {
            Locale currentLocale =
            Locale.fromSubtags(languageCode: locale?.languageCode ?? "zh");
            return supportedLocales.contains(currentLocale)
                ? currentLocale
                : const Locale.fromSubtags(languageCode: "zh");
          },
        );
      },
    );
  }
}

最后,在切换语言操作的地方执行如下代码:

Provider.of<AppLanguageProvider>(context, listen: false).changeLanguage(LanguageCode.zh);

最终效果:


ezgif-5-b8ef0d45fe.gif

好了,以上就是Flutter项目国际化的全部实现过程,示例代码和查看效果在 https://flutter.nnxkcloud.com 上,另外,如果有更好的实现方式,也欢迎留言交流一下。

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

推荐阅读更多精彩内容

  • 通过命令行创建项目flutter create YYFood; 用Android Studio 打开新建项目YYF...
    YanZi_33阅读 556评论 0 0
  • 前言 如果APP有需要支持多种语言,就需要支持国际化,无论是android和ios,现在针对flutter的国际化...
    lebonbill阅读 10,569评论 4 6
  • 一. 国际化的认识 开发一个App,如果我们的App需要面向不同的语种(比如中文、英文、繁体等),那么我们需要对齐...
    5e4c664cb3ba阅读 1,377评论 0 3
  • 什么是国际化 国际化是指在设计软件时,将软件与特定语言及地区脱钩的过程。当软件被移植到不同的语言地区时,软件本身不...
    QiShare阅读 2,754评论 0 2
  • 如果App的用户使用的是不同语言,那进行国际化是必要的。国际化主要包括文案的国际化(不同的语言展示不同的文案)和布...
    chonglingliu阅读 2,114评论 5 6