You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by as...@apache.org on 2026/09/01 08:06:35 UTC

(maka) branch main updated: fix(cli): localize remaining TUI copy (#4422)

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

Astro-Han 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 920d7142d0 fix(cli): localize remaining TUI copy (#4422)
920d7142d0 is described below

commit 920d7142d001e81868bcd50a28a5bb3898415e3b
Author: NekoPunch <95...@users.noreply.github.com>
AuthorDate: Tue Sep 1 01:06:30 2026 -0700

    fix(cli): localize remaining TUI copy (#4422)
    
    The TUI still rendered the wrong language in both directions. English sessions
    saw Chinese copy in the flows #3990 left as follow-up work — rewind notices and
    the rewind picker, skill-invocation notices, `/skill` usage and the skill
    picker, the foreign-transcript scan failure, and the `/new` failure fallback —
    while Chinese sessions saw English `Steering:`/`Queued:` labels in the
    pending-queue bar. These strings lived outside the message contract #3990
    established: as Chinese-only literals, as `locale === 'zh'` ternaries, and, in
    the MCP guided editor, as an inline `copy()` helper carrying its own parallel
    en/zh maps alongside `heading(locale, en, zh)` pairs.
    
    Move the remaining copy into typed `TUI_COPY_RESOURCES` domains — `rewind`,
    `skills`, `connection-identity`, `session-actions`, `pending-queue`, and an
    `editor` sub-tree under `mcp-status` — consumed through the existing
    `defineUiMessageCatalog` / `resolveUiMessageCatalog` / `formatUiMessage`
    contract. The catalog becomes the single authority: `copy()`, `heading()`, and
    `confirmCopy()` are deleted, and `pi-tui-mcp-status.ts` is net smaller. Result
    codes are typed against the union `tui-mcp-control.ts` produces and skill
    failure reasons against `SkillInvocationFailureReason`, so a missing entry is
    now a compile error rather than a raw code leaking to the user;
    `resolution_failed` was previously unmapped and is now covered.
    
    Three behavior changes beyond copy sourcing. The Chinese MCP editor busy label
    distinguished nothing — test, reconnect, and apply all rendered one generic
    line — and now matches English. `/new` failure no longer renders the driver's
    raw `error.message`; it shows a cause-neutral localized notice. The
    foreign-transcript scan failure keeps its `{detail}` interpolation as a
    documented exception, because the detail is a local file-scan diagnostic and
    the notice exists so a store bug is not mistaken for "no sessions".
    
    No migration impact: no persisted representation, protocol, or public API
    changes, and every moved string keeps its text.
---
 packages/cli/src/__tests__/pi-transcript.test.ts   |   8 +-
 .../cli/src/__tests__/pi-tui-mcp-status.test.ts    | 273 ++++++++++++++++++++-
 packages/cli/src/__tests__/pi-tui-runner.test.ts   | 148 ++++++++---
 .../cli/src/__tests__/tui-copy-catalog.test.ts     |  30 +++
 packages/cli/src/pi-transcript.ts                  |  29 ++-
 packages/cli/src/pi-tui-layout.ts                  |   8 +-
 packages/cli/src/pi-tui-mcp-status.ts              | 213 ++++++----------
 packages/cli/src/pi-tui-runner.ts                  | 176 +++++++++----
 packages/cli/src/tui-copy-catalog.ts               | 237 ++++++++++++++++++
 9 files changed, 892 insertions(+), 230 deletions(-)

diff --git a/packages/cli/src/__tests__/pi-transcript.test.ts b/packages/cli/src/__tests__/pi-transcript.test.ts
index 1330205b82..83e67374a2 100644
--- a/packages/cli/src/__tests__/pi-transcript.test.ts
+++ b/packages/cli/src/__tests__/pi-transcript.test.ts
@@ -152,8 +152,12 @@ describe('Maka Pi TUI transcript', () => {
     const renderFor = (platform: NodeJS.Platform) =>
       renderMakaPiPendingQueue(state, 80, platform).map(stripAnsi);
 
-    assert.equal(renderFor('darwin').at(-1), '⌥+↑ 取回队列以重新编辑');
-    assert.equal(renderFor('linux').at(-1), 'Alt+↑ 取回队列以重新编辑');
+    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),
+      'Alt+↑ 取回队列以重新编辑',
+    );
   });
 
   test('renders goal-origin prompts as autonomous provenance, not as user prompts', () => {
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 d255feb83f..d058440674 100644
--- a/packages/cli/src/__tests__/pi-tui-mcp-status.test.ts
+++ b/packages/cli/src/__tests__/pi-tui-mcp-status.test.ts
@@ -20,8 +20,15 @@
 import assert from 'node:assert/strict';
 import { describe, test } from 'node:test';
 import type { TUI } from '@earendil-works/pi-tui';
+import type { McpTestResult } from '@maka/core/mcp';
 import { McpManagementOverlay } from '../pi-tui-mcp-status.js';
-import type { TuiMcpAction, TuiMcpManagement, TuiMcpSnapshot } from '../tui-mcp-control.js';
+import type {
+  TuiMcpAction,
+  TuiMcpActionResult,
+  TuiMcpManagement,
+  TuiMcpSnapshot,
+} from '../tui-mcp-control.js';
+import { TUI_COPY_RESOURCES } from '../tui-copy-catalog.js';
 import { stripAnsi } from '../tui-ansi.js';
 
 describe('MCP management overlay', () => {
@@ -243,8 +250,272 @@ describe('MCP management overlay', () => {
     text = overlay.render(100).map(stripAnsi).join('\n');
     assert.match(text, /› ○ s4/u);
   });
+
+  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: {
+      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;
+
+  for (const locale of ['en', 'zh'] 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));
+    });
+
+    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));
+    });
+
+    test(`labels the busy phase per action kind in ${locale}`, () => {
+      const mcp = surface(listSnapshot());
+      mcp.execute = () => new Promise(() => undefined);
+      const overlay = new McpManagementOverlay({
+        locale,
+        surface: mcp,
+        viewportRows: () => 8,
+        onClose: () => undefined,
+        onChange: () => undefined,
+      });
+      overlay.render(100);
+
+      const labels: [string, string][] = [
+        ['t', expected.testing],
+        ['r', expected.reconnecting],
+        [' ', expected.applying],
+      ];
+      for (const [key, label] of labels) {
+        overlay.handleInput(key);
+        assert.ok(overlay.render(100).map(stripAnsi).join('\n').includes(label));
+        overlay.handleInput('\u001b');
+      }
+    });
+
+    test(`localizes ${locale} action result notices`, async () => {
+      const mcp = surface(listSnapshot());
+      mcp.execute = async () => ({ status: 'applied', effect: 'published' });
+      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));
+      assert.ok(overlay.render(100).map(stripAnsi).join('\n').includes(expected.published));
+
+      mcp.execute = async () => ({ status: 'failed', reason: 'manager-failed' });
+      overlay.handleInput('t');
+      await new Promise<void>((resolve) => setImmediate(resolve));
+      assert.ok(overlay.render(100).map(stripAnsi).join('\n').includes(expected.managerFailed));
+    });
+  }
+
+  const RESULT_CASES: readonly [TuiMcpActionResult, string][] = [
+    [{ status: 'conflict', reason: 'exists' }, 'exists'],
+    [{ status: 'conflict', reason: 'stale_config' }, 'stale_config'],
+    [{ status: 'conflict', reason: 'stale_edit' }, 'stale_edit'],
+    [{ status: 'conflict', reason: 'stale_import' }, 'stale_import'],
+    [{ status: 'conflict', reason: 'missing' }, 'missing'],
+    [{ status: 'failed', reason: 'closed' }, 'closed'],
+    [{ status: 'failed', reason: 'invalid-config' }, 'invalid-config'],
+    [{ status: 'failed', reason: 'credential-cleanup-failed' }, 'credential-cleanup-failed'],
+    [{ status: 'failed', reason: 'persist-failed' }, 'persist-failed'],
+    [{ status: 'failed', reason: 'manager-failed' }, 'manager-failed'],
+    [{ status: 'applied', effect: 'published' }, 'published'],
+    [{ status: 'applied', effect: 'pending_host' }, 'pending_host'],
+    [{ status: 'applied', effect: 'sync_failed' }, 'sync_failed'],
+    [{ status: 'applied', effect: 'publication_failed' }, 'publication_failed'],
+    [{ status: 'tested', test: testResult(true), effect: 'published' }, 'test_ok'],
+    [{ status: 'tested', test: testResult(false), effect: 'published' }, 'test_failed'],
+    [
+      { status: 'tested', test: testResult(true), effect: 'publication_failed' },
+      'test_publication_failed',
+    ],
+    [{ status: 'tested', test: testResult(true), effect: 'pending_host' }, 'test_pending_host'],
+  ];
+
+  for (const locale of ['en', 'zh'] 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})`);
+      }
+    });
+  }
+
+  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') => TUI_COPY_RESOURCES['mcp-status'][locale].editor;
+    assert.equal(editor('zh').results.stale_import, '导入项已变化,请重新预览。');
+    assert.equal(editor('en').confirmImportTitle, 'Import MCP servers?');
+    assert.equal(editor('zh').confirmImportTitle, '导入 MCP 服务器?');
+  });
 });
 
+function testResult(ok: boolean): McpTestResult {
+  return {
+    ok,
+    latencyMs: 1,
+    status: {
+      serverId: 'filesystem',
+      state: ok ? 'connected' : 'error',
+      toolCount: 0,
+      tools: [],
+      updatedAt: 0,
+    },
+  };
+}
+
+function listSnapshot(): TuiMcpSnapshot {
+  return {
+    initialization: 'ready',
+    configuration: 'ready',
+    publication: 'published',
+    toolCount: 0,
+    servers: [
+      { serverId: 'filesystem', configured: true, synchronized: true, enabled: true, toolCount: 0 },
+    ],
+  };
+}
+
+function fakeTui(): TUI {
+  return {
+    requestRender: () => undefined,
+    terminal: { rows: 24, columns: 80 },
+  } as unknown as TUI;
+}
+
 function surface(
   snapshot: TuiMcpSnapshot,
 ): TuiMcpManagement & { subscribe(listener: () => void): () => void } {
diff --git a/packages/cli/src/__tests__/pi-tui-runner.test.ts b/packages/cli/src/__tests__/pi-tui-runner.test.ts
index 906fa40a51..e19d253044 100644
--- a/packages/cli/src/__tests__/pi-tui-runner.test.ts
+++ b/packages/cli/src/__tests__/pi-tui-runner.test.ts
@@ -393,12 +393,17 @@ describe('Maka Pi TUI runner', () => {
     terminal.input('/new');
     terminal.input('\r');
     await waitFor(() => attempted === 1);
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('host_draining'));
+    // The driver's raw error text is not product copy; the TUI reports the
+    // failure with its own localized notice (#2672).
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output()).includes('Could not start a new session'),
+    );
 
     // Identity untouched…
     assert.equal(driver.getSessionId(), 'session-1');
     // …and the transcript was not wiped by the aborted /new.
-    assert.match(plainTerminalOutput(terminal.screenOutput()), /host_draining/);
+    assert.match(plainTerminalOutput(terminal.screenOutput()), /Could not start a new session/);
+    assert.doesNotMatch(plainTerminalOutput(terminal.screenOutput()), /host_draining/);
     assert.ok(transcriptBefore.length > 0);
 
     exitMaka(terminal);
@@ -4424,7 +4429,9 @@ describe('Maka Pi TUI runner', () => {
     terminal.input('\r');
     // The scan failure is surfaced, not swallowed into an empty list.
     await waitFor(() =>
-      plainTerminalOutput(terminal.output()).includes('读取外部对话失败:corrupt index'),
+      plainTerminalOutput(terminal.output()).includes(
+        'Could not read external conversations: corrupt index',
+      ),
     );
 
     exitMaka(terminal);
@@ -4868,7 +4875,9 @@ describe('Maka Pi TUI runner', () => {
 
     terminal.input('/rewind');
     terminal.input('\r');
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('回到选定轮次'));
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output()).includes('Return to before the selected turn'),
+    );
     terminal.input('\r');
     await waitFor(() => hydrationAttempts === 1);
     await waitFor(() => plainTerminalOutput(terminal.screenOutput()).includes('refilled: turn-2'));
@@ -5091,7 +5100,9 @@ describe('Maka Pi TUI runner', () => {
     terminal.input('/rewind');
     terminal.input('\r');
 
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('回到选定轮次'));
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output()).includes('Return to before the selected turn'),
+    );
     await waitFor(() => plainTerminalOutput(terminal.output()).includes('second question'));
     await waitFor(() => plainTerminalOutput(terminal.output()).includes('first question'));
 
@@ -5099,7 +5110,9 @@ describe('Maka Pi TUI runner', () => {
     terminal.input('\r');
     await waitFor(() => driver.rewound.length === 1);
     assert.deepEqual(driver.rewound, ['turn-2']);
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('已回退到该轮之前'));
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output()).includes('Rewound to before this turn'),
+    );
     await waitFor(() => plainTerminalOutput(terminal.output()).includes('first answer'));
     // The rewound turn's prompt is refilled into the editor for an edit-and-resend.
     await waitFor(() => plainTerminalOutput(terminal.output()).includes('refilled: turn-2'));
@@ -5139,15 +5152,21 @@ describe('Maka Pi TUI runner', () => {
     terminal.input('\r');
     // The branch switch is still in flight, but the selection must already be
     // visibly accepted — control-busy otherwise renders nothing (#3383).
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('正在回退到该轮之前'));
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output()).includes('Rewinding to before this turn'),
+    );
     assert.deepEqual(driver.rewound, []);
 
     driver.gate.resolve();
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('已回退到该轮之前'));
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output()).includes('Rewound to before this turn'),
+    );
     await waitFor(() => plainTerminalOutput(terminal.output()).includes('refilled: turn-1'));
     // The in-progress notice is wiped together with the transcript it announced.
     await waitFor(
-      () => plainTerminalOutput(terminal.screenOutput()).includes('正在回退到该轮之前') === false,
+      () =>
+        plainTerminalOutput(terminal.screenOutput()).includes('Rewinding to before this turn') ===
+        false,
     );
 
     exitMaka(terminal);
@@ -5176,7 +5195,9 @@ describe('Maka Pi TUI runner', () => {
     terminal.input('\r');
     await waitFor(() => plainTerminalOutput(terminal.output()).includes('first question'));
     terminal.input('\r');
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('正在回退到该轮之前'));
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output()).includes('Rewinding to before this turn'),
+    );
 
     // Typed while the branch switch is in flight: this is newer user work and
     // must win over the prompt refill (#3383). Enter stays swallowed by
@@ -5190,7 +5211,7 @@ describe('Maka Pi TUI runner', () => {
     // The notice wraps across screen lines at 80 columns, so match against a
     // whitespace-collapsed copy instead of the raw output.
     const collapsedOutput = () => plainTerminalOutput(terminal.output()).replace(/\s+/g, '');
-    await waitFor(() => collapsedOutput().includes('未覆盖'));
+    await waitFor(() => collapsedOutput().includes('untouched'));
     await waitFor(() => editorInputText(terminal) === 'my draft');
     assert.equal(plainTerminalOutput(terminal.output()).includes('refilled: turn-1'), false);
     // The ↑ recovery promise must hold even though nothing was submitted in
@@ -5228,7 +5249,9 @@ describe('Maka Pi TUI runner', () => {
     terminal.input('\r');
     await waitFor(() => plainTerminalOutput(terminal.output()).includes('first question'));
     terminal.input('\r');
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('正在回退到该轮之前'));
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output()).includes('Rewinding to before this turn'),
+    );
 
     // A bracketed paste starts while the branch switch is in flight and its end
     // marker has not arrived: getText() still reports empty, but the buffered
@@ -5239,7 +5262,7 @@ describe('Maka Pi TUI runner', () => {
     // The completion notice wraps across screen lines at 80 columns, so match a
     // whitespace-collapsed copy.
     await waitFor(() =>
-      plainTerminalOutput(terminal.output()).replace(/\s+/g, '').includes('未覆盖'),
+      plainTerminalOutput(terminal.output()).replace(/\s+/g, '').includes('untouched'),
     );
 
     // Only now does the paste complete — after the refill decision was made.
@@ -5280,7 +5303,7 @@ describe('Maka Pi TUI runner', () => {
     await waitFor(() => terminal.progressStates.at(-1) === true);
 
     terminal.input('\r');
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('无法回退'));
+    await waitFor(() => plainTerminalOutput(terminal.output()).includes('Cannot rewind'));
     assert.deepEqual(driver.rewound, []);
 
     driver.turnGate.resolve();
@@ -5362,7 +5385,9 @@ describe('Maka Pi TUI runner', () => {
 
     terminal.input('/rewind');
     terminal.input('\r');
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('回到选定轮次'));
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output()).includes('Return to before the selected turn'),
+    );
     terminal.input('\r');
 
     await waitFor(() => hydrationAttempts === 1);
@@ -5527,7 +5552,9 @@ describe('Maka Pi TUI runner', () => {
 
     terminal.input('/rewind');
     terminal.input('\r');
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('回到选定轮次'));
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output()).includes('Return to before the selected turn'),
+    );
     terminal.input('\r');
     await waitFor(() => hydrationAttempts === 1);
     assert.ok(listener);
@@ -5665,18 +5692,28 @@ describe('Maka Pi TUI runner', () => {
     terminal.input('\x1b');
     terminal.input('z');
     await waitFor(() => editorInputText(terminal) === 'z');
-    assert.equal(plainTerminalOutput(terminal.output()).includes('回到选定轮次'), false);
+    assert.equal(
+      plainTerminalOutput(terminal.output()).includes('Return to before the selected turn'),
+      false,
+    );
     terminal.input('\x7f');
     await waitFor(() => editorInputText(terminal) === '');
 
     // A consecutive Escape pair completes the gesture and opens the picker.
     terminal.input('\x1b');
     terminal.input('\x1b');
-    await waitFor(() => plainTerminalOutput(terminal.output()).includes('回到选定轮次'));
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output()).includes('Return to before the selected turn'),
+    );
 
     // Cancel the picker so Ctrl-C reaches the runner rather than the overlay.
     terminal.input('\x1b');
-    await waitFor(() => !plainTerminalOutput(terminal.screenOutput()).includes('回到选定轮次'));
+    await waitFor(
+      () =>
+        !plainTerminalOutput(terminal.screenOutput()).includes(
+          'Return to before the selected turn',
+        ),
+    );
 
     exitMaka(terminal);
     await Promise.race([
@@ -5717,7 +5754,10 @@ describe('Maka Pi TUI runner', () => {
     // the typed char paints.
     terminal.input('z');
     await waitFor(() => editorInputText(terminal)?.endsWith('z') === true);
-    assert.equal(plainTerminalOutput(terminal.screenOutput()).includes('回到选定轮次'), false);
+    assert.equal(
+      plainTerminalOutput(terminal.screenOutput()).includes('Return to before the selected turn'),
+      false,
+    );
 
     exitMaka(terminal);
     await Promise.race([
@@ -5759,7 +5799,10 @@ describe('Maka Pi TUI runner', () => {
     // the typed char paints.
     terminal.input('z');
     await waitFor(() => editorInputText(terminal) === 'z');
-    assert.equal(plainTerminalOutput(terminal.screenOutput()).includes('回到选定轮次'), false);
+    assert.equal(
+      plainTerminalOutput(terminal.screenOutput()).includes('Return to before the selected turn'),
+      false,
+    );
 
     exitMaka(terminal);
     await Promise.race([
@@ -6026,7 +6069,7 @@ describe('Maka Pi TUI runner', () => {
 
       // The transcript render trails the send by a tick — wait for it.
       await waitFor(() => plainTerminalOutput(terminal.output()).includes('/skill:alpha 帮我整理'));
-      await waitFor(() => plainTerminalOutput(terminal.output()).includes('已加载技能:Alpha'));
+      await waitFor(() => plainTerminalOutput(terminal.output()).includes('Skills loaded: Alpha'));
 
       exitMaka(terminal);
       await Promise.race([
@@ -6064,7 +6107,7 @@ describe('Maka Pi TUI runner', () => {
     await waitFor(() => driver.prompts.length === 1);
     await waitFor(() => {
       const output = plainTerminalOutput(terminal.output());
-      return output.includes('已加载技能:Alpha') && output.includes('/skill:typo(未找到)');
+      return output.includes('Skills loaded: Alpha') && output.includes('/skill:typo (not found)');
     });
 
     exitMaka(terminal);
@@ -6076,6 +6119,47 @@ describe('Maka Pi TUI runner', () => {
     ]);
   });
 
+  test('localizes runner notices in Chinese mode', async () => {
+    const terminal = new FakeTerminal();
+    const driver = new HostSkillDriver({
+      loaded: [],
+      failed: [{ request: 'nope', reason: 'not_found' }],
+      receipts: [],
+    });
+    const run = runMakaPiTui({
+      title: 'Maka',
+      driver,
+      cwd: '/repo',
+      model: 'claude-sonnet-4-5',
+      connectionSlug: 'claude-subscription',
+      permissionMode: 'ask',
+      terminal,
+      locale: 'zh',
+      listSkills: async () => [],
+    });
+
+    terminal.input('/skill');
+    terminal.input('\r');
+    await waitFor(() => plainTerminalOutput(terminal.output()).includes('当前没有可调用的技能。'));
+
+    terminal.input('/skill:nope hi');
+    terminal.input('\r');
+    await waitFor(() =>
+      plainTerminalOutput(terminal.output())
+        .replace(/\s+/g, '')
+        .includes('未能加载技能/skill:nope(未找到);未发起模型请求。'),
+    );
+    assert.equal(driver.prompts.length, 0);
+
+    exitMaka(terminal);
+    await Promise.race([
+      run,
+      delay(CLOSE_BUDGET_MS).then(() => {
+        throw new Error('TUI did not close during test cleanup');
+      }),
+    ]);
+  });
+
   test('does not create a turn when every skill token fails to resolve', async () => {
     {
       const terminal = new FakeTerminal();
@@ -6097,10 +6181,12 @@ describe('Maka Pi TUI runner', () => {
 
       terminal.input('/skill:nope hi');
       terminal.input('\r');
+      // The notice wraps across screen lines at 80 columns, so match against a
+      // whitespace-collapsed copy instead of the raw output.
       await waitFor(() =>
-        plainTerminalOutput(terminal.output()).includes(
-          '未能加载技能 /skill:nope(未找到);未发起模型请求。',
-        ),
+        plainTerminalOutput(terminal.output())
+          .replace(/\s+/g, ' ')
+          .includes('Could not load skills /skill:nope (not found); no model request was made.'),
       );
       assert.equal(driver.prompts.length, 0);
 
@@ -6140,10 +6226,14 @@ describe('Maka Pi TUI runner', () => {
 
       terminal.input(prompt);
       terminal.input('\r');
+      // The notice wraps across screen lines at 80 columns, so match against a
+      // whitespace-collapsed copy instead of the raw output.
       await waitFor(() =>
-        plainTerminalOutput(terminal.output()).includes(
-          '请求超过 50 个上限(调用请求过多);未发起模型请求。',
-        ),
+        plainTerminalOutput(terminal.output())
+          .replace(/\s+/g, ' ')
+          .includes(
+            'more than the 50-request limit (too many requests); no model request was made.',
+          ),
       );
       assert.equal(driver.prompts.length, 0);
 
diff --git a/packages/cli/src/__tests__/tui-copy-catalog.test.ts b/packages/cli/src/__tests__/tui-copy-catalog.test.ts
index b59509f2a4..0a1ef6b4ea 100644
--- a/packages/cli/src/__tests__/tui-copy-catalog.test.ts
+++ b/packages/cli/src/__tests__/tui-copy-catalog.test.ts
@@ -27,6 +27,15 @@ const MESSAGE_VALUES = {
   count: 2,
   detail: 'HTTP 401',
   hasDetail: true,
+  serverId: 'filesystem',
+  names: 'Alpha',
+  failures: '/skill:nope (not found)',
+  outcome: 'no model request was made.',
+  request: 'nope',
+  reason: 'not found',
+  limit: 3,
+  notice: 'The original account was deleted.',
+  recovery: 'Add or enable a connection first.',
 } as const;
 
 describe('TUI copy resources', () => {
@@ -80,6 +89,27 @@ 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.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.failedItem,
+        { request: 'nope', reason: TUI_COPY_RESOURCES.skills.zh.failureReasons.not_found },
+        'zh',
+      ),
+      '/skill:nope(未找到)',
+    );
+  });
+
   test('localizes stable onboarding failure codes at the TUI boundary', () => {
     assert.equal(
       onboardingFailureMessage({ kind: 'rejected', reason: 'connection_not_found' }, 'en'),
diff --git a/packages/cli/src/pi-transcript.ts b/packages/cli/src/pi-transcript.ts
index bbbec37545..1ef16e170d 100644
--- a/packages/cli/src/pi-transcript.ts
+++ b/packages/cli/src/pi-transcript.ts
@@ -37,7 +37,12 @@ import {
 import type { ContextBudgetDiagnostic } from '@maka/core/usage-stats/types';
 import { providerRetryDisplaySeconds } from '@maka/core/provider-retry-countdown';
 import type { ThinkingLevel } from '@maka/core/model-thinking';
-import type { UiLocale } from '@maka/core/ui-locale';
+import {
+  defineUiMessageCatalog,
+  resolveUiMessageCatalog,
+  type UiLocale,
+} from '@maka/core/ui-locale';
+import { TUI_COPY_RESOURCES } from './tui-copy-catalog.js';
 import { isActiveShellRunStatus } from '@maka/core/shell-run';
 import { mergeShellRunStateWithDiagnostics } from '@maka/core/shell-run-result';
 import { projectToolActivityArgs } from '@maka/core/tool-activity-args';
@@ -1871,29 +1876,41 @@ function formatElapsedDuration(elapsedMs: number): string {
  * turn). A trailing hint reminds the user that alt+↑ takes them back to edit.
  * Renders nothing when both queues are empty.
  */
+interface TuiPendingQueueCopy {
+  readonly steeringLabel: string;
+  readonly queuedLabel: string;
+  readonly requeueHint: string;
+}
+
+const TUI_PENDING_QUEUE_COPY = resolveUiMessageCatalog(
+  defineUiMessageCatalog<TuiPendingQueueCopy>()(TUI_COPY_RESOURCES['pending-queue']),
+);
+
 export function renderMakaPiPendingQueue(
   state: MakaPiTranscriptState,
   width: number,
   platform: NodeJS.Platform = process.platform,
+  locale: UiLocale = 'en',
 ): string[] {
   if (state.steering.length === 0 && state.followup.length === 0) {
     return [];
   }
+  const copy = TUI_PENDING_QUEUE_COPY[locale];
   const safeWidth = Math.max(1, width);
   const steering = state.steering;
   const followup = state.followup;
   const lines: string[] = [];
   for (const text of steering) {
     lines.push(
-      fitLine(`${ansi.accent('Steering:')} ${ansi.dim(firstLinePreview(text))}`, safeWidth),
+      fitLine(`${ansi.accent(copy.steeringLabel)} ${ansi.dim(firstLinePreview(text))}`, safeWidth),
     );
   }
   for (const text of followup) {
-    lines.push(fitLine(`${ansi.dim('Queued:')} ${ansi.dim(firstLinePreview(text))}`, safeWidth));
+    lines.push(
+      fitLine(`${ansi.dim(copy.queuedLabel)} ${ansi.dim(firstLinePreview(text))}`, safeWidth),
+    );
   }
-  lines.push(
-    fitLine(ansi.dim(renderTuiShortcutCopy('Alt+↑ 取回队列以重新编辑', platform)), safeWidth),
-  );
+  lines.push(fitLine(ansi.dim(renderTuiShortcutCopy(copy.requeueHint, platform)), safeWidth));
   return lines;
 }
 
diff --git a/packages/cli/src/pi-tui-layout.ts b/packages/cli/src/pi-tui-layout.ts
index 3d31fbe689..bca2fe430c 100644
--- a/packages/cli/src/pi-tui-layout.ts
+++ b/packages/cli/src/pi-tui-layout.ts
@@ -18,6 +18,7 @@
  */
 
 import { Container, type Component, type Terminal } from '@earendil-works/pi-tui';
+import type { UiLocale } from '@maka/core/ui-locale';
 // Deep import (pi-tui does not re-export it): the viewport shadow diff must
 // compare the same canonical lines pi-tui diffs, and pi-tui normalizes Thai/Lao
 // AM sequences before its diff. Pinned to pi-tui 0.80.3.
@@ -111,12 +112,15 @@ export class MakaActivityStripComponent implements Component {
 
 /** The pending-queue bar (Steering:/Queued:) rendered just above the editor. */
 export class MakaPendingQueueComponent implements Component {
-  constructor(private readonly state: MakaPiTranscriptState) {}
+  constructor(
+    private readonly state: MakaPiTranscriptState,
+    private readonly locale: UiLocale = 'en',
+  ) {}
 
   invalidate(): void {}
 
   render(width: number): string[] {
-    return renderMakaPiPendingQueue(this.state, width);
+    return renderMakaPiPendingQueue(this.state, width, process.platform, this.locale);
   }
 }
 
diff --git a/packages/cli/src/pi-tui-mcp-status.ts b/packages/cli/src/pi-tui-mcp-status.ts
index 5777570ad2..300d757e99 100644
--- a/packages/cli/src/pi-tui-mcp-status.ts
+++ b/packages/cli/src/pi-tui-mcp-status.ts
@@ -67,8 +67,35 @@ interface TuiMcpStatusCopy {
   readonly configuredOnly: string;
   readonly configPending: string;
   readonly toolCount: string;
+  readonly editor: {
+    readonly addTitle: string;
+    readonly transportTitle: string;
+    readonly protocolTitle: string;
+    readonly confirmAddTitle: string;
+    readonly confirmImportTitle: string;
+    readonly confirmRemoveTitle: string;
+    readonly confirmRemoveCredentialTitle: string;
+    readonly confirmRemoveCredentialDetail: string;
+    readonly confirmHint: string;
+    readonly synchronizing: string;
+    readonly outOfSync: string;
+    readonly action: Readonly<Record<'test' | 'reconnect' | 'apply', string>>;
+    readonly inputLabels: Readonly<Record<InputKind, string>>;
+    readonly hints: Readonly<Record<'args' | 'map' | 'optional' | 'submit' | 'credential', string>>;
+    readonly results: Readonly<Record<TuiMcpResultCode, string>>;
+  };
 }
 
+type TuiMcpResultCode =
+  | Extract<TuiMcpActionResult, { status: 'conflict' | 'failed' }>['reason']
+  | Extract<TuiMcpActionResult, { status: 'applied' }>['effect']
+  | 'turn_active'
+  | 'invalid'
+  | 'test_ok'
+  | 'test_failed'
+  | 'test_publication_failed'
+  | 'test_pending_host';
+
 const MCP_STATUS_COPY = resolveUiMessageCatalog(
   defineUiMessageCatalog<TuiMcpStatusCopy>()(TUI_COPY_RESOURCES['mcp-status']),
 );
@@ -192,7 +219,7 @@ export class McpManagementOverlay implements Component {
           void this.runAction({ kind: 'add', serverId: this.phase.draft.serverId, config });
         }
       } catch {
-        this.notice = { level: 'error', text: copy(this.input.locale, 'invalid') };
+        this.notice = { level: 'error', text: resultCopy(this.input.locale, 'invalid') };
         this.backToList(false);
       }
     } else if (this.phase.kind === 'confirm_import' && matchesKey(data, 'y')) {
@@ -318,7 +345,7 @@ export class McpManagementOverlay implements Component {
   private startEdit(serverId: string): void {
     const edit = this.management()?.configForEdit(serverId);
     if (!edit) {
-      this.notice = { level: 'error', text: copy(this.input.locale, 'missing') };
+      this.notice = { level: 'error', text: resultCopy(this.input.locale, 'missing') };
       return;
     }
     this.startInput(
@@ -412,7 +439,7 @@ export class McpManagementOverlay implements Component {
       }
       this.notice = undefined;
     } catch {
-      this.notice = { level: 'error', text: copy(this.input.locale, 'invalid') };
+      this.notice = { level: 'error', text: resultCopy(this.input.locale, 'invalid') };
     }
     this.input.onChange();
   }
@@ -422,7 +449,7 @@ export class McpManagementOverlay implements Component {
     if (!management || this.phase.kind === 'busy') return;
     if (this.input.canManage && !this.input.canManage()) {
       this.backToList(false);
-      this.notice = { level: 'error', text: copy(this.input.locale, 'turn_active') };
+      this.notice = { level: 'error', text: resultCopy(this.input.locale, 'turn_active') };
       this.input.onChange();
       return;
     }
@@ -447,30 +474,15 @@ export class McpManagementOverlay implements Component {
     const snapshot = this.input.surface?.snapshot();
     if (!snapshot) return unavailableDocument(this.input.locale);
     if (this.phase.kind === 'input') return this.inputDocument(width);
+    const editor = MCP_STATUS_COPY[this.input.locale].editor;
     if (this.phase.kind === 'add_choice') {
-      return [
-        heading(this.input.locale, 'Add MCP server', '添加 MCP 服务器'),
-        '',
-        'g  Guided setup',
-        'j  Paste JSON',
-      ];
+      return [ansi.bold(editor.addTitle), '', 'g  Guided setup', 'j  Paste JSON'];
     }
     if (this.phase.kind === 'transport') {
-      return [
-        heading(this.input.locale, 'Transport', '传输方式'),
-        '',
-        '1  stdio',
-        '2  Streamable HTTP',
-      ];
+      return [ansi.bold(editor.transportTitle), '', '1  stdio', '2  Streamable HTTP'];
     }
     if (this.phase.kind === 'protocol') {
-      return [
-        heading(this.input.locale, 'Protocol preference', '协议偏好'),
-        '',
-        '1  legacy',
-        '2  auto',
-        '3  2026-07-28',
-      ];
+      return [ansi.bold(editor.protocolTitle), '', '1  legacy', '2  auto', '3  2026-07-28'];
     }
     if (this.phase.kind === 'confirm_add') {
       return confirmAddDocument(this.phase.draft, this.input.locale);
@@ -481,29 +493,25 @@ export class McpManagementOverlay implements Component {
     if (this.phase.kind === 'confirm_remove') {
       return [
         ansi.red(
-          heading(
-            this.input.locale,
-            `Remove ${this.phase.serverId}?`,
-            `删除 ${this.phase.serverId}?`,
+          ansi.bold(
+            formatUiMessage(
+              editor.confirmRemoveTitle,
+              { serverId: this.phase.serverId },
+              this.input.locale,
+            ),
           ),
         ),
         '',
-        confirmCopy(this.input.locale),
+        editor.confirmHint,
       ];
     }
     if (this.phase.kind === 'confirm_remove_publication_credential') {
       return [
-        heading(
-          this.input.locale,
-          'Remove the remote provider credential?',
-          '删除远程 Provider 凭据?',
-        ),
+        ansi.bold(editor.confirmRemoveCredentialTitle),
         '',
-        this.input.locale === 'zh'
-          ? '这会停止向所选 Runtime Host 发布 MCP 工具。'
-          : 'This stops publishing MCP tools to the selected Runtime Host.',
+        editor.confirmRemoveCredentialDetail,
         '',
-        confirmCopy(this.input.locale),
+        editor.confirmHint,
       ];
     }
     if (this.phase.kind === 'busy') return [ansi.yellow(this.phase.label)];
@@ -732,147 +740,82 @@ function actionNotice(
   result: TuiMcpActionResult,
   locale: UiLocale,
 ): { level: 'info' | 'error'; text: string } {
-  if (result.status === 'conflict') return { level: 'error', text: copy(locale, result.reason) };
-  if (result.status === 'failed') return { level: 'error', text: copy(locale, result.reason) };
+  if (result.status === 'conflict' || result.status === 'failed') {
+    return { level: 'error', text: resultCopy(locale, result.reason) };
+  }
   if (result.status === 'tested') {
     if (result.test.ok && result.effect === 'publication_failed') {
-      return { level: 'error', text: copy(locale, 'test_publication_failed') };
+      return { level: 'error', text: resultCopy(locale, 'test_publication_failed') };
     }
     if (result.test.ok && result.effect === 'pending_host') {
-      return { level: 'info', text: copy(locale, 'test_pending_host') };
+      return { level: 'info', text: resultCopy(locale, 'test_pending_host') };
     }
     return {
       level: result.test.ok ? 'info' : 'error',
-      text: result.test.ok ? copy(locale, 'test_ok') : copy(locale, 'test_failed'),
+      text: result.test.ok ? resultCopy(locale, 'test_ok') : resultCopy(locale, 'test_failed'),
     };
   }
   return {
     level:
       result.effect === 'sync_failed' || result.effect === 'publication_failed' ? 'error' : 'info',
-    text: copy(locale, result.effect),
+    text: resultCopy(locale, result.effect),
   };
 }
 
-function copy(locale: UiLocale, key: string): string {
-  const en: Record<string, string> = {
-    exists: 'That server ID already exists.',
-    stale_config: 'MCP configuration changed; retry the action.',
-    stale_edit: 'This server changed; reopen it before editing.',
-    stale_import: 'An imported entry changed; preview the import again.',
-    missing: 'That server no longer exists.',
-    closed: 'The MCP controller is closed.',
-    'invalid-config': 'The server configuration is invalid.',
-    'credential-cleanup-failed':
-      'Stored credentials could not be removed; the configuration was not changed.',
-    'publication-credential-failed': 'The provider credential could not be stored or applied.',
-    'persist-failed': 'The configuration could not be saved.',
-    'manager-failed': 'The MCP connection action failed.',
-    turn_active: 'MCP cannot be changed while a turn or another control action is running.',
-    invalid: 'Check the value and try again.',
-    published: 'Configuration saved and tools refreshed.',
-    pending_host: 'Configuration saved; publication is waiting for Runtime Host.',
-    sync_failed: 'Configuration saved, but the MCP manager is out of sync.',
-    publication_failed: 'Configuration saved, but capability publication failed.',
-    test_ok: 'Connection test passed.',
-    test_failed: 'Connection test failed.',
-    test_publication_failed: 'Connection test passed, but capability publication failed.',
-    test_pending_host: 'Connection test passed; publication is waiting for Runtime Host.',
-  };
-  const zh: Record<string, string> = {
-    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 发布。',
-  };
-  return (locale === 'zh' ? zh : en)[key] ?? key;
+function resultCopy(locale: UiLocale, code: TuiMcpResultCode): string {
+  return MCP_STATUS_COPY[locale].editor.results[code] ?? code;
 }
 
 function confirmAddDocument(draft: GuidedDraft, locale: UiLocale): string[] {
+  const editor = MCP_STATUS_COPY[locale].editor;
   return [
-    heading(locale, 'Add this MCP server?', '添加该 MCP 服务器?'),
+    ansi.bold(editor.confirmAddTitle),
     '',
     `${draft.serverId} · ${draft.transport} · ${draft.protocol}`,
     draft.transport === 'stdio' ? (draft.command ?? '') : (draft.url ?? ''),
     '',
-    confirmCopy(locale),
+    editor.confirmHint,
   ];
 }
 
 function confirmImportDocument(preview: TuiMcpImportPreview, locale: UiLocale): string[] {
+  const editor = MCP_STATUS_COPY[locale].editor;
   return [
-    heading(locale, 'Import MCP servers?', '导入 MCP 服务器?'),
+    ansi.bold(editor.confirmImportTitle),
     '',
     ...preview.entries.map(
       (entry) =>
         `${entry.change === 'add' ? '+' : '~'} ${entry.serverId} · ${entry.transport} · ${entry.protocol}`,
     ),
     '',
-    confirmCopy(locale),
+    editor.confirmHint,
   ];
 }
 
 function actionLabel(action: TuiMcpAction, locale: UiLocale): string {
-  if (locale === 'zh') return '正在应用 MCP 更改…';
-  if (action.kind === 'test') return 'Testing MCP server…';
-  if (action.kind === 'reconnect') return 'Reconnecting MCP server…';
-  return 'Applying MCP configuration…';
+  const labels = MCP_STATUS_COPY[locale].editor.action;
+  if (action.kind === 'test') return labels.test;
+  if (action.kind === 'reconnect') return labels.reconnect;
+  return labels.apply;
 }
 
 function inputLabel(kind: InputKind, locale: UiLocale): string {
-  const labels: Record<InputKind, [string, string]> = {
-    server_id: ['Server ID', '服务器 ID'],
-    command: ['Command', '命令'],
-    args: ['Arguments', '参数'],
-    url: ['Streamable HTTP URL', 'Streamable HTTP URL'],
-    cwd: ['Working directory', '工作目录'],
-    env: ['Environment', '环境变量'],
-    headers: ['Request headers', '请求头'],
-    edit: ['Edit server JSON', '编辑服务器 JSON'],
-    import: ['Paste MCP JSON', '粘贴 MCP JSON'],
-    publication_credential: ['Provider credential', 'Provider 凭据'],
-  };
-  return labels[kind][locale === 'zh' ? 1 : 0];
+  return MCP_STATUS_COPY[locale].editor.inputLabels[kind];
 }
 
 function inputHint(kind: InputKind, locale: UiLocale): string {
-  const optional = locale === 'zh' ? '可留空' : 'optional';
-  if (kind === 'args') return `JSON string array, ${optional}`;
-  if (kind === 'env' || kind === 'headers') return `JSON string map, ${optional}`;
-  if (kind === 'cwd') return optional;
-  if (kind === 'publication_credential') {
-    return locale === 'zh'
-      ? '仅保存到本机凭据存储;不会写入 profile、参数或对话 · Enter 提交 · Esc 返回'
-      : 'Stored only in the local credential store; never written to profiles, arguments, or chat · Enter submit · Esc back';
-  }
-  return locale === 'zh' ? 'Enter 提交 · Esc 返回' : 'Enter submit · Esc back';
+  const hints = MCP_STATUS_COPY[locale].editor.hints;
+  if (kind === 'args') return hints.args;
+  if (kind === 'env' || kind === 'headers') return hints.map;
+  if (kind === 'cwd') return hints.optional;
+  if (kind === 'publication_credential') return hints.credential;
+  return hints.submit;
 }
 
 function configurationLine(state: 'synchronizing' | 'out_of_sync', locale: UiLocale): string {
-  if (state === 'synchronizing') {
-    return locale === 'zh' ? '配置同步中…' : 'Configuration is synchronizing…';
-  }
-  return ansi.red(
-    locale === 'zh'
-      ? '持久化配置与 MCP Manager 尚未同步。'
-      : 'Durable configuration and MCP Manager are out of sync.',
-  );
+  const editor = MCP_STATUS_COPY[locale].editor;
+  if (state === 'synchronizing') return editor.synchronizing;
+  return ansi.red(editor.outOfSync);
 }
 
 function unavailableDocument(locale: UiLocale): string[] {
@@ -880,14 +823,6 @@ function unavailableDocument(locale: UiLocale): string[] {
   return [ansi.yellow(copy.unavailableTitle), copy.unavailableDetail];
 }
 
-function heading(locale: UiLocale, en: string, zh: string): string {
-  return ansi.bold(locale === 'zh' ? zh : en);
-}
-
-function confirmCopy(locale: UiLocale): string {
-  return locale === 'zh' ? 'y 确认 · Esc 取消' : 'y Confirm · Esc cancel';
-}
-
 function loadingCopy(locale: UiLocale): string {
   return MCP_STATUS_COPY[locale].loading;
 }
diff --git a/packages/cli/src/pi-tui-runner.ts b/packages/cli/src/pi-tui-runner.ts
index 167f1016da..299a3fca5a 100644
--- a/packages/cli/src/pi-tui-runner.ts
+++ b/packages/cli/src/pi-tui-runner.ts
@@ -40,7 +40,10 @@ import {
 } from '@maka/core/model-thinking';
 import { type ModelInfo, type ProviderType } from '@maka/core/llm-connections';
 import type { OrchestrationMode } from '@maka/core/orchestration';
-import type { SkillInvocationResult } from '@maka/core/skill-invocation';
+import type {
+  SkillInvocationFailureReason,
+  SkillInvocationResult,
+} from '@maka/core/skill-invocation';
 import { projectRevisionLinkedSessionTree } from '@maka/core/session-revisions';
 import {
   slashCommandsForSurface,
@@ -52,7 +55,12 @@ import {
   type SessionSummary,
   type StoredMessage,
 } from '@maka/core/session';
-import { formatUiMessage, type UiLocale } from '@maka/core/ui-locale';
+import {
+  defineUiMessageCatalog,
+  formatUiMessage,
+  resolveUiMessageCatalog,
+  type UiLocale,
+} from '@maka/core/ui-locale';
 import {
   buildForeignSessionHandoffMessage,
   foreignSessionHandoffDisplayText,
@@ -167,6 +175,7 @@ import {
   isLiveGoalStatus,
 } from './pi-goal.js';
 import { getTuiPrimaryGuidance } from './tui-primary-guidance.js';
+import { TUI_COPY_RESOURCES } from './tui-copy-catalog.js';
 import type { GoalControlAction, GoalProjection } from '@maka/runtime-host/protocol';
 
 export interface MakaPiTuiInput {
@@ -323,36 +332,81 @@ export function safeBoundaryResumeParkedCopy(reason: TurnResumeParkReason): {
   }
 }
 
+interface TuiRewindCopy {
+  readonly pickerTitle: string;
+  readonly pending: string;
+  readonly doneRefilled: string;
+  readonly doneKeptDraft: string;
+  readonly noTargets: string;
+  readonly busy: string;
+  readonly pickerHint: string;
+}
+
+interface TuiSkillsCopy {
+  readonly pickerTitle: string;
+  readonly usage: string;
+  readonly noneAvailable: string;
+  readonly loaded: string;
+  readonly loadFailed: string;
+  readonly outcomeNoRequest: string;
+  readonly outcomeMarkersNotSent: string;
+  readonly failedItem: string;
+  readonly tooManyRequestsItem: string;
+  readonly listSeparator: string;
+  readonly failureReasons: Readonly<Record<SkillInvocationFailureReason, string>>;
+}
+
+interface TuiConnectionIdentityCopy {
+  readonly withRecovery: string;
+  readonly emptyChoiceRecovery: string;
+  readonly confirmAccount: string;
+  readonly accountDeleted: string;
+  readonly identityMismatch: string;
+  readonly accountDisabled: string;
+}
+
+interface TuiSessionActionsCopy {
+  readonly foreignScanFailed: string;
+  readonly newSessionFailed: string;
+}
+
+const TUI_REWIND_COPY = resolveUiMessageCatalog(
+  defineUiMessageCatalog<TuiRewindCopy>()(TUI_COPY_RESOURCES.rewind),
+);
+
+const TUI_SKILLS_COPY = resolveUiMessageCatalog(
+  defineUiMessageCatalog<TuiSkillsCopy>()(TUI_COPY_RESOURCES.skills),
+);
+
+const TUI_CONNECTION_IDENTITY_COPY = resolveUiMessageCatalog(
+  defineUiMessageCatalog<TuiConnectionIdentityCopy>()(TUI_COPY_RESOURCES['connection-identity']),
+);
+
+const TUI_SESSION_ACTIONS_COPY = resolveUiMessageCatalog(
+  defineUiMessageCatalog<TuiSessionActionsCopy>()(TUI_COPY_RESOURCES['session-actions']),
+);
+
 function sessionConnectionIdentityNotice(
   session: Pick<SessionSummary, 'llmConnectionId' | 'llmConnectionSlug'>,
   identities: MakaPiTuiInput['connectionIdentities'],
   locale: UiLocale,
 ): string | undefined {
   if (!identities) return undefined;
-  const emptyChoiceRecovery =
-    locale === 'zh'
-      ? '如果 /model 没有可选项,请先添加或启用连接(API Key 连接可运行 /setup)。'
-      : 'If /model has no choices, add or enable a connection first (run /setup for API-key connections).';
+  const copy = TUI_CONNECTION_IDENTITY_COPY[locale];
+  const withRecovery = (notice: string) =>
+    formatUiMessage(copy.withRecovery, { notice, recovery: copy.emptyChoiceRecovery }, locale);
   if (!session.llmConnectionId) {
-    return locale === 'zh'
-      ? `此任务来自旧版本,需要确认一次账号。运行 /model 选择现有账号和模型。${emptyChoiceRecovery}`
-      : `This task comes from an older version and needs a one-time account confirmation. Run /model and choose an existing account and model. ${emptyChoiceRecovery}`;
+    return withRecovery(copy.confirmAccount);
   }
   const identified = identities.find((entry) => entry.connectionId === session.llmConnectionId);
   if (!identified) {
-    return locale === 'zh'
-      ? `原账号已删除;运行 /model 选择新账号和模型后继续。${emptyChoiceRecovery}`
-      : `The original account was deleted. Run /model and choose a new account and model to continue. ${emptyChoiceRecovery}`;
+    return withRecovery(copy.accountDeleted);
   }
   if (identified.connectionSlug !== session.llmConnectionSlug) {
-    return locale === 'zh'
-      ? `任务保存的账号身份与当前连接不一致;运行 /model 重新选择账号和模型。${emptyChoiceRecovery}`
-      : `The saved account identity no longer matches its connection. Run /model and choose an account and model again. ${emptyChoiceRecovery}`;
+    return withRecovery(copy.identityMismatch);
   }
   if (!identified.enabled) {
-    return locale === 'zh'
-      ? `原账号已停用;请启用该账号,或运行 /model 选择新账号和模型。${emptyChoiceRecovery}`
-      : `The original account is disabled. Enable it, or run /model and choose a new account and model. ${emptyChoiceRecovery}`;
+    return withRecovery(copy.accountDisabled);
   }
   return undefined;
 }
@@ -584,7 +638,7 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
 
   const transcript = new MakaTranscriptComponent(state, metadata);
   const activityStrip = new MakaActivityStripComponent(metadata);
-  const pendingQueue = new MakaPendingQueueComponent(state);
+  const pendingQueue = new MakaPendingQueueComponent(state, locale);
   const statusLine = new MakaStatusLineComponent(metadata);
   // Use the vendor editor's full 20-item autocomplete capacity. Larger command
   // catalogs remain scrollable and keep an exact position/total counter.
@@ -737,35 +791,51 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
   // autocomplete or picker open.
   void listSkillsCached(true);
 
-  const SKILL_INVOCATION_FAILURE_REASON_LABEL: Record<string, string> = {
-    not_found: '未找到',
-    disabled: '已禁用',
-    host_incompatible: '当前主机缺少其依赖的工具',
-    invalid_name: '名称无效',
-    too_many_requests: '调用请求过多',
-  };
+  const skillsCopy = TUI_SKILLS_COPY[locale];
 
   const showSkillInvocation = (skillInvocation: SkillInvocationResult): void => {
     const failed = skillInvocation.failed;
     const failedLabels = failed.map((entry) =>
       entry.reason === 'too_many_requests'
-        ? `请求超过 ${entry.requestLimit} 个上限(${SKILL_INVOCATION_FAILURE_REASON_LABEL[entry.reason]})`
-        : `/skill:${entry.request}(${SKILL_INVOCATION_FAILURE_REASON_LABEL[entry.reason] ?? entry.reason})`,
+        ? formatUiMessage(
+            skillsCopy.tooManyRequestsItem,
+            { limit: entry.requestLimit, reason: skillsCopy.failureReasons[entry.reason] },
+            locale,
+          )
+        : formatUiMessage(
+            skillsCopy.failedItem,
+            { request: entry.request, reason: skillsCopy.failureReasons[entry.reason] },
+            locale,
+          ),
     );
     if (failed.length > 0) {
       state.entries.push({
         kind: 'notice',
         level: 'info',
-        text: `未能加载技能 ${failedLabels.join('、')};${
-          skillInvocation.loaded.length === 0 ? '未发起模型请求。' : '失败的调用标记未发送给模型。'
-        }`,
+        text: formatUiMessage(
+          skillsCopy.loadFailed,
+          {
+            failures: failedLabels.join(skillsCopy.listSeparator),
+            outcome:
+              skillInvocation.loaded.length === 0
+                ? skillsCopy.outcomeNoRequest
+                : skillsCopy.outcomeMarkersNotSent,
+          },
+          locale,
+        ),
       });
     }
     if (skillInvocation.loaded.length > 0) {
       state.entries.push({
         kind: 'notice',
         level: 'info',
-        text: `已加载技能:${skillInvocation.loaded.map((skill) => skill.name).join('、')}`,
+        text: formatUiMessage(
+          skillsCopy.loaded,
+          {
+            names: skillInvocation.loaded.map((skill) => skill.name).join(skillsCopy.listSeparator),
+          },
+          locale,
+        ),
       });
     }
     requestRender();
@@ -1932,7 +2002,7 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
     const pendingNotice: (typeof state.entries)[number] = {
       kind: 'notice',
       level: 'info',
-      text: '正在回退到该轮之前…',
+      text: TUI_REWIND_COPY[locale].pending,
     };
     state.entries.push(pendingNotice);
     requestRender();
@@ -1957,9 +2027,7 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
       state.entries.push({
         kind: 'notice',
         level: 'info',
-        text: refill
-          ? '已回退到该轮之前(分支为新任务,原任务保留),该轮 prompt 已回填输入框,可修改后重新发送。'
-          : '已回退到该轮之前(分支为新任务,原任务保留)。输入框已有未发送内容,未覆盖;该轮 prompt 已存入输入历史,可按 ↑ 找回。',
+        text: refill ? TUI_REWIND_COPY[locale].doneRefilled : TUI_REWIND_COPY[locale].doneKeptDraft,
       });
       requestRender();
     } catch (error) {
@@ -2486,6 +2554,9 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
     // prefixed select value so they never collide with Maka session ids. A scan
     // error is surfaced (not silently swallowed): degrade to no rows but tell
     // the user why, so a real store bug isn't mistaken for "no sessions".
+    // Deliberate #2672 exception: the interpolated detail is a local
+    // file-scan diagnostic (not backend copy) and the TUI has no log channel
+    // to carry it, so dropping it would hide the only debugging signal.
     const foreignByValue = new Map<string, ForeignSessionSummary>();
     if ('error' in foreignScan) {
       const detail =
@@ -2493,7 +2564,11 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
       state.entries.push({
         kind: 'notice',
         level: 'error',
-        text: `读取外部对话失败:${detail}`,
+        text: formatUiMessage(
+          TUI_SESSION_ACTIONS_COPY[locale].foreignScanFailed,
+          { detail },
+          locale,
+        ),
       });
     } else {
       for (const summary of foreignScan.summaries) {
@@ -2578,7 +2653,7 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
       state.entries.push({
         kind: 'notice',
         level: 'info',
-        text: '没有可回退的轮次。',
+        text: TUI_REWIND_COPY[locale].noTargets,
       });
       requestRender();
       return;
@@ -2587,9 +2662,10 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
       value: target.turnId,
       label: target.label,
     }));
+    const rewindCopy = TUI_REWIND_COPY[locale];
     showSelectPicker(
-      'Rewind',
-      'Rewind',
+      rewindCopy.pickerTitle,
+      rewindCopy.pickerTitle,
       items,
       (item) => {
         // runControl drops the action silently when busy is already held (e.g.
@@ -2600,7 +2676,7 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
           state.entries.push({
             kind: 'notice',
             level: 'error',
-            text: '无法回退:当前有正在进行的操作 — 请等待其完成,或中断(Esc)后重试。',
+            text: rewindCopy.busy,
           });
           requestRender();
           return;
@@ -2610,7 +2686,7 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
       {
         minPrimaryColumnWidth: 24,
         maxPrimaryColumnWidth: 48,
-        hint: '回到选定轮次之前(丢弃该轮及之后,prompt 回填输入框) · enter 选择 / esc 取消',
+        hint: rewindCopy.pickerHint,
       },
     );
   };
@@ -2618,17 +2694,15 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
   const newSession = async (): Promise<boolean> => {
     try {
       await input.driver.startNewSession();
-    } catch (error) {
+    } catch {
       // The identity swap was aborted driver-side: the previous Session, its
       // transcript, and every user-command card stay exactly as they were.
-      // Surface why instead of silently stranding the running commands.
+      // Surface the failure instead of silently stranding the running
+      // commands; the driver's raw error text is not product copy (#2672).
       state.entries.push({
         kind: 'notice',
         level: 'error',
-        text:
-          error instanceof Error && error.message.length > 0
-            ? error.message
-            : '无法开始新会话:停止本地命令失败,请稍后重试。',
+        text: TUI_SESSION_ACTIONS_COPY[locale].newSessionFailed,
       });
       requestRender();
       return false;
@@ -2792,13 +2866,13 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
       state.entries.push({
         kind: 'notice',
         level: 'info',
-        text: '当前没有可调用的技能。',
+        text: skillsCopy.noneAvailable,
       });
       requestRender();
       return;
     }
     showSelectPicker(
-      'Invoke Skill',
+      skillsCopy.pickerTitle,
       String(entries.length),
       skillPickerItems(entries),
       (item) => {
@@ -3341,7 +3415,7 @@ export async function runMakaPiTui(input: MakaPiTuiInput): Promise<void> {
           state.entries.push({
             kind: 'notice',
             level: 'error',
-            text: 'Usage: /skill,或直接在消息中输入 /skill:<name>',
+            text: skillsCopy.usage,
           });
           requestRender();
           return;
diff --git a/packages/cli/src/tui-copy-catalog.ts b/packages/cli/src/tui-copy-catalog.ts
index 6f1302b8a5..28bd48ba35 100644
--- a/packages/cli/src/tui-copy-catalog.ts
+++ b/packages/cli/src/tui-copy-catalog.ts
@@ -59,6 +59,70 @@ export const TUI_COPY_RESOURCES = {
       configuredOnly: 'configured only',
       configPending: 'config pending',
       toolCount: '{count, plural, one {# tool} other {# tools}}',
+      editor: {
+        addTitle: 'Add MCP server',
+        transportTitle: 'Transport',
+        protocolTitle: 'Protocol preference',
+        confirmAddTitle: 'Add this MCP server?',
+        confirmImportTitle: 'Import MCP servers?',
+        confirmRemoveTitle: 'Remove {serverId}?',
+        confirmRemoveCredentialTitle: 'Remove the remote provider credential?',
+        confirmRemoveCredentialDetail:
+          'This stops publishing MCP tools to the selected Runtime Host.',
+        confirmHint: 'y Confirm · Esc cancel',
+        synchronizing: 'Configuration is synchronizing…',
+        outOfSync: 'Durable configuration and MCP Manager are out of sync.',
+        action: {
+          test: 'Testing MCP server…',
+          reconnect: 'Reconnecting MCP server…',
+          apply: 'Applying MCP configuration…',
+        },
+        inputLabels: {
+          server_id: 'Server ID',
+          command: 'Command',
+          args: 'Arguments',
+          url: 'Streamable HTTP URL',
+          cwd: 'Working directory',
+          env: 'Environment',
+          headers: 'Request headers',
+          edit: 'Edit server JSON',
+          import: 'Paste MCP JSON',
+          publication_credential: 'Provider credential',
+        },
+        hints: {
+          args: 'JSON string array, optional',
+          map: 'JSON string map, optional',
+          optional: 'optional',
+          submit: 'Enter submit · Esc back',
+          credential:
+            'Stored only in the local credential store; never written to profiles, arguments, or chat · Enter submit · Esc back',
+        },
+        results: {
+          exists: 'That server ID already exists.',
+          stale_config: 'MCP configuration changed; retry the action.',
+          stale_edit: 'This server changed; reopen it before editing.',
+          stale_import: 'An imported entry changed; preview the import again.',
+          missing: 'That server no longer exists.',
+          closed: 'The MCP controller is closed.',
+          'invalid-config': 'The server configuration is invalid.',
+          'credential-cleanup-failed':
+            'Stored credentials could not be removed; the configuration was not changed.',
+          'publication-credential-failed':
+            'The provider credential could not be stored or applied.',
+          'persist-failed': 'The configuration could not be saved.',
+          'manager-failed': 'The MCP connection action failed.',
+          turn_active: 'MCP cannot be changed while a turn or another control action is running.',
+          invalid: 'Check the value and try again.',
+          published: 'Configuration saved and tools refreshed.',
+          pending_host: 'Configuration saved; publication is waiting for Runtime Host.',
+          sync_failed: 'Configuration saved, but the MCP manager is out of sync.',
+          publication_failed: 'Configuration saved, but capability publication failed.',
+          test_ok: 'Connection test passed.',
+          test_failed: 'Connection test failed.',
+          test_publication_failed: 'Connection test passed, but capability publication failed.',
+          test_pending_host: 'Connection test passed; publication is waiting for Runtime Host.',
+        },
+      },
     },
     zh: {
       title: 'MCP 服务器',
@@ -97,6 +161,66 @@ export const TUI_COPY_RESOURCES = {
       configuredOnly: '仅已配置',
       configPending: '配置待同步',
       toolCount: '{count} 个工具',
+      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 string array, 可留空',
+          map: 'JSON string map, 可留空',
+          optional: '可留空',
+          submit: 'Enter 提交 · Esc 返回',
+          credential: '仅保存到本机凭据存储;不会写入 profile、参数或对话 · 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 发布。',
+        },
+      },
     },
   },
   pickers: {
@@ -371,6 +495,119 @@ export const TUI_COPY_RESOURCES = {
       },
     },
   },
+  rewind: {
+    en: {
+      pickerTitle: 'Rewind',
+      pending: 'Rewinding to before this turn…',
+      doneRefilled:
+        'Rewound to before this turn (branched into a new task; the original task is kept). The turn’s prompt was refilled into the input box — edit it and send again.',
+      doneKeptDraft:
+        'Rewound to before this turn (branched into a new task; the original task is kept). The input box already had unsent content and was left untouched; the turn’s prompt was saved to input history — press ↑ to recall it.',
+      noTargets: 'No turns to rewind to.',
+      busy: 'Cannot rewind: another action is in progress — wait for it to finish, or interrupt (Esc) and retry.',
+      pickerHint:
+        'Return to before the selected turn (discards it and later turns; the prompt refills the input box) · enter select / esc cancel',
+    },
+    zh: {
+      pickerTitle: '回退',
+      pending: '正在回退到该轮之前…',
+      doneRefilled:
+        '已回退到该轮之前(分支为新任务,原任务保留),该轮 prompt 已回填输入框,可修改后重新发送。',
+      doneKeptDraft:
+        '已回退到该轮之前(分支为新任务,原任务保留)。输入框已有未发送内容,未覆盖;该轮 prompt 已存入输入历史,可按 ↑ 找回。',
+      noTargets: '没有可回退的轮次。',
+      busy: '无法回退:当前有正在进行的操作 — 请等待其完成,或中断(Esc)后重试。',
+      pickerHint: '回到选定轮次之前(丢弃该轮及之后,prompt 回填输入框) · enter 选择 / esc 取消',
+    },
+  },
+  skills: {
+    en: {
+      pickerTitle: 'Invoke Skill',
+      usage: 'Usage: /skill, or type /skill:<name> inline in a message',
+      noneAvailable: 'No skills are available to invoke right now.',
+      loaded: 'Skills loaded: {names}',
+      loadFailed: 'Could not load skills {failures}; {outcome}',
+      outcomeNoRequest: 'no model request was made.',
+      outcomeMarkersNotSent: 'the failed invocation markers were not sent to the model.',
+      failedItem: '/skill:{request} ({reason})',
+      tooManyRequestsItem: 'more than the {limit}-request limit ({reason})',
+      listSeparator: ', ',
+      failureReasons: {
+        invalid_name: 'invalid name',
+        not_found: 'not found',
+        disabled: 'disabled',
+        host_incompatible: 'the current host is missing a tool it depends on',
+        resolution_failed: 'resolution failed',
+        too_many_requests: 'too many requests',
+      },
+    },
+    zh: {
+      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: {
+      withRecovery: '{notice} {recovery}',
+      emptyChoiceRecovery:
+        'If /model has no choices, add or enable a connection first (run /setup for API-key connections).',
+      confirmAccount:
+        'This task comes from an older version and needs a one-time account confirmation. Run /model and choose an existing account and model.',
+      accountDeleted:
+        'The original account was deleted. Run /model and choose a new account and model to continue.',
+      identityMismatch:
+        'The saved account identity no longer matches its connection. Run /model and choose an account and model again.',
+      accountDisabled:
+        'The original account is disabled. Enable it, or run /model and choose a new account and model.',
+    },
+    zh: {
+      withRecovery: '{notice}{recovery}',
+      emptyChoiceRecovery:
+        '如果 /model 没有可选项,请先添加或启用连接(API Key 连接可运行 /setup)。',
+      confirmAccount: '此任务来自旧版本,需要确认一次账号。运行 /model 选择现有账号和模型。',
+      accountDeleted: '原账号已删除;运行 /model 选择新账号和模型后继续。',
+      identityMismatch: '任务保存的账号身份与当前连接不一致;运行 /model 重新选择账号和模型。',
+      accountDisabled: '原账号已停用;请启用该账号,或运行 /model 选择新账号和模型。',
+    },
+  },
+  'session-actions': {
+    en: {
+      foreignScanFailed: 'Could not read external conversations: {detail}',
+      newSessionFailed: 'Could not start a new session. Try again shortly.',
+    },
+    zh: {
+      foreignScanFailed: '读取外部对话失败:{detail}',
+      newSessionFailed: '无法开始新会话,请稍后重试。',
+    },
+  },
+  'pending-queue': {
+    en: {
+      steeringLabel: 'Steering:',
+      queuedLabel: 'Queued:',
+      requeueHint: 'Alt+↑ take queued messages back to re-edit',
+    },
+    zh: {
+      steeringLabel: '注入:',
+      queuedLabel: '已排队:',
+      requeueHint: 'Alt+↑ 取回队列以重新编辑',
+    },
+  },
   'session-status': {
     en: {
       running: 'running',


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