☰
HarmonyOS 7 Hvigor:多语言资源键集漂移与RTL门禁
2026/10/8 23:04:28 网站建设 项目流程

一、英文没报错,不代表资源是完整的

这次问题出在提审前最后一轮阿拉伯语截图。LocaleSealLab 的中文、英文页面看起来都正常,切到ar_SA后,订单页右侧按钮突然显示成英文,金额说明里的数字顺序也不对。更麻烦的是,构建过程没有失败:ResourceManager 找不到区域资源时会回退到 base,页面仍然能打开,缺失就这样被“正常运行”掩盖了。

我们随后把 312 个 base 文案键与zh_CN、en_US、ar_SA三套资源做了矩阵对账,第一次得到 9 个缺失键、4 处占位符错配、2 处复数规则遗漏和 1 处 RTL 方向问题。它们分散在三个功能分支里,靠人工翻目录很容易漏。我把这次门禁任务定为 I18N-1812,页面叫 LocaleAuditPage,目标不是检查翻译好不好,而是确保每个运行分支都有结构完整、参数安全的资源。

这里先定一个边界:工具不会判断“退款处理中”翻成什么最地道,它只判断键是否存在、占位符类型和数量是否一致、复数资源是否覆盖当前语言规则,以及 RTL 页面在运行时是否真的镜像。语义质量仍需语言审核,但工程结构不能再依赖截图阶段碰运气。

二、把三套 JSON 变成一张可比较的资源矩阵

工程里新建tools/locale-audit,不侵入业务组件。LocaleMatrix.ts负责读取resources/base/element/string.json与限定目录,PlaceholderLint.ts分析%s、%d和位置参数,RuntimeLocaleProbe.ets在模拟器里切换区域并验证实际解析结果,LocaleAuditPage.ets只展示报告。Hvigor 的 release 任务在打包前调用这套脚本,debug 构建则可以手动运行。

第一段代码解决键集漂移。它以 base 为主集合,同时找出目标语言缺失键和反向多出的孤儿键。比较前先规范化 JSON 结构,而不是直接比较文件文本,否则字段排序和格式化会制造无意义差异。

// tools/locale-audit/LocaleMatrix.tstypeLocaleMap=Map<string,string>exportinterfaceLocaleDiff{locale:string;missing:string[];orphan:string[]}exportfunctioncollectLocaleMatrix(base:LocaleMap,locales:Map<string,LocaleMap>):LocaleDiff[]{constbaseKeys=newSet(base.keys())return[...locales.entries()].map(([locale,values])=>{constlocaleKeys=newSet(values.keys())return{locale,missing:[...baseKeys].filter((key)=>!localeKeys.has(key)).sort(),orphan:[...localeKeys].filter((key)=>!baseKeys.has(key)).sort()}})}

我们没有允许“缺失就回退”为通过。对用户可见的 release 页面,只要目标语言声明支持,缺失就是失败;测试页面和仅调试使用的键可以放入带到期版本的豁免清单。孤儿键同样要处理,因为它通常说明 base 中的键已改名,而某个语言包还保留旧版本。豁免必须写明页面、原因和删除版本,不能只给一个永久白名单。

三、占位符错配比缺少一句翻译更危险

最严重的一处英文资源把订单 %1$s 将在 %2$d 分钟后关闭写成了Order %1$s closes in %2$s minutes。键存在、页面也能打开,直到格式化参数走到数字分支才暴露问题。另一个阿拉伯语资源交换了金额和币种位置,却没有使用显式位置参数,导致代码侧传参顺序无法安全调整。

第二段代码将占位符解析成“位置 + 类型”的签名,不比较具体文案。无位置编号的占位符按出现顺序编号;同一键在所有语言中的签名必须与 base 一致。%%会先被剔除,避免把百分号当参数。

// tools/locale-audit/PlaceholderLint.tsconstTOKEN=/%(?:(\d+)\$)?([sdif])/gexportfunctioncomparePlaceholders(key:string,base:string,translated:string):string[]{constsignature=(text:string):string[]=>{letseq=0return[...text.replaceAll('%%','').matchAll(TOKEN)].map((m)=>`${m[1]??++seq}:${m[2]}`)}constexpected=signature(base)constactual=signature(translated)returnexpected.join('|')===actual.join('|')?[]:[`${key}: expected${expected.join(',')}but got${actual.join(',')}`]}

