HarmonyOS Localization Kit + ResourceManager:多语言隐私文案的资源缺口扫描与回退拦截【鸿蒙心迹】
应用切到英文后,隐私页仍然能打开,按钮也能点,表面看没有异常。真正的问题藏在资源回退里:一条删除账号说明从 en_US 回退到了 base 中文,一条数据分析退出说明也没有目标语言定义。功能测试容易把它当作“有文字就行”,发布前核对却需要回答另一件事——每个声明支持的语言,关键合规文案是否都有明确版本。
本文用 LocaleGuard 做一个发布前资源审计工具。页面名 ReviewCheckPage,演示时间统一为 13:27,检查批次 locale_20261001_10,目录为 base / zh_CN / en_US,必检键 12 个。初始结果为缺口 2、回退命中 2、状态 BLOCKED;修复后缺口 0、回退命中 0、状态 READY。这些数字用于说明审计流程,不代表官方审核结论。

HarmonyOS 资源目录支持 base 与语言区域限定词目录,ResourceManager 可以依据当前配置获取资源。系统的回退能力是为了让界面尽量可用,不等于关键文案可以依赖默认值发布。尤其是隐私摘要、权限用途、账号删除和数据分析退出说明,回退后“看得到”与“目标语言已经维护”是两种结论。
一、先把“运行正常”拆成三个检查结果
LocaleGuard 不把所有字符串都设为强制同构。普通菜单文案允许暂时回退,关键合规文案则要求每个目标语言显式存在。审计把结果分成三类:
PRESENT:目标限定词目录存在该键,值非空,也不是占位符。FALLBACK:页面可以从base得到值,但目标语言目录没有显式定义。MISSING:目标目录和base都没有有效值,或内容仍是待办占位符。
为什么要区分 FALLBACK 与 MISSING?因为修复优先级不同。完全缺失通常会直接影响页面;回退则更隐蔽,测试机可能一直显示默认语言。对必检键,二者都阻断发布,但报告必须告诉维护者该补翻译还是补基础定义。
本例 12 个必检键里,问题集中在两项:en_US:data_delete_desc 与 zh_CN:analytics_opt_out。base 中都有中文默认值,所以运行时能显示,静态审计却标记为 FALLBACK。为了让手机诊断图更直观,报告汇总为“缺口 2 / 回退 2”;这里的“缺口”表示目标语言显式定义缺失,不等同于运行时完全无字符串。
二、资源清单要独立于页面代码
如果必检键散落在多个 Text($r(...)) 中,工具很难判断哪些属于发布门禁。示例建立一份 locale-policy.json,列出目标 locale、键名、严重级别和负责人。文章只展示对应的 ArkTS 类型,实际 JSON 可由脚本读取。
这段代码解决关键资源范围不清、每次靠人工翻页面的问题。
export type LocaleCode = 'base' | 'zh_CN' | 'en_US';
export type ResourceState = 'PRESENT' | 'FALLBACK' | 'MISSING';
export interface RequiredString {
key: string;
level: 'BLOCK' | 'WARN';
owner: 'PRIVACY' | 'ACCOUNT' | 'PERMISSION';
}
export const TARGET_LOCALES: LocaleCode[] = ['base', 'zh_CN', 'en_US'];
export const REQUIRED_STRINGS: RequiredString[] = [
{ key: 'privacy_summary', level: 'BLOCK', owner: 'PRIVACY' },
{ key: 'data_delete_desc', level: 'BLOCK', owner: 'ACCOUNT' },
{ key: 'analytics_opt_out', level: 'BLOCK', owner: 'PRIVACY' },
{ key: 'camera_purpose', level: 'BLOCK', owner: 'PERMISSION' }
// 示例工程共 12 项,其余键在策略文件中维护
];
清单不是翻译字典,它只描述审计范围。值仍放在标准资源目录中,页面继续通过 $r('app.string.xxx') 访问。把 owner 写进策略,是为了报告能路由给负责模块,而不是让发布同学逐个猜。
BLOCK 与 WARN 也要克制。若所有字符串都阻断,工具很快会被团队绕过;若关键隐私文案只是警告,门禁又失去意义。一个可维护的原则是:影响用户知情、授权、删除、退出选择或法律主体识别的文本使用 BLOCK,普通营销和辅助提示使用 WARN。
三、静态扫描看目录事实,不模拟系统回退
跨语言完整性最适合在构建前扫描文件。脚本直接读取 resources/base/element/string.json、resources/zh_CN/element/string.json 和 resources/en_US/element/string.json,构造 locale → key → value 映射。目标目录缺键时,不要立刻用 base 填上,而是保留 FALLBACK 状态。
这段代码解决目标语言缺键被默认资源自动掩盖的问题。
import fs from 'node:fs';
import path from 'node:path';
interface StringItem { name: string; value: string }
interface StringFile { string: StringItem[] }
function loadStrings(root: string, locale: LocaleCode): Map<string, string> {
const file = path.join(root, locale, 'element', 'string.json');
if (!fs.existsSync(file)) return new Map<string, string>();
const json = JSON.parse(fs.readFileSync(file, 'utf-8')) as StringFile;
return new Map(json.string.map(item => [item.name, item.value.trim()]));
}
function inspectKey(key: string, locale: LocaleCode,
tables: Map<LocaleCode, Map<string, string>>): ResourceState {
const value = tables.get(locale)?.get(key);
if (value !== undefined && value !== '' && !value.includes('TODO')) {
return 'PRESENT';
}
const fallback = tables.get('base')?.get(key);
return fallback !== undefined && fallback !== '' ? 'FALLBACK' : 'MISSING';
}
这段是 Node 侧 TypeScript 工具代码,不在手机运行。它使用文件事实回答“目标目录是否显式定义”,而 ResourceManager 回答“当前设备配置最终拿到什么”。两个结论需要同时存在,不能用运行时结果替代静态扫描。
脚本还要校验重复键、空白值和占位符。JSON 能解析不代表内容可发布。常见占位包括 TODO、TBD、待翻译、复制的资源键名,以及只有空格的值。判断规则应放在配置里,避免脚本散落大量硬编码。
不要用中文字符比例自动判断英文是否翻译完成。品牌名、链接、邮箱和法规名称可能合法保留;反过来,全英文字符串也可能是错误占位。工具适合发现结构缺口,语言质量仍需要人工复核。
工程目录如下:
entry/src/main/resources/base/element/string.jsonentry/src/main/resources/zh_CN/element/string.jsonentry/src/main/resources/en_US/element/string.jsontools/locale-audit/scan.tstools/locale-audit/locale-policy.jsonpages/ReviewCheckPage.ets
下图是 DevEco Studio 风格的演示画面,不是编译或审核证据。左侧展示三个限定词目录,中间是 inspectKey(),右侧模拟器显示 BLOCKED,底部日志明确列出两处缺口。

