Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/i18n.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: UI languages
on:
pull_request:
push:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
- run: npm ci
- run: node scripts/prepare-production.mjs
- run: npm run typecheck && npm run typecheck:client && npm run test:i18n
- run: npm run build
- run: I18N_CHECK_BUILD=1 npm run test:i18n
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,6 @@ yarn-error.log*

.gemini/
gha-creds-*.json

# Generated bilingual shells
/public/_i18n
17 changes: 17 additions & 0 deletions I18N.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Interface languages

VireoWiki supports Simplified Chinese (`zh-CN`, default) and English (`en`). The language selector keeps the current URL and stores `vireo_locale` for one year. A valid `?lang=` overrides the cookie; `DEFAULT_LOCALE` in Wrangler sets the fallback. Article text, slugs, markup grammar, status codes and user content stay unchanged.

## Adding interface text

Catalogs live in `packages/wiki-shared/src/i18n/`: `source.json` records the original authored message; `zh-CN.json` and `en.json` contain the same keys. `sources.json` records original locations for migration reference. Use a descriptive new key or `m_` plus the first 16 hexadecimal characters of the source message's SHA-256. Add all three catalog values together.

In browser modules import `ui` from `packages/wiki-shared/src/i18n/client`; server modules use `src/i18n/server`. The latter keeps the locale isolated with AsyncLocalStorage, so concurrent requests cannot share a language. Shared server helpers accept a locale explicitly. Classic extensions use `window.VireoI18n.ui` after the page bootstrap.

`ui(key, [value0, value1])` fills `__VIREO_SLOT_0__`, `__VIREO_SLOT_1__`, etc. Keep existing escaping at the interpolation call site. Do not translate input text, namespace prefixes, SQL, CSS classes, API identifiers or parser tokens. Translations may reorder slots, but must preserve every slot exactly once for each original occurrence. HTML templates must preserve markup and behavior attributes; only visible text and accessibility labels change.

## Static pages and checks

Astro creates the original shells. `scripts/localize-shells.mjs` writes both languages to `public/_i18n/{locale}`. Worker routes select the correct shell; direct static HTML uses the default Chinese output. The language bootstrap is a Vite entry at `/dist/i18n.js`, included in every shell. Adding an authored static label also requires cataloging its original text. `<code>`, `<pre>`, scripts and `data-no-i18n` content are protected.

Run `npm run test:i18n`, `npm run typecheck`, `npm run typecheck:client` and `npm run build`. CI checks catalog coverage, slots, HTML structure, language negotiation and concurrent request isolation. No translation service is used at runtime. Main-branch pushes deploy the existing `vireowiki` Worker using `wrangler.production.toml`; credentials remain Cloudflare secrets.
14 changes: 4 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
"predev": "node packages/wiki-shared/scripts/sync-render-css.mjs",
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"build": "node packages/wiki-shared/scripts/sync-render-css.mjs && node scripts/build-skins-css.mjs && astro build && node scripts/copy-astro-pages.mjs && vite build",
"build:astro": "node packages/wiki-shared/scripts/sync-render-css.mjs && node scripts/build-skins-css.mjs && astro build && node scripts/copy-astro-pages.mjs",
"build": "node packages/wiki-shared/scripts/sync-render-css.mjs && node scripts/build-skins-css.mjs && astro build && node scripts/copy-astro-pages.mjs && node scripts/localize-shells.mjs && vite build",
"build:astro": "node packages/wiki-shared/scripts/sync-render-css.mjs && node scripts/build-skins-css.mjs && astro build && node scripts/copy-astro-pages.mjs && node scripts/localize-shells.mjs",
"build:watch": "vite build --watch",
"typecheck": "tsc --noEmit",
"typecheck:client": "tsc --noEmit -p tsconfig.client.json",
"generate:rules": "node scripts/generate-rules.mjs",
"deploy:do": "wrangler deploy -c do-worker/wrangler.toml"
"deploy:do": "wrangler deploy -c do-worker/wrangler.toml",
"test:i18n": "node scripts/test-i18n.mjs"
},
"dependencies": {
"@block65/webcrypto-web-push": "^1.0.2",
Expand All @@ -31,7 +32,9 @@
"astro": "^6.4.2",
"typescript": "^5.5.0",
"vite": "^8.0.10",
"wrangler": "^4.78.0"
"wrangler": "^4.78.0",
"parse5": "^7.3.0",
"esbuild": "^0.27.3"
},
"license": "MPL-2.0"
}
Loading
Loading