鸿蒙 PC Markdown 编辑器界面语言切换:跟随系统、简体中文与 English 的完整状态闭环 📅 2026/7/22 8:44:27 鸿蒙 PC Markdown 编辑器界面语言切换跟随系统、简体中文与 English 的完整状态闭环界面语言设置看起来只是三个按钮真正落到鸿蒙 PC 编辑器中却横跨资源系统、Ability 生命周期、ArkUI 状态、ArkWeb 运行时、持久化和无障碍。任何一段没有接通用户都会遇到“按钮已切换但部分界面没变”“重启后恢复错误”“编辑器内容被刷新清空”之类的问题。OhMarkdown 将语言能力作为工作台基础设施处理而不是把中文字符串散落在组件里。本文基于公开仓库 https://gitcode.com/VON-/codex_md_oh 的真实实现核心功能提交为ed13ee0布局与设备复核后的当前基线为0d8d38b。文章只描述已经进入主分支并通过自动化、构建和 MateBook Pro 2in1 模拟器验证的能力跟随系统、简体中文、English 三种互斥选项运行时立即更新 ArkUI 与 ArkWeb并在应用重启后恢复用户选择。繁体中文、日文和云端翻译不在当前实现范围内。语言设置不是普通的字符串替换Markdown 编辑器同时运行两套 UI 技术栈。文件树、搜索、设置和状态栏属于 ArkUICodeMirror 编辑器、命令面板、预览以及三方差异属于离线 ArkWeb。只更新 ArkUI 资源编辑器占位文案和命令面板仍会保留旧语言只更新 Web 文案系统选择器、工具栏和辅助功能树又不会变化。因此产品层的“当前语言”必须被拆成三个相关但不同的概念用户选择的模式、当前系统解析出的语言标签、Web 编辑器接受的有限语言标识。“跟随系统”也不能等同于“当前是中文”。用户选择的是一条持续策略而不是某次解析结果。若把zh-Hans-CN直接写入设置应用重启后虽然仍显示中文但设置界面无法知道用户原来选的是“跟随系统”还是“固定简体中文”设备语言后来改成英文也不会跟随。OhMarkdown 因此持久化default每次启动和配置更新再解析系统首选语言。完成标准不是文字变了就结束而是三项互斥选择即时生效应用内部的 ArkUI 与 ArkWeb 同步打开文档、光标、撤销历史和脏状态不丢失重启恢复的是策略无障碍树能读出选中态写入失败时仍能给出可理解状态离线边界不改变。统一的语言领域模型entry/src/main/ets/shared/services/LocalizationService.ets用枚举限制可写入值。SYSTEM使用平台约定的default固定中文和英文使用明确的 BCP 47 风格标签。解析函数对旧值或不同大小写进行归一化未知值回退到跟随系统避免损坏的 Preferences 把 UI 留在不可恢复状态。exportenumApplicationLanguage{SYSTEMdefault,SIMPLIFIED_CHINESEzh-Hans-CN,ENGLISHen-US}exportfunctionparseApplicationLanguage(value:string):ApplicationLanguage{constnormalizedvalue.toLowerCase();if(normalized.startsWith(zh)){returnApplicationLanguage.SIMPLIFIED_CHINESE;}if(normalized.startsWith(en)){returnApplicationLanguage.ENGLISH;}returnApplicationLanguage.SYSTEM;}这里没有把任意字符串直接交给平台 API。固定集合使设置面板、资源目录、测试矩阵和 Web 文案表保持一致也给未来增加语言留下明确入口。parseApplicationLanguage接受zh、zh-CN或zh-Hans-CN等历史形态但落回领域枚举它并不声称支持所有中文地区差异当前产品仍只有一套简体中文资源。Web 侧不需要理解完整地区标签。resolveEditorLanguage只映射为zh-CN或en这是当前EDITOR_MESSAGES真正具备的两套词典。把平台标签与 Web 词典键隔开可以避免 UI 层到处编写startsWith(zh)也防止未来增加地区资源时无意访问不存在的 Web 文案。跟随系统的解析与立即生效语言应用函数先告诉系统“应用首选语言是什么”再求出当前进程应使用的具体语言。跟随系统时读取首选语言若平台意外返回空字符串才回退英文固定语言则直接使用枚举值。最后通过应用上下文更新当前资源配置。exportfunctionresolveApplicationLanguageTag(language:ApplicationLanguage,systemLanguage:string):string{if(languageApplicationLanguage.SYSTEM){returnsystemLanguage.length0?systemLanguage:ApplicationLanguage.ENGLISH;}returnlanguage;}exportfunctionapplyApplicationLanguage(context:Context,language:ApplicationLanguage):string{i18n.System.setAppPreferredLanguage(language);constresolvedLanguageresolveApplicationLanguageTag(language,i18n.System.getFirstPreferredLanguage());context.getApplicationContext().setLanguage(resolvedLanguage);returnresolvedLanguage;}调用顺序有实际意义。首选语言保存的是用户策略setLanguage负责当前进程的资源刷新。若只做后者当前画面可能改变但系统和下次启动不知道用户选择若只做前者正在显示的页面可能等待下一次生命周期才刷新用户会误以为按钮失效。该函数明确可能抛出平台业务异常调用方必须保留旧状态并报告失败。语言切换不值得通过吞异常制造“界面一半中文一半英文”的假成功。当前实现不把平台错误详情翻译后覆盖因为原始诊断对开发和设备排查仍有价值。设置面板的互斥交互设置入口放在“更多操作”面板顶部三项用分段式按钮表达互斥关系。它不是三个开关任何时刻都应有且只有一个值。按钮视觉状态由applicationLanguage决定而不是由解析后的language决定所以中文系统下“跟随系统”和“简体中文”不会同时高亮。BuilderprivateapplicationLanguageButton(label:Resource,language:ApplicationLanguage){Button(label).type(ButtonType.Normal).layoutWeight(1).height(28).padding({left:4,right:4}).borderRadius(5).fontSize(10).backgroundColor(this.applicationLanguagelanguage?$r(app.color.workspace_sync_selected):Color.Transparent).accessibilitySelected(this.applicationLanguagelanguage).onClick(()this.updateApplicationLanguage(language))}accessibilitySelected是这段实现中不可省略的一行。只靠背景色不能让屏幕阅读器知道哪个选项生效也不利于自动化从辅助功能树验证状态。模拟器最终返回“跟随系统 Button selectedtrue”另外两项为 false这比截图中的颜色证据更可靠。按钮高度、字体和内边距使用稳定尺寸三个长短不同的标签共享一行。中文“跟随系统”和英文English都不会改变容器高度也不会在点击时推动下方设置项。当前设置侧栏允许在 220 至 480 vp 间调整语言按钮在最小宽度仍保持完整更长语言名称若在未来加入则需要重新评估分段控件而不是继续压缩字体。单次更新必须连接四个状态WorkspaceShell.updateApplicationLanguage同时处理平台资源、选中态、StorageProp语言、ArkWeb 消息和 Preferences。任何一步失败都不应阻断文档会话。具体顺序是先应用平台语言获得已解析标签再更新两个 ArkUI 状态并主动通知 Web最后异步保存用户策略。privateupdateApplicationLanguage(language:ApplicationLanguage):void{constcontextthis.getHostContext();if(!context)return;try{constresolvedLanguageapplyApplicationLanguage(context,language);this.applicationLanguagelanguage;this.languageresolvedLanguage;this.setEditorLanguage(resolvedLanguage);saveApplicationLanguage(context,language).catch((){this.operationStatusresolveEditorLanguage(this.language)zh-CN?无法保存界面语言设置:Unable to save language setting;});this.operationStatusresolveEditorLanguage(resolvedLanguage)zh-CN?界面语言已更新:Language updated;}catch(_){this.operationStatusresolveEditorLanguage(this.language)zh-CN?无法更新界面语言:Unable to update language;}}这里存在两个失败级别。平台应用失败时进入外层catch不宣称语言已更新Preferences 写入失败发生在画面已经切换后保留本次运行结果但状态栏告诉用户无法跨重启保存。这样的语义比“任何保存失败都回滚整个 UI”更稳妥因为回滚资源配置本身也可能再次失败并造成编辑器闪烁。Watch(onLanguageChanged)提供生命周期变化的第二入口。当 Ability 因系统配置变化更新AppStorage工作台会再次调用setEditorLanguage。显式点击和系统变化最终收敛到相同的 Web 更新函数不需要维护两套同步逻辑。Preferences 保存的是选择而不是结果语言设置复用ohmarkdown-settings没有新增数据库或网络账户。读取时默认值为ApplicationLanguage.SYSTEM再经过解析函数写入后明确flush保证模拟器强制停止进程前数据已经落盘。exportasyncfunctionloadApplicationLanguage(context:Context):PromiseApplicationLanguage{constsettingsawaitpreferences.getPreferences(context,ohmarkdown-settings);constvalueawaitsettings.get(application-language,ApplicationLanguage.SYSTEM);returnparseApplicationLanguage(String(value));}exportasyncfunctionsaveApplicationLanguage(context:Context,language:ApplicationLanguage):Promisevoid{constsettingsawaitpreferences.getPreferences(context,ohmarkdown-settings);awaitsettings.put(application-language,language);awaitsettings.flush();}读取失败回退跟随系统避免设置存储异常阻止编辑器打开。加载动作在编辑器 Ready 后的一次性初始化中执行它只恢复分段按钮的策略状态平台已经在 Ability 启动配置中提供当前资源语言。此处没有把异步加载结果重新强制应用一次避免启动阶段重复刷新资源。显式选择时才调用平台应用函数。Preferences 适合这类少量、低频、非敏感的用户设置。它不适合保存整篇 Markdown、撤销历史或大量词典。语言值没有个人隐私也不需要云同步。产品后续若做跨设备配置同步必须定义“跟随系统”在不同设备上的语义不能简单把当前解析语言上传。Ability 生命周期负责系统变化EntryAbility在创建、配置更新和回到前台时把资源管理器中的语言写入AppStorage。这覆盖了应用启动、系统语言变化通知以及后台期间配置改变三种路径。工作台使用StorageProp(language)订阅而不是直接持有一次性读取结果。onConfigurationUpdate(newConfig:Configuration):void{constcolorModenewConfig.colorMode??ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT;AppStorage.setOrCreate(colorMode,colorMode);if(newConfig.language){AppStorage.setOrCreate(language,newConfig.language);}}onForeground():void{constlanguagethis.context.resourceManager.getConfigurationSync().locale??en-US;AppStorage.setOrCreate(language,language);}回到前台时再读取一次是必要的防线。设备控制面板可能在应用后台更改语言配置回调的时序不应成为唯一事实来源。前台同步开销只是一次资源配置读取不涉及文档重载或目录扫描。当前模拟器验证了应用内显式切换与强制重启恢复通过系统控制面板切换设备语言后的完整 PC 真机生命周期仍列为边界。文章不把未执行的真机验证写成已通过。即便如此Ability 的生命周期路径和跟随系统存储语义已由单元测试与辅助功能树证据覆盖。ArkWeb 切换不重建编辑器原生层只执行受限入口window.OhMarkdownEditor?.setLocale(...)。它不向 Web 暴露 Preferences、系统上下文或任意脚本参数。语言值先被resolveEditorLanguage限定为zh-CN或en再通过JSON.stringify进入脚本避免把未验证字符串拼接到 JavaScript 中。privatesetEditorLanguage(languageTag:string):void{consteditorLanguageresolveEditorLanguage(languageTag);this.runEditorScript(window.OhMarkdownEditor?.setLocale(${JSON.stringify(editorLanguage)}));}Web 侧不重新加载index.html也不重新创建EditorView。CodeMirror 的文档状态、选择区、历史和滚动均留在原对象中只有占位扩展通过Compartment.reconfigure更新。命令面板若正在打开会立即重新筛选和渲染三方差异若正在显示会基于现有 baseline/local/disk 数据重绘标签不重新读取文件。这条设计直接保护编辑器的核心资产。用户可能在未保存文档中完成大量输入语言设置不能成为清空内容的高风险动作。自动化用运行时中英文切换断言文档内容和视图状态仍存在而不仅比较某个 DOM 文本。静态资源与动态状态的边界ArkUI 静态文案放在entry/src/main/resources/base/element/string.json与zh_CN/element/string.json。两套资源键集合必须完全一致工具栏、文件面板、搜索、设置、冲突操作和空状态都引用$r(app.string...)。构建验证会解析两个 JSON 并比较键集合漏翻译不会拖到运行时才发现。状态栏里部分文本来自已有英文状态机例如Ready、Modified、Auto saved。当前工作台在展示层通过getOperationStatusText映射常见稳定状态并保留包含文件名、异常详情或底层服务消息的原文。这样既让正常使用路径本地化也不会把动态诊断粗暴切割或隐藏。这一边界还有工程价值文件处理服务继续返回稳定的技术错误不需要引用 UI 资源展示层决定是否翻译。若将来引入结构化错误码应逐步替代字符串比较但不能为了“全翻译”一次性改写所有可靠性状态机。语言功能不应扩大文件保存和冲突处理的回归面。真实应用截图与可见结果下面截图来自 HarmonyOS MateBook Pro 2in1 模拟器。设置面板显示三段式语言选择当前选择“跟随系统”系统语言为中文工具栏、面板标题、状态栏和编辑器占位文案均为中文。模拟器辅助功能树给出的关键结果为跟随系统 Button selectedtrue、简体中文 Button selectedfalse、English Button selectedfalse。强制停止并重新启动后仍是这一状态说明持久化的是default策略而非本次解析得到的中文标签。英文模式另有真实截图和重启验证。原生工作台、Web 占位、命令面板以及三方差异标签同步为英文切回简体中文后状态栏显示“界面语言已更新”和“0 字”。两种切换都没有重建标签或改变未保存状态。自动化测试怎样证明没有假切换Web Playwright 新增运行时切换覆盖不只检查页面标题。测试先建立编辑器内容和命令面板状态再调用setLocale(zh-CN)断言中文占位、中文命令搜索、可访问名称和三方比较文本切回英文后再次断言同时确认核心文档状态没有被重置。最终 Web 测试为30/30通过。ArkTS 单元测试覆盖三组纯函数设置值解析、跟随系统标签解析、平台标签到 Web 词典键的映射。资源验证读取 base 与 zh_CN JSON键集合无差异。Debug HAP、UnitTestBuild和 ohosTest HAP 均构建通过MateBook Pro 2in1 模拟器 ohosTest 为7/7。最终交付 HAP 在布局修复后重新构建entry-default-unsigned.hap大小为 1,520,352 字节SHA-256 为367ab8650479aa1fa8fe73bd1ebadd9a53f46659c850c2e388fc799d5cb88e5bohosTest HAP 为 2,360,824 字节SHA-256 为b7230037b51044fe16168d2c835fb891e1c70f675941a1046165bc895217592c。两者都是未签名测试产物不等同于商店发布包。异常路径与恢复原则语言设置需要考虑五类异常。第一HostContext 不可用点击直接返回不发起不完整切换。第二平台语言 API 抛错保留当前语言并在当前可读语言中显示失败状态。第三Preferences 读取失败策略按钮回退跟随系统不阻塞文档可靠性初始化。第四Preferences 写入失败当前会话仍可使用新语言但提示无法保存。第五ArkWeb 尚未 ReadysetEditorLanguage的调用可能暂时无效不过onEditorReady会再次以当前language同步。Web 词典缺少某条命令翻译时代码只在存在messages.commands[command.id]时覆盖原始英文仍可作为降级不会把标题设为空。三方差异没有打开时不做重绘避免无意义工作。底层文件系统错误维持原始详情防止翻译造成诊断信息丢失。这些策略共同遵守一条原则语言是表现设置绝不能损坏用户文档。即使本地化链路完全失败文件打开、编辑、保存和恢复仍应继续工作。当前实现没有增加网络权限、翻译 SDK或第三方动态代码原有离线安全边界保持不变。性能与内存影响ArkUI 资源切换由平台管理Web 侧更新固定数量的 DOM 属性、命令对象和一个 CodeMirror 扩展不遍历 Markdown 全文。命令面板重绘的候选数量是固定命令集三方差异只在其当前可见时重绘。常态切换复杂度与界面文案数量相关而非文档字符数。语言词典作为静态对象打入离线单 HTML不发网络请求。新增中文文本会增加少量 HAP 体积但不形成长期缓存。切换没有创建第二个 EditorView不会复制文档缓冲区或撤销历史。对于大文档模式语言切换仍只更新 UI避免把预览重新渲染成本混入设置动作。若未来支持更多语言词典和资源体积会线性增加。届时可以评估按构建资源机制管理 Web 文案但在只有中英文时引入动态加载会增加失败点并破坏离线单文件交付收益不足。PC 交互与无障碍验收鸿蒙 PC 用户既可能用鼠标也可能通过键盘和辅助技术操作。语言控件使用标准 Button 和 selected 状态点击命中区域稳定切换后焦点仍在原按钮附近不会因为页面重建丢到窗口起点。设置侧栏支持拖动220 vp 最小宽度下三个按钮仍能读取和选择。辅助功能验收不只看是否存在文本还看角色与状态。分段选项的selected能让测试和屏幕阅读器区分当前值。动态状态栏使用切换后的语言给出成功或失败反馈。Web 的documentElement.lang、工作区、源码编辑器、预览、命令面板和差异区aria-label同步更新避免视觉是中文而朗读仍是英文。键盘快捷键不会因语言改变。命令标识如view.split保持稳定只改变标题、分类和搜索关键词因此原生命令路由和自动化定位不依赖翻译文本。国际化应改变可见表达不应改变协议、文件格式或快捷键语义。已知边界与后续演进当前正式语言只有简体中文和英文。繁体中文不能仅复制简体资源术语、地区习惯和测试设备都需要独立基线。系统控制面板修改语言后的完整真机生命周期仍需在鸿蒙 PC 真机复核模拟器已经证明应用内切换、强制停止和重启持久化但不能替代全部硬件环境。动态文件名、路径、底层异常和部分复杂状态仍保留原始语言这是有意的诊断边界不是宣称百分之百翻译。后续应把稳定状态逐步结构化为错误码与参数再由资源层格式化而不是继续扩大英文字符串比较。资源键可以进入自动 CI 校验防止新功能只补一套语言。语言选择暂不跨设备同步。若将来做账户配置需要区分default与具体语言并尊重每台设备的系统语言。隐式把 A 设备解析出的中文同步到 B 设备会破坏“跟随系统”的本意。结论OhMarkdown 的语言功能不是一组翻译文本而是一条从用户策略到平台资源、从 Ability 生命周期到 ArkUI 状态、再到 ArkWeb 运行时的完整闭环。ed13ee0提供核心实现后续布局提交在可调侧栏和窄窗口中继续复核。最终证据表明三项选择互斥、即时生效、跨重启保持编辑状态不丢失自动化30/30、设备 ohosTest7/7均通过。更重要的是这项能力没有改变文档安全模型不联网、不重载编辑器、不把任意语言字符串暴露给 Bridge也不让设置失败阻止编辑。对于优先适配鸿蒙 PC 的 Markdown 工具这种可预测、可恢复、可验证的基础体验比简单把按钮翻成中文更接近可长期扩展的产品能力。