Skip to content

feat(desktop): 插件设置页按界面语言显示插件包自带的翻译 - #1911

Merged
xiaoyumuxi merged 2 commits into
previewfrom
feat/plugin-settings-l10n
Nov 14, 2026
Merged

xiaoyumuxi merged 2 commits into
previewfrom
feat/plugin-settings-l10n

Conversation

@xiaoyumuxi

Copy link
Copy Markdown
Collaborator

关联 Issue / Linked issue

PR 类型 / Type

  • feat:新功能或用户可见的改进
  • fix:缺陷修复
  • docs:文档、Agent Note、Skill 等规则文件
  • release:稳定版发布准备

改动说明 / What changes

背景 / Why

插件设置页上的分组名、设置标题、说明、选项名和按钮名都直接来自 plugin.json,只有插件作者写的那一种语言,所以界面切到中文后插件设置仍是英文。宿主的 shared/i18n 只翻译宿主自己的文字,不能替每个插件维护词条(那是按插件内容的特判,第三方插件也拿不到)。

Core #36 让插件可以在包里自带 l10n/<语言>.json。本 PR 让桌面端按界面语言读取这些文件,并把译文显示出来。

改动 / Changes

  • 两步读取(pluginsettings):Store.LoadPresented 先只带界面语言调用 settingsState,拿到候选文件(zh-Hans 依次是 l10n/zh-Hans.json、l10n/zh.json)和单文件上限(1 MiB)。然后用 configfile.Capture 从已安装插件目录只读地取这些文件,把内容交回 Core,由 Core 校验并逐条回退到清单原文。
    • 不存在的候选直接跳过,不算错误。存在但读不了的(例如是目录),记进 Snapshot.LocalizationReadFailures() 后继续读下一个候选。
    • 保存后的回读带着同一个语言请求,保存之后页面仍显示译文。
    • 原来的 Load 不变,供后端启动、运行等不需要显示文字的读取方使用。
  • core.ReadPluginSettingsState:新增可选的 localization 请求参数,以及响应里的 state.localization(applied、rejected、candidates、maximumBytes、readRequired)。Kernel 接口同步。
  • pluginservice.LoadSettingsEditor:增加 language 参数。包根使用取清单时的那个已安装插件目录,所以一定是绝对路径,也不会读到别的插件。
  • 设置窗口(settingswindow):
    • 设置页按设置窗口的界面语言加载。界面语言变化后,下一次绘制时用新语言重读。
    • 有未保存草稿时不重读,保留当前文字和草稿。草稿保存或清掉后,再按新语言重读。
    • 按钮的确认弹窗使用页面上译后的按钮标题。动作计划里的 Title 是清单原文,原来会在中文按钮下弹出英文确认框。
    • 分组标题继续用 state.Sections[].Title,它现在是译后的文字。
  • 日志(workbench):页面仍能加载,所以翻译问题记为“部分拒绝”,而不是失败。
    • 用的是已有事件 app.notification.operation_rejected(WARN),operation=plugin_settings_load。新增两个 reason:localization_unreadable(有候选文件读不了)、invalid_localization(Core 拒绝了文件或其中的条目)。
    • 原有的 invalid_stored_values 改用同一个辅助函数生成字段。
    • 不写路径和插件文字。
  • 测试:
    • pluginsettings/localization_test.go:逐个比对契约 localization-cases.json 的 13 个用例,覆盖保存回读、读不了的候选。
    • pluginservice 安装包测试:读取前后包内文件清单和字节不变。
    • settingswindow/plugin_settings_language_test.go:真实 Core 下切换语言、草稿保护、确认标题。
    • workbench:两种 WARN;TestLogEventsMatchCatalog 增加三个 reason 和 plugin_settings_load 失败。
    • 所有调用方和测试替身随新签名更新。
  • 文档:
    • Agent Note 2026-10-21-plugin-settings-surfaces.md 新增决策第 9 节(翻译来源、两步读取、只读边界、草稿规则、确认标题、日志、被否方案)。
    • 2026-10-14-declarative-plugin-settings.md 里“官方插件靠宿主 Localizable.strings 翻译”的过时说法已改正。
    • API 实施计划 API-37 两行更新。
    • 功能矩阵 declarative-plugin-settings 补能力、证据和验证说明(状态仍为 partial/pending)。
  • 附带修复:i18n 生成器改用临时文件调用 gofmt,并设超时(scripts/i18n/generate-i18n.mjs,单独一个提交):
    • 现象:本机验证本 PR 时,node --test scripts/i18n/test-i18n.mjs 两次整套挂起,--test-timeout 也没能终止。进程状态显示 gofmt 一直阻塞在读 stdin。
    • 原因:生成器用 spawnSync("gofmt", [], { input }) 把约 450 KB 的桌面词条源码从 stdin 喂给 gofmt。脱离仓库代码单独复现:本机(Node 24.14.0、Go 1.27.4 的 gofmt)每次喂 450 KB,800 次里有 2 次 gofmt 收不到输入结束、一直等待;把 gofmt 换成 cat 的 400 次全部正常;改成文件参数的 600 次全部正常。另外,spawnSync 会阻塞事件循环,测试框架的超时没有机会触发,违反了 write-stable-tests 的“每个等待都有本地截止时间”。
    • 改法:把源码写进系统临时目录下的私有目录,以文件参数异步调用 gofmt,超时 10 秒,输出上限 64 MiB,结束后删除临时目录。临时文件不在仓库里,--check 仍不改仓库文件;生成结果与原来逐字节相同(--check 通过)。

本 PR 不改 PINNED_CORE_REVISION:preview 上已固定的 2c1fed1(Core #37,#1903 升级)已包含 #36。

不在本 PR 范围内 / Out of scope

  • Go 插件的 l10n/zh-Hans.json(goExecutable、staticcheck、gofumpt、hoverKind 选项等),在 lithe-plugin-go 另提并发预发布。
  • Core shared/plugins/reference/domains/settings.md 里桌面端的支持状态,Core 另提小 PR 更新。
  • 命令、工具窗口等其它插件文字的语言包,以及界面语言变化的插件事件(API-37 其余部分)。
  • 目标平台的整机界面验收(矩阵保持 pending)。

类型专属:feat

多平台对齐

  • 两端都具备:Viva 桌面端是 macOS 与 Windows 共用的同一套 Go 代码,合入后两端都具备该能力(功能矩阵两列证据相同)
  • 只实现了 macOS:已为 Windows 提交跟进 Issue:#
  • 只实现了 Windows:已为 macOS 提交跟进 Issue:#
  • 刻意只属于某个平台(功能矩阵中另一端记为 platform-specific),理由:

范围检查 / Scope checks

功能矩阵 / Feature matrix

  • 已更新 shared/platform-feature-matrix/features/declarative-plugin-settings.json:两端证据、验证动作、notes 都已补充;没有整机验收,状态保持 partial / pending
  • 已运行 ./scripts/platform-feature-matrix/verify-platform-feature-matrix.sh 和 ./scripts/platform-feature-matrix/verify-platform-feature-matrix-change.sh origin/preview HEAD
  • 没有提交 .artifacts/ 下生成的矩阵视图
  • [N/A] 没有更新矩阵的理由:已更新

统一日志系统 / Structured logging

  • 已接入统一日志系统:使用 Core 目录里已声明的 app.notification.operation_rejected,只新增 Token 类型的 reason 值,不新增事件,也没有手写事件 ID
  • 一个操作只有一条终态:整个读取失败时走既有 operation_failed;能加载但有部分拒绝时,每种问题最多一条 WARN;只在用户打开设置页时触发,不在高频路径
  • 没有记录路径、插件文字或文件内容,只记 operation 和 reason 两个 Token
  • [N/A] scripts/logging/generate-log-events.mjs、verify-log-usage.mjs 已不在本仓库(见 logging 参考页),事件目录在 Core,本 PR 不改目录;改由 TestLogEventsMatchCatalog 经真实 Core 开启目录校验来编码这些事件

界面文案 / i18n

  • [N/A] 没有新增宿主文案;显示的是插件包自带的译文,属于插件内容,不进 shared/i18n(见 Note 决策第 9 节)
  • 已运行 node scripts/i18n/verify-app-language-localization.mjs 和 node scripts/i18n/generate-i18n.mjs --check,没有放宽脚本或扩大豁免名单

逻辑归属 / Logic placement

  • 候选顺序、上限、校验和逐条回退都在 Rust Core(适配 macOS 13 并修复应用图标尺寸过大问题 #36,契约 fixture localization-cases.json 先于平台接入);桌面端只负责原生只读 I/O 和呈现
  • [N/A] 没有修改平台层已有的可共享逻辑
  • 宿主和 Core 都没有按插件或工具名加分支,译文由插件包提供

性能与内存 / Performance and memory

  • 只在打开插件设置页时读取,每次最多读 2 个小文件,单文件上限 1 MiB;读取在设置工作线程上进行;没有新增缓存、轮询或子进程
  • 主线程没有文件读写:语言变化只在绘制时比较语言值,重读交给既有的设置工作线程
  • [N/A] 不涉及启动、打开项目、输入、滚动、大文件、长输出或搜索

设置 / Settings

  • [N/A] 没有新增设置项,不涉及 ⓘ 或“自动(实际生效值)”
  • 设置值的读取、回显、确认后写回和回读流程不变;打开页面不写文件,测试确认插件包内文件清单和字节不变
  • [N/A] 没有新增或移动设置页

插件契约 / Plugin contract

原计划编号 方法 用途
— 无 无

发行包与资源 / Bundle and resources

  • 运行时不写 app bundle、安装目录或插件包;只经 os.Root 限定只读地取已安装包内的固定文件,没有新增可写路径;已运行 ./scripts/desktop/verify/verify-runtime-bundle-immutability.sh
  • [N/A] 没有新增或修改下载、解压、生成或缓存资源

无用代码与重构 / Dead code and refactoring

  • 本次没有让任何代码变成无用代码;无语言的 Store.Load 仍有后端启动、运行等 4 个生产调用方
  • [N/A] 没有发现需要另建单的既有无用代码;发现的 gofmt 挂起已在本 PR 修复,没有另建单

验证 / Verification

命令或操作 平台 结果
./scripts/desktop/desktop-go.sh vet ./... macOS 通过
./scripts/desktop/desktop-go.sh test ./...(rebase 到 7c036b3 后) macOS 55 个包全部 ok,无 FAIL
./scripts/desktop/desktop-go.sh test -race 覆盖 core、pluginsettings、pluginservice、settingswindow、workbench(rebase 到 7c036b3 后) macOS 5 个包全部 ok,没有数据竞争(workbench 194 秒)
test-stability-macos.sh:新增和改动的 l10n 测试(20 项,含 13 个契约用例) macOS 全部通过,最长 270 ms,低于 1 s 警告线
./.agents/skills/write-stable-tests/scripts/verify-test-stability.sh --base origin/preview macOS 通过
测试变异(rebase 前):去掉语言跟随、去掉草稿保护、去掉确认标题替换 macOS 对应测试各自失败,恢复后通过
./scripts/agent-notes/verify-agent-notes.sh — 222 篇通过
node scripts/i18n/verify-app-language-localization.mjs、generate-i18n.mjs --check — 通过,中英 11125/11125
node --test --test-timeout=60000 scripts/i18n/test-i18n.mjs(每轮另加 240 秒外部上限) macOS 修复前:两次整套都卡在 gofmt 读 stdin。修复后:连跑 8 轮,每轮 41/41 通过,用时 8–24 秒,没有残留进程
单独复现 gofmt 挂起(仓库外的小脚本,每次 450 KB) macOS stdin 方式 800 次卡住 2 次(都等到 10 秒超时);cat 400 次和文件参数 600 次全部正常
verify-platform-feature-matrix.sh、verify-platform-feature-matrix-change.sh origin/preview HEAD — 通过(224 项)
./scripts/desktop/verify/verify-runtime-bundle-immutability.sh macOS + Windows 源码 通过

未能执行的检查和原因:

  • Windows 实机未运行:本机是 macOS。代码与 macOS 共用,Windows 列的验证状态保持 pending。
  • 没有做整机界面验收和截图:当前没有任何已发布插件带 l10n/ 文件(Go 插件的中文文件见“不在本 PR 范围内”)。界面行为由无头驱动在真实 Core 下验证。

提交前确认 / Final checks

  • 分支从最新的 preview 创建(已 rebase 到 7c036b3,含 fix(desktop): 保留虚拟源码语言和 URI,取消过期类导航 #1906);目标分支 preview
  • 以 Draft 提交,本地验证通过后才标记 ready
  • 除上面单独说明的 i18n 生成器修复(本 PR 的 i18n 验证依赖它)外,没有无关改动;没有提交 .artifacts/ 等产物,没有凭据和个人路径
  • 本次只运行无头 Go 测试,没有启动 Lithe 应用;没有本次产生的进程残留

插件可以在包里放 l10n/<语言>.json(Lithe-core-rust#36,API-37)。设置页
现在按界面语言读取:Core 先给出候选文件和上限,宿主经 configfile 从已安装
插件目录只读地取文件,再交给 Core 校验并逐条回退到清单原文。

- pluginsettings:Store.LoadPresented 两步读取,保存后的回读保留译文;
  读不了的候选记进 Snapshot.LocalizationReadFailures 并继续下一个
- pluginservice:LoadSettingsEditor 带界面语言,包根即取清单的已安装目录
- settingswindow:界面语言变化后重读文字,有未保存草稿时不重读;
  按钮确认弹窗使用译后的按钮标题
- workbench:读不了或被 Core 拒绝的语言文件各记一条
  app.notification.operation_rejected(localization_unreadable、
  invalid_localization)
- Agent Note、API 实施计划 API-37 和功能矩阵同步更新
生成器原来用 spawnSync 把约 450 KB 的桌面词条源码从 stdin 喂给 gofmt。
在 macOS(Node 24.14.0、Go 1.27.4)上,gofmt 偶尔收不到输入结束,会
一直等待:800 次里卡住 2 次,换成 cat 或改用文件参数则不会出现。
spawnSync 还会阻塞事件循环,test-i18n.mjs 的超时也无法终止它。

现在把源码写进系统临时目录下的私有目录,以文件参数异步调用 gofmt,
超时 10 秒,结束后删除该目录。临时文件不在仓库里,--check 仍不改
仓库文件,生成结果逐字节不变。
@ghfind-review ghfind-review Bot added the review: high ghfind author score; see https://ghfind.com label Nov 14, 2026
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review November 14, 2026 09:28
@xiaoyumuxi
xiaoyumuxi merged commit 6196a39 into preview Nov 14, 2026
9 of 13 checks passed
1lck added a commit that referenced this pull request Nov 14, 2026
修复终端菜单无头测试漏触发重绘:先消费输出帧,再通过真实侧栏按钮打开终端;保留全选、复制、关闭及进程清理断言。同步测试计时笔记和 terminal-interaction 的真实验证证据,原生验收状态保持 pending。

Closes #1907
Refs #1908(已在 #1911 修复)

本地回归、竞态及桌面包检查已完成;重放到最新 preview 后终端用例再次连续 5 次通过。按维护者明确要求跳过 CI 并 squash 合并。

[skip ci]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: high ghfind author score; see https://ghfind.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant