Skip to content

feat(settings): 插件设置支持包内翻译 l10n/<语言>.json - #36

Merged
xiaoyumuxi merged 1 commit into
mainfrom
feat/plugin-settings-l10n
Nov 14, 2026
Merged

xiaoyumuxi merged 1 commit into
mainfrom
feat/plugin-settings-l10n

Conversation

@xiaoyumuxi

Copy link
Copy Markdown
Contributor

关联

  • 用户反馈:插件设置页中英文混杂(例如 Go 插件的设置在中文界面下仍显示英文标题、说明和选项名),而清单 plugin.json 只能写一种语言。这个 PR 让插件包自带翻译文件 l10n/<语言>.json,Core 按界面语言挑选,缺哪条就回退到清单原文。
  • 先覆盖设置(API-37);其他插件文案以后按同一格式在顶层加新成员。属于 pluginAPIVersion 0 内的补充,旧宿主不读 l10n/,照常显示清单原文,不需要升能力版本。
  • 合入后的跟进:
    • 桌面端另开 PR:pluginsettings.Store.Load 传界面语言、从已安装插件目录读取候选文件;确认弹窗改用状态里翻译过的动作标题;记录被拒绝的翻译文件;把 scripts/desktop/build-desktop-core.sh 的 PINNED_CORE_REVISION 升到合并提交。
    • 桌面端支持后,回到本仓库更新 reference/domains/settings.md 里“桌面端尚未读取翻译”的那一行。
    • Go 插件(lithe-plugin-go)补 l10n/zh-Hans.json,覆盖全部设置、选项和分组标题,宿主支持后发预发布。

这个 PR 做什么

1. 翻译文件格式(shared/plugins/contract/v1/plugin-l10n.schema.json)

顶层成员 键 可翻译的内容
settings 设置 ID title、description、placeholder、unit、options(enum / multiSelect 的选项标题,按选项值)
sections 清单里写的分组标题原文 显示的分组标题
settingActions 动作 ID title、description
  • 每一项都是可选的,没写的保持清单原文。
  • 顶层不认识的成员(包括 $schema)一律忽略,这样以后给其它文案加翻译的文件在旧宿主上仍然可用;内层对象拒绝未知字段。
  • 引用必须存在于清单:设置 ID、选项值、分组标题、动作 ID;只有清单里有的字段才能翻译(清单没写 description 的设置不能只在翻译里加说明)。
  • 标签非空白且不超过 120 个字符,说明非空白且不超过 480 个字符;不接受 null、重复键;整个文件必须是 JSON 对象且不超过 1 MiB。
  • 翻译只改显示文字:分组和顺序仍按清单原文的分组标题,declaration.section 保留原文,ID、值和选项顺序都不变。

2. plugins.declarative.settingsState 的请求和返回

Core 不做 I/O,所以读取分两步,桌面端 Store.Load 本来就调两次,不增加往返:

  1. 宿主传 language(界面语言的 BCP 47 标签)、不传 localizations。返回的 localization 里 readRequired: true,并列出 candidates:标签逐段截短,例如 zh-Hans-CN 得到 l10n/zh-Hans-CN.json、l10n/zh-Hans.json、l10n/zh.json。
  2. 宿主从已安装的插件包里读存在的候选文件,再调一次,带上 localizations: { 路径: 文件文本 }。

返回的 localization 是 { language, candidates, maximumBytes, readRequired, applied, rejected }:

  • Core 检查每个传入的文件,按候选顺序应用第一个合格的;排在它后面的合格文件不使用,也不报告。
  • 每个不合格的文件进入 rejected:{ path, error: { code, message, field? } },错误码统一为新增的 plugin.settings.invalidLocalization(一码一义);field 指向第一个问题,整个文件的问题(不是 JSON 对象、重复键、null、未知字段、超长)不带 field。被拒绝的文件不会部分生效。
  • rows、actions、sections 和动态语言页、构建系统页(pages)的标题都按应用的翻译显示。plugins.settings.catalog 的分组标题保持原文。

宿主请求本身有误时返回 invalid_request,details 说明原因:language 不是语言标签、传了 localizations 却没传 language、localizations 里的路径不在候选列表里。

3. 代码

  • plugin-model 新增 settings::localization 模块:LocalizationRequest(校验请求、列出候选)、select(逐个检查并挑出应用的翻译、生成报告)、SettingsLocalization(给行、动作、分组标题取翻译文字)。
  • document_state 和 page_sections 接受可选的翻译;plugin-manifest 的 settings_state_command 解析新字段并把报告写进状态的 localization。settings_surfaces 生成目录时传 None。
  • is_language_tag 从 plugin-marketplace 移到 plugin-model::names 并公开,市场的语言标签校验改为复用它,行为不变。

