鸿蒙 PC Markdown 编辑器 ArkUI 资源本地化:从字符串目录到 Ability 生命周期的工程方法

桌面编辑器的本地化最容易在“看起来已经翻译”时留下隐患:工具栏是中文,弹窗仍是英文;启动时正确,回到前台后失步;资源键漏了一项,只有某个冲突流程才暴露;为了翻译错误消息,又把文件服务与 UI 强耦合。鸿蒙 PC 应用需要把语言资源当作可验证的工程资产,而不是在 ArkTS 中到处写条件表达式。

OhMarkdown 的 ArkUI 本地化实现已进入公开仓库 https://gitcode.com/VON-/codex_md_oh,核心提交为 ed13ee0,后续 PC 布局复核基线为 0d8d38b。本文聚焦原生侧:basezh_CN 资源目录、EntryAbility 生命周期、AppStorage 传播、动态状态的展示边界、资源一致性测试和自由窗口验证。ArkWeb 的 CodeMirror 词典与运行时重配另有独立实现,但本文会说明两者怎样在边界处握手。

先定义 ArkUI 本地化的责任边界

ArkUI 负责活动栏、文件面板、搜索、大纲、设置、标签栏、冲突操作和状态栏。这些内容多数是稳定产品文案,应该进入资源文件。文档正文、文件名、相对路径、编码名、LF/CRLF、底层系统错误和用户输入不属于翻译资源;它们必须原样保留或通过结构化参数展示。

这种划分避免两个极端。把所有字符串都写进资源,会迫使文件服务依赖 UI 上下文,也容易在格式化错误中丢失诊断细节;把所有文案都写在 ArkTS 里,则无法由系统根据语言自动解析,资源键也无法静态比对。合理做法是让稳定交互词汇资源化,让动态事实保持事实,再在展示层组合。

当前交付支持英文 base 与简体中文 zh_CN。base 是默认资源,也是未知语言和资源缺失时的回退;zh_CN 是已验证的中文集合。项目没有宣称繁体中文、法语等已经支持,未知 Preferences 值回退跟随系统,Web 词典未知标签回退英文。

资源目录是平台契约

英文资源位于 entry/src/main/resources/base/element/string.json,简体中文位于 entry/src/main/resources/zh_CN/element/string.json。二者使用相同 name,只改变 value。例如搜索能力在两套资源中保持一一对应:

{
  "string": [
    { "name": "search_panel", "value": "Search" },
    { "name": "search_current_document", "value": "Document" },
    { "name": "search_workspace", "value": "Workspace" },
    { "name": "quick_open", "value": "Quick Open" },
    { "name": "match_case", "value": "Case" },
    { "name": "match_whole_word", "value": "Whole" },
    { "name": "use_regular_expression", "value": "Regex" }
  ]
}

对应中文不是逐字机械翻译,而是符合编辑器语境的术语:

{
  "string": [
    { "name": "search_panel", "value": "搜索" },
    { "name": "search_current_document", "value": "当前文档" },
    { "name": "search_workspace", "value": "工作区" },
    { "name": "quick_open", "value": "快速打开" },
    { "name": "match_case", "value": "区分大小写" },
    { "name": "match_whole_word", "value": "全词匹配" },
    { "name": "use_regular_expression", "value": "正则表达式" }
  ]
}

资源名采用稳定语义而非页面位置。match_case 不叫 search_row_first_text,因为同一选项同时服务当前文档与工作区搜索;open_documentopen_action 分开,是因为一个用于完整入口,一个用于紧凑工具栏。稳定命名使布局调整时不必重命名资源,也让测试能按语义判断缺失项。

资源覆盖必须包含低频危险流程

本地化不能只覆盖首屏。OhMarkdown 把外部修改、恢复、保存中断、混合换行等低频但高风险流程也纳入资源。中文资源中真实存在 external_changes_messageuse_disk_version_messageinterrupted_save_messagerestore_backup_actionmixed_line_ending_message 等键。

这些流程的文案质量直接影响用户是否会丢文档。例如“使用磁盘版本”必须明确未保存本地修改会被替换;“恢复上一版本”必须与“保留当前版本”区别清楚。只翻译按钮而漏掉解释文本,会让中文用户在最需要判断时读到混合语言。资源键一致性检查能发现漏键,却不能判断语义质量,因此仍需要产品评审和设备截图。

设置项也使用资源:自动保存三种策略、图片资源目录、拖放复制/移动/仅引用、界面语言三种模式。资源层只描述选项,不改变枚举值。内部 AutoSavePolicy.AFTER_DELAYAssetDropMode.REFERENCEApplicationLanguage.SYSTEM 保持稳定英文标识,避免翻译影响持久化兼容。

组件只引用资源,不复制文案

ArkUI 组件通过 $r('app.string...') 获取 Resource,并把 Resource 继续传给 Builder。按钮、文本和无障碍名称共用同一资源来源,减少视觉文字与辅助功能文字不一致的概率。

@Builder
private activityButton(icon: Resource, label: Resource, panel: string) {
  Button() {
    SymbolGlyph(icon)
      .fontSize(21)
      .fontColor([this.activePanel === panel && this.sidebarOpen ? '#087A63' :
        $r('app.color.workspace_text_secondary')])
  }
  .type(ButtonType.Normal)
  .width(36)
  .height(36)
  .accessibilityText(label)
  .onClick(() => this.selectPanel(panel))
}

同一个 label 既不需要在 Builder 内判断语言,也不会在按钮无文字时失去可访问名称。图标按钮的视觉表达稳定,但屏幕阅读器会根据资源配置朗读“文件”“搜索”或英文对应词。若开发者在 .accessibilityText 里另写硬编码英文,截图看不出问题,辅助功能树却会立即暴露。

模式按钮同样传 Resource,不把“源码、分栏、预览”绑定到内部 source/split/preview。内部模式字符串用于协议,显示资源用于人机界面,两者各自稳定。这是国际化不会污染业务状态机的关键边界。

Ability 创建时建立初始语言事实

应用启动时,EntryAbility.onCreate 从 ResourceManager 同步读取当前 locale,并写入 AppStorage。工作台使用 @StorageProp('language') 订阅。这里读取的是平台已经解析后的当前语言,不是 Preferences 中的用户策略。

onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  const colorMode = this.context.resourceManager.getConfigurationSync().colorMode ??
    ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
  const language = this.context.resourceManager.getConfigurationSync().locale ?? 'en-US';
  AppStorage.setOrCreate('colorMode', colorMode);
  AppStorage.setOrCreate('language', language);
}

平台首选语言已在上一次选择中通过 i18n.System.setAppPreferredLanguage 设置,因此 ResourceManager 是当前资源配置的事实来源。若读取不到 locale,英文 base 是明确回退,而不是让空字符串向下传播。颜色和语言一并进入 AppStorage,但两者的业务更新函数分开,避免切换语言时意外重设主题。

onWindowStageCreate 只加载 pages/Index,不在窗口层重复应用语言。资源配置应在页面构建前可用;若把语言设置放到页面首次渲染后才读取,会先显示英文再闪成中文,PC 大窗口尤其明显。

配置更新与回到前台的双保险

系统语言可能在应用运行中改变。Ability 的 onConfigurationUpdate 接收新配置,只在 newConfig.language 存在时更新语言;缺失字段不应把当前值覆盖为默认。应用从后台回到前台时再从 ResourceManager 读取一次,弥补后台配置回调时序差异。

onConfigurationUpdate(newConfig: Configuration): void {
  const colorMode = newConfig.colorMode ?? ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;
  AppStorage.setOrCreate('colorMode', colorMode);
  if (newConfig.language) {
    AppStorage.setOrCreate('language', newConfig.language);
  }
}

onForeground(): void {
  const language = this.context.resourceManager.getConfigurationSync().locale ?? 'en-US';
  AppStorage.setOrCreate('language', language);
}

双入口不会产生两套业务逻辑。它们只更新同一个 AppStorage 键,工作台的 Watch 再同步 Web。重复写入相同值的成本很低,远小于因漏通知导致 ArkUI 与 ArkWeb 分裂的风险。

当前设备闭环验证的是应用内选择和强制停止重启。系统设置页面切换语言后的完整真机行为仍需要鸿蒙 PC 真机复核。实现覆盖生命周期并不等于所有设备路径已测试,工程文章必须把代码能力与实测证据分开陈述。

用户策略与当前资源语言必须分离

工作台同时持有 applicationLanguagelanguage。前者是 SYSTEMSIMPLIFIED_CHINESEENGLISH 中之一,用于设置按钮选中态并写入 Preferences;后者是平台当前解析出的 locale,用于决定状态栏显示和通知 ArkWeb。

@StorageProp('language') @Watch('onLanguageChanged')
private language: string = 'en-US';

@State private applicationLanguage: ApplicationLanguage =
  ApplicationLanguage.SYSTEM;

private onLanguageChanged(): void {
  this.setEditorLanguage(this.language);
}

中文系统下选择“跟随系统”时,applicationLanguagedefaultlanguage 可能是 zh-Hans-CN。若只保留一个字段,就无法既正确高亮策略,又正确加载当前资源。系统语言从中文改为英文时,前者不变,后者变化;这是跟随系统真正应有的状态转换。

