Skip to content

feat: complete Simplified Chinese and English UI localization - #3

Merged
hekuo5310 merged 1 commit into
mainfrom
feat/i18n
Oct 4, 2026
Merged

hekuo5310 merged 1 commit into
mainfrom
feat/i18n

Conversation

@hekuo5310

Copy link
Copy Markdown
Member

VireoWiki now has a persistent Simplified Chinese / English interface selector. The migration covers static shells, dynamic page UI, editors, admin dialogs, service-side messages and bundled extensions, with localized dates and sorting. Wiki content, markup syntax, namespace prefixes and API status identifiers remain unchanged.

Locale is isolated per request using AsyncLocalStorage. Astro emits both language shells and Worker routes select the appropriate assets. HTML/JSON responses carry Content-Language and avoid shared language-cache leakage. Interpolation values retain existing escaping. Cloudflare production settings continue to deploy the existing vireowiki Worker and preserve its secrets.

Validation: server and client TypeScript checks passed; 9 i18n tests passed, including catalog/slot parity, template structure, protected content, request negotiation/concurrency and all 42 generated shells; production build and Wrangler dry-run passed. CI runs these checks for PRs and main updates. Maintenance notes are in I18N.md.

@hekuo5310
hekuo5310 merged commit 3bbffd6 into main Oct 4, 2026
1 check passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d512653405

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1931 to +1932
"m_326c02a5cb9ae742": "role=\"tabpanel\" aria-labelledby=\"__VIREO_SLOT_0__\" tabindex=\"0\" data-state-key=\"__VIREO_SLOT_1__\"&gt;__VIREO_SLOT_2____VIREO_SLOT_3__</div>",
"m_61ebde6ce0278343": "data-temporal-ms=\"__VIREO_SLOT_0__\" data-temporal-mode=\"__VIREO_SLOT_1__\"&gt;__VIREO_SLOT_2__</div>",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore literal tag terminators in render fragments

These catalog values are concatenated into partially constructed opening <div> tags, but &gt; is not treated as tag-closing syntax while the HTML tokenizer is inside a tag. Consequently, every localized :::tabs panel and valid :::until/:::after block produces malformed markup, often swallowing its body into the opening tag instead of rendering it. Preserve a literal > in both structural fragments; the Chinese catalog contains the same corruption.

Useful? React with 👍 / 👎.

Comment on lines +2045 to +2049
"m_6518ddaaddaaf1ac": " <changefreq>每日</changefreq>\n",
"m_28b76f58ad108f42": " </url>\n",
"m_86c491782b0043ca": " <changefreq>每周</changefreq>\n",
"m_a2aca12bc7570ba7": " <loc>__VIREO_SLOT_0__/博客</loc>\n",
"m_d190756ad4548f50": " <loc>__VIREO_SLOT_0__/博客/__VIREO_SLOT_1__</loc>\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep sitemap routes and enum values untranslated

With the production default locale zh-CN, /sitemap.xml now emits unsupported <changefreq> values such as 每日/每周 and advertises /博客 URLs even though the application route remains /blog. Crawlers may ignore the invalid frequency fields and will crawl wiki-slug fallbacks rather than the real blog pages, so these protocol values and route segments must remain daily, weekly, and /blog regardless of locale.

Useful? React with 👍 / 👎.

"m_c738b61460f613ac": "| Name | Value |\n",
"m_f076669f17b53f92": "| Name | Value |\n\n",
"m_6e5c1fafcd87247d": ":::tab Tab 1\n",
"m_d7cafcbd87c399b7": "Content :::\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the closing directive on its own line

In the English editor, selecting the tabs, accordion, or steps autocomplete scaffold inserts this fragment after the first child header. Because the translation changed Content\n:::\n to Content :::\n, the child directive no longer has a standalone closing ::: line and the block parser cannot recognize the generated structure. Keep the source line breaks while translating only the placeholder text.

Useful? React with 👍 / 👎.

"m_7d564631b75f58ca": "插入脚注",
"m_90f5c358f338ef2c": "[* 脚注内容]",
"m_eeb99d66983b2d76": "展开/折叠",
"m_6754eb453d1381b3": "[+ 展开/折叠标题] 隐藏内容在这里。 [-]",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve newlines in the Chinese fold snippet

For the default Chinese locale, the editor's expand/collapse toolbar button inserts this one-line string, while the renderer only recognizes fold syntax when the [+ ...] header, body, and [-] terminator are separated by newlines. The generated snippet therefore renders as literal text instead of a fold; retain the two source newlines in the translation.

