You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by zh...@apache.org on 2026/09/04 13:29:58 UTC

(maka) branch main updated: feat(i18n): add Traditional Chinese locale (#3853)

This is an automated email from the ASF dual-hosted git repository.

me2seeks pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/main by this push:
     new b1369c8087 feat(i18n): add Traditional Chinese locale (#3853)
b1369c8087 is described below

commit b1369c80875c02b8d6335dd440d3deb76fe69e59
Author: AcerYue <33...@users.noreply.github.com>
AuthorDate: Fri Sep 4 21:29:52 2026 +0800

    feat(i18n): add Traditional Chinese locale (#3853)
    
    Add separate zh-CN and zh-TW resolved locales, including Taiwan-localized UI catalogs and system-locale aliases, while preserving the former persisted zh preference as zh-CN.
    
    Replace the Desktop Settings language segmented control with the @maka/ui Selector, offering Follow system, Simplified Chinese, Traditional Chinese, and English.
    
    Co-authored-by: AcerYue <33...@users.noreply.github.com>
    Co-authored-by: JLee <pk...@photons.com.tw>
---
 apps/desktop/.storybook/preview.tsx                |  12 +-
 apps/desktop/e2e/accessibility-coverage.spec.ts    |   2 +-
 apps/desktop/e2e/context-window-save.spec.ts       |   2 +-
 apps/desktop/e2e/fixtures.ts                       |  34 +-
 .../e2e/request-header-row-contract.spec.ts        |   2 +-
 apps/desktop/e2e/zh-tw-locale.spec.ts              |  42 ++
 apps/desktop/renderer-architecture.json            |  13 +-
 .../src/main/__tests__/about-update-status.test.ts |   4 +-
 .../active-execution-boundary-read-model.test.ts   |   2 +-
 ...copy.test.ts => agent-graph-panel-copy.test.ts} |  17 +-
 .../main/__tests__/command-palette-retired.test.ts |   4 +-
 .../computer-use-status-item-locale.test.ts        |   2 +-
 .../main/__tests__/config-transfer-service.test.ts |  21 +
 .../__tests__/desktop-locale-authority.test.ts     |  57 ++-
 .../src/main/__tests__/desktop-native-copy.test.ts |  16 +-
 ...ter-copy.test.ts => e2e-fixture-locale.test.ts} |  25 +-
 .../src/main/__tests__/goal-arm-outcome.test.ts    |   2 +-
 .../src/main/__tests__/health-center-copy.test.ts  |  22 +-
 .../__tests__/import-tasks-settings-page.test.ts   |   4 +-
 .../main/__tests__/message-queue-ui-state.test.ts  |   2 +-
 .../main/__tests__/new-task-staged-content.test.ts |  10 +-
 .../main/__tests__/notifications-policy.test.ts    |  21 +-
 .../main/__tests__/permission-center-copy.test.ts  |   2 +-
 .../src/main/__tests__/plan-mode-copy.test.ts      |   2 +-
 .../__tests__/provider-connection-status.test.ts   |  10 +-
 .../main/__tests__/renderer-error-report.test.ts   |   2 +-
 ...copy.test.ts => runtime-host-boot-copy.test.ts} |  17 +-
 .../main/__tests__/runtime-host-quit-copy.test.ts  |   4 +-
 .../__tests__/runtime-host-upgrade-dialog.test.ts  |  12 +-
 .../session-inspector-context-format.test.ts       |   2 +-
 .../__tests__/session-status-presentation.test.ts  |  22 +-
 .../__tests__/settings-preferences-copy.test.ts    |  74 +++
 .../__tests__/settings-test-result-copy.test.ts    |   2 +-
 .../src/main/__tests__/streaming-handoff.test.ts   |  24 +-
 ...test.ts => subscription-result-message.test.ts} |  12 +-
 .../main/__tests__/task-readiness-notice.test.ts   |  16 +-
 .../traditional-chinese-peer-mesh-copy.test.ts     |  59 +++
 .../main/__tests__/ui-locale-update-gate.test.ts   |  18 +-
 .../main/__tests__/use-onboarding-snapshot.test.ts |  12 +-
 .../src/main/__tests__/use-session-todo.test.ts    | 151 ++++++
 .../main/__tests__/workhub-surface-flow.test.ts    |   8 +-
 apps/desktop/src/main/browser-message-box.ts       |   6 +-
 .../src/main/client-settings-confirmation-copy.ts  |  38 +-
 apps/desktop/src/main/computer-use/status-item.ts  |   7 +-
 apps/desktop/src/main/config-transfer-service.ts   |  15 +-
 apps/desktop/src/main/desktop-locale-authority.ts  |   7 +-
 apps/desktop/src/main/e2e-fixture.ts               |   4 +-
 apps/desktop/src/main/main.ts                      |  23 +-
 .../src/main/native-diagnostic-dialog-copy.ts      |  51 ++-
 apps/desktop/src/main/notifications-policy.ts      |   5 +
 .../permission-overlay/permission-overlay-copy.ts  |  21 +-
 apps/desktop/src/main/project-picker-copy.ts       |   4 +-
 apps/desktop/src/main/runtime-host-boot-copy.ts    |  90 ++++
 apps/desktop/src/main/runtime-host-quit-copy.ts    |  18 +-
 apps/desktop/src/main/runtime-host-upgrade-copy.ts |  25 +-
 .../src/preload/runtime-host-session-catalog.ts    |   2 +-
 apps/desktop/src/renderer/agent-graph-panel.tsx    |  62 ++-
 apps/desktop/src/renderer/app-shell-copy.ts        |   6 +-
 apps/desktop/src/renderer/app-shell-effects.ts     |   1 -
 apps/desktop/src/renderer/app-shell.tsx            |   2 +-
 apps/desktop/src/renderer/attachment-preflight.ts  |   2 +-
 apps/desktop/src/renderer/conversation-markdown.ts |   2 +-
 apps/desktop/src/renderer/daily-review-actions.ts  |   4 +-
 .../src/renderer/derive-turn-lineage-badges.ts     |   2 +-
 .../renderer/features/connection-settings/index.ts |   1 +
 .../connection-settings/provider-panel-shared.ts   |  30 +-
 .../connection-settings/settings-provider-copy.ts  | 199 +++++++-
 .../subscription-result-message.ts                 |  53 +++
 .../conversation/model/task-readiness-notice.ts    |  78 ++--
 .../features/runtime-host-management/index.ts      |  23 +-
 .../ui/runtime-host-peer-mesh-dialog.tsx           | 197 +++++++-
 .../model/session-project-grouping.ts              |   2 +-
 .../src/renderer/features/workbar/testing.ts       |   1 +
 .../workbar/tools/artifacts/artifact-pane.tsx      |   8 +-
 .../workbar/tools/inspector/use-session-trace.ts   |  10 +-
 .../workbar/tools/review/session-review-panel.tsx  |   6 +-
 .../tools/side-chat/quote-companion-panel.tsx      |   9 +-
 .../workbar/tools/tasks/use-session-todo.ts        |   7 +-
 .../tools/terminal/session-terminal-panel.tsx      |  13 +-
 apps/desktop/src/renderer/locales/artifact-copy.ts |  38 +-
 apps/desktop/src/renderer/locales/browser-copy.ts  |  26 +-
 .../src/renderer/locales/conversation-copy.ts      | 247 +++++++++-
 .../locales/external-session-import-copy.ts        |  58 ++-
 apps/desktop/src/renderer/locales/mcp-copy.ts      |  61 ++-
 .../src/renderer/locales/onboarding-copy.ts        |  51 ++-
 .../src/renderer/locales/permission-center-copy.ts |  49 +-
 .../desktop/src/renderer/locales/plan-mode-copy.ts |  21 +-
 .../renderer/locales/session-collaboration-copy.ts | 105 ++++-
 .../src/renderer/locales/settings-bot-copy.ts      |  92 +++-
 .../renderer/locales/settings-daily-review-copy.ts |  21 +-
 .../src/renderer/locales/settings-data-copy.ts     |  29 +-
 .../src/renderer/locales/settings-health-copy.ts   |  86 +++-
 .../src/renderer/locales/settings-memory-copy.ts   |  47 +-
 .../renderer/locales/settings-navigation-copy.ts   |  28 +-
 .../renderer/locales/settings-preferences-copy.ts  |  97 +++-
 .../src/renderer/locales/settings-projects-copy.ts | 305 ++++++++++++-
 .../src/renderer/locales/settings-shared-copy.ts   |  42 +-
 .../renderer/locales/settings-subagents-copy.ts    |  84 +++-
 .../src/renderer/locales/settings-tasks-copy.ts    |  29 +-
 .../renderer/locales/settings-test-result-copy.ts  |  27 +-
 .../src/renderer/locales/settings-usage-copy.ts    |  24 +-
 .../renderer/locales/settings-web-search-copy.ts   |  19 +-
 apps/desktop/src/renderer/locales/shell-copy.ts    | 508 ++++++++++++++++++++-
 .../src/renderer/locales/shell-remaining-copy.ts   |  78 +++-
 apps/desktop/src/renderer/mcp-catalog.ts           |  22 +-
 apps/desktop/src/renderer/model-catalog-choices.ts |   2 +-
 .../src/renderer/model-connection-errors.ts        |   6 +-
 .../src/renderer/session-error-presentation.ts     |   2 +-
 .../src/renderer/session-status-presentation.ts    |   4 +-
 .../src/renderer/settings/about-settings-page.tsx  |  10 +-
 .../src/renderer/settings/about-update-status.ts   |   8 +-
 .../src/renderer/settings/bot-chat-detail.tsx      |   8 +-
 .../src/renderer/settings/bot-chat-overview.tsx    |   4 +-
 .../src/renderer/settings/bot-chat-shared.tsx      |   4 +-
 .../src/renderer/settings/bot-onboarding-modal.tsx |   6 +-
 .../settings/daily-review-settings-page.tsx        |   2 +-
 .../src/renderer/settings/data-settings-page.tsx   |   2 +-
 .../settings/memory-settings-view-model.ts         |   2 +-
 .../renderer/settings/permission-center-page.tsx   |   4 +-
 .../settings/personalization-settings-section.tsx  |  23 +-
 .../src/renderer/settings/provider-add-form.tsx    |   2 +-
 .../renderer/settings/provider-catalog-page.tsx    |   5 +-
 .../settings/provider-connection-status.ts         |   2 +-
 .../src/renderer/settings/provider-display-copy.ts | 183 +++++---
 .../renderer/settings/provider-oauth-section.tsx   |   2 +-
 .../src/renderer/settings/providers-panel.tsx      |   3 +-
 .../settings/runtime-host-management-dialog.tsx    |  28 +-
 .../settings/runtime-host-profiles-section.tsx     |  42 +-
 .../src/renderer/settings/settings-error-copy.ts   |   9 +-
 .../settings/use-memory-settings-controller.ts     |   2 +-
 .../src/renderer/settings/use-oauth-login-flow.ts  |  37 +-
 apps/desktop/src/renderer/turn-footer-actions.ts   |   2 +-
 .../src/renderer/use-onboarding-snapshot.ts        |   4 +-
 apps/desktop/src/renderer/workhub-surface.tsx      |  74 ++-
 apps/desktop/stories/agent-graph-panel.stories.tsx |   2 +-
 apps/desktop/stories/app-shell.stories.tsx         |   2 +-
 apps/desktop/stories/error-boundary.stories.tsx    |   3 +-
 .../stories/settings/settings-pages.stories.tsx    |  14 +-
 docs/astryx-surface-file-inventory.md              |   2 +-
 packages/cli/src/__tests__/cli-ui-locale.test.ts   |  42 +-
 packages/cli/src/__tests__/cli.test.ts             |   2 +-
 packages/cli/src/__tests__/pi-transcript.test.ts   |  10 +-
 .../cli/src/__tests__/pi-tui-mcp-status.test.ts    | 270 +++++++----
 packages/cli/src/__tests__/pi-tui-runner.test.ts   |  16 +-
 .../cli/src/__tests__/tui-copy-catalog.test.ts     |  91 ++--
 .../cli/src/__tests__/tui-copy-command.test.ts     |   2 +-
 .../cli/src/__tests__/tui-primary-guidance.test.ts |  10 +-
 .../cli/src/__tests__/tui-session-status.test.ts   |   4 +-
 packages/cli/src/cli-ui-locale.ts                  |  10 +-
 packages/cli/src/pi-tui-mcp-status.ts              |  16 +-
 packages/cli/src/tui-copy-catalog.ts               | 361 ++++++++++++++-
 packages/core/src/__tests__/redaction.test.ts      |  37 ++
 packages/core/src/__tests__/relative-time.test.ts  |  10 +-
 packages/core/src/__tests__/settings.test.ts       |  26 ++
 .../core/src/__tests__/tool-quiet-preview.test.ts  |  16 +-
 packages/core/src/__tests__/ui-locale.test.ts      |  64 ++-
 packages/core/src/redaction.ts                     |  87 +++-
 packages/core/src/relative-time.ts                 |  11 +-
 packages/core/src/settings.ts                      |  10 +-
 packages/core/src/tool-quiet-preview.ts            |  20 +-
 packages/core/src/ui-locale.ts                     |  24 +-
 .../ui/src/__tests__/conversation-copy.test.ts     |  15 +-
 .../ui/src/__tests__/daily-review-copy.test.ts     |   7 +-
 .../ui/src/__tests__/live-turn-projection.test.ts  |   2 +-
 packages/ui/src/__tests__/markdown-body.test.ts    |   2 +-
 packages/ui/src/__tests__/materialize.test.ts      |   4 +-
 .../ui/src/__tests__/runtime-resume-copy.test.ts   |   8 +-
 .../src/__tests__/session-hover-card-copy.test.ts  |   2 +-
 .../__tests__/tool-activity-presentation.test.ts   |   6 +-
 .../ui/src/__tests__/tool-format.test.ts           |  20 +-
 .../ui/src/__tests__/transcript-projection.test.ts |   2 +-
 .../ui/src/__tests__/turn-running-spinner.test.tsx |   4 +-
 packages/ui/src/artifact-preview-registry.ts       |   2 +-
 packages/ui/src/assistant-stream.ts                |   4 +-
 packages/ui/src/astryx-copy.ts                     |  72 +++
 packages/ui/src/astryx-i18n.tsx                    |   6 +-
 packages/ui/src/chat-model-helpers.ts              |   4 +-
 packages/ui/src/chat-turn.tsx                      |   3 +-
 packages/ui/src/chat-view.tsx                      |   2 +-
 packages/ui/src/conversation-copy.ts               | 160 ++++++-
 packages/ui/src/daily-review-copy.ts               |  37 +-
 packages/ui/src/daily-review-helpers.ts            |   6 +-
 packages/ui/src/live-turn-projection.ts            |   2 +-
 packages/ui/src/locale-helpers.ts                  |  10 +-
 packages/ui/src/runtime-resume-copy.ts             |  39 +-
 packages/ui/src/scheduled-task-copy.ts             |  37 +-
 packages/ui/src/scheduled-task-panel.tsx           |  12 +-
 packages/ui/src/search-modal.tsx                   |   6 +-
 packages/ui/src/session-hover-card-copy.ts         |  14 +-
 packages/ui/src/session-status-presentation.ts     |   4 +-
 packages/ui/src/shared-ui-copy.ts                  |  86 +++-
 packages/ui/src/shell-controls-copy.ts             |  33 +-
 packages/ui/src/skills-copy.ts                     |  17 +-
 packages/ui/src/thinking-stream.ts                 |   4 +-
 packages/ui/src/tool-activity.tsx                  |   2 +-
 packages/ui/src/tool-activity/copy.ts              | 106 ++++-
 packages/ui/src/tool-activity/preview-utils.ts     |   2 +-
 packages/ui/src/tool-activity/result-projection.ts |   2 +-
 packages/ui/src/tool-format.ts                     |   6 +-
 packages/ui/src/tool-output-stream.ts              |   2 +-
 scripts/fixture-env.mjs                            |   2 +-
 201 files changed, 5525 insertions(+), 866 deletions(-)

diff --git a/apps/desktop/.storybook/preview.tsx b/apps/desktop/.storybook/preview.tsx
index f460a84495..52b022704d 100644
--- a/apps/desktop/.storybook/preview.tsx
+++ b/apps/desktop/.storybook/preview.tsx
@@ -38,7 +38,12 @@ const withMakaRoot: Decorator = (Story, context) => {
   // lines are ~1.8× the width of the Chinese copy, so a row that fits in zh
   // overflows, truncates, or clips in en. Stories were locked to `zh`, which
   // is exactly why those breakages only ever showed up in the shipped app.
-  const locale = context.globals.locale === 'en' ? 'en' : 'zh';
+  const locale =
+    context.globals.locale === 'en'
+      ? 'en'
+      : context.globals.locale === 'zh-TW'
+        ? 'zh-TW'
+        : 'zh-CN';
 
   root.classList.toggle('dark', colorScheme === 'dark');
   root.style.colorScheme = colorScheme;
@@ -96,7 +101,8 @@ const preview: Preview = {
       toolbar: {
         icon: 'globe',
         items: [
-          { title: '中文', value: 'zh' },
+          { title: '简体中文', value: 'zh-CN' },
+          { title: '繁體中文', value: 'zh-TW' },
           { title: 'English', value: 'en' },
         ],
       },
@@ -114,7 +120,7 @@ const preview: Preview = {
   },
   initialGlobals: {
     colorScheme: 'light',
-    locale: 'zh',
+    locale: 'zh-CN',
     palette: 'default',
   },
   parameters: {
diff --git a/apps/desktop/e2e/accessibility-coverage.spec.ts b/apps/desktop/e2e/accessibility-coverage.spec.ts
index 8d55ab9c85..e04e7ddd66 100644
--- a/apps/desktop/e2e/accessibility-coverage.spec.ts
+++ b/apps/desktop/e2e/accessibility-coverage.spec.ts
@@ -72,7 +72,7 @@ test('every settings page exposes named actionable controls', async ({ window: p
   const sectionLabels = (await navigation.getByRole('button').allTextContents())
     .map((label) => label.trim().replace(/\s*Beta$/, ''))
     .filter((label) => label.length > 0 && label !== '返回应用');
-  const expectedSectionLabels = groupedNav('zh')
+  const expectedSectionLabels = groupedNav('zh-CN')
     .flatMap(({ items }) => items)
     .filter(({ enabled }) => enabled)
     .map(({ label }) => label);
diff --git a/apps/desktop/e2e/context-window-save.spec.ts b/apps/desktop/e2e/context-window-save.spec.ts
index 41b42b1cdf..a6094fda4b 100644
--- a/apps/desktop/e2e/context-window-save.spec.ts
+++ b/apps/desktop/e2e/context-window-save.spec.ts
@@ -20,7 +20,7 @@
 import { expect, test } from './fixtures';
 import { getProviderSettingsCopy } from '../src/renderer/features/connection-settings';
 
-const copy = getProviderSettingsCopy('zh').detail;
+const copy = getProviderSettingsCopy('zh-CN').detail;
 const MODEL_ID = 'custom-reasoner';
 
 test('one save persists a context window that is still focused', async ({
diff --git a/apps/desktop/e2e/fixtures.ts b/apps/desktop/e2e/fixtures.ts
index 688c6379ea..45a3b91bb1 100644
--- a/apps/desktop/e2e/fixtures.ts
+++ b/apps/desktop/e2e/fixtures.ts
@@ -202,7 +202,7 @@ async function seedE2eConnection(userDataDir: string): Promise<void> {
   }
 }
 
-async function seedE2eLocale(userDataDir: string, locale: 'zh' | 'en'): Promise<void> {
+async function seedE2eLocale(userDataDir: string, locale: 'zh-CN' | 'zh-TW' | 'en'): Promise<void> {
   const workspaceRoot = path.join(userDataDir, 'workspaces', 'default');
   await createSettingsStore(workspaceRoot).update({
     personalization: { uiLocale: locale },
@@ -426,7 +426,7 @@ async function withE2eWindow(
     seed: boolean;
     readinessSelector: string;
     e2eFixtureScenario?: string;
-    locale?: 'zh' | 'en';
+    locale?: 'zh-CN' | 'zh-TW' | 'en';
     /** Opt this window back into animated scrolling; see `scroll-motion-policy`. */
     scrollMotion?: 'auto' | 'smooth';
     /** #1312: force app:info's platform so the window boots natively into that platform's `data-os` cascade. */
@@ -603,7 +603,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
   }, { scope: 'worker', auto: true }],
   directoryReferenceWindow: async ({}, use) => {
     await withE2eWindow(
-      { seed: true, readinessSelector: COMPOSER_INPUT, locale: 'zh', showWindow: true },
+      { seed: true, readinessSelector: COMPOSER_INPUT, locale: 'zh-CN', showWindow: true },
       async (page, { userDataDir, app }) => {
         const folder = path.join(userDataDir, 'referenced-source');
         await mkdir(path.join(folder, 'nested'), { recursive: true });
@@ -620,7 +620,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
   },
   // Seeded: a pre-staged connection clears onboarding so the composer is ready.
   window: async ({}, use) => {
-    await withE2eWindow({ seed: true, readinessSelector: COMPOSER_INPUT, locale: 'zh' }, use);
+    await withE2eWindow({ seed: true, readinessSelector: COMPOSER_INPUT, locale: 'zh-CN' }, use);
   },
   agentGraphWindow: async ({}, use) => {
     await withE2eWindow(
@@ -628,7 +628,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
         seed: false,
         readinessSelector: '.maka-agent-graph-panel',
         e2eFixtureScenario: 'agent-graph-layout',
-        locale: 'zh',
+        locale: 'zh-CN',
         showWindow: true,
       },
       use,
@@ -638,7 +638,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
     await withE2eWindow({
       seed: false,
       readinessSelector: '[data-maka-contract="onboarding-card"]',
-      locale: 'zh',
+      locale: 'zh-CN',
       showWindow: true,
     }, use);
   },
@@ -647,7 +647,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
       {
         seed: true,
         readinessSelector: COMPOSER_INPUT,
-        locale: 'zh',
+        locale: 'zh-CN',
         gitReviewExtraFiles: 0,
       },
       async (page, context) => {
@@ -663,7 +663,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
     await withE2eWindow({
       seed: true,
       readinessSelector: COMPOSER_INPUT,
-      locale: 'zh',
+      locale: 'zh-CN',
       invocableSkills: true,
     }, use);
   },
@@ -680,7 +680,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
     await withE2eWindow({
       seed: true,
       readinessSelector: COMPOSER_INPUT,
-      locale: 'zh',
+      locale: 'zh-CN',
       newTaskProject: true,
       showWindow: true,
     }, use);
@@ -694,7 +694,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
       seed: false,
       readinessSelector: '[data-maka-contract="search-modal"][open]',
       e2eFixtureScenario: 'sidebar-search-modal-open',
-      locale: 'zh',
+      locale: 'zh-CN',
     }, use);
   },
   parentRemovalWindow: async ({}, use) => {
@@ -702,7 +702,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
       {
         seed: true,
         readinessSelector: COMPOSER_INPUT,
-        locale: 'zh',
+        locale: 'zh-CN',
         parentRemovalSessions: true,
       },
       use,
@@ -713,7 +713,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
       {
         seed: true,
         readinessSelector: COMPOSER_INPUT,
-        locale: 'zh',
+        locale: 'zh-CN',
         railRenderSessions: true,
       },
       use,
@@ -734,7 +734,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
       // Every other fixture window names its locale; without one the renderer
       // takes the host's, so any test that reaches a control by its label
       // passes on a Chinese desktop and cannot find it on an English CI runner.
-      locale: 'zh',
+      locale: 'zh-CN',
       showWindow: true,
     }, async (page, { app }) => {
       const viewport = await page.evaluate(() => ({ width: innerWidth, height: innerHeight }));
@@ -760,7 +760,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
       seed: false,
       readinessSelector: '[data-turn-id]',
       e2eFixtureScenario: 'chat-partial-history',
-      locale: 'zh',
+      locale: 'zh-CN',
       showWindow: true,
     }, use);
   },
@@ -773,7 +773,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
       seed: false,
       readinessSelector: '.settingsSurface',
       e2eFixtureScenario: 'settings-models',
-      locale: 'zh',
+      locale: 'zh-CN',
       showWindow: true,
     }, use);
   },
@@ -782,7 +782,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
       seed: false,
       readinessSelector: '.settingsCapabilityGroup',
       e2eFixtureScenario: 'settings-permissions',
-      locale: 'zh',
+      locale: 'zh-CN',
       showWindow: true,
     }, use);
   },
@@ -794,7 +794,7 @@ export const test = base.extend<E2eTestFixtures, E2eWorkerFixtures>({
       seed: false,
       readinessSelector: '[data-turn-id]',
       e2eFixtureScenario: 'turn-narrative',
-      locale: 'zh',
+      locale: 'zh-CN',
       showWindow: true,
     }, use);
   },
diff --git a/apps/desktop/e2e/request-header-row-contract.spec.ts b/apps/desktop/e2e/request-header-row-contract.spec.ts
index c820b17743..772d5f5ae9 100644
--- a/apps/desktop/e2e/request-header-row-contract.spec.ts
+++ b/apps/desktop/e2e/request-header-row-contract.spec.ts
@@ -37,7 +37,7 @@ import { getProviderSettingsCopy } from '../src/renderer/features/connection-set
  * surfaces as they first render, and this editor is three clicks deep.
  */
 
-const copy = getProviderSettingsCopy('zh').detail;
+const copy = getProviderSettingsCopy('zh-CN').detail;
 
 test('the request header remove button centres on its field', async ({
   requestHeaderRowWindow: page,
diff --git a/apps/desktop/e2e/zh-tw-locale.spec.ts b/apps/desktop/e2e/zh-tw-locale.spec.ts
new file mode 100644
index 0000000000..801542b2b2
--- /dev/null
+++ b/apps/desktop/e2e/zh-tw-locale.spec.ts
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ensureSidebarExpanded, expect, test } from './fixtures';
+
+test('switches General Settings from Simplified to Traditional Chinese', async ({
+  window: page,
+}) => {
+  await ensureSidebarExpanded(page);
+  await page.getByRole('button', { name: '设置', exact: true }).click();
+  await expect(page.getByRole('main', { name: '设置内容' })).toBeVisible();
+  await page.getByRole('button', { name: '通用', exact: true }).click();
+  await expect(page.getByText('界面语言', { exact: true }).first()).toBeVisible();
+  await page.keyboard.press('Escape');
+
+  await page.evaluate(async () => {
+    await window.maka.settings.update({ personalization: { uiLocale: 'zh-TW' } });
+  });
+  await page.reload();
+  await page.waitForSelector('.maka-composer-editor');
+  await ensureSidebarExpanded(page);
+  await page.getByRole('button', { name: '設定', exact: true }).click();
+  await expect(page.getByRole('main', { name: '設定內容' })).toBeVisible();
+  await page.getByRole('button', { name: '通用', exact: true }).click();
+  await expect(page.getByText('介面語言', { exact: true }).first()).toBeVisible();
+});
diff --git a/apps/desktop/renderer-architecture.json b/apps/desktop/renderer-architecture.json
index 8067c02959..74a02cf6df 100644
--- a/apps/desktop/renderer-architecture.json
+++ b/apps/desktop/renderer-architecture.json
@@ -408,8 +408,8 @@
           "./locales/shell-copy.js": 1,
           "@maka/core/redaction": 1
         },
-        "importSpecifiers": 3,
-        "nonTriviaTokens": 515
+        "importSpecifiers": 2,
+        "nonTriviaTokens": 504
       },
       "src/renderer/app-shell-detail-panel.tsx": {
         "importDeclarations": 0,
@@ -444,7 +444,7 @@
         "nonTriviaTokens": 672
       },
       "src/renderer/app-shell-effects.ts": {
-        "importDeclarations": 13,
+        "importDeclarations": 12,
         "bridgePaths": {
           "window.maka.app.info": 1,
           "window.maka.appWindow.subscribeCommand": 1,
@@ -490,12 +490,11 @@
           "./theme": 1,
           "./titlebar-modal-sync": 1,
           "@astryxdesign/core/hooks": 1,
-          "@maka/core/redaction": 1,
           "@maka/core/session-event-health": 1,
           "react": 1
         },
-        "importSpecifiers": 21,
-        "nonTriviaTokens": 3823
+        "importSpecifiers": 20,
+        "nonTriviaTokens": 3816
       },
       "src/renderer/app-shell-overlays.tsx": {
         "importDeclarations": 8,
@@ -2909,7 +2908,6 @@
           "./settings-expandable-row": 1,
           "./settings-section": 1,
           "./settings-skeleton.js": 1,
-          "@astryxdesign/core": 1,
           "@maka/ui": 1,
           "react": 1
         }
@@ -3894,7 +3892,6 @@
           "../features/connection-settings": 1,
           "./oauth-login-flow-guard": 1,
           "./runtime-host-settings-target.js": 1,
-          "@maka/core/redaction": 1,
           "@maka/ui": 1,
           "react": 1
         }
diff --git a/apps/desktop/src/main/__tests__/about-update-status.test.ts b/apps/desktop/src/main/__tests__/about-update-status.test.ts
index c3a33fae15..a3e7e70ac2 100644
--- a/apps/desktop/src/main/__tests__/about-update-status.test.ts
+++ b/apps/desktop/src/main/__tests__/about-update-status.test.ts
@@ -25,7 +25,7 @@ import {
 } from '../../renderer/settings/about-update-status.js';
 import { getSettingsPreferencesCopy } from '../../renderer/locales/settings-preferences-copy.js';
 
-const copy = getSettingsPreferencesCopy('zh').about;
+const copy = getSettingsPreferencesCopy('zh-CN').about;
 
 test('a packaged nightly is tokened Nightly, never 正式版', () => {
   const facts = aboutChannelFacts({ buildMode: 'packaged', updateChannel: 'nightly' }, copy);
@@ -47,7 +47,7 @@ test('buildMode decides before updateChannel, whose dev value is a placeholder',
 
 test('the nightly steady states each read as themselves', () => {
   const detail = (status: Parameters<typeof aboutUpdateStatusDetail>[0]) =>
-    aboutUpdateStatusDetail(status, copy);
+    aboutUpdateStatusDetail(status, copy, { isDevBuild: false });
 
   assert.equal(
     detail({
diff --git a/apps/desktop/src/main/__tests__/active-execution-boundary-read-model.test.ts b/apps/desktop/src/main/__tests__/active-execution-boundary-read-model.test.ts
index c2fbe9867d..bdf9b42987 100644
--- a/apps/desktop/src/main/__tests__/active-execution-boundary-read-model.test.ts
+++ b/apps/desktop/src/main/__tests__/active-execution-boundary-read-model.test.ts
@@ -278,7 +278,7 @@ describe('Boundary decisions notify the read model', () => {
   function handlersWithRecorder() {
     const boundaryChanges: string[] = [];
     const handlers = createAppShellSessionEventHandlers({
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
       activeIdRef: { current: 'session-a' },
       liveTurnBySessionRef: { current: {} },
       refreshMessages: async () => true,
diff --git a/apps/desktop/src/main/__tests__/health-center-copy.test.ts b/apps/desktop/src/main/__tests__/agent-graph-panel-copy.test.ts
similarity index 67%
copy from apps/desktop/src/main/__tests__/health-center-copy.test.ts
copy to apps/desktop/src/main/__tests__/agent-graph-panel-copy.test.ts
index e8f7a6aa5f..0ef254444c 100644
--- a/apps/desktop/src/main/__tests__/health-center-copy.test.ts
+++ b/apps/desktop/src/main/__tests__/agent-graph-panel-copy.test.ts
@@ -19,15 +19,12 @@
 
 import assert from 'node:assert/strict';
 import { test } from 'node:test';
-import { getHealthCenterCopy } from '../../renderer/locales/settings-health-copy.js';
+import { getAgentGraphPanelCopy } from '../../renderer/agent-graph-panel.js';
 
-test('labels blocker counts as global across filtered health views', () => {
-  assert.equal(
-    getHealthCenterCopy('zh').blockers.send(1, 6),
-    '全部健康信号中,1/6 条会阻塞发送',
-  );
-  assert.equal(
-    getHealthCenterCopy('en').blockers.send(1, 6),
-    'Across all health signals, 1 of 6 blocks sending',
-  );
+test('Traditional Chinese Agent Graph copy does not use Simplified fallbacks', () => {
+  const copy = getAgentGraphPanelCopy('zh-TW');
+  assert.equal(copy.loading, '正在讀取 Graph 狀態…');
+  assert.equal(copy.openSession, '開啟子任務');
+  assert.equal(copy.currentEpoch, '目前');
+  assert.equal(copy.status('active'), '執行中');
 });
diff --git a/apps/desktop/src/main/__tests__/command-palette-retired.test.ts b/apps/desktop/src/main/__tests__/command-palette-retired.test.ts
index 9df42247d3..46b8fff553 100644
--- a/apps/desktop/src/main/__tests__/command-palette-retired.test.ts
+++ b/apps/desktop/src/main/__tests__/command-palette-retired.test.ts
@@ -85,7 +85,7 @@ test('a stale in-memory default pointing at a retired connection is not testable
   assert.ok(!ids.includes('diag:test-default'));
 });
 
-for (const locale of ['en', 'zh'] as const) {
+for (const locale of ['en', 'zh-CN'] as const) {
   test(`${locale} static shortcut hints preserve both platform variants`, () => {
     const commands = buildCommandList({
       locale,
@@ -111,6 +111,6 @@ for (const locale of ['en', 'zh'] as const) {
       other: 'Ctrl+,',
     });
     assert.equal(byId.get('diag:copy-diagnostics')?.platformHint?.other, 'Ctrl+Shift+D · ' +
-      (locale === 'zh' ? '脱敏日志 · 仅写入剪贴板' : 'Redacted logs · clipboard only'));
+      (locale === 'zh-CN' ? '脱敏日志 · 仅写入剪贴板' : 'Redacted logs · clipboard only'));
   });
 }
diff --git a/apps/desktop/src/main/__tests__/computer-use-status-item-locale.test.ts b/apps/desktop/src/main/__tests__/computer-use-status-item-locale.test.ts
index cf3912e50e..3b2827c882 100644
--- a/apps/desktop/src/main/__tests__/computer-use-status-item-locale.test.ts
+++ b/apps/desktop/src/main/__tests__/computer-use-status-item-locale.test.ts
@@ -50,7 +50,7 @@ test('rebuilds an active Computer Use menu when its resolved locale changes', ()
   assert.deepEqual(menus.at(-1)?.map((row) => row.label), ['Stop Using Safari']);
 
   locale.observe(mergeSettings(createDefaultSettings(), {
-    personalization: { uiLocale: 'zh' },
+    personalization: { uiLocale: 'zh-CN' },
   }));
   assert.deepEqual(menus.at(-1)?.map((row) => row.label), ['停止操作 Safari']);
 
diff --git a/apps/desktop/src/main/__tests__/config-transfer-service.test.ts b/apps/desktop/src/main/__tests__/config-transfer-service.test.ts
index 2d15fb0eab..ebee2a46c0 100644
--- a/apps/desktop/src/main/__tests__/config-transfer-service.test.ts
+++ b/apps/desktop/src/main/__tests__/config-transfer-service.test.ts
@@ -104,6 +104,27 @@ describe('config-transfer-service', () => {
     assert.deepEqual(writtenMemory, ['# imported memory']);
   });
 
+  it('canonicalizes a legacy zh preference before the imported settings reach observers', async () => {
+    const { deps, updatedSettings } = makeDeps();
+    const bundle = {
+      schemaVersion: 1,
+      exportedAt: '',
+      appVersion: '0.1.0',
+      includedData: ['settings'] as const,
+      data: {
+        settings: {
+          personalization: { uiLocale: 'zh', displayName: 'Maka user' },
+        },
+      },
+    };
+
+    await applyConfigImport(bundle as any, 'skip', deps);
+
+    assert.deepEqual(updatedSettings, [
+      { personalization: { uiLocale: 'zh-CN', displayName: 'Maka user' } },
+    ]);
+  });
+
   it('reports a settings-carried proxy credential skipped by Host target binding', async () => {
     const { deps } = makeDeps({
       settingsStore: {
diff --git a/apps/desktop/src/main/__tests__/desktop-locale-authority.test.ts b/apps/desktop/src/main/__tests__/desktop-locale-authority.test.ts
index 1b6ae08f01..184049a523 100644
--- a/apps/desktop/src/main/__tests__/desktop-locale-authority.test.ts
+++ b/apps/desktop/src/main/__tests__/desktop-locale-authority.test.ts
@@ -24,15 +24,15 @@ import { createDesktopLocaleAuthority } from '../desktop-locale-authority.js';
 
 test('resolves explicit preferences and observes a mid-session settings change', async () => {
   let settings = mergeSettings(createDefaultSettings(), {
-    personalization: { uiLocale: 'zh' },
+    personalization: { uiLocale: 'zh-CN' },
   });
   const authority = createDesktopLocaleAuthority({
     readSettings: async () => settings,
     preferredSystemLanguages: () => ['en-US'],
   });
 
-  assert.equal(await authority.resolve(), 'zh');
-  assert.equal(authority.current(), 'zh');
+  assert.equal(await authority.resolve(), 'zh-CN');
+  assert.equal(authority.current(), 'zh-CN');
   settings = mergeSettings(settings, { personalization: { uiLocale: 'en' } });
   authority.observe(settings);
   assert.equal(authority.current(), 'en');
@@ -50,14 +50,14 @@ test('publishes only resolved-locale changes and allows listeners to detach', ()
     personalization: { uiLocale: 'en' },
   }));
   authority.observe(mergeSettings(createDefaultSettings(), {
-    personalization: { uiLocale: 'zh' },
+    personalization: { uiLocale: 'zh-CN' },
   }));
   unsubscribe();
   authority.observe(mergeSettings(createDefaultSettings(), {
     personalization: { uiLocale: 'en' },
   }));
 
-  assert.deepEqual(observed, ['zh']);
+  assert.deepEqual(observed, ['zh-CN']);
 });
 
 test('keeps automatic preference live against the current system fallback', async () => {
@@ -68,8 +68,10 @@ test('keeps automatic preference live against the current system fallback', asyn
   });
 
   assert.equal(await authority.resolve(), 'en');
+  languages = ['zh-TW'];
+  assert.equal(authority.current(), 'zh-TW');
   languages = ['zh-CN'];
-  assert.equal(authority.current(), 'zh');
+  assert.equal(authority.current(), 'zh-CN');
 });
 
 test('does not let a stale read replace a newer observed preference', async () => {
@@ -86,13 +88,13 @@ test('does not let a stale read replace a newer observed preference', async () =
 
   const read = authority.resolve();
   authority.observe(mergeSettings(createDefaultSettings(), {
-    personalization: { uiLocale: 'zh' },
+    personalization: { uiLocale: 'zh-CN' },
   }));
   release(createDefaultSettings());
 
-  assert.equal(await read, 'zh');
-  assert.equal(authority.current(), 'zh');
-  assert.deepEqual(observed, ['zh']);
+  assert.equal(await read, 'zh-CN');
+  assert.equal(authority.current(), 'zh-CN');
+  assert.deepEqual(observed, ['zh-CN']);
 });
 
 test('falls back to its current projection when settings cannot be read', async () => {
@@ -100,5 +102,38 @@ test('falls back to its current projection when settings cannot be read', async
     readSettings: async () => { throw new Error('unreadable'); },
     preferredSystemLanguages: () => ['zh-CN'],
   });
-  assert.equal(await authority.resolve(), 'zh');
+  assert.equal(await authority.resolve(), 'zh-CN');
+});
+
+test('resolves a delayed persisted preference before startup native copy is selected', async () => {
+  let release!: (settings: ReturnType<typeof createDefaultSettings>) => void;
+  const pending = new Promise<ReturnType<typeof createDefaultSettings>>((resolve) => {
+    release = resolve;
+  });
+  const authority = createDesktopLocaleAuthority({
+    readSettings: () => pending,
+    preferredSystemLanguages: () => ['en-US'],
+  });
+
+  const startupLocale = authority.resolve();
+  release(mergeSettings(createDefaultSettings(), {
+    personalization: { uiLocale: 'zh-TW' },
+  }));
+
+  assert.equal(await startupLocale, 'zh-TW');
+});
+
+test('never projects the legacy zh preference into native copy lookups', async () => {
+  const legacy = mergeSettings(createDefaultSettings(), {
+    personalization: { uiLocale: 'zh-CN' },
+  });
+  (legacy.personalization as { uiLocale: string }).uiLocale = 'zh';
+  const authority = createDesktopLocaleAuthority({
+    readSettings: async () => legacy,
+    preferredSystemLanguages: () => ['en-US'],
+  });
+
+  assert.equal(authority.observe(legacy), 'zh-CN');
+  assert.equal(authority.current(), 'zh-CN');
+  assert.equal(await authority.resolve(), 'zh-CN');
 });
diff --git a/apps/desktop/src/main/__tests__/desktop-native-copy.test.ts b/apps/desktop/src/main/__tests__/desktop-native-copy.test.ts
index f5678fc671..821ecb27e7 100644
--- a/apps/desktop/src/main/__tests__/desktop-native-copy.test.ts
+++ b/apps/desktop/src/main/__tests__/desktop-native-copy.test.ts
@@ -25,11 +25,12 @@ import {
 import { projectPickerTitle } from '../project-picker-copy.js';
 
 test('localizes native picker and client settings confirmation copy', () => {
-  assert.equal(projectPickerTitle('zh'), '添加项目');
+  assert.equal(projectPickerTitle('zh-CN'), '添加项目');
+  assert.equal(projectPickerTitle('zh-TW'), '新增專案');
   assert.deepEqual(
     clientSettingsConfirmation(
       [{ key: 'keepSystemAwake', current: false, next: true }],
-      'zh',
+      'zh-CN',
     ),
     {
       message: '允许 Maka 更新此客户端的设置吗?',
@@ -37,4 +38,15 @@ test('localizes native picker and client settings confirmation copy', () => {
       buttons: ['应用更改', '取消'],
     },
   );
+  assert.deepEqual(
+    clientSettingsConfirmation(
+      [{ key: 'theme', current: 'light', next: 'dark' }],
+      'zh-TW',
+    ),
+    {
+      message: '允許 Maka 更新此用戶端的設定嗎?',
+      detail: '主題: light → dark',
+      buttons: ['套用變更', '取消'],
+    },
+  );
 });
diff --git a/apps/desktop/src/main/__tests__/health-center-copy.test.ts b/apps/desktop/src/main/__tests__/e2e-fixture-locale.test.ts
similarity index 58%
copy from apps/desktop/src/main/__tests__/health-center-copy.test.ts
copy to apps/desktop/src/main/__tests__/e2e-fixture-locale.test.ts
index e8f7a6aa5f..cec94f10f1 100644
--- a/apps/desktop/src/main/__tests__/health-center-copy.test.ts
+++ b/apps/desktop/src/main/__tests__/e2e-fixture-locale.test.ts
@@ -19,15 +19,28 @@
 
 import assert from 'node:assert/strict';
 import { test } from 'node:test';
-import { getHealthCenterCopy } from '../../renderer/locales/settings-health-copy.js';
+import { resolveE2eFixture } from '../e2e-fixture.js';
 
-test('labels blocker counts as global across filtered health views', () => {
+test('preserves both canonical Chinese locale fixture flags', () => {
+  for (const locale of ['zh-CN', 'zh-TW', 'en'] as const) {
+    const fixture = resolveE2eFixture(
+      'settings-general',
+      false,
+      undefined,
+      undefined,
+      locale,
+    );
+    assert.equal(fixture?.locale, locale);
+  }
+});
+
+test('normalizes locale fixture flag casing without widening the contract', () => {
   assert.equal(
-    getHealthCenterCopy('zh').blockers.send(1, 6),
-    '全部健康信号中,1/6 条会阻塞发送',
+    resolveE2eFixture('settings-general', false, undefined, undefined, 'ZH-tw')?.locale,
+    'zh-TW',
   );
   assert.equal(
-    getHealthCenterCopy('en').blockers.send(1, 6),
-    'Across all health signals, 1 of 6 blocks sending',
+    resolveE2eFixture('settings-general', false, undefined, undefined, 'zh-Hant')?.locale,
+    null,
   );
 });
diff --git a/apps/desktop/src/main/__tests__/goal-arm-outcome.test.ts b/apps/desktop/src/main/__tests__/goal-arm-outcome.test.ts
index 09a2b13c11..da86114d6d 100644
--- a/apps/desktop/src/main/__tests__/goal-arm-outcome.test.ts
+++ b/apps/desktop/src/main/__tests__/goal-arm-outcome.test.ts
@@ -83,7 +83,7 @@ test('successful Goal arming closes while every reconciliation result locks the
 });
 
 test('Goal reconciliation copy explains authoritative state in Chinese and English', () => {
-  const zh = getShellCopy('zh').goalDialog;
+  const zh = getShellCopy('zh-CN').goalDialog;
   assert.match(
     zh.reconciledMatching('所有测试通过', zh.statusLabels.active),
     /所有测试通过.*进行中.*无法确认.*提交/,
diff --git a/apps/desktop/src/main/__tests__/health-center-copy.test.ts b/apps/desktop/src/main/__tests__/health-center-copy.test.ts
index e8f7a6aa5f..6ab57b2855 100644
--- a/apps/desktop/src/main/__tests__/health-center-copy.test.ts
+++ b/apps/desktop/src/main/__tests__/health-center-copy.test.ts
@@ -23,7 +23,7 @@ import { getHealthCenterCopy } from '../../renderer/locales/settings-health-copy
 
 test('labels blocker counts as global across filtered health views', () => {
   assert.equal(
-    getHealthCenterCopy('zh').blockers.send(1, 6),
+    getHealthCenterCopy('zh-CN').blockers.send(1, 6),
     '全部健康信号中,1/6 条会阻塞发送',
   );
   assert.equal(
@@ -31,3 +31,23 @@ test('labels blocker counts as global across filtered health views', () => {
     'Across all health signals, 1 of 6 blocks sending',
   );
 });
+
+test('Traditional Chinese health copy localizes structured signal text', () => {
+  const copy = getHealthCenterCopy('zh-TW');
+  const signal = {
+    id: 'connection:test',
+    label: '測試 运行态',
+    scope: 'llm_connection' as const,
+    layer: 'validation' as const,
+    status: 'ok' as const,
+    source: 'connection_test' as const,
+    checkedAt: 1,
+    message: '凭据与端点验证已通过。',
+    detail: '这是连接验证结果,不代表发送、流式输出或中断通路已经运行通过。',
+    blocksSend: false,
+  };
+  assert.equal(copy.layers.configuration.label, '設定');
+  assert.equal(copy.signalLabel(signal), '測試 執行狀態');
+  assert.equal(copy.signalMessage(signal), '憑證與端點驗證已通過。');
+  assert.match(copy.signalDetail(signal) ?? '', /串流輸出/);
+});
diff --git a/apps/desktop/src/main/__tests__/import-tasks-settings-page.test.ts b/apps/desktop/src/main/__tests__/import-tasks-settings-page.test.ts
index a62994ecaf..885cdcea20 100644
--- a/apps/desktop/src/main/__tests__/import-tasks-settings-page.test.ts
+++ b/apps/desktop/src/main/__tests__/import-tasks-settings-page.test.ts
@@ -136,7 +136,7 @@ describe('ImportTasksSettingsPage durable import state', () => {
 
   it('renders durable repeat-import state in Chinese', async () => {
     const harness = await renderPage({
-      locale: 'zh',
+      locale: 'zh-CN',
       catalog: catalog(
         externalSession({
           importState: {
@@ -1211,7 +1211,7 @@ async function renderPage(options: {
    */
   importBySource?: Record<string, 'ok' | 'unknown' | 'throw'>;
   onOpenImported?: (sessionId: string) => void;
-  locale?: 'en' | 'zh';
+  locale?: 'en' | 'zh-CN';
 }): Promise<{
   container: HTMLElement;
   root: Root;
diff --git a/apps/desktop/src/main/__tests__/message-queue-ui-state.test.ts b/apps/desktop/src/main/__tests__/message-queue-ui-state.test.ts
index ece5f859fc..1eddb50f65 100644
--- a/apps/desktop/src/main/__tests__/message-queue-ui-state.test.ts
+++ b/apps/desktop/src/main/__tests__/message-queue-ui-state.test.ts
@@ -27,7 +27,7 @@ test('queue_update events drive the independent desktop queue projection', () =>
   const transientMessages: unknown[] = [];
   const removedTransientMessageIds: string[] = [];
   const handlers = createAppShellSessionEventHandlers({
-    uiLocale: 'zh',
+    uiLocale: 'zh-CN',
     activeIdRef: { current: 'session-1' },
     liveTurnBySessionRef: controller.liveTurnBySessionRef,
     refreshMessages: async () => true,
diff --git a/apps/desktop/src/main/__tests__/new-task-staged-content.test.ts b/apps/desktop/src/main/__tests__/new-task-staged-content.test.ts
index ee1ca35b77..b0fa5ee43e 100644
--- a/apps/desktop/src/main/__tests__/new-task-staged-content.test.ts
+++ b/apps/desktop/src/main/__tests__/new-task-staged-content.test.ts
@@ -66,7 +66,7 @@ afterEach(async () => {
 
 async function mountProbe<T>(useHook: (options: { draftKey: string }) => T): Promise<{
   latest(): T;
-  render(draftKey: string, locale?: 'en' | 'zh'): Promise<void>;
+  render(draftKey: string, locale?: 'en' | 'zh-CN'): Promise<void>;
 }> {
   const { document, window } = parseHTML('<div id="root"></div>');
   Object.assign(globalThis, {
@@ -88,7 +88,7 @@ async function mountProbe<T>(useHook: (options: { draftKey: string }) => T): Pro
     return null;
   }
 
-  const render = async (draftKey: string, locale: 'en' | 'zh' = 'en') => {
+  const render = async (draftKey: string, locale: 'en' | 'zh-CN' = 'en') => {
     await act(async () => {
       root.render(
         createElement(LocaleProvider, {
@@ -275,16 +275,16 @@ test('AppShell composition shows the localized non-vision image notice once per
     description: en.imageAttachmentNotDirectDescription,
   }]);
 
-  await probe.render('session-zh', 'zh');
+  await probe.render('session-zh', 'zh-CN');
   await act(() => probe.latest().attachFilePaths([image]));
-  const zh = getDesktopConversationCopy('zh').actions;
+  const zh = getDesktopConversationCopy('zh-CN').actions;
   assert.deepEqual(calls[1], {
     title: zh.imageAttachmentNotDirectTitle,
     description: zh.imageAttachmentNotDirectDescription,
   });
 
   probe.latest().imageNoticeLifecycle.transfer('session-zh', 'session-transferred');
-  await probe.render('session-transferred', 'zh');
+  await probe.render('session-transferred', 'zh-CN');
   await act(() => probe.latest().attachFilePaths([image]));
   assert.equal(calls.length, 2);
 
diff --git a/apps/desktop/src/main/__tests__/notifications-policy.test.ts b/apps/desktop/src/main/__tests__/notifications-policy.test.ts
index fde041c2b9..a20d0c1300 100644
--- a/apps/desktop/src/main/__tests__/notifications-policy.test.ts
+++ b/apps/desktop/src/main/__tests__/notifications-policy.test.ts
@@ -48,38 +48,43 @@ it('recognizes terminal kinds and keeps distinct localized fallback copy', () =>
     assert.equal(isRunNotificationKind(value), false);
   }
 
-  const completed = runNotificationCopy('completed', 'zh');
-  const errored = runNotificationCopy('errored', 'zh');
+  const completed = runNotificationCopy('completed', 'zh-CN');
+  const errored = runNotificationCopy('errored', 'zh-CN');
   assert.ok(completed.title && completed.body);
   assert.ok(errored.title && errored.body);
   assert.notEqual(completed.title, errored.title);
+
+  assert.deepEqual(runNotificationCopy('completed', 'zh-TW'), {
+    title: '回答已產生',
+    body: 'Maka 已完成本次回答,按一下以檢視。',
+  });
 });
 
 it('sanitizes renderer content, caps it, and falls back per field', () => {
   const clean = resolveNotificationContent(
     { kind: 'completed', title: '  会话  A  ', body: 'line one\n\nline two\tindented' },
-    'zh',
+    'zh-CN',
   );
   assert.deepEqual(clean, { title: '会话 A', body: 'line one line two indented' });
 
-  const completedFallback = runNotificationCopy('completed', 'zh');
+  const completedFallback = runNotificationCopy('completed', 'zh-CN');
   for (const value of ['', '   ', undefined, null, 42, {}]) {
     assert.deepEqual(
-      resolveNotificationContent({ kind: 'completed', title: value, body: value }, 'zh'),
+      resolveNotificationContent({ kind: 'completed', title: value, body: value }, 'zh-CN'),
       completedFallback,
     );
   }
 
   const capped = resolveNotificationContent(
     { kind: 'completed', title: 'S', body: 'x'.repeat(500) },
-    'zh',
+    'zh-CN',
   );
   assert.equal(capped.body.length, 160);
   assert.ok(capped.body.endsWith('…'));
 
-  const erroredFallback = runNotificationCopy('errored', 'zh');
+  const erroredFallback = runNotificationCopy('errored', 'zh-CN');
   assert.deepEqual(
-    resolveNotificationContent({ kind: 'errored', title: '出错的会话', body: '' }, 'zh'),
+    resolveNotificationContent({ kind: 'errored', title: '出错的会话', body: '' }, 'zh-CN'),
     { title: '出错的会话', body: erroredFallback.body },
   );
 });
diff --git a/apps/desktop/src/main/__tests__/permission-center-copy.test.ts b/apps/desktop/src/main/__tests__/permission-center-copy.test.ts
index 6333051056..318b15a877 100644
--- a/apps/desktop/src/main/__tests__/permission-center-copy.test.ts
+++ b/apps/desktop/src/main/__tests__/permission-center-copy.test.ts
@@ -22,6 +22,6 @@ import { test } from 'node:test';
 import { getPermissionCenterCopy } from '../../renderer/locales/permission-center-copy.js';
 
 test('presents a granted OS permission as a verified success', () => {
-  assert.equal(getPermissionCenterCopy('zh').osStates.granted.tone, 'success');
+  assert.equal(getPermissionCenterCopy('zh-CN').osStates.granted.tone, 'success');
   assert.equal(getPermissionCenterCopy('en').osStates.granted.tone, 'success');
 });
diff --git a/apps/desktop/src/main/__tests__/plan-mode-copy.test.ts b/apps/desktop/src/main/__tests__/plan-mode-copy.test.ts
index fbe73bb661..af8cc19329 100644
--- a/apps/desktop/src/main/__tests__/plan-mode-copy.test.ts
+++ b/apps/desktop/src/main/__tests__/plan-mode-copy.test.ts
@@ -22,7 +22,7 @@ import { test } from 'node:test';
 import { getPlanModeCopy } from '../../renderer/locales/plan-mode-copy.js';
 
 test('localizes Plan Mode chrome and abandon confirmation without rewriting plan content', () => {
-  const zh = getPlanModeCopy('zh');
+  const zh = getPlanModeCopy('zh-CN');
   const en = getPlanModeCopy('en');
 
   assert.equal(zh.proposal.statuses.approved, '已批准');
diff --git a/apps/desktop/src/main/__tests__/provider-connection-status.test.ts b/apps/desktop/src/main/__tests__/provider-connection-status.test.ts
index a0429cb72f..f6d1e41658 100644
--- a/apps/desktop/src/main/__tests__/provider-connection-status.test.ts
+++ b/apps/desktop/src/main/__tests__/provider-connection-status.test.ts
@@ -49,7 +49,7 @@ test('a retired connection reads as broken rather than repairable', () => {
   // Nothing else in the list marks this row, so without a status the only
   // signal that it has to go is on the detail page the user has no reason to
   // open.
-  assert.deepEqual(connectionChipStatus(retired, 'zh'), {
+  assert.deepEqual(connectionChipStatus(retired, 'zh-CN'), {
     label: '已停用 · 请删除',
     tone: 'error',
   });
@@ -69,7 +69,7 @@ test('retirement outranks every repairable state', () => {
     { enabled: false },
   ]) {
     assert.deepEqual(
-      connectionChipStatus({ ...retired, ...overrides }, 'zh'),
+      connectionChipStatus({ ...retired, ...overrides }, 'zh-CN'),
       { label: '已停用 · 请删除', tone: 'error' },
       `retirement must win over ${JSON.stringify(overrides)}`,
     );
@@ -77,12 +77,12 @@ test('retirement outranks every repairable state', () => {
 });
 
 test('a live connection keeps its existing statuses', () => {
-  assert.equal(connectionChipStatus(connection({ lastTestStatus: 'verified' }), 'zh'), null);
-  assert.deepEqual(connectionChipStatus(connection({ lastTestStatus: 'needs_reauth' }), 'zh'), {
+  assert.equal(connectionChipStatus(connection({ lastTestStatus: 'verified' }), 'zh-CN'), null);
+  assert.deepEqual(connectionChipStatus(connection({ lastTestStatus: 'needs_reauth' }), 'zh-CN'), {
     label: '需要重新登录',
     tone: 'attention',
   });
-  assert.deepEqual(connectionChipStatus(connection({ enabled: false }), 'zh'), {
+  assert.deepEqual(connectionChipStatus(connection({ enabled: false }), 'zh-CN'), {
     label: '暂不可用',
     tone: 'neutral',
   });
diff --git a/apps/desktop/src/main/__tests__/renderer-error-report.test.ts b/apps/desktop/src/main/__tests__/renderer-error-report.test.ts
index 367e118023..530f26b45c 100644
--- a/apps/desktop/src/main/__tests__/renderer-error-report.test.ts
+++ b/apps/desktop/src/main/__tests__/renderer-error-report.test.ts
@@ -64,7 +64,7 @@ test('bounds and redacts the browser-only renderer error report', () => {
 
 test('keeps raw exceptions out of the localized crash surface', () => {
   for (const [locale, title] of [
-    ['zh', 'Maka 渲染层崩溃了'],
+    ['zh-CN', 'Maka 渲染层崩溃了'],
     ['en', 'The Maka renderer crashed'],
   ] as const) {
     const markup = renderToStaticMarkup(
diff --git a/apps/desktop/src/main/__tests__/health-center-copy.test.ts b/apps/desktop/src/main/__tests__/runtime-host-boot-copy.test.ts
similarity index 65%
copy from apps/desktop/src/main/__tests__/health-center-copy.test.ts
copy to apps/desktop/src/main/__tests__/runtime-host-boot-copy.test.ts
index e8f7a6aa5f..22aa914054 100644
--- a/apps/desktop/src/main/__tests__/health-center-copy.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-boot-copy.test.ts
@@ -19,15 +19,12 @@
 
 import assert from 'node:assert/strict';
 import { test } from 'node:test';
-import { getHealthCenterCopy } from '../../renderer/locales/settings-health-copy.js';
+import { getStartupRecoveryCopy } from '../runtime-host-boot-copy.js';
 
-test('labels blocker counts as global across filtered health views', () => {
-  assert.equal(
-    getHealthCenterCopy('zh').blockers.send(1, 6),
-    '全部健康信号中,1/6 条会阻塞发送',
-  );
-  assert.equal(
-    getHealthCenterCopy('en').blockers.send(1, 6),
-    'Across all health signals, 1 of 6 blocks sending',
-  );
+test('startup recovery copy preserves Traditional Chinese', () => {
+  const copy = getStartupRecoveryCopy('zh-TW');
+  assert.equal(copy.storageRoot.title, 'Maka 工作區需要修復');
+  assert.match(copy.storageRoot.detail('C:\\Maka'), /磁碟識別碼/);
+  assert.equal(copy.runtimeHost.title, '預設 Runtime Host 無法連線');
+  assert.match(copy.runtimeHost.detail('失敗'), /設定中處理/);
 });
diff --git a/apps/desktop/src/main/__tests__/runtime-host-quit-copy.test.ts b/apps/desktop/src/main/__tests__/runtime-host-quit-copy.test.ts
index 0b03b5a540..1c03be70d7 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-quit-copy.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-quit-copy.test.ts
@@ -41,7 +41,7 @@ const manualFailure = new DesktopLocalHostRetirementError(
   { cause: failure.cause },
 );
 
-for (const locale of ['en', 'zh'] as const) {
+for (const locale of ['en', 'zh-CN'] as const) {
   test(`quit failure copy exposes actionable Host facts in ${locale}`, () => {
     const dialog = buildRuntimeHostQuitFailureDialog(manualFailure, locale);
 
@@ -54,7 +54,7 @@ for (const locale of ['en', 'zh'] as const) {
 
 test('manual recovery copy names a cross-platform process-management concept', () => {
   const english = buildRuntimeHostQuitFailureDialog(manualFailure, 'en').options.detail ?? '';
-  const chinese = buildRuntimeHostQuitFailureDialog(manualFailure, 'zh').options.detail ?? '';
+  const chinese = buildRuntimeHostQuitFailureDialog(manualFailure, 'zh-CN').options.detail ?? '';
 
   assert.match(english, /operating system's process-management tool/);
   assert.match(chinese, /操作系统的进程管理工具/);
diff --git a/apps/desktop/src/main/__tests__/runtime-host-upgrade-dialog.test.ts b/apps/desktop/src/main/__tests__/runtime-host-upgrade-dialog.test.ts
index fa1da71958..2c4abd86c1 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-upgrade-dialog.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-upgrade-dialog.test.ts
@@ -48,7 +48,12 @@ test('localizes upgrade activity without changing decision indexes', () => {
   const zh = buildRuntimeHostUpgradeDialog(
     conflict,
     'restart',
-    'zh',
+    'zh-CN',
+  ).options;
+  const zhTw = buildRuntimeHostUpgradeDialog(
+    conflict,
+    'restart',
+    'zh-TW',
   ).options;
   assert.deepEqual(en.buttons, ['Restart Runtime Host', 'Wait', 'Cancel Startup']);
   assert.deepEqual(zh.buttons, ['重启 Runtime Host', '等待', '取消启动']);
@@ -58,6 +63,9 @@ test('localizes upgrade activity without changing decision indexes', () => {
   assert.match(zh.detail ?? '', /每日回顾: 1/);
   assert.match(en.detail ?? '', /Scheduled Task: 2/);
   assert.match(zh.detail ?? '', /计划任务: 2/);
+  assert.deepEqual(zhTw.buttons, ['重啟 Runtime Host', '等待', '取消啟動']);
+  assert.match(zhTw.detail ?? '', /仍有 2 個其他客戶端連線/);
+  assert.match(zhTw.detail ?? '', /每日回顧: 1/);
   assert.match(en.detail ?? '', /Process ID \(PID\):/);
 });
 
@@ -148,7 +156,7 @@ test('explains when the safe replacement check could not verify idle state', ()
   const dialog = buildRuntimeHostUpgradeDialog(
     conflict,
     'replace_may_interrupt_work',
-    'zh',
+    'zh-CN',
   );
 
   assert.match(dialog.options.detail ?? '', /无法确认此 Host 是否处于空闲状态/u);
diff --git a/apps/desktop/src/main/__tests__/session-inspector-context-format.test.ts b/apps/desktop/src/main/__tests__/session-inspector-context-format.test.ts
index b8fd943106..410f265169 100644
--- a/apps/desktop/src/main/__tests__/session-inspector-context-format.test.ts
+++ b/apps/desktop/src/main/__tests__/session-inspector-context-format.test.ts
@@ -22,7 +22,7 @@ import { test } from 'node:test';
 import { compactNumberFormatter } from '../../renderer/features/workbar/testing.js';
 
 test('formats context-window capacity with stable K/M units in every UI locale', () => {
-  for (const locale of ['en', 'zh'] as const) {
+  for (const locale of ['en', 'zh-CN', 'zh-TW'] as const) {
     const format = compactNumberFormatter(locale);
 
     assert.equal(format(256_000), '256K');
diff --git a/apps/desktop/src/main/__tests__/session-status-presentation.test.ts b/apps/desktop/src/main/__tests__/session-status-presentation.test.ts
index 7f2473c70f..133ad3e7da 100644
--- a/apps/desktop/src/main/__tests__/session-status-presentation.test.ts
+++ b/apps/desktop/src/main/__tests__/session-status-presentation.test.ts
@@ -33,7 +33,8 @@ const NOTHING_RAN = {
 
 describe('failed turn presentation', () => {
   it('presents persisted provider server errors as provider failures', () => {
-    assert.match(describeTurnErrorClass('server_error', 'zh'), /模型服务返回错误/);
+    assert.match(describeTurnErrorClass('server_error', 'zh-CN'), /模型服务返回错误/);
+    assert.match(describeTurnErrorClass('server_error', 'zh-TW'), /模型服務回傳錯誤/);
     assert.match(describeTurnErrorClass('server_error', 'en'), /model service returned an error/i);
   });
 
@@ -41,7 +42,7 @@ describe('failed turn presentation', () => {
     // The banner offers a button only for `app_restarted`; every other class
     // has to point at the one action that always exists — send a message.
     for (const errorClass of ['rate_limit', 'network', 'timeout', 'unknown_failure']) {
-      assert.match(describeTurnErrorClass(errorClass, 'zh'), /重新发消息|再发消息|发消息/);
+      assert.match(describeTurnErrorClass(errorClass, 'zh-CN'), /重新发消息|再发消息|发消息/);
     }
   });
 
@@ -59,7 +60,7 @@ describe('failed turn execution state', () => {
   it('warns that a completed tool may already have taken effect', () => {
     // A blind resend after a side-effecting tool can repeat that effect, so
     // this has to survive alongside a transport failure like `timeout`.
-    const zh = describeFailedTurnExecutionState({ ...NOTHING_RAN, toolActivityCount: 1 }, 'zh');
+    const zh = describeFailedTurnExecutionState({ ...NOTHING_RAN, toolActivityCount: 1 }, 'zh-CN');
     assert.match(zh ?? '', /执行过工具|实际改动/);
     const en = describeFailedTurnExecutionState({ ...NOTHING_RAN, toolActivityCount: 1 }, 'en');
     assert.match(en ?? '', /tools already ran/i);
@@ -70,26 +71,27 @@ describe('failed turn execution state', () => {
     // the tool branch win, so `auth` plus an errored tool advised "inspect the
     // tool result" and dropped the sign-in step. They are separate slots now.
     const state = { ...NOTHING_RAN, toolActivityCount: 1, erroredToolCount: 1 };
-    assert.match(describeTurnErrorClass('auth', 'zh'), /重新连接或登录/);
-    assert.match(describeFailedTurnExecutionState(state, 'zh') ?? '', /工具执行出错/);
-    assert.match(describeTurnErrorClass('context_overflow', 'zh'), /减少附件|开启新任务/);
+    assert.match(describeTurnErrorClass('auth', 'zh-CN'), /重新连接或登录/);
+    assert.match(describeFailedTurnExecutionState(state, 'zh-CN') ?? '', /工具执行出错/);
+    assert.match(describeTurnErrorClass('context_overflow', 'zh-CN'), /减少附件|开启新任务/);
+    assert.match(describeFailedTurnExecutionState(state, 'zh-TW') ?? '', /工具執行出錯/);
   });
 
   it('reports nothing when the turn left nothing to re-read', () => {
-    assert.equal(describeFailedTurnExecutionState(NOTHING_RAN, 'zh'), undefined);
+    assert.equal(describeFailedTurnExecutionState(NOTHING_RAN, 'zh-CN'), undefined);
   });
 
   it('prefers the most specific state the turn reached', () => {
     const all = { partialOutputRetained: true, toolActivityCount: 2, erroredToolCount: 1 };
-    assert.match(describeFailedTurnExecutionState(all, 'zh') ?? '', /工具执行出错/);
+    assert.match(describeFailedTurnExecutionState(all, 'zh-CN') ?? '', /工具执行出错/);
     assert.match(
-      describeFailedTurnExecutionState({ ...all, erroredToolCount: 0 }, 'zh') ?? '',
+      describeFailedTurnExecutionState({ ...all, erroredToolCount: 0 }, 'zh-CN') ?? '',
       /执行过工具/,
     );
     assert.match(
       describeFailedTurnExecutionState(
         { ...NOTHING_RAN, partialOutputRetained: true },
-        'zh',
+        'zh-CN',
       ) ?? '',
       /部分回答/,
     );
diff --git a/apps/desktop/src/main/__tests__/settings-preferences-copy.test.ts b/apps/desktop/src/main/__tests__/settings-preferences-copy.test.ts
new file mode 100644
index 0000000000..c0c5a05203
--- /dev/null
+++ b/apps/desktop/src/main/__tests__/settings-preferences-copy.test.ts
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import assert from 'node:assert/strict';
+import { test } from 'node:test';
+import { getSettingsPreferencesCopy } from '../../renderer/locales/settings-preferences-copy.js';
+import { getMcpCatalog } from '../../renderer/mcp-catalog.js';
+import { providerDisplay } from '../../renderer/settings/provider-display-copy.js';
+import { getBotSettingsCopy } from '../../renderer/locales/settings-bot-copy.js';
+
+test('language selector offers every preference with locale-appropriate labels', () => {
+  assert.deepEqual(getSettingsPreferencesCopy('zh-CN').personalization.localeOptions, [
+    ['auto', '跟随系统'],
+    ['zh-CN', '简体中文'],
+    ['zh-TW', '繁體中文'],
+    ['en', 'English'],
+  ]);
+  assert.deepEqual(getSettingsPreferencesCopy('zh-TW').personalization.localeOptions, [
+    ['auto', '自動(跟隨系統)'],
+    ['zh-CN', '简体中文'],
+    ['zh-TW', '繁體中文'],
+    ['en', 'English'],
+  ]);
+  assert.deepEqual(getSettingsPreferencesCopy('en').personalization.localeOptions, [
+    ['auto', 'Follow system'],
+    ['zh-CN', 'Simplified Chinese'],
+    ['zh-TW', 'Traditional Chinese'],
+    ['en', 'English'],
+  ]);
+});
+
+test('Traditional Chinese settings copy uses Taiwan terminology', () => {
+  const copy = getSettingsPreferencesCopy('zh-TW');
+  assert.equal(copy.sections.network, '網路');
+  assert.equal(copy.appearance.paletteLabels.default, '預設');
+  assert.equal(copy.appearance.appIconImport, '匯入圖示…');
+  assert.equal(copy.about.clipboardUnavailable, '剪貼簿不可用或被系統拒絕。');
+});
+
+test('Traditional Chinese MCP catalog does not fall back to Simplified Chinese', () => {
+  const catalog = getMcpCatalog('zh-TW');
+  assert.equal(catalog.find((entry) => entry.id === 'filesystem')?.name, '本機檔案');
+  assert.equal(catalog.find((entry) => entry.id === 'google-calendar')?.name, 'Google 日曆');
+  assert.equal(catalog.find((entry) => entry.id === 'playwright')?.category, '設計與開發');
+});
+
+test('Traditional Chinese provider cards use Taiwan connection terminology', () => {
+  assert.equal(providerDisplay('deepseek', 'zh-TW').description, 'DeepSeek 官方 API 連線');
+  assert.match(providerDisplay('github-copilot', 'zh-TW').description, /訂閱連線/);
+});
+
+test('Traditional Chinese bot settings use Taiwan integration terminology', () => {
+  const copy = getBotSettingsCopy('zh-TW');
+  assert.match(copy.providers.wecom.help, /串接/);
+  assert.doesNotMatch(copy.providers.wecom.help, /接入/);
+  assert.match(copy.overview.more, /串接更多/);
+  assert.doesNotMatch(copy.overview.more, /渠道|訪問|匹配/);
+});
diff --git a/apps/desktop/src/main/__tests__/settings-test-result-copy.test.ts b/apps/desktop/src/main/__tests__/settings-test-result-copy.test.ts
index 757ced374c..9f86548727 100644
--- a/apps/desktop/src/main/__tests__/settings-test-result-copy.test.ts
+++ b/apps/desktop/src/main/__tests__/settings-test-result-copy.test.ts
@@ -29,7 +29,7 @@ test("missing proxy credentials have actionable bilingual copy", () => {
   } as never;
 
   assert.equal(
-    settingsTestResultMessage(result, "zh"),
+    settingsTestResultMessage(result, "zh-CN"),
     "代理认证已开启,请输入代理密码后再测试。",
   );
   assert.equal(
diff --git a/apps/desktop/src/main/__tests__/streaming-handoff.test.ts b/apps/desktop/src/main/__tests__/streaming-handoff.test.ts
index 16e8f1de69..1f910c38f5 100644
--- a/apps/desktop/src/main/__tests__/streaming-handoff.test.ts
+++ b/apps/desktop/src/main/__tests__/streaming-handoff.test.ts
@@ -39,7 +39,7 @@ import { waitFor as pollFor } from '@maka/core/test-only/async-primitives';
 function renderWithLocale(child: ReactNode): string {
   return renderToStaticMarkup(
     createElement(LocaleProvider, {
-      locale: 'zh',
+      locale: 'zh-CN',
       children: createElement(ChatSurfaceLayout, { composer: null, children: child }),
     }),
   );
@@ -303,7 +303,7 @@ describe('single live-turn handoff', () => {
       liveTurnBySessionRef.current = liveTurns.get();
     };
     const handlers = createAppShellSessionEventHandlers({
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
       activeIdRef: { current: 'session-1' },
       liveTurnBySessionRef,
       refreshMessages: async (sessionId, options) => {
@@ -356,7 +356,7 @@ describe('single live-turn handoff', () => {
     const frames: Array<() => void> = [];
     let publications = 0;
     const handlers = createAppShellSessionEventHandlers({
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
       activeIdRef: { current: 'session-1' },
       liveTurnBySessionRef,
       refreshMessages: async () => true,
@@ -410,7 +410,7 @@ describe('single live-turn handoff', () => {
     const displayBatch = createAppShellSessionDisplayBatch();
     let publications = 0;
     const handlers = createAppShellSessionEventHandlers({
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
       activeIdRef: { current: 'session-1' },
       liveTurnBySessionRef,
       refreshMessages: async () => true,
@@ -477,7 +477,7 @@ describe('single live-turn handoff', () => {
     const interactions = createStateSetter<InteractionQueues>({});
     const frames: Array<() => void> = [];
     const handlers = createAppShellSessionEventHandlers({
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
       activeIdRef: { current: 'session-1' },
       liveTurnBySessionRef,
       refreshMessages: async () => true,
@@ -515,7 +515,7 @@ describe('single live-turn handoff', () => {
     const frames: Array<() => void> = [];
     let publications = 0;
     const handlers = createAppShellSessionEventHandlers({
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
       activeIdRef: { current: 'session-1' },
       liveTurnBySessionRef,
       refreshMessages: async () => true,
@@ -578,7 +578,7 @@ describe('single live-turn handoff', () => {
     const displayBatch = createAppShellSessionDisplayBatch();
     let publications = 0;
     const deps = {
-      uiLocale: 'zh' as const,
+      uiLocale: 'zh-CN' as const,
       activeIdRef: { current: 'session-1' },
       liveTurnBySessionRef,
       refreshMessages: async () => true,
@@ -625,7 +625,7 @@ describe('single live-turn handoff', () => {
       ref.current = liveTurns.get();
     };
     const handlers = createAppShellSessionEventHandlers({
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
       activeIdRef: { current: 'session-1' },
       liveTurnBySessionRef: ref,
       refreshMessages: async () => true,
@@ -724,7 +724,7 @@ describe('single live-turn handoff', () => {
       resolveRefresh = resolve;
     });
     const handlers = createAppShellSessionEventHandlers({
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
       activeIdRef: { current: 'session-1' },
       liveTurnBySessionRef: ref,
       refreshMessages: async () => refresh,
@@ -773,7 +773,7 @@ describe('single live-turn handoff', () => {
       | { sessionId: string; turnId: string; eventId: string }
       | undefined;
     const handlers = createAppShellSessionEventHandlers({
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
       activeIdRef: { current: 'session-1' },
       liveTurnBySessionRef: ref,
       refreshMessages: async () => false,
@@ -846,7 +846,7 @@ describe('single live-turn handoff', () => {
     const ref = { current: liveTurns.get() };
     const interactions = createStateSetter<InteractionQueues>({});
     const handlers = createAppShellSessionEventHandlers({
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
       activeIdRef: { current: 'session-1' },
       liveTurnBySessionRef: ref,
       refreshMessages: async () => true,
@@ -886,7 +886,7 @@ describe('single live-turn handoff', () => {
       resolveRefresh = resolve;
     });
     const handlers = createAppShellSessionEventHandlers({
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
       activeIdRef: { current: 'session-1' },
       liveTurnBySessionRef: ref,
       refreshMessages: async () => refresh,
diff --git a/apps/desktop/src/main/__tests__/health-center-copy.test.ts b/apps/desktop/src/main/__tests__/subscription-result-message.test.ts
similarity index 69%
copy from apps/desktop/src/main/__tests__/health-center-copy.test.ts
copy to apps/desktop/src/main/__tests__/subscription-result-message.test.ts
index e8f7a6aa5f..843717e9f0 100644
--- a/apps/desktop/src/main/__tests__/health-center-copy.test.ts
+++ b/apps/desktop/src/main/__tests__/subscription-result-message.test.ts
@@ -19,15 +19,15 @@
 
 import assert from 'node:assert/strict';
 import { test } from 'node:test';
-import { getHealthCenterCopy } from '../../renderer/locales/settings-health-copy.js';
+import { subscriptionResultMessage } from '../../renderer/features/connection-settings/index.js';
 
-test('labels blocker counts as global across filtered health views', () => {
+test('Traditional Chinese subscription errors reject raw Simplified backend copy', () => {
   assert.equal(
-    getHealthCenterCopy('zh').blockers.send(1, 6),
-    '全部健康信号中,1/6 条会阻塞发送',
+    subscriptionResultMessage('GitHub Copilot 登录已过期。', '請重新登入。', 'zh-TW'),
+    '請重新登入。',
   );
   assert.equal(
-    getHealthCenterCopy('en').blockers.send(1, 6),
-    'Across all health signals, 1 of 6 blocks sending',
+    subscriptionResultMessage('network unreachable', '請重新登入。', 'zh-TW'),
+    '網路錯誤',
   );
 });
diff --git a/apps/desktop/src/main/__tests__/task-readiness-notice.test.ts b/apps/desktop/src/main/__tests__/task-readiness-notice.test.ts
index 5fb9c4dd3f..5937431acd 100644
--- a/apps/desktop/src/main/__tests__/task-readiness-notice.test.ts
+++ b/apps/desktop/src/main/__tests__/task-readiness-notice.test.ts
@@ -63,11 +63,25 @@ test('confirmed repair and unavailable states block, while loading uncertainty d
 
 test('model blockers stay owned by existing connection recovery surfaces', () => {
   assert.equal(
-    deriveTaskReadinessNotice(snapshot('repair_required', 'model_target'), 'zh'),
+    deriveTaskReadinessNotice(snapshot('repair_required', 'model_target'), 'zh-CN'),
     undefined,
   );
 });
 
+test('Traditional Chinese readiness notices use Taiwan terminology', () => {
+  assert.deepEqual(deriveTaskReadinessNotice(snapshot('unavailable', 'runtime'), 'zh-TW'), {
+    tone: 'destructive',
+    title: 'Maka 執行服務暫時無法使用。',
+    description: '任務尚未送出。重新檢查執行服務後再試。',
+    actionLabel: '重新檢查',
+    action: 'retry',
+  });
+  assert.equal(
+    deriveTaskReadinessNotice(snapshot('repair_required', 'workspace'), 'zh-TW')?.actionLabel,
+    '選擇工作區',
+  );
+});
+
 function snapshot(
   state: TaskSubmissionReadinessSnapshot['state'],
   id: 'runtime' | 'model_target' | 'workspace',
diff --git a/apps/desktop/src/main/__tests__/traditional-chinese-peer-mesh-copy.test.ts b/apps/desktop/src/main/__tests__/traditional-chinese-peer-mesh-copy.test.ts
new file mode 100644
index 0000000000..67af56d965
--- /dev/null
+++ b/apps/desktop/src/main/__tests__/traditional-chinese-peer-mesh-copy.test.ts
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import assert from 'node:assert/strict';
+import { test } from 'node:test';
+import { getRuntimeHostPeerMeshCopy } from '../../renderer/features/runtime-host-management/index.js';
+import { getDesktopConversationCopy } from '../../renderer/locales/conversation-copy.js';
+import { getProviderSettingsCopy } from '../../renderer/features/connection-settings/index.js';
+import { settingsTestResultMessage } from '../../renderer/locales/settings-test-result-copy.js';
+
+test('Traditional Chinese Peer Mesh copy does not use the Simplified Chinese branch', () => {
+  const copy = getRuntimeHostPeerMeshCopy('zh-TW');
+
+  assert.equal(copy.experimental, '實驗性');
+  assert.equal(copy.invalidResult, 'Peer Mesh 回傳了無效結果');
+  assert.equal(copy.copyPeerId('peer-1'), '複製完整 Peer ID:peer-1');
+  assert.equal(copy.peerIdCopyFailed, '無法複製 Peer ID');
+  assert.equal(copy.peerPathDirect, '直接連線');
+  assert.equal(copy.peerPathTransit, '成員轉送');
+  assert.equal(copy.peerPathOther, '其他');
+  assert.equal(copy.joinHint, '貼上另一個 Peer 產生的一次性邀請碼。');
+});
+
+test('Traditional Chinese connection copy uses 回傳, 回應, and 發送', () => {
+  const conversation = getDesktopConversationCopy('zh-TW');
+  assert.equal(conversation.quoteCompanion.errors.respondFailed, '回應失敗,請稍後重試。');
+  assert.match(conversation.health.reauth.tooltip, /回傳鑑權失敗/);
+  assert.match(conversation.health.reauth.tooltip, /攔截發送/);
+  assert.match(conversation.health.testError.tooltip, /攔截發送/);
+  assert.match(conversation.turnError.provider, /模型服務回傳錯誤/);
+
+  const provider = getProviderSettingsCopy('zh-TW');
+  assert.equal(provider.shared.lastTest['模型服务返回错误'], '模型服務回傳錯誤');
+  assert.equal(provider.shared.lastTest['provider returned an error'], '模型服務回傳錯誤');
+
+  assert.equal(
+    settingsTestResultMessage(
+      { ok: false, code: 'proxy_http_error', message: '', details: { status: 502 } },
+      'zh-TW',
+    ),
+    '代理測試回傳 HTTP 502,請檢查代理服務或測試地址。',
+  );
+});
diff --git a/apps/desktop/src/main/__tests__/ui-locale-update-gate.test.ts b/apps/desktop/src/main/__tests__/ui-locale-update-gate.test.ts
index 87ff3c2f8f..edb1b7acfd 100644
--- a/apps/desktop/src/main/__tests__/ui-locale-update-gate.test.ts
+++ b/apps/desktop/src/main/__tests__/ui-locale-update-gate.test.ts
@@ -48,8 +48,8 @@ describe('UI locale settings update gate', () => {
     const savedLocales: string[] = [];
 
     assert.equal(gate.commit(firstLocaleTicket, 'en', (next) => savedLocales.push(next)), false);
-    assert.equal(gate.commit(latestLocaleTicket, 'zh', (next) => savedLocales.push(next)), true);
-    assert.deepEqual(savedLocales, ['zh']);
+    assert.equal(gate.commit(latestLocaleTicket, 'zh-CN', (next) => savedLocales.push(next)), true);
+    assert.deepEqual(savedLocales, ['zh-CN']);
   });
 
   it('delivers the persisted auto preference without resolving it locally', () => {
@@ -71,7 +71,7 @@ describe('UI locale settings update gate', () => {
 
     assert.equal(gate.commit(saveTicket, 'en', (next) => applied.push(next)), true);
     assert.equal(
-      gate.commitHydration(hydration, 'zh', (next) => applied.push(next)),
+      gate.commitHydration(hydration, 'zh-CN', (next) => applied.push(next)),
       false,
     );
     assert.deepEqual(applied, ['en']);
@@ -84,7 +84,7 @@ describe('UI locale settings update gate', () => {
     const applied: string[] = [];
 
     assert.equal(
-      gate.commitHydration(hydration, 'zh', (next) => applied.push(next)),
+      gate.commitHydration(hydration, 'zh-CN', (next) => applied.push(next)),
       false,
     );
     assert.equal(gate.commit(saveTicket, 'en', (next) => applied.push(next)), true);
@@ -102,7 +102,7 @@ describe('UI locale settings update gate', () => {
       true,
     );
     assert.equal(
-      appShellGate.commit(firstSurfaceTicket, 'zh', (next) => applied.push(next)),
+      appShellGate.commit(firstSurfaceTicket, 'zh-CN', (next) => applied.push(next)),
       false,
     );
     assert.deepEqual(applied, ['en']);
@@ -116,10 +116,10 @@ describe('UI locale settings update gate', () => {
 
     gate.cancel(failedSaveTicket);
     assert.equal(
-      gate.commitHydration(blockedHydration, 'zh', (next) => applied.push(next)),
+      gate.commitHydration(blockedHydration, 'zh-CN', (next) => applied.push(next)),
       true,
     );
-    assert.deepEqual(applied, ['zh']);
+    assert.deepEqual(applied, ['zh-CN']);
   });
 
   it('applies the latest blocked hydration when an intervening locale save fails', () => {
@@ -129,13 +129,13 @@ describe('UI locale settings update gate', () => {
     const applied: string[] = [];
 
     assert.equal(
-      gate.commitHydration(hydration, 'zh', (next) => applied.push(next)),
+      gate.commitHydration(hydration, 'zh-CN', (next) => applied.push(next)),
       false,
     );
     assert.deepEqual(applied, []);
 
     gate.cancel(failedSaveTicket);
-    assert.deepEqual(applied, ['zh']);
+    assert.deepEqual(applied, ['zh-CN']);
   });
 
   it('accepts an older pending save after the newer save fails', () => {
diff --git a/apps/desktop/src/main/__tests__/use-onboarding-snapshot.test.ts b/apps/desktop/src/main/__tests__/use-onboarding-snapshot.test.ts
index d60c58c767..35084cadae 100644
--- a/apps/desktop/src/main/__tests__/use-onboarding-snapshot.test.ts
+++ b/apps/desktop/src/main/__tests__/use-onboarding-snapshot.test.ts
@@ -102,7 +102,7 @@ describe('createOnboardingSnapshotPoller', () => {
         onSnapshot: (s) => events.push({ type: 'snap', payload: s }),
         onError: (m) => events.push({ type: 'err', payload: m }),
       },
-      () => 'zh',
+      () => 'zh-CN',
     );
     await poller.pull();
     assert.deepEqual(events, [{ type: 'err', payload: '鉴权失败' }]);
@@ -130,7 +130,7 @@ describe('createOnboardingSnapshotPoller', () => {
           /* not expected */
         },
       },
-      () => 'zh',
+      () => 'zh-CN',
     );
     // Fire two overlapping pulls.
     const pull1 = poller.pull();
@@ -168,7 +168,7 @@ describe('createOnboardingSnapshotPoller', () => {
         onSnapshot: (s) => snaps.push(s),
         onError: (m) => errs.push(m),
       },
-      () => 'zh',
+      () => 'zh-CN',
     );
     const pull1 = poller.pull();
     const pull2 = poller.pull();
@@ -194,7 +194,7 @@ describe('createOnboardingSnapshotPoller', () => {
         onSnapshot: (s) => events.push({ type: 'snap', payload: s }),
         onError: (m) => events.push({ type: 'err', payload: m }),
       },
-      () => 'zh',
+      () => 'zh-CN',
     );
 
     const pull = poller.pull();
@@ -219,7 +219,7 @@ describe('createOnboardingSnapshotPoller', () => {
         onSnapshot: (s) => events.push({ type: 'snap', payload: s }),
         onError: (m) => events.push({ type: 'err', payload: m }),
       },
-      () => 'zh',
+      () => 'zh-CN',
     );
 
     const pull = poller.pull();
@@ -238,7 +238,7 @@ describe('createOnboardingSnapshotPoller', () => {
         onSnapshot: (s) => events.push({ type: 'snap', payload: s }),
         onError: (m) => events.push({ type: 'err', payload: m }),
       },
-      () => 'zh',
+      () => 'zh-CN',
     );
 
     poller.dispose();
diff --git a/apps/desktop/src/main/__tests__/use-session-todo.test.ts b/apps/desktop/src/main/__tests__/use-session-todo.test.ts
new file mode 100644
index 0000000000..ca05257f6a
--- /dev/null
+++ b/apps/desktop/src/main/__tests__/use-session-todo.test.ts
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { strict as assert } from 'node:assert';
+import { afterEach, describe, it } from 'node:test';
+import { act, createElement } from 'react';
+import type { SessionTodoItem } from '@maka/core/session-todo';
+import type { UiLocale } from '@maka/ui';
+import { cleanupFakeDom, installReactRenderer } from './fake-dom.js';
+import {
+  createFakeWorkbarServices,
+  useSessionTodo,
+  WorkbarServicesProvider,
+  type WorkbarServices,
+} from '../../renderer/features/workbar/testing.js';
+
+/**
+ * Regression coverage for the Traditional Chinese error-copy leak
+ * (2026-09-02 review): this hook used to route zh-CN and zh-TW through the
+ * same hard-coded Simplified helper, so a zh-TW reader saw 请求超时 instead
+ * of 請求逾時. It now goes through the shared, locale-aware classifier like
+ * every sibling surface (use-session-trace.ts, session-review-panel.tsx,
+ * session-terminal-panel.tsx, artifact-pane.tsx).
+ */
+
+function Probe(props: {
+  services: WorkbarServices;
+  sessionId?: string;
+  locale: UiLocale;
+  onSnapshot: (snapshot: ReturnType<typeof useSessionTodo>) => void;
+}) {
+  return createElement(
+    WorkbarServicesProvider,
+    { services: props.services },
+    createElement(TodoProbe, props),
+  );
+}
+
+function TodoProbe(props: {
+  sessionId?: string;
+  locale: UiLocale;
+  onSnapshot: (snapshot: ReturnType<typeof useSessionTodo>) => void;
+}) {
+  const snapshot = useSessionTodo(props.sessionId, {
+    locale: props.locale,
+    loadFailed: 'load failed',
+  });
+  props.onSnapshot(snapshot);
+  return null;
+}
+
+function createHarness(read: () => Promise<SessionTodoItem[]>): WorkbarServices {
+  return createFakeWorkbarServices({
+    todo: {
+      read,
+      subscribeChanges: () => () => undefined,
+    },
+  });
+}
+
+describe('useSessionTodo', () => {
+  afterEach(() => {
+    cleanupFakeDom();
+    delete (globalThis as { window?: unknown }).window;
+  });
+
+  it('renders the Traditional Chinese category copy for a zh-TW reader, not Simplified', async () => {
+    const { root } = installReactRenderer();
+    const services = createHarness(async () => {
+      throw new Error('request timeout');
+    });
+    let snapshot: ReturnType<typeof useSessionTodo> | undefined;
+
+    await act(async () => {
+      root.render(
+        createElement(Probe, {
+          services,
+          sessionId: 'session-1',
+          locale: 'zh-TW',
+          onSnapshot: (value) => {
+            snapshot = value;
+          },
+        }),
+      );
+    });
+
+    assert.equal(snapshot?.error, '請求逾時', 'a zh-TW reader must not see Simplified 请求超时');
+  });
+
+  it('still renders the Simplified Chinese category copy for a zh-CN reader', async () => {
+    const { root } = installReactRenderer();
+    const services = createHarness(async () => {
+      throw new Error('request timeout');
+    });
+    let snapshot: ReturnType<typeof useSessionTodo> | undefined;
+
+    await act(async () => {
+      root.render(
+        createElement(Probe, {
+          services,
+          sessionId: 'session-1',
+          locale: 'zh-CN',
+          onSnapshot: (value) => {
+            snapshot = value;
+          },
+        }),
+      );
+    });
+
+    assert.equal(snapshot?.error, '请求超时');
+  });
+
+  it('falls back to the caller-supplied copy for an unclassified error in English', async () => {
+    const { root } = installReactRenderer();
+    const services = createHarness(async () => {
+      throw new Error('unexpected boom');
+    });
+    let snapshot: ReturnType<typeof useSessionTodo> | undefined;
+
+    await act(async () => {
+      root.render(
+        createElement(Probe, {
+          services,
+          sessionId: 'session-1',
+          locale: 'en',
+          onSnapshot: (value) => {
+            snapshot = value;
+          },
+        }),
+      );
+    });
+
+    assert.equal(snapshot?.error, 'load failed');
+  });
+});
diff --git a/apps/desktop/src/main/__tests__/workhub-surface-flow.test.ts b/apps/desktop/src/main/__tests__/workhub-surface-flow.test.ts
index 076040c109..8d9166794e 100644
--- a/apps/desktop/src/main/__tests__/workhub-surface-flow.test.ts
+++ b/apps/desktop/src/main/__tests__/workhub-surface-flow.test.ts
@@ -186,7 +186,7 @@ test('durable delegation renders terminal link state instead of stale execution
       },
       updatedAt: 10,
     };
-    const render = (locale: 'en' | 'zh') => renderToStaticMarkup(
+    const render = (locale: 'en' | 'zh-CN') => renderToStaticMarkup(
       createElement(LocaleProvider, {
         locale,
         children: createElement(AstryxLocaleProvider, {
@@ -200,7 +200,7 @@ test('durable delegation renders terminal link state instead of stale execution
       }),
     );
     const englishMarkup = render('en');
-    const chineseMarkup = render('zh');
+    const chineseMarkup = render('zh-CN');
 
     assert.match(englishMarkup, new RegExp(english, 'u'));
     assert.doesNotMatch(englishMarkup, />Running</u);
@@ -229,7 +229,7 @@ test('durable creation explicitly announces the new work', () => {
     },
     updatedAt: 10,
   };
-  const render = (locale: 'en' | 'zh') => renderToStaticMarkup(
+  const render = (locale: 'en' | 'zh-CN') => renderToStaticMarkup(
     createElement(LocaleProvider, {
       locale,
       children: createElement(AstryxLocaleProvider, {
@@ -244,7 +244,7 @@ test('durable creation explicitly announces the new work', () => {
   );
 
   assert.match(render('en'), /Created new work:/u);
-  assert.match(render('zh'), /已创建新工作:/u);
+  assert.match(render('zh-CN'), /已创建新工作:/u);
 });
 
 test('surface projection refresh gate rejects older reads after a newer refresh starts', () => {
diff --git a/apps/desktop/src/main/browser-message-box.ts b/apps/desktop/src/main/browser-message-box.ts
index 755ecfb8d4..7ab4302cd7 100644
--- a/apps/desktop/src/main/browser-message-box.ts
+++ b/apps/desktop/src/main/browser-message-box.ts
@@ -266,7 +266,7 @@ function normalizeBrowserMessageBoxPresentation(
     defaultId,
     cancelId,
     dark: appearance.dark,
-    locale: appearance.locale === 'zh' ? 'zh' : 'en',
+    locale: appearance.locale,
     palette: isThemePalette(appearance.palette) ? appearance.palette : 'default',
   };
 }
@@ -348,7 +348,7 @@ export function buildBrowserMessageBoxHtml(
 
 function renderBrowserMessageBoxHtml(input: BrowserMessageBoxPresentation): string {
   const nonce = randomUUID().replaceAll('-', '');
-  const closeLabel = input.locale === 'zh' ? '关闭' : 'Close';
+  const closeLabel = input.locale === 'zh-CN' ? '关闭' : input.locale === 'zh-TW' ? '關閉' : 'Close';
   const closeButton = `<button class="window-close" type="button" data-response="${input.cancelId}" aria-label="${closeLabel}">
     <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
   </button>`;
@@ -386,7 +386,7 @@ function renderBrowserMessageBoxHtml(input: BrowserMessageBoxPresentation): stri
         : '<path d="M12 8v5M12 17h.01" />';
 
   return `<!doctype html>
-<html lang="${input.locale === 'zh' ? 'zh-CN' : 'en'}" data-theme="${input.dark ? 'dark' : 'light'}" data-maka-theme="${input.palette}" data-astryx-theme="maka" class="${input.dark ? 'dark' : ''}">
+<html lang="${input.locale}" data-theme="${input.dark ? 'dark' : 'light'}" data-maka-theme="${input.palette}" data-astryx-theme="maka" class="${input.dark ? 'dark' : ''}">
 <head>
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width,initial-scale=1">
diff --git a/apps/desktop/src/main/client-settings-confirmation-copy.ts b/apps/desktop/src/main/client-settings-confirmation-copy.ts
index 4bec4fd2b9..422e9aef92 100644
--- a/apps/desktop/src/main/client-settings-confirmation-copy.ts
+++ b/apps/desktop/src/main/client-settings-confirmation-copy.ts
@@ -24,30 +24,38 @@ export function clientSettingsConfirmation(
   changes: readonly ClientSettingsChange[],
   locale: UiLocale,
 ): { message: string; detail: string; buttons: [string, string] } {
-  const zh = locale === 'zh';
-  const labels: Record<ClientSettingsChange['key'], readonly [string, string]> = {
-    theme: ['Theme', '主题'],
-    palette: ['Palette', '配色'],
-    uiLocale: ['UI language', '界面语言'],
-    runComplete: ['Run-complete notifications', '回答完成通知'],
-    keepSystemAwake: ['Keep system awake', '保持系统唤醒'],
+  const labels: Record<ClientSettingsChange['key'], readonly [string, string, string]> = {
+    theme: ['Theme', '主题', '主題'],
+    palette: ['Palette', '配色', '色彩配置'],
+    uiLocale: ['UI language', '界面语言', '介面語言'],
+    runComplete: ['Run-complete notifications', '回答完成通知', '回答完成通知'],
+    keepSystemAwake: ['Keep system awake', '保持系统唤醒', '保持系統喚醒'],
   };
+  const localeIndex = locale === 'en' ? 0 : locale === 'zh-CN' ? 1 : 2;
   const value = (input: string | boolean | undefined): string => {
-    if (!zh) return String(input);
-    if (input === true) return '开启';
-    if (input === false) return '关闭';
+    if (locale === 'en') return String(input);
+    if (input === true) return locale === 'zh-CN' ? '开启' : '開啟';
+    if (input === false) return locale === 'zh-CN' ? '关闭' : '關閉';
     return String(input);
   };
   return {
-    message: zh
-      ? '允许 Maka 更新此客户端的设置吗?'
-      : "Allow Maka to update this client's settings?",
+    message:
+      locale === 'en'
+        ? "Allow Maka to update this client's settings?"
+        : locale === 'zh-CN'
+          ? '允许 Maka 更新此客户端的设置吗?'
+          : '允許 Maka 更新此用戶端的設定嗎?',
     detail: changes
       .map(
         (change) =>
-          `${labels[change.key][zh ? 1 : 0]}: ${value(change.current)} → ${value(change.next)}`,
+          `${labels[change.key][localeIndex]}: ${value(change.current)} → ${value(change.next)}`,
       )
       .join('\n'),
-    buttons: zh ? ['应用更改', '取消'] : ['Apply changes', 'Cancel'],
+    buttons:
+      locale === 'en'
+        ? ['Apply changes', 'Cancel']
+        : locale === 'zh-CN'
+          ? ['应用更改', '取消']
+          : ['套用變更', '取消'],
   };
 }
diff --git a/apps/desktop/src/main/computer-use/status-item.ts b/apps/desktop/src/main/computer-use/status-item.ts
index 0506a4d5a9..1dba08cb10 100644
--- a/apps/desktop/src/main/computer-use/status-item.ts
+++ b/apps/desktop/src/main/computer-use/status-item.ts
@@ -132,11 +132,16 @@ interface StatusItemCopy {
 }
 
 const COPY: UiCatalog<StatusItemCopy> = {
-  zh: {
+  'zh-CN': {
     stopUsing: (appName) => `停止操作 ${appName}`,
     stopUnnamed: '停止 Computer Use',
     empty: '没有正在进行的任务',
   },
+  'zh-TW': {
+    stopUsing: (appName) => `停止操作 ${appName}`,
+    stopUnnamed: '停止 Computer Use',
+    empty: '沒有正在進行的任務',
+  },
   en: {
     stopUsing: (appName) => `Stop Using ${appName}`,
     stopUnnamed: 'Stop Computer Use',
diff --git a/apps/desktop/src/main/config-transfer-service.ts b/apps/desktop/src/main/config-transfer-service.ts
index 13c6972208..a412a6c2a2 100644
--- a/apps/desktop/src/main/config-transfer-service.ts
+++ b/apps/desktop/src/main/config-transfer-service.ts
@@ -18,6 +18,7 @@
  */
 
 import type { UpdateAppSettingsInput } from '@maka/core/settings';
+import { normalizeUiLocalePreference } from '@maka/core/ui-locale';
 import {
   reconcileConnectionAfterEnabledModelsChange,
   type LlmConnection,
@@ -123,8 +124,20 @@ export async function applyConfigImport(
 
   let settingsCredentialSkips = 0;
   if (bundle.data.settings && typeof bundle.data.settings === 'object') {
+    const patch = bundle.data.settings as unknown as UpdateAppSettingsInput;
+    const importedPersonalization = patch.personalization as
+      | (NonNullable<UpdateAppSettingsInput['personalization']> & { uiLocale?: unknown })
+      | undefined;
     const applied = await deps.settingsStore.update(
-      bundle.data.settings as unknown as UpdateAppSettingsInput,
+      importedPersonalization && Object.hasOwn(importedPersonalization, 'uiLocale')
+        ? {
+            ...patch,
+            personalization: {
+              ...importedPersonalization,
+              uiLocale: normalizeUiLocalePreference(importedPersonalization.uiLocale),
+            },
+          }
+        : patch,
     );
     settingsCredentialSkips = applied.skippedCredentials;
     result.settings = { applied: true };
diff --git a/apps/desktop/src/main/desktop-locale-authority.ts b/apps/desktop/src/main/desktop-locale-authority.ts
index 8690d2ce73..2e2f423b55 100644
--- a/apps/desktop/src/main/desktop-locale-authority.ts
+++ b/apps/desktop/src/main/desktop-locale-authority.ts
@@ -18,6 +18,7 @@
  */
 
 import {
+  normalizeUiLocalePreference,
   resolveSystemUiLocale,
   resolveUiLocale,
   type UiLocale,
@@ -65,7 +66,7 @@ export function createDesktopLocaleAuthority(input: {
   };
   const observe = (settings: LocaleSettings): UiLocale => {
     observation += 1;
-    preference = settings.personalization.uiLocale;
+    preference = normalizeUiLocalePreference(settings.personalization.uiLocale);
     return publish();
   };
   return Object.freeze({
@@ -79,7 +80,9 @@ export function createDesktopLocaleAuthority(input: {
       const request = ++observation;
       try {
         const settings = await input.readSettings();
-        if (request === observation) preference = settings.personalization.uiLocale;
+        if (request === observation) {
+          preference = normalizeUiLocalePreference(settings.personalization.uiLocale);
+        }
       } catch {
         // Locale presentation must not block startup or an unrelated native action.
       }
diff --git a/apps/desktop/src/main/e2e-fixture.ts b/apps/desktop/src/main/e2e-fixture.ts
index 08dc5b226d..6c741c5248 100644
--- a/apps/desktop/src/main/e2e-fixture.ts
+++ b/apps/desktop/src/main/e2e-fixture.ts
@@ -137,7 +137,9 @@ function parseThemeFlag(raw: string | undefined): 'light' | 'dark' | 'auto' | nu
 
 function parseLocaleFlag(raw: string | undefined): UiLocale | null {
   const normalized = raw?.trim().toLowerCase();
-  return normalized === 'zh' || normalized === 'en' ? normalized : null;
+  if (normalized === 'zh-cn') return 'zh-CN';
+  if (normalized === 'zh-tw') return 'zh-TW';
+  return normalized === 'en' ? 'en' : null;
 }
 
 function parseTimezoneFlag(raw: string | undefined): string | null {
diff --git a/apps/desktop/src/main/main.ts b/apps/desktop/src/main/main.ts
index 16133b4515..565583503f 100644
--- a/apps/desktop/src/main/main.ts
+++ b/apps/desktop/src/main/main.ts
@@ -105,18 +105,27 @@ if (!app.requestSingleInstanceLock()) {
         .whenReady()
         .then(() => {
           const locale = resolveSystemUiLocale(app.getPreferredSystemLanguages());
-          const isChinese = locale === 'zh';
+          const isSimplifiedChinese = locale === 'zh-CN';
+          const isTraditionalChinese = locale === 'zh-TW';
           return showBrowserMessageBox(
             {
               type: 'warning',
-              title: isChinese ? 'Maka Dev 已在运行' : 'Maka Dev is already running',
-              message: isChinese
+              title: isSimplifiedChinese
+                ? 'Maka Dev 已在运行'
+                : isTraditionalChinese
+                  ? 'Maka Dev 已在執行'
+                  : 'Maka Dev is already running',
+              message: isSimplifiedChinese
                 ? '另一个 Maka Dev 实例正在使用此开发配置。'
-                : 'Another Maka Dev instance is using this development profile.',
-              detail: isChinese
+                : isTraditionalChinese
+                  ? '另一個 Maka Dev 執行個體正在使用此開發設定。'
+                  : 'Another Maka Dev instance is using this development profile.',
+              detail: isSimplifiedChinese
                 ? `开发配置:${profilePath}\n\n请先退出正在运行的实例,然后重试。`
-                : `Development profile: ${profilePath}\n\nQuit the running instance, then retry.`,
-              buttons: [isChinese ? '退出' : 'Exit'],
+                : isTraditionalChinese
+                  ? `開發設定:${profilePath}\n\n請先退出正在執行的執行個體,然後重試。`
+                  : `Development profile: ${profilePath}\n\nQuit the running instance, then retry.`,
+              buttons: [isSimplifiedChinese ? '退出' : isTraditionalChinese ? '退出' : 'Exit'],
               defaultId: 0,
               cancelId: 0,
             },
diff --git a/apps/desktop/src/main/native-diagnostic-dialog-copy.ts b/apps/desktop/src/main/native-diagnostic-dialog-copy.ts
index c671249561..bd9ccee1eb 100644
--- a/apps/desktop/src/main/native-diagnostic-dialog-copy.ts
+++ b/apps/desktop/src/main/native-diagnostic-dialog-copy.ts
@@ -118,7 +118,7 @@ const COPY = {
       exit: 'Exit',
     },
   },
-  zh: {
+  'zh-CN': {
     dialog: {
       copy: '复制诊断信息',
       copyAgain: '再次复制',
@@ -167,6 +167,55 @@ const COPY = {
       exit: '退出',
     },
   },
+  'zh-TW': {
+    dialog: {
+      copy: '複製診斷資訊',
+      copyAgain: '再次複製',
+      copied: '診斷資訊已複製,可直接貼上到問題報告中。',
+      copyFailed: '無法複製診斷資訊。',
+    },
+    fatalStartup: {
+      title: 'Maka 啟動失敗',
+      message: 'Maka 無法完成啟動。',
+      detail: '啟動時發生未預期的錯誤。複製診斷資訊可檢視詳細資料。',
+      exit: '退出',
+    },
+    rendererGone: {
+      title: 'Maka 需要復原',
+      message: 'Maka 介面意外停止執行。',
+      detail: '只復原介面,不重新啟動 Maka。Runtime Host、正在執行的工作和背景服務都會保留。',
+      recover: '復原介面',
+      exit: '退出',
+    },
+    runtimeHostRecovery: {
+      title: 'Maka 需要修復 Runtime Host',
+      message: '管理此工作區的 Runtime Host 無法啟動。',
+      detail:
+        'Maka 可以修復此 Desktop 選擇的受管理 Runtime Host。工作區、Host 身分、認證資料和設定都會保留。即使無法確認自動更新相容性,修復也可能使用此 Desktop 選擇的版本取代目前 Host。',
+      activeTasks: 'Host 可能仍有正在執行的任務。繼續會先中斷這些任務,再重新啟動 Host。',
+      repairFailed: '上一次修復未能完成。複製診斷資訊可檢視詳細資料。',
+      repair: '修復 Runtime Host',
+      repairAndRestart: '修復並重新啟動 Host',
+      exit: '退出',
+    },
+    defaultRuntimeHostRecovery: {
+      title: '預設 Runtime Host 無法連線',
+      connectFailed: (profileName) => `無法連線至 ${profileName}`,
+      detail:
+        '你可以重試、改用 Local 作為預設 Host,或保留目前選擇並稍後在設定中處理。複製診斷資訊可檢視連線失敗的詳細資料。',
+      retry: '重試',
+      useLocal: '改用 Local',
+      keepOffline: '保持離線',
+    },
+    storageRootRepair: {
+      title: 'Maka 工作區需要修復',
+      message: 'Maka 無法驗證這個工作區。',
+      detail: (workspaceRoot) =>
+        `系統中的磁碟識別資訊可能已變更。僅當這是本機原本的 Maka 工作區,而不是複製的工作區時,才選擇修復。\n\n${workspaceRoot}`,
+      repair: '修復工作區',
+      exit: '退出',
+    },
+  },
 } satisfies UiCatalog<NativeDiagnosticDialogCopy>;
 
 export function getNativeDiagnosticDialogCopy(locale: UiLocale): NativeDiagnosticDialogCopy {
diff --git a/apps/desktop/src/main/notifications-policy.ts b/apps/desktop/src/main/notifications-policy.ts
index efd72ba96b..8795aa00d9 100644
--- a/apps/desktop/src/main/notifications-policy.ts
+++ b/apps/desktop/src/main/notifications-policy.ts
@@ -86,6 +86,11 @@ export function runNotificationCopy(
       ? { title: 'Conversation error', body: 'This response did not finish. Click to view details.' }
       : { title: 'Response ready', body: 'Maka finished this response. Click to view it.' };
   }
+  if (locale === 'zh-TW') {
+    return kind === 'errored'
+      ? { title: '任務發生錯誤', body: '本次回答未能完成,按一下以檢視詳細資料。' }
+      : { title: '回答已產生', body: 'Maka 已完成本次回答,按一下以檢視。' };
+  }
   if (kind === 'errored') {
     return { title: '任务出错', body: '本轮回答未能完成,点击查看详情。' };
   }
diff --git a/apps/desktop/src/main/permission-overlay/permission-overlay-copy.ts b/apps/desktop/src/main/permission-overlay/permission-overlay-copy.ts
index afb7814138..5311ff0469 100644
--- a/apps/desktop/src/main/permission-overlay/permission-overlay-copy.ts
+++ b/apps/desktop/src/main/permission-overlay/permission-overlay-copy.ts
@@ -50,7 +50,7 @@ export interface PermissionOverlayCopy {
 type Catalog = UiCatalog<Record<DragGrantPermissionId, PermissionOverlayCopy>>;
 
 const COPY: Catalog = {
-  zh: {
+  'zh-CN': {
     accessibility: {
       headline: (appName) => `把 ${appName} 拖到上面的列表里,即可开启「辅助功能」`,
       fallback: '也可以在系统设置里点 + 号,从「应用程序」中选择本 App。',
@@ -69,6 +69,25 @@ const COPY: Catalog = {
       noBundle: '当前不是以 .app 方式运行,无法拖拽。请在系统设置里手动添加。',
     },
   },
+  'zh-TW': {
+    accessibility: {
+      headline: (appName) => `把 ${appName} 拖到上面的列表裡,即可開啟「輔助功能」`,
+      fallback: '也可以在系統設定裡點 + 號,從「應用程式」中選擇本 App。',
+      granted: '輔助功能已開啟',
+      dismiss: '關閉',
+      dragHint: '拖我',
+      noBundle: '目前不是以 .app 方式執行,無法拖拽。請在系統設定裡手動新增。',
+    },
+    screen_recording: {
+      headline: (appName) => `把 ${appName} 拖到上面的列表裡,即可開啟「螢幕錄製」`,
+      fallback: '也可以在系統設定裡點 + 號,從「應用程式」中選擇本 App。',
+      granted: '螢幕錄製已開啟',
+      dismiss: '關閉',
+      dragHint: '拖我',
+      restartHint: '若仍顯示未授權,需要重啟 App —— macOS 會快取上一次的拒絕結果。',
+      noBundle: '目前不是以 .app 方式執行,無法拖拽。請在系統設定裡手動新增。',
+    },
+  },
   en: {
     accessibility: {
       headline: (appName) => `Drag ${appName} into the list above to allow Accessibility`,
diff --git a/apps/desktop/src/main/project-picker-copy.ts b/apps/desktop/src/main/project-picker-copy.ts
index 80e2a347be..45a2598914 100644
--- a/apps/desktop/src/main/project-picker-copy.ts
+++ b/apps/desktop/src/main/project-picker-copy.ts
@@ -20,5 +20,7 @@
 import type { UiLocale } from '@maka/core/ui-locale';
 
 export function projectPickerTitle(locale: UiLocale): string {
-  return locale === 'zh' ? '添加项目' : 'Add project';
+  if (locale === 'zh-CN') return '添加项目';
+  if (locale === 'zh-TW') return '新增專案';
+  return 'Add project';
 }
diff --git a/apps/desktop/src/main/runtime-host-boot-copy.ts b/apps/desktop/src/main/runtime-host-boot-copy.ts
new file mode 100644
index 0000000000..fb1489d8c7
--- /dev/null
+++ b/apps/desktop/src/main/runtime-host-boot-copy.ts
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import type { UiCatalog, UiLocale } from '@maka/core/ui-locale';
+
+interface StartupRecoveryCopy {
+  storageRoot: {
+    title: string;
+    message: string;
+    detail(workspaceRoot: string): string;
+    buttons: [string, string];
+  };
+  runtimeHost: {
+    title: string;
+    message(profileName: string): string;
+    detail(message: string): string;
+    buttons: [string, string, string];
+  };
+}
+
+const STARTUP_RECOVERY_COPY = {
+  'zh-CN': {
+    storageRoot: {
+      title: 'Maka 工作区需要修复',
+      message: 'Maka 无法验证这个工作区。',
+      detail: (workspaceRoot) =>
+        `系统中的磁盘标识可能发生了变化。仅当这是本机原来的 Maka 工作区、而不是复制出的工作区时,才选择修复。\n\n${workspaceRoot}`,
+      buttons: ['修复工作区', '退出'],
+    },
+    runtimeHost: {
+      title: '默认 Runtime Host 无法连接',
+      message: (profileName) => `无法连接 ${profileName}`,
+      detail: (message) =>
+        `${message}\n\n你可以重试、改用 Local 作为默认 Host,或保持当前选择并稍后在设置中处理。`,
+      buttons: ['重试', '改用 Local', '保持离线'],
+    },
+  },
+  'zh-TW': {
+    storageRoot: {
+      title: 'Maka 工作區需要修復',
+      message: 'Maka 無法驗證這個工作區。',
+      detail: (workspaceRoot) =>
+        `系統中的磁碟識別碼可能已變更。只有當這是本機原本的 Maka 工作區,而不是複製的工作區時,才選擇修復。\n\n${workspaceRoot}`,
+      buttons: ['修復工作區', '離開'],
+    },
+    runtimeHost: {
+      title: '預設 Runtime Host 無法連線',
+      message: (profileName) => `無法連線 ${profileName}`,
+      detail: (message) =>
+        `${message}\n\n你可以重試、改用 Local 作為預設 Host,或保留目前選擇並稍後在設定中處理。`,
+      buttons: ['重試', '改用 Local', '保持離線'],
+    },
+  },
+  en: {
+    storageRoot: {
+      title: 'Maka workspace needs repair',
+      message: 'Maka cannot verify this workspace.',
+      detail: (workspaceRoot) =>
+        `The disk identity may have changed. Repair only if this is the original Maka workspace on this computer, not a copied workspace.\n\n${workspaceRoot}`,
+      buttons: ['Repair Workspace', 'Exit'],
+    },
+    runtimeHost: {
+      title: 'Default Runtime Host is unavailable',
+      message: (profileName) => `Could not connect to ${profileName}`,
+      detail: (message) =>
+        `${message}\n\nRetry, use Local as the default Host, or keep the current selection and resolve it later in Settings.`,
+      buttons: ['Retry', 'Use Local', 'Keep Offline'],
+    },
+  },
+} satisfies UiCatalog<StartupRecoveryCopy>;
+
+export function getStartupRecoveryCopy(locale: UiLocale): StartupRecoveryCopy {
+  return STARTUP_RECOVERY_COPY[locale];
+}
diff --git a/apps/desktop/src/main/runtime-host-quit-copy.ts b/apps/desktop/src/main/runtime-host-quit-copy.ts
index 26db6a37e4..e53cf84b89 100644
--- a/apps/desktop/src/main/runtime-host-quit-copy.ts
+++ b/apps/desktop/src/main/runtime-host-quit-copy.ts
@@ -109,7 +109,7 @@ const COPY = {
     retry: 'Retry Quit',
     forceQuit: 'Force Quit Maka',
   },
-  zh: {
+  'zh-CN': {
     activeTitle: 'Maka 正在后台工作',
     activeMessage: '仍有后台工作正在运行。',
     activeDetail: '现在退出会停止 Runtime Host,并可能中断正在执行或等待运行的后台任务。',
@@ -125,4 +125,20 @@ const COPY = {
     retry: '重试退出',
     forceQuit: '强制退出 Maka',
   },
+  'zh-TW': {
+    activeTitle: 'Maka 正在背景工作',
+    activeMessage: '仍有背景工作正在執行。',
+    activeDetail: '現在退出會停止 Runtime Host,並可能中斷正在執行或等待執行的背景工作。',
+    stopAndQuit: '停止工作並退出',
+    keepRunning: '繼續執行 Maka',
+    title: '無法安全退出 Maka',
+    message: '本地 Runtime Host 未能安全停止,Maka 仍在執行。',
+    detail: '退出已取消。請重試;如果問題持續存在,請檢視診斷資訊。',
+    process: (pid: number) => `Runtime Host 程序 PID:${pid}`,
+    manual: '如果重試仍然失敗,請先確認沒有需要保留的執行,再透過作業系統的程序管理工具停止該 PID。',
+    forceWarning: '強制退出可能丟棄尚未完成的外部工作。',
+    cause: '原因',
+    retry: '重試退出',
+    forceQuit: '強制退出 Maka',
+  },
 } as const;
diff --git a/apps/desktop/src/main/runtime-host-upgrade-copy.ts b/apps/desktop/src/main/runtime-host-upgrade-copy.ts
index 53fd212d68..3f03574171 100644
--- a/apps/desktop/src/main/runtime-host-upgrade-copy.ts
+++ b/apps/desktop/src/main/runtime-host-upgrade-copy.ts
@@ -160,7 +160,7 @@ const UPGRADE_COPY = {
       other: 'Other background activity',
     },
   },
-  zh: {
+  'zh-CN': {
     title: '旧版 Runtime Host 正在运行',
     message: '另一个 Runtime Host 进程仍占用此工作区。',
     restart: '重启 Runtime Host',
@@ -184,4 +184,27 @@ const UPGRADE_COPY = {
       resource: 'Runtime 资源', graph: 'Agent Graph', other: '其他后台活动',
     },
   },
+  'zh-TW': {
+    title: '舊版 Runtime Host 正在執行',
+    message: '另一個 Runtime Host 程序仍佔用此工作區。',
+    restart: '重啟 Runtime Host',
+    replace: '停止 Host 並繼續',
+    wait: '等待',
+    cancel: '取消啟動',
+    uptime: (n: number) => `已執行約 ${n} 分鐘`,
+    connections: (n: number) => `仍有 ${n} 個其他客戶端連線`,
+    operations: (n: number) => `有 ${n} 個操作正在執行`,
+    idleNotVerified: '安全替換檢查無法確認此 Host 是否處於閒置狀態。',
+    unknownActivity: '此 Host 版本無法報告後臺活動。',
+    processId: (pid: number) => `程序 ID (PID):${pid}`,
+    restartWarning: '重啟會保留持久化狀態,但可能中斷正在進行的外部工作。',
+    replaceWarning: '停止 Host 會保留持久化狀態,但可能中斷正在進行的外部工作。',
+    replaceExplanation: 'Maka 將停止並安全替換此 Host,然後繼續啟動。',
+    exitOwner: (pid: number) => `請使用系統程序管理工具結束程序 ${pid},以便安全替換此 Host。`,
+    waitExplanation: '若選擇等待,目前 Host 退出後 Maka 將自動繼續。',
+    activity: {
+      goal: '目標', scheduledTask: '計劃任務', dailyReview: '每日回顧', execution: '活動執行',
+      resource: 'Runtime 資源', graph: 'Agent Graph', other: '其他後臺活動',
+    },
+  },
 } as const;
diff --git a/apps/desktop/src/preload/runtime-host-session-catalog.ts b/apps/desktop/src/preload/runtime-host-session-catalog.ts
index 08b506ea97..532c771192 100644
--- a/apps/desktop/src/preload/runtime-host-session-catalog.ts
+++ b/apps/desktop/src/preload/runtime-host-session-catalog.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import type { DesktopSessionSummary } from './bridge-contract.js';
+import type { DesktopSessionSummary } from '../shared/desktop-session-projection.js';
 
 export interface RuntimeHostSessionCatalogRequest {
   readonly hostId: string;
diff --git a/apps/desktop/src/renderer/agent-graph-panel.tsx b/apps/desktop/src/renderer/agent-graph-panel.tsx
index 32e8a38810..48a3bbcda6 100644
--- a/apps/desktop/src/renderer/agent-graph-panel.tsx
+++ b/apps/desktop/src/renderer/agent-graph-panel.tsx
@@ -77,7 +77,7 @@ type GraphPanelCopy = {
 };
 
 export function getAgentGraphPanelCopy(locale: UiLocale): GraphPanelCopy {
-  if (locale === 'zh') {
+  if (locale === 'zh-CN') {
     return {
       title: 'Agent Graph',
       loading: '正在读取 Graph 状态…',
@@ -125,6 +125,54 @@ export function getAgentGraphPanelCopy(locale: UiLocale): GraphPanelCopy {
       wait: waitReasonZh,
     };
   }
+  if (locale === 'zh-TW') {
+    return {
+      title: 'Agent Graph',
+      loading: '正在讀取 Graph 狀態…',
+      retry: '重試',
+      collapse: '收起 Agent Graph',
+      expand: '展開 Agent Graph',
+      dismiss: '關閉 Agent Graph',
+      stop: '停止 Graph',
+      stopping: '停止中…',
+      stopFailed: '停止 Graph 失敗,請重試。',
+      loadFailed: 'Graph 狀態重新整理失敗。',
+      openSession: '開啟子任務',
+      operators: 'Operators',
+      selectedResults: '已選取結果',
+      epoch: 'Graph 執行輪次',
+      currentEpoch: '目前',
+      historicalEpoch: '歷史記錄(唯讀)',
+      cappedEpochs: (count) => `僅顯示最近 ${count} 次執行`,
+      noOperators: '等待主 Agent 建立 operator…',
+      hiddenOperators: (count) => `另有 ${count} 個 operator`,
+      progress: (settled, total, hasOmitted) =>
+        hasOmitted ? `可見項目中 ${settled}/${total} 已結束` : `${settled}/${total} 已結束`,
+      status: (status) =>
+        ({
+          empty: '等待排程',
+          active: '執行中',
+          closing: '收尾中',
+          waiting: '等待中',
+          stopped: '已停止',
+          failed: '失敗',
+          completed: '已完成',
+        })[status],
+      operatorStatus: (status) =>
+        ({
+          not_started: '尚未啟動',
+          waiting: '等待',
+          runnable: '可執行',
+          running: '執行中',
+          blocked: '受阻',
+          completed: '完成',
+          failed: '失敗',
+          aborted: '已中止',
+          cancelled: '已取消',
+        })[status],
+      wait: waitReasonZhTw,
+    };
+  }
   return {
     title: 'Agent Graph',
     loading: 'Loading graph state…',
@@ -566,3 +614,15 @@ function waitReasonZh(operator: AgentGraphClientOperator): string | undefined {
   }
   return `等待 ${wait.operatorId} 结束`;
 }
+
+function waitReasonZhTw(operator: AgentGraphClientOperator): string | undefined {
+  const wait = firstWait(operator);
+  if (!wait) return undefined;
+  if (wait.kind === 'input_route') {
+    return `等待 ${wait.upstreamOperatorIds.join('、')} 的輸入`;
+  }
+  if (wait.kind === 'activation_missing') {
+    return `等待 ${wait.operatorId} 啟用`;
+  }
+  return `等待 ${wait.operatorId} 結束`;
+}
diff --git a/apps/desktop/src/renderer/app-shell-copy.ts b/apps/desktop/src/renderer/app-shell-copy.ts
index 2f15529095..8e5d7c82fb 100644
--- a/apps/desktop/src/renderer/app-shell-copy.ts
+++ b/apps/desktop/src/renderer/app-shell-copy.ts
@@ -20,7 +20,7 @@
 import type { ConnectionTestResult } from '@maka/core/llm-connections';
 import type { TextFileImportPreflightFailureReason } from '@maka/core/text-file-import';
 import type { UiLocale } from '@maka/core/ui-locale';
-import { generalizedErrorMessage, generalizedErrorMessageChinese } from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
 import { getShellCopy } from './locales/shell-copy.js';
 
 const SESSION_READ_MESSAGES_ERROR_MARKER = 'MAKA_SESSION_READ_MESSAGES_ERROR:';
@@ -36,13 +36,13 @@ export function messageRefreshErrorMessage(error: unknown, locale: UiLocale): st
 function sessionMessageErrorMessage(error: unknown, fallback: string, locale: UiLocale): string {
   const raw = error instanceof Error ? error.message : String(error);
   const markerIndex = raw.indexOf(SESSION_READ_MESSAGES_ERROR_MARKER);
-  if (markerIndex < 0 || locale === 'en') return localizedErrorMessage(error, fallback, locale);
+  if (markerIndex < 0 || locale !== 'zh-CN') return localizedErrorMessage(error, fallback, locale);
   const marked = raw.slice(markerIndex + SESSION_READ_MESSAGES_ERROR_MARKER.length).trim();
   return marked.split(/\r?\n/, 1)[0]?.trim() || fallback;
 }
 
 function localizedErrorMessage(error: unknown, fallback: string, locale: UiLocale): string {
-  return locale === 'zh' ? generalizedErrorMessageChinese(error, fallback) : generalizedErrorMessage(error, fallback);
+  return generalizedErrorMessageForLocale(error, fallback, locale);
 }
 
 export function commandPaletteActionErrorMessage(error: unknown, fallback: string, locale: UiLocale): string {
diff --git a/apps/desktop/src/renderer/app-shell-effects.ts b/apps/desktop/src/renderer/app-shell-effects.ts
index 00d596e466..42afdd3708 100644
--- a/apps/desktop/src/renderer/app-shell-effects.ts
+++ b/apps/desktop/src/renderer/app-shell-effects.ts
@@ -25,7 +25,6 @@ import type { SessionEvent } from '@maka/core/events';
 import type { SessionEventStreamSnapshot } from '@maka/core/session-event-health';
 import type { ThemePalette, ThemePreference } from '@maka/core/settings';
 import type { UiLocale } from '@maka/core/ui-locale';
-import { generalizedErrorMessageChinese } from '@maka/core/redaction';
 import { sessionExpectsEventStream } from '@maka/core/session-event-health';
 import { type ShellRunUpdate } from '@maka/core/events';
 import type { LiveTurnProjection, NavSelection } from '@maka/ui';
diff --git a/apps/desktop/src/renderer/app-shell.tsx b/apps/desktop/src/renderer/app-shell.tsx
index 8ecc4e7f93..040e9f4a25 100644
--- a/apps/desktop/src/renderer/app-shell.tsx
+++ b/apps/desktop/src/renderer/app-shell.tsx
@@ -2866,7 +2866,7 @@ function AppShellContent({
                     {workHubEnabled && navSelection.section === 'sessions' && activeId ? (
                       <Button
                         className="workhub-return"
-                        label={uiLocale === 'zh' ? '返回 WorkHub' : 'Return to WorkHub'}
+                        label={uiLocale !== 'en' ? '返回 WorkHub' : 'Return to WorkHub'}
                         variant="secondary"
                         size="sm"
                         onClick={openWorkHub}
diff --git a/apps/desktop/src/renderer/attachment-preflight.ts b/apps/desktop/src/renderer/attachment-preflight.ts
index 0af114c7a9..b88cfd6ba0 100644
--- a/apps/desktop/src/renderer/attachment-preflight.ts
+++ b/apps/desktop/src/renderer/attachment-preflight.ts
@@ -38,7 +38,7 @@ type PreflightItem = {
  * File blobs are sized by the browser File object; approval-token attachments
  * are sized by the pending size stamped at pick time (main re-stats).
  */
-export function preflightAttachmentItems(items: readonly PreflightItem[], locale: UiLocale = 'zh'): void {
+export function preflightAttachmentItems(items: readonly PreflightItem[], locale: UiLocale = 'zh-CN'): void {
   const copy = getDesktopConversationCopy(locale).attachments;
   if (items.length > MAX_ATTACHMENT_COUNT) throw new Error(copy.tooMany);
   const seen = new Set<string>();
diff --git a/apps/desktop/src/renderer/conversation-markdown.ts b/apps/desktop/src/renderer/conversation-markdown.ts
index 526f41d701..0c808af68a 100644
--- a/apps/desktop/src/renderer/conversation-markdown.ts
+++ b/apps/desktop/src/renderer/conversation-markdown.ts
@@ -43,7 +43,7 @@ import { getShellRemainingCopy } from './locales/shell-remaining-copy.js';
  *   export that the user is going to paste somewhere public.
  * - **user text** left untouched (the user typed it, they own it).
  */
-export function renderConversationMarkdown(sessionName: string, messages: StoredMessage[], locale: UiLocale = 'zh'): string {
+export function renderConversationMarkdown(sessionName: string, messages: StoredMessage[], locale: UiLocale = 'zh-CN'): string {
   const copy = getShellRemainingCopy(locale).conversationExport;
   const lines: string[] = [];
   lines.push(`# ${sessionName}`);
diff --git a/apps/desktop/src/renderer/daily-review-actions.ts b/apps/desktop/src/renderer/daily-review-actions.ts
index 02bdc38119..ae3f20f764 100644
--- a/apps/desktop/src/renderer/daily-review-actions.ts
+++ b/apps/desktop/src/renderer/daily-review-actions.ts
@@ -21,7 +21,7 @@ import type { DailyReviewArchive } from '@maka/core/daily-review';
 import type { UiLocale } from '@maka/core/ui-locale';
 import { dailyReviewArchiveId } from '@maka/core/daily-review';
 
-import { generalizedErrorMessage, generalizedErrorMessageChinese } from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
 
 export function dailyReviewExportDefaultName(
   input: Pick<DailyReviewArchive, 'day' | 'range'>,
@@ -30,5 +30,5 @@ export function dailyReviewExportDefaultName(
 }
 
 export function dailyReviewActionErrorMessage(error: unknown, fallback: string, locale: UiLocale): string {
-  return locale === 'zh' ? generalizedErrorMessageChinese(error, fallback) : generalizedErrorMessage(error, fallback);
+  return generalizedErrorMessageForLocale(error, fallback, locale);
 }
diff --git a/apps/desktop/src/renderer/derive-turn-lineage-badges.ts b/apps/desktop/src/renderer/derive-turn-lineage-badges.ts
index f6aa1d8373..5dd2c5791b 100644
--- a/apps/desktop/src/renderer/derive-turn-lineage-badges.ts
+++ b/apps/desktop/src/renderer/derive-turn-lineage-badges.ts
@@ -50,7 +50,7 @@ export interface TurnLineageBadgeInput {
 }
 
 export function deriveTurnLineageBadges(input: TurnLineageBadgeInput): TurnLineageBadge[] {
-  const copy = getDesktopConversationCopy(input.locale ?? 'zh').lineage;
+  const copy = getDesktopConversationCopy(input.locale ?? 'zh-CN').lineage;
   const badges: TurnLineageBadge[] = [];
 
   const forwardFrom = input.regeneratedFromTurnId ?? input.retriedFromTurnId;
diff --git a/apps/desktop/src/renderer/features/connection-settings/index.ts b/apps/desktop/src/renderer/features/connection-settings/index.ts
index d9c5757d2d..cf1c5ddb5f 100644
--- a/apps/desktop/src/renderer/features/connection-settings/index.ts
+++ b/apps/desktop/src/renderer/features/connection-settings/index.ts
@@ -38,6 +38,7 @@ export {
 } from './provider-panel-shared.js';
 export { OnboardingStepForm } from './onboarding-step-form.js';
 export { getProviderSettingsCopy } from './settings-provider-copy.js';
+export { subscriptionResultMessage } from './subscription-result-message.js';
 export type { ProviderSettingsCopy } from './settings-provider-copy.js';
 export type {
   CredentialPresenceStatus,
diff --git a/apps/desktop/src/renderer/features/connection-settings/provider-panel-shared.ts b/apps/desktop/src/renderer/features/connection-settings/provider-panel-shared.ts
index 0a470b680d..b30f2d1a6f 100644
--- a/apps/desktop/src/renderer/features/connection-settings/provider-panel-shared.ts
+++ b/apps/desktop/src/renderer/features/connection-settings/provider-panel-shared.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { generalizedErrorMessage, generalizedErrorMessageChinese, redactSecrets } from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale, redactSecrets } from '@maka/core/redaction';
 import { type ConnectionTestResult } from '@maka/core/llm-connections';
 import { type UiLocale } from '@maka/core/ui-locale';
 import { getProviderSettingsCopy } from './settings-provider-copy.js';
@@ -25,7 +25,7 @@ import { cleanErrorMessage } from '../../application/contracts/connection-error-
 
 export type CredentialPresenceStatus = boolean | 'loading' | 'error';
 
-export function providerPanelActionErrorMessage(error: unknown, locale: UiLocale = 'zh'): string {
+export function providerPanelActionErrorMessage(error: unknown, locale: UiLocale = 'zh-CN'): string {
   const shared = getProviderSettingsCopy(locale).shared;
   // Electron wraps ipcMain.handle rejections as "Error invoking remote method
   // '<channel>': Error: <message>". Classify the original message, not the
@@ -36,15 +36,13 @@ export function providerPanelActionErrorMessage(error: unknown, locale: UiLocale
   if (known) return known;
   // Main-process handlers throw display-ready Chinese copy; keep it instead
   // of flattening it into a coarser classification or the generic fallback.
-  if (/[\u3400-\u9fff]/.test(cleaned)) return cleaned;
+  if (locale === 'zh-CN' && /[\u3400-\u9fff]/.test(cleaned)) return cleaned;
   if (/connection_stale|Unable to delete Connection: connection_stale/i.test(cleaned)) {
-    return locale === 'zh'
-      ? '连接状态已更新,请刷新列表后再删除。'
-      : 'The connection changed while deleting. Refresh the list and try again.';
+    if (locale === 'zh-CN') return '连接状态已更新,请刷新列表后再删除。';
+    if (locale === 'zh-TW') return '連線狀態已更新,請重新整理清單後再刪除。';
+    return 'The connection changed while deleting. Refresh the list and try again.';
   }
-  const classified = locale === 'zh'
-    ? generalizedErrorMessageChinese(new Error(cleaned), '')
-    : generalizedErrorMessage(new Error(cleaned), '');
+  const classified = generalizedErrorMessageForLocale(new Error(cleaned), '', locale);
   return classified || shared.actionFallback;
 }
 
@@ -61,7 +59,7 @@ export interface ConnectionTestTroubleshootingCopy {
 export function connectionTestFailureFallback(
   result: ConnectionTestResult,
   copy: ConnectionTestTroubleshootingCopy,
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): string {
   const shared = getProviderSettingsCopy(locale).shared;
   if (result.statusCode === 429) return shared.rateLimit;
@@ -79,16 +77,14 @@ export function connectionTestFailureFallback(
 export function connectionTestFailureMessage(
   result: ConnectionTestResult,
   copy: ConnectionTestTroubleshootingCopy,
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): string {
   const fallback = connectionTestFailureFallback(result, copy, locale);
   if (!result.errorMessage) return fallback;
-  return locale === 'zh'
-    ? generalizedErrorMessageChinese(new Error(result.errorMessage), fallback)
-    : generalizedErrorMessage(new Error(result.errorMessage), fallback);
+  return generalizedErrorMessageForLocale(new Error(result.errorMessage), fallback, locale);
 }
 
-export function connectionLastTestMessageDisplay(message: string | undefined, locale: UiLocale = 'zh'): string | undefined {
+export function connectionLastTestMessageDisplay(message: string | undefined, locale: UiLocale = 'zh-CN'): string | undefined {
   if (!message) return undefined;
   const trimmed = message.trim();
   if (!trimmed) return undefined;
@@ -96,8 +92,6 @@ export function connectionLastTestMessageDisplay(message: string | undefined, lo
   const copy = getProviderSettingsCopy(locale).shared;
   const known = (copy.lastTest as Readonly<Record<string, string>>)[normalized];
   if (known) return known;
-  const classified = locale === 'zh'
-    ? generalizedErrorMessageChinese(new Error(trimmed), '')
-    : generalizedErrorMessage(new Error(trimmed), '');
+  const classified = generalizedErrorMessageForLocale(new Error(trimmed), '', locale);
   return classified || copy.statusUnavailable;
 }
diff --git a/apps/desktop/src/renderer/features/connection-settings/settings-provider-copy.ts b/apps/desktop/src/renderer/features/connection-settings/settings-provider-copy.ts
index 28cfe0f7a9..da6a223f72 100644
--- a/apps/desktop/src/renderer/features/connection-settings/settings-provider-copy.ts
+++ b/apps/desktop/src/renderer/features/connection-settings/settings-provider-copy.ts
@@ -51,6 +51,30 @@ const zhCapabilitiesCopy = {
   fastAuto: '自动',
   fastEnabled: 'Fast',
 };
+
+const zhTwCapabilitiesCopy = {
+  capabilities: '能力',
+  thinkingEffort: '思考檔位(reasoning_effort)',
+  thinkingEffortHelp: '勾選需要的思考強度檔位,不勾選即為不宣告。',
+  thinkingUndeclared: '未宣告',
+  thinkingSelectedCount: (count: number) => `已選擇 ${count} 個`,
+  thinkingBulk: '批次設定思考檔位',
+  thinkingBulkCoverage: (declared: number, total: number) =>
+    declared === 0 ? '全部未宣告' : `${declared}/${total} 個模型`,
+  visionInput: '視覺輸入(vision)',
+  visionInputHelp: '「自動」跟隨內建後設資料;「啟用/停用」是顯式宣告,覆蓋自動判斷。',
+  visionAuto: '自動',
+  visionEnabledOption: '啟用',
+  visionDisabledOption: '停用',
+  contextWindow: '上下文視窗(tokens)',
+  contextWindowHelp: '聲明後壓縮與預算按此值計算;留空跟隨內建後設資料。',
+  contextWindowHint: (tokens: number) => `該模型宣告的視窗為 ${tokens} tokens`,
+  contextWindowApplyHint: '填入',
+  fastMode: 'Fast 模式',
+  fastModeHelp: '使用 OpenAI 的 fast service tier;留空跟隨服務商預設值。',
+  fastAuto: '自動',
+  fastEnabled: 'Fast',
+};
 const enCapabilitiesCopy = {
   capabilities: 'Capabilities',
   thinkingEffort: 'Thinking levels (reasoning_effort)',
@@ -245,6 +269,175 @@ const zhCopy = {
     storageFailed: (name: string) => `${name} 本地凭据读取失败,请重新登录。`, providerUnavailable: (name: string) => `${name} 已登录,但当前 provider 状态不可用。`,
   },
 } as const;
+const zhTwCopy = {
+  detail: {
+    deviceCode: '登入碼:',
+    delete: '刪除', cancel: '取消', deleteUnused: '不再需要,刪除連線',
+    deleteFailed: '刪除模型連線失敗', credentialReadFailed: '讀取模型憑據狀態失敗', refreshFailed: '重新整理模型連線失敗',
+    saveFailed: '儲存模型連線失敗', saveModelsFailed: '儲存啟用模型失敗',
+    modelKey: '模型金鑰', pasteModelKey: '貼上模型金鑰', getModelKey: '取得模型金鑰', saving: '儲存中…',
+    advancedRequest: '高階請求設定', advancedRequestHelp: '為這個連線的 HTTP 請求新增請求頭和額外 JSON 請求體。請求頭值作為憑據儲存在本機。',
+    requestHeaders: '自訂請求頭', headerName: '請求頭名稱', headerValue: '請求頭值', retainedHeaderValue: '保留已儲存的值', addHeader: '新增請求頭', removeHeader: '移除', noRequestHeaders: '未設定自訂請求頭。',
+    extraRequestBody: '額外請求體(JSON)', extraRequestBodyHelp: '僅新增頂層欄位;若與 Maka 生成的欄位重名,請求會明確失敗。', requestCustomizationInvalid: '請求設定無效', requestHeadersInvalidDetail: '請檢查請求頭名稱和值。', requestBodyInvalidDetail: '請輸入符合要求的 JSON 物件。', saveAdvancedRequest: '儲存高階請求設定', configuredHeaders: (count: number) => `${count} 個請求頭`, noAdvancedRequest: '未設定',
+    oauthLoggedIn: 'OAuth 已登入', oauthLoading: 'OAuth 狀態讀取中', oauthUnknown: 'OAuth 狀態未知', oauthWaiting: '等待 OAuth 登入',
+    oauthLoggedInDetail: '該模型連線使用主程序儲存的 OAuth access token;若請求提示需要重新登入,請到帳號連線重新授權。',
+    oauthLoadingDetail: '正在讀取本機 OAuth 登入狀態,讀取完成前不會把未知狀態顯示成未登入。',
+    oauthUnknownDetail: '暫時無法讀取本機 OAuth 登入狀態;請重新整理頁面或重新開啟設定。',
+    oauthWaitingDetail: '請到帳號連線完成登入;登入成功後會自動出現在模型連線裡。',
+    oauthRetired: '此登入方式已停用',
+    oauthRetiredDetail:
+      '這條連線使用的登入方式已從 Maka 移除,無法再登入,也無法用於對話。改用 Anthropic API Key 連線即可繼續使用 Claude 模型;刪除這條連線會一併清除本機儲存的登入憑據。',
+    credentialLoadingDetail: '正在讀取模型憑據狀態,讀取完成前暫不測試連線或重新整理模型。',
+    credentialUnknownDetail: '模型憑據狀態暫時沒重新整理成功,已避免把未知狀態顯示成未登入或未設定。',
+    testConnection: '測試連線',
+    updateModels: '更新模型目錄', endpoint: '服務地址',
+    connectionName: '名稱',
+    connectionNamePlaceholder: '給這個連線起個名字',
+    addModel: '新增模型',
+    addModelConfirm: '新增',
+    addModelIdField: '模型 ID',
+    addModelIdFieldHelp: '需與服務商完全一致,區分大小寫。',
+    addModelIdPlaceholder: 'deepseek-v4-pro-beta',
+    addModelIdRequired: '請填寫模型 ID。',
+    addModelIdDuplicate: '該模型已在列表中。',
+    addModelContextWindow: '上下文視窗',
+    addModelContextWindowHelp: '服務商模型頁給出的最大 token 數。缺少它 Maka 只能按 32k 處理,長對話會被提前截斷。',
+    addModelContextWindowRequired: '請填寫上下文視窗。',
+    credentials: '連線', dangerZone: '刪除連線', deleteRowHelp: '此操作不可撤銷。',
+    credentialsHelp: '金鑰只儲存在本機。',
+    credentialsHelpAccount: '登入權杖只儲存在本機。',
+    modelManagementHelp: '這些模型會出現在任務的模型選擇器裡。',
+    ...zhTwCapabilitiesCopy,
+    capabilitiesHelp: '宣告每個已啟用模型的思考檔位、視覺與上下文視窗;儲存後生效。',
+    // Row affordances (settings-sidebar 的 InfoRow / ExpandableRow 語言):一行
+    // 只報狀態,改的時候才展開成輸入框。
+    change: '更換', set: '設定', edit: '編輯', save: '儲存',
+    endpointManaged: '由帳號登入或服務商管理',
+    endpointMissing: '尚未設定服務地址',
+    endpointModelOverridesNote: '部分模型會透過其他服務端點發送請求',
+    endpointCredentialsMasked: '已儲存的地址內嵌憑據,編輯時預設隱藏',
+    modelManagement: '模型',
+    disconnectAndDelete: '退出帳號並刪除',
+    login: '登入', loggingIn: '登入中…', relogin: '重新登入',
+    oauthReloginDetail: '若請求提示需要重新登入,點這裡重新走一遍授權。',
+    oauthStartDetail: '點下方按鈕開啟瀏覽器完成登入,授權成功後會自動重新整理這裡的狀態。',
+    status: '連線狀態', statusHealthy: '正常', statusUntested: '未測試',
+    modelsSummary: (enabled: number, total: number) => `已啟用 ${enabled} / ${total}`,
+    filterModels: '搜尋模型', noModelsMatch: '找不到符合的模型',
+    enableModelAria: (name: string) => `啟用模型 ${name}`,
+    declareCapabilities: '設定參數', declareCapabilitiesAria: (name: string) => `設定模型參數:${name}`,
+    modelUndescribed: '缺少該模型的參數資訊,請手動設定。',
+    visionToken: '视觉', thinkingToken: '思考', contextToken: (value: string) => `${value} 上下文`,
+    noModels: '暫無可選模型,請先更新模型目錄。',
+    keySet: '已設定', statusLoading: '正在讀取狀態', credentialUnknown: '憑據狀態未知', keyMissing: '尚未設定金鑰',
+    keyTroubleshooting: '模型金鑰 / 服務地址 / 代理設定', endpointTroubleshooting: '本地服務 / 服務地址 / 代理設定', oauthTroubleshooting: 'OAuth 登入 / 代理設定',
+    unknownDescription: (provider: string) => `該連線使用的 provider「${provider}」在目前版本未註冊。設定和憑據會保留,切回支援它的版本即可繼續使用。`,
+    deleteProviderTitle: (name: string) => `刪除供應商 ${name}?`,
+    deleteConnectionTitle: (name: string) => `刪除模型連線 ${name}?`,
+    deleteUnknownDescription: '刪除後,支援該 provider 的其他版本也無法恢復這條連線及其憑據。',
+    deleteDescription: (isDefault: boolean, disconnectsAccount: boolean) => [
+      disconnectsAccount
+        ? '這會退出本機帳號、刪除 OAuth 憑據和模型連線;重新整理後不會自動重建立立。'
+        : '這會刪除模型連線及其本機憑據;如需再次使用,需要重新新增。',
+      isDefault
+        ? '它目前是預設連線;刪除後預設模型會變成未設定,已有任務可能需要重新選擇模型。'
+        : '',
+    ].filter(Boolean).join(' '),
+    connectionSuccess: (name: string) => `連線成功 · ${name}`, connectionFailed: (name: string) => `連線失敗 · ${name}`,
+    connectionFallbackTitle: (name: string) => `連線可用 · ${name}`,
+    connectionFallbackDetail: (selected: readonly string[], tested: string) =>
+      `你選擇的 ${selected.join('、')} 目前沒有回應,已改用 ${tested} 驗證連線可用;任務中繼續使用你選擇的模型可能會失敗。`,
+    connectionTestError: (name: string) => `連線測試出錯 · ${name}`, modelsFetched: (count: number, name: string) => `已拉取 ${count} 個模型 · ${name}`,
+    modelsFetchFailed: (name: string) => `拉取模型失敗 · ${name}`,
+    modelsFetchFailedDetail: (message: string, troubleshooting: string) => `${message} · 目前繼續顯示靜態列表,請確認 ${troubleshooting} 後重試。`,
+    authTroubleshooting: (value: string) => `鑑權失敗,請確認 ${value} 後重試。`, recheckTroubleshooting: (value: string) => `檢查 ${value} 後重試。`,
+    modelKeyAria: (name: string) => `${name} 模型金鑰`,
+  },
+  shared: {
+    actionFallback: '模型連線服務暫時不可用,請稍後重試。', rateLimit: '目前帳號或模型服務觸發速率限制,請稍後重試。',
+    timeout: '請求超時,請檢查網路或代理後重試。', unavailable: '模型服務暫時不可用,請稍後重試。',
+    network: '網路錯誤,請檢查服務地址或代理設定後重試。', statusUnavailable: '連線測試狀態暫時無法顯示,請重新測試。',
+    filterMatches: (count: number) => (count === 0 ? '沒有符合的結果' : `${count} 個符合結果`),
+    connectionStatuses: { retired: '已停用 · 請刪除', reauth: '需要重新登入', disabledFailed: '暫不可用 · 上次連線失敗', disabled: '暫不可用', failed: '上次連線失敗' },
+    lastTest: {
+      '连接已验证': '連線已驗證', '鉴权失败': '鑑權失敗', '请求超时': '請求超時', '网络错误': '網路錯誤', '模型服务返回错误': '模型服務回傳錯誤', '连接测试失败': '連線測試失敗',
+      'connection verified': '連線已驗證', 'authentication failed': '鑑權失敗', 'request timed out': '請求超時', 'network error': '網路錯誤', 'provider returned an error': '模型服務回傳錯誤', 'connection test failed': '連線測試失敗',
+      'claude oauth 未登录。': 'Claude OAuth 未登入。', 'claude oauth 本地凭据读取失败。': 'Claude OAuth 本地憑據讀取失敗。', 'claude oauth 需要重新登录。': 'Claude OAuth 需要重新登入。', 'claude oauth 已登录。': 'Claude OAuth 已登入。', 'claude oauth 已退出登录。': 'Claude OAuth 已退出登入。',
+      'codex oauth 未登录。': 'Codex OAuth 未登入。', 'codex oauth 本地凭据读取失败。': 'Codex OAuth 本地憑據讀取失敗。', 'codex oauth 需要重新登录。': 'Codex OAuth 需要重新登入。', 'codex oauth 已登录。': 'Codex OAuth 已登入。', 'codex oauth 已退出登录。': 'Codex OAuth 已退出登入。',
+      '当前账号无可用 codex 模型。': '目前帳號無可用 Codex 模型。', 'codex 模型列表获取失败。': 'Codex 模型列表取得失敗。',
+      'github copilot 需要重新导入 github cli 登录。': 'GitHub Copilot 需要重新匯入 GitHub CLI 登入。', 'github copilot 无法读取当前账号可用模型,请重新验证登录。': 'GitHub Copilot 無法讀取目前帳號可用模型,請重新驗證登入。', 'github copilot 登录已导入。': 'GitHub Copilot 登入已匯入。', 'github copilot 连接未能保存,请重新导入登录。': 'GitHub Copilot 連線未能儲存,請重新匯入登入。', 'github copilot 已移除本地登录。': 'GitHub Copilot 已移除本地登入。',
+    },
+  },
+  panel: {
+    groups: { recommended: '推薦', plans: '訂閱計劃', api: 'API', aggregators: '聚合服務', local: '本地' },
+    loadFailed: '載入模型連線失敗', loadingAria: '正在載入模型供應商', connections: '模型連線',
+    connectionsHelp: '已啟用的模型會顯示在任務的模型選擇器中。',
+    retry: '點選重試。', empty: '還沒有模型連線', connectionRemoved: '原連線已刪除或移除,已返回模型連線列表。',
+    connectedLoading: '帳號已連線,正在載入新的模型連線…', connectedLoadFailed: '帳號已連線,但暫時無法載入新的模型連線。',
+    connectionIdentityChanged: '新連線的身分與登入結果不一致,請返回連線列表後重試。',
+    emptyHelp: '從下方的常用服務商開始,或瀏覽全部服務商。', recommended: '推薦服務商', browseAll: '查看全部服務商',
+    default: '預設', setDefault: '設為預設', setDefaultTitle: '讓新任務預設使用這個連線', setDefaultPending: '設定中…', setDefaultFailed: '設為預設失敗', addHelp: '選擇帳號登入、模型計劃、API、聚合服務或本地執行時。',
+    searchPlaceholder: '搜尋服務商', searchAria: '搜尋模型服務商', noMatch: '沒有符合的服務商', clearSearch: '清除搜尋',
+    createSubtitle: '完成必要設定後,連線會出現在模型頁上方。', connection: '模型連線',
+    count: (value: number) => `· ${value}`, modelCount: (value: number) => `${value} 個模型`, connectTitle: (name: string) => `連線 ${name}`,
+    chipAria: (name: string, provider: string, isDefault: boolean, status?: string) => `模型連線:${name},供應商:${provider}${isDefault ? ',預設連線' : ''}${status ? `,${status}` : ''}`,
+    addConnection: '新增連線', backToList: '返回模型連線', backToCatalog: '返回服務商列表',
+  },
+  catalog: {
+    unavailable: '未開放',
+    cardAria: (name: string, description: string) => `新增模型供應商:${name},${description}`,
+  },
+  add: {
+    invalidSlug: '連線標識格式不正確', duplicateSlug: '連線標識已存在', cloudflareAccount: '請填寫 Cloudflare Account ID', endpointRequired: '這個供應商需要填寫服務地址',
+    accountLogin: '請到帳號連線完成登入;登入成功後會自動建立模型連線。',
+    apiKeyPlaceholder: '輸入或貼上 API Key', cancel: '取消', accountTitle: '使用帳號連線登入',
+    advancedRequest: '高階請求設定', expandAdvancedRequest: '展開高階請求設定', collapseAdvancedRequest: '收起高階請求設定',
+    requestHeaders: '自訂請求頭', headerName: '請求頭名稱', headerValue: '請求頭值', retainedHeaderValue: '保留已儲存的值', addHeader: '新增請求頭', removeHeader: '移除', noRequestHeaders: '未設定自訂請求頭。',
+    extraRequestBody: '額外請求體(JSON)', extraRequestBodyHelp: '僅新增頂層欄位;與 Maka 生成欄位重名時會明確失敗。請求頭值將作為憑據儲存在本機。', requestCustomizationInvalid: '請檢查高階請求設定。',
+    accountDetail: '不要在這裡手動新增;請回到模型連線頁的帳號連線完成登入,Maka 會自動建立並重新整理模型連線。',
+    slug: '連線標識', name: '顯示名稱',
+    accountIdPlaceholder: '填寫帳號 ID',
+    saving: '儲存中…', save: '儲存供應商', keyRequired: (name: string) => `請填寫 ${name} API Key`,
+    apiKeyLabel: 'API Key', accountIdLabel: 'Cloudflare Account ID', endpointLabel: '服務地址',
+    defaultModel: '預設模型', defaultModelPlaceholder: '留空即可,儲存後自動拉取', defaultModelHelp: '儲存後 Maka 會向該端點拉取模型目錄。只有當端點不提供目錄時,才需要在這裡手填一個模型 ID。',
+    stepsAria: '新增連線步驟', stepCredentials: '金鑰', stepModels: '選擇模型',
+    onboardingVerifyAndChoose: '驗證並選擇模型', onboardingVerifying: '正在驗證金鑰並取得模型…',
+    onboardingChooseModels: '選擇此連線使用的模型', onboardingChooseModelsHelp: '新增後仍可在連線詳細資料中啟用其他模型。', onboardingEnabledModels: '啟用的模型', onboardingSearchModels: '搜尋模型', onboardingAddConnection: '新增連線', onboardingBack: '返回修改',
+    onboardingSelectedCount: (selected: number, total: number) => `已選 ${selected} / ${total}`, onboardingSelectAll: '全選', onboardingClearAll: '取消全選', onboardingNoModelsMatch: '找不到符合的模型',
+    onboardingDefaultModel: '預設模型', onboardingDefaultModelHelp: '新任務將預設使用此模型,僅可在已勾選的模型中選擇。',
+    onboardingAuthFailed: '金鑰驗證失敗,請檢查後重試。', onboardingTimeout: '驗證逾時,請檢查網路或 Proxy 後重試。', onboardingNetwork: '無法連線至模型服務,請檢查網路、Proxy 或服務位址。', onboardingUnavailable: '模型連線服務暫時無法使用,請稍後重試。', onboardingInvalidResponse: '模型服務回傳無法辨識的結果,請稍後重試。', onboardingCatalogFull: '連線數量已達上限,請先刪除不再使用的連線。', onboardingModelsChanged: '可用模型已變更,請重新驗證後選擇。', onboardingNoModels: '未發現可用模型,目前未建立連線。', onboardingSelectModel: '請至少啟用一個模型。',
+    onboardingOutcomeUnknown: '暫時無法確認儲存結果', onboardingOutcomeUnknownDetail: '請勿再次新增,以免建立重複連線。重新載入連線列表並檢查該連線是否已出現;仍不確定時請先重新連線 Runtime Host。', onboardingReloadConnections: '重新載入連線列表', onboardingRestart: '仍要新增另一個連線',
+    ...zhTwCapabilitiesCopy,
+  },
+  oauthFlow: {
+    refreshFailed: '重新整理登入狀態失敗', accountActionFailed: (name: string) => `${name} 帳號操作失敗`, loginFailedRetry: '登入失敗,請稍後重試。',
+    retry: '請稍後再試。', startFailed: '無法開始登入', startFailedRetry: '無法開始登入,請稍後再試。', openFailed: '無法開啟瀏覽器',
+    openFailedRetry: '無法開啟瀏覽器,請稍後重試。', loginSuccess: '登入成功', bound: (name: string) => `${name} 已繫結本機。`,
+    incomplete: '登入未完成', incompleteRetry: '登入未完成,請重新開啟瀏覽器授權。', loginFailed: '登入失敗',
+    logoutDescription: '將刪除本機儲存的訂閱憑據,之後需要重新登入才能繼續使用這些 OAuth 模型。', logout: '退出登入', cancel: '取消',
+    loggedOut: '已退出登入', credentialsCleared: '本地憑據已清除。', logoutFailed: '退出失敗', logoutFailedRetry: '退出登入失敗,請稍後重試。',
+    serviceUnavailable: '登入服務暫時不可用,請檢查網路後重試。',
+    logoutTitle: (name: string) => `退出 ${name} 登入?`,
+  },
+  oauthSection: {
+    signedIn: '已登入', codexDescription: 'ChatGPT Plus / Pro 訂閱帳號登入。', xaiDescription: 'SuperGrok / X Premium 帳號登入。',
+    configuredConnections: (count: number) => `已有 ${count} 個連線 · 新增另一個帳號`,
+    copilotDescription: '匯入相容 GitHub 憑據連線 Copilot 訂閱。', serviceUnavailable: '登入服務暫時不可用,請檢查網路後重試。',
+    aria: 'OAuth 登入',
+    staleState: 'OAuth 登入狀態暫時沒重新整理成功,已保留上一次狀態。',
+    codexDetail: '點選下方按鈕開啟裝置授權頁,並在頁面中輸入這裡顯示的登入碼。', xaiDetail: '點選下方按鈕開啟瀏覽器登入,授權完成後會自動回寫。', deviceCode: '登入碼:',
+    openingBrowser: '開啟瀏覽器…', waitingAuthorization: '等待瀏覽器授權…', logout: '退出登入', loggingOut: '退出中…',
+    copilotSubtitle: '匯入相容的 GitHub 登入;token 不會暴露給渲染程序。', copilotImported: '已匯入 GitHub Copilot 訂閱帳號。',
+    copilotSetup: '請設定具有 Copilot Requests 權限的 fine-grained PAT;普通 gh auth login 可能不包含該權限。', importing: '匯入中…',
+    copilotSignIn: '使用 GitHub 登入', copilotActionFailed: 'GitHub Copilot 帳號操作失敗',
+    copilotSignInDisabledHint: '本機未啟用 GitHub 登入;請改用匯入相容憑據,或由管理員啟用後重試。',
+    importCredential: '匯入相容憑據',
+    loadingAccount: '正在載入帳號狀態…', authorizing: '請在彈出的瀏覽器視窗完成登入。', refreshing: '正在重新整理存取權杖…', refreshTokenFailed: '權杖重新整理失敗,請重新登入。',
+    cardAria: (intent: 'add' | 'import' | 'manage', name: string, status: string | undefined, description: string) => `${intent === 'add' ? '新增' : intent === 'import' ? '匯入' : '管理'}帳號連線:${name}${status ? `,狀態:${status}` : ''},${description.replace(/[。.!!??]+$/u, '')}`,
+    connectTitle: (name: string) => `連線 ${name}`, addAccountTitle: (name: string) => `新增 ${name} 帳號`, login: (name: string) => `登入 ${name}`, loginAndAdd: '登入並新增', signedOut: (name: string) => `${name} 尚未登入。`,
+    storageFailed: (name: string) => `${name} 本地憑據讀取失敗,請重新登入。`, providerUnavailable: (name: string) => `${name} 已登入,但目前 provider 狀態不可用。`,
+  },
+} as const;
 
 export type ProviderSettingsCopy = WidenCopy<typeof zhCopy>;
 
@@ -416,7 +609,11 @@ const enCopy: ProviderSettingsCopy = {
   },
 };
 
-const PROVIDER_SETTINGS_COPY = { zh: zhCopy, en: enCopy } satisfies UiCatalog<ProviderSettingsCopy>;
+const PROVIDER_SETTINGS_COPY = {
+  'zh-CN': zhCopy,
+  'zh-TW': zhTwCopy,
+  en: enCopy,
+} satisfies UiCatalog<ProviderSettingsCopy>;
 
 export function getProviderSettingsCopy(locale: UiLocale): ProviderSettingsCopy {
   return PROVIDER_SETTINGS_COPY[locale];
diff --git a/apps/desktop/src/renderer/features/connection-settings/subscription-result-message.ts b/apps/desktop/src/renderer/features/connection-settings/subscription-result-message.ts
new file mode 100644
index 0000000000..125e073d68
--- /dev/null
+++ b/apps/desktop/src/renderer/features/connection-settings/subscription-result-message.ts
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { generalizedErrorMessageForLocale, redactSecrets } from '@maka/core/redaction';
+import type { UiLocale } from '@maka/core/ui-locale';
+
+export function subscriptionResultMessage(
+  message: string | undefined,
+  fallback: string,
+  locale: UiLocale = 'zh-CN',
+  reason?: string,
+): string {
+  const raw = redactSecrets(message ?? '').trim();
+  // The Host refuses an enrollment this install has not opted into and says so
+  // with a typed reason. Read the reason, not the English message: a reworded
+  // string or an added locale must not silently disable this branch. The
+  // message match stays only as a fallback for callers without a typed reason.
+  if (reason === 'experimental_disabled' || /enrollment is disabled for this provider/i.test(raw)) {
+    if (locale === 'zh-CN') return '本机未启用该账号登录方式;可改用导入兼容凭据,或由管理员启用后重试。';
+    if (locale === 'zh-TW') return '本機未啟用該帳號登入方式;可改用匯入相容憑據,或由管理員啟用後重試。';
+    return 'This sign-in is not enabled on this install. Import a compatible credential instead, or ask an operator to enable it.';
+  }
+  if (!raw) return fallback;
+  if (/already in progress|superseded by a new attempt/i.test(raw)) {
+    if (locale === 'zh-CN') return '上一轮浏览器登录仍在进行或已切换,请再点一次登录,或稍后再试。';
+    if (locale === 'zh-TW') return '上一輪瀏覽器登入仍在進行或已切換,請再按一次登入,或稍後再試。';
+    return 'A previous browser login is still running or was superseded. Try logging in again shortly.';
+  }
+  if (/did not present OAuth|no matching OAuth presentation/i.test(raw)) {
+    if (locale === 'zh-CN') return '无法打开系统浏览器完成登录,请检查是否拦截了弹窗后重试。';
+    if (locale === 'zh-TW') return '無法開啟系統瀏覽器完成登入,請檢查是否封鎖了彈出式視窗後再試。';
+    return 'Could not open the system browser for login. Check popup blockers and try again.';
+  }
+  const classified = generalizedErrorMessageForLocale(new Error(raw), '', locale);
+  if (classified) return classified;
+  return locale === 'zh-CN' || !/[\u4e00-\u9fff]/.test(raw) ? raw : fallback;
+}
diff --git a/apps/desktop/src/renderer/features/conversation/model/task-readiness-notice.ts b/apps/desktop/src/renderer/features/conversation/model/task-readiness-notice.ts
index 6ebe6b11f9..df90d2a57e 100644
--- a/apps/desktop/src/renderer/features/conversation/model/task-readiness-notice.ts
+++ b/apps/desktop/src/renderer/features/conversation/model/task-readiness-notice.ts
@@ -86,36 +86,56 @@ function noticeForBlocker(
   locale: UiLocale,
 ): TaskReadinessNotice {
   if (blocker.id === 'runtime') {
-    return locale === 'zh'
-      ? {
-          tone: 'destructive',
-          title: 'Maka 运行服务暂时不可用。',
-          description: '任务尚未提交。重新检测运行服务后再试。',
-          actionLabel: '重新检测',
-          action: 'retry',
-        }
-      : {
-          tone: 'destructive',
-          title: 'The Maka runtime is unavailable.',
-          description: 'The task was not submitted. Check the runtime again before retrying.',
-          actionLabel: 'Check again',
-          action: 'retry',
-        };
-  }
-  const action = blocker.repairTarget?.kind === 'workspace_picker' ? 'workspace_picker' : 'retry';
-  return locale === 'zh'
-    ? {
+    if (locale === 'zh-CN') {
+      return {
         tone: 'destructive',
-        title: '当前任务的工作区不可用。',
-        description: '原目录可能已移动、删除或无法访问。请选择可用工作区。',
-        actionLabel: action === 'workspace_picker' ? '选择工作区' : '重新检测',
-        action,
-      }
-    : {
+        title: 'Maka 运行服务暂时不可用。',
+        description: '任务尚未提交。重新检测运行服务后再试。',
+        actionLabel: '重新检测',
+        action: 'retry',
+      };
+    }
+    if (locale === 'zh-TW') {
+      return {
         tone: 'destructive',
-        title: 'This task workspace is unavailable.',
-        description: 'The folder may have moved, been deleted, or become inaccessible. Choose an available workspace.',
-        actionLabel: action === 'workspace_picker' ? 'Choose workspace' : 'Check again',
-        action,
+        title: 'Maka 執行服務暫時無法使用。',
+        description: '任務尚未送出。重新檢查執行服務後再試。',
+        actionLabel: '重新檢查',
+        action: 'retry',
       };
+    }
+    return {
+      tone: 'destructive',
+      title: 'The Maka runtime is unavailable.',
+      description: 'The task was not submitted. Check the runtime again before retrying.',
+      actionLabel: 'Check again',
+      action: 'retry',
+    };
+  }
+  const action = blocker.repairTarget?.kind === 'workspace_picker' ? 'workspace_picker' : 'retry';
+  if (locale === 'zh-CN') {
+    return {
+      tone: 'destructive',
+      title: '当前任务的工作区不可用。',
+      description: '原目录可能已移动、删除或无法访问。请选择可用工作区。',
+      actionLabel: action === 'workspace_picker' ? '选择工作区' : '重新检测',
+      action,
+    };
+  }
+  if (locale === 'zh-TW') {
+    return {
+      tone: 'destructive',
+      title: '目前任務的工作區無法使用。',
+      description: '原始資料夾可能已移動、刪除或無法存取。請選擇可用的工作區。',
+      actionLabel: action === 'workspace_picker' ? '選擇工作區' : '重新檢查',
+      action,
+    };
+  }
+  return {
+    tone: 'destructive',
+    title: 'This task workspace is unavailable.',
+    description: 'The folder may have moved, been deleted, or become inaccessible. Choose an available workspace.',
+    actionLabel: action === 'workspace_picker' ? 'Choose workspace' : 'Check again',
+    action,
+  };
 }
diff --git a/apps/desktop/src/renderer/features/runtime-host-management/index.ts b/apps/desktop/src/renderer/features/runtime-host-management/index.ts
index 005423d585..7b16b77ad4 100644
--- a/apps/desktop/src/renderer/features/runtime-host-management/index.ts
+++ b/apps/desktop/src/renderer/features/runtime-host-management/index.ts
@@ -17,16 +17,19 @@
  * under the License.
  */
 
-export { RuntimeHostPeerMeshDialog } from './ui/runtime-host-peer-mesh-dialog';
-export { PeerMeshPeerIdButton } from './ui/peer-mesh-peer-id-button';
-export { RuntimeHostAddComputerMenu } from './ui/runtime-host-add-computer-menu';
-export { RuntimeHostConnectionCodeButton } from './ui/runtime-host-connection-code-button';
-export { RuntimeHostConnectionCodeDialog } from './ui/runtime-host-connection-code-dialog';
+export {
+  getRuntimeHostPeerMeshCopy,
+  RuntimeHostPeerMeshDialog,
+} from './ui/runtime-host-peer-mesh-dialog.js';
+export { PeerMeshPeerIdButton } from './ui/peer-mesh-peer-id-button.js';
+export { RuntimeHostAddComputerMenu } from './ui/runtime-host-add-computer-menu.js';
+export { RuntimeHostConnectionCodeButton } from './ui/runtime-host-connection-code-button.js';
+export { RuntimeHostConnectionCodeDialog } from './ui/runtime-host-connection-code-dialog.js';
 export {
   RuntimeHostPairingRecoveryButton,
   RuntimeHostProfileMoreMenu,
-} from './ui/runtime-host-profile-pairing-actions';
-export type { RuntimeHostPairingActionCopy } from './ui/runtime-host-profile-pairing-actions';
-export { RuntimeHostManagementServicesProvider } from './services-context';
-export { PeerMeshOperationOutcomeUnknownError } from './ports';
-export type { RuntimeHostManagementServices } from './ports';
+} from './ui/runtime-host-profile-pairing-actions.js';
+export type { RuntimeHostPairingActionCopy } from './ui/runtime-host-profile-pairing-actions.js';
+export { RuntimeHostManagementServicesProvider } from './services-context.js';
+export { PeerMeshOperationOutcomeUnknownError } from './ports.js';
+export type { RuntimeHostManagementServices } from './ports.js';
diff --git a/apps/desktop/src/renderer/features/runtime-host-management/ui/runtime-host-peer-mesh-dialog.tsx b/apps/desktop/src/renderer/features/runtime-host-management/ui/runtime-host-peer-mesh-dialog.tsx
index 49788c0f29..ecd956369b 100644
--- a/apps/desktop/src/renderer/features/runtime-host-management/ui/runtime-host-peer-mesh-dialog.tsx
+++ b/apps/desktop/src/renderer/features/runtime-host-management/ui/runtime-host-peer-mesh-dialog.tsx
@@ -25,6 +25,7 @@ import { Layout, LayoutContent, LayoutFooter } from '@astryxdesign/core/Layout';
 import { HStack } from '@astryxdesign/core/Stack';
 import { Tooltip } from '@astryxdesign/core/Tooltip';
 import type { PeerMeshProjection, PeerMeshQueryResult } from '@maka/runtime-host/protocol';
+import type { UiLocale } from '@maka/core/ui-locale';
 import {
   decodeRuntimeHostWebRtcStunPolicy,
   type RuntimeHostWebRtcStunPolicy,
@@ -126,7 +127,7 @@ export function RuntimeHostPeerMeshDialog(props: {
   readonly onClose: () => void;
 }) {
   const locale = useUiLocale();
-  const copy = peerMeshCopy(locale);
+  const copy = getRuntimeHostPeerMeshCopy(locale);
   const toast = useToast();
   const services = useRuntimeHostManagementServices().peerMesh;
   const [snapshot, setSnapshot] = useState<PeerMeshQueryResult>();
@@ -855,7 +856,7 @@ export function RuntimeHostPeerMeshDialog(props: {
 function PeerMeshAdvancedSettings(props: {
   readonly localPeerId?: string;
   readonly connectivity?: DesktopConnectivityControls;
-  readonly copy: ReturnType<typeof peerMeshCopy>;
+  readonly copy: ReturnType<typeof getRuntimeHostPeerMeshCopy>;
   readonly onCopyPeerId: (peerId: string) => void;
 }) {
   const { connectivity } = props;
@@ -1007,7 +1008,7 @@ function PeerMeshAdvancedSettings(props: {
 
 function Overview(props: {
   readonly snapshot: PeerMeshQueryResult | undefined;
-  readonly copy: ReturnType<typeof peerMeshCopy>;
+  readonly copy: ReturnType<typeof getRuntimeHostPeerMeshCopy>;
   readonly working: boolean;
   readonly localPeerLabel: string;
   readonly onInvite: (meshId: string) => void;
@@ -1232,7 +1233,7 @@ function Overview(props: {
 function UnavailableEndpoint(props: {
   readonly setup: ManagedHostPeerSetup;
   readonly working: boolean;
-  readonly copy: ReturnType<typeof peerMeshCopy>;
+  readonly copy: ReturnType<typeof getRuntimeHostPeerMeshCopy>;
   readonly onEnable: () => void;
   readonly onInspect?: () => void;
   readonly onRefresh: () => void;
@@ -1315,7 +1316,7 @@ function UnavailableEndpoint(props: {
 function JoinView(props: {
   readonly value: string;
   readonly working: boolean;
-  readonly copy: ReturnType<typeof peerMeshCopy>;
+  readonly copy: ReturnType<typeof getRuntimeHostPeerMeshCopy>;
   readonly onChange: (value: string) => void;
 }) {
   return (
@@ -1347,7 +1348,7 @@ function JoinView(props: {
 
 function InvitationView(props: {
   readonly invitation: Extract<PeerMeshDialogView, { readonly kind: 'invitation' }>;
-  readonly copy: ReturnType<typeof peerMeshCopy>;
+  readonly copy: ReturnType<typeof getRuntimeHostPeerMeshCopy>;
 }) {
   return (
     <div className="settingsPeerMeshFocusedView">
@@ -1398,7 +1399,7 @@ function InvitationView(props: {
 function MeshCard(props: {
   readonly mesh: PeerMeshProjection;
   readonly transit: PeerMeshQueryResult['transit'];
-  readonly copy: ReturnType<typeof peerMeshCopy>;
+  readonly copy: ReturnType<typeof getRuntimeHostPeerMeshCopy>;
   readonly working: boolean;
   readonly onInvite: () => void;
   readonly onRemove: (peerId: string) => void;
@@ -1698,7 +1699,7 @@ function TransitMetric(props: { readonly label: string; readonly value: string }
 
 function PeerIdText(props: {
   readonly peerId: string;
-  readonly copy: ReturnType<typeof peerMeshCopy>;
+  readonly copy: ReturnType<typeof getRuntimeHostPeerMeshCopy>;
   readonly onCopy: (peerId: string) => void;
 }) {
   return (
@@ -1717,7 +1718,7 @@ function PeerIdText(props: {
 
 function MeshIdText(props: {
   readonly meshId: string;
-  readonly copy: ReturnType<typeof peerMeshCopy>;
+  readonly copy: ReturnType<typeof getRuntimeHostPeerMeshCopy>;
   readonly onCopy: (meshId: string) => void;
 }) {
   return (
@@ -1803,9 +1804,8 @@ function abbreviate(peerId: string): string {
   return peerId.length <= 22 ? peerId : `${peerId.slice(0, 11)}…${peerId.slice(-7)}`;
 }
 
-function peerMeshCopy(locale: string) {
-  const zh = locale.startsWith('zh');
-  return zh
+export function getRuntimeHostPeerMeshCopy(locale: UiLocale) {
+  return locale === 'zh-CN'
     ? {
         title: 'Peer Mesh',
         experimental: '实验性',
@@ -1881,6 +1881,9 @@ function peerMeshCopy(locale: string) {
         meshIdHelp: '用于诊断和识别此 Mesh;点击 ID 可复制完整值。',
         thisRuntimeHost: '本机 Runtime Host',
         thisDesktop: '本机 Desktop',
+        peerPathDirect: '直连',
+        peerPathTransit: '成员转发',
+        peerPathOther: '其他',
         displayName: '在 Mesh 中显示的名称',
         meshDisplayName: 'Mesh 名称',
         unnamedMesh: '未命名 Mesh',
@@ -1888,6 +1891,7 @@ function peerMeshCopy(locale: string) {
         renameMesh: '修改 Mesh 名称',
         save: '保存',
         peerIdCopied: 'Peer ID 已复制',
+        peerIdCopyFailed: '无法复制 Peer ID',
         meshIdCopied: 'Mesh ID 已复制',
         copyPeerId: (value: string) => `复制完整 Peer ID:${value}`,
         copyMeshId: (value: string) => `复制完整 Mesh ID:${value}`,
@@ -1965,7 +1969,170 @@ function peerMeshCopy(locale: string) {
         localHostMissing: '本机 Runtime Host 尚未加入',
         localHostMissingHint: '加入后,其他成员才能通过此 Mesh 连接本机分享的任务。',
       }
-    : {
+    : locale === 'zh-TW'
+      ? {
+          title: 'Peer Mesh',
+          experimental: '實驗性',
+          failed: 'Peer Mesh 操作失敗',
+          invalidResult: 'Peer Mesh 回傳了無效結果',
+          unknownError: 'Peer Mesh 操作失敗',
+          outcomeUnknown: 'Host 可能已完成此操作。已重新整理目前狀態,請確認後再重試。',
+          invitationOutcomeUnknown:
+            'Host 可能已建立邀請碼,但代碼未能回傳且無法復原。它會自動過期;建立新邀請前請先檢查待使用邀請數量。',
+          outcomeUnknownRefreshFailed:
+            'Host 可能已完成此操作,但目前狀態也無法重新整理。請恢復連線並重新整理後再重試。',
+          unavailable: '目前 endpoint 不支援 Peer Mesh',
+          loading: '正在讀取 Mesh 狀態…',
+          checkingPeerConnection: '正在檢查此 Runtime Host 的 Peer 連線…',
+          peerConnectionDisabled: '此 Runtime Host 尚未開啟 Peer 連線',
+          peerConnectionDisabledHint:
+            '開啟後,此 Host 才能建立或加入 Mesh;現有 SSH 連線會繼續保留。',
+          peerConnectionDisableProfileFirst:
+            '此 Host 的 Direct peer 連線正在使用中,請先在 Host 列表中將它停用。',
+          enablePeerConnection: '開啟 Peer 連線',
+          peerConnectionStarting: 'Peer 連線已開啟,Mesh endpoint 正在就緒',
+          peerConnectionStartingHint: '通常只需幾秒;也可以立即重新檢查。',
+          peerConnectionUpgradeRequired: '此 Runtime Host 版本尚不支援 Peer Mesh 管理',
+          peerConnectionUpgradeRequiredHint: '請先更新此 Host,再回來開啟 Peer 連線。',
+          working: {
+            refresh: '正在重新整理 Peer Mesh…',
+            create: '正在建立 Mesh…',
+            join: '正在加入 Mesh…',
+            invite: '正在準備邀請…',
+            'add-host': '正在將 Runtime Host 加入 Mesh…',
+            'enable-peer': '正在為 Runtime Host 開啟 Peer 連線…',
+            update: '正在更新 Mesh…',
+            rename: '正在儲存名稱…',
+          },
+          settling: '正在確認最終狀態…',
+          endpoint: '管理對象',
+          desktopEndpoint: 'Desktop Client',
+          hostEndpoint: '本機 Runtime Host',
+          desktopEndpointHelp: '此 Client 用於連線至 Mesh 中的 Runtime Host。',
+          hostEndpointHelp: '此 Host 加入後,其他成員才能連線至本機分享的任務。',
+          advancedSettings: '進階設定',
+          technicalDetails: '身分與連線設定',
+          connectivityAutomatic: '自動連線(建議)',
+          connectivityKnownRoutesOnly: '僅使用已知路徑',
+          connectivityCustom: '自訂位址探索',
+          restartRequired: '需要重新啟動',
+          adaptiveConnectivity: '自適應連線',
+          adaptiveConnectivityHelp:
+            'Maka 會自動競速可用的直接連線方式,並在獲准時使用成員轉送;此處不需要選擇特定通訊協定。',
+          connectivityPolicyLoading: '正在讀取連線策略…',
+          connectivityPolicyLoadFailed: '無法讀取連線策略',
+          connectivityPolicySaveFailed: '無法儲存連線策略',
+          restoreDefaultConnectivityPolicy: '恢復預設設定',
+          connectivityPolicyRestartRequired:
+            '重新啟動 Maka 後,已儲存的連線策略變更會套用至新連線。',
+          publicAddressDiscovery: '公網位址探索',
+          publicStunDefault: '公共 STUN(建議)',
+          publicStunDisabled: '不使用公共 STUN',
+          publicStunCustom: '自訂 STUN',
+          customStunUrls: 'STUN 位址',
+          customStunUrlsInvalid: '請輸入以逗號分隔的 stun:主機[:連接埠] 位址,最多 8 個。',
+          publicStunDefaultHelp:
+            '使用 Cloudflare 公共 STUN 盡力探索公網對映。它不承載 Maka 流量,但提供者可觀察來源 IP 和請求時間;Maka 不保證其可用性。',
+          publicStunDisabledHelp:
+            '僅嘗試本機位址和其他已知直接連線路徑;跨 NAT 的直接連線成功率可能降低。',
+          publicStunCustomHelp:
+            '使用逗號分隔的 stun: 位址。STUN 只探索網路位址,不承載 Session 內容。',
+          saveConnectivityPolicy: '儲存變更',
+          peerId: 'Peer ID',
+          peerIdHelp: '此 endpoint 在 Mesh 中的技術身分;點選 ID 可複製完整值。',
+          meshId: 'Mesh ID',
+          meshIdHelp: '用於診斷和識別此 Mesh;點選 ID 可複製完整值。',
+          thisRuntimeHost: '本機 Runtime Host',
+          thisDesktop: '本機 Desktop',
+          peerPathDirect: '直接連線',
+          peerPathTransit: '成員轉送',
+          peerPathOther: '其他',
+          displayName: '在 Mesh 中顯示的名稱',
+          meshDisplayName: 'Mesh 名稱',
+          unnamedMesh: '未命名 Mesh',
+          rename: '修改名稱',
+          renameMesh: '修改 Mesh 名稱',
+          save: '儲存',
+          peerIdCopied: 'Peer ID 已複製',
+          peerIdCopyFailed: '無法複製 Peer ID',
+          meshIdCopied: 'Mesh ID 已複製',
+          copyPeerId: (value: string) => `複製完整 Peer ID:${value}`,
+          copyMeshId: (value: string) => `複製完整 Mesh ID:${value}`,
+          empty: '建立你的第一個 Mesh',
+          emptyHint: '建立新的 Mesh,或透過一次性邀請碼加入。',
+          meshes: 'Mesh',
+          mesh: 'Mesh',
+          members: '成員',
+          activeMeshCount: (value: number) => `${value} 個使用中`,
+          showClosedMeshes: (value: number) => `顯示已關閉(${value})`,
+          noActiveMeshes: '沒有使用中的 Mesh',
+          noActiveMeshesHint: '已關閉的 Mesh 預設隱藏;可透過上方篩選查看。',
+          authority: '管理者',
+          member: '成員',
+          closed: '已關閉',
+          memberCount: (value: number) => `${value} 個成員`,
+          pending: (value: number) => `${value} 個待使用邀請`,
+          transit: '成員轉送',
+          transitHelp: '允許此 Mesh 的成員透過本機建立連線;會使用本機頻寬。',
+          transitToggle: '為此 Mesh 提供轉送',
+          transitStatus: '成員轉送狀態',
+          transitLimitsLabel: '成員轉送限制',
+          transitLimits: (value: PeerMeshQueryResult['transit']) =>
+            value
+              ? `固定上限:每位成員 ${value.maxCircuitsPerPeer} 條連線,每條最長 ${formatHours(value.maxCircuitDurationSeconds)},最多 ${formatMebibytes(value.maxCircuitBytes)}。一次只能為一個 Mesh 開啟。`
+              : '成員轉送使用固定資源上限,一次只能為一個 Mesh 開啟。',
+          allowedMembers: '允許成員',
+          reservations: 'Reservation',
+          circuits: '連線',
+          routeState: {
+            local: '本機',
+            route_available: '路徑可用',
+            coordination_only: '僅協調路徑',
+            stale: '路徑已過期',
+            unknown: '路徑未知',
+          },
+          endpointKind: {
+            client: 'Client',
+            host: 'Runtime Host',
+            unknown: '未識別 Peer',
+          },
+          endpointKindHelp: {
+            client: 'Client 是操作介面:它連線至 Host、瀏覽任務並發起操作,本身不持有任務。',
+            host: 'Runtime Host 持有任務和執行狀態,並執行經過授權的工作。',
+            unknown: '此 Peer 尚未回報它是 Client 或 Runtime Host,通常來自舊版本。',
+          },
+          joinTitle: '加入 Mesh',
+          joinHint: '貼上另一個 Peer 產生的一次性邀請碼。',
+          joinCode: '邀請碼',
+          join: '加入',
+          joinMesh: '加入 Mesh',
+          invite: '邀請成員',
+          invitationTitle: '邀請成員',
+          invitationFor: (value: string) => `Mesh ${value}`,
+          invitationWarning: '此代碼只能使用一次;取得代碼的人可以讓一個 peer 加入此 Mesh。',
+          invitationDirectOnly:
+            '尚未連線至協調節點。此邀請碼只包含直接位址,跨 NAT 時可能無法連線。',
+          invitationExpires: (value: string) => `有效期限至 ${value}`,
+          invitationCopied: '邀請碼已複製',
+          copyInvitation: '複製邀請碼',
+          create: '建立 Mesh',
+          refresh: '重新整理',
+          back: '返回',
+          leave: '退出 Mesh',
+          closeMesh: '關閉 Mesh',
+          remove: '移除成員',
+          cancel: '取消',
+          closeConfirm: '關閉這個 Mesh?',
+          leaveConfirm: '退出這個 Mesh?',
+          removeConfirm: '移除這個成員?',
+          meshActions: 'Mesh 操作',
+          memberActions: (peerId: string) => `${peerId} 的操作`,
+          addLocalHost: '新增本機 Runtime Host',
+          localRuntimeHost: 'Runtime Host',
+          localHostMissing: '本機 Runtime Host 尚未加入',
+          localHostMissingHint: '加入後,其他成員才能透過此 Mesh 連線至本機分享的任務。',
+        }
+      : {
         title: 'Peer Mesh',
         experimental: 'Experimental',
         failed: 'Peer Mesh operation failed',
@@ -2041,6 +2208,9 @@ function peerMeshCopy(locale: string) {
         meshIdHelp: 'Used to identify and diagnose this Mesh. Select the ID to copy its full value.',
         thisRuntimeHost: 'This Runtime Host',
         thisDesktop: 'This Desktop',
+        peerPathDirect: 'Direct',
+        peerPathTransit: 'Member transit',
+        peerPathOther: 'Other',
         displayName: 'Name shown in the Mesh',
         meshDisplayName: 'Mesh name',
         unnamedMesh: 'Unnamed Mesh',
@@ -2048,6 +2218,7 @@ function peerMeshCopy(locale: string) {
         renameMesh: 'Rename Mesh',
         save: 'Save',
         peerIdCopied: 'Peer ID copied',
+        peerIdCopyFailed: 'Could not copy Peer ID',
         meshIdCopied: 'Mesh ID copied',
         copyPeerId: (value: string) => `Copy full Peer ID: ${value}`,
         copyMeshId: (value: string) => `Copy full Mesh ID: ${value}`,
diff --git a/apps/desktop/src/renderer/features/session-navigation/model/session-project-grouping.ts b/apps/desktop/src/renderer/features/session-navigation/model/session-project-grouping.ts
index 8fa85cf5a6..9499e88782 100644
--- a/apps/desktop/src/renderer/features/session-navigation/model/session-project-grouping.ts
+++ b/apps/desktop/src/renderer/features/session-navigation/model/session-project-grouping.ts
@@ -28,7 +28,7 @@ const UNGROUPED_KEY = '__ungrouped__';
 export function deriveProjectGroups(
   sessions: ReadonlyArray<SessionSummary>,
   projects: ReadonlyArray<ProjectRecord>,
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): SessionHistoryGroup[] {
   const sessionsByProject = new Map<string, SessionSummary[]>();
   const canonicalProjectIds = new Map<string, string>();
diff --git a/apps/desktop/src/renderer/features/workbar/testing.ts b/apps/desktop/src/renderer/features/workbar/testing.ts
index e7f38293e8..4c33d49d3e 100644
--- a/apps/desktop/src/renderer/features/workbar/testing.ts
+++ b/apps/desktop/src/renderer/features/workbar/testing.ts
@@ -53,6 +53,7 @@ export * from './tools/terminal/session-terminal-hydration.js';
 export * from './tools/terminal/session-terminal-query.js';
 export * from './tools/terminal/session-terminal-frame.js';
 export * from './tools/inspector/use-session-trace.js';
+export * from './tools/tasks/use-session-todo.js';
 export * from './controller/use-workbar-controller.js';
 export { SideChatCloseConfirmation } from './ui/side-chat-close-confirmation.js';
 
diff --git a/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx b/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx
index c81f06ce6e..89566fe11c 100644
--- a/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx
+++ b/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx
@@ -63,7 +63,7 @@ import {
 import type { ArtifactDescriptor, ArtifactKind } from '@maka/core/artifacts';
 import type { UiLocale } from '@maka/core/ui-locale';
 import { formatRelativeTimestamp } from '@maka/core/relative-time';
-import { generalizedErrorMessage, generalizedErrorMessageChinese, redactSecrets } from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale, redactSecrets } from '@maka/core/redaction';
 import {
   Badge,
   Banner,
@@ -631,11 +631,9 @@ function saveArtifactFailureCopy(reason: string, copy: ArtifactCopy): string {
 function artifactActionErrorMessage(error: unknown, locale: UiLocale, copy: ArtifactCopy): string {
   const raw = redactSecrets(error instanceof Error ? error.message : String(error ?? '')).trim();
   if (!raw) return copy.pane.actionFailed;
-  const classified = locale === 'zh'
-    ? generalizedErrorMessageChinese(new Error(raw), '')
-    : generalizedErrorMessage(new Error(raw), '');
+  const classified = generalizedErrorMessageForLocale(new Error(raw), '', locale);
   if (classified) return classified;
-  return locale === 'zh' && /[\u4e00-\u9fff]/.test(raw) ? raw : copy.pane.actionFailed;
+  return locale === 'zh-CN' && /[\u4e00-\u9fff]/.test(raw) ? raw : copy.pane.actionFailed;
 }
 
 function KindIcon(props: { kind: ArtifactKind }) {
diff --git a/apps/desktop/src/renderer/features/workbar/tools/inspector/use-session-trace.ts b/apps/desktop/src/renderer/features/workbar/tools/inspector/use-session-trace.ts
index 92df88050c..560ff4f47f 100644
--- a/apps/desktop/src/renderer/features/workbar/tools/inspector/use-session-trace.ts
+++ b/apps/desktop/src/renderer/features/workbar/tools/inspector/use-session-trace.ts
@@ -18,7 +18,7 @@
  */
 
 import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { generalizedErrorMessage, generalizedErrorMessageChinese } from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
 import { type UiLocale } from '@maka/core/ui-locale';
 import {
   mergeSessionTraces,
@@ -145,9 +145,7 @@ export function useSessionTrace(
                   loading: false,
                   loadingEarlier: false,
                   error:
-                    copy.locale === 'zh'
-                      ? generalizedErrorMessageChinese(error, copy.loadFailed)
-                      : generalizedErrorMessage(error, copy.loadFailed),
+                    generalizedErrorMessageForLocale(error, copy.loadFailed, copy.locale),
                 }
               : current,
           );
@@ -202,9 +200,7 @@ export function useSessionTrace(
                   loading: false,
                   loadingEarlier: false,
                   error:
-                    copy.locale === 'zh'
-                      ? generalizedErrorMessageChinese(error, copy.loadFailed)
-                      : generalizedErrorMessage(error, copy.loadFailed),
+                    generalizedErrorMessageForLocale(error, copy.loadFailed, copy.locale),
                 }
               : current,
           );
diff --git a/apps/desktop/src/renderer/features/workbar/tools/review/session-review-panel.tsx b/apps/desktop/src/renderer/features/workbar/tools/review/session-review-panel.tsx
index 3f6884d0c0..a31b9f34cf 100644
--- a/apps/desktop/src/renderer/features/workbar/tools/review/session-review-panel.tsx
+++ b/apps/desktop/src/renderer/features/workbar/tools/review/session-review-panel.tsx
@@ -27,7 +27,7 @@ import { Section } from '@astryxdesign/core/Section';
 import { Skeleton } from '@astryxdesign/core/Skeleton';
 import { Text } from '@astryxdesign/core/Text';
 import { redactSecrets as displayRedactSecrets } from '@maka/core/display-redaction';
-import { generalizedErrorMessage, generalizedErrorMessageChinese } from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
 import { type GitReviewReadResult } from '@maka/core/git-review';
 import { DiffCodePreview, useUiLocale } from '@maka/ui';
 import { ICON_SIZE, GitBranch } from '@maka/ui/icons';
@@ -76,9 +76,7 @@ export function SessionReviewPanel(props: {
     } catch (nextError) {
       if (revision === revisionRef.current) {
         setError(
-          locale === 'zh'
-            ? generalizedErrorMessageChinese(nextError, copy.loadFailed)
-            : generalizedErrorMessage(nextError, copy.loadFailed),
+          generalizedErrorMessageForLocale(nextError, copy.loadFailed, locale),
         );
       }
     } finally {
diff --git a/apps/desktop/src/renderer/features/workbar/tools/side-chat/quote-companion-panel.tsx b/apps/desktop/src/renderer/features/workbar/tools/side-chat/quote-companion-panel.tsx
index d5ff70372d..d49fafb4a0 100644
--- a/apps/desktop/src/renderer/features/workbar/tools/side-chat/quote-companion-panel.tsx
+++ b/apps/desktop/src/renderer/features/workbar/tools/side-chat/quote-companion-panel.tsx
@@ -34,10 +34,7 @@ import {
   type ComposerHandle,
 } from '@maka/ui';
 import type { SessionSummary } from '@maka/core/session';
-import {
-  generalizedErrorMessage,
-  generalizedErrorMessageChinese,
-} from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
 import { useQuoteCompanion } from './use-quote-companion';
 import { useComposerAttachments } from '../../../../use-composer-attachments';
 import { useComposerMentionsContext } from '../../../../composer-mentions.js';
@@ -182,9 +179,7 @@ export function QuoteCompanionPanel(props: {
       const compactCopy = getDesktopConversationCopy(locale).quoteCompanion;
       toast.error(
         compactCopy.compactErrorTitle,
-        locale === 'zh'
-          ? generalizedErrorMessageChinese(error, compactCopy.compactErrorFallback)
-          : generalizedErrorMessage(error, compactCopy.compactErrorFallback),
+        generalizedErrorMessageForLocale(error, compactCopy.compactErrorFallback, locale),
         undefined,
         { sessionId },
       );
diff --git a/apps/desktop/src/renderer/features/workbar/tools/tasks/use-session-todo.ts b/apps/desktop/src/renderer/features/workbar/tools/tasks/use-session-todo.ts
index fd2dc7d7df..9aa01b000d 100644
--- a/apps/desktop/src/renderer/features/workbar/tools/tasks/use-session-todo.ts
+++ b/apps/desktop/src/renderer/features/workbar/tools/tasks/use-session-todo.ts
@@ -18,7 +18,7 @@
  */
 
 import { useCallback, useEffect, useRef, useState } from 'react';
-import { generalizedErrorMessage, generalizedErrorMessageChinese } from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
 import type { SessionTodoItem } from '@maka/core/session-todo';
 import type { UiLocale } from '@maka/ui';
 import { useWorkbarServices } from '../../services-context.js';
@@ -58,10 +58,7 @@ export function useSessionTodo(
           sessionId: targetSessionId,
           items: current.sessionId === targetSessionId ? current.items : [],
           loading: false,
-          error:
-            copy.locale === 'zh'
-              ? generalizedErrorMessageChinese(error, copy.loadFailed)
-              : generalizedErrorMessage(error, copy.loadFailed),
+          error: generalizedErrorMessageForLocale(error, copy.loadFailed, copy.locale),
         }));
       },
     );
diff --git a/apps/desktop/src/renderer/features/workbar/tools/terminal/session-terminal-panel.tsx b/apps/desktop/src/renderer/features/workbar/tools/terminal/session-terminal-panel.tsx
index 7ebf7c34bb..86593ba060 100644
--- a/apps/desktop/src/renderer/features/workbar/tools/terminal/session-terminal-panel.tsx
+++ b/apps/desktop/src/renderer/features/workbar/tools/terminal/session-terminal-panel.tsx
@@ -20,10 +20,7 @@
 import { useEffect, useRef, useState } from 'react';
 import { Banner } from '@astryxdesign/core/Banner';
 import { EmptyState } from '@astryxdesign/core/EmptyState';
-import {
-  generalizedErrorMessage,
-  generalizedErrorMessageChinese,
-} from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
 import { useUiLocale } from '@maka/ui';
 import { ICON_SIZE, Terminal as TerminalIcon } from '@maka/ui/icons';
 import { FitAddon } from '@xterm/addon-fit';
@@ -148,9 +145,7 @@ export function SessionTerminalPanel(props: {
         .catch((nextError) => {
           if (disposed || !hydration.isCurrent(epoch)) return;
           setError(
-            locale === 'zh'
-              ? generalizedErrorMessageChinese(nextError, copy.loadFailed)
-              : generalizedErrorMessage(nextError, copy.loadFailed),
+            generalizedErrorMessageForLocale(nextError, copy.loadFailed, locale),
           );
         });
     };
@@ -169,9 +164,7 @@ export function SessionTerminalPanel(props: {
         .catch((nextError) => {
           if (disposed) return;
           setError(
-            locale === 'zh'
-              ? generalizedErrorMessageChinese(nextError, copy.writeFailed)
-              : generalizedErrorMessage(nextError, copy.writeFailed),
+            generalizedErrorMessageForLocale(nextError, copy.writeFailed, locale),
           );
         });
     });
diff --git a/apps/desktop/src/renderer/locales/artifact-copy.ts b/apps/desktop/src/renderer/locales/artifact-copy.ts
index 97dc2ec0fa..ad73b11379 100644
--- a/apps/desktop/src/renderer/locales/artifact-copy.ts
+++ b/apps/desktop/src/renderer/locales/artifact-copy.ts
@@ -92,7 +92,7 @@ export type ArtifactCopy = {
 };
 
 const ARTIFACT_COPY = {
-  zh: {
+  'zh-CN': {
     pane: {
       refreshFailed: '刷新生成文件失败', openFailed: '无法在 Finder 中打开生成文件', copyFailed: '复制失败',
       readTextFailed: '无法读取生成文件文本内容。', copied: '已复制生成文件文本', saved: '已另存生成文件', saveFailed: '另存失败',
@@ -128,6 +128,42 @@ const ARTIFACT_COPY = {
       unsupported: '暂不支持的预览', name: '名称', unnamed: '(未命名)', type: '类型', size: '大小', openInFinder: '在 Finder 中打开', loadingImage: '加载图片预览…',
     },
   },
+  'zh-TW': {
+    pane: {
+      refreshFailed: '重新整理生成檔案失敗', openFailed: '無法在 Finder 中開啟生成檔案', copyFailed: '複製失敗',
+      readTextFailed: '無法讀取生成檔案文本內容。', copied: '已複製生成檔案文本', saved: '已另存生成檔案', saveFailed: '另存失敗',
+      fallbackName: '生成檔案', deleteTitle: (name) => `刪除 "${name}"`, deleteDescription: '軟刪除:在記錄中標記為已刪除,檔案保留 6 小時可恢復。',
+      delete: '刪除', deleteReadOnly: '刪除(只讀檔案)', cancel: '取消', deleted: (name) => `已刪除 ${name}`, deleteFailed: (name) => `刪除 ${name} 失敗`, panelAria: '生成檔案預覽面板',
+      listLoadFailed: '生成檔案列表載入失敗', retrying: '重試中…', retry: '重試', listAria: '生成檔案列表', deletedBadge: '已刪除',
+      previewNamed: (name) => `預覽 ${name}`, empty: '暫無生成檔案', emptyHint: '助手生成檔案後會顯示在這裡。',
+      back: '返回生成檔案列表', moreActions: (name) => `${name} 的更多操作`,
+      openInFinder: '在 Finder 中開啟', saveAs: '另存為', copy: '複製',
+      saveFailures: { not_found: '生成檔案不存在。', not_allowed: '生成檔案路徑檢查未透過。', deleted: '生成檔案已刪除,不能另存。', write_failed: '目標位置無法寫入。', default: '無法儲存生成檔案。' },
+      actionFailed: '生成檔案操作失敗,請稍後重試。',
+    },
+    preview: {
+      loadingFile: '載入檔案預覽…', loadingDiff: '載入 diff 預覽…', loadingHtml: '載入 HTML 預覽…',
+      externalLinks: (count) => `此預覽中已停用外部連結 · ${count} 個連結`, frameTitle: (name) => `生成檔案預覽 · ${name}`,
+      loadingPdf: '載入 PDF 預覽…', pdfFallback: '如果瀏覽器沒有內建 PDF 渲染,請透過更多選單「在 Finder 中開啟」檢視。',
+      rendered: '預覽', source: '原始碼', previewLimited: (limit) => `僅顯示前 ${limit};可透過更多選單開啟或另存完整檔案。`,
+      renderLimited: (limit, lines) => `為保證流暢,富文本預覽僅展開前 ${limit}、最多 ${lines} 行;完整原始碼仍可檢視。`,
+      highlightLimited: (limit, lines) => `為保證流暢,僅高亮前 ${limit}、最多 ${lines} 行,其餘內容以純文本顯示。`,
+      diffLinesLimited: (count) => `為保證流暢,另有 ${count} 行未在預覽中展開。`,
+      readFailed: { title: '無法讀取生成檔案', description: '路徑可能已被外部刪除。請透過更多選單「在 Finder 中開啟」檢查檔案位置。' },
+      notAllowed: { title: '無法讀取生成檔案', description: '路徑檢查未透過,檔案已不在允許預覽的生成檔案目錄內。' },
+      tooLarge: (bytes) => ({ title: '檔案超出預覽大小', description: `${bytes} 位元組超過文本預覽閾值,請透過更多選單開啟或另存完整內容。` }),
+      deleted: { title: '此生成檔案已刪除', description: '預覽已停止。如需檢視原檔案請使用「在 Finder 中開啟」。' },
+      unsupportedMime: { title: '不支援的檔案型別', description: '該生成檔案的 MIME 型別不在內聯預覽允許列表中。請使用工具欄「在 Finder 中開啟」或「另存為」。' },
+    },
+    registry: {
+      kindDisallowed: { title: '目前預覽暫不支援該型別', description: '此類生成檔案不能在面板內直接預覽。請使用「在 Finder 中開啟」檢視。' },
+      mimeDisallowed: { title: '格式暫不支援預覽', description: '已識別到檔案的 MIME 型別,但目前預覽只支援 PNG / JPEG / GIF / WebP / AVIF。' },
+      unknownType: { title: '無法識別檔案型別', description: '檔案沒有 MIME 後設資料,副檔名也未符合。請透過「在 Finder 中開啟」檢視。' },
+      oversize: { title: '檔案過大,暫不預覽', description: '為避免在記憶體中載入大體積圖片,超過 2 MB 的檔案不在此處展開預覽。' },
+      readFailed: { title: '載入預覽失敗', description: '無法讀取檔案內容(可能已被刪除、移動或權限不足)。請透過「在 Finder 中開啟」檢查檔案。' },
+      unsupported: '暫不支援的預覽', name: '名稱', unnamed: '(未命名)', type: '型別', size: '大小', openInFinder: '在 Finder 中開啟', loadingImage: '載入圖片預覽…',
+    },
+  },
   en: {
     pane: {
       refreshFailed: 'Failed to refresh generated files', openFailed: 'Could not show generated file in Finder', copyFailed: 'Copy failed',
diff --git a/apps/desktop/src/renderer/locales/browser-copy.ts b/apps/desktop/src/renderer/locales/browser-copy.ts
index 237ffe6544..f2e7a82dc5 100644
--- a/apps/desktop/src/renderer/locales/browser-copy.ts
+++ b/apps/desktop/src/renderer/locales/browser-copy.ts
@@ -45,7 +45,7 @@ export type BrowserCopy = {
 };
 
 const BROWSER_COPY = {
-  zh: {
+  'zh-CN': {
     unsupportedScheme: '嵌入式浏览器只支持打开 HTTP/HTTPS 网页地址。',
     invalidUrl: '这个地址无法识别,请检查网址后重试。',
     openFailed: '无法打开地址',
@@ -69,6 +69,30 @@ const BROWSER_COPY = {
     title: '嵌入式浏览器',
     description: '输入网址打开页面,或让助手帮你导航并操作。',
   },
+  'zh-TW': {
+    unsupportedScheme: '嵌入式瀏覽器只支援開啟 HTTP/HTTPS 網頁地址。',
+    invalidUrl: '這個地址無法識別,請檢查網址後重試。',
+    openFailed: '無法開啟地址',
+    navigationFailed: '瀏覽器導航失敗',
+    navigationFailedDetail: '頁面暫時無法開啟,請稍後重試。',
+    panelAria: '嵌入式瀏覽器',
+    panelAriaWithTitle: (title) => `嵌入式瀏覽器:${title}`,
+    insecure: '這個站點用 HTTP 傳輸,連線未加密。',
+    backAria: '瀏覽器後退',
+    back: '後退',
+    forwardAria: '瀏覽器前進',
+    forward: '前進',
+    stopAria: '停止載入頁面',
+    refreshAria: '重新整理頁面',
+    stop: '停止',
+    refresh: '重新整理',
+    addressAria: '瀏覽器地址',
+    addressPlaceholder: '輸入網址並回車',
+    closeAria: '關閉瀏覽器頁面',
+    close: '關閉頁面',
+    title: '嵌入式瀏覽器',
+    description: '輸入網址開啟頁面,或讓助手幫你導航並操作。',
+  },
   en: {
     unsupportedScheme: 'The embedded browser only supports HTTP and HTTPS addresses.',
     invalidUrl: 'This address is not valid. Check it and try again.',
diff --git a/apps/desktop/src/renderer/locales/conversation-copy.ts b/apps/desktop/src/renderer/locales/conversation-copy.ts
index 797dda1f15..b4fef1cfa2 100644
--- a/apps/desktop/src/renderer/locales/conversation-copy.ts
+++ b/apps/desktop/src/renderer/locales/conversation-copy.ts
@@ -457,7 +457,7 @@ function enDetail(parts: readonly string[]): string {
 }
 
 const COPY = {
-  zh: {
+  'zh-CN': {
     actions: { stopFailedTitle: '停止失败', stopFailedFallback: '任务操作失败,请稍后重试。', refreshSessionsFailedTitle: '刷新任务列表失败', refreshSessionsFailedFallback: '刷新任务列表失败,请稍后重试。', conversationErrorTitle: '任务出错', conversationErrorFallback: '任务运行失败,请稍后重试。', regenerateStartedTitle: '已发起重新生成', regenerateStartedDescription: '正在生成新的一轮回答', branchCreatedTitle: '已创建分支', branchCreatedDescription: (name) => `新任务 ${name}`, revisionStartedTitle: '已创建修改版草稿', revisionStartedDescription: '原任务仍会保留;修改后发送将在新版本中继续', rev [...]
     attachments: { tooMany: '附件数量超过 8 个', tooLarge: '附件大小超过 50MB', duplicate: '附件来源重复,请勿重复添加同一文件。' },
     model: {
@@ -711,6 +711,251 @@ const COPY = {
     },
     turnError: { unknown: '出错了,原因不明。重新发消息重试。', contextOverflow: '上下文超出模型窗口限制,减少附件或开启新任务。', timeout: '模型请求超时,重新发消息重试。', auth: '模型鉴权失败,请到设置里重新连接或登录。', providerBilling: '模型服务计费受限,请检查账号余额或订阅状态。', providerCapacity: '模型服务暂时满载,等几分钟重试,或换一个模型。', rateLimit: '模型请求太频繁被限流了,等一会儿再发消息重试。', network: '网络连接失败,检查网络后重新发消息。', provider: '模型服务返回错误,稍后重试或换一个模型。', stepCap: '达到工具调用步数上限,任务可能没做完。发消息让它继续。', tool: '工具调用失败,看一下上面的工具结果再决定要不要重试。', permission: '这一轮在等权限确认时结束了,重新发消息会再问一次。', restarted: '本地应�
 ��重启,上一轮没有完成', sandbo [...]
   },
+  'zh-TW': {
+    actions: { stopFailedTitle: '停止失敗', stopFailedFallback: '任務操作失敗,請稍後重試。', refreshSessionsFailedTitle: '重新整理任務列表失敗', refreshSessionsFailedFallback: '重新整理任務列表失敗,請稍後重試。', conversationErrorTitle: '任務出錯', conversationErrorFallback: '任務執行失敗,請稍後重試。', regenerateStartedTitle: '已發起重新生成', regenerateStartedDescription: '正在生成新的一輪迴答', branchCreatedTitle: '已建立分支', branchCreatedDescription: (name) => `新任務 ${name}`, revisionStartedTitle: '已建立修改版草稿', revisionStartedDescription: '原任務仍會保留;修改後傳送將在新版本中繼續', [...]
+    attachments: { tooMany: '附件數量超過 8 個', tooLarge: '附件大小超過 50MB', duplicate: '附件來源重複,請勿重複新增同一檔案。' },
+    model: {
+      fakeBackendLabel: '本地模擬連線',
+      setupTitle: '等待設定真實模型',
+      connectionMissingTitle: '連線已刪除',
+      configurationFallback: '模型連線暫時無法用於傳送,請到 設定 · 模型 檢查後重試。',
+      configurationReason: {
+        missing_default_connection: '等待設定預設模型。請到 設定 · 模型 新增一個可用模型連線後再發送。',
+        connection_missing: '該任務依賴的模型連線已刪除,請到 設定 · 模型 重新選擇或重建連線。',
+        connection_disabled: '目前模型連線已停用。請到 設定 · 模型 啟用或選擇其他預設模型。',
+        missing_api_key: '目前模型連線還沒有可用憑據。請到 設定 · 模型 補齊 API key 或重新登入後再發送。',
+        missing_model: '目前模型連線還沒有可用模型。請到 設定 · 模型 選擇預設模型後再發送。',
+        empty_model_list: '目前模型連線沒有啟用模型。請到 設定 · 模型 新增或啟用模型後再發送。',
+        model_not_enabled: '目前任務選擇的模型未啟用。請到 設定 · 模型 重新選擇可用模型後再發送。',
+        model_not_chat_capable: '目前任務選擇的模型不能用於聊天。請到 設定 · 模型 重新選擇支援聊天的模型後再發送。',
+        fake_backend: '目前任務來自舊的本地模擬連線。請到 設定 · 模型 新增真實模型後建立任務。',
+        provider_retired: '目前任務繫結的連線,其登入方式已從 Maka 移除,無法用於傳送。請到 設定 · 模型 改用其他連線後建立任務。',
+      },
+    },
+    footer: { labels: { regenerate: '重新生成', branch: '分支', copy: '複製', info: '詳情' }, pending: '正在處理…', regenerateRunning: '目前回答仍在進行中,結束後再重新生成', regenerateAgain: '已重新生成過,再次點選將建立新的並行回答', requestRegenerate: '請求擁有者核准重新生成本輪回答', regenerate: '讓模型重新生成本輪迴答', branchRunning: '目前回答仍在進行中,結束後再分支', branchAborted: '從中斷前的上下文分支出新任務', branch: '基於此回答的上下文分支出新任務', copy: '複製回答到剪貼簿', copyEmpty: '此回答尚無可複製的內容' },
+    lineage: { regeneratedFrom: '重新生成自舊回答', regeneratedFromTooltip: '這是重新生成的並行回答,點選檢視被保留的舊回答', regeneratedTo: '已重新生成 → 新回答', regeneratedToTooltip: '點選跳轉到重新生成的新回答' },
+    workbar: {
+      ariaLabel: '任務工作欄',
+      sectionsAriaLabel: '任務工作欄標籤',
+      review: '變更',
+      terminal: '終端',
+      terminalNumbered: (index) => `終端 ${index}`,
+      tasks: '待辦',
+      todoLoadFailed: '待辦載入失敗,請重試。',
+      workBoard: '工作看板',
+      browser: '瀏覽器',
+      files: '生成檔案',
+      inspector: '追蹤',
+      sideChat: '側邊對話',
+      sideChatNumbered: (index) => `側邊對話 ${index}`,
+      openTab: '開啟工作欄標籤',
+      openTools: '開啟工具',
+      closeTab: (label) => `關閉${label}`,
+      tabMenu: (label) => `${label}標籤選單`,
+      moveLeft: '向左移動',
+      moveRight: '向右移動',
+      moveToRight: '移動到右側面板',
+      moveToBottom: '移動到底部面板',
+      pinTab: '固定標籤',
+      pinTabHint: '預覽標籤,雙擊或在內容中操作即可固定',
+      close: '關閉',
+      closeOthers: '關閉其他標籤',
+      closeToRight: '關閉右側標籤',
+      launcher: {
+        review: '檢視目前 Git 工作區變化',
+        terminal: '檢視目前任務的終端執行和即時輸出',
+        tasks: '檢視和維護這個任務的待辦臺賬',
+        workBoard: '記錄和管理暫緩事項',
+        browser: '開啟內建瀏覽器並保留目前頁面',
+        files: '瀏覽目前任務生成的檔案',
+        inspector: '檢查任務呼叫、工具與耗時記錄',
+        sideChat: '在不打斷主任務的情況下追問和只讀探索',
+      },
+    },
+    workBoardPanel: {
+      inbox: 'Inbox',
+      project: '目前專案',
+      noProject: '未選擇專案',
+      createPlaceholder: '記錄稍後處理的事項…',
+      create: '新增',
+      empty: '暫無暫緩事項',
+      loading: '正在載入工作看板…',
+      loadMore: '載入更多',
+      retry: '重試',
+      loadFailed: '工作看板載入失敗',
+      actionFailed: '操作失敗',
+      complete: '完成',
+      reopen: '重開',
+      rename: '改名',
+      renameSave: '儲存',
+      moveToInbox: '移到 Inbox',
+      moveToProject: '移到專案',
+      archive: '歸檔',
+      unarchive: '恢復',
+      delete: '刪除',
+      archived: '已歸檔',
+    },
+    reviewPanel: {
+      ariaLabel: 'Git 變更',
+      empty: '目前 Git 工作區沒有變化',
+      emptyHelp: '提交、暫存或修改檔案後,變化會顯示在這裡。',
+      notGitRepository: '目前任務目錄不是 Git 倉庫',
+      workspaceUnavailable: '目前任務目錄已不可用',
+      unbornRepository: 'Git 倉庫還沒有可比較的提交',
+      gitFailed: '無法讀取 Git 工作區變化',
+      invalidBaseBranch: '選擇的比較分支已不可用',
+      truncated: '變化過多,僅顯示前一部分檔案',
+      showMore: (remaining) => `再顯示 ${Math.min(20, remaining)} 個檔案`,
+      hiddenLines: (count) => `另有 ${count} 行未顯示`,
+      changedFiles: (count) => `${count} 個檔案有變更`,
+      addedLines: (count) => `新增 ${count} 行`,
+      deletedLines: (count) => `刪除 ${count} 行`,
+      added: (count) => `新增 ${count}`,
+      deleted: (count) => `刪除 ${count}`,
+      loadFailed: '無法讀取 Git 變化',
+      retry: '重試',
+    },
+    terminalPanel: {
+      ariaLabel: '任務終端',
+      empty: '目前任務還沒有終端執行',
+      emptyHelp: '任務啟動終端後會顯示在這裡。',
+      loadFailed: '無法讀取終端執行',
+      retry: '重試',
+      refresh: '重新整理終端',
+      readOnly: '顯示代理和你在目前任務中啟動的終端執行',
+      runCount: (count) => `${count} 個終端執行`,
+      newTerminal: '建立終端',
+      commandPlaceholder: '輸入命令並回車',
+      commandLabel: '終端命令',
+      runCommand: '執行命令',
+      stopTerminal: '停止目前終端',
+      startFailed: '無法啟動終端',
+      writeFailed: '無法傳送終端輸入',
+      stopFailed: '無法停止終端',
+    },
+    inspector: {
+      ariaLabel: '任務追蹤',
+      copyPricingKey: '複製定價鍵',
+      pricingKeyCopied: '已複製定價鍵',
+      unpricedPricingKey: '未計價的定價鍵',
+      copyFailed: '複製失敗',
+      copyFailedDetail: '剪貼簿不可用或被系統拒絕。',
+      loadFailed: '追蹤讀取失敗',
+      retry: '重試',
+      empty: '這個任務還沒有可追蹤的活動',
+      emptyHelp: '任務尚無活動記錄。',
+      costUnavailable: '費用未知',
+      costEstimateHelp: '基於已記錄用量和定價估算;缺失或未定價的呼叫可能未計入。',
+      loadEarlier: '載入更早記錄',
+      hideEarlier: '隱藏所有更早記錄',
+      loadingEarlier: '正在載入…',
+      loadingTrace: '正在讀取時間線…',
+      loadingSummary: '正在估算完整會話用量…',
+      summaryUnavailable: '完整會話用量暫時無法估算。',
+      totals: {
+        cost: '估算成本',
+      },
+      tokenUsage: {
+        title: 'Token 統計',
+        segment: {
+          cacheRead: '快取輸入',
+          cacheMiss: '未命中輸入',
+          output: '輸出(含思考)',
+        },
+      },
+      durationUsage: {
+        title: '耗時統計',
+        center: '記錄時長',
+        segment: {
+          model: (count) => `LLM 呼叫 × ${count}`,
+          tool: (count) => `工具執行 × ${count}`,
+        },
+      },
+      coveragePartial: (parts) => `部分呼叫未能完整顯示,下面的數字只少不多${zhDetail(parts)}`,
+      coverageAbsent: (parts) => `這個後端不記錄每次呼叫的明細${zhDetail(parts)}`,
+      unreadable: (count) => `${count} 條記錄讀不出來`,
+      oversizedRuns: (count) => `${count} 條執行記錄過大,無法線上顯示`,
+      turnsMissing: (count) => `${count} 輪沒有呼叫記錄`,
+      turnsShort: (count) => `${count} 輪的呼叫記錄不全`,
+      stepKind: { permission: '權限', compaction: '上下文壓縮', error: '錯誤' },
+      callKind: (kind) => ZH_CALL_KIND[kind as keyof CallKindCopy] ?? kind,
+      permissionDecision: (decision) => ZH_PERMISSION_DECISION[decision] ?? decision,
+      recoveredAs: (disposition) => `已恢復:${ZH_RECOVERED[disposition] ?? disposition}`,
+      retries: (count) => `重試 ${count} 次`,
+      turnFailure: (code) => ZH_TURN_FAILURE[code] ?? '本輪失敗',
+      turnLabel: (startedAt) => `輪次 · ${startedAt}`,
+      overview: {
+        context: '上下文視窗',
+        segment: {
+          cacheRead: '快取命中',
+          fresh: '快取未命中',
+          used: '已佔用',
+          free: '剩餘',
+        },
+        cacheHit: '快取命中率',
+        timelineTab: '時間軸',
+        composition: {
+          title: '構成估算',
+          basis: '按請求位元組估算,非模型報告的 token',
+          part: {
+            system_instructions: '系統提示',
+            tool_definitions: '工具定義',
+            messages: '對話記錄',
+            other: '其他引數',
+          },
+          tools: '按工具',
+          remainingTools: (count) => `其餘 ${count} 個工具`,
+          unlabelled: '未命名的工具',
+          unrecorded: '這次呼叫沒有留下構成記錄',
+        },
+      },
+    },
+    quoteCompanion: {
+      compactStartedTitle: '正在壓縮上下文', compactStartedDescription: '正在將較早的上下文整理為檢查點摘要。', compactSuccessTitle: '上下文已壓縮', compactSuccessDescription: '較早的上下文已替換為檢查點摘要。', compactUnchangedTitle: '無需壓縮', compactUnchangedDescription: '任務已使用最新的檢查點。', compactErrorTitle: '壓縮失敗', compactErrorFallback: '任務暫時無法壓縮,請稍後重試。', workspaceUnavailableTitle: '工作目錄無法使用', workspaceUnavailableDescription: '工作目錄不存在或無法存取。請選擇有效目錄建立新任務。',
+      namePrefix: '側聊:',
+      permissionStreaming: '側邊對話執行中暫時不能更改權限',
+      scrollToBottom: '滾動側邊對話到底部',
+      closeConfirmation: {
+        title: (count) => count > 1 ? `關閉 ${count} 個側邊對話?` : '關閉側邊對話?',
+        description: (count) =>
+          count > 1
+            ? `這 ${count} 個臨時側邊對話會被永久刪除,之後無法恢復。`
+            : '這個臨時側邊對話會被永久刪除,之後無法恢復。',
+        dontAskAgain: '以後不再詢問',
+        cancel: '取消',
+        confirm: '關閉側邊對話',
+      },
+      errors: {
+        forkSetupFailed: '無法建立側邊對話,請稍後重試。',
+        forkSourceBusy: '主對話或子任務仍在執行,請等待完成後重試。',
+        forkUnsupported: '目前對話上下文暫不支援建立側邊對話。',
+        sendRejected: '追問未能開始,請稍後重試。',
+        sendFailed: '追問失敗,請稍後重試。',
+        settlementFailed: '執行已結束,但訊息載入失敗。請重試或重新開啟側邊對話。',
+        respondFailed: '回應失敗,請稍後重試。',
+      },
+    },
+    health: {
+      blocked: {
+        fake_backend: { label: '任務已過期 · 請先設定真實模型', tooltip: () => '原任務使用舊的本地模擬連線,需要先到 設定 · 模型 新增並啟用一個真實模型才能傳送。' },
+        provider_retired: { label: '登入方式已停用', tooltip: (name) => `任務繫結的連線 "${name}" 使用的登入方式已從 Maka 移除,傳送會失敗。請到 設定 · 模型 改用其他連線。` },
+        missing_default_connection: { label: '未設定可用模型', tooltip: () => '目前任務沒有可用的模型連線,傳送會失敗。請到 設定 · 模型 新增並啟用一個模型。' },
+        legacy_connection_identity: { label: '需要選擇模型連線', tooltip: () => '此任務來自舊版本,請選擇要使用的連線和模型。', actionLabel: '選擇連線和模型', settingsTooltip: () => '目前沒有可用連線,請先到 設定 · 模型 新增或啟用連線。' },
+        connection_missing: { label: '連線已刪除', tooltip: () => '此任務依賴的模型連線已被刪除,傳送會失敗。請到 設定 · 模型 檢查連線設定。' },
+        connection_identity_mismatch: { label: '連線身分不符', tooltip: () => '請重新選擇要使用的連線和模型。', actionLabel: '選擇連線和模型', settingsTooltip: () => '目前沒有可用連線,請先到 設定 · 模型 新增或啟用連線。' },
+        connection_disabled: { label: '連線已停用', tooltip: (name) => `任務繫結的連線 "${name}" 已停用,傳送會失敗。請到 設定 · 模型 啟用它或選擇其他連線。` },
+        missing_api_key: { label: '連線缺少金鑰', tooltip: (name) => `連線 "${name}" 未填寫 API key 或未完成登入,傳送會失敗。請到 設定 · 模型 補齊憑據。` },
+        missing_model: { label: '連線未選擇模型', tooltip: (name) => `連線 "${name}" 沒有預設模型,傳送會失敗。請到 設定 · 模型 選擇一個模型。` },
+        empty_model_list: { label: '連線沒有啟用模型', tooltip: (name) => `連線 "${name}" 沒有啟用任何模型,傳送會失敗。請到 設定 · 模型 先新增模型。` },
+        model_not_enabled: { label: '任務模型未啟用', tooltip: (name, model) => `模型 "${model}" 不在連線 "${name}" 的啟用列表中,傳送會失敗。請到 設定 · 模型 重新選擇。` },
+        model_not_chat_capable: { label: '任務模型不支援聊天', tooltip: (name, model) => `模型 "${model}" 不能用於聊天,傳送會失敗。請到 設定 · 模型 選擇支援聊天的模型。` },
+      },
+      connectionChoicesLoading: { tooltip: '連線清單尚未載入完成。', actionLabel: '重新載入連線' },
+      reauth: { label: '上次連線測試鑑權失敗', tooltip: '最近一次連線測試回傳鑑權失敗(401 / 403),金鑰可能已過期或被吊銷。這不會攔截發送,但若傳送失敗請到 設定 · 模型 重新登入。' },
+      testError: { label: '上次連線測試失敗', tooltip: '最近一次連線測試因網路 / 超時 / 5xx 失敗。這不會攔截發送,但若問題持續請到 設定 · 模型 檢查 Base URL / 代理。' },
+    },
+    turnError: { unknown: '出錯了,原因不明。重新傳送訊息重試。', contextOverflow: '上下文超出模型視窗限制,減少附件或開啟新任務。', timeout: '模型請求逾時,重新傳送訊息重試。', auth: '模型鑑權失敗,請到設定裡重新連線或登入。', providerBilling: '模型服務計費受限,請檢查帳號餘額或訂閱狀態。', providerCapacity: '模型服務暫時滿載,請等待幾分鐘或切換模型。', rateLimit: '模型請求太頻繁而受到速率限制,請稍候再傳送訊息重試。', network: '網路連線失敗,檢查網路後重新傳送訊息。', provider: '模型服務回傳錯誤,稍後重試或切換模型。', stepCap: '達到工具呼叫步數上限,任務可能尚未完成。傳送訊息讓它繼續。', tool: '工具呼叫失敗,先看上面的工具結果再決定是否重試。', permission: '這一輪在等待權限確認時結束,重新傳送訊息會再詢問一次。', restart
 ed: '本機應用程式重啟,上一輪沒有完成', [...]
+  },
   en: {
     actions: { stopFailedTitle: 'Failed to stop', stopFailedFallback: 'The task action failed. Try again later.', refreshSessionsFailedTitle: 'Failed to refresh tasks', refreshSessionsFailedFallback: 'The task list could not be refreshed. Try again later.', conversationErrorTitle: 'Task error', conversationErrorFallback: 'The task run failed. Try again later.', regenerateStartedTitle: 'Regeneration started', regenerateStartedDescription: 'Generating a new response', branchCreatedTitle: ' [...]
     attachments: { tooMany: 'You can attach at most 8 files', tooLarge: 'Attachments must be 50 MB or smaller', duplicate: 'This attachment was already added.' },
diff --git a/apps/desktop/src/renderer/locales/external-session-import-copy.ts b/apps/desktop/src/renderer/locales/external-session-import-copy.ts
index 76e6265918..48cb7a829c 100644
--- a/apps/desktop/src/renderer/locales/external-session-import-copy.ts
+++ b/apps/desktop/src/renderer/locales/external-session-import-copy.ts
@@ -94,7 +94,7 @@ type ExternalSessionImportCopy = {
 };
 
 const COPY = {
-  zh: {
+  'zh-CN': {
     sourceLabel: '来源',
     sourceNames: { codex: 'Codex', 'claude-code': 'Claude Code' },
     includeArchived: '包含已归档的对话',
@@ -150,6 +150,62 @@ const COPY = {
     importOutcomeUnknownDescription: (names) =>
       `以下对话的导入结果无法确认:${names.map((name) => `「${name}」`).join('、')}。请先在任务列表中查找,已经出现的不要再次导入。`,
   },
+  'zh-TW': {
+    sourceLabel: '來源',
+    sourceNames: { codex: 'Codex', 'claude-code': 'Claude Code' },
+    includeArchived: '包含已歸檔的對話',
+    searchLabel: '搜尋',
+    searchHelp: '符合對話標題與專案路徑。留空顯示全部。',
+    searchPlaceholder: '標題或路徑的一部分',
+    searchEmpty: (term) => `沒有標題或路徑包含「${term}」的對話。`,
+    loading: '正在讀取外部對話…',
+    listAria: '可匯入的對話',
+    emptyTitle: '沒有可匯入的對話',
+    emptyDescription: '目前來源中沒有找到符合條件的根對話。',
+    unavailableTitle: '沒有檢測到支援的 Agent',
+    // The title already says nothing was detected, so this says what to do
+    // about it instead of saying it again. It names Codex because the renderer
+    // only ever learns which sources *were* detected — nothing but a copy
+    // string can tell someone with none what to go install. The second half is
+    // the promise that earns the permission to read another app's files.
+    unavailableDescription: '在本機使用過 Codex 後,它的對話會出現在這裡。Maka 只讀取這些檔案,不會修改。',
+    loadFailedTitle: '無法讀取外部對話',
+    loadFailedFallback: '外部對話目錄暫時無法讀取,請重試。',
+    retry: '重試',
+    archived: '已歸檔',
+    loadMore: '載入更多',
+    loadingMore: '正在載入…',
+    duplicateNote: '再次匯入同一個對話會建立一個獨立的任務。',
+    importedCount: (count) => `已匯入 ${count} 次`,
+    openLatestImportedTask: '開啟最近匯入的任務',
+    openLatestImportedTaskFor: (name) => `開啟「${name}」最近匯入的任務`,
+    import: '匯入',
+    importAgain: '再次匯入',
+    importTask: (name) => `匯入「${name}」`,
+    importTaskAgain: (name) => `再次匯入「${name}」`,
+    importing: '正在匯入…',
+    importingTask: (name) => `正在匯入「${name}」`,
+    importInProgressTitle: '正在匯入',
+    importInProgressDescription: (name) => `正在匯入「${name}」,完成後會直接開啟這個任務。`,
+    importFailedTitle: '匯入失敗',
+    importFailedFallback: '該對話無法轉換或儲存。請檢查來源後重試。',
+    importRecoveredTitle: '已確認匯入',
+    importRecoveredDescription: (name) => `「${name}」匯入的任務現已可用。`,
+    importNotRecordedTitle: '沒有發現新任務',
+    importNotRecordedDescription: '沒有記錄到新的任務,可以安全重試。',
+    importOutcomeUnknownTitle: '需要確認匯入結果',
+    selectAllAriaLabel: '全選或全部取消選取',
+    selectedCount: (selected, listed) => `已選 ${selected} / ${listed}`,
+    selectRowAriaLabel: (name) => `選取 ${name}`,
+    importSelected: '匯入所選項目',
+    batchProgress: (done, total) => `正在匯入 ${done} / ${total}`,
+    batchDoneTitle: (imported) => `已匯入 ${imported} 個對話`,
+    batchDuplicated: (count) => `其中 ${count} 個先前已匯入,現在各有兩份。`,
+    batchFailed: (count) => `另有 ${count} 個無法匯入。`,
+    batchNothingImported: '沒有匯入任何對話。',
+    importOutcomeUnknownDescription: (names) =>
+      `以下對話的匯入結果無法確認:${names.map((name) => `「${name}」`).join('、')}。請先在任務列表中查詢,已經出現的不要再次匯入。`,
+  },
   en: {
     sourceLabel: 'Source',
     sourceNames: { codex: 'Codex', 'claude-code': 'Claude Code' },
diff --git a/apps/desktop/src/renderer/locales/mcp-copy.ts b/apps/desktop/src/renderer/locales/mcp-copy.ts
index 3d6c41275a..8eb2ad8aaf 100644
--- a/apps/desktop/src/renderer/locales/mcp-copy.ts
+++ b/apps/desktop/src/renderer/locales/mcp-copy.ts
@@ -71,7 +71,7 @@ export type McpCopy = {
 };
 
 const MCP_COPY = {
-  zh: {
+  'zh-CN': {
     errors: {
       load: '载入 MCP 失败', install: (name) => `安装 ${name} 失败`, cancelInstall: (name) => `取消安装 ${name} 失败`, save: '保存 MCP 失败',
       import: '导入 MCP 失败', update: '更新 MCP 失败', test: 'MCP 测试失败', remove: '删除 MCP 失败', unavailableStatus: 'Server 没有返回可用状态。',
@@ -130,6 +130,65 @@ const MCP_COPY = {
       stdioProtocolHelp: '自动协商和“仅 2026-07-28”会先启动一个使用相同命令、参数、目录和环境的短期探测进程;探测结束后才启动实际连接。旧配置默认使用传统协议,只启动一个进程。',
     },
   },
+  'zh-TW': {
+    errors: {
+      load: '載入 MCP 失敗', install: (name) => `安裝 ${name} 失敗`, cancelInstall: (name) => `取消安裝 ${name} 失敗`, save: '儲存 MCP 失敗',
+      import: '匯入 MCP 失敗', update: '更新 MCP 失敗', test: 'MCP 測試失敗', remove: '刪除 MCP 失敗', unavailableStatus: 'Server 沒有返回可用狀態。',
+      mapLine: (line) => `第 ${line} 行應為 KEY=value`, importJson: 'MCP 設定必須是有效的 JSON', importObject: 'MCP JSON 必須是 object',
+      importVersion: (version) => `不支援 MCP 設定版本 ${version},目前支援 version 1、2 和 3`, importServersObject: 'mcpServers 必須是 object',
+      importProtocolVersion: 'remote 的 protocol 需要 version 2 或 3;stdio 的 protocol 需要 version 3',
+    },
+    toast: {
+      templateInstalled: (name) => `${name} 模板已安裝`, templateInstalledDetail: '請在「已安裝」中完成憑據設定,再啟用連線。',
+      installed: (name) => `${name} 已安裝`, installedDetail: '發現的工具會從下一次 agent turn 開始生效。', installCancelled: (name) => `已取消安裝 ${name}`,
+      saved: 'MCP 已儲存', savedDetail: '新工具會從下一次 agent turn 開始生效。', imported: '已匯入 MCP',
+      importedDetail: (count) => `本次匯入 ${count} 個 server。`, connectionOk: 'MCP 連線正常',
+      toolLatency: (count, latencyMs) => `${count} 個工具 · ${latencyMs} ms`, connectionFailed: 'MCP 連線失敗', removed: 'MCP 已刪除',
+    },
+    remove: { title: (id) => `刪除 MCP「${id}」?`, description: '它提供的工具會從下一次 agent turn 中移除,設定無法自動恢復。', confirm: '刪除', cancel: '取消' },
+    page: {
+      actionsAria: 'MCP 操作', refreshing: '重新整理中…', refresh: '重新整理', add: '新增 MCP',
+      metaInstalled: (count) => `${count} 個已安裝`, metaErrors: (count) => `${count} 個連線異常`,
+      searchMatches: (count) => `${count} 個符合`,
+      workspaceAria: 'MCP 市場與已安裝項', toolbarAria: 'MCP 瀏覽操作', setupTitle: '把 Maka 連線到你的工作環境', setupDescription: '從精選模板開始,或新增任意 stdio、Streamable HTTP 與 SSE server。',
+      localStdio: '本地 stdio', categoriesAria: 'MCP 分類', market: '市場', installed: '已安裝',
+      searchPlaceholder: '搜尋 MCP…', searchAria: '搜尋 MCP', noMarket: '沒有找到符合的 MCP', noMarketDetail: (query) => `換一個關鍵詞,或清空「${query}」檢視全部模板。`,
+      clearSearch: '清空搜尋', loading: '正在讀取 MCP 設定…', noInstalled: '還沒有安裝 MCP', noInstalledDetail: '從市場選擇模板,或手動新增你自己的 server。',
+      browseMarket: '瀏覽市場', noInstalledMatch: '沒有符合的已安裝 MCP', noInstalledMatchDetail: (query) => `換一個關鍵詞,或清空「${query}」檢視全部已安裝項。`,
+    },
+    detail: {
+      label: '伺服器詳情', enabled: '啟用', transport: '傳輸方式', endpoint: '端點',
+      toolsLabel: '工具', statusLabel: '狀態', protocolLabel: 'MCP 協議',
+      negotiatedProtocol: (era, revision) => `${era === 'modern' ? '現代' : '傳統'} · ${revision}`,
+      inspectorOpened: (id) => `已開啟 ${id} 的詳情`,
+    },
+    card: {
+      macOnly: '僅 macOS', manage: '管理', cancellingAria: (name) => `正在取消安裝 ${name}`, cancelAria: (name) => `取消安裝 ${name}`, installAria: (name) => `安裝 ${name}`,
+      cancelling: '正在取消…', cancel: '取消安裝', install: '安裝',
+    },
+    row: {
+      testing: '測試中…', test: '測試', edit: '編輯',
+      delete: '刪除', tools: (count) => `${count} 個工具`,
+      disabled: '已停用', disconnected: '未連線', connecting: '連線中', connected: (count) => `${count} 個工具`, failed: '連線失敗',
+    },
+    editor: {
+      importTitle: '透過 JSON 匯入', editTitle: (id) => `編輯 ${id}`, addTitle: '新增 MCP', importSubtitle: '貼上 mcpServers 設定,同名 server 會被更新。',
+      manualSubtitle: '設定儲存在目前工作區的 mcp.json。', modeAria: 'MCP 新增方式', manual: '手動設定', pasteJson: '貼上 JSON', jsonConfig: 'JSON 設定',
+      jsonHelp: '支援完整 mcpServers 設定或直接的 server map。未在本次匯入中出現的已有 MCP 會保留。', cancel: '取消', importConnect: '匯入並連線',
+      transportAria: '連線方式', localStdio: '本地 stdio', remoteUrl: '遠端 URL',
+      serverId: '伺服器 ID', command: '命令',
+      commandPlaceholder: 'npx -y @modelcontextprotocol/server-filesystem /path/to/folder',
+      commandHelp: '完整命令列;含空格的引數用引號包裹,不經過 shell 解析。',
+      workingDirectory: '工作目錄', workingDirectoryPlaceholder: '可選,例如 /path/to/project',
+      environment: '環境變數', environmentHelp: '每行一個 KEY=value;按 MCP 要求填寫。', url: 'MCP URL', headers: 'HTTP 請求頭', headersHelp: '每行一個 Header=value。',
+      saveConnect: '儲存並連線',
+      required: '此欄位為必填項。', invalidUrl: '請輸入有效的 HTTP 或 HTTPS URL。', unbalancedQuote: '引號未閉合。',
+      transportLabel: '傳輸協議', transportAuto: '自動回退', transportStreamableHttp: 'Streamable HTTP', transportLegacySse: '舊版 SSE',
+      protocolLabel: '協議偏好', protocolLegacy: '傳統', protocolAuto: '自動協商', protocolModern: '僅 2026-07-28',
+      protocolHelp: '舊設定預設使用傳統協議;自動協商會根據 server 能力選擇協議。', sseProtocolHelp: '舊版 SSE 僅支援傳統協議。', expandAdvanced: '顯示進階設定', collapseAdvanced: '隱藏進階設定',
+      stdioProtocolHelp: '自動協商和“僅 2026-07-28”會先啟動一個使用相同命令、引數、目錄和環境的短期探測程序;探測結束後才啟動實際連線。舊設定預設使用傳統協議,只啟動一個程序。',
+    },
+  },
   en: {
     errors: {
       load: 'Failed to load MCP', install: (name) => `Failed to install ${name}`, cancelInstall: (name) => `Failed to cancel installation of ${name}`, save: 'Failed to save MCP',
diff --git a/apps/desktop/src/renderer/locales/onboarding-copy.ts b/apps/desktop/src/renderer/locales/onboarding-copy.ts
index e68a6243d4..f408b0ab3b 100644
--- a/apps/desktop/src/renderer/locales/onboarding-copy.ts
+++ b/apps/desktop/src/renderer/locales/onboarding-copy.ts
@@ -51,7 +51,7 @@ export interface OnboardingCatalog {
 }
 
 const ONBOARDING_COPY_BY_LOCALE: UiCatalog<OnboardingCatalog> = {
-  zh: {
+  'zh-CN': {
     hero: {
       needs_connection: {
         eyebrow: '欢迎使用 Maka',
@@ -100,6 +100,55 @@ const ONBOARDING_COPY_BY_LOCALE: UiCatalog<OnboardingCatalog> = {
     skip: '跳过引导',
     snapshotErrorFallback: '首次使用状态暂时不可用,请稍后重试。',
   },
+  'zh-TW': {
+    hero: {
+      needs_connection: {
+        eyebrow: '歡迎使用 Maka',
+        title: '串接一個 AI,開始第一項任務。',
+        body: 'Maka 在本地執行,使用你自己的模型帳號或 API key。先選一個常用服務商。',
+        cta: { label: '瀏覽全部服務商' },
+      },
+      needs_connection_credentials: {
+        eyebrow: '繼續設定連線',
+        title: '補齊這個連線的憑據。',
+        body: '新增 API key 或完成帳號登入,Maka 才能呼叫模型。',
+        cta: { label: '設定連線憑據' },
+      },
+      needs_model: {
+        eyebrow: '繼續設定連線',
+        title: '為這個連線啟用一個可用模型。',
+        body: '啟用一個可用於對話的模型,新任務就可以開始了。',
+        cta: { label: '選擇可用模型' },
+      },
+      'blocked:all_connections_unhealthy': {
+        eyebrow: '連線需要處理',
+        title: '模型連線暫時不可用。',
+        body: '現有連線都沒有透過驗證。檢查憑據、登入狀態或網路後重新測試。',
+        cta: { label: '修復模型連線' },
+        tone: 'destructive',
+      },
+      'blocked:all_connections_retired': {
+        eyebrow: '連線需要處理',
+        title: '現有連線的登入方式已停用。',
+        body: '這些連線使用的登入方式已從 Maka 移除,無法再登入,也無法用於對話。新增一個新的模型連線即可繼續。',
+        cta: { label: '新增模型連線' },
+        tone: 'destructive',
+      },
+    },
+    needsConnection: {
+      pickLabel: '選擇常用服務商',
+      browseProviders: '瀏覽全部服務商',
+    },
+    refresh: {
+      connection: '重新檢測連線',
+      credentials: '重新檢測憑據',
+      model: '重新檢測模型',
+      blocked: '重新檢測連線',
+    },
+    connectionLabel: '連線',
+    skip: '跳過引導',
+    snapshotErrorFallback: '首次使用狀態暫時不可用,請稍後重試。',
+  },
   en: {
     hero: {
       needs_connection: {
diff --git a/apps/desktop/src/renderer/locales/permission-center-copy.ts b/apps/desktop/src/renderer/locales/permission-center-copy.ts
index 3a2e4713ea..91df593854 100644
--- a/apps/desktop/src/renderer/locales/permission-center-copy.ts
+++ b/apps/desktop/src/renderer/locales/permission-center-copy.ts
@@ -97,7 +97,7 @@ export type PermissionCenterCopy = {
 };
 
 const PERMISSION_CENTER_COPY = {
-  zh: {
+  'zh-CN': {
     readiness: {
       not_configured: { label: '等待配置', detail: '需要先打开开关或补齐配置才能启用。', tone: 'neutral' },
       denied: { label: '系统拒绝', detail: '所需系统权限被拒绝或当前平台不支持。', tone: 'error' },
@@ -144,6 +144,53 @@ const PERMISSION_CENTER_COPY = {
     auditSection: '审计记录', noAudit: '暂无审计记录', auditAria: (label) => `${label}审计记录列表`,
     impact: '影响功能', opening: '打开中…', openSettings: '前往系统设置', requesting: '请求中…', request: '请求授权', dragGrant: '引导授权', dragGranting: '引导中…',
   },
+  'zh-TW': {
+    readiness: {
+      not_configured: { label: '等待設定', detail: '需要先開啟開關或補齊設定才能啟用。', tone: 'neutral' },
+      denied: { label: '系統拒絕', detail: '所需系統權限被拒絕或目前平臺不支援。', tone: 'error' },
+      enabled: { label: '執行可用', detail: '目前快照標記為可用,具體層級見下方。', tone: 'success' },
+      degraded: { label: '部分可用', detail: '已有一部分能力可用,但仍有執行態、權限或子功能需要處理。', tone: 'attention' },
+      paused: { label: '已暫停', detail: '功能開關被顯式關閉,但設定仍保留。', tone: 'neutral' },
+    },
+    osPermissions: {
+      accessibility: { label: '輔助功能', purpose: 'Computer Use 需要它來讀取視窗焦點 / 模擬鍵盤滑鼠。', impact: 'Computer Use · 自動化鍵鼠操作' },
+      screen_recording: { label: '螢幕錄製', purpose: 'Computer Use 需要它來讀取視窗內容;未來螢幕活動錄製也會使用。', impact: 'Computer Use · 截圖上下文' },
+      notifications: { label: '通知', purpose: '權限申請、回顧完成等系統通知需要它。', impact: '權限申請提醒 · 每日回顧完成通知' },
+      automation: { label: '自動化(Apple Events)', purpose: 'Computer Use 控制其他 App 需要逐 target 授權。', impact: 'Computer Use · 跨 App 自動化' },
+    },
+    osStates: {
+      unsupported: { label: '目前平臺不支援', tone: 'neutral' }, unknown: { label: '無法讀取狀態', tone: 'neutral' },
+      not_determined: { label: '等待授權', tone: 'attention' }, denied: { label: '已拒絕', tone: 'error' }, granted: { label: '已授權', tone: 'success' },
+    },
+    loading: '正在載入權限快照', readFailed: '無法讀取權限快照', noData: '權限服務未返回資料。', readAgain: '重新讀取',
+    actionFailed: '權限操作失敗',
+    actionFailures: {
+      invalid_id: '內部錯誤:權限 id 無法識別。',
+      unsupported_platform: '目前作業系統不支援這個權限操作。',
+      unsupported_permission: '目前平臺沒有提供這個權限的直串接口。',
+      denied: '你沒有授予這項權限;可以前往系統設定重新開啟。',
+      already_open: '另一個權限引導仍在進行,請先完成或關閉它。',
+      open_settings_failed: '無法開啟系統設定,請手動前往「隱私與安全性」。',
+      failed: '權限操作未成功,請稍後重試。',
+    },
+    title: '權限與能力', subtitle: '檢視 Maka 需要的系統權限和目前授權狀態,直接從這裡前往「系統設定 → 隱私與安全性」完成授權或撤銷,不必自己翻選單。',
+    lastRead: '最近讀取:', detectAgain: '重新檢測', summaryAria: '按授權狀態篩選系統權限', summaryFilterAria: (label, count, selected) => selected ? `${label} ${count} 項,目前篩選;再次按下顯示全部` : `僅顯示${label}權限,共 ${count} 項`, granted: '已授權', pending: '等待授權', denied: '已拒絕', other: '未知 / 不支援',
+    osSection: '系統權限', osSectionHelp: 'Maka 讀到的 OS 級權限狀態。點選右側按鈕可以直接前往「系統設定 → 隱私與安全性」對應分割槽。', osListAria: '系統權限列表',
+    capabilitiesSection: '功能能力', capabilitiesHelp: '每個能力的就緒狀態由「功能開關 · 設定 · 系統權限 · 執行態探測」共同決定。',
+    capabilityListAria: '功能能力列表',
+    footnote: 'Maka 不會自動授予 Accessibility、Automation 或 Screen Recording。高風險自動化能力必須保持逐項審批、可審計、可撤銷。這裡只讀取系統權限與功能能力的目前快照,授權變更仍需在「系統設定 → 隱私與安全性」完成。',
+    layers: {
+      aria: (label) => `${label}能力狀態明細`, feature: '功能開關', configuration: '設定', approval: '操作審批', memory: '記憶寫入', runtime: '執行態探測',
+      featureStates: { enabled: '已開啟', partial: '部分可用', disabled: '已關閉', not_available: '未開放' },
+      configurationStates: { not_required: '不需要設定', missing: '等待補齊設定', present: '已填寫' },
+      approvalStates: { not_required: '不需要審批', required_per_action: '每次呼叫都需審批', required_scoped_lease: '按目標與動作類別授權', pending: '審批掛起', approved: '目前任務已批准', denied: '目前任務已拒絕' },
+      memoryStates: { not_applicable: '不涉及記憶寫入', disabled: '記憶寫入已關閉', draft_required: '需要先草擬 memory 協議', accepted: '記憶寫入已接受' },
+      runtimeStates: { not_available: '尚無執行態探測', not_run: '探測未執行', healthy: '探測透過', degraded: '探測降級' },
+    },
+    requiredPermissions: '所需系統權限', requiredPermissionsAria: (label) => `${label}所需系統權限列表`, guidance: '處理建議', guidanceAria: (label) => `${label}處理建議列表`,
+    auditSection: '審計記錄', noAudit: '暫無審計記錄', auditAria: (label) => `${label}審計記錄列表`,
+    impact: '影響功能', opening: '開啟中…', openSettings: '前往系統設定', requesting: '請求中…', request: '請求授權', dragGrant: '引導授權', dragGranting: '引導中…',
+  },
   en: {
     readiness: {
       not_configured: { label: 'Needs setup', detail: 'Enable the feature or complete its configuration first.', tone: 'neutral' },
diff --git a/apps/desktop/src/renderer/locales/plan-mode-copy.ts b/apps/desktop/src/renderer/locales/plan-mode-copy.ts
index df164c8bee..29f000c729 100644
--- a/apps/desktop/src/renderer/locales/plan-mode-copy.ts
+++ b/apps/desktop/src/renderer/locales/plan-mode-copy.ts
@@ -50,7 +50,7 @@ export interface PlanModeCopy {
 }
 
 const COPY = {
-  zh: {
+  'zh-CN': {
     abandonConfirmation: {
       title: '放弃这个计划?',
       description: (title) => `“${title}”的执行记录会保留,但之后不能继续恢复。`,
@@ -69,6 +69,25 @@ const COPY = {
       stepStatuses: { pending: '未开始', in_progress: '正在执行', completed: '已完成', skipped: '已跳过' },
     },
   },
+  'zh-TW': {
+    abandonConfirmation: {
+      title: '放棄這個計劃?',
+      description: (title) => `“${title}”的執行記錄會保留,但之後不能繼續恢復。`,
+      confirm: '放棄計劃',
+      cancel: '取消',
+    },
+    proposal: {
+      aria: '計劃方案', kicker: '計劃方案', revision: '修訂版', steps: '執行步驟',
+      risks: '風險', revise: '繼續修改', execute: '執行計劃',
+      statuses: { pending_approval: '等待確認', approved: '已批准', stale: '已過期' },
+    },
+    execution: {
+      aria: '計劃執行狀態', interrupted: '計劃已中斷', running: '正在執行計劃',
+      approvedPlan: '已批准計劃', stepCount: (completed, total) => `${completed}/${total} 步`,
+      resume: '恢復執行', abandon: '放棄計劃',
+      stepStatuses: { pending: '未開始', in_progress: '正在執行', completed: '已完成', skipped: '已跳過' },
+    },
+  },
   en: {
     abandonConfirmation: {
       title: 'Abandon this plan?',
diff --git a/apps/desktop/src/renderer/locales/session-collaboration-copy.ts b/apps/desktop/src/renderer/locales/session-collaboration-copy.ts
index 4b2635b2bb..5c8b3d7036 100644
--- a/apps/desktop/src/renderer/locales/session-collaboration-copy.ts
+++ b/apps/desktop/src/renderer/locales/session-collaboration-copy.ts
@@ -19,7 +19,7 @@
 
 import type { UiCatalog, UiLocale } from '@maka/core/ui-locale';
 
-const ZH = {
+const ZH_CN = {
     shareAction: '分享此任务',
     shareTitle: '分享任务',
     shareDescription: '创建一次性邀请,让另一台 Maka 访问这个任务。',
@@ -119,13 +119,112 @@ const ZH = {
 };
 
 type SessionCollaborationCopy = {
-  readonly [Key in keyof typeof ZH]: (typeof ZH)[Key] extends (
+  readonly [Key in keyof typeof ZH_CN]: (typeof ZH_CN)[Key] extends (
     ...args: infer Args
   ) => string
     ? (...args: Args) => string
     : string;
 };
 
+const ZH_TW = {
+    shareAction: '分享此任務',
+    shareTitle: '分享任務',
+    shareDescription: '建立一次性邀請,讓另一台 Maka 存取這個任務。',
+    enableRemoteAccessTitle: '請先開啟本機遠端存取',
+    enableRemoteAccessBody: '已為你開啟 Runtime Host 設定;開啟遠端存取後即可分享此任務。',
+    disclosureTitle: '分享前請確認',
+    disclosureBody:
+      '對方將看到這個任務已有及之後產生的所有可見內容,包括其中已出現的檔案路徑、憑證或其他機密。撤銷存取只能阻止後續讀取,無法收回對方已複製的內容。',
+    accessLabel: '存取權限',
+    observe: '唯讀',
+    observeHelp: '檢視完整歷史記錄和即時更新',
+    requestTurn: '可請求新輪次',
+    revokeTurnRequests: '撤銷輪次請求權限',
+    requestTurnHelp: '檢視完整內容,並提出需要你逐次核准的新輪次請求',
+    createInvitation: '建立邀請',
+    invitationCode: '一次性邀請碼',
+    invitationHelp: '邀請碼包含連線位址和訪客憑證,不包含擁有者憑證。',
+    coordinationReady: '已包含跨網路協調路徑',
+    coordinationReadyBody:
+      'Runtime Host 已取得 Relay reservation;這能提高不同網路中的裝置成功建立連線的機會。',
+    coordinationUnavailable: '尚未取得 Relay reservation',
+    coordinationUnavailableBody:
+      '此邀請碼目前只包含直接路徑,不同網路中的裝置可能無法連線。可稍後撤銷並重新建立。',
+    copy: '複製邀請碼',
+    copied: '已複製邀請碼',
+    close: '完成',
+    activeAccess: '目前存取',
+    accessUnavailable: '分享管理暫時無法使用;此視窗會自動重試。',
+    guest: '訪客',
+    noAccess: '尚未分享給任何人',
+    pending: '待領取',
+    active: '已連線',
+    revoke: '撤銷',
+    joinAction: '加入分享任務',
+    joinTitle: '加入分享任務',
+    joinDescription: '貼上邀請碼,建立獨立的訪客連線。',
+    code: '邀請碼',
+    pasteInvitation: '貼上邀請碼',
+    clipboardEmpty: '剪貼簿中沒有可貼上的邀請碼。',
+    clipboardUnavailable: '剪貼簿中沒有有效的邀請碼;請手動貼上。',
+    join: '加入',
+    validatingInvitation: '正在驗證邀請碼…',
+    discoveringHost: '正在尋找任務所在的 Runtime Host…',
+    preparingRoute: '正在準備可用路徑…',
+    connectingHost: '正在連線至 Runtime Host…',
+    authenticatingGuest: '正在驗證訪客憑證…',
+    finalizingAccess: '正在確認訪客存取權限…',
+    loadingSession: '正在載入分享任務…',
+    invalidCode: '邀請碼無效',
+    connectionFailed: '無法加入分享任務',
+    directPathUnavailable: '無法連線至任務所在的 Runtime Host。請確認目前 Desktop Client 和該 Host 位於同一個 Peer Mesh,且存在可用的直接連線或成員轉送路徑;可在 Peer Mesh 設定中同步路徑並檢查成員轉送。',
+    insecureTitle: '此連線未加密',
+    insecureBody: '訪客憑證、完整任務內容和輪次請求可能被同一網路中的第三方攔截。僅在你了解並接受風險時繼續。',
+    shareInsecure: '接受風險並建立',
+    joinInsecure: '接受風險並加入',
+    retainedTasks: '已加入的分享任務',
+    mountConnected: '已連線',
+    mountConnecting: '正在連線',
+    mountReconnecting: '正在重新連線',
+    mountUnavailable: '目前不可用',
+    directConnection: '直連',
+    memberTransitConnection: '成員轉發',
+    disconnect: '中斷連線',
+    disconnectFailed: '無法中斷分享任務連線',
+    turnRequests: '輪次請求',
+    noTurnRequests: '尚無輪次請求',
+    approve: '核准',
+    reject: '拒絕',
+    turnRequestPlaceholder: '描述你希望發起的新輪次',
+    submitTurnRequest: '請求新輪次',
+    turnRequestSent: '請求已送出,等待擁有者核准',
+  regenerateRequest: '重新生成回答',
+  regenerateRequestSent: '重新生成請求已送出,等待擁有者核准',
+  ownerRegenerateRequestTitle: '訪客請求重新生成回答',
+  turnRequestCount: (count: number) => `${count} 個輪次請求`,
+  turnRequestReconnecting: '正在重新連線 Runtime Host;草稿會保留,連線恢復後即可傳送。',
+  turnRequestWithdrawn: '請求已撤回',
+  withdrawTurnRequest: '撤回',
+  viewSourceTurn: '檢視原輪次',
+  recoveryStarted: '共享任務正在後台連線',
+  recoveryStartedBody: '任務已儲存在此裝置;連線恢復後會自動出現在任務清單中。',
+    turnRequestReconciling: '正在確認 Host 是否已收到請求…',
+    turnRequestPending: '等待核准',
+    turnRequestApproved: '已核准',
+    turnRequestRejected: '已拒絕',
+    turnRequestStarted: '已開始',
+    turnRequestBlocked: '無法開始',
+    turnRequestFailed: '准入失敗',
+    dismissTurnRequest: '關閉',
+    sharedTask: '分享任務',
+    newTurnRequestTitle: (count: number) => count === 1 ? '新的輪次請求' : `${count} 個新的輪次請求`,
+    newTurnRequestSummary: (count: number) => `${count} 個輪次請求正在等待核准`,
+    reviewTurnRequest: '檢視並核准',
+    ownerTurnRequestTitle: '訪客請求開始新輪次',
+    moreTurnRequests: (count: number) => `還有 ${count} 個請求等待核准`,
+    pendingTurnRequestCount: (count: number) => `${count} 個待核准輪次請求`,
+} satisfies SessionCollaborationCopy;
+
 const EN = {
     shareAction: 'Share this task',
     shareTitle: 'Share task',
@@ -226,7 +325,7 @@ const EN = {
     pendingTurnRequestCount: (count: number) => `${count} pending Turn ${count === 1 ? 'request' : 'requests'}`,
 } satisfies SessionCollaborationCopy;
 
-const COPY = { zh: ZH, en: EN } satisfies UiCatalog<SessionCollaborationCopy>;
+const COPY = { 'zh-CN': ZH_CN, 'zh-TW': ZH_TW, en: EN } satisfies UiCatalog<SessionCollaborationCopy>;
 
 export function getSessionCollaborationCopy(locale: UiLocale) {
   return COPY[locale];
diff --git a/apps/desktop/src/renderer/locales/settings-bot-copy.ts b/apps/desktop/src/renderer/locales/settings-bot-copy.ts
index 698aa1f4d9..b34d2fbca1 100644
--- a/apps/desktop/src/renderer/locales/settings-bot-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-bot-copy.ts
@@ -114,6 +114,92 @@ const zhCopy = {
     waiting: '等待扫码确认… 窗口会每 3 秒刷新登录状态。', retrying: '重试中…', retry: '重试', bridgeGenerating: 'bridge 正在生成二维码', bridgeGeneratingHint: '二维码就绪后会自动显示,也可以手动重新获取。', fetching: '获取中…', fetchAgain: '重新获取',
   },
 } as const;
+const zhTwCopy = {
+  providers: {
+    telegram: { label: 'Telegram', help: '透過 @BotFather 建立 Bot 並取得 Token' },
+    feishu: { label: '飛書', help: '在飛書開放平台建立應用並取得憑證' },
+    wecom: { label: '企業微信', help: '透過企業微信 AI 應用串接,使用 WebSocket 長連線' },
+    wechat: { label: '微信', help: '透過本機 wechat-bridge 串接個人微信,需 iOS / Android 微信 8.0.70+。' },
+    discord: { label: 'Discord', help: '在 Discord Developer Portal 建立 Bot' },
+    dingtalk: { label: '釘釘', help: '在釘釘開發者後臺建立機器人應用' },
+    qq: { label: 'QQ', help: '在 QQ 開放平台建立機器人並取得 AppID 和 AppSecret' },
+    slack: { label: 'Slack', help: '使用 Bot Token 與 App-Level Token 透過 Socket Mode 串接' },
+  } satisfies Record<BotProvider, { label: string; help: string }>,
+  readiness: {
+    unscaffolded: { label: '未開放', detail: '該平台目前不可作為遠端串接管道。', tone: 'neutral' },
+    scaffolded: { label: '待設定', detail: '等待補齊這個平台需要的憑證設定。', tone: 'neutral' },
+    configured: { label: '已設定', detail: '已填寫設定;等待完成憑證或執行狀態驗證。', tone: 'attention' },
+    credentials_valid: { label: '憑證有效', detail: '憑證探測透過;這不代表已能收發訊息。', tone: 'attention' },
+    operational: { label: '執行可用', detail: '最近一次真實執行探測成功。', tone: 'success' },
+    degraded: { label: '執行降級', detail: '之前可用,但最近執行狀態探測失敗。', tone: 'error' },
+  } satisfies Record<BotReadinessState, { label: string; detail: string; tone: StatusSemantic }>,
+  planned: { label: '未開放', detail: '該平台目前不會儲存為遠端串接管道或定時任務投遞目標。', tone: 'neutral' as const },
+  status: {
+    disabled: '開關關閉', noToken: '等待填寫 Bot Token', missingFeishuCredentials: '等待填寫飛書 App ID 或 App Secret',
+    feishuDomainRequired: '飛書憑證有效,等待填寫事件訂閱域名', feishuEventsNotConnected: '飛書憑證有效,等待事件回呼串接',
+    unavailable: '該平台目前不可作為遠端串接管道', stopped: '監聽已停止', detailsInLogs: '執行狀態詳情請見記錄',
+    polling: '長輪詢', gateway: '事件通道', webhook: 'Webhook', none: '無',
+  },
+  overview: {
+    loadFailed: '遠端串接狀態載入失敗', reload: '重新載入', active: '正在使用', sortHint: '按需要處理、最近活動排序',
+    empty: '還沒有正在使用的管道', emptyHelp: '從下方選擇一個訊息平台開始設定。', more: '串接更多管道', choose: '選擇平台開始設定',
+    listening: '監聽中', manageAria: (name: string, status: string) => `管理 ${name},${status}`, connectAria: (name: string) => `串接 ${name}`,
+  },
+  page: {
+    saveFailed: (name: string) => `${name} 儲存失敗`, loadFailed: '載入遠端串接狀態失敗', refreshFailed: '重新整理遠端串接狀態失敗',
+    credentialVerified: (name: string) => `${name} 憑證已驗證`, credentialVerifiedDetail: '憑證檢查已透過。', credentialTestFailed: (name: string) => `${name} 憑證測試失敗`, credentialTestFailedDetail: '請檢查憑證和網路設定後重試。', testError: (name: string) => `${name} 測試出錯`,
+    listening: (name: string) => `${name} 已開始監聽`, notListening: (name: string) => `${name} 啟動後未進入監聽`, startFailed: (name: string) => `${name} 啟動失敗`,
+    disconnectTitle: '斷開微信登入?', disconnectDescription: '將清除本機儲存的掃碼登入憑證,之後需要重新掃碼才能繼續使用微信管道。',
+    disconnect: '斷開登入', cancel: '取消', disconnected: '微信登入已斷開', credentialsCleared: '本機關聯憑證已清除。',
+  },
+  detail: {
+    unavailableHint: '該平台未開放,暫不能啟用。', scanFirstHint: '先掃碼串接後才能啟用。', testFirstHint: '先測試並連線後才能啟用。',
+    back: '返回遠端串接', configDocs: '檢視設定文件', enableAria: (name: string) => `啟用${name}管道`, listening: '正在監聽新訊息', healthy: '連線正常,無需處理。',
+    actionsAria: (name: string) => `${name}管道操作`, quickBind: '快捷綁定', scanLogin: '掃碼登入', scanConnect: '掃碼串接', disconnecting: '斷開中…', disconnectWechat: '斷開微信登入', bridgeQr: '本機橋接二維碼',
+    testing: '測試中…', test: '測試連線', connecting: '連線中…', testAndConnect: '測試並連線', restarting: '重啟中…', restart: '重啟監聽',
+    runtimeAria: (name: string) => `${name}執行狀態`, identity: '身份', unknownIdentity: '未取得', connectionType: '通道型別', lastEvent: '最近事件', noneYet: '暫無', lastTest: '最近一次測試', neverTested: '從未測試',
+    statusRefreshFailed: '執行狀態重新整理失敗', latestFailure: '最近一次失敗', latestFailureDetail: '請檢查設定、網路和執行記錄後重試。', savedButNotConnected: '憑證已儲存,但連線尚未成功啟動。', setupMethod: '串接方式', connectionSettings: '連線設定', localCredentials: '憑證僅儲存在本機', autosave: '自動儲存',
+    setupAria: (name: string) => `${name}串接方式`, quickRecommended: '快捷串接(推薦)', manual: '手動設定', quickAria: (name: string) => `${name}快捷串接`,
+    quickWecomTitle: '掃碼建立並綁定機器人', quickTitle: '掃碼自動建立應用與機器人', quickWecomDetail: '企業管理員掃碼確認後,Maka 會儲存 Bot ID 與 Secret 並啟動長連線。',
+    quickQqTitle: '使用手機 QQ 掃碼建立並綁定機器人', quickQqDetail: '確認後,QQ 會安全返回 AppID 與 AppSecret,Maka 在本機儲存憑證並啟動 Gateway。',
+    telegramOfficialFlow: 'Telegram 官方目前僅支援透過 @BotFather 取得 Bot Token,不提供掃碼建立 Bot 並回傳 Token 的 API。',
+    quickDetail: '掃碼確認後,Maka 會在 main process 內儲存憑證並啟動訊息連線。', feishuRegionAria: '選擇飛書帳號區域', feishu: '飛書',
+    beginQuickBind: '開始快捷綁定', scanWith: (name: string) => `使用${name}掃碼串接`, planned: '這個平台目前只作為平台清單展示,不會進入可用管道,也不會儲存為定時任務投遞目標。',
+    credentialsSaved: (name: string) => `${name}憑證已儲存`, scanComplete: (name: string) => `${name}已完成掃碼串接`, savedAndConnected: '憑證已安全儲存並開始連線',
+    proxy: '代理地址', chinaRequired: '(國內網路必填)', authOnly: '(僅用於 Bot 鑑權)', telegramProxyAria: 'Telegram 代理地址',
+    telegramNotice: '請開啟網路的 TUN 模式後重啟應用,以便完成 Telegram Bot 設定', feishuCredentialId: '飛書憑證 ID', feishuSecret: '飛書 App Secret',
+    feishuDomain: '飛書域名', feishuOption: '飛書 (feishu.cn)', discordProxyAria: 'Discord 代理地址',
+    discordNotice: '國內網路存取 Discord:上方代理僅作用於 Bot 鑑權請求,訊息收發走 WebSocket 長連線需要系統級代理。請開啟網路的 TUN 模式後重啟應用。',
+    dingtalkId: '釘釘應用金鑰', dingtalkSecret: '釘釘 Client Secret', wecomBotPlaceholder: '企業微信 AI 應用 Bot ID', wecomBotAria: '企業微信 Bot ID',
+    wecomSecretPlaceholder: 'AI 應用 Secret', wecomSecretAria: '企業微信 Secret', qqId: 'QQ 應用編號',
+    allowedUsersLabel: (count: number, max: number) => `允許的使用者 ID(${count} / ${max})`, allowedUsersPlaceholder: '每行一個使用者 ID,留空表示不限\n例如:123456789',
+    allowedUsersHelp: 'Telegram 使用者 ID 是 64 位整數;填入後只接收列表裡這些 ID 的來信,其它人發的訊息會被靜默忽略(不會回彈任何提示)。',
+    limitReached: '(已達到上限)', invalidUsers: (values: string) => `下列不是數字 ID,可能是使用者名稱之類的輸入,符合不到任何人:${values}`, moreInvalid: (count: number) => ` 等 ${count} 項`,
+  },
+  onboarding: {
+    providers: {
+      dingtalk: { title: '設定釘釘', ariaLabel: '設定釘釘掃碼串接', qrAlt: '設定釘釘二維碼', subtitle: '在釘釘中掃碼完成應用註冊', waiting: '請使用釘釘掃描二維碼並確認授權', scanned: '已掃碼,請在釘釘中完成確認' },
+      feishu: { title: '設定飛書', ariaLabel: '設定飛書掃碼串接', qrAlt: '設定飛書二維碼', subtitle: '使用飛書掃描二維碼,自動建立並設定機器人', waiting: '請使用飛書掃描二維碼並確認建立', scanned: '已掃碼,請在飛書中完成確認' },
+      wecom: { title: '設定企業微信', ariaLabel: '設定企業微信掃碼串接', qrAlt: '設定企業微信二維碼', subtitle: '快捷綁定會自動建立並連線企業微信機器人', waiting: '開啟企業微信,掃描二維碼完成機器人建立', scanned: '已掃碼,請在企業微信中完成確認' },
+      wechat: { title: '掃碼登入', ariaLabel: '微信掃碼登入', qrAlt: '微信掃碼登入二維碼', subtitle: '請使用微信掃描二維碼完成連線', waiting: '請使用微信掃描二維碼並在手機上確認', scanned: '已掃碼,請在微信中完成確認' },
+      qq: { title: '設定 QQ', ariaLabel: '設定 QQ 掃碼串接', qrAlt: '設定 QQ 二維碼', subtitle: '使用手機 QQ 掃碼建立並綁定機器人', waiting: '請使用手機 QQ 掃描二維碼並確認綁定', scanned: '已掃碼,請在 QQ 中完成確認' },
+    },
+    lark: { title: '設定 Lark', ariaLabel: '設定 Lark 掃碼串接', qrAlt: '設定 Lark 二維碼', subtitle: '使用 Lark 掃描二維碼,自動建立並設定機器人', waiting: '請使用 Lark 掃描二維碼並確認建立', scanned: '已掃碼,請在 Lark 中完成確認' },
+    connectedRefreshFailed: (message: string) => `連線已完成,但狀態重新整理失敗:${message}`, close: (title: string) => `關閉${title}`,
+    generatingAria: '正在生成二維碼', privacy: '憑證僅儲存在本機,不會傳給 renderer 或 Maka 雲端。', openBrowser: '無法掃碼?在瀏覽器中開啟',
+    done: '完成', regenerate: '重新生成', refreshQr: '重新整理二維碼', cancel: '取消', generating: '正在生成安全二維碼…', connecting: '授權完成,正在儲存憑證並啟動連線…',
+    connected: (name: string) => `${name} 已連線`, connectedWarning: '憑證已儲存,但連線尚未成功啟動。', expired: '二維碼已過期,請重新生成', denied: '授權已取消,請重新生成二維碼', cancelled: '掃碼串接已取消', failed: '掃碼串接失敗,請重試', preparing: '準備掃碼串接…',
+  },
+  wechat: {
+    token: '微信 Bot Token', tokenPlaceholder: '本機 wechat-bridge Bearer Token', collapseAdvanced: '收起進階設定', expandAdvanced: '進階設定(公眾號 / 本機 bridge 地址)',
+    bridgeAddress: '本機 bridge 地址', appId: '公眾號 App ID', appIdPlaceholder: '微信公眾號 App ID',
+    appSecret: '公眾號 App Secret', appSecretPlaceholder: '微信公眾號 App Secret',
+    advancedNotice: '本機 bridge 預設為 http://127.0.0.1:18400。公眾號 App ID / App Secret 僅用於公眾號訊息傳送,個人微信掃碼登入走本機 bridge。',
+    readQrFailed: '讀取本機 wechat-bridge 二維碼失敗,請確認 bridge 已啟動。', title: '微信掃碼登入', subtitle: '使用手機微信掃描二維碼,並在手機上確認登入本機 wechat-bridge。', close: '關閉微信掃碼登入',
+    generating: '正在生成二維碼…', loggedIn: '微信已登入,返回後可以測試連線或重啟監聽。', expired: '二維碼已過期', expiredHint: '重新整理二維碼後重新掃碼即可繼續登入。', refreshing: '重新整理中…', refresh: '重新整理二維碼', qrAlt: '微信掃碼登入二維碼',
+    waiting: '等待掃碼確認… 視窗會每 3 秒重新整理登入狀態。', retrying: '重試中…', retry: '重試', bridgeGenerating: 'bridge 正在生成二維碼', bridgeGeneratingHint: '二維碼就緒後會自動顯示,也可以手動重新取得。', fetching: '取得中…', fetchAgain: '重新取得',
+  },
+} as const;
 
 export type BotSettingsCopy = WidenCopy<typeof zhCopy>;
 
@@ -143,7 +229,11 @@ const enCopy: BotSettingsCopy = {
   wechat: { token: 'WeChat Bot Token', tokenPlaceholder: 'Local wechat-bridge Bearer Token', collapseAdvanced: 'Hide advanced settings', expandAdvanced: 'Advanced settings (Official Account / local bridge URL)', bridgeAddress: 'Local bridge URL', appId: 'Official Account App ID', appIdPlaceholder: 'WeChat Official Account App ID', appSecret: 'Official Account App Secret', appSecretPlaceholder: 'WeChat Official Account App Secret', advancedNotice: 'The local bridge defaults to http://127. [...]
 };
 
-const BOT_SETTINGS_COPY = { zh: zhCopy, en: enCopy } satisfies UiCatalog<BotSettingsCopy>;
+const BOT_SETTINGS_COPY = {
+  'zh-CN': zhCopy,
+  'zh-TW': zhTwCopy,
+  en: enCopy,
+} satisfies UiCatalog<BotSettingsCopy>;
 
 export function getBotSettingsCopy(locale: UiLocale): BotSettingsCopy {
   return BOT_SETTINGS_COPY[locale];
diff --git a/apps/desktop/src/renderer/locales/settings-daily-review-copy.ts b/apps/desktop/src/renderer/locales/settings-daily-review-copy.ts
index 0c015bd453..489ba80480 100644
--- a/apps/desktop/src/renderer/locales/settings-daily-review-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-daily-review-copy.ts
@@ -40,7 +40,7 @@ export type DailyReviewSettingsCopy = {
 };
 
 const SETTINGS_DAILY_REVIEW_COPY = {
-  zh: {
+  'zh-CN': {
     defaultModel: '跟随任务默认',
     saveFailed: '保存每日回顾设置失败',
     aria: '每日回顾',
@@ -59,6 +59,25 @@ const SETTINGS_DAILY_REVIEW_COPY = {
     model: '分析模型',
     modelHelp: '未指定时跟随当前任务的默认模型。',
   },
+  'zh-TW': {
+    defaultModel: '跟隨任務預設',
+    saveFailed: '儲存每日回顧設定失敗',
+    aria: '每日回顧',
+    unavailable: '目前版本無法讀取每日回顧設定。',
+    loadFailed: (error) => `讀取每日回顧設定失敗:${error}`,
+    scheduleTitle: '定時分析',
+    scheduleDescription: '按本地時間自動分析前一個完整自然日的活動。',
+    enabled: '啟用定時分析',
+    enabledHelp: '每天生成一份昨日活動分析。',
+    executeTime: '執行時間',
+    executeTimeHelp: '使用 24 小時制的本地時間。',
+    executeTimePlaceholder: 'HH:mm',
+    executeTimeInvalid: '請輸入 24 小時制時間,例如 08:00。',
+    analysisTitle: '分析',
+    analysisDescription: '選擇用於生成固定結構報告的模型。',
+    model: '分析模型',
+    modelHelp: '未指定時跟隨目前任務的預設模型。',
+  },
   en: {
     defaultModel: 'Follow task default',
     saveFailed: 'Failed to save Daily Review settings',
diff --git a/apps/desktop/src/renderer/locales/settings-data-copy.ts b/apps/desktop/src/renderer/locales/settings-data-copy.ts
index 7f38a86812..9ad0c79a31 100644
--- a/apps/desktop/src/renderer/locales/settings-data-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-data-copy.ts
@@ -40,7 +40,7 @@ export type DataSettingsCopy = {
 };
 
 const SETTINGS_DATA_COPY = {
-  zh: {
+  'zh-CN': {
     categories: {
       connections: { label: '模型连接', detail: '供应商连接与默认模型(不含密钥)' },
       settings: { label: '应用设置', detail: '常规、搜索、机器人、代理等设置' },
@@ -67,6 +67,33 @@ const SETTINGS_DATA_COPY = {
     sensitiveWarning: '⚠️ 密钥将以明文写入导出文件。任何拿到该文件的人都能使用这些密钥,请妥善保管、不要分享。',
     conflictAria: '导入时同名连接的处理方式', skip: '跳过', overwrite: '覆盖', exportConfig: '导出配置…', importConfig: '导入配置…',
   },
+  'zh-TW': {
+    categories: {
+      connections: { label: '模型連線', detail: '供應商連線與預設模型(不含金鑰)' },
+      settings: { label: '應用設定', detail: '常規、搜尋、機器人、代理等設定' },
+      memory: { label: '本地記憶', detail: '本機 MEMORY.md 的內容' },
+      credentials: { label: '憑據(API 金鑰、權杖)', detail: '模型金鑰與訂閱權杖等敏感資訊', sensitive: true },
+    },
+    importSummary: {
+      connections: (created, overwritten, skipped) => `連線 新增${created}·覆蓋${overwritten}·跳過${skipped}`,
+      settings: '設定已應用', credentials: (applied, skipped) => skipped > 0 ? `憑據 ${applied}(跳過 ${skipped})` : `憑據 ${applied}`,
+      memory: '記憶已應用', empty: '檔案不含可匯入的內容',
+    },
+    loadFailed: '載入資料目錄失敗', openFailed: (label) => `無法開啟${label}`, pathCopied: '已複製工作區路徑', copyFailed: '複製失敗', copyFailedDetail: '剪貼簿不可用或被系統拒絕。',
+    historyCleared: '已清空輸入歷史', historyClearedDetail: '已傳送的提示詞記錄已從本機移除。', selectCategory: '請至少選擇一個類別',
+    exported: '已匯出設定', exportedDetail: (items) => `包含:${items.join('、')}`, exportFailed: '匯出失敗', noCategories: '未選擇任何類別', tryAgain: '請稍後重試',
+    imported: '已匯入設定', importFailed: '匯入失敗', invalidFile: '檔案無效或版本不受支援。',
+    rows: {
+      workspace: '工作區路徑', workspaceDetail: '任務、設定、憑據和技能檔案都存在這個目錄下。', loadValueFailed: '載入失敗', loading: '正在載入…',
+      history: '輸入歷史', historyDetail: '上箭頭 / 下箭頭調出的已傳送提示詞記錄,儲存在本機、重啟後仍在。清空後無法恢復。',
+    },
+    actionsAria: '工作區資料操作', opening: '開啟中…', openWorkspace: '開啟工作區資料夾', copying: '複製中…', copyPath: '複製路徑', clearing: '清空中…', clearHistory: '清空輸入歷史',
+    backupTitle: '備份與恢復', backupNotice: '本機資料儲存在工作區。需要備份時先退出 Maka,再複製整個目錄;恢復時替換同一路徑後重啟。模型連線憑據隨工作區恢復後需要重新測試;訂閱帳號權杖通常需要重新登入。',
+    pathLoadFailed: (error) => `無法載入工作區路徑:${error}`, configAria: '設定匯入匯出', configTitle: '設定匯入匯出',
+    configHelp: '勾選要匯出的內容,生成一個 JSON 備份檔案;換機或重灌時可再匯入。預設不含金鑰。', categoryAria: '選擇匯出內容',
+    sensitiveWarning: '⚠️ 金鑰將以明文寫入匯出檔案。任何拿到該檔案的人都能使用這些金鑰,請妥善保管、不要分享。',
+    conflictAria: '匯入時同名連線的處理方式', skip: '跳過', overwrite: '覆蓋', exportConfig: '匯出設定…', importConfig: '匯入設定…',
+  },
   en: {
     categories: {
       connections: { label: 'Model connections', detail: 'Provider connections and default models (without secrets)' },
diff --git a/apps/desktop/src/renderer/locales/settings-health-copy.ts b/apps/desktop/src/renderer/locales/settings-health-copy.ts
index 6bb4aebbb9..f1b9ed34f0 100644
--- a/apps/desktop/src/renderer/locales/settings-health-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-health-copy.ts
@@ -75,6 +75,71 @@ const layersZh: HealthCenterCopy['layers'] = {
   storage: { label: '存储', description: '工作区文件、JSONL、SQLite 等本地存储健康度。' },
 };
 
+const layersZhTw: HealthCenterCopy['layers'] = {
+  configuration: { label: '設定', description: '設定頁中的必填項目是否完整。' },
+  validation: { label: '驗證', description: '憑證與端點的連線測試結果;驗證通過不代表傳送路徑可用。' },
+  permission: { label: '系統權限', description: '所需的 OS 與 TCC 權限是否已授權。' },
+  feature: { label: '功能狀態', description: '功能是否已明確啟用,以及目前是否可用。' },
+  action_approval: { label: '操作核准', description: '工具呼叫與高風險操作的核准原則狀態。' },
+  memory_acceptance: { label: '記憶寫入', description: '是否已接受記憶寫入約定,以及是否已啟用寫入。' },
+  runtime_probe: { label: '執行狀態探測', description: '最近一次實際傳送、串流或事件接收的探測結果。' },
+  storage: { label: '儲存空間', description: '工作區檔案、JSONL、SQLite 和其他本機儲存空間的健康狀態。' },
+};
+
+const healthMessageZhTw: Readonly<Record<string, string>> = {
+  '连接已关闭。': '連線已關閉。',
+  '等待选择默认模型。': '等待選擇預設模型。',
+  '凭据与端点验证已通过。': '憑證與端點驗證已通過。',
+  '连接需要重新修复认证。': '連線需要重新完成驗證。',
+  '上次连接验证失败。': '上次連線驗證失敗。',
+  '没有启用任何模型。': '尚未啟用任何模型。',
+  '不是工作区的默认模型来源。': '不是工作區的預設模型來源。',
+  '等待验证连接。': '等待驗證連線。',
+  '等待完成发送运行态探测。': '等待完成傳送執行狀態探測。',
+  '能力门禁已满足。': '能力門檻已滿足。',
+  '能力已关闭或暂停。': '能力已關閉或暫停。',
+  '等待补齐能力配置。': '等待完成能力設定。',
+  '能力被必要系统权限阻塞。': '能力受到必要系統權限阻擋。',
+  '能力运行态探测处于降级状态。': '能力執行狀態探測目前處於降級狀態。',
+  '最近一次发送已完成。': '最近一次傳送已完成。',
+  '最近一次发送已由用户停止。': '最近一次傳送已由使用者停止。',
+  '最近一次发送失败。': '最近一次傳送失敗。',
+};
+
+const healthDetailZhTw: Readonly<Record<string, string>> = {
+  '这是连接验证结果,不代表发送、流式输出或中断通路已经运行通过。': '這是連線驗證結果,不代表傳送、串流輸出或中斷路徑已實際執行成功。',
+  '在 设置 · 模型 的连接详情里启用至少一个模型后才能使用该连接。': '請在「設定・模型」的連線詳細資料中啟用至少一個模型,才能使用此連線。',
+  '在任务中显式选择该连接的模型即可正常使用;新对话的默认模型在 设置 · 通用 配置。': '在任務中明確選擇此連線的模型即可使用;新對話的預設模型可在「設定・一般」中設定。',
+  '凭据验证与真实发送、流式输出、中断通路是两层健康信号。': '憑證驗證與實際傳送、串流輸出、中斷路徑是兩層不同的健康訊號。',
+  '该能力当前已关闭。': '此能力目前已關閉。',
+  '等待填写平台凭据。': '等待填寫平台憑證。',
+  '仅 macOS 系统权限可探测。': '只能探測 macOS 系統權限。',
+  '系统未提供可直接读取的授权状态。': '系統未提供可直接讀取的授權狀態。',
+  '状态详情请见对应设置页。': '狀態詳細資料請參閱對應的設定頁。',
+};
+
+function healthSignalLabelZhTw(signal: HealthSignal): string {
+  return signal.label.endsWith(' 运行态')
+    ? `${signal.label.slice(0, -' 运行态'.length)} 執行狀態`
+    : signal.label;
+}
+
+function healthSignalMessageZhTw(signal: HealthSignal): string {
+  return healthMessageZhTw[signal.message]
+    ?? (/[\u3400-\u9fff]/u.test(signal.message) ? '健康狀態已更新。' : signal.message);
+}
+
+function healthSignalDetailZhTw(signal: HealthSignal): string | undefined {
+  if (!signal.detail) return undefined;
+  const runtimeDetail = /^模型=(.*?) · 延迟=(\d+)ms(?: · 错误类型=(.*))?$/u.exec(signal.detail);
+  if (runtimeDetail) {
+    const [, model, latency, errorClass] = runtimeDetail;
+    return `模型=${model} · 延遲=${latency}ms${errorClass ? ` · 錯誤類型=${errorClass}` : ''}`;
+  }
+  return healthDetailZhTw[signal.detail]
+    ?? (/[\u3400-\u9fff]/u.test(signal.detail) ? '詳細資料請參閱對應的設定頁。' : signal.detail);
+}
+
 const layersEn: HealthCenterCopy['layers'] = {
   configuration: { label: 'Configuration', description: 'Whether required settings are complete.' },
   validation: { label: 'Validation', description: 'Credential and endpoint connectivity results. A passing validation does not prove the send path works.' },
@@ -87,7 +152,7 @@ const layersEn: HealthCenterCopy['layers'] = {
 };
 
 const SETTINGS_HEALTH_COPY = {
-  zh: {
+  'zh-CN': {
     loading: '正在加载健康快照', readFailed: '无法读取健康快照', noData: '健康服务未返回数据。', readAgain: '重新读取',
     title: '健康中心', subtitle: '各项能力当前的运行状况检查。',
     badge: '只读快照', lastRead: '最近一次读取:', refresh: '刷新', summaryAria: '按状态筛选健康信号', summaryFilterAria: (label, count, selected) => selected ? `${label} ${count} 项,当前筛选;再次按下显示全部` : `仅显示${label}健康信号,共 ${count} 项`,
@@ -106,6 +171,25 @@ const SETTINGS_HEALTH_COPY = {
     signalMessage: (signal) => signal.message,
     signalDetail: (signal) => signal.detail,
   },
+  'zh-TW': {
+    loading: '正在載入健康快照', readFailed: '無法讀取健康快照', noData: '健康服務未返回資料。', readAgain: '重新讀取',
+    title: '健康中心', subtitle: '各項能力目前的執行狀況檢查。',
+    badge: '只讀快照', lastRead: '最近一次讀取:', refresh: '重新整理', summaryAria: '按狀態篩選健康訊號', summaryFilterAria: (label, count, selected) => selected ? `${label} ${count} 項,目前篩選;再次按下顯示全部` : `僅顯示${label}健康訊號,共 ${count} 項`,
+    blockers: {
+      send: (count, totalCount) => `全部健康訊號中,${count}/${totalCount} 條會阻塞傳送`,
+      capability: (count, totalCount) => `全部健康訊號中,${count}/${totalCount} 條會阻塞能力`,
+    },
+    layerAria: (label) => `${label}健康訊號`, layerListAria: (label) => `${label}健康訊號列表`,
+    footnote: '本頁不直接執行測試、修復或權限變更;它只彙總目前已記錄的健康訊號。需要處理問題時,請進入對應設定頁或重新觸發相關功能。',
+    layers: layersZhTw,
+    statuses: { ok: { label: '正常', tone: 'neutral' }, info: { label: '提示', tone: 'neutral' }, warning: { label: '警告', tone: 'attention' }, error: { label: '錯誤', tone: 'error' }, unknown: { label: '未知', tone: 'neutral' } },
+    scopes: { app: '應用', llm_connection: 'LLM 連線', bot: '機器人', capability: '能力', storage: '儲存' },
+    sources: { connection_test: '連線測試', capability_snapshot: '能力快照', permission_snapshot: '權限快照', runtime_probe: '執行態探測', settings: '設定', storage: '本地儲存' },
+    source: '來源:', blocksSend: '阻塞傳送', blocksCapability: '阻塞能力',
+    signalLabel: healthSignalLabelZhTw,
+    signalMessage: healthSignalMessageZhTw,
+    signalDetail: healthSignalDetailZhTw,
+  },
   en: {
     loading: 'Loading health snapshot', readFailed: 'Could not read health snapshot', noData: 'The health service returned no data.', readAgain: 'Read again',
     title: 'Health center', subtitle: 'How each capability is currently doing.',
diff --git a/apps/desktop/src/renderer/locales/settings-memory-copy.ts b/apps/desktop/src/renderer/locales/settings-memory-copy.ts
index 3c8685a733..39dfd21856 100644
--- a/apps/desktop/src/renderer/locales/settings-memory-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-memory-copy.ts
@@ -82,14 +82,21 @@ const zhText = {
   localFile: '本地 MEMORY.md', localFileHelp: '透明 Markdown 文件,保存在当前本机工作区。这里的内容不会自动从聊天里抽取。', enableLocalFile: '启用本地 MEMORY.md', agentReadable: '模型上下文可读取', agentReadableHelp: '默认关闭。开启后,发送消息时会把本地记忆一并提供给模型;隐身模式下仍然不提供。', enableAgentRead: '允许模型上下文读取本地记忆', waitingFile: '等待创建 MEMORY.md', waitingBackup: '等待生成上一版备份', dirty: '有未保存修改', savedDraft: '草稿已保存', backupCandidates: '备份候选', backupCandidatesAria: '本地记忆备份候选列表', opening: '打开中…', open: '打开', restoring: '恢复中…', restore: '恢复', copying: '复制中…', copyR [...]
 } satisfies Record<MemoryTextKey, string>;
 
+const zhTwText = {
+  localFile: '本地 MEMORY.md', localFileHelp: '透明 Markdown 檔案,儲存在目前本機工作區。這裡的內容不會自動從聊天裡抽取。', enableLocalFile: '啟用本地 MEMORY.md', agentReadable: '模型上下文可讀取', agentReadableHelp: '預設關閉。開啟後,傳送訊息時會把本地記憶一併提供給模型;隱身模式下仍然不提供。', enableAgentRead: '允許模型上下文讀取本地記憶', waitingFile: '等待建立 MEMORY.md', waitingBackup: '等待生成上一版備份', dirty: '有未儲存修改', savedDraft: '草稿已儲存', backupCandidates: '備份候選', backupCandidatesAria: '本地記憶備份候選列表', opening: '開啟中…', open: '開啟', restoring: '恢復中…', restore: '恢復', copying: '複製中…', copyR [...]
+} satisfies Record<MemoryTextKey, string>;
+
 const enText = {
   localFile: 'Local MEMORY.md', localFileHelp: 'A transparent Markdown file stored in the current local workspace. Content is never extracted from chats automatically.', enableLocalFile: 'Enable local MEMORY.md', agentReadable: 'Available to model context', agentReadableHelp: 'Off by default. When enabled, local memory is given to the model along with your message; incognito mode still withholds it.', enableAgentRead: 'Allow model context to read local memory', waitingFile: 'Waiting to c [...]
 } satisfies Record<MemoryTextKey, string>;
 
 const SETTINGS_MEMORY_COPY = {
-  zh: makeCopy('zh-CN', zhText, {
+  'zh-CN': makeCopy('zh-CN', zhText, {
     origins: { manual: '手动记录', imported: '导入记录', extracted: '确认提取', unknown: '手写条目' }, entryStatuses: { draft: '草稿', review_required: '待确认', active: '生效', archived: '已归档', rejected: '已拒绝', unknown: '未识别' }, backupKinds: { reset: '重置前备份', restore: '恢复前备份', save: '保存前备份' }, memoryStatuses: { ok: '本地文件已就绪', disabled: '已关闭', safe_mode: '安全模式', incognito_blocked: '隐身禁用', error: '读取失败' }, promptBlocked: { disabled: '本地记忆已关闭。', incognito: '隐身模式下不会提供本地记忆。', safeMode: 'MEMORY.md 过大,当前不会提供。', agen [...]
   }),
+  'zh-TW': makeCopy('zh-TW', zhTwText, {
+    origins: { manual: '手動記錄', imported: '匯入記錄', extracted: '確認提取', unknown: '手寫條目' }, entryStatuses: { draft: '草稿', review_required: '待確認', active: '生效', archived: '已歸檔', rejected: '已拒絕', unknown: '未識別' }, backupKinds: { reset: '重置前備份', restore: '恢復前備份', save: '儲存前備份' }, memoryStatuses: { ok: '本地檔案已就緒', disabled: '已關閉', safe_mode: '安全模式', incognito_blocked: '隱身停用', error: '讀取失敗' }, promptBlocked: { disabled: '本地記憶已關閉。', incognito: '隱身模式下不會提供本地記憶。', safeMode: 'MEMORY.md 過大,目前不會提供。', agen [...]
+  }),
   en: makeCopy('en-US', enText, {
     origins: { manual: 'Manual entry', imported: 'Imported entry', extracted: 'Confirmed extraction', unknown: 'Handwritten entry' }, entryStatuses: { draft: 'Draft', review_required: 'Needs review', active: 'Active', archived: 'Archived', rejected: 'Rejected', unknown: 'Unrecognized' }, backupKinds: { reset: 'Before reset', restore: 'Before restore', save: 'Before save' }, memoryStatuses: { ok: 'Local file ready', disabled: 'Off', safe_mode: 'Safe mode', incognito_blocked: 'Disabled in  [...]
   }),
@@ -99,26 +106,28 @@ export function getMemorySettingsCopy(locale: UiLocale): MemorySettingsCopy { re
 
 function makeCopy(intlLocale: string, text: Record<MemoryTextKey, string>, values: Pick<MemorySettingsCopy, 'origins' | 'entryStatuses' | 'backupKinds' | 'memoryStatuses' | 'promptBlocked' | 'backupOversize' | 'previewOversize' | 'previewTruncationMarker'>): MemorySettingsCopy {
   const plural = (count: number, one: string, many: string) => `${count} ${count === 1 ? one : many}`;
-  const isZh = intlLocale === 'zh-CN';
+  const isZh = intlLocale !== 'en-US';
+  const isZhTw = intlLocale === 'zh-TW';
+  const zh = (simplified: string, traditional: string) => isZhTw ? traditional : simplified;
   return {
     intlLocale, text, ...values,
-    countActive: (count, draft) => isZh ? `${draft ? '草稿 ' : ''}${count} 条生效` : `${draft ? 'Draft · ' : ''}${plural(count, 'active entry', 'active entries')}`,
-    countArchived: (count, draft) => isZh ? `${draft ? '草稿 ' : ''}${count} 条已归档` : `${draft ? 'Draft · ' : ''}${plural(count, 'archived entry', 'archived entries')}`,
-    saveSummary: (active, archived) => isZh ? `当前 ${active} 条生效${archived > 0 ? ` / ${archived} 条已归档` : ''};已保留上一版备份。` : `${plural(active, 'active entry', 'active entries')}${archived > 0 ? ` / ${plural(archived, 'archived entry', 'archived entries')}` : ''}; the previous version was backed up.`,
-    backupSummary: (active, archived) => isZh ? `${active} 条生效${archived > 0 ? ` / ${archived} 条已归档` : ''}` : `${plural(active, 'active entry', 'active entries')}${archived > 0 ? ` / ${plural(archived, 'archived entry', 'archived entries')}` : ''}`,
-    countEntries: (count) => isZh ? `${count} 条记忆` : plural(count, 'memory', 'memories'),
-    countMatches: (filtered, total) => isZh ? `${filtered} / ${total} 条匹配` : `${filtered} / ${total} matching`,
-    listAria: (title) => isZh ? `${title}列表` : `${title} list`,
-    entryActionsAria: (title) => isZh ? `${title} 记忆操作` : `${title} memory actions`,
+    countActive: (count, draft) => isZh ? `${draft ? '草稿 ' : ''}${count} ${zh('条生效', '則生效')}` : `${draft ? 'Draft · ' : ''}${plural(count, 'active entry', 'active entries')}`,
+    countArchived: (count, draft) => isZh ? `${draft ? '草稿 ' : ''}${count} ${zh('条已归档', '則已歸檔')}` : `${draft ? 'Draft · ' : ''}${plural(count, 'archived entry', 'archived entries')}`,
+    saveSummary: (active, archived) => isZh ? `${zh('当前', '目前')} ${active} ${zh('条生效', '則生效')}${archived > 0 ? ` / ${archived} ${zh('条已归档', '則已歸檔')}` : ''};${zh('已保留上一版备份。', '已保留上一版備份。')}` : `${plural(active, 'active entry', 'active entries')}${archived > 0 ? ` / ${plural(archived, 'archived entry', 'archived entries')}` : ''}; the previous version was backed up.`,
+    backupSummary: (active, archived) => isZh ? `${active} ${zh('条生效', '則生效')}${archived > 0 ? ` / ${archived} ${zh('条已归档', '則已歸檔')}` : ''}` : `${plural(active, 'active entry', 'active entries')}${archived > 0 ? ` / ${plural(archived, 'archived entry', 'archived entries')}` : ''}`,
+    countEntries: (count) => isZh ? `${count} ${zh('条记忆', '則記憶')}` : plural(count, 'memory', 'memories'),
+    countMatches: (filtered, total) => isZh ? `${filtered} / ${total} ${zh('条匹配', '則符合')}` : `${filtered} / ${total} matching`,
+    listAria: (title) => isZh ? `${title}${zh('列表', '清單')}` : `${title} list`,
+    entryActionsAria: (title) => isZh ? `${title} ${zh('记忆操作', '記憶操作')}` : `${title} memory actions`,
     entryActionAria: (action, identity) => isZh ? `${action}:${identity}` : `${action}: ${identity}`,
-    openBackupAria: (label) => isZh ? `打开备份候选 ${label}` : `Open backup candidate ${label}`, restoreBackupAria: (label) => isZh ? `恢复备份候选 ${label}` : `Restore backup candidate ${label}`, copyBackupAria: (label) => isZh ? `复制备份候选引用 ${label}` : `Copy backup candidate reference ${label}`,
-    draftStatusAria: (action) => isZh ? `${action},保存前不会写入 MEMORY.md` : `${action}; MEMORY.md is not written until you save`,
-    restoreLatestDescription: (label) => isZh ? `会先备份当前 MEMORY.md,再用最近一次备份覆盖当前文件。将恢复:${label}` : `The current MEMORY.md will be backed up before the latest backup replaces it. Restore: ${label}`,
-    restoreCandidateDescription: (label) => isZh ? `会先备份当前 MEMORY.md,再用选中的备份覆盖当前文件。将恢复:${label}` : `The current MEMORY.md will be backed up before the selected backup replaces it. Restore: ${label}`,
-    redactedDetail: (summary) => isZh ? `写入前已替换疑似 token、API key 或密码;${summary}` : `Suspected tokens, API keys, or passwords were redacted before writing; ${summary}`,
-    openBackupFailed: (kind) => isZh ? `打开${kind}失败` : `Failed to open ${kind}`,
-    previewTruncated: (limit) => isZh ? `预览已按 ${limit} 字符上限截断` : `Preview truncated at the ${limit}-character limit`,
-    previewUsage: (length, limit) => isZh ? `预览 ${length} / ${limit} 字符` : `Preview ${length} / ${limit} characters`,
-    previewLimit: (limit) => isZh ? `prompt 上限 ${limit} 字符` : `Prompt limit: ${limit} characters`,
+    openBackupAria: (label) => isZh ? `${zh('打开备份候选', '開啟備份候選')} ${label}` : `Open backup candidate ${label}`, restoreBackupAria: (label) => isZh ? `${zh('恢复备份候选', '還原備份候選')} ${label}` : `Restore backup candidate ${label}`, copyBackupAria: (label) => isZh ? `${zh('复制备份候选引用', '複製備份候選引用')} ${label}` : `Copy backup candidate reference ${label}`,
+    draftStatusAria: (action) => isZh ? `${action},${zh('保存前不会写入', '儲存前不會寫入')} MEMORY.md` : `${action}; MEMORY.md is not written until you save`,
+    restoreLatestDescription: (label) => isZh ? `${zh('会先备份当前 MEMORY.md,再用最近一次备份覆盖当前文件。将恢复', '會先備份目前的 MEMORY.md,再以最近一次備份覆蓋目前檔案。將還原')}:${label}` : `The current MEMORY.md will be backed up before the latest backup replaces it. Restore: ${label}`,
+    restoreCandidateDescription: (label) => isZh ? `${zh('会先备份当前 MEMORY.md,再用选中的备份覆盖当前文件。将恢复', '會先備份目前的 MEMORY.md,再以選取的備份覆蓋目前檔案。將還原')}:${label}` : `The current MEMORY.md will be backed up before the selected backup replaces it. Restore: ${label}`,
+    redactedDetail: (summary) => isZh ? `${zh('写入前已替换疑似', '寫入前已遮蔽疑似')} token、API key ${zh('或密码', '或密碼')};${summary}` : `Suspected tokens, API keys, or passwords were redacted before writing; ${summary}`,
+    openBackupFailed: (kind) => isZh ? `${zh('打开', '開啟')}${kind}${zh('失败', '失敗')}` : `Failed to open ${kind}`,
+    previewTruncated: (limit) => isZh ? `${zh('预览已按', '預覽已依')} ${limit} ${zh('字符上限截断', '字元上限截斷')}` : `Preview truncated at the ${limit}-character limit`,
+    previewUsage: (length, limit) => isZh ? `${zh('预览', '預覽')} ${length} / ${limit} ${zh('字符', '字元')}` : `Preview ${length} / ${limit} characters`,
+    previewLimit: (limit) => isZh ? `prompt ${zh('上限', '上限')} ${limit} ${zh('字符', '字元')}` : `Prompt limit: ${limit} characters`,
   };
 }
diff --git a/apps/desktop/src/renderer/locales/settings-navigation-copy.ts b/apps/desktop/src/renderer/locales/settings-navigation-copy.ts
index cd81e72bf8..ecd1395e0e 100644
--- a/apps/desktop/src/renderer/locales/settings-navigation-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-navigation-copy.ts
@@ -27,7 +27,7 @@ export type SettingsNavigationCopy = {
 };
 
 const SETTINGS_NAVIGATION_COPY_BY_LOCALE = {
-  zh: {
+  'zh-CN': {
     groups: {
       preferences: '偏好',
       capabilities: '能力',
@@ -53,6 +53,32 @@ const SETTINGS_NAVIGATION_COPY_BY_LOCALE = {
       about: { label: '关于', description: '版本、更新与隐私承诺。' },
     },
   },
+  'zh-TW': {
+    groups: {
+      preferences: '偏好',
+      capabilities: '能力',
+      activity: '活動',
+      system: '系統',
+    },
+    sections: {
+      general: { label: '通用', description: '顯示名稱與介面語言、隱私與通知、任務預設與網路代理。' },
+      appearance: { label: '外觀', description: '介面主題與調色盤。' },
+      projects: { label: '工作區', description: '管理 Runtime Host 連線,以及預設 Host 上的專案。' },
+      models: { label: '模型', description: '模型連線、API key 與 OAuth 訂閱管理。' },
+      subagents: { label: '子 Agent', description: '設定主 Agent 可以自動選擇的子 Agent、能力邊界與模型。' },
+      usage: { label: '使用統計', description: 'token、模型、工具使用走勢與配額追蹤。' },
+      'archived-tasks': { label: '已歸檔任務', description: '恢復或徹底刪除已歸檔的任務。' },
+      'import-tasks': { label: '匯入任務', description: '把本機其他 Agent 的對話記錄轉換成 Maka 任務。' },
+      memory: { label: '記憶', description: 'Maka 記住的內容,以及本地 MEMORY.md 檔案。' },
+      'daily-review': { label: '每日回顧', description: '每天分析本機任務,生成摘要、遺漏提醒和建議。' },
+      'bot-chat': { label: '遠端串接', description: '透過 Telegram、飛書、微信等平臺從其他裝置與 Maka 對話。' },
+      search: { label: '聯網搜尋', description: '聯網搜尋供應商(如 Tavily)憑據與隱私邊界。' },
+      data: { label: '資料', description: '本地工作區路徑、備份與恢復。' },
+      permissions: { label: '權限與能力', description: '系統權限授予狀態與 Maka 能力執行時檢查。' },
+      health: { label: '健康', description: '執行時連線、模型探針與本地健康狀態。' },
+      about: { label: '關於', description: '版本、執行環境與隱私承諾。' },
+    },
+  },
   en: {
     groups: {
       preferences: 'Preferences',
diff --git a/apps/desktop/src/renderer/locales/settings-preferences-copy.ts b/apps/desktop/src/renderer/locales/settings-preferences-copy.ts
index d4f31f31eb..343e1ba79e 100644
--- a/apps/desktop/src/renderer/locales/settings-preferences-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-preferences-copy.ts
@@ -269,10 +269,10 @@ export type SettingsPreferencesCopy = {
 };
 
 const SETTINGS_PREFERENCES_COPY_BY_LOCALE = {
-  zh: {
+  'zh-CN': {
     personalization: {
       saveFailed: '保存失败', displayName: '显示名称', displayNameHelp: 'Maka 在聊天里会以这个名字称呼你。留空就用默认的“你”。', displayNamePlaceholder: '例如:JK', displayNameUnset: '未设置,Maka 会称呼你“你”', displayNameChange: '更改', displayNameSet: '设置',
-      interfaceLanguage: '界面语言', interfaceLanguageHelp: '选择 Maka 界面的显示语言。切换后立即生效,重启后保持。', localeOptions: [['auto', '跟随系统'], ['zh', '中文'], ['en', 'English']],
+      interfaceLanguage: '界面语言', interfaceLanguageHelp: '选择 Maka 界面的显示语言。切换后立即生效,重启后保持。', localeOptions: [['auto', '跟随系统'], ['zh-CN', '简体中文'], ['zh-TW', '繁體中文'], ['en', 'English']],
       assistantTone: '助手语气偏好', assistantToneHelp: '最多 500 字,只影响回答的语气和风格。权限确认与安全规则不受影响;改动会自动保存。', assistantTonePlaceholder: '例如:技术严谨、偏简洁、不要 emoji。',
     },
     sections: {
@@ -367,9 +367,100 @@ const SETTINGS_PREFERENCES_COPY_BY_LOCALE = {
     },
     password: { copyFailed: '复制失败', clipboardUnavailable: '剪贴板不可用或被系统拒绝。', copying: '复制中', copied: '已复制', copy: '复制', hide: '隐藏', show: '显示', value: '凭据值' },
   },
+  'zh-TW': {
+    personalization: {
+      saveFailed: '儲存失敗', displayName: '顯示名稱', displayNameHelp: 'Maka 在聊天裡會以這個名字稱呼你。留空就用預設的“你”。', displayNamePlaceholder: '例如:JK', displayNameUnset: '未設定,Maka 會稱呼你“你”', displayNameChange: '更改', displayNameSet: '設定',
+      interfaceLanguage: '介面語言', interfaceLanguageHelp: '選擇 Maka 介面的顯示語言。切換後立即生效,重新啟動後仍會保留。', localeOptions: [['auto', '自動(跟隨系統)'], ['zh-CN', '简体中文'], ['zh-TW', '繁體中文'], ['en', 'English']],
+      assistantTone: '助手語氣偏好', assistantToneHelp: '最多 500 字,只影響回答的語氣和風格。權限確認與安全規則不受影響;改動會自動儲存。', assistantTonePlaceholder: '例如:技術嚴謹、偏簡潔、不要 emoji。',
+    },
+    sections: {
+      identity: '身份', identityHelp: 'Maka 如何稱呼你,以及介面語言和回答語氣。',
+      privacy: '隱私與通知', privacyHelp: '本地資料的讀寫範圍,以及桌面通知時機。',
+      chatDefaults: '任務預設', chatDefaultsHelp: '新任務的起始模型、權限模式與思考級別。',
+      shell: '命令列環境', shellHelp: '選擇 Runtime Host 執行 Bash 工具和終端命令時使用的 shell。',
+      network: '網路', networkHelp: 'AI 模型請求走的網路通道。',
+      theme: '主題', themeHelp: '介面跟隨系統,還是固定淺色或深色。',
+      palette: '調色盤', paletteHelp: '強調色與畫布色調;切換會立即生效並儲存在本地。',
+      appIcon: '應用圖示', appIconHelp: 'Dock、工作列和切換器裡顯示的 Maka 圖示;切換會立即生效。',
+      fontSize: '字型大小', fontSizeHelp: '介面與終端機的文字大小;調整會立即生效並儲存在本機。',
+      pets: '自訂寵物', petsHelp: '管理你自己匯入的 PetPack。Maka 不預裝、也不預設啟用任何寵物。',
+    },
+    appearance: {
+      saveFailed: '儲存外觀設定失敗', theme: '主題', palette: '調色盤',
+      themeOptions: { light: { label: '淺色', help: '始終使用淺色介面。' }, dark: { label: '深色', help: '始終使用深色介面。' }, auto: { label: '跟隨系統', help: '符合系統目前的淺色或深色偏好。' } },
+      paletteLabels: { default: '預設', onedark: 'One Dark', 'catppuccin-mocha': 'Catppuccin Mocha', 'tokyo-night': 'Tokyo Night', nord: 'Nord', coral: '珊瑚', azure: '湖藍', forest: '森林', dusk: '暮光', sand: '沙金', mono: '極簡灰' },
+      paletteHelp: { default: 'Maka 品牌藍強調色', onedark: '編輯器經典深色', 'catppuccin-mocha': '紫調柔和深色', 'tokyo-night': '深藍主題', nord: '北歐冷色', coral: '暖粉 / 珊瑚強調色', azure: '湖藍強調色,乾淨冷靜', forest: '深苔綠與暖蜂蜜強調色', dusk: '深紫羅蘭與冷調畫布', sand: '琥珀沙金與暖奶白', mono: '純灰階,無彩色干擾' },
+      paletteGroups: { editor: '編輯器主題', product: '產品色調' },
+      appIconLabels: { default: '經典', mono: '單色', 'sky': '原色天藍', 'cyan': '青藍', 'ice': '冰藍漸變', 'pale-inverted': '淡底深標', 'ink': '墨黑', 'paper': '紙白', 'graphite': '石墨', 'pencil-kraft': '鉛筆・牛皮紙', 'pencil-sky': '鉛筆・天藍', 'pencil-navy': '鉛筆・深藍', 'alpine': '晴空雪山', 'dusk': '黃昏', 'night': '夜山', 'midnight': '午夜藍', 'carbon': 'OLED 純黑', 'slate': '石板灰', 'obsidian': '黑曜石', 'neon-cyan': '霓虹青', 'matrix': '磷光綠', 'magenta': '洋紅', 'amber-crt': '琥珀 CRT', 'clay': '陶土', 'sage': '鼠尾草', 'dust': '灰粉', 'fog': '霧藍', [...]
+      appIconHelp: { default: 'Maka 預設品牌圖示', mono: '灰階版本,Dock 裡更安靜', 'sky': '幾何 M 標,品牌藍', 'cyan': '偏青的藍', 'ice': '由淺到深的藍色漸變', 'pale-inverted': '淡藍底配深藍標', 'ink': '黑底白標,對比最強', 'paper': '白底黑標', 'graphite': '白底黑標,筆尖為灰', 'pencil-kraft': '鉛筆意象,牛皮紙底', 'pencil-sky': '鉛筆意象,天藍底', 'pencil-navy': '鉛筆意象,深藍底', 'alpine': '雪頂山峰,晴空底', 'dusk': '雪頂山峰,黃昏底', 'night': '雪頂山峰,夜色底', 'midnight': '深藍底搭配亮藍標誌,在深色 Dock 上仍保有清楚輪廓', 'carbon': '純黑背景,OLED 螢幕只顯示標誌', 'slate': '冷色石板灰底搭配淺灰標誌', 'obsidian': '紫黑漸層底搭配淡紫標誌', 'neon [...]
+      appIconGroups: {
+        mascot: '擬人', blue: '藍色系', contrast: '黑白', pencil: '鉛筆', mountain: '高山',
+        dark: '深色', neon: '霓虹', muted: '柔和', warm: '暖色', nature: '自然', metal: '金屬', highContrast: '高對比',
+        custom: '自訂',
+      },
+      appIconSplitLabel: '淺色與深色模式使用不同圖示',
+      appIconSplitHelp: '關閉時,兩種外觀會共用同一個圖示。',
+      appIconTargets: { light: '淺色', dark: '深色' },
+      appIconCustom: '匯入的圖示',
+      appIconCustomHelp: '你自己匯入的圖片',
+      appIconImport: '匯入圖示…',
+      appIconImporting: '正在匯入…',
+      appIconImportHelp: '方形 PNG 最好;四周留約 10% 透明邊,Dock 裡才會和其它應用一樣大。',
+      appIconRemove: '刪除',
+      appIconImportError: '匯入圖示失敗',
+      appIconRemoveFailed: '刪除圖示失敗',
+      appIconSelectFailed: '切換圖示失敗',
+      appIconImportFailed: {
+        too_large: '檔案太大,換一張小一點的圖片',
+        too_many_pixels: '圖片尺寸太大,最多 4096×4096',
+        unsupported_format: '只支援 PNG 和 JPEG',
+        unreadable: '這個檔案讀不出影像',
+        too_small: '圖片太小,至少需要 128×128',
+        write_failed: '無法儲存匯入的圖示',
+      },
+      appIconUnavailable: '無法載入應用圖示',
+      fontSize: { uiLabel: 'UI 字型大小', uiHelp: '調整介面使用的基準字型大小', terminalLabel: '終端機字型大小', terminalHelp: '調整終端機命令輸出與程式碼使用的字型大小' },
+    },
+    pets: {
+      import: '匯入 PetPack', importing: '正在匯入…', loading: '正在載入自訂寵物…',
+      status: '桌面寵物', activePet: (name) => `目前使用:${name}`, disabled: '已關閉', disable: '關閉寵物', disabling: '正在關閉…',
+      empty: '還沒有匯入寵物', emptyHelp: '選擇一個包含 pet.json 和精靈圖的本地資料夾。',
+      selected: '正在使用', select: '使用', selecting: '正在切換…', remove: '刪除', removing: '正在刪除…',
+      removeTitle: (name) => `刪除“${name}”?`, removeDescription: '這會刪除 Maka 本地儲存的該寵物包,且無法撤銷。原始資料夾不會受影響。', confirmRemove: '刪除', cancel: '取消',
+      loadFailed: '無法載入自訂寵物', importFailed: '匯入寵物失敗', selectFailed: '切換寵物失敗', removeFailed: '刪除寵物失敗',
+      importErrors: { invalid_directory: '所選資料夾無效。', invalid_manifest: 'pet.json 不符合 maka.pet/v1 格式。', invalid_asset: '精靈圖缺失、無效或超出限制。', already_installed: '已經匯入了相同 ID 的寵物。', read_failed: '無法讀取所選資料夾。' },
+      selectErrors: { invalid_id: '寵物 ID 無效。', not_found: '該寵物已不在本地寵物庫中。', read_failed: '無法讀取寵物庫。', write_failed: '無法儲存寵物選擇。' },
+      removeErrors: { invalid_id: '寵物 ID 無效。', remove_failed: '無法刪除本機寵物包。' },
+    },
+    general: {
+      incognito: '隱身模式', incognitoHelp: '開啟後暫停本地記憶讀寫、聯網搜尋和定時任務觸發。', enableIncognito: '啟用隱身模式', incognitoFailed: '隱身模式切換失敗', notifications: '完成時傳送系統通知', notificationsHelp: '視窗不在前臺時,在回答完成或出錯後傳送桌面通知。', notificationsFailed: '通知設定切換失敗', workspaceInstructions: '遵循專案指令', workspaceInstructionsHelp: '自動讀取每個專案中已有的 AGENTS.md、CLAUDE.md 或 GEMINI.md;檔案仍由各自專案管理。', workspaceInstructionsFailed: '專案指令設定切換失敗', workHub: '啟用 WorkHub', workHubHelp: '在一個入口檢視已有工作,並將新輸入保守地送往普通任務。', workHubFailed: 'WorkHub 設定切換失敗 [...]
+      defaultModel: '預設模型', defaultModelHelp: '新任務預設使用的模型。', notSet: '未設定', saveDefaultModelFailed: '儲存預設模型失敗', defaultPermission: '預設權限模式', defaultPermissionHelp: '新任務預設使用的權限模式;可在任務內隨時切換。', saveDefaultPermissionFailed: '儲存預設權限模式失敗', defaultThinking: '預設思考級別', defaultThinkingHelp: '新任務的思考級別;目前模型不支援所選級別時用模型預設。', followModelDefault: '跟隨模型預設', saveDefaultThinkingFailed: '儲存預設思考級別失敗',
+      shellPreference: 'Bash 工具 shell', shellPreferenceHelp: '自動模式保持 Windows 的 PowerShell 優先規則;Git Bash 是僅對目前 Runtime Host 生效的顯式覆蓋。', shellAuto: '自動(推薦)', shellGitBash: 'Git Bash', shellExecutable: 'Git Bash 執行檔', shellExecutableHelp: '填寫 Runtime Host 所在 Windows 機器上 bash.exe 的絕對路徑。也支援該機器上的舊版 System32 WSL Bash;儲存時會驗證 GNU Bash。', saveShell: '儲存 shell 設定', savingShell: '正在儲存…', shellSaved: '已儲存', saveShellFailed: '儲存 shell 設定失敗', shellExecutableRejected: '目前 Runtime Host 無法把該路徑作為 GNU Bash 執 [...]
+      proxy: '代理伺服器', proxyHelp: '為 AI 模型請求設定網路代理', enableProxy: '啟用代理伺服器', saveNetworkFailed: '儲存網路設定失敗', proxyProtocol: '代理協議', serverAddress: '伺服器地址', port: '埠', proxyAuth: '代理認證', proxyAuthHelp: '需要使用者名稱和密碼時開啟。', enableProxyAuth: '啟用代理認證', username: '使用者名稱', password: '密碼', bypassList: '代理白名單', bypassHelp: '這些域名將繞過代理直連,多個用逗號分隔。', autoBypass: (count) => `已自動新增 ${count} 個域名。代理僅作用於 AI 模型請求。`, testing: '測試中…', testCurrent: '測試目前設定', proxyReachable: '代理可達', proxyTestFailed: '代理測試失敗', prox [...]
+      passwordSavedPlaceholder: '密碼已儲存;輸入新密碼以替換',
+    },
+    about: {
+      loadFailed: '載入關於資訊失敗', loading: '正在載入關於頁', unavailable: '無法載入關於資訊', copied: '已複製診斷資訊', pasteHint: '檢查內容後,可直接貼上到問題報告', copyFailed: '複製失敗', clipboardUnavailable: '剪貼簿不可用或被系統拒絕。', devBuild: '本地開發版', nightlyBuild: 'Nightly', buildLabel: '建構', supportTitle: '支援', copyAction: '複製', reportIssueHelp: '帶上診斷資訊去 GitHub Issues,回覆更快。', reportIssueOpen: '開啟', channelSummaries: { dev: '本地開發建構,不檢查更新。', nightly: '每日建構的預發佈版,自動更新到最新 nightly,會覆蓋正式版安裝。', release: '正式發佈版,自動接收穩定更新。' }, privacyLabel: '隱私 [...]
+
+      checkForUpdates: '檢查更新',
+      checkingForUpdates: '檢查中…',
+
+
+      updateIdle: '尚未檢查更新。',
+      updateNotAvailable: '已是最新版本。',
+      updateAvailable: (version) => `發現新版本 v${version},正在準備下載…`,
+      updateDownloading: (version, percent) => `正在下載 v${version}(${percent}%)…`,
+      updateVerifying: (version) => `正在驗證 v${version} 的發佈來源…`,
+      updateDownloaded: (version) => `v${version} 已下載,可在側欄選擇重啟安裝。`,
+      updateInstalling: (version) => `正在安裝 v${version}…`,
+      updateCheckFailed: '檢查更新失敗',
+      updateCheckFailedDetail: (message) => message,
+    },
+    password: { copyFailed: '複製失敗', clipboardUnavailable: '剪貼簿不可用或被系統拒絕。', copying: '複製中', copied: '已複製', copy: '複製', hide: '隱藏', show: '顯示', value: '憑據值' },
+  },
   en: {
     personalization: {
-      saveFailed: 'Could not save', displayName: 'Display name', displayNameHelp: 'Maka uses this name when addressing you. Leave it blank to use “you”.', displayNamePlaceholder: 'For example: JK', displayNameUnset: 'Not set — Maka will say “you”', displayNameChange: 'Change', displayNameSet: 'Set', interfaceLanguage: 'Interface language', interfaceLanguageHelp: 'Choose the language used by Maka. Changes apply immediately and persist after restart.', localeOptions: [['auto', 'Follow syst [...]
+      saveFailed: 'Could not save', displayName: 'Display name', displayNameHelp: 'Maka uses this name when addressing you. Leave it blank to use “you”.', displayNamePlaceholder: 'For example: JK', displayNameUnset: 'Not set — Maka will say “you”', displayNameChange: 'Change', displayNameSet: 'Set', interfaceLanguage: 'Interface language', interfaceLanguageHelp: 'Choose the language used by Maka. Changes apply immediately and persist after restart.', localeOptions: [['auto', 'Follow syst [...]
     },
     sections: {
       identity: 'Identity', identityHelp: 'How Maka addresses you, plus interface language and response tone.',
diff --git a/apps/desktop/src/renderer/locales/settings-projects-copy.ts b/apps/desktop/src/renderer/locales/settings-projects-copy.ts
index fa2aee3c2c..c393ee1f08 100644
--- a/apps/desktop/src/renderer/locales/settings-projects-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-projects-copy.ts
@@ -311,7 +311,7 @@ export type SettingsProjectsCopy = {
 };
 
 const SETTINGS_PROJECTS_COPY_BY_LOCALE = {
-  zh: {
+  'zh-CN': {
     runtimeHost: {
       title: 'Runtime Host',
       description: 'Local 与其他已启用的 Host 会同时保持连接;任务仍由其所属 Host 处理。',
@@ -628,6 +628,309 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = {
     emptyBody: '添加一个项目目录后,新任务就能默认从它打开,侧边栏也会按项目归类任务。',
     moreActions: (projectName: string) => `更多操作:${projectName}`,
   },
+  'zh-TW': {
+    runtimeHost: {
+      title: 'Runtime Host',
+      description: 'Local 與啟用的遠端 Host 會同時保持連線;任務仍由其所屬 Host 處理。',
+      selected: '預設 Host',
+      selectedHelp: '新任務和未指定 Host 的設定使用預設 Host',
+      remoteTitle: '遠端 Host',
+      remoteDescription: '透過 SSH 自動設定一臺電腦,或手動連線已有 Runtime Host。',
+      addComputer: '新增電腦',
+      useConnectionCode: '使用連線碼', useConnectionCodeDescription: '貼上另一台電腦產生的一次性連線碼', pasteConnectionCode: '貼上', connectionCodeHelpLabel: '如何取得連線碼', connectionCodeHelp: '在目標電腦的 Maka 中開啟「設定 → 工作區 → 遠端存取」,或開啟已透過 SSH 管理的 Host 並選擇「新建連線碼」。也可在目標電腦執行 maka runtime-host access connection-code。連線碼將在 15 分鐘後過期且只能使用一次。', addSshComputer: '透過 SSH 設定', addSshComputerDescription: '在可透過 SSH 登入的電腦上安裝並連線 Host', addWslEnvironment: '新增 WSL 環境', addWslEnvironmentDescription: '在這台 Windows 電腦的 WSL 中安裝並連線 Host', se [...]
+      configureManually: '手動設定',
+      thisComputerRemoteAccess: '遠端存取',
+      thisComputerRemoteAccessHelp: '透過實驗性端對端直接連線存取此 Host;可自動探索公用協調節點以協助穿透 NAT',
+      remoteAccessOn: '已開啟',
+      remoteAccessOff: '未開啟',
+      enableRemoteAccess: '開啟',
+      remoteAccessEnabling: '正在準備並開啟遠端存取;首次可能需要一點時間。',
+      disableRemoteAccess: '關閉連線',
+      disableRemoteAccessConfirm: '關閉遠端連線?',
+      disableRemoteAccessDescription: '這只會停止 Direct peer 連線;已授予的分享存取權仍會保留。',
+      revokeSharedAccess: '撤銷分享存取權',
+      revokeSharedAccessConfirm: '撤銷分享存取權?',
+      revokeSharedAccessDescription: '已連線的 Desktop 將中斷連線,尚未使用的連線碼也會失效。',
+      revokeSharedAccessDone: '已撤銷分享存取權',
+      createConnectionCode: '建立連線碼',
+      connectionCodeTitle: '連線至這台電腦',
+      connectionCodeDescription: '連線碼將於 15 分鐘後過期且只能使用一次。對方將取得 Owner 權限;Direct peer 沒有備援連線。',
+      importConnectionCodeTitle: '使用連線碼',
+      importConnectionCodeDescription: '連線後將取得對方 Host 的 Owner 權限。Direct peer 沒有備援連線。',
+      connectionCode: '連線碼',
+      copyConnectionCode: '複製連線碼',
+      connectionCodeCopied: '已複製連線碼',
+      connectionCodeInvalid: '連線碼格式無效。',
+      connectionCodeUnavailable: '連線碼已過期或已被使用。請在另一台電腦上建立新的連線碼。',
+      connectionCodeHostUnreachable: '無法建立 Direct peer 連線。請確認兩台電腦均在線上,且網路允許 UDP。',
+      connectionCodeHostMismatch: '連線碼指向的 Host 與實際連線的 Host 不符,或版本不相容。',
+      connectionCodeUnknownError: '連線結果不明。請先檢查遠端 Host 清單,再決定是否重試。',
+      connectWithCode: '連線',
+      remoteAccessActiveTasks: '這台電腦仍有執行中的任務',
+      remoteAccessActiveTasksDescription: '開啟遠端存取需要將 Local Host 交由系統服務管理。是否中斷目前任務並繼續?',
+      uninstallActiveTasksDescription: '移除背景服務會停止目前任務。是否中斷這些任務並繼續?',
+      interruptAndEnable: '中斷任務並開啟',
+      interruptAndUninstall: '中斷任務並移除',
+      remoteAccessFailed: '遠端存取操作失敗',
+      setupTitle: '新增遠端電腦',
+
+      setupName: '顯示名稱(可選)',
+
+
+
+      wslDistribution: 'WSL 發行版本',
+      setupSshPort: 'SSH 埠(可選)',
+      setupDirectoryRootsDescription: '留空時使用遠端 Home 資料夾。新增資料夾後,只能瀏覽這些位置以加入專案。',
+      setupConnect: '連線',
+      setupCancel: '取消',
+      setupRetry: '重試',
+      setupDone: '完成',
+      setupChooseProject: '選擇專案',
+      setupComplete: 'Runtime Host 已連線',
+      setupPhase: {
+        preparing_cli: '正在準備本地 CLI…',
+        connecting_ssh: '正在連線 SSH…',
+        connecting_wsl: '正在連線 WSL 環境…',
+        checking_environment: '正在檢查遠端環境…',
+        installing_package: '正在安裝 Maka…',
+        installing_service: '正在啟動 Runtime Host…',
+        pairing_client: '正在配對這臺裝置…',
+        verifying_connection: '正在驗證憑據…',
+        connecting_host: '正在建立安全連線…',
+      },
+      add: '新增遠端 Host',
+      cancel: '取消',
+      name: '顯示名稱',
+      nameHelp: '僅用於在這臺裝置上識別該 Host',
+      transport: '連線方式',
+      transportHelp: '優先使用 TLS;內網中可透過 SSH tunnel 連線僅監聽本機的 Host',
+      tls: 'TLS',
+      ssh: 'SSH tunnel',
+      plaintext: '明文 WebSocket',
+      url: 'WSS 地址',
+      urlHelp: '遠端 Runtime Host 的 wss:// 地址',
+      plaintextUrl: 'WS 地址',
+      plaintextUrlHelp: '遠端 Runtime Host 的 ws:// 地址',
+      sshDestination: 'SSH 目標',
+      sshDestinationHelp: 'OpenSSH 可識別的 user@host 或 SSH config 別名',
+      sshPort: 'SSH 埠',
+      sshPortHelp: '可選;留空使用 OpenSSH 預設值或 SSH config',
+      remotePort: '遠端 Host 埠',
+      remotePortHelp: '遠端 Runtime Host 在 127.0.0.1 上監聽的 WebSocket 埠',
+      websocketPath: 'WebSocket 路徑',
+      websocketPathHelp: '通常為 /runtime-host',
+      plaintextAcknowledgement: '我瞭解明文連線的風險',
+      plaintextAcknowledgementHelp: '存取憑據和資料可能被同一網路中的第三方截獲',
+      plaintextWarning: '僅在可信且隔離的網路中使用;公網連線應使用 TLS 或 SSH tunnel',
+      sshTerminalTitle: '連線遠端 Runtime Host',
+      sshTerminalDescription: '按 OpenSSH 提示確認主機或輸入密碼。已有 SSH key 時通常無需操作。',
+      sshTerminalClosed: 'SSH 連線已結束',
+      sshTerminalClose: '關閉',
+      rootId: 'State Root ID',
+      rootIdHelp: '來自遠端 service 的 ready 輸出,用於確認連線的是預期 Host',
+      credential: '存取憑據',
+      credentialHelp: '在遠端機器使用 desktop-client preset 簽發',
+      saveAndEnable: '儲存並啟用',
+      defaultBadge: '預設',
+      defaultDisableHelp: '先選擇另一個預設 Host,才能停用此 Host',
+      unavailable: '無法連線',
+      manage: '管理',
+      managementTitle: (name: string) => `管理 ${name}`,
+      serviceStatus: '服務狀態',
+      serviceState: {
+        not_installed: '未安裝',
+        stopped: '已停止',
+        starting: '正在啟動',
+        running: '執行中',
+        failed: '啟動失敗',
+      },
+      experimentalBadge: '實驗性',
+      directPeer: 'Direct peer(實驗性)',
+      directPeerDescription: '建立獨立的實驗性 Direct profile。受限 NAT 或遭封鎖的 UDP 可能導致無法連線,且不會自動回退;請保留 SSH profile 以便手動復原。',
+      directPeerState: {
+        unsupported: '需要更新', not_configured: '未設定', disabled: '已停用', enabled: '已啟用', unavailable: '無法使用',
+      },
+      directPeerUnavailable: '無法讀取 Direct peer 狀態',
+      directPeerUpgradeRequired: '請先更新遠端 Runtime Host,再管理 Direct peer。',
+      directPeerClientUnavailable: '目前 Desktop 建置不包含 Direct peer 支援。',
+      directPeerDisableProfileFirst: '請先在 Runtime Host 清單中停用 Direct peer。',
+      directPeerId: 'Peer ID', directPeerRoutes: '可用路徑',
+      directPeerCoordinationRelays: '連線協調節點(可選)',
+      directPeerCoordinationRelaysPlaceholder: '多個位址請以逗號分隔',
+      directPeerAdvancedCoordination: '手動設定協調節點',
+      directPeerAdvancedNatTraversal: 'NAT 穿透(進階)',
+      directPeerStunPolicy: '公網位址探索',
+      directPeerStunPolicyOptions: {
+        default: '公共 STUN(建議)',
+        disabled: '不使用公共 STUN',
+        custom: '自訂 STUN',
+      },
+      directPeerStunUrls: 'STUN 位址',
+      directPeerStunDefaultHelp:
+        '使用 Cloudflare 公共 STUN 盡力探索公網對映。它不轉送 Maka 流量,但提供者可觀察來源 IP 和請求時間;Maka 不保證其可用性。',
+      directPeerStunDisabledHelp:
+        '僅嘗試本機位址和其他已知直接連線路徑;跨 NAT 的直接連線成功率可能降低。',
+      directPeerStunCustomHelp:
+        '使用逗號分隔的 stun: 位址。STUN 只探索網路位址,不承載 Session 內容。',
+      directPeerAutomaticRelayDiscovery: '自動探索協調節點',
+      directPeerAutomaticRelayDiscoveryHelp:
+        '協調節點使用 Circuit Relay v2 通訊協定,只協助建立端對端直接連線,不承載應用程式流量。Maka 會透過公用 IPFS 網路盡力探索可用節點;手動設定的節點優先。',
+      directPeerEnable: '啟用並新增', directPeerDisable: '停用', directPeerAddProfile: '新增至 Desktop',
+      directPeerActionFailed: 'Direct peer 操作失敗',
+      peerMesh: 'Peer Mesh',
+      peerMeshHelp: '管理此 Desktop peer 的私人 Mesh 成員資格和邀請',
+      managePeerMesh: '管理 Peer Mesh',
+      installedVersion: '版本',
+      operatingSystem: '系統',
+      processId: '程序 ID',
+      lastExitCode: '上次退出碼',
+      stateRoot: 'State Root',
+      directoryRoots: '可用目錄',
+      directoryRootsDescription: '遠端用戶端只能從這些資料夾瀏覽並新增專案。移除資料夾不會刪除已新增的專案。',
+      directoryRootsUnavailable: '更新或修復此 Host 後,即可在 Desktop 管理這些資料夾。',
+      directoryRootsChanged: '這些資料夾已在其他位置變更',
+      directoryRootsChangedDescription: '你的編輯內容仍會保留。請先載入目前設定,再繼續編輯。',
+      reloadDirectoryRoots: '載入目前設定',
+      noDirectoryRoots: '未設定額外目錄',
+      directoryRootLabel: '標籤',
+      directoryRootPath: '遠端絕對路徑',
+      addDirectoryRoot: '新增資料夾',
+      removeDirectoryRoot: '移除',
+      saveDirectoryRoots: '套用資料夾',
+      directoryRootsActiveTasks: '此 Host 仍有執行中的任務',
+      directoryRootsActiveTasksDescription: '套用資料夾需要安全地重新啟動遠端服務。只有在明確確認後,才會中斷這些任務。',
+      configureDirectoriesInterrupt: '中斷任務並套用',
+      refresh: '重新整理',
+      startService: '啟動',
+      restartService: '重啟',
+      restartActiveTasksDescription: '重新啟動會停止目前任務。是否中斷這些任務並繼續?',
+      restartInterrupt: '中斷任務並重啟',
+      repairService: '修復',
+      updateService: '安裝配套版本',
+      updatePolicy: '更新策略',
+      updatePolicyDescription: '選擇這個 Host 跟隨的 Maka 版本',
+      updatePolicyManual: '手動',
+      updatePolicyAutomatic: '自動',
+      updatePolicyOptions: {
+        manual: '手動更新',
+        fixed: '固定版本',
+        latest: 'Latest 穩定頻道',
+        next: 'Next 預覽頻道',
+      },
+      updatePolicyFixedVersion: '版本',
+      updatePolicySave: '儲存策略',
+      updatePolicyCheckNow: '立即檢查',
+      updatePolicyUnavailable: '無法讀取自動更新策略',
+      updateSchedulerUnavailable: '此 Runtime Host 尚不支援自動更新',
+      updateSchedulerUnavailableBody: '請先更新或修復服務,再啟用固定版本或釋出頻道',
+      updateSchedulerUnsupported: '不支援',
+      updateSchedulerInactive: '未執行',
+      updateSchedulerInactiveBody: '更新排程器未在執行,請啟動或修復服務後再啟用自動更新',
+      updateSchedulerNeedsRepair: '需要修復',
+      updateSchedulerNeedsRepairBody: '更新排程器未在執行,請修復服務後再啟用自動更新',
+      updatePolicyDisabled: '自動更新已關閉',
+      updatePolicyActiveTasks: 'Runtime Host 正在執行任務,本次更新已推遲',
+      updatePolicyNotNewer: (version: string) => `Maka ${version} 不高於目前版本`,
+      updatePolicyManualAction: (version: string) => `Maka ${version} 需要手動更新`,
+      updatePolicyManualReason: {
+        current_compatibility_unknown: '無法確認目前版本的儲存相容性',
+        target_compatibility_unknown: '無法確認目標版本的儲存相容性',
+        compatibility_mismatch: '目標版本需要手動處理儲存相容性',
+      },
+      updatePhase: {
+        preparing_cli: '正在準備本地 CLI…',
+        checking: '正在檢查版本…',
+        staging: '正在準備新版本…',
+        retiring: '正在安全停止目前 Runtime Host…',
+        replacing: '正在啟動並驗證新版本…',
+      },
+      updateBlockedTitle: 'Runtime Host 可能仍在執行任務',
+      updateBlockedBody: '無法確認目前 Host 可以安全停止。繼續更新會中斷目前執行,但會保留可恢復的任務狀態和無法確認的外部效果。',
+      updateInterrupt: '中斷任務並更新',
+      updateComplete: (from: string, to: string) => `Runtime Host 已從 ${from} 更新到 ${to}`,
+      updateRepaired: (version: string) => `Runtime Host ${version} 已恢復執行`,
+      updateAlreadyCurrent: (version: string) => `Runtime Host 已是 ${version}`,
+      showLogs: '檢視記錄',
+      noLogs: '沒有服務記錄',
+      uninstallService: '解除安裝服務',
+      uninstallConfirmTitle: '解除安裝遠端 Runtime Host?',
+      uninstallConfirmBody: '這會停止並移除 Maka 管理的服務與程式,但保留 State Root、專案和任務資料。目前 Desktop Profile 不會被刪除。',
+      uninstallConfirm: '解除安裝服務',
+      uninstallRetained: (path: string) => `服務已解除安裝,資料保留在 ${path}`,
+      managementActionFailed: '無法管理 Runtime Host 服務',
+      managementReconnectFailed: '變更已套用,但 Desktop 無法重新連線',
+      manageAccess: '管理存取權限',
+      accessTitle: '存取權限',
+      noAccessCredentials: '沒有存取憑據',
+      currentDesktop: '目前 Desktop',
+      accessKind: {
+        owner: '客戶端存取',
+        capabilityProvider: 'Capability Provider',
+      },
+      accessPending: '等待確認',
+      accessCreated: (date: string) => `建立於 ${date}`,
+      rotateCredential: '輪換憑據',
+      rotateCredentialConfirmTitle: '輪換目前 Desktop 的憑據?',
+      rotateCredentialConfirmBody: '輪換會重新連線這個 Runtime Host,並可能中斷正在進行的工作。請先完成或暫停活躍任務。',
+      rotateCredentialConfirm: '繼續輪換',
+      enableBeforeRotate: '請先啟用這個 Runtime Host,再輪換目前 Desktop 的憑據。',
+      startBeforeChangingAccess: '請先啟動 Runtime Host 服務,再修改存取權限。',
+      revokeCredential: '撤銷',
+      revokeCredentialConfirm: (name: string) => `撤銷 ${name} 的存取權限?`,
+      revokeCredentialConfirmBody: '使用此憑據的客戶端會立即斷開連線,並可能中斷正在進行的工作。',
+      accessActionFailed: '無法管理存取權限',
+      back: '返回',
+      remove: '移除',
+      empty: '還沒有遠端 Host',
+      loadFailed: '無法讀取 Runtime Host profiles',
+      selectFailed: '無法更新 Runtime Host',
+      saveFailed: '無法儲存 Runtime Host profile',
+      removeFailed: '無法移除 Runtime Host profile',
+      pairingRecoveryTitle: '有未完成的配對',
+      pairingRecoveryDescription: '可在對應 Host 的選單中重試;如果不再需要,也可以放棄配對並清理未完成的連線。',
+      resolvePairingRecovery: '重試配對',
+      resolvePairingRecoveryFailed: '無法處理配對恢復',
+      pairingPendingBadge: '配對未完成',
+      discardPairing: '放棄配對',
+      discardPairingConfirmTitle: '放棄這次配對?',
+      discardPairingConfirmBody: '這會刪除未完成的連線,並清理儲存在本機的暫時憑證。之後仍可使用新的邀請碼重新加入。',
+      discardPairingFailed: '無法放棄配對',
+      moreActions: (name: string) => `更多操作:${name}`,
+    },
+    section: '工作區',
+    // Says all three layers of the rule in one sentence, because a help line
+    // that only mentions the default would leave the user guessing what
+    // happens before they set one.
+    sectionHelp: '新任務預設開啟此專案;未設定時沿用上次使用的專案。任何任務都能在輸入框旁臨時切換。',
+    addProject: '新增專案',
+    defaultBadge: '預設',
+    setDefault: '設為預設',
+    setDefaultTitle: '新任務預設開啟這個專案',
+    setDefaultDisabledTitle: '目錄不可用,無法設為預設',
+    setDefaultFailed: '設定預設專案失敗',
+    rename: '重新命名',
+    renameLabel: '專案名稱',
+    renameFailed: '重新命名失敗',
+    openFolder: '開啟專案資料夾',
+    // Says which of the two things went wrong, because the fix differs: a
+    // missing folder is the user's to restore, a refusal to open is not.
+    openFolderFailed: '打不開這個目錄,它可能已被移動或刪除',
+    save: '儲存',
+    cancel: '取消',
+    clearDefault: '取消預設',
+    remove: '從 Maka 移除',
+    removeConfirmTitle: '從 Maka 移除這個專案?',
+    // The one thing a user actually fears here, stated first and plainly.
+    removeConfirmBody: '僅從 Maka 的專案列表移除,磁碟上的檔案不受影響。該專案下已有的任務會移到"未歸屬"分組,不會被刪除。',
+    removeConfirm: '移除',
+    removeCancel: '取消',
+    actionFailed: '操作失敗',
+    unavailable: '目錄不可用',
+    defaultUnavailable: '原來的預設專案已不可用,新任務暫時沿用上次使用的專案。',
+    emptyTitle: '還沒有專案',
+    emptyBody: '新增一個專案目錄後,新任務就能預設從它開啟,側邊欄也會按專案歸類任務。',
+    moreActions: (projectName: string) => `更多操作:${projectName}`,
+  },
   en: {
     runtimeHost: {
       title: 'Runtime Host',
diff --git a/apps/desktop/src/renderer/locales/settings-shared-copy.ts b/apps/desktop/src/renderer/locales/settings-shared-copy.ts
index f414806ed0..0b087c10e9 100644
--- a/apps/desktop/src/renderer/locales/settings-shared-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-shared-copy.ts
@@ -68,7 +68,7 @@ export type SettingsSharedCopy = {
 };
 
 const SETTINGS_SHARED_COPY_BY_LOCALE = {
-  zh: {
+  'zh-CN': {
     modalLabel: '设置',
     contentLabel: '设置内容',
     sidebarLabel: '设置侧栏',
@@ -108,6 +108,46 @@ const SETTINGS_SHARED_COPY_BY_LOCALE = {
       reviewScheduleHelp: '每日回顾的生成时间与使用的模型。',
     },
   },
+  'zh-TW': {
+    modalLabel: '設定',
+    contentLabel: '設定內容',
+    sidebarLabel: '設定側欄',
+    navigationLabel: '設定分組',
+    backToApp: '返回應用',
+    close: '關閉',
+    loading: '正在載入設定',
+    retry: '重試',
+    save: '儲存',
+    cancel: '取消',
+    copy: '複製',
+    copied: '已複製',
+    failed: '失敗',
+    settingsLoadFailed: '載入設定失敗',
+    usageLoadFailed: '載入使用統計失敗',
+    runtimeHost: 'Runtime Host',
+    runtimeHostUnavailable: '這個 Runtime Host 目前不可用。請選擇其他 Host,或在“專案”中重試連線。',
+    unknownError: '出現錯誤,請稍後重試。',
+    unavailablePage: '該設定頁已納入 Maka 設定樹,會隨對應 runtime 能力一起工作。',
+    showDetails: '展開詳情',
+    hideDetails: '收起詳情',
+    ready: '就緒',
+    groups: {
+      memorySources: '記憶',
+      memorySourcesHelp: 'Maka 會在任務中記住你確認過的資訊,用於之後的回答。',
+      memoryDocument: '記憶檔案與備份',
+      memoryDocumentHelp: '記憶儲存在本機 MEMORY.md 裡;這裡可以直接編輯原文或恢復備份。',
+      memoryEntries: '已記住的內容',
+      memoryEntriesHelp: '可以篩選、手動新增,或把不再需要的條目歸檔。',
+      searchProvider: '搜尋服務商',
+      searchProviderHelp: '聯網搜尋使用的服務商與憑據。',
+      searchBehavior: '搜尋行為',
+      searchBehaviorHelp: '什麼時候發起搜尋,以及每次取回多少結果。',
+      dataLocation: '資料位置',
+      dataLocationHelp: '任務、設定、使用統計與憑據都以檔案形式存放在本機的這個位置。',
+      reviewSchedule: '回顧計劃',
+      reviewScheduleHelp: '每日回顧的生成時間與使用的模型。',
+    },
+  },
   en: {
     modalLabel: 'Settings',
     contentLabel: 'Settings content',
diff --git a/apps/desktop/src/renderer/locales/settings-subagents-copy.ts b/apps/desktop/src/renderer/locales/settings-subagents-copy.ts
index 6241da520e..a77ccfae23 100644
--- a/apps/desktop/src/renderer/locales/settings-subagents-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-subagents-copy.ts
@@ -100,7 +100,7 @@ export type SubagentSettingsCopy = {
 };
 
 const SETTINGS_SUBAGENTS_COPY_BY_LOCALE = {
-  zh: {
+  'zh-CN': {
     section: {
       title: '已批准的子 Agent',
       count: (total) => `共 ${total} 个配置`,
@@ -182,6 +182,88 @@ const SETTINGS_SUBAGENTS_COPY_BY_LOCALE = {
       max: '最大',
     },
   },
+  'zh-TW': {
+    section: {
+      title: '已批准的子 Agent',
+      count: (total) => `共 ${total} 個設定`,
+      add: '新增子 Agent',
+      emptyTitle: '還沒有子 Agent 設定',
+      emptyDescription: '新增一個設定後,主 Agent 就能把合適的任務交給獨立模型處理。',
+    },
+    row: {
+      enabled: '啟用',
+      configure: (name) => `設定“${name}”`,
+      fallbackDescription: '尚未填寫適用場景',
+    },
+    status: {
+      missingConnection: '連線不存在',
+      providerRetired: '登入方式已移除 · 請改用其他連線',
+      connectionDisabled: '連線已停用',
+      modelDisabled: '模型未啟用',
+    },
+    editor: {
+      backToList: '返回子 Agent 列表',
+      createSubtitle: '建立一個可由主 Agent 自動選擇的模型設定。',
+      editSubtitle: '修改適用場景、能力邊界和模型路由。',
+      groupPurpose: '用途',
+      groupPurposeHelp: '主 Agent 主要根據這裡的名稱和適用場景挑選設定。',
+      groupRoute: '能力與模型',
+      groupRouteHelp: '固定這個子 Agent 能做什麼,以及它執行在哪個模型上。',
+      dangerZone: '刪除子 Agent',
+      dangerZoneHelp: '此操作不可撤銷。',
+      delete: '刪除',
+      enabled: '啟用',
+      enabledDescription: '關閉後設定仍會保留,但主 Agent 暫時不會選擇它。',
+      name: '顯示名稱',
+      namePlaceholder: '快速程式碼閱讀',
+      id: 'subagent_id',
+      idDescription: '建立後保持不變,主 Agent 和歷史任務會用它識別此設定。',
+      idPlaceholder: 'fast-reader',
+      description: '適用場景',
+      descriptionPlaceholder: '適合快速、低成本地閱讀大型倉庫',
+      profile: '能力 Profile',
+      connection: '模型連線',
+      model: '模型',
+      thinking: '思考級別',
+      defaultThinking: '跟隨模型預設',
+      implementationWarning: '實現程式碼 Profile 可以寫檔案和執行命令,並會在隔離 worktree 中執行。',
+      noConnection: '請先在“模型”頁啟用一個模型連線。',
+      noModel: '所選連線沒有已啟用的模型。',
+      requiredName: '請輸入顯示名稱。',
+      invalidId: (max) => `只能使用字母、數字、點、下劃線、冒號和連字元,最多 ${max} 個字元。`,
+      duplicateId: '這個 subagent_id 已經存在。',
+      invalidConnection: '請選擇一個已啟用的模型連線。',
+      invalidModel: '請選擇一個已啟用的模型。',
+      cancel: '取消',
+      create: '建立',
+      save: '儲存',
+      saving: '儲存中…',
+    },
+    remove: {
+      title: (name) => `刪除“${name}”?`,
+      description: '主 Agent 將不再看到這個設定。已建立的子任務不會被刪除。',
+      confirm: '刪除',
+      cancel: '取消',
+    },
+    toast: {
+      saveFailed: '儲存子 Agent 設定失敗',
+      rejected: '設定沒有被儲存。請確認名稱長度和設定數量都在上限之內。',
+    },
+    profiles: {
+      local_read: { label: '程式碼閱讀', description: '只讀存取目前工作區,適合搜尋、理解和總結程式碼。' },
+      web_research: { label: '網路研究', description: '只使用聯網搜尋,適合查詢外部資料和最新資訊。' },
+      implementation: { label: '實現程式碼', description: '可以讀寫檔案並執行命令,在隔離 worktree 中完成改動。' },
+    },
+    thinking: {
+      off: '關閉',
+      minimal: '最少',
+      low: '低',
+      medium: '中',
+      high: '高',
+      xhigh: '超高',
+      max: '最大',
+    },
+  },
   en: {
     section: {
       title: 'Approved subagents',
diff --git a/apps/desktop/src/renderer/locales/settings-tasks-copy.ts b/apps/desktop/src/renderer/locales/settings-tasks-copy.ts
index 4bd7d41e33..d58505f2e6 100644
--- a/apps/desktop/src/renderer/locales/settings-tasks-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-tasks-copy.ts
@@ -60,7 +60,7 @@ export type SettingsTasksCopy = {
 };
 
 const SETTINGS_TASKS_COPY_BY_LOCALE = {
-  zh: {
+  'zh-CN': {
     listAria: '已归档任务',
     noProject: '无项目',
     deletedParent: '原父任务已删除',
@@ -87,6 +87,33 @@ const SETTINGS_TASKS_COPY_BY_LOCALE = {
     emptyTitle: '没有已归档的任务',
     emptyBody: '在侧栏里归档一个任务后,可以在这里恢复或彻底删除它。',
   },
+  'zh-TW': {
+    listAria: '已歸檔任務',
+    noProject: '無專案',
+    deletedParent: '原父任務已刪除',
+    searchLabel: '搜尋已歸檔任務',
+    purgeAll: '清空全部',
+    purgeMatches: (count: number) => `刪除這 ${count} 條`,
+    purgeAllConfirmTitle: (count: number) => `清空全部 ${count} 條已歸檔任務?`,
+    purgeMatchesConfirmTitle: (count: number) => `刪除搜尋到的 ${count} 條任務?`,
+    purgeConfirmBody: '這些任務及其全部訊息會被永久刪除,無法撤銷。',
+    purgeSubtaskNote: '其中的普通子任務不會被刪除,將保留並移入歸檔。',
+    purgeConfirmAction: '永久刪除',
+    purgedToast: (count: number) => `已刪除 ${count} 條任務`,
+    purgedSubtaskNote: (count: number) => `${count} 個子任務已移入歸檔`,
+    purgeKeptRestored: (count: number) => `另有 ${count} 條在此期間被恢復,已保留。`,
+    purgeFailedTitle: '刪除任務失敗',
+    purgeFailedBody: (count: number) => `${count} 條仍在,請重試。`,
+    purgeUnverified: '任務已刪除,但無法讀取列表確認結果。請重新開啟本頁檢視。',
+    noMatchTitle: '沒有符合的任務',
+    noMatchBody: '換個關鍵詞試試。',
+    moreActions: (name: string) => `「${name}」的更多操作`,
+    restore: '恢復',
+    restoreTask: (name: string) => `恢復「${name}」`,
+    delete: '徹底刪除',
+    emptyTitle: '沒有已歸檔的任務',
+    emptyBody: '在側欄裡歸檔一個任務後,可以在這裡恢復或徹底刪除它。',
+  },
   en: {
     listAria: 'Archived tasks',
     noProject: 'No project',
diff --git a/apps/desktop/src/renderer/locales/settings-test-result-copy.ts b/apps/desktop/src/renderer/locales/settings-test-result-copy.ts
index 48a4097fb9..ee5ce9b90d 100644
--- a/apps/desktop/src/renderer/locales/settings-test-result-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-test-result-copy.ts
@@ -44,7 +44,7 @@ type SettingsTestResultCopy = {
 };
 
 const COPY = {
-  zh: {
+  'zh-CN': {
     proxy: {
       reachable: (endpoint, location) =>
         ["代理配置有效", endpoint, location].filter(Boolean).join(" · "),
@@ -69,6 +69,31 @@ const COPY = {
       connectionFailed: "请检查凭据和网络设置后重试。",
     },
   },
+  'zh-TW': {
+    proxy: {
+      reachable: (endpoint, location) =>
+        ["代理設定有效", endpoint, location].filter(Boolean).join(" · "),
+      disabled: "請先啟用代理伺服器,再進行測試。",
+      configurationMissing: "請填寫代理伺服器地址和埠後再測試。",
+      credentialMissing: "代理認證已開啟,請輸入代理密碼後再測試。",
+      timeout: "代理測試超時,請檢查代理服務是否可達。",
+      httpError: (status) =>
+        status === undefined
+          ? "代理測試回傳了錯誤回應,請檢查代理服務或測試地址。"
+          : `代理測試回傳 HTTP ${status},請檢查代理服務或測試地址。`,
+      unreachable: "代理不可達,請檢查伺服器地址、埠和認證資訊。",
+    },
+    bot: {
+      credentialsValid: (username) =>
+        username
+          ? `憑據檢查已透過 · ${username}。這不代表訊息收發服務已啟動。`
+          : "憑據檢查已透過。這不代表訊息收發服務已啟動。",
+      tokenMissing: "請填寫 Bot Token 後再測試。",
+      tokenInvalid: "Bot Token 無效,請檢查後重試。",
+      appCredentialsMissing: "請填寫 App ID 和 App Secret 後再測試。",
+      connectionFailed: "請檢查憑據和網路設定後重試。",
+    },
+  },
   en: {
     proxy: {
       reachable: (endpoint, location) =>
diff --git a/apps/desktop/src/renderer/locales/settings-usage-copy.ts b/apps/desktop/src/renderer/locales/settings-usage-copy.ts
index f5160b4e2c..75cf3752ad 100644
--- a/apps/desktop/src/renderer/locales/settings-usage-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-usage-copy.ts
@@ -37,7 +37,7 @@ export type UsageSettingsCopy = {
 };
 
 const SETTINGS_USAGE_COPY = {
-  zh: {
+  'zh-CN': {
     saveFailed: '保存使用统计设置失败', toolbarAria: '使用统计范围与刷新', rangeAria: '使用统计时间范围', ranges: ['24h', '7天', '30天', '全部'],
     refreshingAria: '正在刷新使用统计', refreshAria: '刷新使用统计', summaryAria: '使用统计汇总指标', totalRequests: '模型调用', totalCost: '总费用', costHelp: '以模型供应商最终结算为准',
     totalTokens: '总 Token', tokenDetail: (input, output) => `输入 ${input} / 输出 ${output}`, cacheTokens: '缓存 Token',
@@ -59,6 +59,28 @@ const SETTINGS_USAGE_COPY = {
       pricingEmptyBody: '未配置定价覆盖时,费用按内置模型定价表结算;在此可为特定模型登记自定义价格。',
     },
   },
+  'zh-TW': {
+    saveFailed: '儲存使用統計設定失敗', toolbarAria: '使用統計範圍與重新整理', rangeAria: '使用統計時間範圍', ranges: ['24h', '7天', '30天', '全部'],
+    refreshingAria: '正在重新整理使用統計', refreshAria: '重新整理使用統計', summaryAria: '使用統計彙總指標', totalRequests: '總請求', totalCost: '總費用', costHelp: '以模型供應商最終結算為準',
+    totalTokens: '總 Token', tokenDetail: (input, output) => `輸入 ${input} / 輸出 ${output}`, cacheTokens: '快取 Token',
+    cacheDetail: (miss, read, creation) => `新 ${miss} / 命中 ${read} / 建立 ${creation}`, viewAria: '使用統計檢視', tabs: ['請求記錄', '供應商統計', '模型統計', '工具統計', '定價設定'],
+    filtersAria: '請求記錄篩選', filterPlaceholder: '按模型或工具篩選…', filterAria: '按模型或工具篩選請求記錄', statusAria: '請求狀態篩選',
+    statuses: ['全部狀態', '成功', '錯誤', '已中止'], details: '詳情記錄', detailsAria: '顯示使用統計詳情記錄', recordCount: (count) => `共 ${count} 條記錄`, clearFilters: '清除篩選',
+    summaryOnly: '目前僅顯示彙總指標。開啟詳情記錄後,可以檢視逐條模型請求和工具呼叫,按模型、工具或狀態篩選,並用於排查費用與失敗請求。',
+    costUnavailable: '費用未知', incompleteTitle: '統計可能不完整',
+    incompleteBody: '部分記錄可能無法讀取、尚未納入統計或超出顯示上限,實際用量可能高於此處顯示。',
+    showDetails: '顯示明細', filteredEmpty: '沒有符合篩選條件的請求記錄', filteredEmptyHelp: '調整或清除篩選條件後可檢視全部請求記錄。', requestEmpty: '暫無請求記錄',
+    tables: {
+      providersAria: '使用統計供應商統計表', modelsAria: '使用統計模型統計表', toolsAria: '使用統計工具統計表', pricingAria: '使用統計定價設定表', requestsAria: '使用統計請求記錄表',
+      providerHeaders: ['供應商', '請求', 'Token', '費用'], modelHeaders: ['模型', '請求', 'Token', '費用'], toolHeaders: ['工具', '呼叫', '成功', '錯誤', '平均耗時'],
+      pricingHeaders: ['供應商', '模型', '輸入 / 1M', '輸出 / 1M'], requestHeaders: ['時間', '型別', '物件', '任務', 'Token', '費用', '延遲', '狀態'],
+      noPricing: '暫無定價覆蓋設定', modelKind: '模型', toolKind: '工具', unknown: '未知', openSession: (label) => `開啟 ${label}`, untitledSession: '未命名會話', success: '成功', error: '錯誤', aborted: '已中止',
+      providerEmptyTitle: '暫無供應商用量', providerEmptyBody: '完成一次模型請求後,這裡會按供應商聚合請求數、Token 與費用。',
+      modelEmptyTitle: '暫無模型用量', modelEmptyBody: '完成一次模型請求後,這裡會按模型聚合請求數、Token 與費用。',
+      toolEmptyTitle: '暫無工具呼叫', toolEmptyBody: '智慧體呼叫工具後,這裡會按工具聚合呼叫次數、成功、錯誤與平均耗時。',
+      pricingEmptyBody: '未設定定價覆蓋時,費用按內建模型定價表結算;在此可為特定模型登記自訂價格。',
+    },
+  },
   en: {
     saveFailed: 'Failed to save usage settings', toolbarAria: 'Usage range and refresh', rangeAria: 'Usage time range', ranges: ['24h', '7 days', '30 days', 'All'],
     refreshingAria: 'Refreshing usage', refreshAria: 'Refresh usage', summaryAria: 'Usage summary metrics', totalRequests: 'Model calls', totalCost: 'Total cost', costHelp: 'Final billing is determined by the model provider',
diff --git a/apps/desktop/src/renderer/locales/settings-web-search-copy.ts b/apps/desktop/src/renderer/locales/settings-web-search-copy.ts
index 9f42cbfcc7..0cd8730aff 100644
--- a/apps/desktop/src/renderer/locales/settings-web-search-copy.ts
+++ b/apps/desktop/src/renderer/locales/settings-web-search-copy.ts
@@ -38,7 +38,7 @@ export type WebSearchSettingsCopy = {
 };
 
 const SETTINGS_WEB_SEARCH_COPY = {
-  zh: {
+  'zh-CN': {
     saveFailed: '保存联网搜索设置失败', saveStatusFailed: '保存联网搜索状态失败', keySaved: '已保存 Tavily 密钥', keySavedDetail: '可点击「测试」做一次真实请求验证。',
     credentialsCleared: '已清空 Tavily 凭据', credentialsClearedDetail: '联网搜索已自动关闭。', credentialValid: 'Tavily 凭据可用', resultCount: (count) => `返回 ${count} 条结果。`,
     testFailed: '联网搜索测试失败', testError: '联网搜索测试出错', enabled: '启用联网搜索', enabledHelp: '启用后,Maka 可以在需要最新外部信息时调用所选搜索来源。',
@@ -55,6 +55,23 @@ const SETTINGS_WEB_SEARCH_COPY = {
     sources: { model: '来源:当前模型连接', envWithSaved: '来源:环境变量(已保存密钥备用)', env: '来源:环境变量', saved: '来源:本机已保存密钥', none: '来源:未配置' },
     errors: { invalid_query: '请输入有效的搜索内容。', incognito_active: '无痕模式下无法使用联网搜索。', not_configured: '所选搜索来源尚未配置完成。', invalid_credentials: '搜索来源拒绝了当前凭据,请更新后重试。', rate_limited: '搜索请求过于频繁,请稍后重试。', network_error: '网络请求失败,请检查网络后重试。', timeout: '搜索请求超时,请重试。', unsupported_provider: '当前模型不支持服务端搜索,或 Maka 尚未实现它的协议;可改用 Tavily。', experimental_disabled: '联网搜索实验功能当前已关闭。' },
   },
+  'zh-TW': {
+    saveFailed: '儲存聯網搜尋設定失敗', saveStatusFailed: '儲存聯網搜尋狀態失敗', keySaved: '已儲存 Tavily 金鑰', keySavedDetail: '可點選「測試」做一次真實請求驗證。',
+    credentialsCleared: '已清空 Tavily 憑據', credentialsClearedDetail: '聯網搜尋已自動關閉。', credentialValid: 'Tavily 憑據可用', resultCount: (count) => `返回 ${count} 條結果。`,
+    testFailed: '聯網搜尋測試失敗', testError: '聯網搜尋測試出錯', enabled: '啟用聯網搜尋', enabledHelp: '啟用後,Maka 可以在需要最新外部資訊時呼叫所選搜尋來源。',
+    provider: '搜尋來源', providerHelp: '優先複用目前模型的服務端搜尋;不支援時可顯式改用 Tavily。', providerModel: '目前模型', providerTavily: 'Tavily',
+    modelCredential: '主模型原生搜尋', modelCredentialHelp: 'Maka 會在每個任務回合開始時,根據目前連線與精確模型決定是否把原生 web_search 注入同一次模型請求。不儲存第二份搜尋金鑰,也不會從設定頁另發一次模型呼叫。',
+    statusAria: '聯網搜尋憑據狀態', lastTest: '最近測試 ', enabledAria: '啟用聯網搜尋', key: 'Tavily 金鑰',
+    envKeyHelp: '目前使用環境變數 TAVILY_API_KEY / MAKA_TAVILY_API_KEY;如需改用儲存的金鑰,請移除環境變數後重啟。', savedKeyHelp: '金鑰只儲存在本機。申請地址:',
+    envPlaceholder: '由環境變數提供', storedPlaceholder: '已儲存(輸入新金鑰可替換)', keyPlaceholder: 'tvly-xxxxxxxx', keyAria: 'Tavily 金鑰',
+    actions: '憑據操作', actionsHelp: '儲存後可以測試一次真實請求;清空憑據會同步關閉聯網搜尋。', saving: '儲存中…', saveKey: '儲存金鑰', testing: '測試中…', testKey: '測試憑據', clearing: '清空中…', clearKey: '清空金鑰',
+    testSearch: '測試搜尋', testSearchHelp: '發一條真實查詢,確認所選聯網搜尋來源是否設定可用。結果只顯示在這裡,不寫入任務。', queryPlaceholder: '例如:本週 AI 產品釋出動態',
+    searching: '搜尋中…', search: '搜尋', queryFailed: (error) => `查詢失敗:${error}`, noResults: '沒有結果。', resultsAria: '聯網搜尋真實查詢結果',
+    disabledReasons: { noKey: '先設定所選搜尋來源', disabled: '先啟用聯網搜尋', noQuery: '輸入查詢後再搜尋' },
+    statuses: { valid: '已驗證', invalid_credentials: '金鑰無效', rate_limited: '服務限流', timeout: '測試超時', network_error: '網路異常', not_configured: '等待設定', untested: '未測試', validEnabled: '已驗證 · 已啟用', validDisabled: '已驗證 · 未啟用', unknownEnabled: '未測試 · 已啟用', modelEnabled: '已啟用 · 按任務模型判定', modelDisabled: '目前模型來源 · 未啟用' },
+    sources: { model: '來源:目前模型連線', envWithSaved: '來源:環境變數(已儲存金鑰備用)', env: '來源:環境變數', saved: '來源:本機已儲存金鑰', none: '來源:未設定' },
+    errors: { invalid_query: '請輸入有效的搜尋內容。', incognito_active: '無痕模式下無法使用聯網搜尋。', not_configured: '所選搜尋來源尚未設定完成。', invalid_credentials: '搜尋來源拒絕了目前憑據,請更新後重試。', rate_limited: '搜尋請求過於頻繁,請稍後重試。', network_error: '網路請求失敗,請檢查網路後重試。', timeout: '搜尋請求超時,請重試。', unsupported_provider: '目前模型不支援服務端搜尋,或 Maka 尚未實現它的協議;可改用 Tavily。', experimental_disabled: '聯網搜尋實驗功能目前已關閉。' },
+  },
   en: {
     saveFailed: 'Failed to save web search settings', saveStatusFailed: 'Failed to save web search status', keySaved: 'Tavily key saved', keySavedDetail: 'Select Test credentials to verify it with a real request.',
     credentialsCleared: 'Tavily credentials cleared', credentialsClearedDetail: 'Web search was disabled automatically.', credentialValid: 'Tavily credentials work', resultCount: (count) => `Returned ${count} ${count === 1 ? 'result' : 'results'}.`,
diff --git a/apps/desktop/src/renderer/locales/shell-copy.ts b/apps/desktop/src/renderer/locales/shell-copy.ts
index ed545587d2..3c6c50b745 100644
--- a/apps/desktop/src/renderer/locales/shell-copy.ts
+++ b/apps/desktop/src/renderer/locales/shell-copy.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { generalizedErrorMessage, generalizedErrorMessageChinese } from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
 
 import { type UiCatalog, type UiLocale } from '@maka/core/ui-locale';
 
@@ -758,7 +758,7 @@ const EN_SETTINGS_SECTIONS: Record<SettingsSection, string> = {
 };
 
 const SHELL_COPY_BY_LOCALE = {
-  zh: {
+  'zh-CN': {
     navigation: { settings: '设置' },
     actions: { retry: '重试' },
     paths: {
@@ -1260,6 +1260,508 @@ const SHELL_COPY_BY_LOCALE = {
       resizeWorkbar: '调整任务工作栏宽度',
     },
   },
+  'zh-TW': {
+    navigation: { settings: '設定' },
+    actions: { retry: '重試' },
+    paths: {
+      workspace: '工作區資料夾',
+      project: '專案目錄',
+      skills: 'Skills 資料夾',
+    },
+    errors: {
+      messageRead: '任務內容暫時無法讀取,請稍後重試。',
+      messageRefresh: '任務內容暫時無法重新整理,請稍後重試。',
+      openPath: (path: string) => `無法開啟${path},請稍後重試。`,
+      workspaceUnavailableTitle: '工作目錄不可用',
+      workspaceUnavailableDescription: '工作目錄不存在或無法存取。請選擇有效目錄建立新任務。',
+    },
+    chatActions: {
+      newConversation: '建立任務',
+      sendFailedTitle: '傳送失敗',
+      sendFailedFallback: '訊息暫時無法傳送,請稍後重試。',
+      skillInvocationBlockedTitle: 'Skill 呼叫失敗,訊息未傳送',
+      skillInvocationBlockedDescription: (items) => `${items.join('、')}。請調整選擇後重試。`,
+      skillInvocationFailedTitle: '部分 Skill 未能呼叫',
+      skillInvocationFailedDescription: (items) => `${items.join('、')}。其餘 Skill 已正常呼叫。`,
+      skillInvocationFailureReason: {
+        invalid_name: '名稱無效',
+        not_found: '未找到',
+        disabled: '已停用',
+        host_incompatible: '目前環境缺少依賴',
+        resolution_failed: '解析失敗',
+        too_many_requests: 'Skill 呼叫請求超過 50 個上限',
+      },
+      responseFailedTitle: '響應失敗',
+      responseFailedFallback: '任務操作失敗,請稍後重試。',
+      refreshFailedTitle: '重新整理任務失敗',
+      sessionStartFailedTitle: '開始任務失敗',
+      sessionStartFailedFallback: '任務暫時無法開始,請稍後重試。',
+    },
+    projectActions: {
+      currentProject: '目前專案',
+      readPathFailedTitle: '讀取專案路徑失敗',
+      readPathFailedFallback: '專案路徑暫時無法讀取,請稍後重試。',
+      selectDirectoryFailedTitle: '選擇工作目錄失敗',
+      selectedPathUnreadable: '所選路徑不存在或不可讀。',
+      directorySwitchedTitle: '已切換工作目錄',
+      projectUpdateFailedTitle: '專案操作失敗',
+      projectUpdateFailedFallback: '暫時無法更新專案,請稍後重試。',
+      catalogUnavailable: 'Runtime Host 暫時不可用',
+      retryCatalog: '重試載入',
+      remoteDirectoryTitle: (host: string) => `在 ${host} 上新增專案`,
+      remoteDirectoryBreadcrumbs: '目前資料夾',
+      remoteDirectoryHome: '主目錄',
+      remoteDirectoryEmpty: '此資料夾中沒有子資料夾',
+      remoteDirectorySelect: '新增此資料夾',
+      remoteDirectoryCancel: '取消',
+      remoteDirectoryRetry: '重試',
+      remoteDirectoryLoading: '正在讀取資料夾…',
+      remoteDirectoryShowHidden: '顯示隱藏目錄',
+      remoteDirectoryHideHidden: '不顯示隱藏目錄',
+      runtimeHostReadiness: {
+        connecting: '連線中',
+        reconnecting: '正在重連',
+        unavailable: '不可用',
+      },
+      openFailedTitle: (path: string) => `無法開啟${path}`,
+      openPathLabels: {
+        workspace: '工作區目錄',
+        skills: 'Skills 目錄',
+        memory: '記憶目錄',
+        project: '專案目錄',
+      },
+      openPathFailures: {
+        'unknown-key': '未知的工作區目錄。',
+        'not-allowed': '路徑不在允許開啟的工作區範圍內。',
+        missing: '目錄不存在。',
+        'not-a-directory': '目標不是目錄。',
+        'open-failed': '系統沒有開啟該目錄。',
+        unknown: '無法開啟目錄。',
+      },
+    },
+    commandActions: {
+      connectionVerified: (name: string) => `連線已驗證 · ${name}`,
+      connectionLatency: (latency: number | string, model?: string) =>
+        `延遲 ${latency} ms${model ? ` · ${model}` : ''}`,
+      connectionTestFailed: (name: string) => `連線測試失敗 · ${name}`,
+      testErrorTitle: '測試出錯',
+      connectionUnavailable: '連線測試暫時不可用,請稍後重試。',
+      connectionFailures: {
+        rateLimit: '目前帳號或模型服務觸發速率限制,請稍後重試。',
+        timeout: '請求超時,請檢查網路或代理後重試。',
+        auth: '鑑權失敗,請檢查模型金鑰、訂閱帳號登入或憑據設定後重試。',
+        network: '網路錯誤,請檢查網路或代理後重試。',
+        provider: '模型服務返回錯誤,請稍後重試。',
+        unknown: '連線測試失敗,請稍後重試。',
+      },
+      setDefaultSuccess: (name: string) => `已設為預設 · ${name}`,
+      setDefaultFailedTitle: '切換預設失敗',
+      setDefaultFallback: '預設模型暫時無法切換,請稍後重試。',
+      newConversation: '建立任務',
+      conversationCopiedTitle: '已複製任務為 Markdown',
+      lineCount: (lines: number) => `${lines} 行 · 可貼上到 Notion / Obsidian / GitHub`,
+      copyFailedTitle: '複製失敗',
+      clipboardUnavailable: '剪貼簿不可用',
+      conversationSavedTitle: '已儲存目前任務',
+      saveSummary: (lines: number, fileName: string) => `${lines} 行 · 儲存為 ${fileName}`,
+      saveFailedTitle: '儲存失敗',
+      invalidExport: '匯出內容無效',
+      writeFailed: '無法寫入選擇的位置',
+      exportFallback: '匯出目前任務失敗,請稍後重試。',
+      memoryOpenFailedTitle: '無法開啟 MEMORY.md',
+      openFailedTitle: '開啟失敗',
+      memoryOpenFallback: '無法開啟 MEMORY.md,請稍後重試。',
+      today: '今天',
+      reviewCopiedTitle: '已複製今日回顧為 Markdown',
+      reviewSummary: (sessions: number, requests: number) => `${sessions} 個任務 · ${requests} 個請求`,
+      reviewCopyFallback: '今日回顧暫時不可用,或剪貼簿被系統拒絕。',
+      reviewPastedTitle: '已追加今日回顧到輸入框',
+      reviewCopied: (label: string) => `已複製${label}回顧`,
+      reviewPasted: (label: string) => `已追加${label}回顧到輸入框`,
+      reviewSaved: (label: string) => `已儲存${label}回顧`,
+      reviewSaveFallback: '儲存每日回顧失敗,請稍後重試。',
+      pasteFailedTitle: '貼上失敗',
+      reviewUnavailable: '今日回顧暫時不可用,請稍後重試。',
+      diagnosticsCopiedTitle: '已複製診斷資訊',
+      diagnosticsCopiedDescription: '檢查內容後,可直接貼上到問題報告',
+      clipboardDenied: '剪貼簿不可用或被系統拒絕',
+      networkPassedTitle: '網路代理測試透過',
+      networkFailedTitle: '網路代理測試失敗',
+      genericTestFailedTitle: '測試失敗',
+      networkTestFallback: '網路代理測試暫時不可用,請稍後重試。',
+    },
+    sessionRowActions: {
+      actionFallback: '任務操作失敗,請稍後重試。',
+      flagFailedTitle: '標記任務失敗',
+      unflagFailedTitle: '取消標記失敗',
+      archiveFailedTitle: '歸檔任務失敗',
+      unarchiveFailedTitle: '恢復任務失敗',
+      renameFailedTitle: '重新命名任務失敗',
+      deleteFailedTitle: '刪除任務失敗',
+      currentConversation: '目前任務',
+      deleteTitle: (name: string) => `刪除 "${name}"`,
+      deleteDescription: '任務和全部訊息會從磁碟上永久移除。該操作不可撤銷。',
+      deleteLabel: '刪除',
+      cancelLabel: '取消',
+      deletedTitle: (name: string) => `已刪除 ${name}`,
+      deleteRestoredTitle: (name: string) => `${name} 已被恢復,未刪除`,
+      deleteSubtaskNote: () => '其普通子任務不會被刪除,將保留並移入歸檔。',
+      deleteSubtaskNoteUncertain: () => '其普通子任務(如有)不會被刪除,將保留並移入歸檔。',
+      deletedSubtaskNote: (count: number) => `${count} 個子任務已移入歸檔`,
+      bulkArchiveDescription: '歸檔後可在「設定 › 活動 › 已歸檔任務」中找回。',
+      bulkArchivedTitle: (count: number) => `已歸檔 ${count} 個任務`,
+      bulkArchiveFailedTitle: '部分任務無法歸檔',
+      bulkFailedBody: (count: number) => `還有 ${count} 個未處理成功。`,
+    },
+    skillActions: {
+      refreshSkillsFailedTitle: '重新整理技能失敗',
+      refreshSkillsFallback: '重新整理技能失敗,請稍後重試。',
+      refreshSourcesFailedTitle: '重新整理來源庫失敗',
+      refreshSourcesFallback: '重新整理來源庫失敗,請稍後重試。',
+      refreshBundledFailedTitle: '重新整理內建技能失敗',
+      refreshBundledFallback: '重新整理內建技能失敗,請稍後重試。',
+      installBundledFailedTitle: '無法安裝內建 Skill',
+      installBundledFallback: '無法安裝內建 Skill,請稍後重試。',
+      installedBundledTitle: '已安裝內建 Skill',
+      installedDescription: (id: string) => `${id}/SKILL.md 已放到目前工作區。`,
+      importSourceFailedTitle: '無法匯入 Skill 來源',
+      importSourceFallback: '無法匯入 Skill 來源,請稍後重試。',
+      importedSourceTitle: '已匯入 Skill 來源',
+      installFailedTitle: '無法安裝 Skill',
+      installFallback: '無法安裝 Skill,請稍後重試。',
+      installedTitle: '已安裝 Skill',
+      previewFailedTitle: '無法預覽 Skill 更新',
+      previewFallback: '無法預覽 Skill 更新,請稍後重試。',
+      updateFailedTitle: '無法更新 Skill',
+      updateFallback: '無法更新 Skill,請稍後重試。',
+      updatedTitle: '已更新 Skill',
+      forceUpdatedTitle: '已覆蓋更新 Skill',
+      updatedDescription: (id: string) => `${id}/SKILL.md 已更新到來源庫版本。`,
+      toggleFailedTitle: '無法切換 Skill',
+      toggleFallback: '無法切換 Skill,請稍後重試。',
+      enabledTitle: '已啟用 Skill',
+      disabledTitle: '已停用 Skill',
+      pinnedTitle: '已固定到技能上下文',
+      unpinnedTitle: '已取消固定',
+      runtimeDescription: (name: string) => `${name} 已更新目前專案的執行狀態。`,
+      deleteFailedTitle: '無法刪除 Skill',
+      deleteFallback: '無法刪除 Skill,請稍後重試。',
+      deletedTitle: '已刪除 Skill',
+      deletedDescription: (id: string) => `${id} 已移除。`,
+      openFailedTitle: '無法開啟 Skill',
+      openFallback: '無法開啟 Skill,請稍後重試。',
+      openFailures: {
+        invalid_id: 'Skill 名稱不在允許範圍內。',
+        missing: '沒有找到對應的 SKILL.md。',
+        blocked_path: 'Skill 路徑不在工作區 skills 目錄內,已阻止開啟。',
+        not_file: '目標不是一個可開啟的 SKILL.md 檔案。',
+        not_directory: '目標不是一個可開啟的目錄。',
+        open_failed: '系統開啟檔案失敗。',
+      },
+      sourceFailures: {
+        invalid_skill: '請選擇有效的 SKILL.md 檔案。',
+        already_exists: '來源庫裡已經有同名 Skill。',
+        blocked_path: '該檔案路徑不允許匯入。',
+        write_failed: '寫入來源庫失敗,請檢查檔案權限。',
+        cancelled: '已取消。',
+      },
+      installFailures: {
+        not_found: '沒有找到這個 Skill 來源。',
+        already_exists: '目前工作區已經有同名 Skill。',
+        blocked_path: '目標路徑不允許寫入。',
+        write_failed: '寫入工作區失敗,請檢查檔案權限。',
+      },
+      updateFailures: {
+        not_managed: '這個 Skill 不是受管理來源。',
+        source_missing: '來源庫中找不到對應來源。',
+        local_modified: '工作區副本已經被修改。請開啟本地檔案和來原始檔手動比較後再更新。',
+        metadata_error: 'Skill 後設資料異常,不能安全更新。',
+        blocked_path: '目標路徑不允許寫入。',
+        write_failed: '寫入工作區失敗,請檢查檔案權限。',
+      },
+      previewFailures: {
+        not_managed: '這個 Skill 不是受管理來源。',
+        source_missing: '來源庫中找不到對應來源。',
+        metadata_error: 'Skill 後設資料異常,不能安全預覽。',
+        blocked_path: '目標路徑不允許讀取。',
+        read_failed: '讀取 Skill 內容失敗,請檢查檔案權限。',
+      },
+      deleteFailures: {
+        not_found: '目前工作區找不到這個 Skill。',
+        blocked_path: 'Skill 路徑不允許刪除。',
+        blocked_scope: '專案內的 Skill 由倉庫管理,請直接在專案裡刪除。',
+        delete_failed: '刪除 Skill 失敗,請檢查檔案權限。',
+      },
+      runtimeFailures: {
+        not_found: '目前工作區找不到這個 Skill。',
+        blocked_path: 'Skill 狀態路徑不允許寫入。',
+        state_error: '目前工作區的 Skill 狀態檔案異常,需要先修復。',
+        write_failed: '寫入目前專案的 Skill 狀態失敗,請檢查檔案權限。',
+      },
+    },
+    sessionSettingsActions: {
+      bypassConfirmTitle: '切換到完全權限?',
+      bypassConfirmDescription:
+        '本地工具將直接讀寫你的檔案並存取網路,不經 Maka 的保護層。僅用於你完全信任、或已在外部隔離環境中執行的任務。',
+      bypassConfirmLabel: '開啟完全權限',
+      bypassCancelLabel: '保持自動',
+      permissionFailedTitle: '切換權限模式失敗',
+      permissionFallback: '權限模式暫時無法切換,請稍後重試。',
+      modelFailedTitle: '切換模型失敗',
+      modelFallback: '模型暫時無法切換,請稍後重試。',
+      thinkingFailedTitle: '切換思考級別失敗',
+      thinkingFallback: '思考級別暫時無法切換,請稍後重試。',
+    },
+    goalDialog: {
+      title: '設定 Goal',
+      description: 'Goal 會在每輪結束後自動續行,直到達成、判定不可行,或觸及下面的預算。隨時可在輸入框上方停止。',
+      conditionLabel: '達成條件',
+      conditionDescription: '用一句話說明什麼算做完;Maka 每輪都據此判斷。',
+      conditionPlaceholder: '例如:所有測試透過,且 lint 無告警',
+      maxIterationsLabel: '最多輪數',
+      maxIterationsDescription: '留空使用預設值。',
+      maxIterationsInvalid: (max) => `請填 1 到 ${max} 之間的整數,或留空。`,
+      tokenBudgetLabel: 'Token 預算',
+      tokenBudgetDescription: '留空表示不設 token 上限。',
+      tokenBudgetInvalid: (min) => `請填不小於 ${min} 的整數,或留空。`,
+      cancel: '取消',
+      close: '關閉',
+      submit: '開始',
+      failedFallback: '無法設定 Goal,請稍後重試。',
+      statusLabels: {
+        active: '進行中',
+        waiting: '等待中',
+        paused: '已暫停',
+        achieved: '已達成',
+        impossible: '不可行',
+        cleared: '已清除',
+        stalled: '已停滯',
+        budget_limited: '已達到 Token 預算',
+        max_iterations: '已達到最多輪數',
+      },
+      reconciledMatching: (condition, status) =>
+        `已重新讀取目前 Goal:“${condition}”(${status})。它符合你的請求,但無法確認剛才的操作是否提交。`,
+      reconciledDifferent: (condition, status) =>
+        `已重新讀取目前 Goal:“${condition}”(${status})。它與本次請求不同。`,
+      reconciledNoGoal: '已重新讀取目前狀態:目前未讀到 Goal。',
+      reconciliationUnavailable: '連線中斷後暫時無法確認目前 Goal 狀態。請關閉後重新開啟再檢查;此視窗不會重複提交。',
+    },
+    errorBoundary: {
+      copyPending: '複製中…',
+      copied: '已複製',
+      copyFailed: '複製失敗',
+      copyReport: '複製診斷資訊',
+      title: 'Maka 渲染層崩潰了',
+      description:
+        '已捕捉一次未處理的 React 例外狀況。可以重試以清除這次崩潰,或重新載入整個視窗。需要交接時請先複製診斷資訊。',
+      retry: '重試',
+      reload: '重新載入',
+      clipboardFailure: '剪貼簿無法使用或遭系統拒絕,請稍後重試。',
+    },
+    commandPalette: {
+      label: '命令面板',
+      searchLabel: '命令面板搜尋',
+      placeholder: '搜尋命令、設定項或任務…',
+      closeLabel: '關閉命令面板',
+      resultsLabel: '命令面板結果',
+      emptyTitle: '沒有符合的命令',
+      emptyDescription: '換個關鍵詞,或按 Esc 關閉。',
+      selectHint: '選擇',
+      runHint: '執行',
+      closeHint: '關閉',
+      current: '目前',
+      groups: {
+        settings: '設定',
+        permissions: '權限',
+        connections: '連線',
+        conversations: '任務',
+      },
+      staticKeywords: STATIC_COMMAND_KEYWORDS,
+      commands: ZH_STATIC_COMMANDS,
+      settingsSections: ZH_SETTINGS_SECTIONS,
+      permissionModes: {
+        explore: { label: '權限 · 只讀', hint: '讀取和搜尋直通,寫入和網路仍需確認' },
+        ask: { label: '權限 · 自動', hint: '在 Maka 的保護層內執行;需要超出目前權限範圍時再詢問' },
+        bypass: {
+          label: '權限 · 完全權限',
+          hint: '不經 Maka 的保護層,直接存取你的檔案和網路',
+        },
+      },
+      settingsCommand: (section: string) => `設定 · ${section}`,
+      testDefaultConnection: (name: string) => `測試預設連線 · ${name}`,
+      setDefaultConnection: (name: string) => `設為預設 · ${name}`,
+      testConnection: (name: string) => `測試連線 · ${name}`,
+      settingsKeywords: (section: SettingsSection, label: string) => [section, label, 'settings', '設定'],
+      permissionKeywords: (mode: PermissionMode) => [mode, 'permission', 'mode', '權限', '模式'],
+      connectionKeywords: (action: 'default' | 'test', name: string, providerType: string) => [
+        action,
+        'connection',
+        '連線',
+        '預設',
+        '測試',
+        name,
+        providerType,
+      ],
+    },
+    keyboardHelp: {
+      title: '鍵盤快捷鍵',
+      sections: [
+        {
+          heading: '通用',
+          rows: [
+            {
+              keys: ['⌘', 'K'],
+              description: '開啟命令面板(跳任務 / 設定 / 主題等)',
+            },
+            { keys: ['?'], description: '開啟 / 關閉此快捷鍵面板' },
+            { keys: ['⌘', 'N'], description: '建立任務' },
+            { keys: ['⌘', ','], description: '開啟設定' },
+            {
+              keys: ['⌘', 'Shift', 'D'],
+              description: '複製目前上下文的診斷資訊',
+            },
+            { keys: ['Esc'], description: '關閉目前模態框' },
+          ],
+        },
+        {
+          heading: 'Composer 輸入',
+          rows: [
+            { keys: ['Enter'], description: '傳送訊息' },
+            { keys: ['Shift', 'Enter'], description: '插入換行' },
+            { keys: ['Alt', 'Enter'], description: '插入換行(備用)' },
+          ],
+        },
+        {
+          heading: '任務列表',
+          rows: [
+            { keys: ['Tab'], description: '在任務與導航之間移動焦點' },
+            { keys: ['↑', '↓'], description: '上下移動聚焦的任務' },
+            { keys: ['Home', 'End'], description: '跳到列表頂部 / 底部' },
+            { keys: ['Enter'], description: '開啟聚焦的任務' },
+            { keys: ['Delete'], description: '彈出刪除確認(永遠不靜默刪除)' },
+            { keys: ['F'], description: '聚焦任務列表搜尋框(按 Esc 清空)' },
+          ],
+        },
+        {
+          heading: '聊天區',
+          rows: [
+            { keys: ['Tab'], description: '聚焦工具活動 / 複製按鈕' },
+            { keys: ['Space', 'Enter'], description: '展開 / 摺疊工具呼叫' },
+          ],
+        },
+        {
+          heading: '面板調整',
+          rows: [
+            { keys: ['Tab'], description: '聚焦左右分割條' },
+            { keys: ['←', '→'], description: '微調任務列表寬度(±10 px)' },
+            { keys: ['Shift', '←', '→'], description: '快速調整(±50 px)' },
+            { keys: ['Home', 'End'], description: '直接拉到最小 / 最大寬度' },
+          ],
+        },
+      ],
+    },
+    chrome: {
+      windowActions: '視窗快捷操作',
+      searchConversations: '搜尋任務',
+      expandSidebar: '展開側邊欄',
+      collapseSidebar: '收起側邊欄',
+      newTask: '新任務',
+      expandWorkbar: '展開任務工作欄',
+      collapseWorkbar: '收起任務工作欄',
+      workspaceActions: '工作區輔助操作',
+    },
+    app: {
+      loadingWorkbarLabel: '正在載入任務工作欄',
+      loadingWorkbar: '正在載入任務工作欄…',
+      useSkillPrompt: (skillName: string) => `使用 ${skillName} 技能:`,
+      newConversation: '建立任務',
+      compactSuccessTitle: '上下文已壓縮',
+      compactSuccessDescription: '較早的上下文已替換為檢查點摘要。',
+      compactStartedTitle: '正在壓縮上下文',
+      compactStartedDescription: '正在將較早的上下文整理為檢查點摘要。',
+      compactUnchangedTitle: '無需壓縮',
+      compactUnchangedDescription: '任務已使用最新的檢查點。',
+      compactErrorTitle: '壓縮失敗',
+      compactErrorFallback: '任務暫時無法壓縮,請稍後重試。',
+      slashCommands: {
+        compact: { name: '壓縮上下文', description: '壓縮舊歷史並保留目前任務' },
+        graph: { name: '使用 Graph', description: '檢視、切換或單次執行 Graph' },
+        side: { name: '開啟側聊', description: '在右側開始一個具體話題' },
+        swarm: { name: '使用 Swarm', description: '檢視、切換或單次執行 Swarm' },
+      },
+      sideChatUnavailableTitle: '暫時無法開啟側邊對話',
+      sideChatUnavailableDescription: '請先在主任務中傳送一條訊息,再使用 /side。',
+      sideChatContextPendingTitle: '先處理待發送的上下文',
+      sideChatContextPendingDescription:
+        '目前 Composer 還有附件、引用或檔案 mention。請先發送或移除它們,再使用 /side。',
+      resumeStartedTitle: '已開始安全恢復',
+      resumeStartedDescription: '正在從最後一個完整執行邊界繼續',
+      resumeFailedTitle: '恢復失敗',
+      resumeFailedFallback: '無法啟動安全恢復,請檢查任務狀態後重試。',
+      goalClearFailedTitle: '停止目標失敗',
+      goalClearFailedFallback: '目標仍可能繼續執行,請立即重試。',
+      goalPauseFailedTitle: '暫停目標失敗',
+      goalPauseFailedFallback: '目標可能仍在自動續行,請立即重試。',
+      goalResumeFailedTitle: '恢復目標失敗',
+      goalResumeFailedFallback: '目標仍處於暫停狀態,請重試。',
+      appearanceLoadErrorTitle: '載入外觀設定失敗',
+      appearanceLoadErrorFallback: '外觀設定暫時無法載入,請稍後重試。',
+      memoryRefreshErrorTitle: '重新整理本地記憶狀態失敗',
+      memoryLoadErrorTitle: '載入本地記憶狀態失敗',
+      memoryErrorFallback: '本地記憶狀態暫時無法重新整理,請稍後重試。',
+      openModelSettings: '開啟設定 · 模型',
+      configureModelsOnHost: (hostName: string) =>
+        `請先在 ${hostName} 上設定模型連線。`,
+      sidebarCollapsed: '側邊欄已收起',
+      resizeConversationList: '調整任務列表寬度',
+      skipErrorTitle: '跳過失敗',
+      tryAgainLater: '請稍後重試。',
+      updateInstallFailedTitle: '無法安裝更新',
+      updateInstallFailedFallback: '請稍後重試。',
+      updateInstallManualFallback: '請稍後重試,或手動下載最新版本。',
+      updateActiveTasksTitle: '仍有任務正在執行',
+      updateActiveTasksDescription: '仍有任務正在執行。更新會中斷這些任務,是否繼續?',
+      updateActiveTasksConfirm: '仍然更新',
+      updateActiveTasksCancel: '取消',
+      updateRetryFailedTitle: '無法重新下載更新',
+      updateRetryFailedFallback: '請稍後重試,或手動下載最新版本。',
+      loading: '載入中',
+      goToModels: '去模型',
+      boundaryUnreadableTitle: '暫時讀不到這個任務的權限',
+      boundaryUnreadableDetail: '在讀到之前,這裡暫時不能輸入。可以重試,或先切換到別的任務。',
+      boundaryUnreadableRetry: '重試',
+      boundaryUnreadableRetrying: '重試中…',
+      permissionModeStreaming: '目前任務正在流式輸出,等結束後再切換權限模式。',
+      permissionModeRunning: '目前任務正在執行,等結束後再切換權限模式。',
+      permissionModeWaiting: '目前有工具呼叫正在等待確認,處理後再切換權限模式。',
+      modeChangeLoading: '會話還在載入,稍候即可切換模式。',
+      modeChanging: '模式正在切換,完成後再繼續操作。',
+      modeChangeStreaming: '目前任務正在流式輸出,等結束後再切換模式。',
+      modeChangeRunning: '目前任務正在執行,等結束後再切換模式。',
+      modeChangeWaiting: '目前有工具呼叫正在等待確認,處理後再切換模式。',
+      goalTurnActive: 'Goal 從下一輪開始生效。等目前這一輪結束後再設定。',
+      planModeFailedTitle: '切換 Plan 模式失敗',
+      planModeFallback: 'Plan 模式暫時無法切換,請稍後重試。',
+      orchestrationModeFailedTitle: '切換編排模式失敗',
+      orchestrationModeFallback: '編排模式暫時無法切換,請稍後重試。',
+      planModeExitPendingTitle: '放棄目前方案?',
+      planModeExitPendingDescription: (title: string) =>
+        `「${title}」尚未審批。退出 Plan Mode 後,該方案會標記為已放棄,但歷史記錄仍會保留。`,
+      planModeExitConfirm: '放棄並退出',
+      planModeExitCancel: '繼續規劃',
+      planModeExecutionActiveTitle: '計劃仍在執行',
+      planModeExecutionActiveDescription: '請先中斷目前執行,再進入 Plan Mode 調整方案。',
+      swarmModeEnabledTitle: 'Swarm Mode 已開啟',
+      swarmModeDisabledTitle: 'Swarm Mode 未開啟',
+      swarmModeStatusDescription: '使用 /swarm on、/swarm off,或 /swarm <任務> 單次執行。',
+      graphModeEnabledTitle: 'Graph Mode 已開啟',
+      graphModeDisabledTitle: 'Graph Mode 未開啟',
+      graphModeStatusDescription: '使用 /graph on、/graph off,或 /graph <任務> 單次執行。',
+      graphHistoryTitle: 'Graph 歷史',
+      graphHistoryDescription: '請在 Agent Graph 面板的執行輪次選單中檢視歷史記錄。',
+      resizeWorkbar: '調整任務工作欄寬度',
+    },
+  },
   en: {
     navigation: { settings: 'Settings' },
     actions: { retry: 'Retry' },
@@ -1818,7 +2320,7 @@ export function getShellCopy(locale: UiLocale): ShellCopy {
 }
 
 export function localizedShellErrorMessage(error: unknown, fallback: string, locale: UiLocale): string {
-  return locale === 'zh' ? generalizedErrorMessageChinese(error, fallback) : generalizedErrorMessage(error, fallback);
+  return generalizedErrorMessageForLocale(error, fallback, locale);
 }
 
 export function sessionSettingFailureCopy(
diff --git a/apps/desktop/src/renderer/locales/shell-remaining-copy.ts b/apps/desktop/src/renderer/locales/shell-remaining-copy.ts
index 75ace87c6e..17fb805908 100644
--- a/apps/desktop/src/renderer/locales/shell-remaining-copy.ts
+++ b/apps/desktop/src/renderer/locales/shell-remaining-copy.ts
@@ -97,6 +97,78 @@ const zhCopy = {
     intentSeparator: " — ",
   },
 } as const;
+const zhTwCopy = {
+  scheduledTaskActions: {
+    refreshFailed: "重新整理計劃失敗",
+    refreshFallback: "重新整理定時任務失敗,請稍後重試。",
+    created: "已建立定時任務",
+    createFailed: "建立計劃失敗",
+    createFallback: "建立定時任務失敗,請稍後重試。",
+    createIncognitoBlocked: "隱身模式開啟時不能建立定時任務。",
+    saved: "已儲存定時任務",
+    saveFailed: "儲存計劃失敗",
+    saveFallback: "儲存定時任務失敗,請稍後重試。",
+    enabled: "已啟用任務",
+    paused: "已暫停任務",
+    updateFailed: "更新計劃失敗",
+    updateFallback: "更新定時任務失敗,請稍後重試。",
+    triggered: "已觸發定時任務",
+    triggerFailed: "觸發計劃失敗",
+    triggerFallback: "觸發定時任務失敗,請稍後重試。",
+    snoozed: "已延後 10 分鐘",
+    snoozeFailed: "延後計劃失敗",
+    snoozeFallback: "延後定時任務失敗,請稍後重試。",
+    task: "定時任務",
+    clearTitle: (name: string) => `清空 “${name}” 的執行記錄`,
+    clearDescription: "定時任務本身會保留;只清空最近執行記錄和最近狀態。",
+    clear: "清空記錄",
+    cancel: "取消",
+    cleared: "已清空執行記錄",
+    clearFailed: "清空記錄失敗",
+    clearFallback: "清空定時任務記錄失敗,請稍後重試。",
+    deleteTitle: (name: string) => `刪除 “${name}”`,
+    deleteDescription: "該任務和最近執行記錄會被刪除。該操作不可撤銷。",
+    delete: "刪除",
+    deleted: "已刪除定時任務",
+    deleteFailed: "刪除計劃失敗",
+    deleteFallback: "刪除定時任務失敗,請稍後重試。",
+  },
+  dailyReview: {
+    yesterday: "昨天",
+    today: "今天",
+    followSettings: "跟隨設定",
+    unavailable: "每日回顧生成暫不可用",
+    historyUnavailable: "每日回顧歷史暫不可用",
+    archiveMissing: "找不到每日回顧報告",
+    settingsUnavailable: "每日回顧設定暫不可用",
+  },
+  connections: {
+    refreshFailed: "重新整理模型連線失敗",
+    refreshFallback: "模型連線暫時無法重新整理,請稍後重試。",
+  },
+  tasks: { loadFailed: "待辦載入失敗,請重試。" },
+  projects: { ungrouped: "未歸屬專案" },
+  models: { unavailable: "目前不可用" },
+  overlays: {
+    loadingSettings: "正在載入設定",
+    loadingSettingsProgress: "正在載入設定…",
+  },
+  notifications: {
+    scheduledTask: "定時任務",
+    viewScheduledTasks: "檢視定時任務",
+  },
+  previousMainProcessInterruption: {
+    title: "Maka 已恢復",
+    description: "上次退出未完成。",
+    copyDiagnostics: "複製報告",
+  },
+  conversationExport: {
+    exported: (date: string) => `由 Maka 於 ${date} 匯出。`,
+    you: "你",
+    toolCalls: "工具呼叫",
+    intentSeparator: " — ",
+  },
+} as const;
 
 export type ShellRemainingCopy = WidenCopy<typeof zhCopy>;
 
@@ -179,7 +251,11 @@ const enCopy: ShellRemainingCopy = {
   },
 };
 
-const COPY = { zh: zhCopy, en: enCopy } satisfies UiCatalog<ShellRemainingCopy>;
+const COPY = {
+  'zh-CN': zhCopy,
+  'zh-TW': zhTwCopy,
+  en: enCopy,
+} satisfies UiCatalog<ShellRemainingCopy>;
 
 export function getShellRemainingCopy(locale: UiLocale): ShellRemainingCopy {
   return COPY[locale];
diff --git a/apps/desktop/src/renderer/mcp-catalog.ts b/apps/desktop/src/renderer/mcp-catalog.ts
index 417c4c001e..9d5b834ecf 100644
--- a/apps/desktop/src/renderer/mcp-catalog.ts
+++ b/apps/desktop/src/renderer/mcp-catalog.ts
@@ -240,9 +240,27 @@ const MCP_CATALOG_ENGLISH_COPY: Record<McpCatalogId, McpCatalogLocalizedCopy> =
   'sequential-thinking': { name: 'Sequential thinking', description: 'Provide revisable, verifiable structured reasoning for complex problems.', category: 'Reasoning and planning', setupLabel: undefined },
 };
 
+const MCP_CATALOG_TRADITIONAL_COPY: Record<McpCatalogId, McpCatalogLocalizedCopy> = {
+  dingtalk: { name: 'DingTalk', description: '管理聯絡人、行事曆、任務和協作資料。', category: '通訊', setupLabel: '需要 Client ID 和 Client Secret' },
+  feishu: { name: '飛書', description: '存取飛書文件、行事曆、訊息和 OpenAPI。', category: '通訊', setupLabel: '需要 App ID 和 App Secret' },
+  slack: { name: 'Slack', description: '傳送訊息、管理頻道,並在 Slack 工作區協作。', category: '通訊', setupLabel: '需要 Bot Token 和 Team ID' },
+  line: { name: 'LINE', description: '透過 LINE Bot Messaging API 傳送和管理訊息。', category: '通訊', setupLabel: '需要 Channel Access Token' },
+  notion: { name: 'Notion', description: '搜尋、讀取和更新 Notion 工作區。', category: '知識與文件', setupLabel: '需要登入授權' },
+  'macos-apps': { name: 'macOS 應用程式', description: '透過原生系統權限連接「行事曆」與「提醒事項」。', category: '系統與生產力', setupLabel: undefined },
+  'google-calendar': { name: 'Google 日曆', description: '管理活動、建立會議並檢查可用時間。', category: '系統與生產力', setupLabel: '需要 OAuth 憑證檔案' },
+  figma: { name: 'Figma', description: '讀取設計檔案、元件和開發交付資料。', category: '設計與開發', setupLabel: '需要 Personal Access Token' },
+  vercel: { name: 'Vercel', description: '檢查專案、部署、記錄和平台文件。', category: '設計與開發', setupLabel: '需要登入授權' },
+  supabase: { name: 'Supabase', description: '管理資料庫、專案設定、移轉和 Edge Functions。', category: '設計與開發', setupLabel: '需要登入授權' },
+  filesystem: { name: '本機檔案', description: '在選取的資料夾中安全地讀取、寫入和管理檔案。', category: '檔案與知識', setupLabel: '需要選擇允許存取的資料夾' },
+  memory: { name: '持久記憶', description: '使用結構化知識圖譜記住實體、關係和重要事實。', category: '檔案與知識', setupLabel: undefined },
+  playwright: { name: '瀏覽器自動化', description: '讓 Maka 透過 Playwright 讀取和操作實際網頁。', category: '設計與開發', setupLabel: undefined },
+  'sequential-thinking': { name: '循序思考', description: '為複雜問題提供可修正、可驗證的結構化推理。', category: '推理與規劃', setupLabel: undefined },
+};
+
 export function getMcpCatalog(locale: UiLocale): McpCatalogEntry[] {
-  if (locale === 'zh') return MCP_CATALOG;
-  return MCP_CATALOG.map((entry) => ({ ...entry, ...MCP_CATALOG_ENGLISH_COPY[entry.id] }));
+  if (locale === 'zh-CN') return MCP_CATALOG;
+  const copy = locale === 'zh-TW' ? MCP_CATALOG_TRADITIONAL_COPY : MCP_CATALOG_ENGLISH_COPY;
+  return MCP_CATALOG.map((entry) => ({ ...entry, ...copy[entry.id] }));
 }
 
 export function catalogEntryMatches(entry: McpCatalogEntry, normalizedQuery: string): boolean {
diff --git a/apps/desktop/src/renderer/model-catalog-choices.ts b/apps/desktop/src/renderer/model-catalog-choices.ts
index d1d8b09a46..fdd90df6c3 100644
--- a/apps/desktop/src/renderer/model-catalog-choices.ts
+++ b/apps/desktop/src/renderer/model-catalog-choices.ts
@@ -48,7 +48,7 @@ export function buildCatalogRecommendedDefaultModel(providerType: ProviderType):
 export function buildCatalogDailyReviewModelOptions(
   connections: readonly (LlmConnection & HostResolvedConnectionCatalog)[],
   currentModelKey: string,
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): Array<readonly [string, string]> {
   const current = parseDailyReviewModelKey(currentModelKey);
   const candidates: Array<{ key: string; label: string; safeSourceLabel: string }> = [];
diff --git a/apps/desktop/src/renderer/model-connection-errors.ts b/apps/desktop/src/renderer/model-connection-errors.ts
index fe3fdf5568..1581398750 100644
--- a/apps/desktop/src/renderer/model-connection-errors.ts
+++ b/apps/desktop/src/renderer/model-connection-errors.ts
@@ -46,7 +46,7 @@ export function noRealConnectionReasonFromEvent(event: Extract<SessionEvent, { t
   ).reason;
 }
 
-export function noRealConnectionSetupDescription(reason: string | undefined, locale: UiLocale = 'zh'): string {
+export function noRealConnectionSetupDescription(reason: string | undefined, locale: UiLocale = 'zh-CN'): string {
   const copy = getDesktopConversationCopy(locale).model;
   return reason && Object.hasOwn(copy.configurationReason, reason)
     ? copy.configurationReason[reason as ChatConfigurationReason]
@@ -55,7 +55,7 @@ export function noRealConnectionSetupDescription(reason: string | undefined, loc
 
 export function sessionEventErrorMessage(
   event: Extract<SessionEvent, { type: 'error' }>,
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): string {
   if (isNoRealConnectionEvent(event)) {
     return noRealConnectionSetupDescription(noRealConnectionReasonFromEvent(event), locale);
@@ -69,7 +69,7 @@ export function sessionEventErrorMessage(
 export function modelSetupToastCopy(
   reason: string | undefined,
   fallback: string,
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): { title: string; description: string } {
   const copy = getDesktopConversationCopy(locale).model;
   if (reason === 'connection_missing') {
diff --git a/apps/desktop/src/renderer/session-error-presentation.ts b/apps/desktop/src/renderer/session-error-presentation.ts
index f3bccf1e7b..9dd8985ab5 100644
--- a/apps/desktop/src/renderer/session-error-presentation.ts
+++ b/apps/desktop/src/renderer/session-error-presentation.ts
@@ -25,7 +25,7 @@ import { getDesktopConversationCopy } from './locales/conversation-copy.js';
  * runtime. Unknown reasons intentionally return undefined so callers can use
  * their existing safe fallback instead of displaying raw provider text.
  */
-export function describeSessionErrorReason(reason: string | undefined, locale: UiLocale = 'zh'): string | undefined {
+export function describeSessionErrorReason(reason: string | undefined, locale: UiLocale = 'zh-CN'): string | undefined {
   const copy = getDesktopConversationCopy(locale).turnError;
   switch (reason?.toLowerCase()) {
     case 'context_overflow':
diff --git a/apps/desktop/src/renderer/session-status-presentation.ts b/apps/desktop/src/renderer/session-status-presentation.ts
index c039d73e30..99088d068f 100644
--- a/apps/desktop/src/renderer/session-status-presentation.ts
+++ b/apps/desktop/src/renderer/session-status-presentation.ts
@@ -97,7 +97,7 @@ export function normalizeSessionSummaryForDisplay<T extends SessionSummary>(sess
  * the UI; they just fall through to the catch-all until the mapping
  * is extended.
  */
-export function describeTurnErrorClass(errorClass: string | undefined, locale: UiLocale = 'zh'): string {
+export function describeTurnErrorClass(errorClass: string | undefined, locale: UiLocale = 'zh-CN'): string {
   const copy = getDesktopConversationCopy(locale).turnError;
   if (!errorClass) return copy.unknown;
   const reasonDescription = describeSessionErrorReason(errorClass, locale);
@@ -169,7 +169,7 @@ export interface FailedTurnExecutionState {
  */
 export function describeFailedTurnExecutionState(
   state: FailedTurnExecutionState,
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): string | undefined {
   const copy = getDesktopConversationCopy(locale).turnError.executionState;
   if (state.erroredToolCount > 0) return copy.erroredTool;
diff --git a/apps/desktop/src/renderer/settings/about-settings-page.tsx b/apps/desktop/src/renderer/settings/about-settings-page.tsx
index aad940a6a5..ad7aa8ed1d 100644
--- a/apps/desktop/src/renderer/settings/about-settings-page.tsx
+++ b/apps/desktop/src/renderer/settings/about-settings-page.tsx
@@ -125,7 +125,10 @@ export function AboutSettingsPage(props: { onOpenKeyboardHelp?(): void }) {
       const status = await window.maka.app.checkForUpdates();
       if (aboutPageMountedRef.current) setUpdateStatus(status);
       if (status.state === 'error') {
-        toast.error(copy.updateCheckFailed, copy.updateCheckFailedDetail(status.message));
+        toast.error(
+          copy.updateCheckFailed,
+          copy.updateCheckFailedDetail(settingsActionErrorMessage(status.message, locale)),
+        );
       }
     } catch (error) {
       if (aboutPageMountedRef.current) {
@@ -198,7 +201,10 @@ export function AboutSettingsPage(props: { onOpenKeyboardHelp?(): void }) {
               window floor the sentence needs the full width. */}
           {isDevBuild ? null : (
             <HStack gap={3} justify="between" wrap="wrap">
-              <Text type="body">{aboutUpdateStatusDetail(updateStatus, copy)}</Text>
+              <Text type="body">{aboutUpdateStatusDetail(updateStatus, copy, {
+                isDevBuild,
+                errorDetail: (message) => settingsActionErrorMessage(message, locale),
+              })}</Text>
               <Button
                 variant="secondary"
                 size="sm"
diff --git a/apps/desktop/src/renderer/settings/about-update-status.ts b/apps/desktop/src/renderer/settings/about-update-status.ts
index 6e39e3b532..dd7c71e93a 100644
--- a/apps/desktop/src/renderer/settings/about-update-status.ts
+++ b/apps/desktop/src/renderer/settings/about-update-status.ts
@@ -69,6 +69,10 @@ export function aboutChannelFacts(
 export function aboutUpdateStatusDetail(
   status: AppUpdateStatus | null,
   copy: AboutCopy,
+  options: {
+    readonly isDevBuild: boolean;
+    readonly errorDetail?: (message: string) => string;
+  },
 ): string {
   if (!status || status.state === 'idle') return copy.updateIdle;
   if (status.state === 'checking') return copy.checkingForUpdates;
@@ -80,5 +84,7 @@ export function aboutUpdateStatusDetail(
   if (status.state === 'verifying') return copy.updateVerifying(status.latestVersion);
   if (status.state === 'downloaded') return copy.updateDownloaded(status.latestVersion);
   if (status.state === 'installing') return copy.updateInstalling(status.latestVersion);
-  return copy.updateCheckFailedDetail(status.message);
+  return copy.updateCheckFailedDetail(
+    options.errorDetail ? options.errorDetail(status.message) : status.message,
+  );
 }
diff --git a/apps/desktop/src/renderer/settings/bot-chat-detail.tsx b/apps/desktop/src/renderer/settings/bot-chat-detail.tsx
index 2e443a042e..ae9ee1aeaf 100644
--- a/apps/desktop/src/renderer/settings/bot-chat-detail.tsx
+++ b/apps/desktop/src/renderer/settings/bot-chat-detail.tsx
@@ -305,7 +305,7 @@ export function BotChatChannelDetail(props: {
           title={detailCopy.latestFailure}
           description={(
             <span className="settingsBotBannerDescription">
-              {locale === 'zh' ? viewState.currentError : detailCopy.latestFailureDetail}
+              {locale === 'zh-CN' ? viewState.currentError : detailCopy.latestFailureDetail}
             </span>
           )} />
       )}
@@ -415,7 +415,7 @@ export function BotChatChannelDetail(props: {
             if (snapshot.warning) {
               toast.warning(
                 detailCopy.credentialsSaved(providerPresentation.label),
-                locale === 'zh' ? snapshot.warning : detailCopy.savedButNotConnected,
+                locale === 'zh-CN' ? snapshot.warning : detailCopy.savedButNotConnected,
               );
               return;
             }
@@ -635,7 +635,7 @@ function BotAllowedUserIdsField(props: {
     if (!same) props.onChange(next);
   };
   const warning = invalidEntries.length > 0
-    ? `${copy.invalidUsers(invalidEntries.slice(0, 3).join(locale === 'zh' ? '、' : ', '))}${invalidEntries.length > 3 ? copy.moreInvalid(invalidEntries.length) : ''}`
+    ? `${copy.invalidUsers(invalidEntries.slice(0, 3).join(locale !== 'en' ? '、' : ', '))}${invalidEntries.length > 3 ? copy.moreInvalid(invalidEntries.length) : ''}`
     : undefined;
 
   return (
@@ -653,7 +653,7 @@ function BotAllowedUserIdsField(props: {
   );
 }
 
-function botConnectionLabel(connection: BotStatus['connection'], locale: 'zh' | 'en'): string {
+function botConnectionLabel(connection: BotStatus['connection'], locale: 'zh-CN' | 'zh-TW' | 'en'): string {
   const copy = getBotSettingsCopy(locale).status;
   switch (connection) {
     case 'polling': return copy.polling;
diff --git a/apps/desktop/src/renderer/settings/bot-chat-overview.tsx b/apps/desktop/src/renderer/settings/bot-chat-overview.tsx
index f3f096fe99..b2e248a006 100644
--- a/apps/desktop/src/renderer/settings/bot-chat-overview.tsx
+++ b/apps/desktop/src/renderer/settings/bot-chat-overview.tsx
@@ -145,7 +145,7 @@ function botOverviewDetail(
   currentError: string | undefined,
   fallback: string,
   liveOperational: boolean,
-  locale: 'zh' | 'en',
+  locale: 'zh-CN' | 'zh-TW' | 'en',
 ): ReactNode {
   const copy = getBotSettingsCopy(locale).overview;
   const identity = status?.identity?.username ?? status?.identity?.displayName;
@@ -157,7 +157,7 @@ function botOverviewDetail(
       </>
     );
   }
-  if (currentError) return locale === 'zh' ? currentError : fallback;
+  if (currentError) return locale === 'zh-CN' ? currentError : fallback;
   if (status?.reason) return botStatusDetail(status, locale);
   return fallback;
 }
diff --git a/apps/desktop/src/renderer/settings/bot-chat-shared.tsx b/apps/desktop/src/renderer/settings/bot-chat-shared.tsx
index 101d069aa7..0a8b7a7ff9 100644
--- a/apps/desktop/src/renderer/settings/bot-chat-shared.tsx
+++ b/apps/desktop/src/renderer/settings/bot-chat-shared.tsx
@@ -60,7 +60,7 @@ export const BOT_LABELS: Record<BotProvider, { support: 'runtime' | 'credentials
   slack: { support: 'runtime' },
 };
 
-export function botReadinessCopyForSupport(support: 'runtime' | 'credentials' | 'planned', readiness: BotReadinessState, locale: UiLocale = 'zh') {
+export function botReadinessCopyForSupport(support: 'runtime' | 'credentials' | 'planned', readiness: BotReadinessState, locale: UiLocale = 'zh-CN') {
   const copy = getBotSettingsCopy(locale);
   if (support === 'planned') return copy.planned;
   return copy.readiness[readiness] ?? copy.readiness.scaffolded;
@@ -98,7 +98,7 @@ export function BotBrandLogo(props: { provider: BotProvider; size?: 'compact' |
 export type BotPendingActionName = 'test' | 'connect' | 'restart' | 'disconnect';
 export type BotPendingAction = { provider: BotProvider; action: BotPendingActionName };
 
-export function botStatusDetail(status: BotStatus, locale: UiLocale = 'zh'): string {
+export function botStatusDetail(status: BotStatus, locale: UiLocale = 'zh-CN'): string {
   const copy = getBotSettingsCopy(locale).status;
   switch (status.reason) {
     case 'disabled': return copy.disabled;
diff --git a/apps/desktop/src/renderer/settings/bot-onboarding-modal.tsx b/apps/desktop/src/renderer/settings/bot-onboarding-modal.tsx
index 05d99c09af..90496c3070 100644
--- a/apps/desktop/src/renderer/settings/bot-onboarding-modal.tsx
+++ b/apps/desktop/src/renderer/settings/bot-onboarding-modal.tsx
@@ -251,7 +251,7 @@ function statusCopy(
   starting: boolean,
   error: string | null,
   copy: BotSettingsCopy['onboarding']['providers'][BotOnboardingProvider],
-  locale: 'zh' | 'en' = 'zh',
+  locale: 'zh-CN' | 'zh-TW' | 'en' = 'zh-CN',
 ): string {
   const shared = getBotSettingsCopy(locale).onboarding;
   if (starting) return shared.generating;
@@ -263,12 +263,12 @@ function statusCopy(
     // PR1197 review (P0-3): honour the honest "saved but not connected" notice
     // instead of claiming a healthy connection.
     case 'connected': return snapshot.warning
-      ? (locale === 'zh' ? snapshot.warning : shared.connectedWarning)
+      ? (locale === 'zh-CN' ? snapshot.warning : shared.connectedWarning)
       : shared.connected(getBotSettingsCopy(locale).providers[snapshot.provider].label);
     case 'expired': return shared.expired;
     case 'denied': return shared.denied;
     case 'cancelled': return shared.cancelled;
-    case 'error': return locale === 'zh' ? (snapshot.error ?? shared.failed) : shared.failed;
+    case 'error': return locale === 'zh-CN' ? (snapshot.error ?? shared.failed) : shared.failed;
     default: return shared.preparing;
   }
 }
diff --git a/apps/desktop/src/renderer/settings/daily-review-settings-page.tsx b/apps/desktop/src/renderer/settings/daily-review-settings-page.tsx
index 57fdc46fcf..d8e244ecc8 100644
--- a/apps/desktop/src/renderer/settings/daily-review-settings-page.tsx
+++ b/apps/desktop/src/renderer/settings/daily-review-settings-page.tsx
@@ -39,7 +39,7 @@ function buildDailyReviewModelOptions(
   connections: readonly ProjectedLlmConnection[],
   currentModelKey: string,
   copy: DailyReviewSettingsCopy,
-  locale: 'zh' | 'en',
+  locale: 'zh-CN' | 'zh-TW' | 'en',
 ): Array<{ value: string; label: string }> {
   return [
     { value: DAILY_REVIEW_DEFAULT_MODEL_VALUE, label: copy.defaultModel },
diff --git a/apps/desktop/src/renderer/settings/data-settings-page.tsx b/apps/desktop/src/renderer/settings/data-settings-page.tsx
index 517563eb0f..7ab341d52d 100644
--- a/apps/desktop/src/renderer/settings/data-settings-page.tsx
+++ b/apps/desktop/src/renderer/settings/data-settings-page.tsx
@@ -220,7 +220,7 @@ export function DataSettingsPage(props: {
       if (res.ok) {
         toast.success(copy.imported, summarizeImportResult(res.result, copy));
       } else if (res.reason !== 'canceled') {
-        const detail = res.message && (locale === 'zh' || !/[\u3400-\u9fff]/u.test(res.message))
+        const detail = res.message && (locale === 'zh-CN' || !/[\u3400-\u9fff]/u.test(res.message))
           ? res.message
           : copy.invalidFile;
         toast.error(copy.importFailed, detail, undefined, diagnosticTarget);
diff --git a/apps/desktop/src/renderer/settings/memory-settings-view-model.ts b/apps/desktop/src/renderer/settings/memory-settings-view-model.ts
index 2359bd57cb..d64ef28e79 100644
--- a/apps/desktop/src/renderer/settings/memory-settings-view-model.ts
+++ b/apps/desktop/src/renderer/settings/memory-settings-view-model.ts
@@ -36,7 +36,7 @@ export function deriveMemorySettingsViewModel(input: {
   query: string;
   copy?: MemorySettingsCopy;
 }) {
-  const copy = input.copy ?? getMemorySettingsCopy('zh');
+  const copy = input.copy ?? getMemorySettingsCopy('zh-CN');
   const effective = input.state ?? {
     path: '',
     enabled: input.localMemorySettings.enabled,
diff --git a/apps/desktop/src/renderer/settings/permission-center-page.tsx b/apps/desktop/src/renderer/settings/permission-center-page.tsx
index 0bf7a7c1fb..ec4c6517d7 100644
--- a/apps/desktop/src/renderer/settings/permission-center-page.tsx
+++ b/apps/desktop/src/renderer/settings/permission-center-page.tsx
@@ -705,7 +705,7 @@ function localizedCapabilityLabel(capability: CapabilitySnapshot, locale: UiLoca
 }
 
 function localizedSnapshotText(value: string | undefined, locale: UiLocale): string | undefined {
-  if (!value || (locale === 'en' && /[\u3400-\u9fff]/u.test(value))) return undefined;
+  if (!value || (locale !== 'zh-CN' && /[\u3400-\u9fff]/u.test(value))) return undefined;
   return value;
 }
 
@@ -714,7 +714,7 @@ function localizedCapabilityGuidance(
   locale: UiLocale,
   copy: PermissionCenterCopy,
 ): readonly string[] {
-  return capability.guidance.filter((item) => locale === 'zh' || !/[\u3400-\u9fff]/u.test(item));
+  return capability.guidance.filter((item) => locale === 'zh-CN' || !/[\u3400-\u9fff]/u.test(item));
 }
 
 function featureTone(state: CapabilitySnapshot['feature']['state']): StatusSemantic {
diff --git a/apps/desktop/src/renderer/settings/personalization-settings-section.tsx b/apps/desktop/src/renderer/settings/personalization-settings-section.tsx
index 1ba7fedee8..7328350266 100644
--- a/apps/desktop/src/renderer/settings/personalization-settings-section.tsx
+++ b/apps/desktop/src/renderer/settings/personalization-settings-section.tsx
@@ -28,13 +28,12 @@
 // another. It is one section of someone else's page: named, filed, and shaped
 // as one.
 import { useEffect, useRef, useState } from 'react';
-import { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core';
 import { SettingsField, SettingsRow, SettingsSection } from './settings-section';
 import { SettingsExpandableRow } from './settings-expandable-row';
 import { getSettingsSharedCopy } from '../locales/settings-shared-copy';
 import type { AppSettings, PersonalizationSettings, UpdateAppSettingsResult } from '@maka/core/settings';
 import type { UiLocalePreference } from '@maka/core/ui-locale';
-import { TextArea, TextInput, useMountedRef, useToast, useUiLocale } from '@maka/ui';
+import { Selector, TextArea, TextInput, useMountedRef, useToast, useUiLocale } from '@maka/ui';
 import { settingsActionErrorMessage } from './settings-error-copy';
 import { getSettingsPreferencesCopy } from '../locales/settings-preferences-copy.js';
 import { useOptionalRuntimeHostSettingsTarget } from './runtime-host-settings-target.js';
@@ -232,22 +231,22 @@ export function PersonalizationSettingsSection(props: {
       ) : null}
       {/*
         PR-LANG-PREF-0 (WAWQAQ msg `edc9cb41` + kenji `7e532892`
-        acceptance criteria): 自动 / 中文 / English. User explicit
+        acceptance criteria): automatic / Simplified Chinese / Traditional Chinese / English. User explicit
         choice wins over the temporary auto -> zh fallback;
         e2e-fixture override wins over both (deterministic baselines).
       */}
       <SettingsRow
         label={copy.interfaceLanguage}
         description={copy.interfaceLanguageHelp}
-        end={<SegmentedControl
-          value={uiLocale}
-          onChange={(next) => persistLocale(next as UiLocalePreference)}
-          label={copy.interfaceLanguage}
-        >
-          {copy.localeOptions.map(([value, label]) => (
-            <SegmentedControlItem key={value} value={value} label={label} />
-          ))}
-        </SegmentedControl>}
+        end={
+          <Selector
+            label={copy.interfaceLanguage}
+            isLabelHidden
+            value={uiLocale}
+            options={copy.localeOptions.map(([value, label]) => ({ value, label }))}
+            onChange={(next) => persistLocale(next as UiLocalePreference)}
+          />
+        }
       />
       {props.runtimeHostSettingsAvailable ? <SettingsField>
         {/* No fixed height style: it lands as inline style on the wrapper,
diff --git a/apps/desktop/src/renderer/settings/provider-add-form.tsx b/apps/desktop/src/renderer/settings/provider-add-form.tsx
index dbab46b5bd..1588995847 100644
--- a/apps/desktop/src/renderer/settings/provider-add-form.tsx
+++ b/apps/desktop/src/renderer/settings/provider-add-form.tsx
@@ -181,7 +181,7 @@ export function AddProviderForm(props: {
     if (issue.field === 'slug') {
       return issue.reason === 'duplicate'
         ? copy.duplicateSlug
-        : locale === 'zh'
+        : locale === 'zh-CN'
           ? issue.detail
           : copy.invalidSlug;
     }
diff --git a/apps/desktop/src/renderer/settings/provider-catalog-page.tsx b/apps/desktop/src/renderer/settings/provider-catalog-page.tsx
index a4ea2202c8..b910072874 100644
--- a/apps/desktop/src/renderer/settings/provider-catalog-page.tsx
+++ b/apps/desktop/src/renderer/settings/provider-catalog-page.tsx
@@ -34,6 +34,7 @@ import {
   type ProviderType,
 } from '@maka/core/provider-registry';
 import { PROVIDER_REGISTRY, type LlmConnection } from '@maka/core/llm-connections';
+import type { UiLocale } from '@maka/core/ui-locale';
 import { Button, TextInput, useUiLocale } from '@maka/ui';
 import { AddProviderForm } from './provider-add-form';
 import { ProviderLogo, providerDisplay } from './provider-display';
@@ -204,7 +205,7 @@ export function ProviderCatalogPage(props: {
  */
 function ProviderCatalogRows(props: {
   title?: string;
-  locale: 'zh' | 'en';
+  locale: UiLocale;
   cards: readonly OAuthCard[];
   providers: readonly ProviderType[];
   onPick(target: SetupTarget): void;
@@ -317,7 +318,7 @@ export function ProviderSetupPage(props: {
 function providersMatching(
   source: readonly ProviderType[],
   query: string,
-  locale: 'zh' | 'en',
+  locale: UiLocale,
   group?: ProviderCatalogGroup,
 ): ProviderType[] {
   const normalizedQuery = query.toLocaleLowerCase();
diff --git a/apps/desktop/src/renderer/settings/provider-connection-status.ts b/apps/desktop/src/renderer/settings/provider-connection-status.ts
index b74f2c3bab..e8f22c4cc6 100644
--- a/apps/desktop/src/renderer/settings/provider-connection-status.ts
+++ b/apps/desktop/src/renderer/settings/provider-connection-status.ts
@@ -62,7 +62,7 @@ export interface ConnectionChipStatus {
  *   readiness, fixed to credential-only language. Matches the doc warning
  *   at SettingsModal `验证通过 ≠ 运行可用`.
  */
-export function connectionChipStatus(connection: LlmConnection, locale: UiLocale = 'zh'): ConnectionChipStatus | null {
+export function connectionChipStatus(connection: LlmConnection, locale: UiLocale = 'zh-CN'): ConnectionChipStatus | null {
   const copy = getProviderSettingsCopy(locale).shared.connectionStatuses;
   // Ahead of every other branch, including needs_reauth: a retired provider
   // has no sign-in left to return to, so any repair-shaped status here would
diff --git a/apps/desktop/src/renderer/settings/provider-display-copy.ts b/apps/desktop/src/renderer/settings/provider-display-copy.ts
index 077cf893e9..14de01deda 100644
--- a/apps/desktop/src/renderer/settings/provider-display-copy.ts
+++ b/apps/desktop/src/renderer/settings/provider-display-copy.ts
@@ -48,217 +48,270 @@ export interface ProviderCopy {
 }
 
 export const UNKNOWN_PROVIDER_DESCRIPTION = {
-  zh: '该 provider 在当前版本未注册。',
+  'zh-CN': '该 provider 在当前版本未注册。',
+  'zh-TW': '該 provider 在目前版本未註冊。',
   en: 'This provider is not registered in the current build.',
 } satisfies UiCatalog<string>;
 
 export const PROVIDER_DISPLAY_COPY = {
   'kimi-coding-plan': {
-    zh: { name: 'Kimi Coding Plan', description: '月之暗面 · Anthropic 兼容', badge: 'Coding' },
+    'zh-CN': { name: 'Kimi Coding Plan', description: '月之暗面 · Anthropic 兼容', badge: 'Coding' },
+    'zh-TW': { name: 'Kimi Coding Plan', description: '月之暗面 · Anthropic 相容', badge: 'Coding' },
     en: { name: 'Kimi Coding Plan', description: 'Moonshot · Anthropic-compatible', badge: 'Coding' },
   },
   'minimax-coding-plan': {
-    zh: { name: 'MiniMax Coding Plan', description: 'MiniMax Coding 套餐 · Anthropic 兼容', badge: 'Coding' },
+    'zh-CN': { name: 'MiniMax Coding Plan', description: 'MiniMax Coding 套餐 · Anthropic 兼容', badge: 'Coding' },
+    'zh-TW': { name: 'MiniMax Coding Plan', description: 'MiniMax Coding 套餐 · Anthropic 相容', badge: 'Coding' },
     en: { name: 'MiniMax Coding Plan', description: 'MiniMax coding plan · Anthropic-compatible', badge: 'Coding' },
   },
   deepseek: {
-    zh: { name: 'DeepSeek', description: 'DeepSeek 官方接入', badge: 'API' },
+    'zh-CN': { name: 'DeepSeek', description: 'DeepSeek 官方接入', badge: 'API' },
+    'zh-TW': { name: 'DeepSeek', description: 'DeepSeek 官方 API 連線', badge: 'API' },
     en: { name: 'DeepSeek', description: 'Official DeepSeek API access.', badge: 'API' },
   },
   moonshot: {
-    zh: { name: 'Moonshot', description: 'Moonshot 官方接入', badge: 'API' },
+    'zh-CN': { name: 'Moonshot', description: 'Moonshot 官方接入', badge: 'API' },
+    'zh-TW': { name: 'Moonshot', description: 'Moonshot 官方 API 連線', badge: 'API' },
     en: { name: 'Moonshot', description: 'Official Moonshot API access.', badge: 'API' },
   },
   'zai-coding-plan': {
-    zh: { name: 'Z.AI Coding Plan', description: '智谱 · OpenAI 兼容', badge: 'Coding' },
+    'zh-CN': { name: 'Z.AI Coding Plan', description: '智谱 · OpenAI 兼容', badge: 'Coding' },
+    'zh-TW': { name: 'Z.AI Coding Plan', description: '智譜 · OpenAI 相容', badge: 'Coding' },
     en: { name: 'Z.AI Coding Plan', description: 'Zhipu · OpenAI-compatible', badge: 'Coding' },
   },
   MiniMax: {
-    zh: { name: 'MiniMax', description: 'MiniMax · Anthropic 兼容', badge: 'API' },
+    'zh-CN': { name: 'MiniMax', description: 'MiniMax · Anthropic 兼容', badge: 'API' },
+    'zh-TW': { name: 'MiniMax', description: 'MiniMax · Anthropic 相容', badge: 'API' },
     en: { name: 'MiniMax', description: 'MiniMax · Anthropic-compatible', badge: 'API' },
   },
   'MiniMax-cn': {
-    zh: { name: 'MiniMax 中国站', description: 'MiniMax 中国站 · Anthropic 兼容', badge: 'API' },
+    'zh-CN': { name: 'MiniMax 中国站', description: 'MiniMax 中国站 · Anthropic 兼容', badge: 'API' },
+    'zh-TW': { name: 'MiniMax 中國站', description: 'MiniMax 中國站 · Anthropic 相容', badge: 'API' },
     en: { name: 'MiniMax China', description: 'MiniMax China · Anthropic-compatible', badge: 'API' },
   },
   siliconflow: {
-    zh: { name: 'SiliconFlow', description: '硅基流动多模型 API,支持精确模型 ID。', badge: '聚合' },
+    'zh-CN': { name: 'SiliconFlow', description: '硅基流动多模型 API,支持精确模型 ID。', badge: '聚合' },
+    'zh-TW': { name: 'SiliconFlow', description: '矽基流動多模型 API,支援精確模型 ID。', badge: '聚合' },
     en: { name: 'SiliconFlow', description: 'Hosted multi-model API with exact upstream model ids.', badge: 'Aggregator' },
   },
   anthropic: {
-    zh: { name: 'Anthropic', description: 'Anthropic 官方接入', badge: 'API' },
+    'zh-CN': { name: 'Anthropic', description: 'Anthropic 官方接入', badge: 'API' },
+    'zh-TW': { name: 'Anthropic', description: 'Anthropic 官方 API 連線', badge: 'API' },
     en: { name: 'Anthropic', description: 'Official Anthropic API access.', badge: 'API' },
   },
   openai: {
-    zh: { name: 'OpenAI', description: 'OpenAI 官方接入', badge: 'API' },
+    'zh-CN': { name: 'OpenAI', description: 'OpenAI 官方接入', badge: 'API' },
+    'zh-TW': { name: 'OpenAI', description: 'OpenAI 官方 API 連線', badge: 'API' },
     en: { name: 'OpenAI', description: 'Official OpenAI API access.', badge: 'API' },
   },
   google: {
-    zh: { name: 'Google Gemini', description: 'Google AI Studio 接入', badge: 'API' },
+    'zh-CN': { name: 'Google Gemini', description: 'Google AI Studio 接入', badge: 'API' },
+    'zh-TW': { name: 'Google Gemini', description: 'Google AI Studio API 連線', badge: 'API' },
     en: { name: 'Google Gemini', description: 'Google AI Studio API access.', badge: 'API' },
   },
   xai: {
-    zh: { name: 'xAI', description: 'xAI 官方接入,Grok 系列模型', badge: 'API' },
+    'zh-CN': { name: 'xAI', description: 'xAI 官方接入,Grok 系列模型', badge: 'API' },
+    'zh-TW': { name: 'xAI', description: 'xAI 官方 API 連線,支援 Grok 系列模型', badge: 'API' },
     en: { name: 'xAI', description: 'Official xAI API access for Grok models.', badge: 'API' },
   },
   zai: {
-    zh: { name: 'Z.AI', description: '智谱官方接入,GLM 系列模型', badge: 'API' },
+    'zh-CN': { name: 'Z.AI', description: '智谱官方接入,GLM 系列模型', badge: 'API' },
+    'zh-TW': { name: 'Z.AI', description: '智譜官方 API 連線,支援 GLM 系列模型', badge: 'API' },
     en: { name: 'Z.AI', description: 'Official Z.AI API access for GLM models.', badge: 'API' },
   },
   xiaomi: {
-    zh: { name: 'Xiaomi', description: '小米官方接入,MiMo 系列模型', badge: 'API' },
+    'zh-CN': { name: 'Xiaomi', description: '小米官方接入,MiMo 系列模型', badge: 'API' },
+    'zh-TW': { name: 'Xiaomi', description: '小米官方 API 連線,支援 MiMo 系列模型', badge: 'API' },
     en: { name: 'Xiaomi', description: 'Official Xiaomi API access for MiMo models.', badge: 'API' },
   },
   'xiaomi-token-plan-cn': {
-    zh: { name: 'Xiaomi Token Plan 中国', description: '小米 MiMo Token Plan 订阅 · 中国 · 编码工具', badge: 'Token' },
+    'zh-CN': { name: 'Xiaomi Token Plan 中国', description: '小米 MiMo Token Plan 订阅 · 中国 · 编码工具', badge: 'Token' },
+    'zh-TW': { name: 'Xiaomi Token Plan 中國', description: '小米 MiMo Token Plan 訂閱 · 中國 · 編碼工具', badge: 'Token' },
     en: { name: 'Xiaomi Token Plan (China)', description: 'Xiaomi MiMo Token Plan subscription (China) for coding tools.', badge: 'Token' },
   },
   'xiaomi-token-plan-sgp': {
-    zh: { name: 'Xiaomi Token Plan 新加坡', description: '小米 MiMo Token Plan 订阅 · 新加坡 · 编码工具', badge: 'Token' },
+    'zh-CN': { name: 'Xiaomi Token Plan 新加坡', description: '小米 MiMo Token Plan 订阅 · 新加坡 · 编码工具', badge: 'Token' },
+    'zh-TW': { name: 'Xiaomi Token Plan 新加坡', description: '小米 MiMo Token Plan 訂閱 · 新加坡 · 編碼工具', badge: 'Token' },
     en: { name: 'Xiaomi Token Plan (Singapore)', description: 'Xiaomi MiMo Token Plan subscription (Singapore) for coding tools.', badge: 'Token' },
   },
   'xiaomi-token-plan-ams': {
-    zh: { name: 'Xiaomi Token Plan 欧洲', description: '小米 MiMo Token Plan 订阅 · 欧洲 · 编码工具', badge: 'Token' },
+    'zh-CN': { name: 'Xiaomi Token Plan 欧洲', description: '小米 MiMo Token Plan 订阅 · 欧洲 · 编码工具', badge: 'Token' },
+    'zh-TW': { name: 'Xiaomi Token Plan 歐洲', description: '小米 MiMo Token Plan 訂閱 · 歐洲 · 編碼工具', badge: 'Token' },
     en: { name: 'Xiaomi Token Plan (Europe)', description: 'Xiaomi MiMo Token Plan subscription (Europe) for coding tools.', badge: 'Token' },
   },
   cerebras: {
-    zh: { name: 'Cerebras', description: '高速推理托管开源模型', badge: 'API' },
+    'zh-CN': { name: 'Cerebras', description: '高速推理托管开源模型', badge: 'API' },
+    'zh-TW': { name: 'Cerebras', description: '高速推理託管開源模型', badge: 'API' },
     en: { name: 'Cerebras', description: 'Fast hosted open-model inference.', badge: 'API' },
   },
   mistral: {
-    zh: { name: 'Mistral', description: 'Mistral 官方接入', badge: 'API' },
+    'zh-CN': { name: 'Mistral', description: 'Mistral 官方接入', badge: 'API' },
+    'zh-TW': { name: 'Mistral', description: 'Mistral 官方 API 連線', badge: 'API' },
     en: { name: 'Mistral', description: 'Official Mistral API access.', badge: 'API' },
   },
   togetherai: {
-    zh: { name: 'Together AI', description: '托管开源模型 API', badge: 'API' },
+    'zh-CN': { name: 'Together AI', description: '托管开源模型 API', badge: 'API' },
+    'zh-TW': { name: 'Together AI', description: '託管開源模型 API', badge: 'API' },
     en: { name: 'Together AI', description: 'Hosted open models over one API.', badge: 'API' },
   },
   ollama: {
-    zh: { name: 'Ollama', description: '本机运行 · 离线可用', badge: 'Local' },
+    'zh-CN': { name: 'Ollama', description: '本机运行 · 离线可用', badge: 'Local' },
+    'zh-TW': { name: 'Ollama', description: '本機執行 · 離線可用', badge: 'Local' },
     en: { name: 'Ollama', description: 'Runs locally · works offline', badge: 'Local' },
   },
   'lm-studio': {
-    zh: { name: 'LM Studio', description: '本机 LM Studio 服务 · 离线可用', badge: 'Local' },
+    'zh-CN': { name: 'LM Studio', description: '本机 LM Studio 服务 · 离线可用', badge: 'Local' },
+    'zh-TW': { name: 'LM Studio', description: '本機 LM Studio 服務 · 離線可用', badge: 'Local' },
     en: { name: 'LM Studio', description: 'Local models served by LM Studio.', badge: 'Local' },
   },
   localai: {
-    zh: { name: 'LocalAI', description: '本机 LocalAI 服务,可选密钥保护', badge: 'Local' },
+    'zh-CN': { name: 'LocalAI', description: '本机 LocalAI 服务,可选密钥保护', badge: 'Local' },
+    'zh-TW': { name: 'LocalAI', description: '本機 LocalAI 服務,可選金鑰保護', badge: 'Local' },
     en: { name: 'LocalAI', description: 'Local models served by LocalAI, with optional API key.', badge: 'Local' },
   },
   'openai-compatible': {
-    zh: { name: '自定义中转站(OpenAI Chat)', description: 'OpenAI Chat Completions 兼容中转站、代理服务或自部署网关。', badge: '中转' },
+    'zh-CN': { name: '自定义中转站(OpenAI Chat)', description: 'OpenAI Chat Completions 兼容中转站、代理服务或自部署网关。', badge: '中转' },
+    'zh-TW': { name: '自訂中轉站(OpenAI Chat)', description: 'OpenAI Chat Completions 相容中轉站、代理服務或自部署閘道器。', badge: '中轉' },
     en: { name: 'Custom relay (OpenAI Chat)', description: 'OpenAI Chat Completions-compatible relay, proxy, or self-hosted gateway.', badge: 'Relay' },
   },
   'openai-responses-compatible': {
-    zh: { name: '自定义中转站(OpenAI Responses)', description: 'OpenAI Responses API 兼容中转站、代理服务或自部署网关。', badge: 'Responses' },
+    'zh-CN': { name: '自定义中转站(OpenAI Responses)', description: 'OpenAI Responses API 兼容中转站、代理服务或自部署网关。', badge: 'Responses' },
+    'zh-TW': { name: '自訂中轉站(OpenAI Responses)', description: 'OpenAI Responses API 相容中轉站、代理服務或自部署閘道器。', badge: 'Responses' },
     en: { name: 'Custom relay (OpenAI Responses)', description: 'OpenAI Responses-compatible relay, proxy, or self-hosted gateway.', badge: 'Responses' },
   },
   'anthropic-compatible': {
-    zh: { name: '自定义中转站(Anthropic)', description: 'Anthropic Messages 兼容中转站、代理服务或自部署网关。', badge: 'Anthropic' },
+    'zh-CN': { name: '自定义中转站(Anthropic)', description: 'Anthropic Messages 兼容中转站、代理服务或自部署网关。', badge: 'Anthropic' },
+    'zh-TW': { name: '自訂中轉站(Anthropic)', description: 'Anthropic Messages 相容中轉站、代理服務或自部署閘道器。', badge: 'Anthropic' },
     en: { name: 'Custom relay (Anthropic)', description: 'Anthropic Messages-compatible relay, proxy, or self-hosted gateway.', badge: 'Anthropic' },
   },
   'fireworks-ai': {
-    zh: { name: 'Fireworks AI', description: 'Serverless 开源模型托管', badge: 'API' },
+    'zh-CN': { name: 'Fireworks AI', description: 'Serverless 开源模型托管', badge: 'API' },
+    'zh-TW': { name: 'Fireworks AI', description: 'Serverless 開源模型託管', badge: 'API' },
     en: { name: 'Fireworks AI', description: 'Serverless open models with exact Fireworks model paths.', badge: 'API' },
   },
   nvidia: {
-    zh: { name: 'NVIDIA', description: 'NVIDIA 官方托管模型接入', badge: 'API' },
+    'zh-CN': { name: 'NVIDIA', description: 'NVIDIA 官方托管模型接入', badge: 'API' },
+    'zh-TW': { name: 'NVIDIA', description: 'NVIDIA 官方託管模型服務', badge: 'API' },
     en: { name: 'NVIDIA', description: 'NVIDIA-hosted models API access.', badge: 'API' },
   },
   'tencent-tokenhub': {
-    zh: { name: 'Tencent TokenHub', description: '腾讯云 TokenHub 按量接入,混元等模型', badge: 'API' },
+    'zh-CN': { name: 'Tencent TokenHub', description: '腾讯云 TokenHub 按量接入,混元等模型', badge: 'API' },
+    'zh-TW': { name: 'Tencent TokenHub', description: '騰訊雲 TokenHub 按量計費模型服務,包含混元等模型', badge: 'API' },
     en: { name: 'Tencent TokenHub', description: 'Tencent Cloud TokenHub pay-as-you-go access.', badge: 'API' },
   },
   stepfun: {
-    zh: { name: 'StepFun 中国站', description: '阶跃星辰官方接入 · 中国站', badge: 'API' },
+    'zh-CN': { name: 'StepFun 中国站', description: '阶跃星辰官方接入 · 中国站', badge: 'API' },
+    'zh-TW': { name: 'StepFun 中國站', description: '階躍星辰官方 API 連線 · 中國站', badge: 'API' },
     en: { name: 'StepFun (China)', description: 'Official StepFun China API access.', badge: 'API' },
   },
   'tencent-coding-plan': {
-    zh: { name: 'Tencent Coding Plan', description: '腾讯云 Coding 套餐 · OpenAI 兼容', badge: 'Coding' },
+    'zh-CN': { name: 'Tencent Coding Plan', description: '腾讯云 Coding 套餐 · OpenAI 兼容', badge: 'Coding' },
+    'zh-TW': { name: 'Tencent Coding Plan', description: '騰訊雲 Coding 套餐 · OpenAI 相容', badge: 'Coding' },
     en: { name: 'Tencent Coding Plan', description: 'Tencent Cloud coding plan · OpenAI-compatible', badge: 'Coding' },
   },
   'stepfun-ai': {
-    zh: { name: 'StepFun 国际站', description: '阶跃星辰官方接入 · 国际站', badge: 'API' },
+    'zh-CN': { name: 'StepFun 国际站', description: '阶跃星辰官方接入 · 国际站', badge: 'API' },
+    'zh-TW': { name: 'StepFun 國際站', description: '階躍星辰官方 API 連線 · 國際站', badge: 'API' },
     en: { name: 'StepFun (Global)', description: 'Official StepFun Global API access.', badge: 'API' },
   },
   'volcengine-ark': {
-    zh: { name: '火山方舟', description: '火山引擎官方接入,豆包等模型', badge: 'API' },
+    'zh-CN': { name: '火山方舟', description: '火山引擎官方接入,豆包等模型', badge: 'API' },
+    'zh-TW': { name: '火山方舟', description: '火山引擎官方 API 連線,包含豆包等模型', badge: 'API' },
     en: { name: 'Volcengine Ark (China)', description: 'Volcengine Ark direct API access in China.', badge: 'API' },
   },
   'volcengine-coding-plan': {
-    zh: { name: '火山方舟 Coding Plan', description: '火山引擎 Coding 订阅 · OpenAI 兼容', badge: 'Coding' },
+    'zh-CN': { name: '火山方舟 Coding Plan', description: '火山引擎 Coding 订阅 · OpenAI 兼容', badge: 'Coding' },
+    'zh-TW': { name: '火山方舟 Coding Plan', description: '火山引擎 Coding 訂閱 · OpenAI 相容', badge: 'Coding' },
     en: { name: 'Volcengine Ark Coding Plan (China)', description: 'Volcengine Ark coding subscription · OpenAI-compatible', badge: 'Coding' },
   },
   'volcengine-agent-plan': {
-    zh: { name: '火山方舟 Agent Plan', description: '火山引擎 Agent 订阅 · Responses API', badge: 'Agent' },
+    'zh-CN': { name: '火山方舟 Agent Plan', description: '火山引擎 Agent 订阅 · Responses API', badge: 'Agent' },
+    'zh-TW': { name: '火山方舟 Agent Plan', description: '火山引擎 Agent 訂閱 · Responses API', badge: 'Agent' },
     en: { name: 'Volcengine Ark Agent Plan (China)', description: 'Volcengine Ark agent subscription · Responses API', badge: 'Agent' },
   },
   'tencent-token-plan': {
-    zh: { name: 'Tencent Token Plan', description: '腾讯云 Token 套餐,个人智能体与编码工具', badge: 'Token' },
+    'zh-CN': { name: 'Tencent Token Plan', description: '腾讯云 Token 套餐,个人智能体与编码工具', badge: 'Token' },
+    'zh-TW': { name: 'Tencent Token Plan', description: '騰訊雲 Token 套餐,個人智慧體與編碼工具', badge: 'Token' },
     en: { name: 'Tencent Token Plan', description: 'Tencent Cloud token plan for personal agents and coding tools.', badge: 'Token' },
   },
   'stepfun-step-plan': {
-    zh: { name: 'StepFun Step Plan 中国站', description: '阶跃星辰订阅套餐 · 中国站', badge: 'Plan' },
+    'zh-CN': { name: 'StepFun Step Plan 中国站', description: '阶跃星辰订阅套餐 · 中国站', badge: 'Plan' },
+    'zh-TW': { name: 'StepFun Step Plan 中國站', description: '階躍星辰訂閱套餐 · 中國站', badge: 'Plan' },
     en: { name: 'StepFun Step Plan (China)', description: 'StepFun China subscription for coding and agent tools.', badge: 'Plan' },
   },
   deepinfra: {
-    zh: { name: 'DeepInfra', description: '开源模型托管推理 · OpenAI 兼容', badge: 'API' },
+    'zh-CN': { name: 'DeepInfra', description: '开源模型托管推理 · OpenAI 兼容', badge: 'API' },
+    'zh-TW': { name: 'DeepInfra', description: '開源模型託管推理 · OpenAI 相容', badge: 'API' },
     en: { name: 'DeepInfra', description: 'Hosted open-model inference · OpenAI-compatible', badge: 'API' },
   },
   cohere: {
-    zh: { name: 'Cohere', description: 'Cohere 官方接入', badge: 'API' },
+    'zh-CN': { name: 'Cohere', description: 'Cohere 官方接入', badge: 'API' },
+    'zh-TW': { name: 'Cohere', description: 'Cohere 官方 API 連線', badge: 'API' },
     en: { name: 'Cohere', description: 'Official Cohere Chat API access.', badge: 'API' },
   },
   vercel: {
-    zh: { name: 'Vercel AI Gateway', description: '一个密钥接入多家托管模型', badge: '网关' },
+    'zh-CN': { name: 'Vercel AI Gateway', description: '一个密钥接入多家托管模型', badge: '网关' },
+    'zh-TW': { name: 'Vercel AI Gateway', description: '使用一組金鑰連線多家託管模型', badge: '閘道器' },
     en: { name: 'Vercel AI Gateway', description: 'One API key for hosted models with exact creator/model ids.', badge: 'Gateway' },
   },
   'stepfun-ai-step-plan': {
-    zh: { name: 'StepFun Step Plan 国际站', description: '阶跃星辰订阅套餐 · 国际站', badge: 'Plan' },
+    'zh-CN': { name: 'StepFun Step Plan 国际站', description: '阶跃星辰订阅套餐 · 国际站', badge: 'Plan' },
+    'zh-TW': { name: 'StepFun Step Plan 國際站', description: '階躍星辰訂閱套餐 · 國際站', badge: 'Plan' },
     en: { name: 'StepFun Step Plan (Global)', description: 'StepFun Global subscription for coding and agent tools.', badge: 'Plan' },
   },
   'cloudflare-workers-ai': {
-    zh: { name: 'Cloudflare Workers AI', description: 'Cloudflare 托管模型,账户级接入', badge: 'API' },
+    'zh-CN': { name: 'Cloudflare Workers AI', description: 'Cloudflare 托管模型,账户级接入', badge: 'API' },
+    'zh-TW': { name: 'Cloudflare Workers AI', description: 'Cloudflare 託管模型,使用帳號層級連線', badge: 'API' },
     en: { name: 'Cloudflare Workers AI', description: 'Cloudflare-hosted models over the account-scoped API.', badge: 'API' },
   },
   huggingface: {
-    zh: { name: 'Hugging Face', description: 'Inference Providers 路由,聚合多家托管模型', badge: '路由' },
+    'zh-CN': { name: 'Hugging Face', description: 'Inference Providers 路由,聚合多家托管模型', badge: '路由' },
+    'zh-TW': { name: 'Hugging Face', description: 'Inference Providers 路由,聚合多家託管模型', badge: '路由' },
     en: { name: 'Hugging Face', description: 'Inference Providers router across hosted models.', badge: 'Router' },
   },
   'ollama-cloud': {
-    zh: { name: 'Ollama Cloud', description: 'Ollama 官方云端托管模型', badge: 'API' },
+    'zh-CN': { name: 'Ollama Cloud', description: 'Ollama 官方云端托管模型', badge: 'API' },
+    'zh-TW': { name: 'Ollama Cloud', description: 'Ollama 官方雲端託管模型', badge: 'API' },
     en: { name: 'Ollama Cloud', description: 'Ollama-hosted cloud models over the official remote API.', badge: 'API' },
   },
   zenmux: {
-    zh: { name: 'ZenMux', description: '模型路由网关,一个密钥接入多家模型', badge: '网关' },
+    'zh-CN': { name: 'ZenMux', description: '模型路由网关,一个密钥接入多家模型', badge: '网关' },
+    'zh-TW': { name: 'ZenMux', description: '模型路由閘道器,使用一組金鑰連線多家模型', badge: '閘道器' },
     en: { name: 'ZenMux', description: 'One API key for routed models with exact creator/model ids.', badge: 'Gateway' },
   },
   opencode: {
-    zh: { name: 'OpenCode Zen', description: '面向编码智能体的按量模型精选', badge: 'Plan' },
+    'zh-CN': { name: 'OpenCode Zen', description: '面向编码智能体的按量模型精选', badge: 'Plan' },
+    'zh-TW': { name: 'OpenCode Zen', description: '面向編碼智慧體的按量模型精選', badge: 'Plan' },
     en: { name: 'OpenCode Zen', description: 'Curated pay-as-you-go models for coding agents.', badge: 'Plan' },
   },
   'opencode-go': {
-    zh: { name: 'OpenCode Go', description: '低价订阅制的开源编码模型精选', badge: 'Plan' },
+    'zh-CN': { name: 'OpenCode Go', description: '低价订阅制的开源编码模型精选', badge: 'Plan' },
+    'zh-TW': { name: 'OpenCode Go', description: '低價訂閱制的開源編碼模型精選', badge: 'Plan' },
     en: { name: 'OpenCode Go', description: 'Low-cost subscription to curated open coding models.', badge: 'Plan' },
   },
   'opencode-free': {
-    zh: { name: 'OpenCode Free', description: '免费匿名 OpenCode Zen 模型,无需密钥,按 IP 限速', badge: 'Free' },
+    'zh-CN': { name: 'OpenCode Free', description: '免费匿名 OpenCode Zen 模型,无需密钥,按 IP 限速', badge: 'Free' },
+    'zh-TW': { name: 'OpenCode Free', description: '免費匿名 OpenCode Zen 模型,無需金鑰,按 IP 限速', badge: 'Free' },
     en: { name: 'OpenCode Free', description: 'Free anonymous OpenCode Zen models — no API key, IP-limited.', badge: 'Free' },
   },
   groq: {
-    zh: { name: 'Groq', description: 'LPU 高速推理托管开源模型', badge: 'API' },
+    'zh-CN': { name: 'Groq', description: 'LPU 高速推理托管开源模型', badge: 'API' },
+    'zh-TW': { name: 'Groq', description: 'LPU 高速推理託管開源模型', badge: 'API' },
     en: { name: 'Groq', description: 'Ultra-fast LPU-hosted open models.', badge: 'API' },
   },
   openrouter: {
-    zh: { name: 'OpenRouter', description: '一个密钥接入各大模型厂商 · OpenAI 兼容', badge: '聚合' },
+    'zh-CN': { name: 'OpenRouter', description: '一个密钥接入各大模型厂商 · OpenAI 兼容', badge: '聚合' },
+    'zh-TW': { name: 'OpenRouter', description: '使用一組金鑰連線各大模型廠商 · OpenAI 相容', badge: '聚合' },
     en: { name: 'OpenRouter', description: 'One API key across all major model labs · OpenAI-compatible', badge: 'Aggregator' },
   },
   alibaba: {
-    zh: { name: 'Alibaba', description: '阿里云百炼接入,通义千问 Qwen 模型', badge: 'API' },
+    'zh-CN': { name: 'Alibaba', description: '阿里云百炼接入,通义千问 Qwen 模型', badge: 'API' },
+    'zh-TW': { name: 'Alibaba', description: '阿里雲百鍊 API 連線,支援通義千問 Qwen 模型', badge: 'API' },
     en: { name: 'Alibaba', description: 'Alibaba Cloud API access for Qwen models.', badge: 'API' },
   },
   'alibaba-cn': {
-    zh: { name: 'Alibaba 中国站', description: '阿里云百炼中国站接入,通义千问 Qwen 模型', badge: 'API' },
+    'zh-CN': { name: 'Alibaba 中国站', description: '阿里云百炼中国站接入,通义千问 Qwen 模型', badge: 'API' },
+    'zh-TW': { name: 'Alibaba 中國站', description: '阿里雲百鍊中國站 API 連線,支援通義千問 Qwen 模型', badge: 'API' },
     en: {
       name: 'Alibaba (China)',
       description: 'Alibaba Cloud China-platform API access for Qwen models.',
@@ -266,37 +319,45 @@ export const PROVIDER_DISPLAY_COPY = {
     },
   },
   'alibaba-coding-plan-cn': {
-    zh: { name: 'Alibaba Coding Plan 中国站', description: '阿里云百炼 Coding Plan 订阅 · 中国站', badge: 'Plan' },
+    'zh-CN': { name: 'Alibaba Coding Plan 中国站', description: '阿里云百炼 Coding Plan 订阅 · 中国站', badge: 'Plan' },
+    'zh-TW': { name: 'Alibaba Coding Plan 中國站', description: '阿里雲百鍊 Coding Plan 訂閱 · 中國站', badge: 'Plan' },
     en: { name: 'Alibaba Coding Plan (China)', description: 'Alibaba Cloud Model Studio Coding Plan for interactive coding tools · China.', badge: 'Plan' },
   },
   'alibaba-coding-plan': {
-    zh: { name: 'Alibaba Coding Plan 国际站', description: '阿里云百炼 Coding Plan 订阅 · 国际站', badge: 'Plan' },
+    'zh-CN': { name: 'Alibaba Coding Plan 国际站', description: '阿里云百炼 Coding Plan 订阅 · 国际站', badge: 'Plan' },
+    'zh-TW': { name: 'Alibaba Coding Plan 國際站', description: '阿里雲百鍊 Coding Plan 訂閱 · 國際站', badge: 'Plan' },
     en: { name: 'Alibaba Coding Plan', description: 'Alibaba Cloud Model Studio Coding Plan for interactive coding tools.', badge: 'Plan' },
   },
   'alibaba-token-plan-cn': {
-    zh: { name: 'Alibaba Token Plan(团队版)', description: '阿里云百炼 Token Plan 订阅,交互式智能体与编码工具 · 北京', badge: 'Token' },
+    'zh-CN': { name: 'Alibaba Token Plan(团队版)', description: '阿里云百炼 Token Plan 订阅,交互式智能体与编码工具 · 北京', badge: 'Token' },
+    'zh-TW': { name: 'Alibaba Token Plan(團隊版)', description: '阿里雲百鍊 Token Plan 訂閱,互動式智慧體與編碼工具 · 北京', badge: 'Token' },
     en: { name: 'Alibaba Token Plan (China)', description: 'Alibaba Cloud Model Studio Token Plan for interactive agents and coding tools, Beijing region.', badge: 'Token' },
   },
   'alibaba-token-plan': {
-    zh: { name: 'Alibaba Token Plan(团队版)', description: '阿里云百炼 Token Plan 订阅,交互式智能体与编码工具 · 新加坡', badge: 'Token' },
+    'zh-CN': { name: 'Alibaba Token Plan(团队版)', description: '阿里云百炼 Token Plan 订阅,交互式智能体与编码工具 · 新加坡', badge: 'Token' },
+    'zh-TW': { name: 'Alibaba Token Plan(團隊版)', description: '阿里雲百鍊 Token Plan 訂閱,互動式智慧體與編碼工具 · 新加坡', badge: 'Token' },
     en: { name: 'Alibaba Token Plan', description: 'Alibaba Cloud Model Studio Token Plan for interactive agents and coding tools, Singapore region.', badge: 'Token' },
   },
   // OAuth account providers (not in CATALOG_PROVIDER_TYPES; shown in the
   // accounts section and on connection rows).
   'github-copilot': {
-    zh: { name: 'GitHub Copilot', description: 'GitHub Copilot 订阅接入,复用本机 GitHub 登录', badge: 'Account' },
+    'zh-CN': { name: 'GitHub Copilot', description: 'GitHub Copilot 订阅接入,复用本机 GitHub 登录', badge: 'Account' },
+    'zh-TW': { name: 'GitHub Copilot', description: '透過 GitHub Copilot 訂閱連線,沿用本機 GitHub 登入', badge: 'Account' },
     en: { name: 'GitHub Copilot', description: 'GitHub Copilot subscription access using an existing GitHub login.', badge: 'Account' },
   },
   'claude-subscription': {
-    zh: { name: 'Claude Subscription', description: 'Claude Pro / Max 订阅账号登录;登录后自动成为可用模型连接。' },
+    'zh-CN': { name: 'Claude Subscription', description: 'Claude Pro / Max 订阅账号登录;登录后自动成为可用模型连接。' },
+    'zh-TW': { name: 'Claude Subscription', description: 'Claude Pro / Max 訂閱帳號登入;登入後自動成為可用模型連線。' },
     en: { name: 'Claude Subscription', description: 'Sign in with a Claude Pro / Max subscription; it becomes an available model connection once signed in.' },
   },
   'openai-codex': {
-    zh: { name: 'OpenAI OAuth', description: 'ChatGPT / Codex 账号登录;登录后自动成为可用模型连接。' },
+    'zh-CN': { name: 'OpenAI OAuth', description: 'ChatGPT / Codex 账号登录;登录后自动成为可用模型连接。' },
+    'zh-TW': { name: 'OpenAI OAuth', description: 'ChatGPT / Codex 帳號登入;登入後自動成為可用模型連線。' },
     en: { name: 'OpenAI OAuth', description: 'Sign in with a ChatGPT / Codex account; it becomes an available model connection once signed in.' },
   },
   'xai-oauth': {
-    zh: { name: 'xAI OAuth', description: '使用 SuperGrok 或 X Premium 账号登录。', badge: 'Account' },
+    'zh-CN': { name: 'xAI OAuth', description: '使用 SuperGrok 或 X Premium 账号登录。', badge: 'Account' },
+    'zh-TW': { name: 'xAI OAuth', description: '使用 SuperGrok 或 X Premium 帳號登入。', badge: 'Account' },
     en: { name: 'xAI OAuth', description: 'Sign in with SuperGrok or X Premium.', badge: 'Account' },
   },
 } satisfies Record<ProviderType, UiCatalog<ProviderCopy>>;
diff --git a/apps/desktop/src/renderer/settings/provider-oauth-section.tsx b/apps/desktop/src/renderer/settings/provider-oauth-section.tsx
index a5c16a7924..3f144ded6b 100644
--- a/apps/desktop/src/renderer/settings/provider-oauth-section.tsx
+++ b/apps/desktop/src/renderer/settings/provider-oauth-section.tsx
@@ -29,6 +29,7 @@ import {
 } from '@maka/ui';
 import {
   getProviderSettingsCopy,
+  subscriptionResultMessage,
   type ConnectionOAuthProviderBridge,
   type ConnectionsBridge,
   type ProviderSettingsCopy,
@@ -36,7 +37,6 @@ import {
 import {
   useOAuthLoginFlow,
   subscriptionActionErrorMessage,
-  subscriptionResultMessage,
   type OAuthAuthorizationFlowBridge,
   type OAuthConnectionIdentity,
   type SubscriptionSnapshot,
diff --git a/apps/desktop/src/renderer/settings/providers-panel.tsx b/apps/desktop/src/renderer/settings/providers-panel.tsx
index 19f02b68a0..239125b90c 100644
--- a/apps/desktop/src/renderer/settings/providers-panel.tsx
+++ b/apps/desktop/src/renderer/settings/providers-panel.tsx
@@ -40,6 +40,7 @@ import {
   type ProjectedLlmConnection,
   type ProviderType,
 } from '@maka/core/llm-connections';
+import type { UiLocale } from '@maka/core/ui-locale';
 import { dotForStatus, useMountedRef, useUiLocale } from '@maka/ui';
 import { settingsActionErrorMessage } from './settings-error-copy';
 import { connectionChipStatus } from './provider-connection-status';
@@ -645,7 +646,7 @@ function ProvidersPanelContent({ bridge, apiKeyOnboardingBridge, initialPage = '
 }
 
 /** Provider · models · default model — the row's second line, and the detail's subtitle. */
-function connectionSubtitle(connection: IdentifiedLlmConnection, locale: 'zh' | 'en'): string {
+function connectionSubtitle(connection: IdentifiedLlmConnection, locale: UiLocale): string {
   const copy = getProviderSettingsCopy(locale).panel;
   const providerName = providerDisplay(connection.providerType, locale).name;
   const enabledCount = connectionEnabledModelIds(connection).length;
diff --git a/apps/desktop/src/renderer/settings/runtime-host-management-dialog.tsx b/apps/desktop/src/renderer/settings/runtime-host-management-dialog.tsx
index 14e8316f7d..586eb442ad 100644
--- a/apps/desktop/src/renderer/settings/runtime-host-management-dialog.tsx
+++ b/apps/desktop/src/renderer/settings/runtime-host-management-dialog.tsx
@@ -155,6 +155,8 @@ export function RuntimeHostManagementDialog(props: {
     useState(createWebRtcStunPolicyDraft);
   const nextDirectoryRootId = useRef(1);
   const logsRef = useRef<HTMLPreElement>(null);
+  const localizedError = (message: string): string =>
+    settingsActionErrorMessage(new Error(message), locale);
 
   const target = props.target;
   useEffect(() => {
@@ -188,7 +190,7 @@ export function RuntimeHostManagementDialog(props: {
           reconcileDirectoryPolicy(response.service);
           shouldLoadUpdatePolicy = response.service.state !== 'not_installed';
         }
-        else if (response.kind === 'error') setError(response.error.message);
+        else if (response.kind === 'error') setError(localizedError(response.error.message));
         else setUninstalledRoot(response.retainedStateRoot);
       } catch (failure) {
         if (!disposed) setError(settingsActionErrorMessage(failure, locale));
@@ -255,9 +257,10 @@ export function RuntimeHostManagementDialog(props: {
           setConfirmation({ kind: 'restart' });
           return;
         }
+        const message = settingsActionErrorMessage(new Error(response.error.message), locale);
         setUpdatePolicy(undefined);
-        setError(response.error.message);
-        toast.error(copy.managementActionFailed, response.error.message);
+        setError(message);
+        toast.error(copy.managementActionFailed, message);
         return;
       }
       if (response.kind === 'uninstalled') {
@@ -374,9 +377,10 @@ export function RuntimeHostManagementDialog(props: {
         allowInterruptActiveTasks,
       );
       if (response.kind === 'error') {
+        const message = localizedError(response.error.message);
         setUpdatePolicy(undefined);
-        setError(response.error.message);
-        toast.error(copy.managementActionFailed, response.error.message);
+        setError(message);
+        toast.error(copy.managementActionFailed, message);
         return;
       }
       if (response.kind === 'uninstalled') {
@@ -461,8 +465,9 @@ export function RuntimeHostManagementDialog(props: {
         allowInterruptActiveTasks,
       );
       if (response.kind === 'error') {
-        setError(response.error.message);
-        toast.error(copy.managementActionFailed, response.error.message);
+        const message = localizedError(response.error.message);
+        setError(message);
+        toast.error(copy.managementActionFailed, message);
         return;
       }
       if (response.kind === 'uninstalled' || response.action !== 'configure') {
@@ -544,9 +549,10 @@ export function RuntimeHostManagementDialog(props: {
     try {
       const response = await window.maka.runtimeHostManagement.reconcileUpdate(target.id);
       if (response.kind === 'error') {
+        const message = localizedError(response.error.message);
         setUpdatePolicy(undefined);
-        setUpdatePolicyError(response.error.message);
-        toast.error(copy.managementActionFailed, response.error.message);
+        setUpdatePolicyError(message);
+        toast.error(copy.managementActionFailed, message);
         return;
       }
       setLastUpdateOutcome(response.reconciliation);
@@ -586,9 +592,9 @@ export function RuntimeHostManagementDialog(props: {
   function applyReconnectWarning(
     reconnectError: { readonly message: string } | undefined,
   ): void {
-    setReconnectWarning(reconnectError?.message);
+    setReconnectWarning(reconnectError ? localizedError(reconnectError.message) : undefined);
     if (reconnectError) {
-      toast.warning(copy.managementReconnectFailed, reconnectError.message);
+      toast.warning(copy.managementReconnectFailed, localizedError(reconnectError.message));
     }
   }
 
diff --git a/apps/desktop/src/renderer/settings/runtime-host-profiles-section.tsx b/apps/desktop/src/renderer/settings/runtime-host-profiles-section.tsx
index 73d8d2161e..49fbb8ac3f 100644
--- a/apps/desktop/src/renderer/settings/runtime-host-profiles-section.tsx
+++ b/apps/desktop/src/renderer/settings/runtime-host-profiles-section.tsx
@@ -59,6 +59,7 @@ import {
 } from './runtime-host-management-dialog.js';
 import {
   PeerMeshPeerIdButton,
+  getRuntimeHostPeerMeshCopy,
   RuntimeHostAddComputerMenu,
   RuntimeHostConnectionCodeDialog,
   RuntimeHostPairingRecoveryButton,
@@ -92,6 +93,7 @@ export function RuntimeHostProfilesSection(props: {
 }) {
   const locale = useUiLocale();
   const copy = getSettingsProjectsCopy(locale).runtimeHost;
+  const peerMeshCopy = getRuntimeHostPeerMeshCopy(locale);
   const pairingActionCopy: RuntimeHostPairingActionCopy = {
     retry: copy.resolvePairingRecovery,
     retryFailed: copy.resolvePairingRecoveryFailed,
@@ -195,7 +197,7 @@ export function RuntimeHostProfilesSection(props: {
       if (result.kind === "unavailable") {
         toast.error(
           copy.selectFailed,
-          result.message,
+          settingsActionErrorMessage(result.message, locale),
           undefined,
           { profileId },
         );
@@ -668,11 +670,9 @@ export function RuntimeHostProfilesSection(props: {
                               <PeerMeshPeerIdButton
                                 peerId={profile.transport.reachability.lease.peerId}
                                 displayValue={abbreviatePeerId(profile.transport.reachability.lease.peerId)}
-                                copyLabel={locale.startsWith('zh')
-                                  ? `复制完整 Peer ID:${profile.transport.reachability.lease.peerId}`
-                                  : `Copy full Peer ID: ${profile.transport.reachability.lease.peerId}`}
-                                copiedTitle={locale.startsWith('zh') ? 'Peer ID 已复制' : 'Peer ID copied'}
-                                failedTitle={locale.startsWith('zh') ? '无法复制 Peer ID' : 'Could not copy Peer ID'}
+                                copyLabel={peerMeshCopy.copyPeerId(profile.transport.reachability.lease.peerId)}
+                                copiedTitle={peerMeshCopy.peerIdCopied}
+                                failedTitle={peerMeshCopy.peerIdCopyFailed}
                                 errorMessage={(error) => settingsActionErrorMessage(error, locale)}
                                 className="settingsRuntimeHostPeerId"
                               />
@@ -695,11 +695,13 @@ export function RuntimeHostProfilesSection(props: {
                         <Badge variant="neutral" label={copy.unavailable} />
                       ) : null}
                       {entry.peerPath ? (
-                        <Tooltip content={peerPathDetail(entry.peerPath, locale)}>
+                        <Tooltip content={peerPathDetail(entry.peerPath, peerMeshCopy)}>
                           <span>
                             <Badge
                               variant="neutral"
-                              label={peerPathLabel(entry.peerPath, locale)}
+                              label={entry.peerPath.kind === 'direct'
+                                ? peerMeshCopy.peerPathDirect
+                                : peerMeshCopy.peerPathTransit}
                             />
                           </span>
                         </Tooltip>
@@ -842,12 +844,10 @@ export function RuntimeHostProfilesSection(props: {
 
 function peerPathDetail(
   path: RuntimeHostPeerConnectionPath,
-  locale: string,
+  copy: ReturnType<typeof getRuntimeHostPeerMeshCopy>,
 ): string {
   if (path.kind === 'transit') {
-    return locale.startsWith('zh')
-      ? `成员转发 · ${abbreviatePeerId(path.relayPeerId)}`
-      : `Member transit · ${abbreviatePeerId(path.relayPeerId)}`;
+    return `${copy.peerPathTransit} · ${abbreviatePeerId(path.relayPeerId)}`;
   }
   const transport = path.transport === 'webrtc'
     ? 'WebRTC'
@@ -855,24 +855,10 @@ function peerPathDetail(
       ? 'QUIC'
       : path.transport === 'tcp'
         ? 'TCP'
-        : locale.startsWith('zh')
-          ? '其他'
-          : 'Other';
-  return `${locale.startsWith('zh') ? '直连' : 'Direct'} · ${transport}`;
+        : copy.peerPathOther;
+  return `${copy.peerPathDirect} · ${transport}`;
 }
 
-function peerPathLabel(
-  path: RuntimeHostPeerConnectionPath,
-  locale: string,
-): string {
-  if (path.kind === 'transit') {
-    return locale.startsWith('zh') ? '成员转发' : 'Member transit';
-  }
-  if (path.transport === 'webrtc') return 'WebRTC';
-  if (path.transport === 'quic') return 'QUIC';
-  if (path.transport === 'tcp') return 'TCP';
-  return locale.startsWith('zh') ? '直连' : 'Direct';
-}
 
 function abbreviatePeerId(peerId: string): string {
   return peerId.length <= 20 ? peerId : `${peerId.slice(0, 10)}…${peerId.slice(-6)}`;
diff --git a/apps/desktop/src/renderer/settings/settings-error-copy.ts b/apps/desktop/src/renderer/settings/settings-error-copy.ts
index 5456695179..8e759e7b8f 100644
--- a/apps/desktop/src/renderer/settings/settings-error-copy.ts
+++ b/apps/desktop/src/renderer/settings/settings-error-copy.ts
@@ -17,19 +17,20 @@
  * under the License.
  */
 
-import { generalizedErrorMessageChinese } from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
 import { type UiLocale } from '@maka/core/ui-locale';
 import { redactSecrets } from '@maka/ui';
 import { getSettingsSharedCopy } from '../locales/settings-shared-copy.js';
 
-export function settingsActionErrorMessage(error: unknown, locale: UiLocale = 'zh'): string {
+export function settingsActionErrorMessage(error: unknown, locale: UiLocale = 'zh-CN'): string {
   const raw = error instanceof Error
     ? error.message
     : typeof error === 'string'
       ? error
       : '';
-  const classified = locale === 'zh' ? generalizedErrorMessageChinese(new Error(raw), '') : '';
+  const classified = generalizedErrorMessageForLocale(new Error(raw), '', locale);
   if (classified) return classified;
   const redacted = redactSecrets(raw).trim();
-  return redacted || getSettingsSharedCopy(locale).unknownError;
+  if (locale === 'zh-CN' && redacted && /[\u4E00-\u9FFF]/.test(redacted)) return redacted;
+  return getSettingsSharedCopy(locale).unknownError;
 }
diff --git a/apps/desktop/src/renderer/settings/use-memory-settings-controller.ts b/apps/desktop/src/renderer/settings/use-memory-settings-controller.ts
index 40bec015cd..2bfa9a4732 100644
--- a/apps/desktop/src/renderer/settings/use-memory-settings-controller.ts
+++ b/apps/desktop/src/renderer/settings/use-memory-settings-controller.ts
@@ -689,5 +689,5 @@ export function useMemoryDocumentController(props: MemoryDocumentControllerProps
 }
 
 function memoryResultMessage(message: string, locale: UiLocale, fallback: string): string {
-  return locale === 'zh' || !/[\u3400-\u9fff]/u.test(message) ? message : fallback;
+  return locale === 'zh-CN' || !/[\u3400-\u9fff]/u.test(message) ? message : fallback;
 }
diff --git a/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts b/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts
index fbfdc16031..f237429d4d 100644
--- a/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts
+++ b/apps/desktop/src/renderer/settings/use-oauth-login-flow.ts
@@ -18,7 +18,6 @@
  */
 
 import { useEffect, useRef, useState } from 'react';
-import { generalizedErrorMessage, generalizedErrorMessageChinese, redactSecrets } from '@maka/core/redaction';
 import { type UiLocale } from '@maka/core/ui-locale';
 import {
   useMountedRef,
@@ -26,7 +25,7 @@ import {
   useUiLocale,
 } from '@maka/ui';
 import { createOneShotActionGuard, teardownPendingAuthorization } from './oauth-login-flow-guard';
-import { getProviderSettingsCopy } from '../features/connection-settings';
+import { getProviderSettingsCopy, subscriptionResultMessage } from '../features/connection-settings';
 import { useRuntimeHostSettingsErrorReporter } from './runtime-host-settings-target.js';
 
 // Shared browser-assisted OAuth login-flow controller (device-code polling).
@@ -329,7 +328,7 @@ export function useOAuthLoginFlow(params: OAuthLoginFlowParams): OAuthLoginFlowC
   };
 }
 
-export function subscriptionActionErrorMessage(error: unknown, locale: UiLocale = 'zh'): string {
+export function subscriptionActionErrorMessage(error: unknown, locale: UiLocale = 'zh-CN'): string {
   const message = error instanceof Error
     ? error.message
     : typeof error === 'string'
@@ -337,35 +336,3 @@ export function subscriptionActionErrorMessage(error: unknown, locale: UiLocale
       : '';
   return subscriptionResultMessage(message, getProviderSettingsCopy(locale).oauthFlow.serviceUnavailable, locale);
 }
-
-export function subscriptionResultMessage(message: string | undefined, fallback: string, locale: UiLocale = 'zh', reason?: string): string {
-  const raw = redactSecrets(message ?? '').trim();
-  // The Host refuses an enrollment this install has not opted into and says so
-  // with a typed reason. Read the reason, not the English message: a reworded
-  // string or an added locale must not silently disable this branch. The
-  // message match stays only as a fallback for callers without a typed reason.
-  if (reason === 'experimental_disabled' || /enrollment is disabled for this provider/i.test(raw)) {
-    return locale === 'zh'
-      ? '本机未启用该账号登录方式;可改用导入兼容凭据,或由管理员启用后重试。'
-      : 'This sign-in is not enabled on this install. Import a compatible credential instead, or ask an operator to enable it.';
-  }
-  if (!raw) return fallback;
-  // Host conflict / supersede copy before the coarse keyword classifier turns
-  // "authorization" into a generic 鉴权失败 that does not tell the user what to do.
-  // This is error-path copy: do not claim a new login already started.
-  if (/already in progress|superseded by a new attempt/i.test(raw)) {
-    return locale === 'zh'
-      ? '上一轮浏览器登录仍在进行或已切换,请再点一次登录,或稍后再试。'
-      : 'A previous browser login is still running or was superseded. Try logging in again shortly.';
-  }
-  if (/did not present OAuth|no matching OAuth presentation/i.test(raw)) {
-    return locale === 'zh'
-      ? '无法打开系统浏览器完成登录,请检查是否拦截了弹窗后重试。'
-      : 'Could not open the system browser for login. Check popup blockers and try again.';
-  }
-  const classified = locale === 'zh'
-    ? generalizedErrorMessageChinese(new Error(raw), '')
-    : generalizedErrorMessage(new Error(raw), '');
-  if (classified) return classified;
-  return locale === 'zh' || !/[\u4e00-\u9fff]/.test(raw) ? raw : fallback;
-}
diff --git a/apps/desktop/src/renderer/turn-footer-actions.ts b/apps/desktop/src/renderer/turn-footer-actions.ts
index 3d60ef76f5..ffc02bf7ea 100644
--- a/apps/desktop/src/renderer/turn-footer-actions.ts
+++ b/apps/desktop/src/renderer/turn-footer-actions.ts
@@ -113,7 +113,7 @@ export interface TurnFooterContext {
  */
 export function deriveTurnFooterActions(input: TurnFooterContext): TurnFooterAction[] {
   const { status, hasContent, alreadyRegenerated, pendingActions, metaSummary } = input;
-  const copyText = getDesktopConversationCopy(input.locale ?? 'zh').footer;
+  const copyText = getDesktopConversationCopy(input.locale ?? 'zh-CN').footer;
   const actionLabel = copyText.labels;
   const isPending = (id: TurnFooterActionId) => pendingActions?.has(id) ?? false;
   const PENDING_TOOLTIP = copyText.pending;
diff --git a/apps/desktop/src/renderer/use-onboarding-snapshot.ts b/apps/desktop/src/renderer/use-onboarding-snapshot.ts
index da694b9cd9..8e00c44f20 100644
--- a/apps/desktop/src/renderer/use-onboarding-snapshot.ts
+++ b/apps/desktop/src/renderer/use-onboarding-snapshot.ts
@@ -33,7 +33,7 @@
  */
 
 import { useCallback, useEffect, useRef, useState } from 'react';
-import { generalizedErrorMessage, generalizedErrorMessageChinese } from '@maka/core/redaction';
+import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
 import { type LlmConnection } from '@maka/core/llm-connections';
 import { type SessionSummary } from '@maka/core/session';
 import { type UiLocale } from '@maka/core/ui-locale';
@@ -229,7 +229,7 @@ export function createOnboardingSnapshotPoller(
 
 export function onboardingSnapshotErrorMessage(error: unknown, locale: UiLocale): string {
   const fallback = getOnboardingCopy(locale).snapshotErrorFallback;
-  return locale === 'zh' ? generalizedErrorMessageChinese(error, fallback) : generalizedErrorMessage(error, fallback);
+  return generalizedErrorMessageForLocale(error, fallback, locale);
 }
 
 /**
diff --git a/apps/desktop/src/renderer/workhub-surface.tsx b/apps/desktop/src/renderer/workhub-surface.tsx
index 8f9bf0532a..8a7cf93ad2 100644
--- a/apps/desktop/src/renderer/workhub-surface.tsx
+++ b/apps/desktop/src/renderer/workhub-surface.tsx
@@ -819,13 +819,17 @@ function SubmittedWorkView(props: {
 }
 
 export function workHubAmbiguousCommandPrompt(locale: UiLocale): string {
-  return locale === 'zh'
-    ? '没有开始新工作。如果需要我直接执行,请给出明确指令,例如“修复登录”。'
-    : 'I did not start new work. If you want me to do it, give a direct instruction, for example “Fix login”.';
+  if (locale === 'zh-CN') {
+    return '没有开始新工作。如果需要我直接执行,请给出明确指令,例如“修复登录”。';
+  }
+  if (locale === 'zh-TW') {
+    return '沒有開始新工作。如果需要我直接執行,請給出明確指令,例如「修復登入」。';
+  }
+  return 'I did not start new work. If you want me to do it, give a direct instruction, for example “Fix login”.';
 }
 
 function workHubCopy(locale: UiLocale) {
-  if (locale === 'zh') {
+  if (locale === 'zh-CN') {
     return {
       locale,
       subtitle: '在一个入口里继续、创建和查看普通 Session',
@@ -886,6 +890,68 @@ function workHubCopy(locale: UiLocale) {
       turnStates: { running: '进行中', completed: '已完成', aborted: '已中止', failed: '失败' },
     } as const;
   }
+  if (locale === 'zh-TW') {
+    return {
+      locale,
+      subtitle: '在一個入口繼續、建立和檢視一般 Session',
+      emptyTitle: '從這裡繼續所有工作',
+      emptyBody: (count: number) => count > 0
+        ? `WorkHub 會根據現有 ${count} 個 Session 判斷目標;不確定時會先詢問你。`
+        : '提出一個明確目標,WorkHub 會建立一般 Session 並將結果帶回這裡。',
+      workCount: (count: number) => `${count} 項工作`, clarification: '選擇工作',
+      chooseWork: '這則輸入可能與多項工作有關,請選擇目標:',
+      confirmCommand: workHubAmbiguousCommandPrompt(locale),
+      discussionStayed: '這則內容暫時保留在 WorkHub,沒有建立或變更 Session。',
+      discussionHint: '提出明確的執行目標後,我會將它交給對應的 Session。',
+      answering: '正在回答…',
+      choseWork: (name: string) => `選擇「${name}」`,
+      sentTo: '已交給:', createdWork: '已建立新工作:', accepted: '已接收', sessionFallback: '一般 Session',
+      stopTargetRequired: '請明確說出要停止的工作名稱,例如「停止 支付任務」。',
+      stopTargetAmbiguous: '這個名稱對應多項工作;請開啟具體的 Session 停止對應委派。',
+      stopTargetUnavailable: '這項工作現在沒有可以停止的單一 WorkHub 委派;請開啟該 Session 檢視。',
+      waitingForDecision: '這項工作正在等待你的決定。',
+      requestNotSent: '新請求尚未傳送;處理原 Session 中的互動後可以再次傳送。',
+      routing: '正在判斷應該交給哪個 Session…', loadFailed: '無法讀取現有工作。',
+      loading: '正在讀取現有工作…',
+      preparing: '正在準備 WorkHub…', unavailable: '暫時無法使用',
+      coordinationFailedTitle: 'WorkHub 暫時無法啟動',
+      coordinationFailedBody: '請檢查目前 Runtime Host 的預設模型設定,然後重試。',
+      retry: '重試',
+      stoppingWork: '正在請求停止:', stopping: '正在處理', stopRecorded: '結果已記錄',
+      openSessionToStop: '這個 Turn 不由該委派獨佔;請開啟 Session 處理',
+      stopOutcomes: {
+        cancelled_pending: '已取消尚未開始的工作:',
+        stop_delivered: '已向執行中的工作發出停止請求:',
+        already_terminal: '這項工作已經結束:',
+        not_owned: '未停止共享或使用者擁有的 Turn:',
+      },
+      submitFailures: {
+        candidates_changed: '工作清單已變更,請重新傳送以使用最新目標。',
+        linked_correction_unavailable: '跨 Session 更正將於持久委派關聯完成後開放;請先開啟原 Session 並停止目前工作。',
+        target_waiting: '目標 Session 正在等待你的處理;請先開啟並完成該互動。',
+        action_changed: '這次操作已變更,請重新傳送。',
+        delivery_failed: '輸入未能送達,請重試。',
+      },
+      scrollToBottom: '捲動到底部', archived: '已封存',
+      states: { active: '使用中', running: '進行中', waiting_for_user: '等待你', blocked: '受阻', aborted: '已中止' },
+      delegationStates: {
+        accepted: '已接收',
+        running: '進行中',
+        waiting_for_user: '等待你',
+        completed: '已完成',
+        failed: '失敗',
+        aborted: '已中止',
+        recovering: '正在恢復',
+      },
+      assignmentLinkStates: {
+        active: (execution: string) => `關聯有效 · ${execution}`,
+        superseded: '已被更正',
+        aborted: '更正已中止',
+        stopped: '已停止關聯',
+      },
+      turnStates: { running: '進行中', completed: '已完成', aborted: '已中止', failed: '失敗' },
+    } as const;
+  }
   return {
     locale,
     subtitle: 'Continue, create, and review ordinary Sessions from one place',
diff --git a/apps/desktop/stories/agent-graph-panel.stories.tsx b/apps/desktop/stories/agent-graph-panel.stories.tsx
index f43088dd1e..cb60e7647c 100644
--- a/apps/desktop/stories/agent-graph-panel.stories.tsx
+++ b/apps/desktop/stories/agent-graph-panel.stories.tsx
@@ -39,7 +39,7 @@ import { withScopedMakaBridge } from './maka-bridge';
 
 const ROOT_SESSION_ID = 'session-graph';
 const GRAPH_ID = 'graph-storybook';
-const LOCALE = 'zh';
+const LOCALE = 'zh-CN';
 const copy = getAgentGraphPanelCopy(LOCALE);
 
 const meta = {
diff --git a/apps/desktop/stories/app-shell.stories.tsx b/apps/desktop/stories/app-shell.stories.tsx
index 3f61524c7e..66c84b7cdf 100644
--- a/apps/desktop/stories/app-shell.stories.tsx
+++ b/apps/desktop/stories/app-shell.stories.tsx
@@ -370,7 +370,7 @@ function ComposedShell(props: {
     deriveAppShellTurnPresentation(turns, {
       activeId: active?.id,
       pendingTurnActions: new Set<string>(),
-      uiLocale: 'zh',
+      uiLocale: 'zh-CN',
     });
   const projectGroups: SessionGroup[] = catalogProjects.map((item) => ({
     id: `project:${item.id}`,
diff --git a/apps/desktop/stories/error-boundary.stories.tsx b/apps/desktop/stories/error-boundary.stories.tsx
index 3ca04fc13d..beb79f6ba8 100644
--- a/apps/desktop/stories/error-boundary.stories.tsx
+++ b/apps/desktop/stories/error-boundary.stories.tsx
@@ -38,7 +38,8 @@ const onCopyReport = fn();
 const onReset = fn();
 const onReload = fn();
 
-const resolveLocale = (globals: Record<string, unknown>) => (globals.locale === 'en' ? 'en' : 'zh');
+const resolveLocale = (globals: Record<string, unknown>) =>
+  globals.locale === 'en' ? 'en' : globals.locale === 'zh-TW' ? 'zh-TW' : 'zh-CN';
 
 function fallback(copyState: ErrorBoundaryCopyState) {
   return (_args: unknown, { globals }: { globals: Record<string, unknown> }) => (
diff --git a/apps/desktop/stories/settings/settings-pages.stories.tsx b/apps/desktop/stories/settings/settings-pages.stories.tsx
index e3be3bb49e..032e326f80 100644
--- a/apps/desktop/stories/settings/settings-pages.stories.tsx
+++ b/apps/desktop/stories/settings/settings-pages.stories.tsx
@@ -85,7 +85,7 @@ import { getUsageSettingsCopy } from '../../src/renderer/locales/settings-usage-
  * function, so CI could not tell us. A story that drives the UI by its visible
  * text has to source that text where the UI does.
  */
-const DAILY_REVIEW_DEFAULT_MODEL_LABEL = getDailyReviewSettingsCopy('zh').defaultModel;
+const DAILY_REVIEW_DEFAULT_MODEL_LABEL = getDailyReviewSettingsCopy('zh-CN').defaultModel;
 /** A 1×1 transparent PNG: the picker needs a valid data URL, not real art. */
 const STORY_ICON_PREVIEW =
   'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
@@ -1852,7 +1852,7 @@ export const GeneralCachedRevalidation: Story = {
     await expect(
       canvas.getByRole('switch', { name: '完成时发送系统通知' }),
     ).toBeEnabled();
-    await expect(canvas.getByRole('radio', { name: '中文' })).toBeEnabled();
+    await expect(canvas.getByRole('combobox', { name: '界面语言' })).toBeEnabled();
     const mixedBoundary = canvasElement.querySelector<HTMLElement>(
       '.settingsRuntimeHostInteractionBoundary',
     );
@@ -1909,7 +1909,7 @@ export const GeneralHostGenerationRevalidation: Story = {
     await expect(
       canvas.getByRole('switch', { name: '完成时发送系统通知' }),
     ).toBeEnabled();
-    await expect(canvas.getByRole('radio', { name: '中文' })).toBeEnabled();
+    await expect(canvas.getByRole('combobox', { name: '界面语言' })).toBeEnabled();
     const mixedBoundary = canvasElement.querySelector<HTMLElement>(
       '.settingsRuntimeHostInteractionBoundary',
     );
@@ -1974,7 +1974,7 @@ export const GeneralBackgroundHostReconnectThenSelect: Story = {
     await expect(
       canvas.getByRole('switch', { name: '完成时发送系统通知' }),
     ).toBeEnabled();
-    await expect(canvas.getByRole('radio', { name: '中文' })).toBeEnabled();
+    await expect(canvas.getByRole('combobox', { name: '界面语言' })).toBeEnabled();
   },
 };
 // Error is a real signal rather than a loading state. Desktop-owned controls
@@ -2083,8 +2083,10 @@ export const UsageLongTail: Story = {
   render: () => <SettingsStory section="usage" />,
   play: async ({ canvasElement, globals }) => {
     const canvas = within(canvasElement);
-    const usageCopy = getUsageSettingsCopy(globals.locale === 'en' ? 'en' : 'zh');
-    expect(
+    const usageCopy = getUsageSettingsCopy(
+      globals.locale === 'en' ? 'en' : globals.locale === 'zh-TW' ? 'zh-TW' : 'zh-CN',
+    );
+      expect(
       await canvas.findByText(usageCopy.totalRequests, {
         selector: '[data-slot="stat-tile-label"]',
       }),
diff --git a/docs/astryx-surface-file-inventory.md b/docs/astryx-surface-file-inventory.md
index e4e5f0f362..5b7e3fdb67 100644
--- a/docs/astryx-surface-file-inventory.md
+++ b/docs/astryx-surface-file-inventory.md
@@ -117,7 +117,7 @@ Wiki bar: Design Conventions · API Use-the-System · Theming · Container Paddi
 | `apps/desktop/src/renderer/settings/memory-settings-sections.tsx` | settings-module | Button | aligned — uses Astryx (Button) | aligned |
 | `apps/desktop/src/renderer/settings/password-input.tsx` | settings-module | IconButton, InputGroup, InputGroupText, TextInput | aligned — uses Astryx (IconButton, InputGroup, InputGroupText, TextInput) | aligned |
 | `apps/desktop/src/renderer/settings/permission-center-page.tsx` | settings-page | Banner, Button, Collapsible, CollapsibleGroup, HStack, List, ListItem, MetadataList, MetadataListItem, StatusDot, Text, VStack | aligned — uses Astryx (Banner, Button, Collapsible, CollapsibleGroup, HStack, List, ListItem, MetadataList) | aligned |
-| `apps/desktop/src/renderer/settings/personalization-settings-section.tsx` | settings-module | SegmentedControl, SegmentedControlItem, TextArea, TextInput | aligned — uses Astryx (SegmentedControl, SegmentedControlItem, TextArea, TextInput) | aligned |
+| `apps/desktop/src/renderer/settings/personalization-settings-section.tsx` | settings-module | Selector, TextArea, TextInput | aligned — uses Astryx (Selector, TextArea, TextInput) | aligned |
 | `apps/desktop/src/renderer/settings/projects-settings-page.tsx` | settings-page | Badge, Banner, Button, EmptyState, HStack, List, ListItem, MoreMenu, TextInput | aligned — uses Astryx (Badge, Banner, Button, EmptyState, HStack, List, ListItem, MoreMenu) | aligned |
 | `apps/desktop/src/renderer/settings/provider-add-form.tsx` | settings-module | Banner, Button, CheckboxList, CheckboxListItem, Collapsible, EmptyState, FormLayout, HStack, Selector, Step, Stepper, Text, TextInput, VStack | aligned — uses Astryx (Banner, Button, CheckboxList, CheckboxListItem, Collapsible, EmptyState, FormLayout, HStack) | aligned |
 | `apps/desktop/src/renderer/settings/provider-add-model-dialog.tsx` | settings-module | Button, Dialog, DialogHeader, FormLayout, HStack, Layout, LayoutContent, LayoutFooter, NumberInput, TextInput | aligned — uses Astryx (Button, Dialog, DialogHeader, FormLayout, HStack, Layout, LayoutContent, LayoutFooter) | aligned |
diff --git a/packages/cli/src/__tests__/cli-ui-locale.test.ts b/packages/cli/src/__tests__/cli-ui-locale.test.ts
index e92a26fb18..8c4e22d7b7 100644
--- a/packages/cli/src/__tests__/cli-ui-locale.test.ts
+++ b/packages/cli/src/__tests__/cli-ui-locale.test.ts
@@ -23,9 +23,13 @@ import { resolveCliUiLocale } from '../cli-ui-locale.js';
 
 describe('CLI TUI locale authority', () => {
   test('an explicit MAKA_LOCALE wins over the POSIX locale', () => {
-    assert.deepEqual(resolveCliUiLocale({ MAKA_LOCALE: 'zh', LC_ALL: 'en_US.UTF-8' }), {
+    assert.deepEqual(resolveCliUiLocale({ MAKA_LOCALE: 'zh-CN', LC_ALL: 'en_US.UTF-8' }), {
       ok: true,
-      locale: 'zh',
+      locale: 'zh-CN',
+    });
+    assert.deepEqual(resolveCliUiLocale({ MAKA_LOCALE: 'ZH-TW', LC_ALL: 'zh_CN.UTF-8' }), {
+      ok: true,
+      locale: 'zh-TW',
     });
     assert.deepEqual(resolveCliUiLocale({ MAKA_LOCALE: 'EN', LANG: 'zh_CN.UTF-8' }), {
       ok: true,
@@ -33,6 +37,21 @@ describe('CLI TUI locale authority', () => {
     });
   });
 
+  test('preserves legacy and POSIX-style Chinese aliases', () => {
+    assert.deepEqual(resolveCliUiLocale({ MAKA_LOCALE: 'zh' }), {
+      ok: true,
+      locale: 'zh-CN',
+    });
+    assert.deepEqual(resolveCliUiLocale({ MAKA_LOCALE: 'zh_CN' }), {
+      ok: true,
+      locale: 'zh-CN',
+    });
+    assert.deepEqual(resolveCliUiLocale({ MAKA_LOCALE: 'zh_TW' }), {
+      ok: true,
+      locale: 'zh-TW',
+    });
+  });
+
   test('auto honors POSIX locale authority order', () => {
     assert.deepEqual(
       resolveCliUiLocale({
@@ -45,19 +64,34 @@ describe('CLI TUI locale authority', () => {
     );
     assert.deepEqual(resolveCliUiLocale({ LC_MESSAGES: 'zh_CN.UTF-8', LANG: 'en_US.UTF-8' }), {
       ok: true,
-      locale: 'zh',
+      locale: 'zh-CN',
     });
     assert.deepEqual(resolveCliUiLocale({ LANG: 'en_US.UTF-8' }), {
       ok: true,
       locale: 'en',
     });
+    assert.deepEqual(resolveCliUiLocale({ LANG: 'zh_TW.UTF-8' }), {
+      ok: true,
+      locale: 'zh-TW',
+    });
     assert.deepEqual(resolveCliUiLocale({}), { ok: true, locale: 'en' });
   });
 
+  test('treats empty and whitespace-only MAKA_LOCALE as auto', () => {
+    assert.deepEqual(resolveCliUiLocale({ MAKA_LOCALE: '', LANG: 'zh_TW.UTF-8' }), {
+      ok: true,
+      locale: 'zh-TW',
+    });
+    assert.deepEqual(resolveCliUiLocale({ MAKA_LOCALE: '  \t ', LANG: 'zh_CN.UTF-8' }), {
+      ok: true,
+      locale: 'zh-CN',
+    });
+  });
+
   test('rejects an unsupported explicit preference with a configuration error', () => {
     assert.deepEqual(resolveCliUiLocale({ MAKA_LOCALE: 'fr' }), {
       ok: false,
-      message: 'Invalid MAKA_LOCALE "fr". Expected zh, en, or auto.',
+      message: 'Invalid MAKA_LOCALE "fr". Expected zh-CN, zh-TW, en, or auto.',
     });
   });
 });
diff --git a/packages/cli/src/__tests__/cli.test.ts b/packages/cli/src/__tests__/cli.test.ts
index a4975f9409..09ed0228b9 100644
--- a/packages/cli/src/__tests__/cli.test.ts
+++ b/packages/cli/src/__tests__/cli.test.ts
@@ -142,7 +142,7 @@ describe('Maka CLI args', () => {
   });
 
   test('does not add a discoverable global locale flag', () => {
-    assert.deepEqual(parseMakaCliArgs(['--locale', 'zh'], '0.1.0'), {
+    assert.deepEqual(parseMakaCliArgs(['--locale', 'zh-CN'], '0.1.0'), {
       kind: 'error',
       message: 'Unexpected argument: --locale',
       exitCode: 2,
diff --git a/packages/cli/src/__tests__/pi-transcript.test.ts b/packages/cli/src/__tests__/pi-transcript.test.ts
index 6a8e205ed5..8cd0e2a40e 100644
--- a/packages/cli/src/__tests__/pi-transcript.test.ts
+++ b/packages/cli/src/__tests__/pi-transcript.test.ts
@@ -123,7 +123,7 @@ describe('Maka Pi TUI transcript', () => {
     assert.match(english, /Type a message to start/);
     assert.match(english, /\/session\s+Switch or resume a session/);
 
-    const chinese = renderMakaPiTranscript(state, { ...meta(), uiLocale: 'zh' }, 100)
+    const chinese = renderMakaPiTranscript(state, { ...meta(), uiLocale: 'zh-CN' }, 100)
       .map(stripAnsi)
       .join('\n');
     assert.match(chinese, /陪你把事做完/);
@@ -156,7 +156,7 @@ describe('Maka Pi TUI transcript', () => {
     assert.equal(renderFor('darwin').at(-1), '⌥+↑ take queued messages back to re-edit');
     assert.equal(renderFor('linux').at(-1), 'Alt+↑ take queued messages back to re-edit');
     assert.equal(
-      renderMakaPiPendingQueue(state, 80, 'linux', 'zh').map(stripAnsi).at(-1),
+      renderMakaPiPendingQueue(state, 80, 'linux', 'zh-CN').map(stripAnsi).at(-1),
       'Alt+↑ 取回队列以重新编辑',
     );
   });
@@ -224,7 +224,7 @@ describe('Maka Pi TUI transcript', () => {
     assert.equal(
       stripAnsi(
         renderMakaPiStatusLine(
-          { ...meta(), uiLocale: 'zh', sideConversation: { view: 'side' } },
+          { ...meta(), uiLocale: 'zh-CN', sideConversation: { view: 'side' } },
           200,
         ),
       ),
@@ -243,7 +243,7 @@ describe('Maka Pi TUI transcript', () => {
           renderMakaPiStatusLine(
             {
               ...meta(),
-              uiLocale: 'zh',
+              uiLocale: 'zh-CN',
               sideConversation: { view: 'side', parentStatus },
             },
             200,
@@ -255,7 +255,7 @@ describe('Maka Pi TUI transcript', () => {
     assert.match(
       stripAnsi(
         renderMakaPiStatusLine(
-          { ...meta(), uiLocale: 'zh', sideConversation: { view: 'parent' } },
+          { ...meta(), uiLocale: 'zh-CN', sideConversation: { view: 'parent' } },
           200,
         ),
       ),
diff --git a/packages/cli/src/__tests__/pi-tui-mcp-status.test.ts b/packages/cli/src/__tests__/pi-tui-mcp-status.test.ts
index 9a0e12fbaf..cdb4cb6dae 100644
--- a/packages/cli/src/__tests__/pi-tui-mcp-status.test.ts
+++ b/packages/cli/src/__tests__/pi-tui-mcp-status.test.ts
@@ -64,7 +64,7 @@ describe('MCP management overlay', () => {
 
   test('states the remote limitation instead of implying an empty local config', () => {
     const overlay = new McpManagementOverlay({
-      locale: 'zh',
+      locale: 'zh-CN',
       viewportRows: () => 6,
       onClose: () => undefined,
       onChange: () => undefined,
@@ -137,7 +137,7 @@ describe('MCP management overlay', () => {
 
   test('localizes manager states without changing their source values', () => {
     const overlay = new McpManagementOverlay({
-      locale: 'zh',
+      locale: 'zh-CN',
       surface: surface({
         initialization: 'ready',
         configuration: 'ready',
@@ -164,6 +164,39 @@ describe('MCP management overlay', () => {
     assert.doesNotMatch(text, /needs-auth/u);
   });
 
+  test('renders Traditional Chinese without Simplified fallbacks', () => {
+    const overlay = new McpManagementOverlay({
+      locale: 'zh-TW',
+      surface: surface({
+        initialization: 'ready',
+        configuration: 'ready',
+        publication: 'not_published',
+        toolCount: 0,
+        servers: [
+          {
+            serverId: 'oauth',
+            configured: true,
+            synchronized: true,
+            state: 'disconnected',
+            transport: 'streamable-http',
+            toolCount: 0,
+            error: '连接失败。',
+          },
+        ],
+      }),
+      viewportRows: () => 7,
+      onClose: () => undefined,
+      onChange: () => undefined,
+    });
+
+    const text = overlay.render(100).map(stripAnsi).join('\n');
+    assert.match(text, /MCP 伺服器/u);
+    assert.match(text, /未發佈 · 0 個工具/u);
+    assert.match(text, /oauth  未連線/u);
+    assert.match(text, /MCP 伺服器連線失敗/u);
+    assert.doesNotMatch(text, /连接|发布/u);
+  });
+
   test('subscribes only for the overlay lifetime', () => {
     let subscribed = 0;
     let disposed = 0;
@@ -251,73 +284,118 @@ describe('MCP management overlay', () => {
     assert.match(text, /› ○ s4/u);
   });
 
-  test('routes the guided add flow through catalog headings, labels, and hints', () => {
-    const locale = 'en';
-    const expected = TUI_COPY_RESOURCES['mcp-status'][locale].editor;
-    const overlay = new McpManagementOverlay({
-      locale,
-      tui: fakeTui(),
-      surface: surface(listSnapshot()),
-      viewportRows: () => 14,
-      onClose: () => undefined,
-      onChange: () => undefined,
-    });
-    const rendered = () => overlay.render(100).map(stripAnsi).join('\n');
-
-    overlay.handleInput('a');
-    assert.ok(rendered().includes(expected.addTitle));
-    overlay.handleInput('g');
-    let text = rendered();
-    assert.ok(text.includes(expected.inputLabels.server_id));
-    assert.ok(text.includes(expected.hints.submit));
-    for (const char of 'demo') overlay.handleInput(char);
-    overlay.handleInput('\r');
-    assert.ok(rendered().includes(expected.transportTitle));
-    overlay.handleInput('1');
-    text = rendered();
-    assert.ok(text.includes(expected.inputLabels.command));
-    for (const char of 'echo') overlay.handleInput(char);
-    overlay.handleInput('\r');
-    text = rendered();
-    assert.ok(text.includes(expected.inputLabels.args));
-    assert.ok(text.includes(expected.hints.args));
-    overlay.handleInput('\r');
-    assert.ok(rendered().includes(expected.protocolTitle));
-    overlay.handleInput('2');
-    text = rendered();
-    assert.ok(text.includes(expected.inputLabels.cwd));
-    assert.ok(text.includes(expected.hints.optional));
-    overlay.handleInput('\r');
-    text = rendered();
-    assert.ok(text.includes(expected.inputLabels.env));
-    assert.ok(text.includes(expected.hints.map));
-    overlay.handleInput('\r');
-    text = rendered();
-    assert.ok(text.includes(expected.confirmAddTitle));
-    assert.ok(text.includes('demo · stdio · auto'));
-    assert.ok(text.includes('echo'));
-    assert.ok(text.includes(expected.confirmHint));
-  });
+  const editorCopy = {
+    en: {
+      add: 'Add MCP server',
+      transport: 'Transport',
+      protocol: 'Protocol preference',
+      confirmAdd: 'Add this MCP server?',
+      confirmRemove: 'Remove filesystem?',
+      confirmHint: 'y Confirm · Esc cancel',
+      serverId: 'Server ID',
+      command: 'Command',
+      args: 'Arguments',
+      argsHint: 'JSON string array, optional',
+      cwd: 'Working directory',
+      optionalHint: 'optional',
+      env: 'Environment',
+      mapHint: 'JSON string map, optional',
+      submitHint: 'Enter submit · Esc back',
+      testing: 'Testing MCP server…',
+      reconnecting: 'Reconnecting MCP server…',
+      applying: 'Applying MCP configuration…',
+      published: 'Configuration saved and tools refreshed.',
+      managerFailed: 'The MCP connection action failed.',
+    },
+    'zh-CN': {
+      add: '添加 MCP 服务器',
+      transport: '传输方式',
+      protocol: '协议偏好',
+      confirmAdd: '添加该 MCP 服务器?',
+      confirmRemove: '删除 filesystem?',
+      confirmHint: 'y 确认 · Esc 取消',
+      serverId: '服务器 ID',
+      command: '命令',
+      args: '参数',
+      argsHint: 'JSON string array, 可留空',
+      cwd: '工作目录',
+      optionalHint: '可留空',
+      env: '环境变量',
+      mapHint: 'JSON string map, 可留空',
+      submitHint: 'Enter 提交 · Esc 返回',
+      testing: '正在测试 MCP 服务器…',
+      reconnecting: '正在重连 MCP 服务器…',
+      applying: '正在应用 MCP 配置…',
+      published: '配置已保存,工具已刷新。',
+      managerFailed: 'MCP 连接操作失败。',
+    },
+  } as const;
 
-  test('routes remove confirmation copy and interpolates the server id', () => {
-    const locale = 'en';
-    const expected = TUI_COPY_RESOURCES['mcp-status'][locale].editor;
-    const overlay = new McpManagementOverlay({
-      locale,
-      surface: surface(listSnapshot()),
-      viewportRows: () => 8,
-      onClose: () => undefined,
-      onChange: () => undefined,
+  for (const locale of ['en', 'zh-CN'] as const) {
+    const expected = editorCopy[locale];
+
+    test(`walks the guided add flow with ${locale} headings, labels, and hints`, () => {
+      const overlay = new McpManagementOverlay({
+        locale,
+        tui: fakeTui(),
+        surface: surface(listSnapshot()),
+        viewportRows: () => 14,
+        onClose: () => undefined,
+        onChange: () => undefined,
+      });
+      const rendered = () => overlay.render(100).map(stripAnsi).join('\n');
+
+      overlay.handleInput('a');
+      assert.ok(rendered().includes(expected.add));
+      overlay.handleInput('g');
+      let text = rendered();
+      assert.ok(text.includes(expected.serverId));
+      assert.ok(text.includes(expected.submitHint));
+      for (const char of 'demo') overlay.handleInput(char);
+      overlay.handleInput('\r');
+      assert.ok(rendered().includes(expected.transport));
+      overlay.handleInput('1');
+      text = rendered();
+      assert.ok(text.includes(expected.command));
+      for (const char of 'echo') overlay.handleInput(char);
+      overlay.handleInput('\r');
+      text = rendered();
+      assert.ok(text.includes(expected.args));
+      assert.ok(text.includes(expected.argsHint));
+      overlay.handleInput('\r');
+      assert.ok(rendered().includes(expected.protocol));
+      overlay.handleInput('2');
+      text = rendered();
+      assert.ok(text.includes(expected.cwd));
+      assert.ok(text.includes(expected.optionalHint));
+      overlay.handleInput('\r');
+      text = rendered();
+      assert.ok(text.includes(expected.env));
+      assert.ok(text.includes(expected.mapHint));
+      overlay.handleInput('\r');
+      text = rendered();
+      assert.ok(text.includes(expected.confirmAdd));
+      assert.ok(text.includes('demo · stdio · auto'));
+      assert.ok(text.includes('echo'));
+      assert.ok(text.includes(expected.confirmHint));
     });
-    overlay.render(100);
 
-    overlay.handleInput('d');
-    const text = overlay.render(100).map(stripAnsi).join('\n');
-    assert.ok(text.includes(expected.confirmRemoveTitle.replace('{serverId}', 'filesystem')));
-    assert.ok(text.includes(expected.confirmHint));
-  });
+    test(`renders the ${locale} remove confirmation with the server id`, () => {
+      const overlay = new McpManagementOverlay({
+        locale,
+        surface: surface(listSnapshot()),
+        viewportRows: () => 8,
+        onClose: () => undefined,
+        onChange: () => undefined,
+      });
+      overlay.render(100);
+
+      overlay.handleInput('d');
+      const text = overlay.render(100).map(stripAnsi).join('\n');
+      assert.ok(text.includes(expected.confirmRemove));
+      assert.ok(text.includes(expected.confirmHint));
+    });
 
-  for (const locale of ['en', 'zh'] as const) {
     test(`labels the busy phase per action kind in ${locale}`, () => {
       const expected = TUI_COPY_RESOURCES['mcp-status'][locale].editor;
       const mcp = surface(listSnapshot());
@@ -369,24 +447,52 @@ describe('MCP management overlay', () => {
     [{ status: 'tested', test: testResult(true), effect: 'pending_host' }, 'test_pending_host'],
   ];
 
-  test('routes every action result to its catalog notice', async () => {
-    for (const [result, code] of RESULT_CASES) {
-      const mcp = surface(listSnapshot());
-      mcp.execute = async () => result;
-      const overlay = new McpManagementOverlay({
-        locale: 'en',
-        surface: mcp,
-        viewportRows: () => 8,
-        onClose: () => undefined,
-        onChange: () => undefined,
-      });
-      overlay.render(100);
+  for (const locale of ['en', 'zh-CN'] as const) {
+    test(`routes every action result to its ${locale} catalog notice`, async () => {
+      const results = TUI_COPY_RESOURCES['mcp-status'][locale].editor.results;
+      for (const [result, code] of RESULT_CASES) {
+        const mcp = surface(listSnapshot());
+        mcp.execute = async () => result;
+        const overlay = new McpManagementOverlay({
+          locale,
+          surface: mcp,
+          viewportRows: () => 8,
+          onClose: () => undefined,
+          onChange: () => undefined,
+        });
+        overlay.render(100);
+
+        overlay.handleInput(' ');
+        await new Promise<void>((resolve) => setImmediate(resolve));
+        const text = overlay.render(100).map(stripAnsi).join('\n');
+        assert.ok(text.includes(results[code as keyof typeof results]), `${code} (${locale})`);
+      }
+    });
+  }
 
-      overlay.handleInput(' ');
-      await new Promise<void>((resolve) => setImmediate(resolve));
-      const text = overlay.render(100).map(stripAnsi).join('\n');
-      assert.ok(text.includes(resultCopy[code]), code);
-    }
+  test('renders an unknown runtime result code as the raw code', async () => {
+    const mcp = surface(listSnapshot());
+    mcp.execute = async () =>
+      ({ status: 'failed', reason: 'future-code' }) as unknown as TuiMcpActionResult;
+    const overlay = new McpManagementOverlay({
+      locale: 'en',
+      surface: mcp,
+      viewportRows: () => 8,
+      onClose: () => undefined,
+      onChange: () => undefined,
+    });
+    overlay.render(100);
+
+    overlay.handleInput(' ');
+    await new Promise<void>((resolve) => setImmediate(resolve));
+    assert.ok(overlay.render(100).map(stripAnsi).join('\n').includes('future-code'));
+  });
+
+  test('keeps punctuation-sensitive catalog strings byte-for-byte', () => {
+    const editor = (locale: 'en' | 'zh-CN') => TUI_COPY_RESOURCES['mcp-status'][locale].editor;
+    assert.equal(editor('zh-CN').results.stale_import, '导入项已变化,请重新预览。');
+    assert.equal(editor('en').confirmImportTitle, 'Import MCP servers?');
+    assert.equal(editor('zh-CN').confirmImportTitle, '导入 MCP 服务器?');
   });
 });
 
diff --git a/packages/cli/src/__tests__/pi-tui-runner.test.ts b/packages/cli/src/__tests__/pi-tui-runner.test.ts
index 2c633b4642..1253daf81f 100644
--- a/packages/cli/src/__tests__/pi-tui-runner.test.ts
+++ b/packages/cli/src/__tests__/pi-tui-runner.test.ts
@@ -246,7 +246,7 @@ describe('Maka Pi TUI runner', () => {
     const driver = new SlashCommandDriver();
     const run = runMakaPiTui({
       title: 'Maka',
-      locale: 'zh',
+      locale: 'zh-CN',
       driver,
       cwd: '/repo',
       model: 'claude-sonnet-4-5',
@@ -325,7 +325,7 @@ describe('Maka Pi TUI runner', () => {
     const driver = new UserCommandDriver();
     const run = runMakaPiTui({
       title: 'Maka',
-      locale: 'zh',
+      locale: 'zh-CN',
       driver,
       cwd: '/repo',
       model: 'claude-sonnet-4-5',
@@ -799,7 +799,7 @@ describe('Maka Pi TUI runner', () => {
       model: 'claude-sonnet-4-5',
       connectionSlug: 'claude-subscription',
       permissionMode: 'bypass',
-      locale: 'zh',
+      locale: 'zh-CN',
       terminal,
       onboarding: fakeOnboardingSurface({
         verify: async (input) => {
@@ -868,7 +868,7 @@ describe('Maka Pi TUI runner', () => {
       model: 'claude-sonnet-4-5',
       connectionSlug: 'claude-subscription',
       permissionMode: 'bypass',
-      locale: 'zh',
+      locale: 'zh-CN',
       terminal,
       onboarding: fakeOnboardingSurface({
         save: async () => {
@@ -4119,7 +4119,7 @@ describe('Maka Pi TUI runner', () => {
       models: ['gpt-5', 'gpt-5-mini'],
       connectionSlug: 'openai',
       permissionMode: 'ask',
-      locale: 'zh',
+      locale: 'zh-CN',
       terminal,
     });
 
@@ -4141,7 +4141,7 @@ describe('Maka Pi TUI runner', () => {
       model: 'gpt-5',
       connectionSlug: 'openai',
       permissionMode: 'ask',
-      locale: 'zh',
+      locale: 'zh-CN',
       modelChoices: [
         {
           connectionId: 'connection-openai',
@@ -6746,7 +6746,7 @@ describe('Maka Pi TUI runner', () => {
       connectionSlug: 'claude-subscription',
       permissionMode: 'ask',
       terminal,
-      locale: 'zh',
+      locale: 'zh-CN',
       listSkills: async () => [],
     });
 
@@ -8235,7 +8235,7 @@ describe('Maka Pi TUI runner', () => {
       model: 'claude-sonnet-4-5',
       connectionSlug: 'claude-subscription',
       permissionMode: 'ask',
-      locale: 'zh',
+      locale: 'zh-CN',
       terminal,
     });
 
diff --git a/packages/cli/src/__tests__/tui-copy-catalog.test.ts b/packages/cli/src/__tests__/tui-copy-catalog.test.ts
index 289fbb16f2..c233121b96 100644
--- a/packages/cli/src/__tests__/tui-copy-catalog.test.ts
+++ b/packages/cli/src/__tests__/tui-copy-catalog.test.ts
@@ -19,7 +19,7 @@
 
 import assert from 'node:assert/strict';
 import { describe, test } from 'node:test';
-import { formatUiMessage } from '@maka/core/ui-locale';
+import { formatUiMessage, UI_LOCALES } from '@maka/core/ui-locale';
 import { getTuiPickerCopy, onboardingFailureMessage } from '../pi-tui-pickers.js';
 import { TUI_COPY_RESOURCES } from '../tui-copy-catalog.js';
 
@@ -45,33 +45,37 @@ const MESSAGE_VALUES = {
 describe('TUI copy resources', () => {
   test('registers every domain without a locale-specific getter branch', () => {
     for (const [domain, catalog] of Object.entries(TUI_COPY_RESOURCES)) {
-      assert.ok(catalog.en, `${domain}/en`);
-      assert.ok(catalog.zh, `${domain}/zh`);
+      for (const locale of UI_LOCALES) assert.ok(catalog[locale], `${domain}/${locale}`);
     }
   });
 
-  test('keeps Chinese coverage, variables, and ICU formatting aligned with English', () => {
+  test('keeps translated coverage, variables, and ICU formatting aligned with English', () => {
     for (const [domain, catalog] of Object.entries(TUI_COPY_RESOURCES)) {
       const enLeaves = new Map(leafEntries(catalog.en));
-      const zhLeaves = new Map(leafEntries(catalog.zh));
-
-      assert.deepEqual([...zhLeaves.keys()].sort(), [...enLeaves.keys()].sort(), `${domain}/zh`);
-      for (const [path, enTemplate] of enLeaves) {
-        const zhTemplate = zhLeaves.get(path)!;
-        const enVariables = messageVariables(enTemplate);
-        const zhVariables = messageVariables(zhTemplate);
-        assert.deepEqual(zhVariables, enVariables, `${domain}/zh/${path}`);
-        if (enVariables.length === 0) continue;
-        assert.notEqual(
-          formatUiMessage(enTemplate, MESSAGE_VALUES, 'en'),
-          enTemplate,
-          `${domain}/en/${path}`,
-        );
-        assert.notEqual(
-          formatUiMessage(zhTemplate, MESSAGE_VALUES, 'zh'),
-          zhTemplate,
-          `${domain}/zh/${path}`,
+      for (const locale of ['zh-CN', 'zh-TW'] as const) {
+        const translatedLeaves = new Map(leafEntries(catalog[locale]));
+        assert.deepEqual(
+          [...translatedLeaves.keys()].sort(),
+          [...enLeaves.keys()].sort(),
+          `${domain}/${locale}`,
         );
+        for (const [path, enTemplate] of enLeaves) {
+          const translatedTemplate = translatedLeaves.get(path)!;
+          const enVariables = messageVariables(enTemplate);
+          const translatedVariables = messageVariables(translatedTemplate);
+          assert.deepEqual(translatedVariables, enVariables, `${domain}/${locale}/${path}`);
+          if (enVariables.length === 0) continue;
+          assert.notEqual(
+            formatUiMessage(enTemplate, MESSAGE_VALUES, 'en'),
+            enTemplate,
+            `${domain}/en/${path}`,
+          );
+          assert.notEqual(
+            formatUiMessage(translatedTemplate, MESSAGE_VALUES, locale),
+            translatedTemplate,
+            `${domain}/${locale}/${path}`,
+          );
+        }
       }
     }
   });
@@ -79,11 +83,13 @@ describe('TUI copy resources', () => {
   test('keeps model picker copy locale-specific', () => {
     assert.equal(getTuiPickerCopy('en').modelPickerTitle, 'Select Model');
     assert.equal(getTuiPickerCopy('en').searchLabel, 'Search');
-    assert.equal(getTuiPickerCopy('zh').modelPickerTitle, '选择模型');
-    assert.equal(getTuiPickerCopy('zh').searchLabel, '搜索');
-    assert.equal(getTuiPickerCopy('zh').selectPickerHint, '↑↓ 选择 · Enter 确认 · Esc 关闭');
-    assert.equal(getTuiPickerCopy('zh').currentMarker, '当前');
-    assert.equal(getTuiPickerCopy('zh').defaultMarker, '默认');
+    assert.equal(getTuiPickerCopy('zh-CN').modelPickerTitle, '选择模型');
+    assert.equal(getTuiPickerCopy('zh-CN').searchLabel, '搜索');
+    assert.equal(getTuiPickerCopy('zh-TW').modelPickerTitle, '選擇模型');
+    assert.equal(getTuiPickerCopy('zh-TW').searchLabel, '搜尋');
+    assert.equal(getTuiPickerCopy('zh-TW').selectPickerHint, '↑↓ 選擇 · Enter 確認 · Esc 關閉');
+    assert.equal(getTuiPickerCopy('zh-CN').currentMarker, '当前');
+    assert.equal(getTuiPickerCopy('zh-TW').defaultMarker, '預設');
   });
 
   test('formats the English MCP count with ICU plural rules', () => {
@@ -93,23 +99,48 @@ describe('TUI copy resources', () => {
     assert.equal(formatUiMessage(template, { count: 2 }, 'en'), '2 tools');
   });
 
+  test('localizes rewind and skill notices in both locales', () => {
+    assert.equal(TUI_COPY_RESOURCES.rewind.en.noTargets, 'No turns to rewind to.');
+    assert.equal(TUI_COPY_RESOURCES.rewind['zh-CN'].noTargets, '没有可回退的轮次。');
+    assert.equal(
+      formatUiMessage(
+        TUI_COPY_RESOURCES.skills.en.failedItem,
+        { request: 'nope', reason: TUI_COPY_RESOURCES.skills.en.failureReasons.not_found },
+        'en',
+      ),
+      '/skill:nope (not found)',
+    );
+    assert.equal(
+      formatUiMessage(
+        TUI_COPY_RESOURCES.skills['zh-CN'].failedItem,
+        { request: 'nope', reason: TUI_COPY_RESOURCES.skills['zh-CN'].failureReasons.not_found },
+        'zh-CN',
+      ),
+      '/skill:nope(未找到)',
+    );
+  });
+
   test('localizes stable onboarding failure codes at the TUI boundary', () => {
     assert.equal(
       onboardingFailureMessage({ kind: 'rejected', reason: 'connection_not_found' }, 'en'),
       'This connection no longer exists. Reopen /setup and try again.',
     );
     assert.equal(
-      onboardingFailureMessage({ kind: 'rejected', reason: 'connection_not_found' }, 'zh'),
+      onboardingFailureMessage({ kind: 'rejected', reason: 'connection_not_found' }, 'zh-CN'),
       '该连接已不存在,请重新打开 /setup 后重试。',
     );
     assert.equal(
-      onboardingFailureMessage({ kind: 'failed', errorClass: 'auth' }, 'zh'),
+      onboardingFailureMessage({ kind: 'failed', errorClass: 'auth' }, 'zh-CN'),
       '服务商身份验证失败,请检查 API key 后重试。',
     );
     assert.equal(
-      onboardingFailureMessage({ kind: 'unavailable' }, 'zh'),
+      onboardingFailureMessage({ kind: 'unavailable' }, 'zh-CN'),
       '无法连接 Runtime Host,请重试。',
     );
+    assert.equal(
+      onboardingFailureMessage({ kind: 'failed', errorClass: 'auth' }, 'zh-TW'),
+      '服務商身分驗證失敗,請檢查 API key 後重試。',
+    );
   });
 });
 
diff --git a/packages/cli/src/__tests__/tui-copy-command.test.ts b/packages/cli/src/__tests__/tui-copy-command.test.ts
index fa9dc3cd52..b62e181dc1 100644
--- a/packages/cli/src/__tests__/tui-copy-command.test.ts
+++ b/packages/cli/src/__tests__/tui-copy-command.test.ts
@@ -155,7 +155,7 @@ describe('serializeTranscriptText', () => {
 describe('getTuiCopyCopy', () => {
   test('resolves localized copy for each locale', () => {
     assert.equal(typeof getTuiCopyCopy('en').nothingToCopy, 'string');
-    assert.equal(typeof getTuiCopyCopy('zh').nothingToCopy, 'string');
+    assert.equal(typeof getTuiCopyCopy('zh-CN').nothingToCopy, 'string');
     assert.ok(getTuiCopyCopy('en').copiedLast.includes('{count'));
     const tooLarge = getTuiCopyCopy('en').tooLarge;
     assert.ok(tooLarge.includes('{bytes}') && tooLarge.includes('{limit}'));
diff --git a/packages/cli/src/__tests__/tui-primary-guidance.test.ts b/packages/cli/src/__tests__/tui-primary-guidance.test.ts
index bb6330b9cf..01d49a06ff 100644
--- a/packages/cli/src/__tests__/tui-primary-guidance.test.ts
+++ b/packages/cli/src/__tests__/tui-primary-guidance.test.ts
@@ -28,26 +28,26 @@ describe('TUI primary guidance catalog', () => {
       .map((command) => command.id)
       .sort();
 
-    for (const locale of ['zh', 'en'] as const) {
+    for (const locale of ['zh-CN', 'en'] as const) {
       assert.deepEqual(Object.keys(getTuiPrimaryGuidance(locale).commands).sort(), expected);
     }
   });
 
   test('keeps keybinding tokens identical across locales', () => {
-    const tokens = (locale: 'zh' | 'en') =>
+    const tokens = (locale: 'zh-CN' | 'en') =>
       getTuiPrimaryGuidance(locale).help.keybindings.flatMap((line) => {
         const match = /^\s*(.*?)\s*—/u.exec(line);
         return match ? [match[1]!.replace(/\s*[((].*$/u, '')] : [];
       });
 
-    assert.deepEqual(tokens('zh'), tokens('en'));
+    assert.deepEqual(tokens('zh-CN'), tokens('en'));
   });
 
   test('renders Option shortcut labels on macOS and Alt labels elsewhere', () => {
-    const guidanceFor = (locale: 'zh' | 'en', platform: NodeJS.Platform) =>
+    const guidanceFor = (locale: 'zh-CN' | 'en', platform: NodeJS.Platform) =>
       getTuiPrimaryGuidance(locale, platform);
 
-    for (const locale of ['zh', 'en'] as const) {
+    for (const locale of ['zh-CN', 'en'] as const) {
       const macKeybindings = guidanceFor(locale, 'darwin').help.keybindings.join('\n');
       assert.match(macKeybindings, /⌥\+Enter/u);
       assert.match(macKeybindings, /⌥\+↑/u);
diff --git a/packages/cli/src/__tests__/tui-session-status.test.ts b/packages/cli/src/__tests__/tui-session-status.test.ts
index 1336d599e6..dd2d59f33c 100644
--- a/packages/cli/src/__tests__/tui-session-status.test.ts
+++ b/packages/cli/src/__tests__/tui-session-status.test.ts
@@ -47,7 +47,7 @@ describe('TUI Session status badge', () => {
       ),
       'needs permission',
     );
-    assert.equal(sessionStatusBadge(session({ status: 'waiting_for_user' }), 'zh'), '等你确认');
+    assert.equal(sessionStatusBadge(session({ status: 'waiting_for_user' }), 'zh-CN'), '等你确认');
   });
 
   test('marks only actionable blocked reasons and keeps resting rows unmarked', () => {
@@ -74,7 +74,7 @@ describe('TUI Session status badge', () => {
     }
     assert.equal(sessionStatusBadge(session({ status: 'active' }), 'en'), undefined);
     assert.equal(sessionStatusBadge(session({ status: 'aborted' }), 'en'), 'stopped');
-    assert.equal(sessionStatusBadge(session({ status: 'aborted' }), 'zh'), '已中止');
+    assert.equal(sessionStatusBadge(session({ status: 'aborted' }), 'zh-CN'), '已中止');
   });
 });
 
diff --git a/packages/cli/src/cli-ui-locale.ts b/packages/cli/src/cli-ui-locale.ts
index 21dcf979dd..a32cad9412 100644
--- a/packages/cli/src/cli-ui-locale.ts
+++ b/packages/cli/src/cli-ui-locale.ts
@@ -33,11 +33,17 @@ export type CliUiLocaleResolution =
 /** Resolve the interactive TUI locale without changing the top-level CLI grammar. */
 export function resolveCliUiLocale(environment: CliEnvironment): CliUiLocaleResolution {
   const rawPreference = environment.MAKA_LOCALE?.trim();
-  const preference = rawPreference ? rawPreference.toLowerCase() : 'auto';
+  const normalizedPreference = rawPreference?.replaceAll('_', '-').toLowerCase() || 'auto';
+  const preference =
+    normalizedPreference === 'zh' || normalizedPreference === 'zh-cn'
+      ? 'zh-CN'
+      : normalizedPreference === 'zh-tw'
+        ? 'zh-TW'
+        : normalizedPreference;
   if (!isUiLocalePreference(preference)) {
     return {
       ok: false,
-      message: `Invalid MAKA_LOCALE ${JSON.stringify(rawPreference)}. Expected zh, en, or auto.`,
+      message: `Invalid MAKA_LOCALE ${JSON.stringify(rawPreference)}. Expected zh-CN, zh-TW, en, or auto.`,
     };
   }
 
diff --git a/packages/cli/src/pi-tui-mcp-status.ts b/packages/cli/src/pi-tui-mcp-status.ts
index d9dbff2fed..ff61bcae81 100644
--- a/packages/cli/src/pi-tui-mcp-status.ts
+++ b/packages/cli/src/pi-tui-mcp-status.ts
@@ -28,6 +28,7 @@ import {
   type TUI,
 } from '@earendil-works/pi-tui';
 import type { McpProtocolPreference, McpServerConfig } from '@maka/core/mcp';
+import { generalizedErrorMessageForLocale } from '@maka/core/redaction';
 import {
   defineUiMessageCatalog,
   formatUiMessage,
@@ -67,6 +68,7 @@ interface TuiMcpStatusCopy {
   readonly configuredOnly: string;
   readonly configPending: string;
   readonly toolCount: string;
+  readonly connectionFailed: string;
   readonly editor: {
     readonly addTitle: string;
     readonly transportTitle: string;
@@ -732,7 +734,17 @@ function serverLines(server: TuiMcpServerSnapshot, locale: UiLocale, selected: b
   const cursor = selected ? ansi.accent('›') : ' ';
   return [
     `${cursor} ${statusMarker(server.state)} ${ansi.bold(server.serverId)}  ${details}`,
-    ...(server.error ? [`    ${ansi.red(server.error)}`] : []),
+    ...(server.error
+      ? [
+          `    ${ansi.red(
+            generalizedErrorMessageForLocale(
+              new Error(server.error),
+              locale === 'en' ? server.error : copy.connectionFailed,
+              locale,
+            ),
+          )}`,
+        ]
+      : []),
   ];
 }
 
@@ -763,7 +775,7 @@ function actionNotice(
 }
 
 function resultCopy(locale: UiLocale, code: TuiMcpResultCode): string {
-  return MCP_STATUS_COPY[locale].editor.results[code];
+  return MCP_STATUS_COPY[locale].editor.results[code] ?? code;
 }
 
 function confirmAddDocument(draft: GuidedDraft, locale: UiLocale): string[] {
diff --git a/packages/cli/src/tui-copy-catalog.ts b/packages/cli/src/tui-copy-catalog.ts
index 3cfdd9cf26..44f8efa249 100644
--- a/packages/cli/src/tui-copy-catalog.ts
+++ b/packages/cli/src/tui-copy-catalog.ts
@@ -33,7 +33,7 @@ export const TUI_COPY_RESOURCES = {
       roleGoalContinuation: 'Goal continuation (autonomous):',
       roleLegacyAutomation: 'Legacy automation (history only):',
     },
-    zh: {
+    'zh-CN': {
       copiedLast:
         '已将最后一条回复发送到终端 · {count} 个字符——仅当终端(以及 tmux,如使用)允许写入剪贴板时才会真正复制。',
       copiedAll:
@@ -47,6 +47,20 @@ export const TUI_COPY_RESOURCES = {
       roleGoalContinuation: '目标续跑(自主):',
       roleLegacyAutomation: '旧版自动化(仅历史):',
     },
+    'zh-TW': {
+      copiedLast:
+        '已將最後一則回覆傳送至終端機 · {count} 個字元——僅當終端機(以及 tmux,如有使用)允許寫入剪貼簿時才會真正複製。',
+      copiedAll:
+        '已將整段對話傳送至終端機 · {count} 個字元——僅當終端機(以及 tmux,如有使用)允許寫入剪貼簿時才會真正複製。',
+      tooLarge:
+        '內容過大,無法複製 · {bytes} 位元組超過 {limit} 位元組上限;更大的內容會被終端機直接捨棄。',
+      nothingToCopy: '目前沒有可複製的內容。',
+      usage: '用法:/copy [all]',
+      roleUser: '你:',
+      roleAssistant: 'Maka:',
+      roleGoalContinuation: 'Goal 繼續執行(自主):',
+      roleLegacyAutomation: '舊版自動化(僅歷史):',
+    },
   },
   'mcp-status': {
     en: {
@@ -89,6 +103,7 @@ export const TUI_COPY_RESOURCES = {
       configuredOnly: 'configured only',
       configPending: 'config pending',
       toolCount: '{count, plural, one {# tool} other {# tools}}',
+      connectionFailed: 'MCP server connection failed',
       editor: {
         addTitle: 'Add MCP server',
         transportTitle: 'Transport',
@@ -154,7 +169,7 @@ export const TUI_COPY_RESOURCES = {
         },
       },
     },
-    zh: {
+    'zh-CN': {
       title: 'MCP 服务器',
       footer: {
         back: 'Esc 返回',
@@ -191,6 +206,7 @@ export const TUI_COPY_RESOURCES = {
       configuredOnly: '仅已配置',
       configPending: '配置待同步',
       toolCount: '{count} 个工具',
+      connectionFailed: 'MCP 服务器连接失败',
       editor: {
         addTitle: '添加 MCP 服务器',
         transportTitle: '传输方式',
@@ -252,6 +268,106 @@ export const TUI_COPY_RESOURCES = {
         },
       },
     },
+    'zh-TW': {
+      title: 'MCP 伺服器',
+      footer: {
+        back: 'Esc 返回',
+        readOnly: '↑/↓ 捲動 · q/Esc 關閉',
+        manage: 'a 新增 · Enter 編輯 · Space 啟用/停用 · t 測試 · r 重新連線 · d 刪除 · Esc 關閉',
+        managePublication:
+          'a 新增 · Enter 編輯 · Space 啟用/停用 · t 測試 · r 重新連線 · d 刪除 · p 設定 Provider 認證資料 · x 刪除認證資料 · Esc 關閉',
+      },
+      unavailableTitle: '目前 TUI 未連線至本機 MCP 控制介面。',
+      unavailableDetail: '遠端 Runtime Host 的用戶端 MCP 工具關聯將於後續版本提供。',
+      loading: '正在讀取 mcp.json 並探索工具…',
+      loadError: '無法讀取或套用 MCP 設定;未向 Runtime Host 發佈任何工具。',
+      noServers: '尚未設定 MCP 伺服器。按 a 新增。',
+      publication: {
+        waiting: '等待發佈',
+        host_unavailable: 'Runtime Host 重新連線中',
+        credential_required: '需要 Provider 認證資料',
+        credential_rejected: 'Provider 認證資料遭拒',
+        provider_conflict: 'Provider 已在另一個 TUI 中執行',
+        target_mismatch: 'Provider 目標不符',
+        publishing: '正在發佈',
+        published: '已發佈',
+        not_published: '未發佈',
+        error: '發佈失敗',
+      },
+      serverState: {
+        disabled: '已停用',
+        disconnected: '未連線',
+        connecting: '連線中',
+        connected: '已連線',
+        'needs-auth': '需要登入',
+        error: '錯誤',
+      },
+      configuredOnly: '僅已設定',
+      configPending: '設定待同步',
+      toolCount: '{count} 個工具',
+      connectionFailed: 'MCP 伺服器連線失敗',
+      editor: {
+        addTitle: '新增 MCP 伺服器',
+        transportTitle: '傳輸方式',
+        protocolTitle: '通訊協定偏好',
+        confirmAddTitle: '新增這個 MCP 伺服器?',
+        confirmImportTitle: '匯入 MCP 伺服器?',
+        confirmRemoveTitle: '刪除 {serverId}?',
+        confirmRemoveCredentialTitle: '移除遠端 Provider 認證資料?',
+        confirmRemoveCredentialDetail: '這將停止向所選 Runtime Host 發佈 MCP 工具。',
+        confirmHint: 'y 確認 · Esc 取消',
+        synchronizing: '設定同步中…',
+        outOfSync: '持久化設定與 MCP Manager 尚未同步。',
+        action: {
+          test: '正在測試 MCP 伺服器…',
+          reconnect: '正在重新連線 MCP 伺服器…',
+          apply: '正在套用 MCP 設定…',
+        },
+        inputLabels: {
+          server_id: '伺服器 ID',
+          command: '命令',
+          args: '參數',
+          url: 'Streamable HTTP URL',
+          cwd: '工作目錄',
+          env: '環境變數',
+          headers: '請求標頭',
+          edit: '編輯伺服器 JSON',
+          import: '貼上 MCP JSON',
+          publication_credential: 'Provider 認證資料',
+        },
+        hints: {
+          args: 'JSON 字串陣列,可留空',
+          map: 'JSON 字串對映,可留空',
+          optional: '可留空',
+          submit: 'Enter 送出 · Esc 返回',
+          credential:
+            '僅儲存於本機認證資料存放區;不會寫入設定檔、參數或聊天內容 · Enter 送出 · Esc 返回',
+        },
+        results: {
+          exists: '該伺服器 ID 已存在。',
+          stale_config: 'MCP 設定已變更,請重試。',
+          stale_edit: '該伺服器已變更,請重新開啟後編輯。',
+          stale_import: '匯入項目已變更,請重新預覽。',
+          missing: '該伺服器已不存在。',
+          closed: 'MCP 控制器已關閉。',
+          'invalid-config': '伺服器設定無效。',
+          'credential-cleanup-failed': '無法移除已儲存的認證資料;設定未變更。',
+          'publication-credential-failed': '無法儲存或套用 Provider 認證資料。',
+          'persist-failed': '無法儲存設定。',
+          'manager-failed': 'MCP 連線操作失敗。',
+          turn_active: 'Turn 或其他控制操作執行期間無法修改 MCP。',
+          invalid: '請檢查輸入後重試。',
+          published: '設定已儲存,工具已重新整理。',
+          pending_host: '設定已儲存,正等待 Runtime Host 發佈。',
+          sync_failed: '設定已儲存,但 MCP Manager 尚未同步。',
+          publication_failed: '設定已儲存,但 capability 發佈失敗。',
+          test_ok: '連線測試通過。',
+          test_failed: '連線測試失敗。',
+          test_publication_failed: '連線測試通過,但 capability 發佈失敗。',
+          test_pending_host: '連線測試通過,正等待 Runtime Host 發佈。',
+        },
+      },
+    },
   },
   'form-interaction': {
     en: {
@@ -280,7 +396,7 @@ export const TUI_COPY_RESOURCES = {
       falseValue: 'false',
       selectedCount: '{count} selected',
     },
-    zh: {
+    'zh-CN': {
       requestedBy: '由 {detail} 请求',
       sensitiveWarning: '请勿输入密码、API 密钥、访问令牌或支付信息。',
       required: '必填',
@@ -306,6 +422,32 @@ export const TUI_COPY_RESOURCES = {
       falseValue: '否',
       selectedCount: '已选择 {count} 项',
     },
+    'zh-TW': {
+      requestedBy: '由 {detail} 請求',
+      sensitiveWarning: '請勿輸入密碼、API 金鑰、存取權杖或付款資訊。',
+      required: '必填',
+      optional: '選填',
+      omitted: '未提供',
+      empty: '空',
+      invalid: '該值不符合欄位約束',
+      minimumLength: '至少 {minimum} 個字元',
+      maximumLength: '最多 {maximum} 個字元',
+      lengthRange: '長度 {minimum}–{maximum} 個字元',
+      minimumValue: '最小值 {minimum}',
+      maximumValue: '最大值 {maximum}',
+      valueRange: '範圍 {minimum}–{maximum}',
+      minimumItems: '至少選取 {minimum} 項',
+      maximumItems: '最多選取 {maximum} 項',
+      itemRange: '選取 {minimum}–{maximum} 項',
+      format: '格式:{format}',
+      reviewHint: '↑↓ 選取欄位 · Enter 編輯 · Space 提供/省略 · s 提交 · d 拒絕 · Esc 取消',
+      textHint: '輸入值 · Enter 返回檢查 · Esc 返回檢查',
+      choiceHint: '↑↓ 選取 · Enter 返回檢查 · Esc 返回檢查',
+      multiHint: '↑↓ 移動 · Space 切換 · Enter 返回檢查 · Esc 返回檢查',
+      trueValue: '是',
+      falseValue: '否',
+      selectedCount: '已選取 {count} 項',
+    },
   },
   pickers: {
     en: {
@@ -409,7 +551,7 @@ export const TUI_COPY_RESOURCES = {
       closeAction: 'Enter to close',
       thinkingUnsupported: 'This model does not support changing the thinking level.',
     },
-    zh: {
+    'zh-CN': {
       modelPickerTitle: '选择模型',
       modelSwitchCacheWarning: '⚠ 切换模型可能需要重建提示缓存;下一次请求可能更慢或成本更高。',
       modelSearchHint: '搜索模型 / 服务商 / 连接 · ↑↓ 选择 · Enter 确认 · Esc 取消',
@@ -498,6 +640,95 @@ export const TUI_COPY_RESOURCES = {
       closeAction: 'Enter 关闭',
       thinkingUnsupported: '当前模型不支持思考级别切换。',
     },
+    'zh-TW': {
+      modelPickerTitle: '選擇模型',
+      modelSwitchCacheWarning: '⚠ 切換模型可能需要重建提示快取;下一次請求可能較慢或成本較高。',
+      modelSearchHint: '搜尋模型 / 服務商 / 連線 · ↑↓ 選擇 · Enter 確認 · Esc 取消',
+      searchLabel: '搜尋',
+      noMatchingModels: '沒有符合的模型',
+      selectPickerHint: '↑↓ 選擇 · Enter 確認 · Esc 關閉',
+      providerConfigured: '已設定',
+      addAccount: '新增帳號',
+      thinkingLevels: {
+        off: '關',
+        minimal: '最小',
+        low: '低',
+        medium: '中',
+        high: '高',
+        xhigh: '超高',
+        max: '最高',
+      },
+      defaultThinkingLevel: '預設',
+      thinkingPickerTitle: '選擇思考級別',
+      currentMarker: '目前',
+      defaultMarker: '預設',
+      setupTitle: '設定模型服務商',
+      baseUrlLabel: 'Base URL',
+      apiKeyLabel: 'API key',
+      connectionNameLabel: '名稱',
+      connectionSlugLabel: '識別碼(slug)',
+      identityHint: '為該連線起名,或保留預設值自動分配 · Enter 繼續 · Esc 返回',
+      identitySlugInvalid: '識別碼需為 2–64 位小寫字母、數字或連字號',
+      identityNameTooLong: '名稱不能超過 256 個字元',
+      onboardingUnavailable: 'Onboarding 無法使用:目前執行環境未提供設定入口。',
+      onboardingRequestFailed: '無法連線 Runtime Host,請重試。',
+      onboardingRejections: {
+        provider_unsupported: '此服務商不支援 API key 設定。',
+        connection_not_found: '此連線已不存在,請重新開啟 /setup 後重試。',
+        credential_not_configured: '需要填寫 API key。',
+        base_url_not_configured: '此服務商需要填寫 Base URL。',
+        catalog_full: '連線目錄已滿。',
+        model_unavailable: '所選模型已無法使用,請重新驗證並選擇。',
+        slug_taken: '該識別碼已被佔用。請更換,或清空後自動分配。',
+        superseded: '此連線已變更,請重新開啟 /setup 後重試。',
+      },
+      onboardingFailures: {
+        auth: '服務商身分驗證失敗,請檢查 API key 後重試。',
+        timeout: '服務商驗證逾時,請重試。',
+        provider_unavailable: '服務商目前無法使用,請稍後重試。',
+        network: '網路錯誤中斷了服務商驗證,請重試。',
+        invalid_response: '服務商傳回無效回應,請重試。',
+        unknown: '服務商驗證失敗,請重試。',
+      },
+      accountSavedRefreshFailed: '帳號已儲存,但模型清單尚未重新整理。重新啟動 Maka 後會重新載入。',
+      listProvidersFailed: '無法讀取已設定的連線。',
+      noConfigurableProviders: '沒有可設定的 API key 服務商。',
+      baseUrlRequired: '需要填寫 Base URL',
+      baseUrlInvalid: 'Base URL 不是有效的 URL',
+      baseUrlProtocol: 'Base URL 必須使用 http 或 https',
+      baseUrlCredentials: 'Base URL 不能包含帳號密碼',
+      baseUrlQuery: 'Base URL 不能包含查詢字串或片段',
+      baseUrlTooLong: 'Base URL 不能超過 2048 位元組',
+      selectModelBeforeSaving: '至少選擇一個模型再儲存',
+      reuseBaseUrlHint: '留空以重用已儲存的 Base URL,或輸入新位址替換 · Esc 返回選擇服務商',
+      enterBaseUrlHint: '輸入中轉站的 Base URL(http/https)· Esc 返回選擇服務商',
+      continueAction: 'Enter 繼續',
+      providerSearchHint: '搜尋服務商,↑↓ 選擇 · Enter 確認 · Esc 取消',
+      noMatchingProviders: '沒有符合的服務商',
+      keyHints: {
+        reuse: {
+          baseUrl: '留空以重用已儲存的 key,或輸入新 key 輪換 · Esc 返回 Base URL',
+          provider: '留空以重用已儲存的 key,或輸入新 key 輪換 · Esc 返回選擇服務商',
+          // identity 步驟只存在於新建流程,reuse 不會落到這裡;沿用 provider 文案保持記錄完整。
+          identity: '留空以重用已儲存的 key,或輸入新 key 輪換 · Esc 返回選擇服務商',
+        },
+        enter: {
+          baseUrl: '輸入 API key · 僅儲存在本機 · Esc 返回 Base URL',
+          provider: '輸入 API key · 僅儲存在本機 · Esc 返回選擇服務商',
+          identity: '輸入 API key · 僅儲存在本機 · Esc 返回命名連線',
+        },
+      },
+      submitAction: 'Enter 送出',
+      verifyingKey: '正在驗證 key…',
+      modelSelectionHint: '搜尋模型,↑↓ 選擇 · Space 切換 · Enter 儲存 · Esc 返回',
+      selectedModels: '已選 {count}',
+      selectedModelsAndSave: '已選 {count} · Enter 儲存',
+      saving: '正在儲存…',
+      complete: '完成',
+      enabledModels: '已啟用模型:{count}',
+      closeAction: 'Enter 關閉',
+      thinkingUnsupported: '目前模型不支援思考級別切換。',
+    },
   },
   'primary-guidance': {
     en: {
@@ -551,7 +782,7 @@ export const TUI_COPY_RESOURCES = {
         ],
       },
     },
-    zh: {
+    'zh-CN': {
       welcome: {
         tagline: '陪你把事做完',
         start: '输入消息开始对话',
@@ -602,6 +833,57 @@ export const TUI_COPY_RESOURCES = {
         ],
       },
     },
+    'zh-TW': {
+      welcome: {
+        tagline: '陪你把事做完',
+        start: '輸入訊息開始對話',
+        session: '切換或恢復會話',
+        model: '切換模型',
+        setup: '設定模型服務商',
+      },
+      commands: {
+        compact: '壓縮會話上下文',
+        context: '檢視最近一次請求的上下文用量',
+        copy: '複製最後一則回覆(或 /copy all)到剪貼簿',
+        exit: '退出 Maka',
+        goal: '檢視自主目標狀態',
+        graph: '檢視、啟用、停用 Graph 模式,或執行一次 Graph 任務',
+        help: '檢視命令和快捷鍵',
+        mcp: '管理用戶端 MCP 伺服器與工具發佈狀態',
+        model: '選擇模型',
+        move: '將目前會話移到其他目錄',
+        new: '建立會話',
+        permissions: '設定會話權限',
+        recap: '用一句話總結目前會話',
+        rename: '重新命名目前會話',
+        resume: '從安全邊界恢復最近一次中斷的執行',
+        rewind: '回退到較早的對話輪次',
+        session: '切換或恢復會話',
+        setup: '設定模型服務商(API Key)',
+        side: '開啟臨時 Side Conversation',
+        skill: '呼叫 Skill(也可直接輸入 /skill:<name>)',
+        swarm: '檢視、啟用、停用 Swarm 模式,或執行一次 Swarm 任務',
+        thinking: '設定思考級別',
+        transcript: '在 Maka 內瀏覽完整對話記錄',
+      },
+      help: {
+        commandsHeading: '命令',
+        userCommand: '  !<command> — 執行一次僅使用者可見的 shell 命令',
+        keybindingsHeading: '快捷鍵',
+        keybindings: [
+          '  Ctrl+O — 展開或摺疊所有工具輸出',
+          '  Ctrl+T — 展開或摺疊檢視中的所有思考區塊',
+          '  使用終端機或觸控板捲動對話記錄',
+          '  Enter(任務執行中)— 將訊息注入目前任務',
+          '  Alt+Enter(任務執行中)— 將訊息排入下一輪',
+          '  Alt+↑ — 將已排隊訊息取回編輯器重新編輯',
+          '  Esc Esc(任務執行中)— 中斷目前任務',
+          '  Esc Esc(空閒時)— 回退到較早的輪次',
+          '  Ctrl+C — 停止任務、清空輸入,或連續按兩次退出',
+          '  Ctrl+D — 輸入為空時退出',
+        ],
+      },
+    },
   },
   rewind: {
     en: {
@@ -616,7 +898,7 @@ export const TUI_COPY_RESOURCES = {
       pickerHint:
         'Return to before the selected turn (discards it and later turns; the prompt refills the input box) · enter select / esc cancel',
     },
-    zh: {
+    'zh-CN': {
       pickerTitle: '回退',
       pending: '正在回退到该轮之前…',
       doneRefilled:
@@ -627,6 +909,17 @@ export const TUI_COPY_RESOURCES = {
       busy: '无法回退:当前有正在进行的操作 — 请等待其完成,或中断(Esc)后重试。',
       pickerHint: '回到选定轮次之前(丢弃该轮及之后,prompt 回填输入框) · enter 选择 / esc 取消',
     },
+    'zh-TW': {
+      pickerTitle: '回退',
+      pending: '正在回退到該輪之前…',
+      doneRefilled:
+        '已回退到該輪之前(分支為新任務,原任務保留),該輪 prompt 已回填輸入框,可修改後重新傳送。',
+      doneKeptDraft:
+        '已回退到該輪之前(分支為新任務,原任務保留)。輸入框已有未傳送內容,未覆蓋;該輪 prompt 已存入輸入歷史,可按 ↑ 找回。',
+      noTargets: '沒有可回退的輪次。',
+      busy: '無法回退:目前有正在進行的操作 — 請等待完成,或中斷(Esc)後重試。',
+      pickerHint: '回到選定輪次之前(捨棄該輪及之後,prompt 回填輸入框) · enter 選擇 / esc 取消',
+    },
   },
   skills: {
     en: {
@@ -649,7 +942,7 @@ export const TUI_COPY_RESOURCES = {
         too_many_requests: 'too many requests',
       },
     },
-    zh: {
+    'zh-CN': {
       pickerTitle: '调用技能',
       usage: 'Usage: /skill,或直接在消息中输入 /skill:<name>',
       noneAvailable: '当前没有可调用的技能。',
@@ -669,6 +962,26 @@ export const TUI_COPY_RESOURCES = {
         too_many_requests: '调用请求过多',
       },
     },
+    'zh-TW': {
+      pickerTitle: '呼叫技能',
+      usage: 'Usage: /skill,或直接在訊息中輸入 /skill:<name>',
+      noneAvailable: '目前沒有可呼叫的技能。',
+      loaded: '已載入技能:{names}',
+      loadFailed: '無法載入技能 {failures};{outcome}',
+      outcomeNoRequest: '未發起模型請求。',
+      outcomeMarkersNotSent: '失敗的呼叫標記未傳送給模型。',
+      failedItem: '/skill:{request}({reason})',
+      tooManyRequestsItem: '請求超過 {limit} 個上限({reason})',
+      listSeparator: '、',
+      failureReasons: {
+        invalid_name: '名稱無效',
+        not_found: '找不到',
+        disabled: '已停用',
+        host_incompatible: '目前主機缺少其相依工具',
+        resolution_failed: '解析失敗',
+        too_many_requests: '呼叫請求過多',
+      },
+    },
   },
   'connection-identity': {
     en: {
@@ -684,7 +997,7 @@ export const TUI_COPY_RESOURCES = {
       accountDisabled:
         'The original account is disabled. Enable it, or run /model and choose a new account and model.',
     },
-    zh: {
+    'zh-CN': {
       withRecovery: '{notice}{recovery}',
       emptyChoiceRecovery:
         '如果 /model 没有可选项,请先添加或启用连接(API Key 连接可运行 /setup)。',
@@ -693,6 +1006,15 @@ export const TUI_COPY_RESOURCES = {
       identityMismatch: '任务保存的账号身份与当前连接不一致;运行 /model 重新选择账号和模型。',
       accountDisabled: '原账号已停用;请启用该账号,或运行 /model 选择新账号和模型。',
     },
+    'zh-TW': {
+      withRecovery: '{notice}{recovery}',
+      emptyChoiceRecovery:
+        '如果 /model 沒有可選項,請先新增或啟用連線(API Key 連線可執行 /setup)。',
+      confirmAccount: '此任務來自舊版本,需要確認一次帳號。執行 /model 選擇現有帳號和模型。',
+      accountDeleted: '原帳號已刪除;執行 /model 選擇新帳號和模型後繼續。',
+      identityMismatch: '任務儲存的帳號身分與目前連線不一致;執行 /model 重新選擇帳號和模型。',
+      accountDisabled: '原帳號已停用;請啟用該帳號,或執行 /model 選擇新帳號和模型。',
+    },
   },
   'session-actions': {
     en: {
@@ -700,10 +1022,14 @@ export const TUI_COPY_RESOURCES = {
       newSessionFailed:
         'Could not start a new session: a local command could not be stopped. Press Ctrl+C to stop it, then try again.',
     },
-    zh: {
+    'zh-CN': {
       foreignScanFailed: '读取外部对话失败:{detail}',
       newSessionFailed: '无法开始新会话:无法停止本地命令。请按 Ctrl+C 停止命令后重试。',
     },
+    'zh-TW': {
+      foreignScanFailed: '讀取外部對話失敗:{detail}',
+      newSessionFailed: '無法開始新工作階段,請稍後重試。',
+    },
   },
   'pending-queue': {
     en: {
@@ -711,11 +1037,16 @@ export const TUI_COPY_RESOURCES = {
       queuedLabel: 'Queued:',
       requeueHint: 'Alt+↑ take queued messages back to re-edit',
     },
-    zh: {
+    'zh-CN': {
       steeringLabel: '注入:',
       queuedLabel: '已排队:',
       requeueHint: 'Alt+↑ 取回队列以重新编辑',
     },
+    'zh-TW': {
+      steeringLabel: '調整方向:',
+      queuedLabel: '已排入佇列:',
+      requeueHint: 'Alt+↑ 取回佇列訊息重新編輯',
+    },
   },
   'session-status': {
     en: {
@@ -726,7 +1057,7 @@ export const TUI_COPY_RESOURCES = {
       signInRequired: 'needs sign-in',
       stopped: 'stopped',
     },
-    zh: {
+    'zh-CN': {
       running: '进行中',
       waitingForUser: '等你确认',
       permissionRequired: '需要权限',
@@ -734,5 +1065,13 @@ export const TUI_COPY_RESOURCES = {
       signInRequired: '需要重新登录',
       stopped: '已中止',
     },
+    'zh-TW': {
+      running: '進行中',
+      waitingForUser: '等你確認',
+      permissionRequired: '需要權限',
+      connectionRequired: '需要連線',
+      signInRequired: '需要重新登入',
+      stopped: '已中止',
+    },
   },
 } as const;
diff --git a/packages/core/src/__tests__/redaction.test.ts b/packages/core/src/__tests__/redaction.test.ts
index 4e593790a5..57094a2c32 100644
--- a/packages/core/src/__tests__/redaction.test.ts
+++ b/packages/core/src/__tests__/redaction.test.ts
@@ -23,6 +23,8 @@ import { describe, test } from 'node:test';
 import {
   generalizedErrorMessage,
   generalizedErrorMessageChinese,
+  generalizedErrorMessageForLocale,
+  generalizedErrorMessageTraditionalChinese,
   redactSecrets,
 } from '../redaction.js';
 
@@ -381,3 +383,38 @@ describe('generalizedErrorMessageChinese', () => {
     );
   });
 });
+
+describe('localized generalized error messages', () => {
+  test('routes one shared classification through each locale catalog', () => {
+    const error = new Error('HTTP 503 from provider');
+    assert.equal(
+      generalizedErrorMessageForLocale(error, 'fallback', 'en'),
+      'Provider returned an error',
+    );
+    assert.equal(generalizedErrorMessageForLocale(error, '後備', 'zh-CN'), '模型服务返回错误');
+    assert.equal(generalizedErrorMessageForLocale(error, '備援', 'zh-TW'), '模型服務傳回錯誤');
+  });
+
+  test('uses Taiwan terminology for Traditional Chinese categories', () => {
+    for (const [raw, expected] of [
+      ['Request timeout after 30s', '請求逾時'],
+      ['HTTP 429 Too Many Requests', '已達模型速率限制'],
+      ['401 Unauthorized', '驗證失敗'],
+      ['HTTP 500 Internal Server Error', '模型服務傳回錯誤'],
+      ['network unreachable', '網路錯誤'],
+      ['something weird happened', '操作失敗'],
+    ]) {
+      assert.equal(generalizedErrorMessageTraditionalChinese(new Error(raw)), expected);
+    }
+  });
+
+  test('routes each resolved locale without changing the supplied fallback', () => {
+    const error = new Error('something weird happened');
+    assert.equal(generalizedErrorMessageForLocale(error, '简中后备', 'zh-CN'), '简中后备');
+    assert.equal(generalizedErrorMessageForLocale(error, '繁中備援', 'zh-TW'), '繁中備援');
+    assert.equal(
+      generalizedErrorMessageForLocale(error, 'English fallback', 'en'),
+      'English fallback',
+    );
+  });
+});
diff --git a/packages/core/src/__tests__/relative-time.test.ts b/packages/core/src/__tests__/relative-time.test.ts
index 2507f2c167..9cb6cc031d 100644
--- a/packages/core/src/__tests__/relative-time.test.ts
+++ b/packages/core/src/__tests__/relative-time.test.ts
@@ -37,13 +37,13 @@ describe('relative timestamp labels', () => {
     resetRelativeTimeFormatters();
 
     for (const ageMs of [0, 1_000, 30_000, 59_999]) {
-      assert.equal(formatRelativeTimestamp(NOW - ageMs, NOW, 'zh'), '刚刚');
+      assert.equal(formatRelativeTimestamp(NOW - ageMs, NOW, 'zh-CN'), '刚刚');
       assert.equal(formatRelativeTimestamp(NOW - ageMs, NOW, 'en'), 'just now');
-      assert.equal(formatCompactTimestamp(NOW - ageMs, NOW, 'zh'), '刚刚');
-      assert.equal(formatSidebarTimestamp(NOW - ageMs, NOW, 'zh'), '刚刚');
+      assert.equal(formatCompactTimestamp(NOW - ageMs, NOW, 'zh-CN'), '刚刚');
+      assert.equal(formatSidebarTimestamp(NOW - ageMs, NOW, 'zh-CN'), '刚刚');
     }
 
-    assert.equal(formatRelativeTimestamp(NOW - 60_000, NOW, 'zh'), '1分钟前');
+    assert.equal(formatRelativeTimestamp(NOW - 60_000, NOW, 'zh-CN'), '1分钟前');
     assert.equal(formatRelativeTimestamp(NOW - 60_000, NOW, 'en'), '1 minute ago');
   });
 
@@ -54,7 +54,7 @@ describe('relative timestamp labels', () => {
   });
 
   it('uses scan-friendly units for sidebar timestamps', () => {
-    for (const locale of ['zh', 'en'] as const) {
+    for (const locale of ['zh-CN', 'en'] as const) {
       for (const [ageMs, expected] of [
         [60_000, '1min'],
         [46 * 60_000, '46min'],
diff --git a/packages/core/src/__tests__/settings.test.ts b/packages/core/src/__tests__/settings.test.ts
index acc1106af7..b9eee9c4ac 100644
--- a/packages/core/src/__tests__/settings.test.ts
+++ b/packages/core/src/__tests__/settings.test.ts
@@ -100,6 +100,32 @@ describe('custom pet selection settings', () => {
   });
 });
 
+describe('UI locale preferences', () => {
+  test('preserves every supported preference and migrates the former generic zh value', () => {
+    for (const uiLocale of ['auto', 'zh-CN', 'zh-TW', 'en'] as const) {
+      const normalized = normalizeSettings({
+        personalization: {
+          displayName: '',
+          assistantTone: '',
+          uiLocale,
+          selectedPetId: null,
+        },
+      });
+      assert.strictEqual(normalized.personalization.uiLocale, uiLocale);
+    }
+
+    const normalizedLegacy = normalizeSettings({
+      personalization: {
+        displayName: '',
+        assistantTone: '',
+        uiLocale: 'zh',
+        selectedPetId: null,
+      },
+    });
+    assert.strictEqual(normalizedLegacy.personalization.uiLocale, 'zh-CN');
+  });
+});
+
 test('shell settings default, normalize, and merge through their shared boundary', () => {
   const defaults = createDefaultSettings();
   assert.deepStrictEqual(defaults.shell, { preference: 'auto', executable: '' });
diff --git a/packages/core/src/__tests__/tool-quiet-preview.test.ts b/packages/core/src/__tests__/tool-quiet-preview.test.ts
index 49037bf1e1..1404b67ad2 100644
--- a/packages/core/src/__tests__/tool-quiet-preview.test.ts
+++ b/packages/core/src/__tests__/tool-quiet-preview.test.ts
@@ -66,9 +66,19 @@ describe('formatToolInvocationLine', () => {
           ],
         },
       },
-      'zh',
+      'zh-CN',
     );
     assert.equal(line, '选哪个方案? 等 2 问');
+    assert.equal(
+      formatToolInvocationLine(
+        {
+          toolName: 'AskUserQuestion',
+          args: { questions: [{ question: '選哪個方案?' }, { question: '繼續嗎?' }] },
+        },
+        'zh-TW',
+      ),
+      '選哪個方案? 等 2 問',
+    );
   });
 
   it('keeps the ScheduledTask title headline', () => {
@@ -77,7 +87,7 @@ describe('formatToolInvocationLine', () => {
         toolName: 'ScheduledTask',
         args: { title: '每天 9:00 生成日报', schedule: { kind: 'cron' } },
       },
-      'zh',
+      'zh-CN',
     );
     assert.equal(line, '每天 9:00 生成日报');
   });
@@ -170,7 +180,7 @@ describe('projectToolArgsPreview', () => {
       size: { cols: 80, rows: 24 },
     });
     const preview = projectToolArgsPreview('WriteStdin', projected);
-    const line = formatToolInvocationLine({ toolName: 'WriteStdin', args: preview }, 'zh');
+    const line = formatToolInvocationLine({ toolName: 'WriteStdin', args: preview }, 'zh-CN');
     assert.ok(line !== undefined);
     assert.match(line, /后台终端交互/);
     assert.match(line, /80x24/);
diff --git a/packages/core/src/__tests__/ui-locale.test.ts b/packages/core/src/__tests__/ui-locale.test.ts
index 6fcc097723..3304657925 100644
--- a/packages/core/src/__tests__/ui-locale.test.ts
+++ b/packages/core/src/__tests__/ui-locale.test.ts
@@ -24,11 +24,61 @@ import {
   defineUiMessageCatalog,
   formatUiMessage,
   isUiLocale,
+  isUiLocalePreference,
+  normalizeUiLocalePreference,
   resolveSystemUiLocale,
+  resolveUiLocale,
   resolveUiMessageCatalog,
   uiLocaleToIntlLocale,
 } from '../ui-locale.js';
 
+describe('UI locale', () => {
+  it('accepts only the supported resolved locales and preferences', () => {
+    assert.equal(['zh-CN', 'zh-TW', 'en'].every(isUiLocale), true);
+    assert.equal(isUiLocale('zh'), false);
+    assert.equal(['auto', 'zh-CN', 'zh-TW', 'en'].every(isUiLocalePreference), true);
+  });
+
+  it('normalizes the legacy persisted preference without widening the locale contract', () => {
+    assert.equal(normalizeUiLocalePreference('zh'), 'zh-CN');
+    assert.equal(normalizeUiLocalePreference('zh-TW'), 'zh-TW');
+    assert.equal(normalizeUiLocalePreference('unsupported'), 'auto');
+  });
+
+  for (const [languages, expected] of [
+    [['zh-CN'], 'zh-CN'],
+    [['zh-SG'], 'zh-CN'],
+    [['zh-Hans'], 'zh-CN'],
+    [['zh-TW'], 'zh-TW'],
+    [['zh-Hant-TW'], 'zh-TW'],
+    [['zh-HK'], 'zh-TW'],
+    [['zh_MO'], 'zh-TW'],
+    [['zh_TW.UTF-8'], 'zh-TW'],
+    [['fr-FR', 'en-US'], 'en'],
+    [[], 'en'],
+  ] as const) {
+    it(`resolves system languages ${languages.join(',')} to ${expected}`, () => {
+      assert.equal(resolveSystemUiLocale(languages), expected);
+    });
+  }
+
+  it('resolves explicit preferences and overrides before the system locale', () => {
+    assert.equal(resolveUiLocale('auto', 'zh-TW'), 'zh-TW');
+    assert.equal(resolveUiLocale('zh-CN', 'zh-TW'), 'zh-CN');
+    assert.equal(resolveUiLocale('zh-CN', 'zh-CN', 'en'), 'en');
+  });
+
+  it('keeps every locale guard and formatter in step with UI_LOCALES', () => {
+    for (const locale of UI_LOCALES) {
+      assert.ok(isUiLocale(locale), locale);
+      assert.equal(resolveSystemUiLocale([locale]), locale);
+      assert.equal(uiLocaleToIntlLocale(locale), locale);
+    }
+    const intlLocales = UI_LOCALES.map(uiLocaleToIntlLocale);
+    assert.equal(new Set(intlLocales).size, UI_LOCALES.length);
+  });
+});
+
 describe('UI message catalogs', () => {
   it('falls back to complete English copy for missing translations', () => {
     const catalog = defineUiMessageCatalog<{
@@ -36,12 +86,13 @@ describe('UI message catalogs', () => {
       detail: { ready: string; waiting: string };
     }>()({
       en: { title: 'Status', detail: { ready: 'Ready', waiting: 'Waiting' } },
-      zh: { title: '状态', detail: { ready: '就绪' } },
+      'zh-CN': { title: '状态', detail: { ready: '就绪' } },
     });
 
     assert.deepEqual(resolveUiMessageCatalog(catalog), {
       en: { title: 'Status', detail: { ready: 'Ready', waiting: 'Waiting' } },
-      zh: { title: '状态', detail: { ready: '就绪', waiting: 'Waiting' } },
+      'zh-CN': { title: '状态', detail: { ready: '就绪', waiting: 'Waiting' } },
+      'zh-TW': { title: 'Status', detail: { ready: 'Ready', waiting: 'Waiting' } },
     });
   });
 
@@ -56,13 +107,4 @@ describe('UI message catalogs', () => {
     assert.equal(formatUiMessage('Hello {name}', {}, 'en'), 'Hello {name}');
     assert.equal(formatUiMessage('{constructor}', {}, 'en'), '{constructor}');
   });
-
-  it('keeps every locale guard in step with UI_LOCALES', () => {
-    for (const locale of UI_LOCALES) {
-      assert.ok(isUiLocale(locale), locale);
-      assert.equal(resolveSystemUiLocale([locale]), locale);
-    }
-    const intlLocales = UI_LOCALES.map(uiLocaleToIntlLocale);
-    assert.equal(new Set(intlLocales).size, UI_LOCALES.length);
-  });
 });
diff --git a/packages/core/src/redaction.ts b/packages/core/src/redaction.ts
index 9165533c73..add461e25a 100644
--- a/packages/core/src/redaction.ts
+++ b/packages/core/src/redaction.ts
@@ -17,6 +17,8 @@
  * under the License.
  */
 
+import type { UiCatalog, UiLocale } from './ui-locale.js';
+
 const SENSITIVE_KEY_SUFFIXES = new Set([
   'auth',
   'authorization',
@@ -206,23 +208,62 @@ function isAssignmentSensitiveKey(key: string): boolean {
   return suffix !== 'auth' && suffix !== 'authorization';
 }
 
-export function generalizedErrorMessage(error: unknown, fallback = 'Operation failed'): string {
+type GeneralizedErrorCategory = 'timeout' | 'rateLimit' | 'authentication' | 'provider' | 'network';
+
+const GENERALIZED_ERROR_COPY: UiCatalog<Record<GeneralizedErrorCategory, string>> = {
+  'zh-CN': {
+    timeout: '请求超时',
+    rateLimit: '触发模型速率限制',
+    authentication: '鉴权失败',
+    provider: '模型服务返回错误',
+    network: '网络错误',
+  },
+  'zh-TW': {
+    timeout: '請求逾時',
+    rateLimit: '已達模型速率限制',
+    authentication: '驗證失敗',
+    provider: '模型服務傳回錯誤',
+    network: '網路錯誤',
+  },
+  en: {
+    timeout: 'Request timed out',
+    rateLimit: 'Rate limit exceeded',
+    authentication: 'Authentication failed',
+    provider: 'Provider returned an error',
+    network: 'Network error',
+  },
+};
+
+function classifyGeneralizedError(error: unknown): GeneralizedErrorCategory | null {
   const message = error instanceof Error ? error.message : String(error);
   const redacted = redactSecrets(message);
   const lower = redacted.toLowerCase();
-  if (lower.includes('timeout')) return 'Request timed out';
-  if (lower.includes('429') || lower.includes('rate')) return 'Rate limit exceeded';
+  if (lower.includes('timeout')) return 'timeout';
+  if (lower.includes('429') || lower.includes('rate')) return 'rateLimit';
   if (lower.includes('401') || lower.includes('403') || isAuthenticationErrorText(lower))
-    return 'Authentication failed';
-  if (lower.includes('5') && /\b5\d\d\b/.test(lower)) return 'Provider returned an error';
+    return 'authentication';
+  if (lower.includes('5') && /\b5\d\d\b/.test(lower)) return 'provider';
   if (
     lower.includes('network') ||
     lower.includes('fetch') ||
     lower.includes('econn') ||
     lower.includes('enotfound')
   )
-    return 'Network error';
-  return fallback;
+    return 'network';
+  return null;
+}
+
+function localizedGeneralizedErrorMessage(
+  error: unknown,
+  fallback: string,
+  locale: UiLocale,
+): string {
+  const category = classifyGeneralizedError(error);
+  return category ? GENERALIZED_ERROR_COPY[locale][category] : fallback;
+}
+
+export function generalizedErrorMessage(error: unknown, fallback = 'Operation failed'): string {
+  return localizedGeneralizedErrorMessage(error, fallback, 'en');
 }
 
 /**
@@ -239,22 +280,22 @@ export function generalizedErrorMessage(error: unknown, fallback = 'Operation fa
  * UX when the classifier can't categorize.
  */
 export function generalizedErrorMessageChinese(error: unknown, fallback = '操作失败'): string {
-  const message = error instanceof Error ? error.message : String(error);
-  const redacted = redactSecrets(message);
-  const lower = redacted.toLowerCase();
-  if (lower.includes('timeout')) return '请求超时';
-  if (lower.includes('429') || lower.includes('rate')) return '触发模型速率限制';
-  if (lower.includes('401') || lower.includes('403') || isAuthenticationErrorText(lower))
-    return '鉴权失败';
-  if (lower.includes('5') && /\b5\d\d\b/.test(lower)) return '模型服务返回错误';
-  if (
-    lower.includes('network') ||
-    lower.includes('fetch') ||
-    lower.includes('econn') ||
-    lower.includes('enotfound')
-  )
-    return '网络错误';
-  return fallback;
+  return localizedGeneralizedErrorMessage(error, fallback, 'zh-CN');
+}
+
+export function generalizedErrorMessageTraditionalChinese(
+  error: unknown,
+  fallback = '操作失敗',
+): string {
+  return localizedGeneralizedErrorMessage(error, fallback, 'zh-TW');
+}
+
+export function generalizedErrorMessageForLocale(
+  error: unknown,
+  fallback: string,
+  locale: UiLocale,
+): string {
+  return localizedGeneralizedErrorMessage(error, fallback, locale);
 }
 
 export function isAuthenticationErrorText(message: string): boolean {
diff --git a/packages/core/src/relative-time.ts b/packages/core/src/relative-time.ts
index 5ce6f475bd..76ab7511c8 100644
--- a/packages/core/src/relative-time.ts
+++ b/packages/core/src/relative-time.ts
@@ -48,7 +48,8 @@ const SIDEBAR_TIME_BUCKETS = [
 ] as const;
 
 const JUST_NOW: UiCatalog<string> = {
-  zh: '刚刚',
+  'zh-CN': '刚刚',
+  'zh-TW': '剛剛',
   en: 'just now',
 };
 
@@ -93,7 +94,7 @@ function getAbsoluteFormat(uiLocale: UiLocale): Intl.DateTimeFormat {
  * reading a relative label falls back to and a tooltip shows; `@maka/ui` had
  * its own uncached copy of the same `Intl` options until this became public.
  */
-export function formatAbsoluteTimestamp(ts: number, locale: UiLocale = 'zh'): string {
+export function formatAbsoluteTimestamp(ts: number, locale: UiLocale = 'zh-CN'): string {
   return getAbsoluteFormat(locale).format(new Date(ts));
 }
 
@@ -105,7 +106,7 @@ export function formatAbsoluteTimestamp(ts: number, locale: UiLocale = 'zh'): st
 export function formatRelativeTimestamp(
   ts: number,
   now: number = Date.now(),
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): string {
   const diffMs = relativeAgeMs(ts, now);
   if (diffMs < JUST_NOW_MS) {
@@ -158,7 +159,7 @@ function getCompactFormats(uiLocale: UiLocale): {
 export function formatCompactTimestamp(
   ts: number,
   now: number = Date.now(),
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): string {
   const diffMs = relativeAgeMs(ts, now);
   if (diffMs <= RELATIVE_HORIZON_MS) return formatRelativeTimestamp(ts, now, locale);
@@ -178,7 +179,7 @@ export function formatCompactTimestamp(
 export function formatSidebarTimestamp(
   ts: number,
   now: number = Date.now(),
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): string {
   const diffMs = relativeAgeMs(ts, now);
   if (diffMs < JUST_NOW_MS) return JUST_NOW[locale];
diff --git a/packages/core/src/settings.ts b/packages/core/src/settings.ts
index b6d2daccec..0c02f0c50f 100644
--- a/packages/core/src/settings.ts
+++ b/packages/core/src/settings.ts
@@ -40,6 +40,7 @@ import type { UsageProvenance } from './usage-ledger-merge.js';
 import {
   UI_LOCALE_PREFERENCES,
   isUiLocalePreference,
+  normalizeUiLocalePreference,
   type UiLocalePreference,
 } from './ui-locale.js';
 import { normalizeSubagentSettings, type SubagentSettings } from './subagent-settings.js';
@@ -978,13 +979,12 @@ export function normalizeSettings(input: unknown): AppSettings {
     // PR-LANG-PREF-0: closed-enum fail-closed for the new
     // `personalization.uiLocale` preference. mergeSettings spreads
     // raw user values, so an unknown value would otherwise reach the
-    // renderer outside the closed reactive-locale contract. Fall back to
-    // 'auto' on any miss.
+    // renderer outside the closed reactive-locale contract. Preserve the
+    // former generic `zh` preference as Simplified Chinese, then fall back to
+    // 'auto' on any other miss.
     personalization: {
       ...base.personalization,
-      uiLocale: isUiLocalePreference(base.personalization.uiLocale)
-        ? base.personalization.uiLocale
-        : 'auto',
+      uiLocale: normalizeUiLocalePreference(base.personalization.uiLocale),
       selectedPetId: normalizeSelectedPetId(base.personalization.selectedPetId),
     },
     botChat: normalizeBotChatSettings(base.botChat, value.botChat),
diff --git a/packages/core/src/tool-quiet-preview.ts b/packages/core/src/tool-quiet-preview.ts
index e2adb0d7b6..f52d03b966 100644
--- a/packages/core/src/tool-quiet-preview.ts
+++ b/packages/core/src/tool-quiet-preview.ts
@@ -52,7 +52,7 @@ interface QuietPreviewStrings {
 }
 
 const STRINGS_BY_LOCALE: Record<UiLocale, QuietPreviewStrings> = {
-  zh: {
+  'zh-CN': {
     backgroundTerminal: '后台终端交互',
     empty: '(空)',
     done: '已完成',
@@ -62,6 +62,16 @@ const STRINGS_BY_LOCALE: Record<UiLocale, QuietPreviewStrings> = {
     bytes: (n) => `共 ${n} 字节`,
     moreQuestions: (total) => (total > 1 ? ` 等 ${total} 问` : ''),
   },
+  'zh-TW': {
+    backgroundTerminal: '後臺終端互動',
+    empty: '(空)',
+    done: '已完成',
+    notDone: '未完成',
+    replacements: (n) => `${n} 處`,
+    written: '已寫入',
+    bytes: (n) => `共 ${n} 位元組`,
+    moreQuestions: (total) => (total > 1 ? ` 等 ${total} 問` : ''),
+  },
   en: {
     backgroundTerminal: 'Background terminal interaction',
     empty: '(empty)',
@@ -75,7 +85,7 @@ const STRINGS_BY_LOCALE: Record<UiLocale, QuietPreviewStrings> = {
 };
 
 function strings(locale: UiLocale): QuietPreviewStrings {
-  return STRINGS_BY_LOCALE[locale] ?? STRINGS_BY_LOCALE.zh;
+  return STRINGS_BY_LOCALE[locale] ?? STRINGS_BY_LOCALE['zh-CN'];
 }
 
 // ── Tool command extraction ──────────────────────────────────────────────
@@ -230,7 +240,7 @@ export interface ToolInvocationInput {
  */
 export function formatToolInvocationLine(
   item: ToolInvocationInput,
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): string | undefined {
   const s = strings(locale);
   const args = asRecord(item.args);
@@ -518,7 +528,7 @@ export interface QuietPreview {
  * Primary list/text fields become the main body; remaining fields (error, ok,
  * truncated, …) are appended so diagnostics cannot vanish.
  */
-export function formatQuietJsonValue(value: unknown, locale: UiLocale = 'zh'): QuietPreview {
+export function formatQuietJsonValue(value: unknown, locale: UiLocale = 'zh-CN'): QuietPreview {
   const s = strings(locale);
   if (value === null || value === undefined) {
     return { body: s.empty };
@@ -677,7 +687,7 @@ function formatArrayAsBody(values: unknown[], locale: UiLocale): string {
 export function formatAsKeyValueLines(
   record: Record<string, unknown>,
   depth = 0,
-  locale: UiLocale = 'zh',
+  locale: UiLocale = 'zh-CN',
 ): string {
   const s = strings(locale);
   if (depth > 3) return redactSecrets(String(record));
diff --git a/packages/core/src/ui-locale.ts b/packages/core/src/ui-locale.ts
index 3a7d161734..069254891c 100644
--- a/packages/core/src/ui-locale.ts
+++ b/packages/core/src/ui-locale.ts
@@ -20,7 +20,7 @@
 import { IntlMessageFormat } from 'intl-messageformat';
 
 /** Resolved locales supported by human-facing Maka clients. */
-export const UI_LOCALES = ['zh', 'en'] as const;
+export const UI_LOCALES = ['zh-CN', 'zh-TW', 'en'] as const;
 
 export type UiLocale = (typeof UI_LOCALES)[number];
 
@@ -122,19 +122,29 @@ function isMessageRecord(value: unknown): value is Readonly<Record<string, unkno
 }
 
 export function isUiLocale(value: unknown): value is UiLocale {
-  return value === 'zh' || value === 'en';
+  return value === 'zh-CN' || value === 'zh-TW' || value === 'en';
 }
 
 export function isUiLocalePreference(value: unknown): value is UiLocalePreference {
   return value === 'auto' || isUiLocale(value);
 }
 
+/** Decode persisted or externally supplied preferences into the closed locale vocabulary. */
+export function normalizeUiLocalePreference(value: unknown): UiLocalePreference {
+  if (value === 'zh') return 'zh-CN';
+  return isUiLocalePreference(value) ? value : 'auto';
+}
+
 /** Resolve the first supported language in the operating system preference list. */
 export function resolveSystemUiLocale(languages: readonly string[] | null | undefined): UiLocale {
   for (const language of languages ?? []) {
-    const normalized = language.trim();
-    if (/^zh(?:[-_]|$)/iu.test(normalized)) return 'zh';
-    if (/^en(?:[-_]|$)/iu.test(normalized)) return 'en';
+    const normalized = language.trim().replaceAll('_', '-');
+    if (/^zh(?:[-.]|$)/iu.test(normalized)) {
+      if (/^zh-(?:tw|hk|mo)(?:[-.]|$)/iu.test(normalized)) return 'zh-TW';
+      if (/^zh-hant(?:[-.]|$)/iu.test(normalized)) return 'zh-TW';
+      return 'zh-CN';
+    }
+    if (/^en(?:[-.]|$)/iu.test(normalized)) return 'en';
   }
   return 'en';
 }
@@ -156,6 +166,6 @@ export function resolveUiLocale(
 }
 
 /** Locale identifier used by every locale-sensitive Intl formatter. */
-export function uiLocaleToIntlLocale(locale: UiLocale): 'zh-CN' | 'en' {
-  return locale === 'zh' ? 'zh-CN' : 'en';
+export function uiLocaleToIntlLocale(locale: UiLocale): UiLocale {
+  return locale;
 }
diff --git a/packages/ui/src/__tests__/conversation-copy.test.ts b/packages/ui/src/__tests__/conversation-copy.test.ts
index f1d9c37e8f..11dc7c6f2c 100644
--- a/packages/ui/src/__tests__/conversation-copy.test.ts
+++ b/packages/ui/src/__tests__/conversation-copy.test.ts
@@ -22,12 +22,13 @@ import test from 'node:test';
 import { getConversationCopy } from '../conversation-copy.js';
 
 test('labels the Chinese default thinking level as default', () => {
-  assert.equal(getConversationCopy('zh').model.defaultLevel, '默认');
+  assert.equal(getConversationCopy('zh-CN').model.defaultLevel, '默认');
+  assert.equal(getConversationCopy('zh-TW').model.defaultLevel, '預設');
 });
 
 test('explains why folder-reference messages cannot be edited and resent', () => {
   assert.equal(
-    getConversationCopy('zh').messages.editMessageDisabledDirectoryReferences,
+    getConversationCopy('zh-CN').messages.editMessageDisabledDirectoryReferences,
     '包含文件夹引用的历史消息暂不支持编辑并重发',
   );
   assert.equal(
@@ -38,7 +39,7 @@ test('explains why folder-reference messages cannot be edited and resent', () =>
 
 test('context usage explains missing data without exposing provider internals', () => {
   assert.equal(
-    getConversationCopy('zh').messages.systemNotes.contextUsageUnavailable,
+    getConversationCopy('zh-CN').messages.systemNotes.contextUsageUnavailable,
     '暂无用量数据',
   );
   assert.equal(
@@ -49,7 +50,7 @@ test('context usage explains missing data without exposing provider internals',
 
 test('context usage tooltip leads with the measured share', () => {
   assert.equal(
-    getConversationCopy('zh').messages.systemNotes.contextUsageShare(12_345, 128_000),
+    getConversationCopy('zh-CN').messages.systemNotes.contextUsageShare(12_345, 128_000),
     '已用 12,345 / 128,000 token(10%)',
   );
   assert.equal(
@@ -60,7 +61,7 @@ test('context usage tooltip leads with the measured share', () => {
 
 test('context usage tooltip keeps measured usage when the limit is unknown', () => {
   assert.equal(
-    getConversationCopy('zh').messages.systemNotes.contextUsageNoWindow(12_345),
+    getConversationCopy('zh-CN').messages.systemNotes.contextUsageNoWindow(12_345),
     '已用 12,345 token;上下文上限未知',
   );
   assert.equal(
@@ -75,7 +76,8 @@ test('context usage tooltip keeps measured usage when the limit is unknown', ()
  * five-digit second count that reads as a frozen hang (#3401).
  */
 test('providerRetryScheduled humanizes hour-scale delays in both locales', () => {
-  const zh = getConversationCopy('zh').messages.providerRetryScheduled;
+  const zh = getConversationCopy('zh-CN').messages.providerRetryScheduled;
+  const zhTw = getConversationCopy('zh-TW').messages.providerRetryScheduled;
   const en = getConversationCopy('en').messages.providerRetryScheduled;
 
   // Sub-second and zero inputs still read as one second (never "0秒后重试").
@@ -92,6 +94,7 @@ test('providerRetryScheduled humanizes hour-scale delays in both locales', () =>
   assert.equal(zh(75, 2, 10), '1分 15秒后重试(2/10)');
   assert.equal(en(75, 2, 10), 'Retrying in 1m 15s (2/10)');
   assert.equal(zh(16_083, 2, 10), '4小时 28分 3秒后重试(2/10)');
+  assert.equal(zhTw(16_083, 2, 10), '4小時 28分 3秒後重試(2/10)');
   assert.equal(en(16_083, 2, 10), 'Retrying in 4h 28m 3s (2/10)');
   assert.equal(zh(90_061, 2, 10), '1天 1小时 1分 1秒后重试(2/10)');
   assert.equal(en(90_061, 2, 10), 'Retrying in 1d 1h 1m 1s (2/10)');
diff --git a/apps/desktop/src/main/__tests__/permission-center-copy.test.ts b/packages/ui/src/__tests__/daily-review-copy.test.ts
similarity index 73%
copy from apps/desktop/src/main/__tests__/permission-center-copy.test.ts
copy to packages/ui/src/__tests__/daily-review-copy.test.ts
index 6333051056..7a7aa41164 100644
--- a/apps/desktop/src/main/__tests__/permission-center-copy.test.ts
+++ b/packages/ui/src/__tests__/daily-review-copy.test.ts
@@ -19,9 +19,8 @@
 
 import assert from 'node:assert/strict';
 import { test } from 'node:test';
-import { getPermissionCenterCopy } from '../../renderer/locales/permission-center-copy.js';
+import { getDailyReviewCopy } from '../daily-review-copy.js';
 
-test('presents a granted OS permission as a verified success', () => {
-  assert.equal(getPermissionCenterCopy('zh').osStates.granted.tone, 'success');
-  assert.equal(getPermissionCenterCopy('en').osStates.granted.tone, 'success');
+test('uses Taiwan wording for the Daily Review append action', () => {
+  assert.equal(getDailyReviewCopy('zh-TW').export.append, '貼到輸入框');
 });
diff --git a/packages/ui/src/__tests__/live-turn-projection.test.ts b/packages/ui/src/__tests__/live-turn-projection.test.ts
index 63d788c85f..29fc03fc7b 100644
--- a/packages/ui/src/__tests__/live-turn-projection.test.ts
+++ b/packages/ui/src/__tests__/live-turn-projection.test.ts
@@ -70,7 +70,7 @@ describe('the unconfirmed claim an arm carries', () => {
 
 describe('provider retry copy', () => {
   it('describes capacity retries without collapsing them into generic unavailability', () => {
-    assert.match(getConversationCopy('zh').messages.providerRetryReason.provider_capacity, /满载/);
+    assert.match(getConversationCopy('zh-CN').messages.providerRetryReason.provider_capacity, /满载/);
     assert.match(
       getConversationCopy('en').messages.providerRetryReason.provider_capacity,
       /capacity/,
diff --git a/packages/ui/src/__tests__/markdown-body.test.ts b/packages/ui/src/__tests__/markdown-body.test.ts
index f824394dc0..62bb6c9d7e 100644
--- a/packages/ui/src/__tests__/markdown-body.test.ts
+++ b/packages/ui/src/__tests__/markdown-body.test.ts
@@ -263,7 +263,7 @@ it('does not force the single-line scrollbar layout on multiline code', () => {
 it('gives collapsible plaintext code a localized accessible name', () => {
   const code = Array.from({ length: 10 }, (_, index) => `line ${index + 1}`);
 
-  for (const [locale, label] of [['en', 'Code'], ['zh', '代码']] as const) {
+  for (const [locale, label] of [['en', 'Code'], ['zh-CN', '代码']] as const) {
     const markup = renderToStaticMarkup(createElement(LocaleProvider, {
       locale,
       children: createElement(AstryxLocaleProvider, {
diff --git a/packages/ui/src/__tests__/materialize.test.ts b/packages/ui/src/__tests__/materialize.test.ts
index 179be9ea18..a7a2a76a3a 100644
--- a/packages/ui/src/__tests__/materialize.test.ts
+++ b/packages/ui/src/__tests__/materialize.test.ts
@@ -209,11 +209,11 @@ describe("materializeChat message metadata", () => {
       "Context compacted to keep this session within the model window.",
     );
     assert.equal(
-      materializeChat(messages, "zh")[0]?.text,
+      materializeChat(messages, "zh-CN")[0]?.text,
       "已压缩较早的对话内容,以适应模型上下文窗口。",
     );
     assert.equal(
-      materializeTurns(messages, "zh")[0]?.notes[0]?.text,
+      materializeTurns(messages, "zh-CN")[0]?.notes[0]?.text,
       "已压缩较早的对话内容,以适应模型上下文窗口。",
     );
   });
diff --git a/packages/ui/src/__tests__/runtime-resume-copy.test.ts b/packages/ui/src/__tests__/runtime-resume-copy.test.ts
index 6fa9eb9909..f1618df9de 100644
--- a/packages/ui/src/__tests__/runtime-resume-copy.test.ts
+++ b/packages/ui/src/__tests__/runtime-resume-copy.test.ts
@@ -48,8 +48,8 @@ test('resolves English park copy for every known reason with no Chinese (#4489)'
   }
 });
 
-test('keeps the Chinese copy for zh', () => {
-  const copy = resumeParkToastCopy(['pending_permission'], 'zh');
+test('keeps the Chinese copy for zh-CN', () => {
+  const copy = resumeParkToastCopy(['pending_permission'], 'zh-CN');
 
   assert.equal(copy.title, '暂时无法继续这一轮');
   assert.equal(copy.description, '上次执行仍在等待权限确认。');
@@ -60,7 +60,7 @@ test('resolves the missing-candidate special case per locale', () => {
     title: 'Nothing to resume',
     description: 'This task is already up to date.',
   });
-  assert.deepEqual(resumeParkToastCopy(['resume_candidate_missing'], 'zh'), {
+  assert.deepEqual(resumeParkToastCopy(['resume_candidate_missing'], 'zh-CN'), {
     title: '没有可恢复的任务',
     description: '任务已是最新状态。',
   });
@@ -71,6 +71,6 @@ test('falls back to the generic description for unknown reasons and dedupes repe
   assert.equal(unknown.title, 'This round cannot be resumed yet');
   assert.equal(unknown.description, 'This task does not currently meet the conditions to continue.');
 
-  const deduped = resumeParkToastCopy(['pending_permission', 'pending_permission'], 'zh');
+  const deduped = resumeParkToastCopy(['pending_permission', 'pending_permission'], 'zh-CN');
   assert.equal(deduped.description, '上次执行仍在等待权限确认。');
 });
diff --git a/packages/ui/src/__tests__/session-hover-card-copy.test.ts b/packages/ui/src/__tests__/session-hover-card-copy.test.ts
index 948a7dfeb4..89391207da 100644
--- a/packages/ui/src/__tests__/session-hover-card-copy.test.ts
+++ b/packages/ui/src/__tests__/session-hover-card-copy.test.ts
@@ -22,7 +22,7 @@ import test from 'node:test';
 import { getSessionHoverCardCopy } from '../session-hover-card-copy.js';
 
 test('localizes task and project hover card summaries', () => {
-  const zh = getSessionHoverCardCopy('zh');
+  const zh = getSessionHoverCardCopy('zh-CN');
   const en = getSessionHoverCardCopy('en');
 
   assert.equal(zh.sessionDetailsLabel('发布说明'), '发布说明 任务详情');
diff --git a/packages/ui/src/__tests__/tool-activity-presentation.test.ts b/packages/ui/src/__tests__/tool-activity-presentation.test.ts
index 97f234d4d8..e0f2877b90 100644
--- a/packages/ui/src/__tests__/tool-activity-presentation.test.ts
+++ b/packages/ui/src/__tests__/tool-activity-presentation.test.ts
@@ -32,7 +32,7 @@ import {
   isComputerTool,
 } from '../tool-activity/computer-action-label.js';
 
-function renderToStaticMarkup(node: ReactNode, locale: 'zh' | 'en' = 'zh'): string {
+function renderToStaticMarkup(node: ReactNode, locale: 'zh-CN' | 'en' = 'zh-CN'): string {
   return renderReactToStaticMarkup(createElement(LocaleProvider, {
     locale,
     children: node,
@@ -126,7 +126,7 @@ describe('tool activity presentation', () => {
     };
 
     assert.equal(isComputerTool(item), true);
-    assert.equal(computerActionLabel(item, 'zh'), '观察「计算器」窗口');
+    assert.equal(computerActionLabel(item, 'zh-CN'), '观察「计算器」窗口');
     const markup = renderToStaticMarkup(
       createElement(ToolTrow, { items: [item] }),
     );
@@ -166,7 +166,7 @@ describe('tool activity presentation', () => {
     assert.match(markup, /「计算器」窗口/);
     assert.match(markup, />7\/11</);
     assert.equal(
-      computerRunningLabel([observed, sequence], 'zh'),
+      computerRunningLabel([observed, sequence], 'zh-CN'),
       '正在操作「计算器」窗口 · 连续操作第 7/11 步',
     );
   });
diff --git a/apps/desktop/src/main/__tests__/health-center-copy.test.ts b/packages/ui/src/__tests__/tool-format.test.ts
similarity index 65%
copy from apps/desktop/src/main/__tests__/health-center-copy.test.ts
copy to packages/ui/src/__tests__/tool-format.test.ts
index e8f7a6aa5f..5bfd6d93b4 100644
--- a/apps/desktop/src/main/__tests__/health-center-copy.test.ts
+++ b/packages/ui/src/__tests__/tool-format.test.ts
@@ -19,15 +19,17 @@
 
 import assert from 'node:assert/strict';
 import { test } from 'node:test';
-import { getHealthCenterCopy } from '../../renderer/locales/settings-health-copy.js';
+import { describeLoadToolResult } from '../tool-format.js';
 
-test('labels blocker counts as global across filtered health views', () => {
-  assert.equal(
-    getHealthCenterCopy('zh').blockers.send(1, 6),
-    '全部健康信号中,1/6 条会阻塞发送',
-  );
-  assert.equal(
-    getHealthCenterCopy('en').blockers.send(1, 6),
-    'Across all health signals, 1 of 6 blocks sending',
+test('custom load-tool groups use Traditional Chinese action copy', () => {
+  const result = describeLoadToolResult(
+    { group: 'custom' },
+    {
+      activated: ['custom_tool'],
+      group: { id: 'custom', label: '自訂工具', description: '專案工具' },
+    },
+    'zh-TW',
   );
+  assert.equal(result?.actionLabel, '啟用 自訂工具');
+  assert.equal(result?.title, '自訂工具 已啟用');
 });
diff --git a/packages/ui/src/__tests__/transcript-projection.test.ts b/packages/ui/src/__tests__/transcript-projection.test.ts
index 559ce849fe..86715a0963 100644
--- a/packages/ui/src/__tests__/transcript-projection.test.ts
+++ b/packages/ui/src/__tests__/transcript-projection.test.ts
@@ -86,7 +86,7 @@ describe('incremental transcript projection', () => {
     const chinese = projection.project({
       sessionId: SESSION,
       messages,
-      locale: 'zh',
+      locale: 'zh-CN',
     });
 
     assert.equal(
diff --git a/packages/ui/src/__tests__/turn-running-spinner.test.tsx b/packages/ui/src/__tests__/turn-running-spinner.test.tsx
index 9f8f4d185d..1c30024a0c 100644
--- a/packages/ui/src/__tests__/turn-running-spinner.test.tsx
+++ b/packages/ui/src/__tests__/turn-running-spinner.test.tsx
@@ -50,7 +50,7 @@ function statusHasSpinner(toolStatuses: readonly ('running' | 'completed')[]): b
   return document.querySelector('.maka-turn-processing .astryx-spinner') !== null;
 }
 
-function runningStatusText(locale: 'en' | 'zh'): string {
+function runningStatusText(locale: 'en' | 'zh-CN'): string {
   const turn: TurnViewModel = {
     turnId: 'turn-1',
     status: 'running',
@@ -78,6 +78,6 @@ test('keeps the turn spinner when a collapsed group hides the running tool', ()
 });
 
 test('describes provider silence without inventing semantic progress', () => {
-  assert.equal(runningStatusText('zh'), '等待模型输出…');
+  assert.equal(runningStatusText('zh-CN'), '等待模型输出…');
   assert.equal(runningStatusText('en'), 'Waiting for model output…');
 });
diff --git a/packages/ui/src/artifact-preview-registry.ts b/packages/ui/src/artifact-preview-registry.ts
index def9198076..cb724e2bd6 100644
--- a/packages/ui/src/artifact-preview-registry.ts
+++ b/packages/ui/src/artifact-preview-registry.ts
@@ -81,7 +81,7 @@ function exceedsImagePayloadCap(base64: string): boolean {
   return base64.length > IMAGE_PAYLOAD_MAX_BASE64_LENGTH;
 }
 
-export function formatPreviewSize(sizeBytes: number | undefined, locale: UiLocale = 'zh'): string {
+export function formatPreviewSize(sizeBytes: number | undefined, locale: UiLocale = 'zh-CN'): string {
   if (sizeBytes === undefined || sizeBytes < 0 || !Number.isFinite(sizeBytes)) return getSharedUiCopy(locale).artifact.unknownSize;
   if (sizeBytes < 1024) return `${sizeBytes} B`;
   if (sizeBytes < 1024 * 1024) return `${(sizeBytes / 1024).toFixed(1)} KB`;
... 1138 lines suppressed ...


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org