初始化时 Preferences 异步恢复前者,Ability 已经提供后者。读取失败只让按钮回退跟随系统,不阻塞编辑器 Ready。显式选择时先更新平台资源,再同步两个字段,随后异步持久化策略。状态关系清晰后,重启与运行时切换才不会互相覆盖。

动态状态不应强行进入静态资源

状态栏包含稳定状态和动态详情。ReadyModifiedSavedAuto saved 等可以在展示层映射;Opened docs/plan.md:430Save failed: ...、系统异常和文件名包含动态参数,不适合简单做整句字符串匹配。

private getOperationStatusText(): string {
  if (resolveEditorLanguage(this.language) !== 'zh-CN') {
    return this.operationStatus;
  }
  if (this.operationStatus === 'Ready') return '就绪';
  if (this.operationStatus === 'Modified') return '已修改';
  if (this.operationStatus === 'Opened') return '已打开';
  if (this.operationStatus === 'Saved') return '已保存';
  if (this.operationStatus === 'Auto saved') return '已自动保存';
  if (this.operationStatus === 'Recovered') return '已恢复';
  return this.operationStatus;
}

当前实现优先完成常见稳定状态,其他详情原样保留。它不是终极国际化架构,但比在服务层到处读取资源安全。后续更合理的方向是结构化状态,例如 { code: 'SEARCH_RESULT_OPENED', path, line },展示层再用资源模板格式化。迁移必须逐步进行,因为保存、恢复和冲突状态关系到文档安全,不能为了翻译一次性重写可靠性链路。

字数也属于动态展示。中文显示 ${wordCount} 字,英文根据 1 或其他数值选择 word/words。它说明不同语言不总能靠同一静态字符串拼接;真正扩展到更多语言时应使用平台复数资源,而不是不断增加 if 分支。

资源键一致性是最低自动化门槛

两个 JSON 能解析并不代表资源完整。验证脚本应提取 string 数组中的 name,比较集合差异,同时检查重复键和空值。最终测试报告确认 base 与 zh_CN 键集合完全一致,两个 JSON 均解析通过。

资源集合测试能抓住三类常见回归:开发新按钮只添加 base;中文键拼写不同导致运行时回退;删除组件后只清理一套资源。它不能判断“Whole”是否应翻译为“全词匹配”,也不能发现中文过长造成裁切,所以仍需 UI 设备测试。

ArkTS 单元测试还覆盖语言解析:default 对应 SYSTEM,zh-Hans-CN 对应简体中文,en-US 对应英文,未知 fr-FR 回退 SYSTEM;跟随系统在中文系统中解析为中文,固定英文不受系统中文影响;Web 映射只输出 zh-CNen。这些纯函数测试让资源层与运行时的边界可重复验证。

长文本会直接改变 PC 布局

同一语义在不同语言中宽度差异很大。英文搜索选项是 Case / Whole / Regex,中文是“区分大小写 / 全词匹配 / 正则表达式”。侧栏可缩放到 220 vp 后,三项固定单行会裁切最右文字。这个问题不是翻译错误,而是国际化触发的响应式布局错误。

修复提交 0d8d38b 使用 300 vp 稳定断点:小于 300 时前两项一行,正则单独一行;达到 300 时恢复三项单行。每个 Text 自身 maxLines(1),由容器决定换行,而不是让文字在复选框旁随机折行。中文 220 vp 和 408 vp 均在模拟器辅助功能树中获得完整边界。

这说明资源验收必须覆盖最短与最长文案、最窄与常用窗口、显示缩放和无障碍文本。只在英文默认宽度截图通过,无法证明中文 PC 体验。资源层本身不控制布局,但本地化交付必须把布局证据纳入完成定义。

真实应用截图如何验证资源链路

下面是 MateBook Pro 2in1 模拟器中的简体中文工作台。文件、搜索、设置、工具栏、状态栏和编辑器占位文案使用同一语言,语言分段按钮显示“跟随系统 / 简体中文 / English”。图片来自应用内部,不是设计稿。

在这里插入图片描述

截图能够证明可见结果,但不能单独证明策略持久化和辅助功能状态。因此设备闭环还读取辅助功能树,确认“跟随系统 selected=true”,强制停止应用,再次启动后读取相同状态。英文模式也完成重启验证,原生工作台与 Web 编辑器同步英文。

布局测试继续覆盖默认 264 vp、最小 220 vp、拖动后 392 vp、最大 480 vp。资源文本没有造成标签栏、搜索选项或设置分段控件互相遮挡。系统原生标题栏右侧留白仍保留为窗口拖动区域,它不是应用资源遗漏,也不应被应用内容填满。