四、报告要保留“哪个目录缺了哪一项”
只输出 missing=2 对修复没有帮助。脚本需要给出 locale、key、状态、来源与严重级别,并以非零退出码阻断候选发布构建。报告 JSON 可以保存到发布快照,方便后续解释当时检查了哪些键。
这段代码解决扫描结果无法定位、CI 仍把缺口当成功的问题。
interface AuditIssue {
locale: LocaleCode;
key: string;
state: ResourceState;
fallbackFrom?: 'base';
level: 'BLOCK' | 'WARN';
}
export function audit(root: string): AuditIssue[] {
const tables = new Map<LocaleCode, Map<string, string>>();
TARGET_LOCALES.forEach(locale => tables.set(locale, loadStrings(root, locale)));
const issues: AuditIssue[] = [];
for (const locale of TARGET_LOCALES.filter(item => item !== 'base')) {
for (const required of REQUIRED_STRINGS) {
const state = inspectKey(required.key, locale, tables);
if (state !== 'PRESENT') {
issues.push({
locale,
key: required.key,
state,
fallbackFrom: state === 'FALLBACK' ? 'base' : undefined,
level: required.level
});
}
}
}
return issues;
}
const issues = audit('entry/src/main/resources');
const blockers = issues.filter(item => item.level === 'BLOCK');
console.log(JSON.stringify({ auditId: 'locale_20261001_10', issues }, null, 2));
process.exitCode = blockers.length > 0 ? 2 : 0;
本例第一次运行返回退出码 2,并产生两条 BLOCK:en_US:data_delete_desc 与 zh_CN:analytics_opt_out。修复后再次运行,缺口与回退均为 0,退出码才回到 0。门禁状态因此从 BLOCKED 进入 READY。
注意不要让脚本自动把 base 文案复制进目标语言目录。复制后结构检查会变绿,内容仍然错误,反而失去提示。工具可以生成待办清单,但翻译与法务确认应由明确责任人完成。
报告中也不需要收集所有文案全文。对发布快照,键名、内容哈希、locale、状态与策略版本通常足够;敏感或尚未公开的文案不应在公共 CI 日志里完整打印。需要人工比对时,从受控构建产物读取。
手机运行图显示审计首轮:批次 locale_20261001_10,12 个必检键,base / zh_CN / en_US,缺口 2、回退 2,状态 BLOCKED。红色标注只指向两处需要修复的键和发布阻断状态。

