资讯动态

Flutter 国际化零漏译:基于 flutter_string_extractor 的 OpenHarmony 构建集成

发布时间:2026/10/3 3:35:01 来源:尧图企业网站定制
做 Flutter 国际化的同学基本都被同一个问题折磨过漏译。代码里明明写了一个 Text(请稍候)发布之后英语环境用户看到的却是中文。过去我靠人肉对照 ARB 文件来防漏项目早期还行等到页面过百、字符串过千这套办法直接失灵。后来我把 flutter_string_extractor 接进项目用源码级字符串全量扫描替代手工检查再顺势把整个国际化流程搬到 OpenHarmony 构建链上才算真正解决了零漏译的痛点。这篇文章就把这套工作流完整拆给你看包含原理、配置、构建集成和排坑记录适合正在做 Flutter 国际化、或者准备把 Flutter 项目往 OpenHarmony 上迁移的开发者参考。1. 为什么要在 OpenHarmony 上重构国际化流程1.1 漏译是怎么在项目里悄悄滋生的先说一个我接手过的真实项目。团队用 Flutter 做一款工具类应用支持中英日三语。最初的国际化做法是最朴素的那种维护一份手写的 en.arb、ja.arb代码里字符串写死再手工去 ARB 文件里建条目。这种模式在第一个版本还能撑住因为文案量就几十条。到了第二个版本产品开始频繁迭代每周都有新页面上线每次都靠人肉检查代码里有没有新增字符串。结果就是发布周期越长积压的欠账越多。我后来用扫描工具把整个 lib 目录过了一遍发现三件事。第一ARB 文件里存在 30 多条 key在代码中根本找不到引用属于历史遗留的僵尸 key。第二代码里有 40 多处硬编码字符串没有进 ARB包括按钮提示、错误信息、Semantics 语义标签。第三占位符问题最隐蔽代码里用的是 $name 这种 Dart 插值翻译文件里却写成 {name} 或者 {{name}}导致运行时解析异常。这三个问题的本质是源码和翻译账本之间失去了同步。而漏译只是这种失同步的一个最直观的表现。为什么人会反复在这类问题上失手因为手写 ARB 文件这件事情本质上是在维护一份与源码平行的数据。编译器能从源码生成符号表不会漏人却做不到逐行比对。尤其是当你修复了一个漏译下一轮迭代又会因为新代码引入新的漏译。这种反复的账目对账恰恰是自动化工具最能发挥价值的地方。理想状态应该是代码变更完成提取工具自动把新增字符串转换成 ARB 条目翻译者只负责翻译不再负责找茬。1.2 flutter_string_extractor 定位自动记账本社区里解决字符串提取的方案不算少但差异很大。intl_utils 是官方推荐的辅助工具之一它要求你在字符串前加 Intl.message 之类的注解才能被扫描到。这套方案对新项目挺优雅写代码的时候顺手加注解就行但对存量项目很不友好历史代码几千处字符串要逐个补注解劳动量巨大。arb_utils 偏重 ARB 文件的合并、排序、校验本身不是以源码扫描为核心。还有一些团队自己写正则脚本简单粗暴但遇到 Dart 字符串插值、多行字符串、注释里的字符串就容易出偏差误提取一堆噪音。flutter_string_extractor 的思路跟它们不一样核心是源码级全量扫描。它直接解析 Dart 源码而不是依赖开发者额外写注解。也就是说代码里只要出现字符串字面量——不管你是写在 Text() 里、拼在变量后面还是放在语义标签里——工具都会尝试把它捞出来自动归类成 ARB 条目。这种做法对存量项目非常友好你不需要回头改一行老代码跑一次扫描就能得到一份完整的字符串清单然后拿着它跟手头 ARB 文件做差异比对所有漏译、僵尸 key、错位占位符一次暴露。我实际用下来最大的感受是它把找漏译这件事从主观工作变成了客观检查。以前是代码评审阶段靠人眼扫现在是流水线上一个固定的质检关卡。少了人力的反复核对错误率明显下降团队也敢在靠近发布节点的时候改文案了。毕竟改完跑一遍提取新增内容立刻进入翻译流程不需要担心改一处漏一处。1.3 鸿蒙适配真正要适配的是什么标题里写了鸿蒙适配很多人第一反应是这个 Dart 工具能编译到 OpenHarmony 上吗我的答案是flutter_string_extractor 本身是一个纯 Dart 开发命令行工具不需要依赖 Android 或 iOS 的平台通道所以能不能在鸿蒙上用从来不是难点。真正的难点在于当 Flutter 项目跑在 OpenHarmony 的 Flutter SDK 分支上时国际化的完整链路——字符串提取、ARB 生成、gen_l10n 编译、运行时资源加载——是不是还能顺畅运转。这里需要先交代一下背景。Flutter 社区为 OpenHarmony 维护了独立的 SDK 分支工程结构、构建脚本和原版有差异。你的项目如果想同时支持 Android、iOS 和 OpenHarmony通常需要维护多套 build 配置。而国际化资源这套东西本来就是在构建期生成的中间产物分支一变、构建命令一变原来的生成链路可能就断了。比如 l10n.yaml 里配置的输出目录在鸿蒙工程里的相对路径可能不同gen_l10n 生成的 Dart 文件在鸿蒙分支的编译环境下需要重新确认可用。所以适配的核心不是把 flutter_string_extractor 塞进鸿蒙而是把提取 → 翻译 → 生成 → 校验这套工作流重新架设在 OpenHarmony 的构建链上让新增文案从进入代码库的那一刻起就自动进入国际化流程直到最终产物在鸿蒙设备上屏全程不需要人工干预。这才是零漏译工作流在 OpenHarmony 上落地的真正含义。2. flutter_string_extractor 原理拆解扫描背后的关键机制2.1 从 Dart 源码到 ARB 文件的完整链路先讲清楚工具从源码到 ARB 的完整处理过程这样后面你调参的时候才不至于抓瞎。第一步是遍历文件。工具默认会递归扫描 lib 目录下的所有 .dart 文件也会根据配置把 test、example 之类的目录排除在外。这一步决定了扫描范围。我通常只让 lib 目录参与提取因为测试代码里的字符串大多不需要翻译排除掉可以减少噪音。第二步是词法解析。这一步是 flutter_string_extractor 跟普通正则脚本拉开差距的地方。它不会简单地用双引号配对来找字符串而是基于 Dart 语法做词法分析。于是字符串字面量、注释、import、注解、保留字这些 token 在扫描之前就能被区分开。举个例子注释里的中文、字符串里的中文、import 路径里的包名在词法层面是三种完全不同的 token正则脚本很容易把它们混在一起词法分析不会。第三步是过滤噪音。字符串被识别出来之后还要通过一系列规则决定要不要收进 ARB。常见的过滤规则包括纯空白字符串、纯数字、URL、文件路径、颜色值、正则表达式字符串、以及被显式标记为忽略的字符串。这里有个细节不同版本的工具提供的忽略方式不同有的支持在字符串后面加一个类似// ignore_extract的标记注释有的支持配置 ignore 正则。你最好选一个支持标记注释的版本因为你总会在某些场景里遇到这个字符串就是不想翻译的需求靠正则硬排除太脆弱。第四步是上下文归类。扫描器会根据字符串在 AST 里的位置尝试判断它的用途。比如字符串出现在 Text() 构造参数里、AppBar 的 title 属性里、TextField 的 hintText 里、MaterialButton 的 child 里这些都是需要翻译的 UI 文案但如果出现在 key: ValueKey(screen_loading) 这种地方它就不是展示给用户的文案不需要进 ARB。这一步分类越细生成的 ARB 质量越高翻译者拿到的上下文就越准确。第五步是生成 ARB 条目。每个被认定为需要翻译的字符串会生成一个条目通常包含 key、原始文案、以及一段描述。描述字段非常有用它会从 AST 上下文里抽取一点信息比如AppBar title、Button label之类翻译者看到描述就知道这是页面标题还是按钮文字翻译准确度会明显提升。最后一步是合并与输出。工具会把本次扫描结果与已有的 ARB 文件做合并新增的条目补进去已存在的条目保留 translations 字段在源码中已经消失的 key 标记待清理。这一步对整个工作流特别关键如果没有合并能力每次全量扫描都会把翻译文件冲回原点翻译者就永远没法增量工作了。2.2 零漏译的两个设计核心全量扫描与语义 ID为什么我说这套机制可以实现零漏译关键在于全量这两个字。每次运行工具它都是对当前所有源码的完整扫描而不是只扫上次提交之后改动的文件。这意味着任何新写入代码库的字符串只要跑一次提取就必然进入 ARB 文件。这是不漏的第一层保证。有人会担心全量扫描在大型项目里会不会太慢。实测下来扫描速度主要跟 lib 目录的代码量和机器有关。对于几千个 Dart 文件的中型项目一次全量扫描通常几十秒内完成。为了进一步提速很多版本支持基于文件 hash 的增量模式只对内容变化的文件重新解析其他文件直接复用上次的结果但扫描结果仍然以全量集合为基准。你可以把它理解成货架盘点时用上次的存货清单 本次的进出库记录但最终库存仍以盘点全表为准。增量只是优化手段全量才是兜底逻辑。第二个核心是语义 ID 设计。ARB 文件的 key 如果直接使用字符串原文中文原文可以当 key但英文原文当 key 就太长了如果使用哈希值可读性又太差翻译者根本不知道这个 key 对应界面上哪个位置。flutter_string_extractor 常用的做法是根据上下文自动生成可读 ID比如homePage_loadingLabel、loginButton_cancel这种风格。它能把字符串所属文件、父级 widget、属性名等信息拼进 ID既保证了唯一性又给翻译者提供了位置线索。ID 生成规则里最容易出问题的是重名。同一个页面里可能会出现几十个 Text(确定)如果它们都会被提取ID 就必须有办法区分。有的工具会为重复字符串聚合到同一个 key这适合完全相同的文案如果两个确定出现在不同页面聚合到一个 key 其实没有问题因为翻译结果一致。但如果上下文不同却需要不同翻译就必须靠描述字段来区分或者在 ID 生成规则里引入文件路径、父组件路径。这属于需要你根据实际项目手工微调的部分也是评估一个提取工具是否成熟的关键点。2.3 工具的实际配置与运行方式讲完原理说点可以直接上手的配置。下面是我在一个中型 Flutter 项目里的实际接法具体参数名以你项目安装的版本为准不同小版本可能有差异整体结构类似。先在 pubspec.yaml 的 dev_dependencies 里加上依赖dev_dependencies: flutter_string_extractor: ^1.0.0然后准备一个配置文件声明扫描目录、排除规则、输出目录和默认语言大致长这样# extract_config.yaml source_dirs: - lib exclude_dirs: - lib/generated - lib/l10n output_dir: lib/l10n template_file: app_en.arb default_language: en ignore_patterns: - ^[0-9\\s]$ - ^https?://运行提取命令时先安装依赖然后直接跑flutter pub get dart run flutter_string_extractor --config extract_config.yaml执行完之后lib/l10n/app_en.arb 就被更新了新增条目补进来了已有翻译保留住被删除的 key 会进入一个 not_found 列表。你拿这份 ARB 文件提交给翻译团队或者交给机器翻译批量处理都可以。这里有几个我踩过的配置坑。第一output_dir 尽量和 gen_l10n 的输入目录保持一致否则还要做一次文件搬运多一环就多一个出错点。第二ignore 规则不要一开始就写得太死建议先跑一版不带 ignore 的看看噪音长什么样再逐个加规则避免误伤真实文案。第三配置文件建议提交进版本库整个团队共用同一套规则不然不同成员机器上生成出来的 ARB 会有差异。3. 在 OpenHarmony 上落地零漏译工作流3.1 环境准备先让 Flutter 在鸿蒙上跑起来要讨论鸿蒙侧的国际化流程前提是项目已经能在 OpenHarmony 上正常构建运行。这里假设你对 Flutter 的 OpenHarmony 分支已经有一定了解我只把与国际化链路相关的环境准备说清楚。第一步是拿到 OpenHarmony 的 Flutter SDK。社区有专门的 flutter_flutter 仓库挑了支持 OpenHarmony 的分支。你需要在本地指定这个 SDK 路径而不是用官方稳定版 SDK。我习惯用 fvm 来管理多版本 Flutter SDK给 OpenHarmony 分支单独建一个版本别名切换起来干净利落。第二步使用 DevEco Studio 打开生成的鸿蒙工程。Flutter 的 OpenHarmony 分支在构建时会生成一个独立的鸿蒙壳工程里面有 entry、module.json5 之类的文件。国际化资源最终要经过这条壳工程链路上到设备。你需要确认 DevEco Studio 的 SDK 版本与 Flutter 分支兼容这个兼容性矩阵不同时期变化挺大建议安装前先查一遍对应分支的 README。第三步是跑通一个最小案例。不要一上来就做国际化工作流先建一个空项目把 Flutter 页面跑在 OpenHarmony 模拟器或真机上确认 Flutter 引擎、Dart VM 都正常。这一步过了后面所有问题都可以定位在三层以内工具层、生成层、鸿蒙壳工程层不会牵扯到 Flutter 引擎是否可用这种大问题。3.2 把提取命令嵌入鸿蒙构建链环境跑通之后核心工作是把字符串提取命令嵌入到鸿蒙的构建链上让每次构建之前都先跑一次提取。这一步的目的是自动化让漏译没有机会溜进产物。我建议在 CI 里加一个独立的 job替代人肉检查这一环节。这个 job 做的事情很简单拉代码、切到 OpenHarmony Flutter SDK、执行 flutter pub get、执行字符串提取命令、然后跑一个 git diff 检查。只要 diff 里出现 ARB 文件的变更说明源码里有新增字符串还没有翻译构建就应该失败直到翻译补上。这是零漏译的第二层保证不是发布时提醒你而是提交时就不让你过。flutter pub get dart run flutter_string_extractor --config extract_config.yaml if git diff --name-only | grep -q \.arb$; then echo ARB files changed: new strings need translation exit 1 fi你会不会觉得只要 ARB 有变更就失败太不近人情我一开始也这么想过但跑了几轮就发现它带来的收益远超那点麻烦。第一它逼着团队在提交流程里就处理翻译而不是攒到发布前痛不欲生第二diff 可以被当成自然的变更记录翻译团队每天只看这个 diff就不会漏掉任何新增。如果你觉得直接失败太严格可以加一个 staging 参数把变更提交到翻译分支但无论如何至少要保证变更被呈现出来。下一步是接入 Flutter 官方的 gen_l10n。这里需要一份 l10n.yaml配置 ARB 目录、模板文件和输出类名arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart output-class: AppLocalizations在鸿蒙分支的构建流程里你要注意 l10n.yaml 的输出路径是否会被鸿蒙壳工程的构建脚本覆盖。如果 gen_l10n 生成的 Dart 文件无法被正确编译优先检查输出目录有没有落在鸿蒙工程清理范围里然后再排查 SDK 版本差异。3.3 从新增文案到鸿蒙上屏一次完整的发布演练下面用一个具体的场景把整个流程串一遍。假设你的产品在鸿蒙上发现一个文案错误需要把加载中改成正在加载。开发人员做三件事改 Text(加载中) 为 Text(正在加载)、在 ja.arb 里补翻译、把 zh 和 en 的翻译也同步好。改完代码开发人员本地跑一次提取命令。扫描器发现加载中在源码中已经消失正在加载是新字符串于是自动生成新条目并把加载中放进清理列表。接着开发人员提交代码CI 提取 job 检测到 ARB 有变更要求对应语言文件已同步。翻译人员填完所有语言版本后提交更新后的 ARB 文件。CI 再次通过进入鸿蒙构建。在鸿蒙构建阶段Flutter 侧的 Dart 代码经过编译gen_l10n 生成的 AppLocalizations 类被引用字符串资源打包进鸿蒙壳工程。最终在 OpenHarmony 真机上运行切换到英文环境看到的不再是中文文案而是正确的英文翻译。整个过程里没有任何一个环节需要人肉去检查有没有漏。这句话说起来轻巧真正从零搭起来是需要把工具选型、CI 策略、翻译流程和鸿蒙构建路径全部打通才能做到的状态。4. 常见问题与排查技巧实录4.1 扫描结果总差几条多半是过滤规则问题我在实际用提取工具时第一个遇到的奇怪问题是扫描结果里总是少了几个界面上的文案但在代码里明明能看到。查了一圈发现那几条文案在源码里是用字符串拼接写的比如 正在 加载扫描器把它们当成两个独立字符串各自过滤了一遍最后都没有出现在 ARB 里。这类问题要靠跑完提取后人工比对一遍扫描日志来发现工具本身很难替你判断哪些拼接字符串应该合并成一句文案。还有一次界面上有段长文案在代码里用了多行字符串写法扫描器只提取了第一段后面几段成了孤儿。最后我把那处改成单行写法才彻底解决。遇到这种差几条的情况不要急着加过滤规则先把缺失字符串的反向定位做好看看它在源码里的写法是不是特殊。多数时候不是工具漏是源码写法超出了工具的默认识别范围。4.2 误提取与占位符错乱怎么处理误提取的主要来源是日志字符串被当成 UI 文案、国际化字符串里的大段富文本、以及把 URL 和文件路径错误地当成翻译目标。解决办法是维护一份清晰的 ignore 规则并且给不需要翻译的字符串加上标记注释。那些真正做了标记的地方后续代码评审时一眼就能看到这里有字符串被刻意排除避免同事误删。占位符错乱则往往出现在 ARB 合并之后。执行提取后有些条目里的 {} 与 Dart 里 ${} 插值语法不完全对应构造函数的模板信息对不齐。遇到这种情况逐个看 description 字段确认代码里用的是位置占位符 {0} 还是命名占位符 {name}然后再做统一替换。我习惯在提取之后加一个小校验脚本专门检查 ARB 里所有 value 的占位符集合跟模板文件对比不一致就直接报错防患于未然。4.3 鸿蒙构建链上的特有坑鸿蒙侧有些问题是 Android 上没有的。我遇到过最典型的一个gen_l10n 生成的 Dart 文件在 Android 构建环境下一切正常切到鸿蒙分支后却报了一堆编译错误。查下来是生成器产物里的类型声明与 OpenHarmony Flutter 分支自带的 analyzer 版本存在细微出入。解决办法通常是在 l10n.yaml 里显式声明要使用的输出类名并锁定 Flutter 分支版本让生成器的模板与 SDK 保持一致。另一个坑是资源目录被鸿蒙壳工程的构建脚本清理。Flutter 侧生成的 localizations 资源属于中间产物如果输出目录恰好落在鸿蒙工程维护的路径黑名单里构建产物里就会缺失本地化资源。表面现象是运行时永远显示默认语言排查起来容易绕弯。建议把国际化中间产物的输出统一收口到一个专用目录并且在鸿蒙壳工程的构建配置里加入保留或拷贝规则。4.4 排查问题速查表症状大概率原因快速解法扫描结果比界面上实际文案少字符串拼接在源码里被拆开检查拼接位置手动合并或补一条提取规则ARB 里出现僵尸 key删除的字符串没有被回收利用 not_found 列表清理不要手工删占位符翻译后显示 {0}ARB 中占位符与 Dart 插值不对应统一占位符风格之后加脚本校验gen_l10n 在鸿蒙分支编译失败生成的 Dart 与 SDK 分支不兼容检查 SDK 分支版本或者手动切换输出类运行时回到默认语言资源目录被鸿蒙壳工程清理把输出目录移到不被清理的路径加入构建脚本拷贝这些坑我基本都踩过一遍要说最值得记下的经验就是先让流程跑通再谈优化。很多团队一上来就想做增量扫描、做自动翻译结果基础链路都不稳。我个人的建议是先把全量扫描 ARB 变更失败机制跑起来让团队适应这种节奏等到流程本身稳定了再慢慢加增量提速、加机器翻译、加自动填充。工具不是越复杂越好能跟你的团队协作方式磨合好的才是能长期用的。就这样动手试试看。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