自动化、构建与设备结果

语言交付时 Playwright 30/30 通过,覆盖 Web 运行时切换;ArkTS UnitTestBuild 通过,覆盖语言纯函数;Debug HAP 与 ohosTest HAP 构建通过;MateBook Pro 2in1 模拟器 ohosTest 7/7。资源键集合无差异,git diff --check 通过。

PC 布局修复后再次执行全套验证。最终 Debug HAP 大小 1,520,352 字节,SHA-256 为 367ab8650479aa1fa8fe73bd1ebadd9a53f46659c850c2e388fc799d5cb88e5b;ohosTest HAP 大小 2,360,824 字节,SHA-256 为 b7230037b51044fe16168d2c835fb891e1c70f675941a1046165bc895217592c。哈希用于定位本次测试产物,两份均为未签名包,不能替代发布签名与商店验收。

测试分层各自回答不同问题:JSON 集合检查资源完整;单元测试证明映射规则;Playwright 证明 Web 状态不丢;HAP 构建证明 ArkTS 和资源可打包;ohosTest 证明设备服务路径;模拟器截图与辅助功能树证明真实 PC 界面。把它们合并成一个“测试通过”会掩盖证据边界。

性能、安全与离线属性

静态资源由 HAP 打包,不访问远程翻译服务。切换语言不会上传文档、文件名或使用行为,也没有新增网络权限。资源文件只包含产品文案,不包含用户数据。未知语言回退 base,避免动态下载失败造成空界面。

运行时切换主要是平台资源重配和固定数量组件更新,复杂度与界面组件数相关,与 Markdown 文档长度无关。ArkUI 不复制文档缓冲区,Ability 只传播短语言标签。资源体积的增量很小,当前无需引入按需下载;更多语言到来时再根据 HAP 体积测量决定。

资源值不能进入文件路径、命令 ID 或 Preferences 键。翻译人员改变“快速打开”文本不应改变 SearchPanelMode.QUICK_OPEN;改变“仅引用”文本不应改变 AssetDropMode.REFERENCE。这一隔离是安全性和升级兼容性的共同基础。

失败路径与降级策略

资源不存在时平台回退 base,至少保留可操作英文。Preferences 损坏或读取失败时回退 SYSTEM,不把未知值交给平台。HostContext 不可用时不尝试切换。平台更新失败时维持旧界面并显示失败状态。设置已应用但 flush 失败时保留当前会话语言,同时告诉用户无法跨重启保存。

Ability 配置更新没有 language 字段时保持旧值,前台再同步 ResourceManager。Web 尚未 Ready 时,工作台在 onEditorReady 再发一次当前语言。所有失败路径都不能阻止文件打开、编辑、保存和恢复;语言表现层优先降级,文档事实层继续运行。

对资源 JSON 的错误应尽量在构建前发现。解析失败、重复键、集合差异都应该成为质量门禁,而不是靠运行时某个低频弹窗触发。中文文本过长则通过 220 vp 侧栏、窄窗口和辅助功能边界测试发现。

术语治理与后续维护

本地化规模变大后,术语一致性比新增翻译速度更重要。工作区、快速打开、源码、分栏、预览、全词匹配、外部修改、磁盘版本等词应保持稳定。资源名作为技术词典索引,值作为产品术语,两者都需要代码审查。

新增功能的完成清单应包含:base 和 zh_CN 同时新增;按钮与 accessibilityText 引用资源;最窄侧栏和窗口检查;动态参数不硬拼;Web 命令如有对应文本同步词典;资源集合测试通过;至少一条真实设备路径。这样国际化不会成为开发结束后的补丁。

未来增加繁体中文或日文时,应创建独立资源目录、术语表、文本长度测试和设备验证,而不是把简体中文当作所有中文地区的默认正确答案。系统 default 策略仍应原样保存,让每台鸿蒙 PC 按自己的语言解析。

结论

OhMarkdown 的 ArkUI 本地化把资源目录、领域枚举、Ability 生命周期、AppStorage、组件引用、动态状态边界和测试证据连成一条可维护路径。ed13ee0 完成中英文基础能力,0d8d38b 进一步解决中文长标签在最窄侧栏中的裁切。最终自动化 30/30、ohosTest 7/7、资源集合一致和模拟器重启证据共同说明它不是只在截图里成立的翻译。

对鸿蒙 PC Markdown 编辑器而言,好的本地化不会侵入文件协议,也不会重建编辑器或依赖网络;它让稳定文案由平台资源管理,让动态事实保持真实,并在自由窗口、键鼠和辅助功能环境中都能完整表达。这才是后续扩大语言数量时可以继续演进的原生基础。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