Useful? React with 👍 / 👎.

Comment thread src/client/sw.ts
@@ -7,6 +7,7 @@
// classic-script-global 호환은 필요 없다. 이 파일은 service worker 컨텍스트에서만 평가된다.

/// <reference lib="webworker" />
import { ui } from '../../packages/wiki-shared/src/i18n/client';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the proxied service worker self-contained

Importing the shared i18n client makes Vite/Rollup emit a shared chunks/... import from the sw.js entry because the same module is used by many entries. The application serves that built file through the root URL /sw.js, so its relative import resolves as /chunks/... rather than /dist/chunks/...; that asset does not exist and new module service-worker registrations fail, while the classic fallback cannot parse the generated import. Inline the required message or otherwise ensure the proxied worker's chunk URLs resolve correctly.

Useful? React with 👍 / 👎.

"m_ab81ac6483934fb3": "Unknown key: \"__VIREO_SLOT_0__\" — only type, labels, series are supported (series items require indentation).",
"m_0e36ab1fef14b593": "type is required (bar, line, pie, doughnut, radar).",
"m_ed77553468de39f6": "labels are required (e.g. labels: [1Q, 2Q, 3Q, 4Q]).",
"m_30bbebfd6

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Set the crawler document language from the active locale

The crawler template is now used for translated English and Chinese responses, but it still emits <html lang="ko"> for both. This contradicts the response's localized content and Content-Language header, causing screen readers and search engines to apply Korean pronunciation and language classification to English or Chinese crawler pages. Interpolate the negotiated locale into the document language instead of retaining the former Korean-only value.

Useful? React with 👍 / 👎.

Comment thread src/routes/admin-mcp.ts
Comment on lines 146 to +149
export const USER_READ_TOOL_DEFS: McpToolDef[] = [
{
name: 'read_revision',
description: '특정 리비전의 본문을 읽어옵니다. revision_id 는 get_recent_changes 응답에 포함된 정수 id 이며, title 은 그 리비전이 속한 문서 슬러그입니다. raw=true 로 설정하면 위키 문법 변환을 건너뜁니다 (기본은 위키 문법 그대로 반환). 응답에는 본문, 작성자, 생성 시각이 포함됩니다.',
description: ui("m_35463423c1da3db2"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Build MCP definitions inside the request locale

These exported tool definitions call ui() while the module is being initialized, before localeMiddleware has established an AsyncLocalStorage context. They are therefore permanently populated from the hard-coded zh-CN fallback, so even an MCP request negotiated with ?lang=en receives Chinese tool descriptions and schemas; the draft lifecycle constants below have the same problem. Construct or translate these values per request rather than caching localized strings at module scope.

Useful? React with 👍 / 👎.

Comment thread src/routes/discussion.ts
@@ -353,7 +354,7 @@ discussionRoutes.post('/discussions/thread/:id/comments', requireAuth, requirePe
}

const link = `/w/${encodeURIComponent(discussionInfo.slug)}?mode=discussions&id=${discussionId}`;
const notifContent = `'${discussionInfo.title}' 토론에 새 댓글이 달렸습니다.`;
const notifContent = ui("m_417980f272a6a783", [discussionInfo.title]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid persisting the sender's locale in notifications

This text is translated in the comment author's request context and then stored verbatim in every recipient's notifications.content and push payload. If an English user comments on a discussion followed by Chinese users, all those users permanently receive an English notification even when fetching the notification list under zh-CN (and the reverse also occurs). Persist a locale-independent message key plus arguments, or resolve each recipient's locale when creating/rendering the notification.

Useful? React with 👍 / 👎.

"m_1596d8fae424d0ca": "<span><kbd>Enter</kbd> Select</span>",
"m_115518dce9b7a39f": "<span><kbd>Tab</kbd> Search all</span>",
"m_2a9b0e7c31cc401b": "<span><kbd>Esc</kbd> Close</span>",
"m_d5fe8044502d75dd": "”</strong> 전체 검색",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Translate the command-palette all-search label

The English catalog retains the Korean suffix 전체 검색, and the command palette concatenates this value directly after the user's query for its global-search result. Consequently, every English user who types a query sees a mixed-language primary action despite the rest of the palette being translated. Replace the retained Korean text with the English label; the Chinese catalog contains the same untranslated value.

Useful? React with 👍 / 👎.

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