ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Flutter 国际化实战:用 intl 搭一套可维护的多语言骨架,并配 TaoToken 统一 Key 通道

Flutter 国际化实战:用 intl 搭一套可维护的多语言骨架,并配 TaoToken 统一 Key 通道 1. 从硬编码到多语言Flutter 国际化到底解决什么问题Flutter 国际化intl能做什么简单说它把散落在各个页面里的中文字符串抽出来集中放进.arb文件再通过代码生成产出类型安全的AppLocalizations类让Text(发现)变成Text(context.l10n.homeTitle)。适合谁适合所有准备上线海外版本、或者团队里已经有非中文成员、又或者单纯想把文案从代码里剥离出来的 Flutter 项目。我见过太多项目一开始图省事直接写一个AppText常量类abstract final class AppText { static const String appName 我的应用; static const String homeTitle 发现; static const String loginBtn 立即登录; }用起来是方便Text(AppText.homeTitle)一行搞定。但问题很快暴露不支持多语言、改文案要重新发版、带占位符的句子比如「7 日平均消耗{value} 千卡」没法优雅处理。等到产品说「下个月要出英文版和日文版」你就得把几百个常量全部重写一遍。intl方案的核心价值在于三点文案与代码分离、编译期类型检查写错 key 直接报错不是运行时才崩、占位符与复数规则内置。这篇文章我会从零走一遍完整路径装依赖、开开关、写 arb、配 l10n.yaml、生成代码、接入 MaterialApp、运行时切换语言最后再讲怎么用 TaoToken 把多环境的 Key 和 API 通道统一管起来避免密钥散落在各个.env文件里。2. 前置准备依赖、开关与 TaoToken 的 Key 通道2.1 安装 intl 相关依赖在项目根目录执行flutter pub add flutter_localizations --sdkflutter flutter pub add intl第一条命令装的是 Flutter 官方的本地化代理提供 Material/Cupertino 组件内置文案的多语言支持第二条是intl核心包。注意flutter_localizations必须带--sdkflutter否则会去 pub.dev 找同名包导致版本冲突。2.2 打开代码生成开关打开pubspec.yaml在根节点的flutter:下面加一行flutter: generate: true这个开关是flutter gen-l10n能自动跑起来的前提。没有它你手动执行生成命令也行但热重载时不会自动重新生成改完 arb 得手动跑一次很烦。2.3 用 TaoToken 统一管理多环境密钥国际化项目通常伴随多环境dev/staging/prod每个环境可能有不同的 API 地址和密钥。如果直接把 Key 写进--dart-define或者.env文件团队协作时很容易泄露或搞混。我的做法是用 TaoToken 作为统一的 Key/API 通道在控制台创建不同项目的 Key通过环境变量注入代码里只读环境变量不关心具体值。先去官网注册并进入控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite创建一个项目然后在 API Keys 页面生成一个 Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite。这个 Key 后面会通过--dart-define传给 Flutter 应用。如果你还想在开发阶段直接对话调试模型输出比如验证多语言文案的翻译质量可以用模型对话页面https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite快速试。长期做编码和 Agent 开发的建议看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite额度更划算。3. 可复制配置arb 文件、l10n.yaml 与 MaterialApp 接入3.1 创建 arb 模板文件新建目录lib/l10n/在里面放两个文件。lib/l10n/app_en.arb模板文件必须第一个写{ locale: en, appName: My App, homeTitle: Discover, loginBtn: Log In, sevenDayAvgBurn: 7-Day Avg Burn: {value} kcal, sevenDayAvgBurn: { placeholders: { value: { type: int } } } }lib/l10n/app_zh.arb{ locale: zh, appName: 我的应用, homeTitle: 发现, loginBtn: 立即登录, sevenDayAvgBurn: 7 日平均消耗{value} 千卡, sevenDayAvgBurn: { placeholders: { value: { type: int } } } }注意locale的值要和文件名后缀一致app_en.arb对应en。占位符的元数据只需要在模板文件里写一次其他语言文件直接写翻译即可。3.2 配置 l10n.yaml在项目根目录和pubspec.yaml同级创建l10n.yamlarb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart output-class: AppLocalizations nullable-getter: false这里我加了nullable-getter: false这样AppLocalizations.of(context)返回的是非空类型调用时不用写!。如果你用的是 intl 0.19.x生成文件会跑到.dart_tool/flutter_gen/gen_l10n/0.20.x 之后默认和 arb 同目录。两种版本的 import 路径不同下面会分别说明。3.3 生成代码flutter gen-l10n执行完检查一下0.19.x 看.dart_tool/flutter_gen/gen_l10n/app_localizations.dart是否存在0.20.x 看lib/l10n/app_localizations.dart。如果报错No arb-dir found说明l10n.yaml位置不对必须在项目根目录。3.4 接入 MaterialAppimport package:flutter/material.dart; import package:flutter_localizations/flutter_localizations.dart; // intl 0.19.x 用这行 // import package:flutter_gen/gen_l10n/app_localizations.dart; // intl 0.20.x 用这行工程名替换成你的 import package:your_app/l10n/app_localizations.dart; class MyApp extends StatefulWidget { const MyApp({super.key}); override StateMyApp createState() _MyAppState(); } class _MyAppState extends StateMyApp { Locale _locale const Locale(zh); void _switchLocale(Locale locale) { setState(() _locale locale); } override Widget build(BuildContext context) { return MaterialApp( locale: _locale, localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: AppLocalizations.supportedLocales, home: HomePage(onSwitchLocale: _switchLocale), ); } }supportedLocales直接取AppLocalizations.supportedLocales它会根据你 arb 文件的数量自动生成不用手写。3.5 简化调用给 BuildContext 加扩展每次写AppLocalizations.of(context)!.homeTitle太啰嗦加个扩展import package:flutter/material.dart; import package:your_app/l10n/app_localizations.dart; extension ExtBuildContext on BuildContext { AppLocalizations get l10n AppLocalizations.of(this); }之后直接用Text(context.l10n.homeTitle)。带占位符的调用是context.l10n.sevenDayAvgBurn(3200)。4. 验证请求切换语言、热重载与 TaoToken 通道测试4.1 运行时切换语言在HomePage里放两个按钮class HomePage extends StatelessWidget { final void Function(Locale) onSwitchLocale; const HomePage({super.key, required this.onSwitchLocale}); override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(context.l10n.appName)), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(context.l10n.homeTitle, style: const TextStyle(fontSize: 24)), const SizedBox(height: 16), Text(context.l10n.sevenDayAvgBurn(3200)), const SizedBox(height: 32), ElevatedButton( onPressed: () onSwitchLocale(const Locale(zh)), child: const Text(中文), ), ElevatedButton( onPressed: () onSwitchLocale(const Locale(en)), child: const Text(English), ), ], ), ), ); } }点按钮触发setStateMaterialApp的locale变化整棵树重建文案立即切换。不需要重启应用。4.2 热重载验证改一下app_zh.arb里的homeTitle保存然后在终端按r触发热重载。如果generate: true生效了Flutter 会自动重新跑gen-l10n新文案直接出现在界面上。如果没生效检查pubspec.yaml的generate是否在flutter:节点下以及l10n.yaml是否在根目录。4.3 用 TaoToken 通道做一次请求验证在main.dart里读取环境变量注入的 Keyconst apiKey String.fromEnvironment(TAOTOKEN_API_KEY); const apiBase https://taotoken.net/api; void main() { assert(apiKey.isNotEmpty, TAOTOKEN_API_KEY 未注入); runApp(const MyApp()); }运行命令flutter run --dart-defineTAOTOKEN_API_KEYsk-你的key这样 Key 不会进版本库团队成员各自用自己的 Key。如果要在 CI 里跑把--dart-define换成从 CI 的 secret 变量读取即可。TaoToken 的 API 地址是https://taotoken.net/api不带 UTM 参数直接用于代码里的 base URL。5. 本篇常见错排查5.1 报错Target of URI doesnt exist: package:flutter_gen/gen_l10n/app_localizations.dart这是 intl 版本差异导致的。0.19.x 生成到.dart_tool/flutter_gen/gen_l10n/import 路径是package:flutter_gen/gen_l10n/app_localizations.dart0.20.x 生成到 arb 同目录import 路径是package:你的工程名/l10n/app_localizations.dart。先确认pubspec.lock里 intl 的版本再改 import。5.2flutter gen-l10n报No arb-dir foundl10n.yaml必须在项目根目录和pubspec.yaml同级。如果你把它放进了lib/命令找不到。另外arb-dir的值是相对于项目根目录的路径不是相对于l10n.yaml。5.3 切换语言后部分文案没变检查MaterialApp的localizationsDelegates是否包含了GlobalMaterialLocalizations.delegate和GlobalCupertinoLocalizations.delegate。如果只加了AppLocalizations.delegate你自己定义的文案会切换但 Material 组件内置的比如日期选择器、返回按钮的 tooltip不会变。5.4 占位符类型不匹配导致运行时报错sevenDayAvgBurn的占位符声明了type: int调用时必须传 int。如果传了 String运行时会抛type String is not a subtype of type int。改 arb 里的类型声明或者调用时做转换。5.5 热重载不重新生成代码确认pubspec.yaml里generate: true在flutter:节点下不是顶层。另外有些 IDE 的热重载不会触发代码生成需要手动跑一次flutter gen-l10n或者完全重启flutter run。6. 把 Key 通道和国际化一起管起来国际化解决的是「文案怎么跟着语言走」TaoToken 解决的是「密钥怎么跟着环境走」。两者结合后你的项目结构会清晰很多lib/l10n/放所有 arb 和生成代码lib/config/放环境相关的常量Key 通过--dart-define注入代码里只读String.fromEnvironment。如果你在接入过程中遇到 Key 鉴权或通道配置的问题直接看接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite里面有完整的请求示例和错误码说明。需要新建或轮换 Key 就去 API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite。长期做 Flutter Agent 混合开发的Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite的额度模型比按量付费更适合高频调试。最后提醒一个实操细节l10n.yaml里的nullable-getter: false只在 intl 0.20.x 之后支持如果你用的是 0.19.x这行会报未知参数删掉即可调用时记得加!。这个坑我踩过当时排查了半小时才发现是版本问题。
返回列表