第44篇多语言切换文案错乱把资源键和页面刷新路径管住摘要多语言切换后有的页面变了、有的页面没变通常不是翻译文件本身的问题而是资源键散落、页面缓存了文案字符串、局部组件没有响应语言变化。更稳的做法是统一资源键命名页面只保存业务值不保存翻译后的文案并在语言切换后明确刷新可见页面。一个设置页切换英文后首页标题变成英文订单状态仍是中文弹窗按钮又变成了默认语言。翻译文件看起来都在问题出在代码里有的地方用$r有的地方在 Service 里提前拼好中文有的地方把按钮文案保存进了本地缓存。多语言问题要从资源 owner 和刷新路径一起查。这篇文章解决四个实际问题资源键按业务语义命名不按页面临时文案命名。模型和缓存保存业务枚举不保存翻译后的字符串。语言切换后发布 LocaleChangeEvent页面重新派生文案。缺失资源要有兜底语言和发布前扫描。先找谁缓存了翻译后的字符串多语言错乱往往不是资源文件少一条而是某层把中文文案当成业务数据保存了。先找缓存字符串再查资源缺失。位置现象风险处理方向首页变了列表没变列表项保存了 displayText缓存文案不会刷新保存业务枚举弹窗按钮错语言文案在 Policy 里写死切换后仍旧中文Policy 返回资源键部分组件不刷新没有监听语言变更局部状态不更新重建派生文案缺失资源某语言没有 key显示默认或空值发布前扫描这一步的价值是先把问题放回运行链路。只要能确认问题停在哪个位置后面就不用靠猜测改页面。业务数据不能保存翻译结果订单状态应该保存Paid、Pending这类枚举展示时再映射到资源。否则语言切换后本地缓存里的中文会一直存在。模块应该负责不应该负责Model状态枚举、金额、时间翻译后的 displayTextResourceKeys稳定资源键业务流程LocaleService当前语言和变更事件页面布局Page根据状态读取资源持久化语言文件Audit缺失 key 和硬编码文案用户操作边界定清后代码就不会在页面、服务和回调之间来回复制同一段判断。后续新增场景也能先判断应该落在哪一层。状态模型保存枚举不保存文案exportenumOrderStatus{PendingPaypending_pay,Paidpaid,Refundingrefunding,Closedclosed}exportinterfaceOrderListItem{orderId:stringstatus:OrderStatus amountText:stringupdatedAt:number}exportfunctionorderStatusResourceKey(status:OrderStatus):string{returnapp.string.order_status_${status}}模型层只保存状态枚举。amountText这种已经格式化的值也要谨慎跨语言场景最好由展示层按 locale 格式化。这类模型最好放在models或common中页面、Service 和仓储都使用同一份类型避免各层用字符串互相猜。语言切换只保存 locale不改业务缓存exportinterfaceLocaleSnapshot{localeCode:stringchangedAt:number}exportclassLocaleService{constructor(privatereadonlystore:LocaleStore){}asyncswitchLocale(localeCode:string):PromiseLocaleSnapshot{constsnapshot:LocaleSnapshot{localeCode,changedAt:Date.now()}awaitthis.store.save(snapshot)AppStorage.setOrCreateLocaleSnapshot(locale_snapshot,snapshot)returnsnapshot}}切语言不去批量改订单、消息、按钮缓存。它只更新 locale 快照页面收到变化后重新派生文案。Service 的目标不是把所有逻辑都塞进去而是把跨页面、跨生命周期或需要持久化的判断集中起来。页面只表达用户动作。页面根据 locale 重新渲染文案Componentstruct OrderStatusText{status:OrderStatusStorageLink(locale_snapshot)localeSnapshot:LocaleSnapshot{localeCode:zh-Hans,changedAt:0}privatestatusTextKey():string{returnorderStatusResourceKey(this.status)}build(){Text($r(this.statusTextKey())).fontSize(14).fontColor(#334155)}}这里的localeSnapshot不是为了直接拼文案而是让组件在语言变化时重新构建。真正展示仍通过资源键。页面层要控制展示和交互节奏但不应该拥有底层事实。这样页面重建、横竖屏变化或返回前台时都能重新从 Service 拿到可信结果。切换语言后刷新可见页面派生状态Componentstruct SettingsLanguagePage{Statesaving:booleanfalseprivatelocaleService:LocaleServicecreateLocaleService()asyncapplyLocale(localeCode:string):Promisevoid{this.savingtrueconstsnapshotawaitthis.localeService.switchLocale(localeCode)AppStorage.setOrCreateLocaleSnapshot(locale_snapshot,snapshot)this.savingfalse}}页面不需要手动遍历所有组件改文字。统一更新 locale 快照后依赖该快照的页面会重新派生资源文案。实际项目里很多问题不是第一次进入页面暴露而是在冷启动、返回前台、页面重建或回调晚到时暴露。把这段生命周期补上修复才算闭环。缺失资源要在发布前暴露情况页面表现工程处理某语言缺 key扫描资源文件差异补齐或回退到 base硬编码中文搜索中文字符串迁入 string.jsonService 拼文案返回枚举和参数页面负责资源映射切换后不刷新组件未消费 locale加入 locale_snapshot 依赖兜底路径不是可有可无。用户遇到异常时页面至少要给出当前状态、可执行动作和可排查线索。排查时搜硬编码和缓存文案rg-n[一-龥]{2,}entry/src/main/ets rg-ndisplayText|statusText|titleText|buttonText|LocaleSnapshotentry features common rg-nstring.json|order_status_|locale_snapshotentry/src/main/resources entry features common第一组命令找旧路径第二组命令找新边界第三组命令看生命周期或回调位置。命中后不要只看字段名要判断它是否仍在直接改页面状态。资源键要有稳定 owner多语言资源一乱后面会很难维护。一个常见问题是同一句文案在首页、订单页、弹窗里各自起了一个 key另一个问题是组件库也定义了同名 key打包后到底用哪一个值变得不可预测。资源键需要明确 owner谁渲染这段文案谁就拥有这条资源。资源类型建议 owner命名示例订单状态订单 featureorder_status_pending_pay通用按钮公共 UI 模块common_action_confirm设置项标题设置 featuresettings_language_title组件库内部文案组件库自身empty_panel_retry不要为了省事把所有文案都塞进一个巨大string.json后就不管。资源 owner 清楚后补语言、删页面、拆 HSP/HAR 时才知道该改哪里也能避免多个模块重复声明同一个资源名。验证清单验证项预期结果切英文首页、列表、弹窗都刷新订单状态不保存中文 displayText缺失 key发布前能扫到切回中文所有可见页面一致重启应用读取上次 locale这些验证最好在真机或模拟器里按顺序走一遍。只看源码很难发现冷启动、重启和回调晚到这类问题。常见问题和处理方式现象常见原因处理方式部分仍中文缓存了翻译字符串保存业务枚举弹窗按钮不变Policy 硬编码返回资源键或业务 kind切换无刷新组件不依赖 locale加入 LocaleSnapshot某语言空白资源 key 缺失扫描并补齐如果现象没有出现在表里仍然建议按“输入来源 - 中间状态 - 持久化或页面展示 - 失败兜底”的顺序排查。小结多语言切换的核心是别缓存文案多语言问题不是只补翻译文件。业务层保存枚举资源键稳定命名页面按当前 locale 派生文案切换后刷新可见页面发布前扫描硬编码和缺失 key。这样语言切换才不会出现半中文半英文。|如果现象没有出现在表里仍然建议按“输入来源 - 中间状态 - 持久化或页面展示 - 失败兜底”的顺序排查。小结多语言切换的核心是别缓存文案多语言问题不是只补翻译文件。业务层保存枚举资源键稳定命名页面按当前 locale 派生文案切换后刷新可见页面发布前扫描硬编码和缺失 key。这样语言切换才不会出现半中文半英文。