脚本只在构建进程中持有文件内容,执行结束立即释放,不生成带完整文案的持久缓存。重复调用时以文件哈希作为输入身份;同一哈希已经通过便复用结果,但规则版本变化会强制重跑。这样本地保存触发多次构建也不会并发写坏报告。

四、复数和 RTL 必须进入运行时探针

静态 JSON 对账解决不了两个问题。第一,复数规则并不是“单数、复数”两档,阿拉伯语的类别更多;第二,资源文件正确不代表组件布局真的随 RTL 镜像。于是门禁增加运行时探针:依次切换三种区域,调用 ResourceManager 读取指定键,再打开订单卡片采集方向、对齐和可见文本摘要。

// RuntimeLocaleProbe.etsexportasyncfunctionrunRuntimeProbe(context:UIAbilityContext,locale:string):Promise<ProbeResult>{constrm=context.resourceManagerconstorderId='20261003-1812'consttitle=awaitrm.getStringByName('order_expire',orderId,15)constzero=awaitrm.getPluralStringByName('cart_items',0,0)consttwo=awaitrm.getPluralStringByName('cart_items',2,2)constdirection=locale==='ar_SA'?'RTL':'LTR'hilog.info(0x1812,'LocaleSeal',`task=I18N-1812 locale=${locale}direction=${direction}probe=PASS`)return{locale,direction,values:[title,zero,two],passed:true}}

探针在专用测试 Ability 内运行,切换区域后重建页面,不在业务进程里热改全局语言。每个区域完成后销毁页面、取消布局监听,再进入下一轮,避免上一个方向的状态残留。若中途进入后台,当前区域标记为 ABORTED,恢复后从该区域重新开始,不沿用半份快照。

RTL 检查也没有粗暴要求所有 Row 反转。金额、订单号和时间戳可保持自身书写方向,镜像的是导航层级、边距、图标和阅读顺序。报告因此同时记录容器方向与文本方向,一处异常最终定位到开发者把margin.left写死,而不是使用 start/end 语义。

DevEco Studio 中能看到完整项目树、占位符比较代码、右侧三语言门禁页面和底部 HiLog。日志统一使用任务 I18N-1812:keys=312 locales=3 checks=936,最终行是state=RELEASE_READY missing=0 placeholder=0 plural=0 rtl=0 pass=936/936。这些数字也写入导出的报告,任何一处对不上都不允许 release 产物晋级。

五、从 16 个问题到一份可复查的证据

第一次扫描的 9 个缺失键中,4 个是新功能没有同步到ar_SA,3 个是重命名后留下的旧键,2 个属于错误豁免。4 处占位符错配全部改成显式位置参数;2 处复数资源补齐类别;那处 RTL 问题改用逻辑方向边距。重新运行后,三种区域、312 个键形成 936 项检查,全部通过,任务进入 RELEASE_READY。

手机结果页刻意不展示大量翻译文本,而是给出最有价值的结构证据:语言矩阵、问题归零、运行探针和 936/936 进度。按钮“注入占位符错配”用于演示失败路径,点击后英文资源的签名被测试层替换,状态立即变为 BLOCKED;“导出上架报告”只在 RELEASE_READY 可用。注入只存在内存,不修改资源源文件,退出页面自动清理。

六、门禁要能被维护,而不是成为新的包袱

规则接入 Hvigor 后,我们保留了三条维护约束。其一,base 删除键时必须同步清理各区域孤儿键;其二,新增支持语言要先进入 warning 模式,翻译交付完成后再切换 blocking;其三,规则升级要写版本号,旧报告不能冒充新规则结果。报告采用临时文件写完再原子替换,避免构建被取消时留下一个看似完整的 JSON。

工具的资源释放也要和页面一样严谨。文件句柄在单文件解析后关闭,运行探针的订阅在每个区域结束时解绑,导出任务使用 generation 丢弃迟到结果。连续点击扫描按钮只保留最新一轮,旧轮次即使完成也不能覆盖当前状态。

这次处理完以后,多语言审核不再是“把系统切成英文看一眼”。我们把原本隐藏在回退机制里的结构问题拆成键集、占位符、复数和方向四类,每一类都有静态证据或运行证据。翻译质量依旧需要人,但工程完整性终于可以在上架之前自动回答。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询