From 337dfb64103e7d6dadf5366e163d3bdfb44ba07a Mon Sep 17 00:00:00 2001 From: Teakowa <27560638+Teakowa@users.noreply.github.com> Date: Sun, 27 Sep 2026 11:44:54 +0000 Subject: [PATCH 01/10] feat: add Simplified Chinese localization and language switcher Serve English at / and Simplified Chinese at /zh-CN from one optional [[lang=locale]] route. Copy moves to typed per-locale files keyed by the shared ids in site.ts, so both locales make the same claims; commands, repository names, and CLI output stay literal. - Header language menu (desktop popover, mobile sheet list) built on real links with hreflang/lang, keeping the current section on switch. - Per-locale lang, canonical, hreflang alternates, and og:locale. - CJK typography: no negative tracking, taller leading, SC font stack, headings wrap only between words. - English copy tightened into plainer wording; claims unchanged. - verify-build checks both pages' language metadata and literals. Closes #24 --- README.md | 25 +- scripts/verify-build.mjs | 38 ++ src/app.css | 70 ++++ src/app.d.ts | 12 + src/app.html | 2 +- src/hooks.server.ts | 10 + src/lib/components/AgentsSection.svelte | 12 +- src/lib/components/CopyButton.svelte | 13 +- src/lib/components/EcosystemSection.svelte | 14 +- src/lib/components/Footer.svelte | 9 +- src/lib/components/Header.svelte | 54 ++- src/lib/components/Hero.svelte | 21 +- src/lib/components/InstallSection.svelte | 44 +-- src/lib/components/LanguageSwitcher.svelte | 181 ++++++++++ src/lib/components/LanguagesSection.svelte | 24 +- src/lib/components/Terminal.svelte | 11 +- src/lib/components/ThemeSwitcher.svelte | 30 +- src/lib/components/ToolingSection.svelte | 11 +- src/lib/components/Wordmark.svelte | 6 +- src/lib/content/en.ts | 241 +++++++++++++ src/lib/content/index.ts | 23 ++ src/lib/content/types.ts | 96 +++++ src/lib/content/zh-CN.ts | 247 +++++++++++++ src/lib/locales.ts | 42 +++ src/lib/site.ts | 400 +++++---------------- src/params/locale.ts | 6 + src/routes/+layout.svelte | 17 - src/routes/+layout.ts | 1 - src/routes/+page.svelte | 30 -- src/routes/[[lang=locale]]/+layout.svelte | 29 ++ src/routes/[[lang=locale]]/+layout.ts | 8 + src/routes/[[lang=locale]]/+page.svelte | 45 +++ svelte.config.js | 5 +- 33 files changed, 1319 insertions(+), 458 deletions(-) create mode 100644 src/hooks.server.ts create mode 100644 src/lib/components/LanguageSwitcher.svelte create mode 100644 src/lib/content/en.ts create mode 100644 src/lib/content/index.ts create mode 100644 src/lib/content/types.ts create mode 100644 src/lib/content/zh-CN.ts create mode 100644 src/lib/locales.ts create mode 100644 src/params/locale.ts delete mode 100644 src/routes/+layout.svelte delete mode 100644 src/routes/+layout.ts delete mode 100644 src/routes/+page.svelte create mode 100644 src/routes/[[lang=locale]]/+layout.svelte create mode 100644 src/routes/[[lang=locale]]/+layout.ts create mode 100644 src/routes/[[lang=locale]]/+page.svelte diff --git a/README.md b/README.md index 5f16f13..0af3696 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,29 @@ that a capability ships. Describe direction (for example the intent-driven agent loop) as direction, not as current behavior. Keep internal terms such as WIR, HIR, frontend, and provider out of primary homepage messaging. -All copy, navigation, and support claims live in `src/lib/site.ts`. The hero -terminal shows real `wright` output; refresh it when the CLI output changes. +Locale-independent data (URLs, commands, repository names, support status, +and the hero terminal) lives in `src/lib/site.ts`. The hero terminal shows real +`wright` output; refresh it when the CLI output changes. + +## Localization + +English is the default at `/`; Simplified Chinese is at `/zh-CN`. There is no +browser-language redirect; readers switch from the header language menu. + +- Copy lives in `src/lib/content/.ts`, typed by + `src/lib/content/types.ts`. Records keyed by the ids in `site.ts` and + fixed-length lists keep every locale making the same claims; change a claim + in all locales in the same PR. +- Commands, repository names, protocol names, and CLI output stay literal. +- Write each locale for its readers, not word for word. Keep it short and + plain: the audience is Workshop, OverPy, and OSTW developers, not compiler + engineers. Chinese follows the zh-CN game client's Workshop terms (地图工坊, + 规则, 动作, 值, 服务器负载) and puts a space between CJK text and Latin words. +- Long Chinese headings may use `\u200b` to mark where they are allowed to + wrap; headings otherwise break only at spaces and punctuation. +- To add a locale: add it to `src/lib/locales.ts`, add a content file, and add + the path to `prerender.entries` in `svelte.config.js` and to + `scripts/verify-build.mjs`. ## Stack diff --git a/scripts/verify-build.mjs b/scripts/verify-build.mjs index d9e503b..22a2a64 100644 --- a/scripts/verify-build.mjs +++ b/scripts/verify-build.mjs @@ -41,3 +41,41 @@ for (const installer of installers) { process.exit(1); } } + +// Each locale must ship as its own prerendered page with correct language +// metadata, and commands and captured CLI output must stay untranslated. +const SITE_URL = 'https://wrightkit.dev'; +const pages = [ + { file: 'index.html', lang: 'en', canonical: `${SITE_URL}/` }, + { file: 'zh-CN.html', lang: 'zh-CN', canonical: `${SITE_URL}/zh-CN` } +]; +const literals = [ + 'curl -fsSL https://wrightkit.dev/install.sh | bash', + 'brew install wrightkit/tap/wright', + 'wright check src/main.opy', + "error[unknown-member]: unknown member 'setHealht'" +]; + +for (const page of pages) { + const path = resolve(ROOT_DIR, 'build', page.file); + if (!existsSync(path)) { + console.error(`[verify-build] Error: build/${page.file} does not exist.`); + process.exit(1); + } + const html = readFileSync(path, 'utf8'); + const expected = [ + ` ` + +import type { Locale } from '$lib/locales'; + +declare global { + namespace App { + interface PageData { + locale?: Locale; + } + } +} + +export {}; diff --git a/src/app.html b/src/app.html index 91a4496..ce1e1a9 100644 --- a/src/app.html +++ b/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..db10bce --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,10 @@ +import type { Handle } from '@sveltejs/kit'; +import { localeFromPath, localeInfo } from '$lib/locales'; + +// Runs while prerendering, so each static page ships with its own `lang`. +export const handle: Handle = ({ event, resolve }) => { + const lang = localeInfo[localeFromPath(event.url.pathname)].tag; + return resolve(event, { + transformPageChunk: ({ html }) => html.replace('%lang%', lang) + }); +}; diff --git a/src/lib/components/AgentsSection.svelte b/src/lib/components/AgentsSection.svelte index 25d30c6..168b569 100644 --- a/src/lib/components/AgentsSection.svelte +++ b/src/lib/components/AgentsSection.svelte @@ -1,15 +1,19 @@
- {#each agents.columns as column (column.title)} + {#each columns as column, c (c)}

{column.title}