五、ResourceManager 用来确认运行时最终取值
静态扫描修好后,还要在应用里确认关键资源能被当前配置正常解析。HarmonyOS ResourceManager 提供 getStringValue()、getStringSync() 等接口;官方参考说明 getStringValue() 从 API 9 起可用,旧的 getString() 已弃用。示例使用当前 UIAbility 上下文的 resourceManager,不调用已弃用接口。
这段代码解决资源文件存在,但运行时 ID、引用或格式仍异常的问题。
import { common } from '@kit.AbilityKit';
interface RuntimeCheck {
key: string;
status: 'RESOLVED' | 'ERROR';
length: number;
}
export async function verifyRuntime(context: common.UIAbilityContext):
Promise<RuntimeCheck[]> {
const targets = [
{ key: 'privacy_summary', res: $r('app.string.privacy_summary') },
{ key: 'data_delete_desc', res: $r('app.string.data_delete_desc') },
{ key: 'analytics_opt_out', res: $r('app.string.analytics_opt_out') }
];
const result: RuntimeCheck[] = [];
for (const item of targets) {
try {
const value = await context.resourceManager.getStringValue(item.res.id);
result.push({ key: item.key, status: 'RESOLVED', length: value.length });
} catch (_) {
result.push({ key: item.key, status: 'ERROR', length: 0 });
}
}
return result;
}
运行时检查只证明“当前配置能解析”,不会告诉你值是否来自目标目录还是 base。因此它不能替代静态扫描。反过来,静态文件存在也不证明资源 ID 使用正确,二者是互补关系。
示例只记录长度和状态,不把隐私全文写入 HiLog。页面可以在受控调试构建中展示内容摘要,发布版本不需要保留这套诊断入口。若资源包含 %s、%d 等格式化占位,还要检查参数数量和类型;单纯 getStringValue 成功不代表格式化调用一定正确。
当应用支持运行中切换语言时,还要确认页面如何重建或刷新。本文不假设修改设备语言后所有组件自动完成业务状态恢复。语言切换属于配置变化,页面应保存与语言无关的业务 ID,再重新读取资源,不要把已经解析的字符串长期缓存为业务数据。
六、修复完成页不只显示一片绿色
第二张手机图与首轮结果明显不同:它展示修复后的详情。en_US:data_delete_desc 与 zh_CN:analytics_opt_out 均为 PRESENT,静态扫描 12 / 12,运行时抽检 3 / 3 RESOLVED,回退 0,最终状态 READY。

