fix(server): leave plugin listing translations to the registry

The daemon rejected manifests whose translations the registry would refuse,
so a plugin main installs could fail to load here. Paseo never reads
translations; the registry validates them at publish. Docs now say which
checks are automated at publish and which are human review.
This commit is contained in:
Mohamed Boudra
2026-10-20 08:39:50 +02:00
parent 778b0ce773
commit 7f6a0651e8
6 changed files with 48 additions and 76 deletions
@@ -68,11 +68,6 @@ describe("plugin manifest", () => {
{ id: "INVALID" },
{ description: 42 },
{ requirements: { paseoo: ">=0.11.0" } },
{ translations: { "zh-cn": { name: "示例" } } },
{ translations: { en: { name: "Example" } } },
{ translations: { "not a tag": { name: "Example" } } },
{ translations: { "zh-CN": { name: " " } } },
{ translations: { "zh-CN": { desc: "示例" } } },
])("rejects invalid known fields: %j", async (fields) => {
const directory = await mkdtemp(path.join(tmpdir(), "paseo-plugin-manifest-"));
directories.push(directory);
@@ -133,17 +128,20 @@ describe("plugin manifest", () => {
await expect(readPluginManifest(directory)).rejects.toThrow();
});
it("reads listing translations with each field optional", async () => {
// Listing translations are read by the registry when it publishes, never by the daemon, so a
// value the registry would refuse must not stop a plugin from installing.
it.each([
{ "zh-cn": { name: "示例" } },
{ "zh-CN": { desc: "示例" } },
"zh-CN",
])("ignores listing translations: %j", async (translations) => {
const directory = await mkdtemp(path.join(tmpdir(), "paseo-plugin-manifest-"));
directories.push(directory);
const manifest = {
id: "described",
name: "Reviewer",
description: "Reviews changes before merge",
translations: { "zh-CN": { description: "合并前审查改动" } },
};
await writeFile(path.join(directory, "paseo-plugin.json"), JSON.stringify(manifest));
await expect(readPluginManifest(directory)).resolves.toEqual(manifest);
await writeFile(
path.join(directory, "paseo-plugin.json"),
JSON.stringify({ id: "described", translations }),
);
await expect(readPluginManifest(directory)).resolves.toEqual({ id: "described" });
});
it("accepts only non-empty argv arrays for build commands", async () => {
@@ -22,27 +22,10 @@ const PluginAssetPathSchema = z
value.split("/").at(-1) !== ".",
"Expected a relative file path inside the plugin package",
);
// Keys are canonical language tags such as `zh-CN`, so a miscased `zh-cn` fails instead of never
// matching a reader's language. A misspelled field would silently show English, so entries are strict.
const PluginListingTranslationsSchema = z.record(
z
.string()
.refine(
(tag) => tag !== "en" && isCanonicalLanguageTag(tag),
"Expected a canonical language tag other than en, such as zh-CN",
),
z
.object({
name: z.string().trim().min(1).optional(),
description: z.string().trim().min(1).optional(),
})
.strict(),
);
const PluginManifestSchema = z.object({
id: PluginIdSchema,
name: z.string().trim().min(1).optional(),
description: z.string().trim().min(1).optional(),
translations: PluginListingTranslationsSchema.optional(),
icon: PluginAssetPathSchema.refine(
(value) => /\.png$/i.test(value),
"Expected a relative path to a PNG",
@@ -53,14 +36,6 @@ const PluginManifestSchema = z.object({
build: z.array(PluginBuildCommandSchema).min(1).optional(),
});
function isCanonicalLanguageTag(tag: string): boolean {
try {
return Intl.getCanonicalLocales(tag)[0] === tag;
} catch {
return false;
}
}
export type PluginManifest = z.infer<typeof PluginManifestSchema>;
export async function readPluginManifest(directory: string): Promise<PluginManifest> {
+13 -8
View File
@@ -51,8 +51,8 @@ Also supply a one-sentence description: use `package.json`'s `description` for n
### Other languages
The website shows listings in English and Simplified Chinese. English is required; translations are
optional. Add translated listing text to `paseo-plugin.json`, keyed by language tag:
Listings show in English and Simplified Chinese. English is required and translations are optional.
Add a translated `name` and `description` to `paseo-plugin.json`, keyed by language tag:
```json
{
@@ -65,13 +65,18 @@ optional. Add translated listing text to `paseo-plugin.json`, keyed by language
}
```
Each field is optional. A missing translated `name` or `description` shows the English one.
Each field is optional, and a missing one shows the English text. The registry checks
`translations` when it publishes the plugin: keys are canonical language tags other than `en`, and
each value sets only `name` or `description`, as non-empty strings. Paseo itself does not read
`translations`, so a mistake there fails publication, not installation.
To translate the overview, put a whole copy beside `OVERVIEW.md` named for the language, such as
`OVERVIEW.zh-CN.md`, and include it in the published package. The scaffold's `files` list already
includes `OVERVIEW.*.md`. Readers in that language see the translated overview; without one, they
see the English overview with a notice. Keep the translation in step with `OVERVIEW.md`; the overview rules
above apply to it too.
To translate the overview, publish a complete copy named for the language next to `OVERVIEW.md`,
such as `OVERVIEW.zh-CN.md`. The scaffold's `files` list already includes `OVERVIEW.*.md`. The
registry reads every `OVERVIEW.<tag>.md` as a translation and fails publication on a tag that is not
a canonical language tag, so do not name other files that way. Readers in that language see the
translation; others see the English overview with a notice. The overview rules above apply to
translations, so update them when `OVERVIEW.md` changes. Reviewers can add or correct translations
in the registry.
## Icons and screenshots
+3 -3
View File
@@ -35,7 +35,7 @@ nav: 发布
### 其他语言 {#other-languages}
网站以英文和简体中文显示展示页。英文是必需的;翻译是可选的。在 `paseo-plugin.json` 中添加翻译后的展示文本,以语言标签为键:
展示页以英文和简体中文显示。英文是必需的,翻译是可选的。在 `paseo-plugin.json` 中添加翻译后的 `name` 和 `description`,以语言标签为键:
```json
{
@@ -48,9 +48,9 @@ nav: 发布
}
```
每个字段都是可选的。缺少翻译后的 `name` 或 `description` 时,会显示英文的值。
每个字段都是可选的,缺少的字段会显示英文。注册表在发布插件时检查 `translations`:键必须是除 `en` 之外的标准语言标签,每个值只能设置 `name` 或 `description`,且必须是非空字符串。Paseo 本身不读取 `translations`,因此这里的错误会导致发布失败,而不会影响安装。
要翻译概览,请在 `OVERVIEW.md` 旁边放一份以语言命名的完整副本,例如 `OVERVIEW.zh-CN.md`,并把它包含在发布的包中。脚手架的 `files` 列表已经包含 `OVERVIEW.*.md`。使用该语言的读者会看到翻译后的概览;没有翻译时,他们会看到英文概览和一条提示。请让翻译与 `OVERVIEW.md` 保持同步;上面的概览规则同样适用于它。
要翻译概览,请在 `OVERVIEW.md` 旁边发布一份以语言命名的完整副本,例如 `OVERVIEW.zh-CN.md`。脚手架的 `files` 列表已经包含 `OVERVIEW.*.md`。注册表会把每个 `OVERVIEW.<标签>.md` 都当作翻译,标签不是标准语言标签时发布会失败,因此不要用这种方式命名其他文件。使用该语言的读者会看到翻译;其他读者会看到英文概览和一条提示。上面的概览规则同样适用于翻译,因此 `OVERVIEW.md` 变更时请同步更新翻译。审查者可以在注册表中补充或修正翻译。
## 图标和截图 {#icons-and-screenshots}
+10 -14
View File
@@ -52,16 +52,16 @@ The required root manifest is `paseo-plugin.json`:
}
```
| Field | Required | Behavior |
| -------------- | -------- | ----------------------------------------------------------------------------------------- |
| `id` | Yes | Default installation ID. |
| `name` | No | Non-empty display name for the registry and website; independent of the installation ID. |
| `icon` | No | Relative path to a PNG inside the plugin package. |
| `media` | No | Array of image or video paths inside the package, or HTTPS URLs. An empty array is valid. |
| `description` | No | Non-empty summary shown below the plugin ID in **Settings → Plugins**. |
| `translations` | No | Listing `name` and `description` in other languages, keyed by language tag. |
| `requirements` | No | Supported Paseo versions, described below. |
| `build` | No | Preparation commands, described in the CLI reference. |
| Field | Required | Behavior |
| -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | Yes | Default installation ID. |
| `name` | No | Non-empty display name for the registry and website; independent of the installation ID. |
| `icon` | No | Relative path to a PNG inside the plugin package. |
| `media` | No | Array of image or video paths inside the package, or HTTPS URLs. An empty array is valid. |
| `description` | No | Non-empty summary shown below the plugin ID in **Settings → Plugins**. |
| `translations` | No | Listing text in other languages, read by the registry and ignored by Paseo. See [other languages](/docs/plugins/publishing#other-languages). |
| `requirements` | No | Supported Paseo versions, described below. |
| `build` | No | Preparation commands, described in the CLI reference. |
Paths are relative to `paseo-plugin.json`, use forward slashes, and cannot contain `..`
segments. Registry listings for visual plugins require an interface image even though `media` is
@@ -74,10 +74,6 @@ Unknown top-level fields are ignored. Known fields still validate, and unknown k
Manifests using `name`, `icon`, or `media` require Paseo 0.11.0 or later; older daemons reject
these fields during installation.
`translations` keys are canonical language tags other than `en`, such as `zh-CN`. Each value may
set `name`, `description`, or both, as non-empty strings; other keys are rejected. See
[other languages](/docs/plugins/publishing#other-languages) for how listings use them.
### Requirements
`requirements` is an optional object. Its currently supported key, `paseo`, accepts an npm semver
+10 -12
View File
@@ -50,23 +50,21 @@ my-plugin/
}
```
| 字段 | 是否必填 | 行为 |
| -------------- | -------- | -------------------------------------------------------- |
| `id` | 是 | 默认的安装 ID。 |
| `name` | 否 | 注册表和网站使用的非空显示名称;与安装 ID 无关。 |
| `icon` | 否 | 插件包内 PNG 文件的相对路径。 |
| `media` | 否 | 包内图片或视频路径的数组,或 HTTPS URL。空数组是有效的。 |
| `description` | 否 | 在 **设置 → 插件** 中显示在插件 ID 下方的非空摘要。 |
| `translations` | 否 | 其他语言的展示 `name` 和 `description`,以语言标签为键。 |
| `requirements` | 否 | 支持的 Paseo 版本,见下文。 |
| `build` | 否 | 准备命令,见 CLI 参考。 |
| 字段 | 是否必填 | 行为 |
| -------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `id` | 是 | 默认的安装 ID。 |
| `name` | 否 | 注册表和网站使用的非空显示名称;与安装 ID 无关。 |
| `icon` | 否 | 插件包内 PNG 文件的相对路径。 |
| `media` | 否 | 包内图片或视频路径的数组,或 HTTPS URL。空数组是有效的。 |
| `description` | 否 | 在 **设置 → 插件** 中显示在插件 ID 下方的非空摘要。 |
| `translations` | 否 | 其他语言的展示文本,由注册表读取,Paseo 会忽略它。见[其他语言](/docs/plugins/publishing#other-languages)。 |
| `requirements` | 否 | 支持的 Paseo 版本,见下文。 |
| `build` | 否 | 准备命令,见 CLI 参考。 |
路径相对于 `paseo-plugin.json`,使用正斜杠,并且不能包含 `..` 片段。即使 `media` 在运行时 manifest 中是可选的,可视化插件的注册表展示页仍然需要一张界面图片。接受的格式和展示页要求见[图标和截图](/docs/plugins/publishing#icons-and-screenshots)。Paseo 会校验这些引用,但不会打开本地资源或获取 URL。
未知的顶层字段会被忽略。已知字段仍然会被校验,`requirements` 中的未知键会被拒绝,以免拼写错误的约束悄悄跳过兼容性检查。使用 `name`、`icon` 或 `media` 的 manifest 需要 Paseo 0.11.0 或更高版本;较旧的 daemon 会在安装时拒绝这些字段。
`translations` 的键是除 `en` 之外的标准语言标签,例如 `zh-CN`。每个值可以设置 `name`、`description` 或两者,必须是非空字符串;其他键会被拒绝。展示页如何使用它们,见[其他语言](/docs/plugins/publishing#other-languages)。
### 版本要求 {#requirements}
`requirements` 是一个可选对象。它目前支持的键 `paseo` 接受一个 npm semver 范围。省略 `requirements.paseo` 表示 `<0.8.0`:即该插件早于第一个破坏性的插件版本。Paseo 0.8 及更高版本会拒绝它,并附上[迁移指南](migration)的链接。空字符串、无效的范围以及未知的 manifest 要求键都会被拒绝。