mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-10 02:15:02 +00:00
feat(website): serve paseo.sh in Simplified Chinese
Every page of the website now has a Simplified Chinese version under
/zh-CN: the interface, docs, blog posts, comparison pages, legal pages,
all changelog releases and the homepage mockup. English URLs are unchanged.
The URL alone decides the language. There is no browser-language redirect
and no stored preference, because either would have to redirect the
unprefixed English URLs that people share and search engines index.
Internal links carry the page's language instead, and hreflang alternates
point Chinese search results at /zh-CN.
Translated headings pin the English anchor with {#id}, so section links
work in both languages. content-translations.test.ts fails when a page
has no translation or a translation's headings drift from the English.
Changelog releases and plugin listings without a translation render
whole in English under a notice, so a release never waits on one.
Plugin manifests and the registry gain optional translations and
OVERVIEW.zh-CN.md fields; older clients and registries ignore them.
This commit is contained in:
@@ -68,6 +68,11 @@ 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);
|
||||
@@ -128,6 +133,19 @@ describe("plugin manifest", () => {
|
||||
await expect(readPluginManifest(directory)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("reads listing translations with each field optional", async () => {
|
||||
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);
|
||||
});
|
||||
|
||||
it("accepts only non-empty argv arrays for build commands", async () => {
|
||||
const directory = await mkdtemp(path.join(tmpdir(), "paseo-plugin-manifest-"));
|
||||
directories.push(directory);
|
||||
|
||||
@@ -22,10 +22,27 @@ 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",
|
||||
@@ -36,6 +53,14 @@ 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> {
|
||||
|
||||
Reference in New Issue
Block a user