READY 的含义要写得克制:本工具定义的资源结构门禁通过,不代表应用已经通过官方审核,也不代表翻译的法律含义得到确认。报告页应明确“结构检查”“语言审校”“法务确认”“平台审核”是不同步骤。
实际项目还可以增加策略版本,例如 policyVersion=3。当必检键增加时,旧报告不能继续冒充新规则下的通过记录。发布快照至少保留 auditId、Git commit、策略版本、目标 locale、键集合哈希、问题数和生成时间。
七、最容易误判的四种情况
第一,base 使用英文,因此 en_US 缺键似乎没有视觉问题。若团队明确把 base 作为英文权威来源,可以在策略中允许;但这个决定要显式记录,不能由脚本根据字符猜测。本例 base 为中文,所以英文关键键必须显式存在。
第二,同一个资源键在 HSP 与 entry 中都有定义。运行时访问方式与模块上下文会影响最终资源,单扫 entry 可能漏掉来源。多模块项目应为每个发布模块建立资源图,必要时使用带 bundleName、moduleName 的 Resource 对象核对跨包访问。
第三,字符串存在但语义过期。比如隐私摘要仍写旧服务名称,结构扫描不会发现。可以给关键文案附内容版本或审批单号,工具核对版本,但最终语义仍需人工判断。
第四,资源值被代码拼接。合规文案被拆成多段后,翻译顺序和链接位置可能变化。关键说明最好使用完整可审校的资源模板,通过受支持的格式化参数插入变量,不要用多个片段硬拼句子。
八、把门禁接在候选发布前,而不是每次输入后
静态扫描很快,可以在提交阶段运行;完整运行时抽检需要构建和启动应用,更适合候选发布流水线。两者不必绑成一个大脚本。快速检查负责尽早发现缺键,运行时检查负责确认产物行为。
流水线失败时输出修复清单,不自动修改文件;通过时保存报告但不宣称“审核通过”。若平台规则或目标市场改变,先更新策略文件,再重新生成报告。工具的价值不是代替审核人员,而是把低级、重复、可枚举的资源问题挡在提交之前。
文章开头那个“页面能打开”的现象,正是资源回退最容易制造的错觉。系统尽量给用户一个可用界面,工程门禁则要告诉团队哪些语言其实没有完成。一个负责运行连续性,一个负责发布完整性,二者并不矛盾。
最后留下的做法是:用策略文件定义关键键,用 Node 扫描目录事实,用 ResourceManager 验证当前配置解析,用非零退出码阻断候选发布。不要把默认回退当翻译完成,也不要把工具通过写成官方审核结论。
九、限定词不止语言,扫描范围也不能只看三个目录
资源匹配还可能受到地区、屏幕方向、设备类型、深浅色等配置影响。本文聚焦多语言,因此只把 base、zh_CN、en_US 放进主报告,但工具设计不能默认资源树永远只有这三层。若关键文案在更具体的限定词目录中被覆盖,目标设备上看到的可能不是语言目录里的值。
举例来说,项目存在一个面向特定地区的资源目录,里面复制了旧版 privacy_summary。静态语言扫描显示 zh_CN 与 en_US 都齐全,运行到该地区配置时却命中更具体的旧资源。要避免这种情况,可以先遍历所有资源目录,找出同名关键键的覆盖关系,再对每个覆盖值做版本或哈希检查。
工具不需要模拟完整的系统匹配算法,否则很容易把自己写成另一个 ResourceManager。更现实的做法是两层检查:静态层列出所有关键键定义位置,发现意外覆盖就阻断或警告;运行时层在计划支持的代表性配置上读取最终值。算法仍由系统负责,工具负责暴露工程事实。
目录命名也要按官方资源限定词规则执行,不能为了让脚本好写自创 english、china 之类目录。扫描器遇到未知目录时不应悄悄忽略,至少在报告中列为 UNRECOGNIZED_QUALIFIER,提醒维护者确认它是否会进入构建。
此外,base 不是“中文目录”的同义词,而是默认资源。示例工程选择中文作为 base,只是项目决策。若另一个应用把英文放在 base,策略就应改变,不要复制本篇结论。审计的关键是显式声明权威来源,而不是假定某个目录天然属于某种语言。
十、格式化参数错位比缺键更晚暴露
多语言资源即使都有键,格式化参数也可能不一致。中文写“将在 %d 天后删除”,英文误写成两个 %d;或者译文把 %1$s 改成普通文本,代码传参仍能编译,运行到特定页面才出现异常或错误文案。
静态工具可以提取 %s、%d、%f 及带序号的占位符,比较 base 与目标语言的参数集合。比较时关注类型和序号,不强求出现顺序相同,因为不同语言可能需要重排。%% 是转义百分号,不能误判为参数。
复数资源也不能拿普通 string 规则处理。官方 ResourceManager 提供复数字符串访问能力,英文存在单复数变化,而中文规则不同。工具应为 plural 建立独立策略,检查目标语言需要的类别是否齐全;不要因为中文只有一种形式,就要求英文也只有一种。
链接占位同样敏感。隐私页常在整句中插入“隐私政策”“第三方清单”等可点击片段。如果页面通过多段字符串拼接,目标语言可能需要改变顺序。更稳妥的是让资源保存完整模板和明确占位符,UI 根据标记建立富文本;静态工具再校验标记成对、目标地址键存在。
这些检查适合成为第二阶段规则。第一版先解决缺键、空值、占位和意外回退,团队愿意使用后再加入参数一致性。一次塞入几十条未经验证的规则,会让误报淹没真正问题。
十一、报告本身也要可追溯
如果审计报告只存在控制台里,几周后很难说明某个发布包当时检查了什么。LocaleGuard 建议把报告作为候选发布的旁路产物,文件名包含 auditId,不把它打进 HAP,也不展示给终端用户。
报告至少包含策略版本、Git commit、模块名、目标 locale、关键键总数、问题明细、扫描器版本和生成时间。若记录内容哈希,应先规范化换行和空白,避免不同操作系统产生无意义差异。哈希用于确认版本,不用于判断译文质量。
修复后不要覆盖失败报告。第一次 BLOCKED 与第二次 READY 是一条完整过程,保留两份更容易复盘:哪两个键被补齐、策略是否改变、是否还有警告。若直接覆盖,最终只剩一片绿色,失去了修改证据。
发布人员看到 READY 时,还要核对它对应的 commit 与候选包一致。旧 commit 的报告不能复用到新包。可以在构建流水线中把 commit 和策略哈希写入报告,并在打包阶段比较;不一致就重新扫描。
报告保存周期根据团队合规要求决定,本文不指定固定天数。无论保存多久,都应避免包含完整敏感文案、个人信息或密钥。结构事实与内容哈希足以支持大多数工程追溯。
十二、验收矩阵要覆盖回退与显式定义的差别
最小测试矩阵至少包括八种情况:目标语言键存在;目标语言缺键但 base 存在;两边都缺;目标值为空;目标值为 TODO;目标值参数集合错误;未知限定词覆盖关键键;运行时资源 ID 无效。
对 en_US:data_delete_desc,首轮期望是静态 FALLBACK、发布 BLOCKED、运行时可能 RESOLVED。这个组合正好证明运行时可解析不等于目标语言完整。修复后才应变成静态 PRESENT、运行时 RESOLVED、发布门禁通过。
对 zh_CN:analytics_opt_out 同理。若修复时只是把 base 中文复制过去,结构会通过,但人工审校可能认为措辞仍未确认。因此验收表应有独立的“语言审校状态”,不要把它塞进 ResourceState。
未知限定词覆盖场景则要看工具是否列出定义位置,而不是一定判断哪个值最终胜出。运行时代表性配置负责验证最终解析。两层证据合起来,比脚本自己模拟一套不完整的匹配顺序更可靠。
最后还应验证失败出口。存在 BLOCK 项时,脚本退出码为 2,候选发布任务停止,但普通本地开发不必因此无法启动。门禁位置要与使用场景匹配:开发阶段给清晰提示,候选发布阶段执行阻断,正式提交前再由人工确认报告。
十三、参考资料与核对说明
- HarmonyOS ResourceManager API 参考:https://developer.huawei.com/consumer/en/doc/harmonyos-references-V13/js-apis-resource-manager-V13
- OpenHarmony 资源分类、限定词目录与访问说明:https://gitee.com/openharmony/docs/blob/6c4995a2bb86624aa86bbb350951f13f51dd5f96/zh-cn/application-dev/quick-start/resource-categories-and-access.md
- HarmonyOS 上架审核社区主题:https://developer.huawei.com/consumer/cn/forum/topic/0201218124295377819
本文未声明完成 DevEco Studio 构建、语言专家审校、法务确认或官方审核。图片为与正文数据一致的演示界面。
更多推荐




所有评论(0)