4. 文档

  • 新增 plugin-l10n.schema.json;契约 README 新增“Translations”一节,并更新顶部文件表、fixture 表、包布局、title / description 一行(“用英文写,包内可以翻译”)和错误码表。
  • shared/plugins/README.md 中英两张 schema 表、$schema 说明、VS Code **/l10n/*.json 关联示例。
  • rust/contracts/rust-core-api.md:settingsState 的请求字段、localization 返回、分组排序、invalid_request 的几种情况、settingsActionPlan 的 title 是清单原文(确认弹窗应使用状态里的动作标题)、目录不翻译、命令表一行。
  • 参考页:API 总表 API-37 中英两行、设置领域页中英两版(翻译一节、契约行、示例、桌面端状态)、功能索引一行。
  • 协议更新记录 2026-11-14 加一行(API-37,pluginAPIVersion 0 内的补充)。settingsState 是宿主调用 Core 的命令,不是插件可调用的宿主方法,不涉及宿主方法索引。
  • 新增 fixture:fixtures/settings/l10n/zh-Hans.json(settings.plugin.json 的翻译)和 localization-cases.json(13 个用例,宿主和 Core 共用)。

测试

测试 覆盖
settings::localization::tests::candidates_drop_one_subtag_at_a_time 候选路径逐段截短
settings::localization::tests::translates_only_declared_text_and_keeps_the_rest 只替换翻译写了的字段,其余保留原文
settings::localization::tests::the_most_specific_valid_file_wins_and_invalid_files_are_reported 按候选顺序应用第一个合格文件,不合格的列入 rejected
settings::localization::tests::rejects_files_that_do_more_than_translate 未知 ID、选项值、分组、动作,清单没有的字段,空白和超长文字
settings::localization::tests::rejects_duplicate_keys_and_oversized_files 重复键、超过 1 MiB
settings::localization::tests::reports_what_to_read_and_rejects_malformed_requests readRequired 报告;非法语言标签、缺 language、非候选路径
tests::plugin_settings_documents::settings_state_names_translation_candidates_before_they_are_read 经分发器的第一步:完整的 localization 报告
tests::plugin_settings_documents::settings_state_shows_packaged_translation_and_keeps_manifest_identity 用 fixture 的 zh-Hans.json:标题、选项、分组、动作标题翻译;declaration.section 保留原文;中英两种语言下每个分组里的行和动作完全一致;language:elixir 页的分组标题也翻译
tests::plugin_settings_documents::settings_state_localization_fixture_cases 跑 localization-cases.json 的全部 13 个用例
tests::plugin_settings_documents::settings_state_refuses_malformed_localization_requests 三种错误请求返回 invalid_request 和对应的 details

验证

命令(在 rust/) 结果
cargo test --workspace --no-fail-fast 1977 个通过,0 失败
cargo clippy --workspace --all-targets -- -D warnings 无告警
cargo fmt --all -- --check 通过
scripts/rust-core/verify-rust-core-comments.sh(仓库根) 通过
node shared/plugins/scripts/verify-docs.mjs(仓库根) 通过
node --test shared/plugins/scripts/*.test.mjs(仓库根) 2 个通过

没有改插件后端协议,没有跑 sdk-e2e.mjs。只在 macOS 上运行。

插件包可以带 l10n/<语言>.json,按设置 ID、选项值、分组原文和按钮 ID
翻译设置页的标题、说明、占位符、单位、选项标题、分组标题和按钮文字。
ID、值、分组和顺序不变,文件没写到的文字显示 plugin.json 原文。

- plugins.declarative.settingsState 新增可选 language、localizations,
  返回 localization:候选文件(语言标签逐级截短)、是否需要读取、
  选中的文件和被拒绝的文件。Core 不做 I/O,宿主按候选读包内文件后再调一次。
- 有问题的文件整份不用,报 plugin.settings.invalidLocalization 并带字段;
  宿主请求格式错误报 invalid_request。
- 新增 plugin-l10n.schema.json、fixtures/settings/l10n/zh-Hans.json
  和 localization-cases.json(13 个用例),契约、Core API、API 总表
  API-37、设置功能页和更新记录同步更新。
- is_language_tag 从 marketplace 移到 plugin-model 共用。
@xiaoyumuxi
xiaoyumuxi marked this pull request as ready for review November 14, 2026 06:22
@xiaoyumuxi
xiaoyumuxi merged commit 3cff440 into main Nov 14, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant