From 82880665e61102517683fb2892d727d5b502ea8a Mon Sep 17 00:00:00 2001 From: me2seeks Date: Tue, 13 Oct 2026 14:54:11 +0800 Subject: [PATCH] refactor(i18n): preserve shipped copy while preparing locale fallback Require complete English, Simplified Chinese and Traditional Chinese catalogs while allowing partial overlays for future locales. Preserve function signatures and optional translated fields during resolution. Complete the TUI, shared UI and Desktop compatibility inventory, including locale-shaped Astryx/provider maps and locale parameters. Keep the current language set and all existing copy unchanged. Co-authored-by: Heewon Chae <68272931+heeoneie@users.noreply.github.com> Generated-by: Claude Code Generated-by: OpenAI Codex --- apps/desktop/renderer-architecture.json | 104 +- .../main/__tests__/bot-chat-detail.test.ts | 14 +- .../main/__tests__/health-center-copy.test.ts | 12 +- .../__tests__/settings-memory-copy.test.ts | 20 +- apps/desktop/src/main/browser-message-box.ts | 4 +- .../main/client-settings-confirmation-copy.ts | 6 +- .../src/main/computer-use/status-item.ts | 19 +- apps/desktop/src/main/main.ts | 16 +- .../src/main/native-diagnostic-dialog-copy.ts | 19 +- .../src/main/native-file-dialog-copy.ts | 6 +- apps/desktop/src/main/notifications-policy.ts | 6 +- .../permission-overlay-copy.ts | 21 +- apps/desktop/src/main/project-picker-copy.ts | 4 +- .../main/runtime-host-artifacts-ipc-main.ts | 6 +- .../src/main/runtime-host-config-ipc-main.ts | 6 +- .../src/main/runtime-host-quit-copy.ts | 6 +- .../src/main/runtime-host-wsl-handoff.ts | 6 +- apps/desktop/src/main/settings-recovery.ts | 10 +- apps/desktop/src/main/windows-app-tray.ts | 6 +- .../contracts/conversation-copy.ts | 6 +- .../application/contracts/transcript-copy.ts | 6 +- .../app-update/locales/app-update-copy.ts | 6 +- .../settings-provider-copy.ts | 6 +- .../conversation/model/executor-submission.ts | 6 +- .../diagnostics/locales/diagnostics-copy.ts | 6 +- .../locales/runtime-host-handoff-copy.ts | 6 +- .../ui/runtime-host-resource-dialog.tsx | 68 +- .../locales/archive-retention-copy.ts | 6 +- .../session-workspace-recovery-copy.ts | 6 +- .../workbar/ui/recent-turn-overlay.tsx | 16 +- .../workhub/locales/workhub-live-copy.ts | 6 +- .../src/renderer/locales/agent-graph-copy.ts | 19 +- .../src/renderer/locales/artifact-copy.ts | 19 +- .../src/renderer/locales/browser-copy.ts | 19 +- .../locales/capability-reason-copy.ts | 6 +- .../locales/external-session-import-copy.ts | 19 +- apps/desktop/src/renderer/locales/mcp-copy.ts | 19 +- .../src/renderer/locales/onboarding-copy.ts | 6 +- .../src/renderer/locales/peer-mesh-copy.ts | 6 +- .../locales/permission-center-copy.ts | 19 +- .../src/renderer/locales/plan-mode-copy.ts | 19 +- .../locales/session-collaboration-copy.ts | 4 +- .../renderer/locales/session-local-copy.ts | 6 +- .../src/renderer/locales/settings-bot-copy.ts | 12 +- .../locales/settings-daily-review-copy.ts | 6 +- .../renderer/locales/settings-data-copy.ts | 6 +- .../locales/settings-external-agents-copy.ts | 4 +- .../renderer/locales/settings-health-copy.ts | 45 +- .../src/renderer/locales/settings-jev-copy.ts | 6 +- .../renderer/locales/settings-memory-copy.ts | 19 +- .../locales/settings-navigation-copy.ts | 6 +- .../locales/settings-preferences-copy.ts | 19 +- .../locales/settings-projects-copy.ts | 6 +- .../renderer/locales/settings-shared-copy.ts | 6 +- .../locales/settings-subagents-copy.ts | 6 +- .../renderer/locales/settings-tasks-copy.ts | 6 +- .../locales/settings-test-result-copy.ts | 19 +- .../renderer/locales/settings-usage-copy.ts | 19 +- .../locales/settings-web-search-copy.ts | 6 +- .../src/renderer/locales/shell-copy.ts | 19 +- .../renderer/locales/shell-remaining-copy.ts | 6 +- .../renderer/locales/storage-usage-copy.ts | 6 +- .../renderer/locales/task-readiness-copy.ts | 6 +- .../renderer/locales/work-board-error-copy.ts | 6 +- .../src/renderer/settings/bot-chat-detail.tsx | 4 +- .../renderer/settings/bot-chat-overview.tsx | 4 +- .../settings/daily-review-settings-page.tsx | 4 +- .../settings/provider-display-copy.ts | 11 +- packages/cli/src/tui-copy-catalog.ts | 2972 +++++++++-------- packages/core/src/__tests__/ui-locale.test.ts | 33 + packages/core/src/redaction.ts | 54 +- packages/core/src/relative-time-policy.ts | 14 +- packages/core/src/tool-quiet-preview.ts | 74 +- packages/core/src/ui-locale.ts | 66 +- .../src/client/host-handoff-copy.ts | 396 +-- .../tool-activity-presentation.test.ts | 12 +- packages/ui/src/astryx-i18n.tsx | 6 +- packages/ui/src/conversation-copy.ts | 19 +- packages/ui/src/daily-review-copy.ts | 19 +- packages/ui/src/executor-model-picker.tsx | 6 +- packages/ui/src/locale-helpers.ts | 6 +- packages/ui/src/progress-card.tsx | 10 +- packages/ui/src/runtime-resume-copy.ts | 6 +- packages/ui/src/scheduled-task-copy.ts | 19 +- packages/ui/src/session-hover-card-copy.ts | 6 +- packages/ui/src/shared-ui-copy.ts | 19 +- packages/ui/src/shell-controls-copy.ts | 6 +- packages/ui/src/skills-copy.ts | 19 +- packages/ui/src/tool-activity/copy.ts | 19 +- 89 files changed, 2428 insertions(+), 2206 deletions(-) diff --git a/apps/desktop/renderer-architecture.json b/apps/desktop/renderer-architecture.json index 9b57266c893..6f8bdb00d8b 100644 --- a/apps/desktop/renderer-architecture.json +++ b/apps/desktop/renderer-architecture.json @@ -1092,7 +1092,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/artifact-copy.ts": { "bridgePaths": {}, @@ -1101,7 +1103,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/browser-copy.ts": { "bridgePaths": {}, @@ -1110,7 +1114,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/capability-reason-copy.ts": { "bridgePaths": {}, @@ -1130,7 +1136,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/mcp-copy.ts": { "bridgePaths": {}, @@ -1139,7 +1147,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/onboarding-copy.ts": { "bridgePaths": {}, @@ -1148,7 +1158,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/peer-mesh-copy.ts": { "bridgePaths": {}, @@ -1157,7 +1169,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/permission-center-copy.ts": { "bridgePaths": {}, @@ -1166,7 +1180,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/plan-mode-copy.ts": { "bridgePaths": {}, @@ -1186,7 +1202,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/session-local-copy.ts": { "bridgePaths": {}, @@ -1195,7 +1213,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-bot-copy.ts": { "bridgePaths": {}, @@ -1215,7 +1235,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-data-copy.ts": { "bridgePaths": {}, @@ -1224,7 +1246,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-external-agents-copy.ts": { "bridgePaths": {}, @@ -1233,7 +1257,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-health-copy.ts": { "bridgePaths": {}, @@ -1242,7 +1268,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-jev-copy.ts": { "bridgePaths": {}, @@ -1251,7 +1279,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-memory-copy.ts": { "bridgePaths": {}, @@ -1271,7 +1301,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-preferences-copy.ts": { "bridgePaths": {}, @@ -1280,7 +1312,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-projects-copy.ts": { "bridgePaths": {}, @@ -1300,7 +1334,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-subagents-copy.ts": { "bridgePaths": {}, @@ -1309,7 +1345,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-tasks-copy.ts": { "bridgePaths": {}, @@ -1318,7 +1356,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-test-result-copy.ts": { "bridgePaths": {}, @@ -1328,7 +1368,7 @@ "unresolvedDependencies": 0, "actionFactories": [], "dependencyPaths": { - "@maka/core/ui-locale": 1 + "@maka/core/ui-locale": 2 } }, "src/renderer/locales/settings-usage-copy.ts": { @@ -1338,7 +1378,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/settings-web-search-copy.ts": { "bridgePaths": {}, @@ -1347,7 +1389,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/shell-copy.ts": { "bridgePaths": {}, @@ -1369,7 +1413,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/storage-usage-copy.ts": { "bridgePaths": {}, @@ -1378,7 +1424,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/task-readiness-copy.ts": { "bridgePaths": {}, @@ -1387,7 +1435,9 @@ "lifecycleMethods": {}, "unresolvedDependencies": 0, "actionFactories": [], - "dependencyPaths": {} + "dependencyPaths": { + "@maka/core/ui-locale": 1 + } }, "src/renderer/locales/work-board-error-copy.ts": { "bridgePaths": {}, @@ -1397,7 +1447,7 @@ "unresolvedDependencies": 0, "actionFactories": [], "dependencyPaths": { - "@maka/core/ui-locale": 1 + "@maka/core/ui-locale": 2 } }, "src/renderer/model-catalog-choices.ts": { diff --git a/apps/desktop/src/main/__tests__/bot-chat-detail.test.ts b/apps/desktop/src/main/__tests__/bot-chat-detail.test.ts index fe9c4040cad..1f74ad6b2dc 100644 --- a/apps/desktop/src/main/__tests__/bot-chat-detail.test.ts +++ b/apps/desktop/src/main/__tests__/bot-chat-detail.test.ts @@ -27,7 +27,7 @@ import { pathToFileURL } from 'node:url'; import { createDefaultBotChannel } from '@maka/core/bot-chat-settings'; import { MAX_ALLOWED_USER_IDS, createDefaultSettings } from '@maka/core/settings'; import { BotRegistry, SlackBotBridge, WechatBridge, type BotStatus } from '@maka/runtime/bots'; -import { UI_LOCALES, type UiCatalog, type UiLocale } from '@maka/core/ui-locale'; +import { UI_LOCALES, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { AstryxLocaleProvider, LocaleProvider, ToastProvider } from '@maka/ui'; import { build } from 'esbuild'; import { parseHTML } from 'linkedom'; @@ -69,7 +69,11 @@ after(async () => { }); const invalidUsers = ['@alice', '@bob', '@carol', '@dave', '@eve']; -const expectedCopy = { +const expectedCopy = resolveUiMessageCatalog(defineShippedUiMessageCatalog<{ + help: string; + cappedHelp: string; + warnings: [number, string][]; +}>()({ 'zh-CN': { help: 'Telegram 用户 ID 是 64 位整数;填入后只接收列表里这些 ID 的来信,其它人发的消息会被静默忽略(不会回弹任何提示)。', cappedHelp: 'Telegram 用户 ID 是 64 位整数;填入后只接收列表里这些 ID 的来信,其它人发的消息会被静默忽略(不会回弹任何提示)。 (已达到上限)', @@ -100,11 +104,7 @@ const expectedCopy = { [5, 'These entries are not numeric IDs and may be usernames, so they will not match anyone: @alice, @bob, @carol and 2 more'], ], }, -} satisfies UiCatalog<{ - help: string; - cappedHelp: string; - warnings: [number, string][]; -}>; +})); for (const locale of UI_LOCALES) { const expected = expectedCopy[locale]; 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 5cf03c20ae3..5c1dd7a3da4 100644 --- a/apps/desktop/src/main/__tests__/health-center-copy.test.ts +++ b/apps/desktop/src/main/__tests__/health-center-copy.test.ts @@ -20,7 +20,7 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; import { CAPABILITY_REASON_CODES } from '@maka/core/capabilities'; -import { UI_LOCALES, type UiCatalog } from '@maka/core/ui-locale'; +import { UI_LOCALES, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { connectionLastTestMessageDisplay } from '../../renderer/features/connection-settings/index.js'; import { getHealthCenterCopy } from '../../renderer/locales/settings-health-copy.js'; import { getCapabilityReasonCopy } from '../../renderer/locales/capability-reason-copy.js'; @@ -100,11 +100,11 @@ test('renders runtime probe details from structured params, not string parsing', }); test('capability reason codes have copy in every locale, unknown reasons keep the fallback', () => { - const fallback = { - 'zh-CN': '状态详情请见对应设置页。', - 'zh-TW': '狀態詳細資料請參閱對應的設定頁。', - en: 'See the corresponding settings page for details.', - } satisfies UiCatalog; + const fallback = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ + 'zh-CN': '状态详情请见对应设置页。', + 'zh-TW': '狀態詳細資料請參閱對應的設定頁。', + en: 'See the corresponding settings page for details.', + })); assert.equal(getCapabilityReasonCopy('zh-CN').platform_credentials_missing, '未配置平台凭据'); for (const locale of UI_LOCALES) { const reasons = getCapabilityReasonCopy(locale); diff --git a/apps/desktop/src/main/__tests__/settings-memory-copy.test.ts b/apps/desktop/src/main/__tests__/settings-memory-copy.test.ts index d18566f5939..ba0c0f1f5a9 100644 --- a/apps/desktop/src/main/__tests__/settings-memory-copy.test.ts +++ b/apps/desktop/src/main/__tests__/settings-memory-copy.test.ts @@ -19,7 +19,7 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import { UI_LOCALES, type UiCatalog } from '@maka/core/ui-locale'; +import { UI_LOCALES, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { getMemorySettingsCopy, memoryResultMessage, @@ -51,7 +51,14 @@ test('falls back for an unknown Runtime Host memory rejection code', () => { } }); -const formattedCopy = { +const formattedCopy = resolveUiMessageCatalog(defineShippedUiMessageCatalog<{ + counts: string[][]; + summaries: string[][]; + redacted: string[]; + backupFailures: string[]; + preview: string[]; + labels: string[]; +}>()({ 'zh-CN': { counts: [ ['0 条生效', '草稿 0 条生效', '0 条已归档', '草稿 0 条已归档', '0 条记忆'], @@ -145,14 +152,7 @@ const formattedCopy = { 'The current MEMORY.md will be backed up before the selected backup replaces it. Restore: Backup #2', ], }, -} satisfies UiCatalog<{ - counts: string[][]; - summaries: string[][]; - redacted: string[]; - backupFailures: string[]; - preview: string[]; - labels: string[]; -}>; +})); for (const locale of UI_LOCALES) { const expected = formattedCopy[locale]; diff --git a/apps/desktop/src/main/browser-message-box.ts b/apps/desktop/src/main/browser-message-box.ts index d3f1af108bd..b8cf3f31a8b 100644 --- a/apps/desktop/src/main/browser-message-box.ts +++ b/apps/desktop/src/main/browser-message-box.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog } from '@maka/core/ui-locale'; +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { randomUUID } from 'node:crypto'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; @@ -649,4 +649,4 @@ function escapeHtml(value: string): string { }); } -const CLOSE_LABEL = { 'zh-CN': '关闭', 'zh-TW': '關閉', en: 'Close' } satisfies UiCatalog; +const CLOSE_LABEL = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': '关闭', 'zh-TW': '關閉', en: 'Close' })); diff --git a/apps/desktop/src/main/client-settings-confirmation-copy.ts b/apps/desktop/src/main/client-settings-confirmation-copy.ts index 6168a8776f6..b6e882d9b0b 100644 --- a/apps/desktop/src/main/client-settings-confirmation-copy.ts +++ b/apps/desktop/src/main/client-settings-confirmation-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { ClientSettingsChange } from './client-settings-tools.js'; type ConfirmationCopy = { @@ -28,7 +28,7 @@ type ConfirmationCopy = { buttons: [string, string]; }; -const COPY = { +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { labels: { theme: '主题', palette: '配色', uiLocale: '界面语言', runComplete: '系统通知', keepSystemAwake: '保持系统唤醒' }, on: '开启', @@ -50,7 +50,7 @@ const COPY = { message: "Allow Maka to update this client's settings?", buttons: ['Apply changes', 'Cancel'], }, -} satisfies UiCatalog; +})); export function clientSettingsConfirmation( changes: readonly ClientSettingsChange[], diff --git a/apps/desktop/src/main/computer-use/status-item.ts b/apps/desktop/src/main/computer-use/status-item.ts index 1dba08cb108..a944ba19150 100644 --- a/apps/desktop/src/main/computer-use/status-item.ts +++ b/apps/desktop/src/main/computer-use/status-item.ts @@ -20,7 +20,7 @@ import { createRequire } from 'node:module'; import { join } from 'node:path'; import type { Menu as MenuType, Tray } from 'electron'; -import { resolveSystemUiLocale, type UiCatalog, type UiLocale } from '@maka/core/ui-locale'; +import { resolveSystemUiLocale, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { CuOverlayHook } from '@maka/runtime/computer-use-types'; // Electron is CommonJS, and named imports from it fail outside a main process. @@ -130,24 +130,23 @@ interface StatusItemCopy { /** Codex's empty state. Reachable only if a menu outlives its rows. */ empty: string; } - -const COPY: UiCatalog = { - 'zh-CN': { +const copyZhCn: StatusItemCopy = { stopUsing: (appName) => `停止操作 ${appName}`, stopUnnamed: '停止 Computer Use', empty: '没有正在进行的任务', - }, - 'zh-TW': { + }; +const copyZhTw: StatusItemCopy = { stopUsing: (appName) => `停止操作 ${appName}`, stopUnnamed: '停止 Computer Use', empty: '沒有正在進行的任務', - }, - en: { + }; +const copyEn: StatusItemCopy = { stopUsing: (appName) => `Stop Using ${appName}`, stopUnnamed: 'Stop Computer Use', empty: 'No Active Sessions', - }, -}; + }; + +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': copyZhCn, 'zh-TW': copyZhTw, en: copyEn })); function defaultResolveLocale(): UiLocale { // Lazy on purpose: tool assembly runs at module load, before diff --git a/apps/desktop/src/main/main.ts b/apps/desktop/src/main/main.ts index 3384fa327c3..156d05c2392 100644 --- a/apps/desktop/src/main/main.ts +++ b/apps/desktop/src/main/main.ts @@ -17,7 +17,7 @@ * under the License. */ -import { resolveSystemUiLocale, type UiCatalog } from '@maka/core/ui-locale'; +import { resolveSystemUiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { DEV_LOSER_EXIT_CODE, developmentLaunchResultFile, @@ -263,7 +263,12 @@ if (!app.requestSingleInstanceLock()) { }); } -const DEV_SINGLETON_COPY = { +const DEV_SINGLETON_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog<{ + title: string; + message: string; + detail(profilePath: string): string; + exit: string; +}>()({ 'zh-CN': { title: 'Maka Dev 已在运行', message: '另一个 Maka Dev 实例正在使用此开发配置。', @@ -282,9 +287,4 @@ const DEV_SINGLETON_COPY = { detail: (profilePath: string) => `Development profile: ${profilePath}\n\nQuit the running instance, then retry.`, exit: 'Exit', }, -} satisfies UiCatalog<{ - title: string; - message: string; - detail(profilePath: string): string; - exit: string; -}>; +})); diff --git a/apps/desktop/src/main/native-diagnostic-dialog-copy.ts b/apps/desktop/src/main/native-diagnostic-dialog-copy.ts index 522033559cc..ec20f2e7967 100644 --- a/apps/desktop/src/main/native-diagnostic-dialog-copy.ts +++ b/apps/desktop/src/main/native-diagnostic-dialog-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; interface NativeDiagnosticDialogCopy { readonly dialog: { @@ -55,9 +55,7 @@ interface NativeDiagnosticDialogCopy { readonly exit: string; }; } - -const COPY = { - en: { +const copyEn: NativeDiagnosticDialogCopy = { dialog: { copy: 'Copy Diagnostics', copyAgain: 'Copy Again', @@ -95,8 +93,8 @@ const COPY = { repair: 'Repair Workspace', exit: 'Exit', }, - }, - 'zh-CN': { + }; +const copyZhCn: NativeDiagnosticDialogCopy = { dialog: { copy: '复制诊断信息', copyAgain: '再次复制', @@ -133,8 +131,8 @@ const COPY = { repair: '修复工作区', exit: '退出', }, - }, - 'zh-TW': { + }; +const copyZhTw: NativeDiagnosticDialogCopy = { dialog: { copy: '複製診斷資訊', copyAgain: '再次複製', @@ -171,8 +169,9 @@ const COPY = { repair: '修復工作區', exit: '退出', }, - }, -} satisfies UiCatalog; + }; + +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ en: copyEn, 'zh-CN': copyZhCn, 'zh-TW': copyZhTw })); export function getNativeDiagnosticDialogCopy(locale: UiLocale): NativeDiagnosticDialogCopy { return COPY[locale]; diff --git a/apps/desktop/src/main/native-file-dialog-copy.ts b/apps/desktop/src/main/native-file-dialog-copy.ts index 3ab00ff9b3d..7ea01a6d6aa 100644 --- a/apps/desktop/src/main/native-file-dialog-copy.ts +++ b/apps/desktop/src/main/native-file-dialog-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; /** * Copy for the native open/save panels. Format names (`Markdown`, @@ -35,7 +35,7 @@ interface NativeFileDialogCopy { readonly allFiles: string; } -const COPY = { +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { referenceFolder: '引用文件夹', addAttachments: '添加附件', @@ -63,7 +63,7 @@ const COPY = { saveConversation: 'Save conversation', allFiles: 'All Files', }, -} satisfies UiCatalog; +})); export function nativeFileDialogCopy(locale: UiLocale): NativeFileDialogCopy { return COPY[locale]; diff --git a/apps/desktop/src/main/notifications-policy.ts b/apps/desktop/src/main/notifications-policy.ts index 6cdc22586bd..487beace2cb 100644 --- a/apps/desktop/src/main/notifications-policy.ts +++ b/apps/desktop/src/main/notifications-policy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { SessionAttention } from '@maka/runtime-host/protocol'; export interface RunNotificationGate { @@ -36,7 +36,7 @@ export interface RunNotificationCopy { readonly body: string; } -const RUN_NOTIFICATION_COPY = { +const RUN_NOTIFICATION_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog>()({ 'zh-CN': { errored: { title: '任务出错', body: '本轮回答未能完成,点击查看详情。' }, completed: { title: '回答已生成', body: 'Maka 已完成本轮回答,点击查看。' }, @@ -52,7 +52,7 @@ const RUN_NOTIFICATION_COPY = { completed: { title: 'Response ready', body: 'Maka finished this response. Click to view it.' }, waiting: { title: 'Waiting for you', body: 'Maka needs your answer to continue. Click to view it.' }, }, -} satisfies UiCatalog>; +})); export interface RunNotificationEvent extends SessionAttention { readonly title?: string; 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 5311ff04693..1d55b052869 100644 --- a/apps/desktop/src/main/permission-overlay/permission-overlay-copy.ts +++ b/apps/desktop/src/main/permission-overlay/permission-overlay-copy.ts @@ -25,7 +25,7 @@ * resolves the locale and ships the finished strings in the show payload. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { DragGrantPermissionId } from './permission-overlay-controller.js'; export interface PermissionOverlayCopy { @@ -46,11 +46,7 @@ export interface PermissionOverlayCopy { /** Shown instead of the drag affordance when there is no .app to drag. */ noBundle: string; } - -type Catalog = UiCatalog>; - -const COPY: Catalog = { - 'zh-CN': { +const copyZhCn: Record = { accessibility: { headline: (appName) => `把 ${appName} 拖到上面的列表里,即可开启「辅助功能」`, fallback: '也可以在系统设置里点 + 号,从「应用程序」中选择本 App。', @@ -68,8 +64,8 @@ const COPY: Catalog = { restartHint: '若仍显示未授权,需要重启 App —— macOS 会缓存上一次的拒绝结果。', noBundle: '当前不是以 .app 方式运行,无法拖拽。请在系统设置里手动添加。', }, - }, - 'zh-TW': { + }; +const copyZhTw: Record = { accessibility: { headline: (appName) => `把 ${appName} 拖到上面的列表裡,即可開啟「輔助功能」`, fallback: '也可以在系統設定裡點 + 號,從「應用程式」中選擇本 App。', @@ -87,8 +83,8 @@ const COPY: Catalog = { restartHint: '若仍顯示未授權,需要重啟 App —— macOS 會快取上一次的拒絕結果。', noBundle: '目前不是以 .app 方式執行,無法拖拽。請在系統設定裡手動新增。', }, - }, - en: { + }; +const copyEn: Record = { accessibility: { headline: (appName) => `Drag ${appName} into the list above to allow Accessibility`, fallback: 'Or click + in System Settings and pick the app from Applications.', @@ -106,8 +102,9 @@ const COPY: Catalog = { restartHint: 'If it still reads as denied, restart the app — macOS caches the previous denial.', noBundle: 'Not running from a .app bundle, so there is nothing to drag. Add it manually in System Settings.', }, - }, -}; + }; + +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog>()({ 'zh-CN': copyZhCn, 'zh-TW': copyZhTw, en: copyEn })); export function getPermissionOverlayCopy( locale: UiLocale, diff --git a/apps/desktop/src/main/project-picker-copy.ts b/apps/desktop/src/main/project-picker-copy.ts index 1ce70aa251f..a84e42ca4fe 100644 --- a/apps/desktop/src/main/project-picker-copy.ts +++ b/apps/desktop/src/main/project-picker-copy.ts @@ -17,9 +17,9 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; -const TITLE = { 'zh-CN': '添加项目', 'zh-TW': '新增專案', en: 'Add project' } satisfies UiCatalog; +const TITLE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': '添加项目', 'zh-TW': '新增專案', en: 'Add project' })); export function projectPickerTitle(locale: UiLocale): string { return TITLE[locale]; diff --git a/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts b/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts index 2f52baf456e..750015b4ec6 100644 --- a/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts +++ b/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { randomUUID } from "node:crypto"; import { open, mkdir, rename, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; @@ -322,8 +322,8 @@ async function syncDirectory(path: string): Promise { } } -const ARTIFACT_DIALOG_COPY = { +const ARTIFACT_DIALOG_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog<{ saveAs(name: string): string }>()({ 'zh-CN': { saveAs: (name: string) => `另存为 ${name}` }, 'zh-TW': { saveAs: (name: string) => `另存為 ${name}` }, en: { saveAs: (name: string) => `Save ${name} as` }, -} satisfies UiCatalog<{ saveAs(name: string): string }>; +})); diff --git a/apps/desktop/src/main/runtime-host-config-ipc-main.ts b/apps/desktop/src/main/runtime-host-config-ipc-main.ts index 4f6f39160f9..42b65e93033 100644 --- a/apps/desktop/src/main/runtime-host-config-ipc-main.ts +++ b/apps/desktop/src/main/runtime-host-config-ipc-main.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { readFile, writeFile } from 'node:fs/promises'; import { isDeepStrictEqual } from 'node:util'; import type { IpcMain } from 'electron'; @@ -662,8 +662,8 @@ function sanitizeStrategy(value: unknown): ConnectionConflictStrategy { return value === 'overwrite' ? 'overwrite' : 'skip'; } -const CONFIG_DIALOG_COPY = { +const CONFIG_DIALOG_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog<{ exportTitle: string; importTitle: string }>()({ 'zh-CN': { exportTitle: '导出 Maka 配置', importTitle: '导入 Maka 配置' }, 'zh-TW': { exportTitle: '匯出 Maka 設定', importTitle: '匯入 Maka 設定' }, en: { exportTitle: 'Export Maka configuration', importTitle: 'Import Maka configuration' }, -} satisfies UiCatalog<{ exportTitle: string; importTitle: string }>; +})); diff --git a/apps/desktop/src/main/runtime-host-quit-copy.ts b/apps/desktop/src/main/runtime-host-quit-copy.ts index 4fd7bb58393..0a2550b5bb9 100644 --- a/apps/desktop/src/main/runtime-host-quit-copy.ts +++ b/apps/desktop/src/main/runtime-host-quit-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { MessageBoxOptions } from 'electron'; export interface RuntimeHostQuitDialog { @@ -54,7 +54,7 @@ interface RuntimeHostQuitCopy { readonly keepRunning: string; } -const COPY = { +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ en: { activeTitle: 'Maka is still working', activeMessage: 'Background work is still running.', @@ -79,4 +79,4 @@ const COPY = { stopAndQuit: '停止工作並結束', keepRunning: '繼續執行 Maka', }, -} satisfies UiCatalog; +})); diff --git a/apps/desktop/src/main/runtime-host-wsl-handoff.ts b/apps/desktop/src/main/runtime-host-wsl-handoff.ts index 67cc4034b4d..a270f434fb5 100644 --- a/apps/desktop/src/main/runtime-host-wsl-handoff.ts +++ b/apps/desktop/src/main/runtime-host-wsl-handoff.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { EnvironmentRuntimeHostProfile, HostHandoffBlocker, HostHandoffPhase, RuntimeHostRemoteCompatibilityError } from '@maka/runtime-host/client'; import { compareProductReleaseVersions } from '@maka/runtime-host/operator'; import type { DesktopRuntimeHostManagedServiceBinding } from './runtime-host-managed-services.js'; @@ -118,7 +118,7 @@ interface WslHandoffGuidance { readonly development: string; } -const GUIDANCE = { +const GUIDANCE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ en: { client: 'Update Desktop to a build compatible with this Host. The Host will not be downgraded.', binding: 'The WSL management binding is unavailable. Restore it through Runtime Host settings before updating.', @@ -140,4 +140,4 @@ const GUIDANCE = { target: '目前 Desktop 無法驗證合適的替換套件。請更新 Desktop,或透過開發環境明確管理選定的開發套件;現有 Host 會被保留。', development: '選定的開發構建', }, -} satisfies UiCatalog; +})); diff --git a/apps/desktop/src/main/settings-recovery.ts b/apps/desktop/src/main/settings-recovery.ts index 8f2a151a770..206e8d634d3 100644 --- a/apps/desktop/src/main/settings-recovery.ts +++ b/apps/desktop/src/main/settings-recovery.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { CorruptSettingsRecovery } from '@maka/storage/settings-store'; interface RecoveryCopy { @@ -28,7 +28,9 @@ interface RecoveryCopy { acknowledge: string; } -const COPY = { +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog & { + review: string; settings: string; backup: string; acknowledge: string; +}>()({ 'zh-CN': { recovered: { title: '设置已恢复为默认值', @@ -71,9 +73,7 @@ const COPY = { backup: 'Original file backup: ', acknowledge: 'OK', }, -} satisfies UiCatalog & { - review: string; settings: string; backup: string; acknowledge: string; -}>; +})); export function settingsRecoveryCopy( recovery: CorruptSettingsRecovery, diff --git a/apps/desktop/src/main/windows-app-tray.ts b/apps/desktop/src/main/windows-app-tray.ts index 66762db5698..3913b42ccfa 100644 --- a/apps/desktop/src/main/windows-app-tray.ts +++ b/apps/desktop/src/main/windows-app-tray.ts @@ -18,7 +18,7 @@ */ import type { Menu, MenuItemConstructorOptions, Tray } from 'electron'; -import type { UiCatalog } from '@maka/core/ui-locale'; +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { DesktopLocaleAuthority } from './desktop-locale-authority.js'; type TraySurface = Pick; @@ -40,11 +40,11 @@ interface WindowsAppTrayCopy { readonly quit: string; } -const copy = { +const copy = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ en: { open: 'Open Maka', workHub: 'Open WorkHub', quit: 'Quit Maka' }, 'zh-CN': { open: '打开 Maka', workHub: '打开 WorkHub', quit: '退出 Maka' }, 'zh-TW': { open: '開啟 Maka', workHub: '開啟 WorkHub', quit: '結束 Maka' }, -} satisfies UiCatalog; +})); /** Windows needs a visible way back after the last product window closes. */ export function createWindowsAppTray(deps: WindowsAppTrayDeps) { diff --git a/apps/desktop/src/renderer/application/contracts/conversation-copy.ts b/apps/desktop/src/renderer/application/contracts/conversation-copy.ts index 2a461f7dfc4..5193ee22a2d 100644 --- a/apps/desktop/src/renderer/application/contracts/conversation-copy.ts +++ b/apps/desktop/src/renderer/application/contracts/conversation-copy.ts @@ -17,6 +17,8 @@ * under the License. */ +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; + import type { InspectorCopy } from './session-inspector/copy.js'; import type { ChatConfigurationReason } from '@maka/core/connection-readiness'; @@ -342,7 +344,7 @@ function enDetail(parts: readonly string[]): string { return parts.length > 0 ? `: ${parts.join(', ')}` : ''; } -const COPY = { +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { actions: { stopFailedTitle: '停止失败', stopFailedFallback: '任务操作失败,请稍后重试。', refreshSessionsFailedTitle: '刷新任务列表失败', refreshSessionsFailedFallback: '刷新任务列表失败,请稍后重试。', conversationErrorTitle: '任务出错', conversationErrorFallback: '任务运行失败,请稍后重试。', branchCreatedTitle: '已创建分支', branchCreatedDescription: (name) => `新任务 ${name}`, revisionStartedTitle: '已创建修改版草稿', revisionStartedDescription: '原任务仍会保留;发送后将在新版本中继续', revisionReadyTitle: '可以修改并重发了', revisionReadyDescription: '已回到该消息之前;可直接重发或修改后发送', revisionUnavailableTitle: '暂时无法编辑这条消息', revisionAttachmentsUnsupported: '这条消息自带的附件不参与编辑并重发,请复制文字后新建消息。', revisionTransformedTextUnsupported: '通过显式技能发送的历史消息暂不支持编辑并重发,请复制文字后重新选择技能。', revisionDraftAttachmentConflict: 'Composer 中已有待发送附件,请先发送或移除附件,再编辑历史消息。', revisionDraftQuoteConflict: 'Composer 中已有暂存引用,请先发送或移除引用,再编辑历史消息。', revisionMixedContextUnsupported: '编辑期间新增的暂存内容不能与恢复的引用一起发送,请取消本次编辑后重试。', revisionCommandUnsupported: '修改消息时不能执行 /compact、/side 或编排命令,请取消修改后再试。', revisionAlreadyActive: '已有一条消息正在修改,请先发送或取消当前修改。', revisionCancelLabel: '取消', revisionBannerTitle: '正在修改已发送消息', revisionBannerDetail: '· 发送后创建新版本', operationFailedTitle: '操作失败', operationFailedFallback: '任务操作失败,请稍后重试。', attachmentFailedTitle: '添加附件失败', folderNotAttachable: '文件夹不能作为附件添加。', folderNotAttachableUseReference: '文件夹不能作为附件添加,请改用“引用文件夹”。', imageAttachmentNotDirectTitle: '图片已作为附件添加', imageAttachmentNotDirectDescription: '当前模型不会直接接收图片。图片已作为附件提供给模型。', tryAgain: '请稍后重试。', modelReboundTitle: '已切换到可用模型', modelReboundDescription: (modelId) => `原任务使用的连接已不可用${modelId ? ` · ${modelId}` : ''}`, messageReadFailedTitle: '读取任务失败', scrollMainToBottom: '滚动主对话到底部' }, model: { @@ -1091,7 +1093,7 @@ const COPY = { }, turnError: { streamTruncated: 'The response stream ended before completion.', requestRejected: 'The model service rejected the request. Check the model and request configuration.', retryExhausted: 'The automatic retry limit was reached.', retryDeclined: { side_effects: 'Tool activity already occurred in this attempt. Automatic retry was declined to avoid repeating operations. Check the tool results first.', observable_output: 'This attempt already produced output, so it was not retried automatically. Check the retained content first.', policy: 'This attempt was not retried under the current retry policy.', budget: 'The execution budget was exhausted, so this attempt was not retried automatically.' }, unknown: 'Something went wrong; the cause is unknown.', contextOverflow: 'Context exceeded the model window. Reduce attachments or start a new task.', timeout: 'The model request timed out.', auth: 'Model authentication failed. Reconnect or sign in again from Settings.', providerBilling: 'Model billing is restricted. Check the account balance or subscription.', providerCapacity: 'The model service is temporarily at capacity.', rateLimit: 'Requests were rate-limited.', network: 'The network connection failed. Check the network.', provider: 'The model service returned an error.', stepCap: 'The tool-step limit was reached, so the task may be incomplete. Send a message to continue.', tool: 'A tool call failed. Check the tool result above before deciding whether to retry.', permission: 'This turn ended while waiting for permission. Send a message and it will ask again.', restarted: 'The app restarted before the previous turn completed', sandboxBoundaryClosed: 'The app restarted, so the pending request to reach outside the workspace was closed as denied. Send a message to decide again.', executionState: { erroredTool: 'A tool errored during this turn. Read its result before deciding whether to send another message.', toolRan: 'Tools already ran during this turn and may have made real changes. Read their results before sending another message.' } }, }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getDesktopConversationCopy(locale: UiLocale): DesktopConversationCopy { return COPY[locale]; diff --git a/apps/desktop/src/renderer/application/contracts/transcript-copy.ts b/apps/desktop/src/renderer/application/contracts/transcript-copy.ts index d748cfa263a..36d8b7f8e93 100644 --- a/apps/desktop/src/renderer/application/contracts/transcript-copy.ts +++ b/apps/desktop/src/renderer/application/contracts/transcript-copy.ts @@ -17,14 +17,14 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { classifiedErrorFallback } from './operation-diagnostics.js'; import { getDesktopConversationCopy } from './conversation-copy.js'; -const COPY = { +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog<{ read: string; refresh: string; refreshTitle: string }>()({ 'zh-CN': { read: '任务内容暂时无法读取,请稍后重试。', refresh: '任务内容暂时无法刷新,请稍后重试。', refreshTitle: '刷新任务失败' }, 'zh-TW': { read: '任務內容暫時無法讀取,請稍後重試。', refresh: '任務內容暫時無法重新整理,請稍後重試。', refreshTitle: '重新整理任務失敗' }, en: { read: 'Task content is temporarily unavailable. Try again later.', refresh: 'Task content could not be refreshed. Try again later.', refreshTitle: 'Could not refresh task' }, -} satisfies UiCatalog<{ read: string; refresh: string; refreshTitle: string }>; +})); export function transcriptErrorMessage(error: unknown, locale: UiLocale, kind: 'read' | 'refresh' | 'restore'): string { if (kind === 'restore') { return classifiedErrorFallback(error, getDesktopConversationCopy(locale).actions.operationFailedFallback, locale, 'desktop'); diff --git a/apps/desktop/src/renderer/features/app-update/locales/app-update-copy.ts b/apps/desktop/src/renderer/features/app-update/locales/app-update-copy.ts index 695ab79621e..43661e9c925 100644 --- a/apps/desktop/src/renderer/features/app-update/locales/app-update-copy.ts +++ b/apps/desktop/src/renderer/features/app-update/locales/app-update-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export interface AppUpdateCopy { readonly installFailedTitle: string; @@ -31,7 +31,7 @@ export interface AppUpdateCopy { readonly retryFailedFallback: string; } -const COPY_BY_LOCALE = { +const COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { installFailedTitle: '无法安装更新', installFailedFallback: '请稍后重试。', @@ -65,7 +65,7 @@ const COPY_BY_LOCALE = { retryFailedTitle: 'Could not retry update download', retryFailedFallback: 'Try again later, or download the latest version manually.', }, -} satisfies UiCatalog; +})); export function getAppUpdateCopy(locale: UiLocale): AppUpdateCopy { return COPY_BY_LOCALE[locale]; 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 19df1d16644..c82acfca634 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 @@ -19,7 +19,7 @@ import { generalizedErrorMessageForLocale, redactSecrets } from '@maka/core/redaction'; import type { SubscriptionActionCode, SubscriptionActionFailureReason } from '@maka/core/oauth-subscription'; -import { type UiCatalog, type UiLocale, lookupCopy } from '@maka/core/ui-locale'; +import { type UiLocale, lookupCopy, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; type SubscriptionResultCode = | SubscriptionActionCode @@ -803,11 +803,11 @@ const enCopy: ProviderSettingsCopy = { }, }; -const PROVIDER_SETTINGS_COPY = { +const PROVIDER_SETTINGS_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': zhCopy, 'zh-TW': zhTwCopy, en: enCopy, -} satisfies UiCatalog; +})); export function getProviderSettingsCopy(locale: UiLocale): ProviderSettingsCopy { return PROVIDER_SETTINGS_COPY[locale]; diff --git a/apps/desktop/src/renderer/features/conversation/model/executor-submission.ts b/apps/desktop/src/renderer/features/conversation/model/executor-submission.ts index 9b2cd59ddfe..d12359de284 100644 --- a/apps/desktop/src/renderer/features/conversation/model/executor-submission.ts +++ b/apps/desktop/src/renderer/features/conversation/model/executor-submission.ts @@ -22,7 +22,7 @@ import type { ChatDefaultPermissionMode } from '@maka/core/settings'; import type { CollaborationMode } from '@maka/core/collaboration'; import type { OrchestrationMode } from '@maka/core/orchestration'; import type { ThinkingLevel } from '@maka/core/model-thinking'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { NewChatModel } from './shell-chat-model-selection.js'; import type { SessionSummary } from '@maka/core/session'; @@ -47,7 +47,7 @@ export interface ExecutorSubmission { Partial>; } -const SUBMISSION_COPY = { +const SUBMISSION_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog<{ attachments: string; unavailable: string; invalid: string }>()({ en: { attachments: 'Remove unsupported attachments or select Maka. Your draft is preserved.', unavailable: 'Check External Agents settings or start a new task.', @@ -63,7 +63,7 @@ const SUBMISSION_COPY = { unavailable: '請檢查外部 Agent 設定,或建立新任務。', invalid: '所選 Agent 設定已失效,請重新選擇。', }, -} satisfies UiCatalog<{ attachments: string; unavailable: string; invalid: string }>; +})); export function executorSubmissionError( input: ExecutorSubmission, diff --git a/apps/desktop/src/renderer/features/diagnostics/locales/diagnostics-copy.ts b/apps/desktop/src/renderer/features/diagnostics/locales/diagnostics-copy.ts index 01180e7fb80..e5be095a20f 100644 --- a/apps/desktop/src/renderer/features/diagnostics/locales/diagnostics-copy.ts +++ b/apps/desktop/src/renderer/features/diagnostics/locales/diagnostics-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export interface DiagnosticsCopy { readonly previousMainProcessInterruption: { @@ -27,7 +27,7 @@ export interface DiagnosticsCopy { }; } -const COPY_BY_LOCALE = { +const COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { previousMainProcessInterruption: { title: 'Maka 已恢复', @@ -49,7 +49,7 @@ const COPY_BY_LOCALE = { copyDiagnostics: 'Copy report', }, }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getDiagnosticsCopy(locale: UiLocale): DiagnosticsCopy { return COPY_BY_LOCALE[locale]; diff --git a/apps/desktop/src/renderer/features/runtime-host-management/locales/runtime-host-handoff-copy.ts b/apps/desktop/src/renderer/features/runtime-host-management/locales/runtime-host-handoff-copy.ts index 2ec1f706ef2..308090ebfc5 100644 --- a/apps/desktop/src/renderer/features/runtime-host-management/locales/runtime-host-handoff-copy.ts +++ b/apps/desktop/src/renderer/features/runtime-host-management/locales/runtime-host-handoff-copy.ts @@ -17,14 +17,14 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; interface RuntimeHostHandoffCopy { readonly copyDiagnostics: string; readonly diagnosticsCopied: string; } -const COPY_BY_LOCALE: UiCatalog = { +const COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { copyDiagnostics: '复制诊断信息', diagnosticsCopied: '诊断信息已复制', @@ -37,7 +37,7 @@ const COPY_BY_LOCALE: UiCatalog = { copyDiagnostics: 'Copy diagnostics', diagnosticsCopied: 'Diagnostics copied', }, -}; +})); export function getRuntimeHostHandoffCopy(locale: UiLocale): RuntimeHostHandoffCopy { return COPY_BY_LOCALE[locale]; diff --git a/apps/desktop/src/renderer/features/runtime-host-management/ui/runtime-host-resource-dialog.tsx b/apps/desktop/src/renderer/features/runtime-host-management/ui/runtime-host-resource-dialog.tsx index 79d48ab8b05..845169f12dc 100644 --- a/apps/desktop/src/renderer/features/runtime-host-management/ui/runtime-host-resource-dialog.tsx +++ b/apps/desktop/src/renderer/features/runtime-host-management/ui/runtime-host-resource-dialog.tsx @@ -17,6 +17,8 @@ * under the License. */ +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog, type UiMessageShape } from '@maka/core/ui-locale'; + import { useEffect, useState } from 'react'; import { Dialog, DialogHeader } from '@astryxdesign/core/Dialog'; import { Layout, LayoutContent, LayoutFooter } from '@astryxdesign/core/Layout'; @@ -262,7 +264,38 @@ function formatPercent(value: number): string { return `${value.toFixed(value >= 10 ? 0 : 1)}%`; } -const HOST_RESOURCE_COPY = { +const HOST_RESOURCE_COPY_EN = { + title: 'Host resources', + open: 'View Host resources', + done: 'Done', + unavailable: 'Resource information is available while the Host is connected', + cpu: 'CPU', + cpuUsage: 'CPU usage', + memory: 'Memory', + graphics: 'Graphics adapters', + storage: 'Storage', + noGraphicsAdapter: 'None detected', + graphicsUnknown: 'Unable to determine', + sharedMemory: 'Shared memory', + memoryUnknown: 'Memory unknown', + network: 'Network throughput', + measuring: 'Measuring…', + logicalProcessors: (count: number, available: number) => + count === available + ? `${count} logical processors` + : `${available} of ${count} logical processors available`, + networkRate: (interfaceName: string, received: string, transmitted: string) => + `${interfaceName} · ↓ ${received}/s · ↑ ${transmitted}/s`, + storageVolume: ( + mount: string, + available: string, + total: string, + filesystem: string | undefined, + ) => + `${mount} · ${available} available / ${total}${filesystem ? ` · ${filesystem}` : ''}`, + }; + +const HOST_RESOURCE_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog>()({ 'zh-CN': { title: '主机资源', open: '查看主机资源', @@ -321,37 +354,8 @@ const HOST_RESOURCE_COPY = { filesystem: string | undefined, ) => `${mount} · ${available} 可用 / ${total}${filesystem ? ` · ${filesystem}` : ''}`, }, - en: { - title: 'Host resources', - open: 'View Host resources', - done: 'Done', - unavailable: 'Resource information is available while the Host is connected', - cpu: 'CPU', - cpuUsage: 'CPU usage', - memory: 'Memory', - graphics: 'Graphics adapters', - storage: 'Storage', - noGraphicsAdapter: 'None detected', - graphicsUnknown: 'Unable to determine', - sharedMemory: 'Shared memory', - memoryUnknown: 'Memory unknown', - network: 'Network throughput', - measuring: 'Measuring…', - logicalProcessors: (count: number, available: number) => - count === available - ? `${count} logical processors` - : `${available} of ${count} logical processors available`, - networkRate: (interfaceName: string, received: string, transmitted: string) => - `${interfaceName} · ↓ ${received}/s · ↑ ${transmitted}/s`, - storageVolume: ( - mount: string, - available: string, - total: string, - filesystem: string | undefined, - ) => - `${mount} · ${available} available / ${total}${filesystem ? ` · ${filesystem}` : ''}`, - }, -} satisfies Record; + en: HOST_RESOURCE_COPY_EN, +})) satisfies Record; function hostResourceCopy(locale: UiLocale) { return HOST_RESOURCE_COPY[locale]; diff --git a/apps/desktop/src/renderer/features/storage-usage/locales/archive-retention-copy.ts b/apps/desktop/src/renderer/features/storage-usage/locales/archive-retention-copy.ts index f43ab19a756..59eaeb8f044 100644 --- a/apps/desktop/src/renderer/features/storage-usage/locales/archive-retention-copy.ts +++ b/apps/desktop/src/renderer/features/storage-usage/locales/archive-retention-copy.ts @@ -17,6 +17,8 @@ * under the License. */ +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; + import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; export interface ArchiveRetentionCopy { @@ -55,7 +57,7 @@ export interface ArchiveRetentionCopy { readonly cancel: string; } -const COPY_BY_LOCALE = { +const COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { noticeTitle: (hostName: string) => `「${hostName}」的自动清理`, viewArchivedTasks: '查看已归档任务', @@ -171,7 +173,7 @@ const COPY_BY_LOCALE = { confirmChange: 'Change', cancel: 'Cancel', }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getArchiveRetentionCopy(locale: UiLocale): ArchiveRetentionCopy { return COPY_BY_LOCALE[locale]; diff --git a/apps/desktop/src/renderer/features/task-entry/locales/session-workspace-recovery-copy.ts b/apps/desktop/src/renderer/features/task-entry/locales/session-workspace-recovery-copy.ts index a443be91ef9..d7b54436f9c 100644 --- a/apps/desktop/src/renderer/features/task-entry/locales/session-workspace-recovery-copy.ts +++ b/apps/desktop/src/renderer/features/task-entry/locales/session-workspace-recovery-copy.ts @@ -17,6 +17,8 @@ * under the License. */ +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; + import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; interface SessionWorkspaceRecoveryCopy { @@ -34,7 +36,7 @@ interface SessionWorkspaceRecoveryCopy { readonly refreshFailed: string; } -const COPY_BY_LOCALE = { +const COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { confirmDescription: '此目录属于一个已归档的项目。是否恢复原项目,并将当前任务的工作区切换到该项目目录?不会创建新任务或自动发送消息。', confirmLabel: '恢复并用于当前任务', @@ -77,7 +79,7 @@ const COPY_BY_LOCALE = { refreshFailedTitle: 'Could not refresh the project list', refreshFailed: 'A refresh failure does not undo a completed project restoration or workspace switch. Refresh the project list.', }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getSessionWorkspaceRecoveryCopy(locale: UiLocale): SessionWorkspaceRecoveryCopy { return COPY_BY_LOCALE[locale]; diff --git a/apps/desktop/src/renderer/features/workbar/ui/recent-turn-overlay.tsx b/apps/desktop/src/renderer/features/workbar/ui/recent-turn-overlay.tsx index a08aa1ed222..27a776b3717 100644 --- a/apps/desktop/src/renderer/features/workbar/ui/recent-turn-overlay.tsx +++ b/apps/desktop/src/renderer/features/workbar/ui/recent-turn-overlay.tsx @@ -17,6 +17,8 @@ * under the License. */ +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; + import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { ChevronDown, ChevronRight, FolderOpen, MessageSquare, Minus, Terminal } from '@maka/ui/icons'; import { Button, MarkdownBody, ProgressCard, progressStatusCopy, applyLiveTurnBufferEvent, type LiveTurnBuffer, type LiveTurnProjection, useUiLocale } from '@maka/ui'; @@ -37,7 +39,17 @@ const COPY: Record = { +}> = resolveUiMessageCatalog(defineShippedUiMessageCatalog<{ + processingFor: (elapsed: number) => string; + noReply: string; + runningTool: string; + finishedTool: string; + waiting: string; + readFailed: string; + minimize: string; + restore: string; + partial: string; +}>()({ 'zh-CN': { processingFor: (elapsed) => elapsed < 60 ? `已处理 ${elapsed} 秒` : `已处理 ${Math.floor(elapsed / 60)} 分钟 ${elapsed % 60} 秒`, @@ -59,7 +71,7 @@ const COPY: Record>()({ en: { selectedAnswers: 'Selected', clarifiedPreferences: 'Your answers', filterConversation: 'Filter conversation by Work', clearConversationFilter: 'Show all conversations', noWorkConversation: 'No conversations for this Work in this part of history.', work: 'Work', attachmentLimit: 'Attachment count or size exceeds the limit', attachmentUploadFailed: 'Attachment upload did not return a reference', reviewAttachments: 'Please review the attachments.', sendFailed: 'Could not send', retrySteering: 'Retry the original text and attachments with Cmd/Ctrl+Enter to resolve the previous submission first.', retryFollowup: 'Retry the original text and attachments with Enter to resolve the previous submission first.', @@ -91,4 +93,4 @@ export const workHubLiveCopy = { restore: '收回工作台', openWork: '開啟任務', openResult: '開啟結果', }, -} satisfies UiCatalog>; +})) satisfies UiCatalog>; diff --git a/apps/desktop/src/renderer/locales/agent-graph-copy.ts b/apps/desktop/src/renderer/locales/agent-graph-copy.ts index 3111132a63e..e3cf9ac9dd7 100644 --- a/apps/desktop/src/renderer/locales/agent-graph-copy.ts +++ b/apps/desktop/src/renderer/locales/agent-graph-copy.ts @@ -21,7 +21,7 @@ import type { AgentGraphClientOperator, AgentGraphClientSnapshot, } from '@maka/runtime/stream-graph-read-model'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export interface AgentGraphPanelCopy { title: string; @@ -51,9 +51,7 @@ export interface AgentGraphPanelCopy { operatorStatus(status: AgentGraphClientOperator['status']): string; wait(operator: AgentGraphClientOperator): string | undefined; } - -const AGENT_GRAPH_PANEL_COPY = { - 'zh-CN': { +const agentGraphPanelCopyZhCn: AgentGraphPanelCopy = { title: 'Agent Graph', loading: '正在读取 Graph 状态…', retry: '重试', @@ -101,8 +99,8 @@ const AGENT_GRAPH_PANEL_COPY = { cancelled: '取消', })[status], wait: waitReasonZh, - }, - 'zh-TW': { + }; +const agentGraphPanelCopyZhTw: AgentGraphPanelCopy = { title: 'Agent Graph', loading: '正在讀取 Graph 狀態…', retry: '重試', @@ -150,8 +148,8 @@ const AGENT_GRAPH_PANEL_COPY = { cancelled: '已取消', })[status], wait: waitReasonZhTw, - }, - en: { + }; +const agentGraphPanelCopyEn: AgentGraphPanelCopy = { title: 'Agent Graph', loading: 'Loading graph state…', retry: 'Retry', @@ -199,8 +197,9 @@ const AGENT_GRAPH_PANEL_COPY = { cancelled: 'Cancelled', })[status], wait: waitReasonEn, - }, -} satisfies UiCatalog; + }; + +const AGENT_GRAPH_PANEL_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': agentGraphPanelCopyZhCn, 'zh-TW': agentGraphPanelCopyZhTw, en: agentGraphPanelCopyEn })) satisfies UiCatalog; export function getAgentGraphPanelCopy(locale: UiLocale): AgentGraphPanelCopy { return AGENT_GRAPH_PANEL_COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/artifact-copy.ts b/apps/desktop/src/renderer/locales/artifact-copy.ts index 22d72fb79bf..071b023d277 100644 --- a/apps/desktop/src/renderer/locales/artifact-copy.ts +++ b/apps/desktop/src/renderer/locales/artifact-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; type ReasonCopy = { title: string; description: string }; @@ -94,9 +94,7 @@ export type ArtifactCopy = { loadingImage: string; }; }; - -const ARTIFACT_COPY = { - 'zh-CN': { +const artifactCopyZhCn: ArtifactCopy = { pane: { refreshFailed: '刷新生成文件失败', openFailed: '无法在 Finder 中打开生成文件', copyFailed: '复制失败', readTextFailed: '无法读取生成文件文本内容。', copied: '已复制生成文件文本', saved: '已另存生成文件', saveFailed: '另存失败', @@ -133,8 +131,8 @@ const ARTIFACT_COPY = { readFailed: { title: '加载预览失败', description: '无法读取文件内容(可能已被删除、移动或权限不足)。请通过「在 Finder 中打开」检查文件。' }, unsupported: '暂不支持的预览', name: '名称', unnamed: '(未命名)', type: '类型', size: '大小', openInFinder: '在 Finder 中打开', loadingImage: '加载图片预览…', }, - }, - 'zh-TW': { + }; +const artifactCopyZhTw: ArtifactCopy = { pane: { refreshFailed: '重新整理生成檔案失敗', openFailed: '無法在 Finder 中開啟生成檔案', copyFailed: '複製失敗', readTextFailed: '無法讀取生成檔案文本內容。', copied: '已複製生成檔案文本', saved: '已另存生成檔案', saveFailed: '另存失敗', @@ -171,8 +169,8 @@ const ARTIFACT_COPY = { readFailed: { title: '載入預覽失敗', description: '無法讀取檔案內容(可能已被刪除、移動或權限不足)。請透過「在 Finder 中開啟」檢查檔案。' }, unsupported: '暫不支援的預覽', name: '名稱', unnamed: '(未命名)', type: '型別', size: '大小', openInFinder: '在 Finder 中開啟', loadingImage: '載入圖片預覽…', }, - }, - en: { + }; +const artifactCopyEn: ArtifactCopy = { pane: { refreshFailed: 'Failed to refresh generated files', openFailed: 'Could not show generated file in Finder', copyFailed: 'Copy failed', readTextFailed: 'Could not read the generated file as text.', copied: 'Generated file text copied', saved: 'Generated file saved as', saveFailed: 'Save as failed', @@ -209,8 +207,9 @@ const ARTIFACT_COPY = { readFailed: { title: 'Failed to load preview', description: 'The file could not be read. It may have been deleted, moved, or blocked by permissions. Use “Show in Finder” to inspect it.' }, unsupported: 'Unsupported preview', name: 'Name', unnamed: '(unnamed)', type: 'Type', size: 'Size', openInFinder: 'Show in Finder', loadingImage: 'Loading image preview…', }, - }, -} satisfies UiCatalog; + }; + +const ARTIFACT_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': artifactCopyZhCn, 'zh-TW': artifactCopyZhTw, en: artifactCopyEn })) satisfies UiCatalog; export function getArtifactCopy(locale: UiLocale): ArtifactCopy { return ARTIFACT_COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/browser-copy.ts b/apps/desktop/src/renderer/locales/browser-copy.ts index c2ac816479a..aa24eaa028b 100644 --- a/apps/desktop/src/renderer/locales/browser-copy.ts +++ b/apps/desktop/src/renderer/locales/browser-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export type BrowserCopy = { unsupportedScheme: string; @@ -59,9 +59,7 @@ export type BrowserCopy = { title: string; description: string; }; - -const BROWSER_COPY = { - 'zh-CN': { +const browserCopyZhCn: BrowserCopy = { unsupportedScheme: '嵌入式浏览器只支持打开 HTTP/HTTPS 网页地址。', invalidUrl: '这个地址无法识别,请检查网址后重试。', openFailed: '无法打开地址', @@ -100,8 +98,8 @@ const BROWSER_COPY = { restorePreview: '还原分栏', title: '嵌入式浏览器', description: '输入网址打开页面,或让助手帮你导航并操作。', - }, - 'zh-TW': { + }; +const browserCopyZhTw: BrowserCopy = { unsupportedScheme: '嵌入式瀏覽器只支援開啟 HTTP/HTTPS 網頁地址。', invalidUrl: '這個地址無法識別,請檢查網址後重試。', openFailed: '無法開啟地址', @@ -140,8 +138,8 @@ const BROWSER_COPY = { restorePreview: '還原分欄', title: '嵌入式瀏覽器', description: '輸入網址開啟頁面,或讓助手幫你導航並操作。', - }, - en: { + }; +const browserCopyEn: BrowserCopy = { unsupportedScheme: 'The embedded browser only supports HTTP and HTTPS addresses.', invalidUrl: 'This address is not valid. Check it and try again.', openFailed: 'Could not open address', @@ -180,8 +178,9 @@ const BROWSER_COPY = { restorePreview: 'Restore split view', title: 'Embedded browser', description: 'Enter an address, or ask the assistant to navigate and interact with a page.', - }, -} satisfies UiCatalog; + }; + +const BROWSER_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': browserCopyZhCn, 'zh-TW': browserCopyZhTw, en: browserCopyEn })) satisfies UiCatalog; export function getBrowserCopy(locale: UiLocale): BrowserCopy { return BROWSER_COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/capability-reason-copy.ts b/apps/desktop/src/renderer/locales/capability-reason-copy.ts index 1178ebb03d4..6fd150d3541 100644 --- a/apps/desktop/src/renderer/locales/capability-reason-copy.ts +++ b/apps/desktop/src/renderer/locales/capability-reason-copy.ts @@ -18,11 +18,11 @@ */ import type { CapabilityReasonCode } from '@maka/core/capabilities'; -import { type UiCatalog, type UiLocale, lookupCopy } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, lookupCopy, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export type CapabilityReasonCopy = Record; -const CAPABILITY_REASON_COPY = { +const CAPABILITY_REASON_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { disabled: '该能力当前已关闭。', platform_credentials_missing: '未配置平台凭据', @@ -98,7 +98,7 @@ const CAPABILITY_REASON_COPY = { notifications_unsupported: 'Electron notifications are unavailable', permission_probe_failed: 'Permission probe failed', }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getCapabilityReasonCopy(locale: UiLocale): CapabilityReasonCopy { return CAPABILITY_REASON_COPY[locale]; 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 2dda9d7ea6a..194eac4fb7f 100644 --- a/apps/desktop/src/renderer/locales/external-session-import-copy.ts +++ b/apps/desktop/src/renderer/locales/external-session-import-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { ExternalSessionLimit } from '@maka/core/external-session'; /** @@ -118,9 +118,7 @@ type ExternalSessionImportCopy = { bundleUnreadable: string; bundleFailed: string; }; - -const COPY = { - 'zh-CN': { +const copyZhCn: ExternalSessionImportCopy = { modeLabel: '导入或导出', modeImport: '导入任务', modeExport: '导出任务', @@ -202,8 +200,8 @@ const COPY = { batchNothingImported: '没有对话被导入。', importOutcomeUnknownDescription: (names) => `以下对话的导入结果无法确认:${names.map((name) => `「${name}」`).join('、')}。可以先在任务列表中查找,也可以再次导入;再次导入会创建独立任务。`, - }, - 'zh-TW': { + }; +const copyZhTw: ExternalSessionImportCopy = { modeLabel: '匯入或匯出', modeImport: '匯入任務', modeExport: '匯出任務', @@ -285,8 +283,8 @@ const COPY = { batchNothingImported: '沒有匯入任何對話。', importOutcomeUnknownDescription: (names) => `以下對話的匯入結果無法確認:${names.map((name) => `「${name}」`).join('、')}。可以先在任務列表中查詢,也可以再次匯入;再次匯入會建立獨立任務。`, - }, - en: { + }; +const copyEn: ExternalSessionImportCopy = { modeLabel: 'Import or export', modeImport: 'Import tasks', modeExport: 'Export tasks', @@ -372,8 +370,9 @@ const COPY = { batchNothingImported: 'No conversation was imported.', importOutcomeUnknownDescription: (names) => `Maka could not confirm the outcome of these imports: ${names.map((name) => `“${name}”`).join(', ')}. Check the task list or import again; importing again creates an independent task.`, - }, -} satisfies UiCatalog; + }; + +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': copyZhCn, 'zh-TW': copyZhTw, en: copyEn })) satisfies UiCatalog; export function getExternalSessionImportCopy(locale: UiLocale): ExternalSessionImportCopy { return COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/mcp-copy.ts b/apps/desktop/src/renderer/locales/mcp-copy.ts index d8e525777f1..11adda42879 100644 --- a/apps/desktop/src/renderer/locales/mcp-copy.ts +++ b/apps/desktop/src/renderer/locales/mcp-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export type McpCopy = { errors: { @@ -62,9 +62,7 @@ export type McpCopy = { protocolHelp: string; sseProtocolHelp: string; advanced: string; stdioProtocolHelp: string; }; }; - -const MCP_COPY = { - 'zh-CN': { +const mcpCopyZhCn: McpCopy = { errors: { load: '载入 MCP 失败', save: '保存 MCP 失败', invalidConfigFile: (path) => `${path} 中的 JSON 无效,文件未被修改。请关闭应用,备份并修复此文件后重试。`, @@ -125,8 +123,8 @@ const MCP_COPY = { protocolHelp: '自动协商会按服务支持的版本连接;遇到兼容性问题时再固定版本。', sseProtocolHelp: '旧版 SSE 使用传统协议。', advanced: '高级设置', stdioProtocolHelp: '自动协商或仅使用新版协议时,会额外启动一次服务器进行探测。', }, - }, - 'zh-TW': { + }; +const mcpCopyZhTw: McpCopy = { errors: { load: '載入 MCP 失敗', save: '儲存 MCP 失敗', invalidConfigFile: (path) => `${path} 中的 JSON 無效,檔案未被修改。請關閉應用程式,備份並修復此檔案後重試。`, @@ -187,8 +185,8 @@ const MCP_COPY = { protocolHelp: '自動協商會依服務支援的版本連線;遇到相容性問題時再固定版本。', sseProtocolHelp: '舊版 SSE 使用傳統協議。', advanced: '進階設定', stdioProtocolHelp: '自動協商或僅使用新版協議時,會額外啟動一次伺服器進行探測。', }, - }, - en: { + }; +const mcpCopyEn: McpCopy = { errors: { load: 'Failed to load MCP', save: 'Failed to save MCP', invalidConfigFile: (path) => `Invalid JSON in ${path}. The file is unchanged. Close the app, back up and repair this file before retrying.`, @@ -249,8 +247,9 @@ const MCP_COPY = { protocolHelp: 'Auto-negotiation uses a version the service supports; pin a version only for compatibility.', sseProtocolHelp: 'Legacy SSE uses the legacy protocol.', advanced: 'Advanced settings', stdioProtocolHelp: 'Auto-negotiation or modern-only mode starts the server one extra time to check protocol support.', }, - }, -} satisfies UiCatalog; + }; + +const MCP_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': mcpCopyZhCn, 'zh-TW': mcpCopyZhTw, en: mcpCopyEn })) satisfies UiCatalog; export function getMcpCopy(locale: UiLocale): McpCopy { return MCP_COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/onboarding-copy.ts b/apps/desktop/src/renderer/locales/onboarding-copy.ts index fb4fd829588..19ba5278baa 100644 --- a/apps/desktop/src/renderer/locales/onboarding-copy.ts +++ b/apps/desktop/src/renderer/locales/onboarding-copy.ts @@ -18,7 +18,7 @@ */ import type { OnboardingState } from '@maka/core/onboarding'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { OnboardingHeroCopy } from '../onboarding-hero-copy.js'; // Blocked states are keyed by reason, not just by kind: a new blocked reason @@ -50,7 +50,7 @@ export interface OnboardingCatalog { snapshotErrorFallback: string; } -const ONBOARDING_COPY_BY_LOCALE: UiCatalog = { +const ONBOARDING_COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { hero: { needs_connection: { @@ -198,7 +198,7 @@ const ONBOARDING_COPY_BY_LOCALE: UiCatalog = { skip: 'Skip onboarding', snapshotErrorFallback: 'First-run status is temporarily unavailable. Try again later.', }, -}; +})) satisfies UiCatalog; export function getOnboardingCopy(locale: UiLocale): OnboardingCatalog { return ONBOARDING_COPY_BY_LOCALE[locale]; diff --git a/apps/desktop/src/renderer/locales/peer-mesh-copy.ts b/apps/desktop/src/renderer/locales/peer-mesh-copy.ts index c9fa53ee34f..75a035ef40c 100644 --- a/apps/desktop/src/renderer/locales/peer-mesh-copy.ts +++ b/apps/desktop/src/renderer/locales/peer-mesh-copy.ts @@ -18,7 +18,7 @@ */ import type { PeerMeshMemberProjection, PeerMeshQueryResult } from '@maka/runtime-host/protocol'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export interface PeerMeshCopy { readonly title: string; @@ -146,7 +146,7 @@ export interface PeerMeshCopy { readonly localHostMissingHint: string; } -const PEER_MESH_COPY = { +const PEER_MESH_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { title: 'Peer Mesh', experimental: '实验性', @@ -626,7 +626,7 @@ const PEER_MESH_COPY = { localHostMissingHint: 'Add it so other members can reach tasks shared from this device through the Mesh.', }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getPeerMeshCopy(locale: UiLocale): PeerMeshCopy { return PEER_MESH_COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/permission-center-copy.ts b/apps/desktop/src/renderer/locales/permission-center-copy.ts index e2398a71eb2..8257c253ea0 100644 --- a/apps/desktop/src/renderer/locales/permission-center-copy.ts +++ b/apps/desktop/src/renderer/locales/permission-center-copy.ts @@ -27,7 +27,7 @@ import type { RuntimeProbeState, } from '@maka/core/capabilities'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; type Tone = StatusSemantic; type StatusCopy = { label: string; tone: Tone }; @@ -99,9 +99,7 @@ export type PermissionCenterCopy = { cuBackendStatus(missingPermissionLabels: readonly string[], health: RuntimeProbeState): string; reasonFallback: string; }; - -const PERMISSION_CENTER_COPY = { - 'zh-CN': { +const permissionCenterCopyZhCn: PermissionCenterCopy = { readiness: { not_configured: { label: '等待配置', detail: '需要先打开开关或补齐配置才能启用。', tone: 'neutral' }, denied: { label: '系统拒绝', detail: '所需系统权限被拒绝或当前平台不支持。', tone: 'error' }, @@ -157,8 +155,8 @@ const PERMISSION_CENTER_COPY = { not_run: 'service 将在首次调用时启动;按目标与动作类别授权后可操作本机应用。', } satisfies Record)[health], reasonFallback: '状态详情请查看运行日志。', - }, - 'zh-TW': { + }; +const permissionCenterCopyZhTw: PermissionCenterCopy = { readiness: { not_configured: { label: '等待設定', detail: '需要先開啟開關或補齊設定才能啟用。', tone: 'neutral' }, denied: { label: '系統拒絕', detail: '所需系統權限被拒絕或目前平臺不支援。', tone: 'error' }, @@ -214,8 +212,8 @@ const PERMISSION_CENTER_COPY = { not_run: 'service 將在首次呼叫時啟動;依目標與動作類別授權後可操作本機應用程式。', } satisfies Record)[health], reasonFallback: '狀態詳情請查看執行日誌。', - }, - en: { + }; +const permissionCenterCopyEn: PermissionCenterCopy = { readiness: { not_configured: { label: 'Needs setup', detail: 'Enable the feature or complete its configuration first.', tone: 'neutral' }, denied: { label: 'Denied by system', detail: 'A required system permission was denied or is unsupported on this platform.', tone: 'error' }, @@ -271,8 +269,9 @@ const PERMISSION_CENTER_COPY = { not_run: 'The service starts on first use; grant by target and action category to operate local apps.', } satisfies Record)[health], reasonFallback: 'See the runtime logs for details.', - }, -} satisfies UiCatalog; + }; + +const PERMISSION_CENTER_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': permissionCenterCopyZhCn, 'zh-TW': permissionCenterCopyZhTw, en: permissionCenterCopyEn })) satisfies UiCatalog; export function getPermissionCenterCopy(locale: UiLocale): PermissionCenterCopy { return PERMISSION_CENTER_COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/plan-mode-copy.ts b/apps/desktop/src/renderer/locales/plan-mode-copy.ts index 71a035ea11f..c2d860182fc 100644 --- a/apps/desktop/src/renderer/locales/plan-mode-copy.ts +++ b/apps/desktop/src/renderer/locales/plan-mode-copy.ts @@ -18,7 +18,7 @@ */ import type { PlanExecutionStep, PlanProposal } from '@maka/core/plan'; -import { lookupCopy, type UiCatalog, type UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, lookupCopy, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { PlanControlErrorCode } from '@maka/runtime-host/protocol'; import type { PlanControlIpcResult } from '../../shared/plan-mode-ipc.js'; @@ -56,9 +56,7 @@ export interface PlanModeCopy { readonly stepStatuses: Record; }; } - -const COPY = { - 'zh-CN': { +const copyZhCn: PlanModeCopy = { operationFailed: '计划操作失败,请稍后重试。', controlFailure: { not_found: '计划方案已不存在,请刷新后重试', @@ -90,8 +88,8 @@ const COPY = { resume: '恢复执行', abandon: '放弃计划', stepStatuses: { pending: '未开始', in_progress: '正在执行', completed: '已完成', skipped: '已跳过' }, }, - }, - 'zh-TW': { + }; +const copyZhTw: PlanModeCopy = { operationFailed: '計劃操作失敗,請稍後重試。', controlFailure: { not_found: '計劃方案已不存在,請重新整理後重試', @@ -123,8 +121,8 @@ const COPY = { resume: '恢復執行', abandon: '放棄計劃', stepStatuses: { pending: '未開始', in_progress: '正在執行', completed: '已完成', skipped: '已跳過' }, }, - }, - en: { + }; +const copyEn: PlanModeCopy = { operationFailed: 'The plan action failed. Try again later.', controlFailure: { not_found: 'This plan proposal no longer exists. Refresh and try again.', @@ -157,8 +155,9 @@ const COPY = { resume: 'Resume', abandon: 'Abandon plan', stepStatuses: { pending: 'Not started', in_progress: 'In progress', completed: 'Completed', skipped: 'Skipped' }, }, - }, -} satisfies UiCatalog; + }; + +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': copyZhCn, 'zh-TW': copyZhTw, en: copyEn })) satisfies UiCatalog; export function getPlanModeCopy(locale: UiLocale): PlanModeCopy { return COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/session-collaboration-copy.ts b/apps/desktop/src/renderer/locales/session-collaboration-copy.ts index 4488a852e2a..56c248610c5 100644 --- a/apps/desktop/src/renderer/locales/session-collaboration-copy.ts +++ b/apps/desktop/src/renderer/locales/session-collaboration-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; const ZH_CN = { shareAction: '分享此任务', @@ -352,7 +352,7 @@ const EN = { pendingTurnRequestCount: (count: number) => `${count} pending Turn ${count === 1 ? 'request' : 'requests'}`, } satisfies SessionCollaborationCopy; -const COPY = { 'zh-CN': ZH_CN, 'zh-TW': ZH_TW, en: EN } satisfies UiCatalog; +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': ZH_CN, 'zh-TW': ZH_TW, en: EN })) satisfies UiCatalog; export function getSessionCollaborationCopy(locale: UiLocale) { return COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/session-local-copy.ts b/apps/desktop/src/renderer/locales/session-local-copy.ts index f5ff60c4487..54a591d353d 100644 --- a/apps/desktop/src/renderer/locales/session-local-copy.ts +++ b/apps/desktop/src/renderer/locales/session-local-copy.ts @@ -17,6 +17,8 @@ * under the License. */ +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; + import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; const en = { @@ -42,7 +44,7 @@ const en = { draftReady: 'Ready to edit. The failed message is kept until you delete it.', }; type SessionLocalCopy = { [K in keyof typeof en]: string }; -const catalog = { +const catalog = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ en, 'zh-CN': { saved: '等待发送', @@ -88,6 +90,6 @@ const catalog = { draftBlocked: '請先完成或清空輸入框中的草稿、附件和引用,再編輯這則訊息。', draftReady: '已恢復到輸入框,可編輯後傳送。失敗訊息會保留,直到你刪除它。', }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getSessionLocalCopy(locale: UiLocale): SessionLocalCopy { return catalog[locale]; } diff --git a/apps/desktop/src/renderer/locales/settings-bot-copy.ts b/apps/desktop/src/renderer/locales/settings-bot-copy.ts index a36d58c3d0b..c6ec1595735 100644 --- a/apps/desktop/src/renderer/locales/settings-bot-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-bot-copy.ts @@ -23,7 +23,7 @@ import type { BotStatusCode, BotTestErrorCode, WechatBridgeQrHintCode } from '@m import type { BotOnboardingErrorCode, BotOnboardingProvider, BotOnboardingSnapshot, BotOnboardingRetryFailureCategory } from '@maka/core/bot-onboarding'; import type { GeneralizedErrorClass } from '@maka/core/redaction'; -import { type UiCatalog, type UiLocale, lookupCopy } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, lookupCopy, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; type WidenCopy = T extends string ? string @@ -33,7 +33,7 @@ type WidenCopy = T extends string // Bot transport failures name the platform, not the model service that the // shared generalized copy describes. -const BOT_TRANSPORT_ERRORS = { +const BOT_TRANSPORT_ERRORS = resolveUiMessageCatalog(defineShippedUiMessageCatalog>()({ 'zh-CN': { timeout: '请求超时,请稍后重试', rate_limited: '请求过于频繁,请稍后重试', @@ -55,7 +55,7 @@ const BOT_TRANSPORT_ERRORS = { provider_error: 'The platform is temporarily unavailable. Try again later', network_error: 'Network error. Check the network and proxy settings', }, -} satisfies UiCatalog>; +})) satisfies UiCatalog>; const zhCopy = { providers: { @@ -425,11 +425,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.0.0.1:18400. Official Account App ID and App Secret are used only for Official Account messaging; personal WeChat QR sign-in uses the local bridge.', readQrFailed: 'Could not read a QR code from the local wechat-bridge. Make sure the bridge is running.', title: 'WeChat QR sign-in', subtitle: 'Scan the QR code with WeChat and confirm signing in to the local wechat-bridge on your phone.', close: 'Close WeChat QR sign-in', generating: 'Generating QR code…', loggedIn: 'WeChat is signed in. Return to test the connection or restart the listener.', expired: 'QR code expired', expiredHint: 'Refresh the QR code and scan again to continue signing in.', refreshing: 'Refreshing…', refresh: 'Refresh QR code', qrAlt: 'WeChat sign-in QR code', waiting: 'Waiting for confirmation… Sign-in status refreshes every 3 seconds.', retrying: 'Retrying…', retry: 'Retry', bridgeGenerating: 'The bridge is generating a QR code', bridgeGeneratingHint: 'The QR code appears automatically once ready; you can also fetch it again.', fetching: 'Fetching…', fetchAgain: 'Fetch again' }, }; -const BOT_SETTINGS_COPY = { +const BOT_SETTINGS_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': zhCopy, 'zh-TW': zhTwCopy, en: enCopy, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getBotSettingsCopy(locale: UiLocale): BotSettingsCopy { return BOT_SETTINGS_COPY[locale]; @@ -524,7 +524,7 @@ export function botOnboardingStatusCopy( starting: boolean, error: string | null, copy: BotSettingsCopy['onboarding']['providers'][BotOnboardingProvider], - locale: 'zh-CN' | 'zh-TW' | 'en', + locale: UiLocale, ): string { const shared = getBotSettingsCopy(locale).onboarding; if (starting) return shared.generating; 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 489ba804802..2451ed7809a 100644 --- a/apps/desktop/src/renderer/locales/settings-daily-review-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-daily-review-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export type DailyReviewSettingsCopy = { defaultModel: string; @@ -39,7 +39,7 @@ export type DailyReviewSettingsCopy = { modelHelp: string; }; -const SETTINGS_DAILY_REVIEW_COPY = { +const SETTINGS_DAILY_REVIEW_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { defaultModel: '跟随任务默认', saveFailed: '保存每日回顾设置失败', @@ -97,7 +97,7 @@ const SETTINGS_DAILY_REVIEW_COPY = { model: 'Analysis model', modelHelp: 'Follows the current task default when unspecified.', }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getDailyReviewSettingsCopy(locale: UiLocale): DailyReviewSettingsCopy { return SETTINGS_DAILY_REVIEW_COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/settings-data-copy.ts b/apps/desktop/src/renderer/locales/settings-data-copy.ts index 17c53c62b9a..7bcedcc90ae 100644 --- a/apps/desktop/src/renderer/locales/settings-data-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-data-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { ConfigCategory } from '@maka/storage/config-transfer'; export type DataSettingsCopy = { @@ -40,7 +40,7 @@ export type DataSettingsCopy = { exportConfig: string; importConfig: string; }; -const SETTINGS_DATA_COPY = { +const SETTINGS_DATA_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { categories: { connections: { label: '模型连接', detail: '供应商连接与默认模型(不含密钥)' }, @@ -125,7 +125,7 @@ const SETTINGS_DATA_COPY = { sensitiveWarning: '⚠️ Secrets will be written to the export file as plain text. Anyone with this file can use them. Store it securely and do not share it.', conflictAria: 'How to handle connections with the same name during import', skip: 'Skip', overwrite: 'Overwrite', exportConfig: 'Export configuration…', importConfig: 'Import configuration…', }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getDataSettingsCopy(locale: UiLocale): DataSettingsCopy { return SETTINGS_DATA_COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/settings-external-agents-copy.ts b/apps/desktop/src/renderer/locales/settings-external-agents-copy.ts index 2ddf6418344..7045afd77e8 100644 --- a/apps/desktop/src/renderer/locales/settings-external-agents-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-external-agents-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiLocale, UiCatalog } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { ExternalAgentSetupFailure } from '@maka/runtime-host/protocol'; const en = { programTitle: "Program", @@ -201,7 +201,7 @@ const zhTW: typeof en = { cleanup_failed: '無法釋放暫時程序,請重新啟動 Host 後重試。', }, }; -const catalog = { en, 'zh-CN': zh, 'zh-TW': zhTW } satisfies UiCatalog; +const catalog = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ en, 'zh-CN': zh, 'zh-TW': zhTW })) satisfies UiCatalog; export function getExternalAgentsCopy(locale: UiLocale) { return catalog[locale]; } diff --git a/apps/desktop/src/renderer/locales/settings-health-copy.ts b/apps/desktop/src/renderer/locales/settings-health-copy.ts index d4a089e7431..ef019283cf8 100644 --- a/apps/desktop/src/renderer/locales/settings-health-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-health-copy.ts @@ -28,7 +28,7 @@ import type { HealthSignalStatus, } from '@maka/core/health'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; /** * Health signals carry their own severity ladder — error > warning > info > ok @@ -105,9 +105,7 @@ const layersEn: HealthCenterCopy['layers'] = { runtime_probe: { label: 'Runtime probe', description: 'The latest real send, stream, or event-receipt observation.' }, storage: { label: 'Storage', description: 'Health of workspace files, JSONL, SQLite, and other local storage.' }, }; - -const SETTINGS_HEALTH_COPY = { - 'zh-CN': { +const settingsHealthCopyZhCn: HealthCenterCopy = { loading: '正在加载健康快照', readFailed: '无法读取健康快照', noData: '健康服务未返回数据。', readAgain: '重新读取', title: '健康中心', subtitle: '各项能力当前的运行状况检查。', checksTitle: '检查项', badge: '只读快照', lastRead: '最近一次读取:', refresh: '刷新', summaryAria: '按状态筛选健康信号', summaryFilterAria: (label, count, selected) => selected ? `${label} ${count} 项,当前筛选;再次按下显示全部` : `仅显示${label}健康信号,共 ${count} 项`, @@ -125,8 +123,8 @@ const SETTINGS_HEALTH_COPY = { signalLabel: (signal) => (signal.id.endsWith(':runtime') ? `${signal.label} 运行态` : signal.label), signalMessage: (signal) => signalMessagesZh[signal.message], signalDetail: (signal) => signalDetailZh(signal.detail), - }, - 'zh-TW': { + }; +const settingsHealthCopyZhTw: HealthCenterCopy = { loading: '正在載入健康快照', readFailed: '無法讀取健康快照', noData: '健康服務未返回資料。', readAgain: '重新讀取', title: '健康中心', subtitle: '各項能力目前的執行狀況檢查。', checksTitle: '檢查項目', badge: '只讀快照', lastRead: '最近一次讀取:', refresh: '重新整理', summaryAria: '按狀態篩選健康訊號', summaryFilterAria: (label, count, selected) => selected ? `${label} ${count} 項,目前篩選;再次按下顯示全部` : `僅顯示${label}健康訊號,共 ${count} 項`, @@ -144,8 +142,8 @@ const SETTINGS_HEALTH_COPY = { signalLabel: (signal) => (signal.id.endsWith(':runtime') ? `${signal.label} 執行狀態` : signal.label), signalMessage: (signal) => signalMessagesZhTw[signal.message], signalDetail: (signal) => signalDetailZhTw(signal.detail), - }, - en: { + }; +const settingsHealthCopyEn: HealthCenterCopy = { 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.', checksTitle: 'Checks', badge: 'Read-only snapshot', lastRead: 'Last read: ', refresh: 'Refresh', summaryAria: 'Filter health signals by status', summaryFilterAria: (label, count, selected) => selected ? `${label}, ${count}; filter selected. Press again to show all signals` : `Show only ${label.toLowerCase()} health signals, ${count}`, @@ -163,8 +161,9 @@ const SETTINGS_HEALTH_COPY = { signalLabel: (signal) => (signal.id.endsWith(':runtime') ? `${signal.label} runtime` : signal.label), signalMessage: (signal) => signalMessagesEn[signal.message], signalDetail: (signal) => signalDetailEn(signal.detail), - }, -} satisfies UiCatalog; + }; + +const SETTINGS_HEALTH_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': settingsHealthCopyZhCn, 'zh-TW': settingsHealthCopyZhTw, en: settingsHealthCopyEn })) satisfies UiCatalog; export function getHealthCenterCopy(locale: UiLocale): HealthCenterCopy { return SETTINGS_HEALTH_COPY[locale]; @@ -229,30 +228,29 @@ const signalMessagesEn: Record = { capability_denied: 'The capability is blocked by a required system permission.', capability_degraded: 'The capability runtime probe is degraded.', }; - -const connectionTestErrorMessages = { - 'zh-CN': { +const connectiontesterrormessagesZhCn: Record = { auth: '鉴权失败', timeout: '请求超时', provider_unavailable: '模型服务返回错误', network: '网络错误', unknown: '连接测试失败', - }, - 'zh-TW': { + }; +const connectiontesterrormessagesZhTw: Record = { auth: '驗證失敗', timeout: '請求逾時', provider_unavailable: '模型服務傳回錯誤', network: '網路錯誤', unknown: '連線測試失敗', - }, - en: { + }; +const connectiontesterrormessagesEn: Record = { auth: 'Authentication failed', timeout: 'Request timed out', provider_unavailable: 'Model service returned an error', network: 'Network error', unknown: 'Connection test failed', - }, -} satisfies UiCatalog>; + }; + +const connectionTestErrorMessages = resolveUiMessageCatalog(defineShippedUiMessageCatalog>()({ 'zh-CN': connectiontesterrormessagesZhCn, 'zh-TW': connectiontesterrormessagesZhTw, en: connectiontesterrormessagesEn })) satisfies UiCatalog>; function signalDetailZh(detail: HealthSignalDetail | undefined): string | undefined { if (!detail) return undefined; @@ -337,12 +335,11 @@ function signalDetailEn(detail: HealthSignalDetail | undefined): string | undefi return unhandledDetail(detail); } } +const unknownruntimeerrorclassZhCn: string = '未知错误'; +const unknownruntimeerrorclassZhTw: string = '未知錯誤'; +const unknownruntimeerrorclassEn: string = 'Unknown error'; -const unknownRuntimeErrorClass = { - 'zh-CN': '未知错误', - 'zh-TW': '未知錯誤', - en: 'Unknown error', -} satisfies UiCatalog; +const unknownRuntimeErrorClass = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': unknownruntimeerrorclassZhCn, 'zh-TW': unknownruntimeerrorclassZhTw, en: unknownruntimeerrorclassEn })) satisfies UiCatalog; // Runtime probes carry the turn's failure class (rate_limit, context_overflow, // …), a wider vocabulary than connection tests; unmapped classes stay visible. diff --git a/apps/desktop/src/renderer/locales/settings-jev-copy.ts b/apps/desktop/src/renderer/locales/settings-jev-copy.ts index ab24bd9ac9e..a38e63d4e50 100644 --- a/apps/desktop/src/renderer/locales/settings-jev-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-jev-copy.ts @@ -17,9 +17,9 @@ * under the License. */ -import type { UiCatalog } from '@maka/core/ui-locale'; +import { type UiCatalog, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; -export const JEV_COPY = { +export const JEV_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog>()({ 'zh-CN': { advanced: '高级设置', title: 'Jev 辅助决策', help: '使用 TypeSafe Jev 辅助 WorkHub 的意图分类和工作路由。会发送当前消息、近期对话与候选工作摘要;对话、执行和标题仍使用原模型。', @@ -44,4 +44,4 @@ export const JEV_COPY = { behavior: 'Uncertain decisions ask for clarification; service failures use the existing router.', failure: 'Could not save Jev settings. Please try again.', }, -} satisfies UiCatalog>; +})) satisfies UiCatalog>; diff --git a/apps/desktop/src/renderer/locales/settings-memory-copy.ts b/apps/desktop/src/renderer/locales/settings-memory-copy.ts index aeaa911bb69..0e802cae66a 100644 --- a/apps/desktop/src/renderer/locales/settings-memory-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-memory-copy.ts @@ -20,7 +20,7 @@ import type { LocalMemoryOperationCode, LocalMemoryState } from '@maka/core/local-memory'; import type { MemoryMutationRejectionReason } from '@maka/runtime-host/protocol'; -import { type UiCatalog, type UiLocale, lookupCopy } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, lookupCopy, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; type MemoryTextKey = | 'localFile' | 'localFileHelp' | 'enableLocalFile' | 'agentReadable' | 'agentReadableHelp' | 'enableAgentRead' @@ -93,9 +93,7 @@ const zhTwText = { 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 create MEMORY.md', waitingBackup: 'Waiting to create a previous-version backup', dirty: 'Unsaved changes', savedDraft: 'Draft saved', backupCandidates: 'Backup candidates', backupCandidatesAria: 'Local memory backup candidates', opening: 'Opening…', open: 'Open', restoring: 'Restoring…', restore: 'Restore', copying: 'Copying…', copyReference: 'Copy reference', backupHelp: 'Previous-version actions use the latest candidate. Only metadata is shown here, never backup contents.', savedAt: 'Saved ', previewPaused: 'Draft entry preview paused', filterAria: 'Filter local memory', filterPlaceholder: 'Filter title, content, ID, or tags', clear: 'Clear', filterEmpty: 'No matching memory entries', filterEmptyHelp: 'Filtering does not modify MEMORY.md. Clear the filter to show all entries.', activeMemories: 'Active memories', archivedMemories: 'Archived memories', waitingEntry: 'Ready to add a memory entry', waitingEntryHelp: 'Confirm a memory in chat, or use "Add memory" above.', manualAddAria: 'Add local memory manually', manualAdd: 'Add memory manually', manualAddHelp: 'Written to the local MEMORY.md immediately.', title: 'Memory title', titlePlaceholder: 'Title', tags: 'Memory tags', tagsPlaceholder: 'Tags (comma-separated, optional)', content: 'Memory content', contentPlaceholder: 'Content', addDraft: 'Add memory', sensitiveDraft: 'Draft may contain sensitive fields', sensitiveDraftHelp: 'Suspected tokens, API keys, and passwords are redacted before MEMORY.md is written.', fileContent: 'MEMORY.md content', fileActionsAria: 'MEMORY.md file actions', saving: 'Saving…', save: 'Save', saved: 'Saved', openFile: 'Open MEMORY.md', openFolder: 'Open containing folder', loading: 'Loading…', reload: 'Reload', openPrevious: 'Open previous version', copyPath: 'Copy path', copyPrevious: 'Copy previous-version reference', resetting: 'Resetting…', resetBackup: 'Reset and back up', restorePrevious: 'Restore previous version', archiveDraftNotice: 'Archive and restore actions update only the draft until you save MEMORY.md.', noMatchEntry: 'No matching entries.', noEntry: 'No entries yet.', created: 'Created ', updated: 'Updated ', archivedNoPrompt: 'Archived; not given to the model', activePrompt: 'Active entry; given to the model when you send', locateDraft: 'Locate in draft', promptPreview: 'Model context preview', willInject: 'Included when sending', willNotInject: 'Not currently included', copyContext: 'Copy context', promptPreviewHelp: 'What will be given to the model when you send. Archived entries are excluded and suspected secrets are redacted.', safeModePreview: 'MEMORY.md is too large, so no model-context preview is generated.', emptyPromptPreview: 'No active memories will be given to the model.', loadFailed: 'Failed to load local memory', reloaded: 'MEMORY.md reloaded', reloadDiscarded: 'Unsaved draft changes were discarded.', toggleFailed: 'Failed to update local memory', agentReadFailed: 'Failed to update model read access', saveBlocked: 'Save blocked', safeMode: 'MEMORY.md is too large and entered safe mode.', savedRedacted: 'Saved with sensitive fields redacted', savedFile: 'MEMORY.md saved', saveFailed: 'Failed to save MEMORY.md', resetDone: 'MEMORY.md reset', resetDoneDetail: 'The previous version was saved as a backup.', resetFailed: 'Failed to reset MEMORY.md', noBackup: 'No backup available to restore', noBackupDetail: 'A previous-version backup is created after you save or reset MEMORY.md.', restoreLatestTitle: 'Restore the previous MEMORY.md version?', restoreCandidateTitle: 'Restore this MEMORY.md backup?', confirmRestore: 'Restore', cancel: 'Cancel', restoredLatest: 'Previous MEMORY.md version restored', restoredCandidate: 'MEMORY.md backup candidate restored', restoredDetail: 'The file from before the restore was saved as restore.bak.', restoreFailed: 'Restore failed', restoreLatestFailed: 'Failed to restore previous version', restoreCandidateFailed: 'Failed to restore backup', openFailed: 'Open failed', openPreviousFailed: 'Failed to open previous version', pathCopied: 'Path copied', copyFailed: 'Copy failed', copyFailedDetail: 'The clipboard is unavailable or access was denied by the system.', backupReferenceCopied: 'Previous-version reference copied', entryReferenceCopied: 'Memory reference copied', locateFailed: 'Could not locate memory', locateFailedDetail: 'This memory is not in the current draft. Save or reload, then try again.', emptyTitle: 'Title is required', emptyTitleDetail: 'Give this memory a short title.', emptyContent: 'Content is required', emptyContentDetail: 'Enter the preference or fact to retain.', draftOversize: 'Draft is too large', oversizeDetail: 'MEMORY.md exceeds the safety limit. Remove older content first.', addedDraft: 'Memory added', addedDraftDetail: 'Written to MEMORY.md.', updateFailed: 'Could not update memory', invalidIdDetail: 'This memory has no recognizable ID, so the update was stopped.', archivedDraft: 'Memory archived in draft', restoredDraft: 'Memory restored in draft', updateBlocked: 'Update blocked', archived: 'Memory archived', restored: 'Memory restored', archiveFailed: 'Failed to archive memory', entryRestoreFailed: 'Failed to restore memory', promptCopied: 'Model context preview copied', promptCopiedDetail: 'Uses the same prompt-preview and redaction path.', restoreDraftAction: 'Restore to draft', archiveDraftAction: 'Archive in draft', restoreAction: 'Restore', archiveAction: 'Archive', } satisfies Record; - -const SETTINGS_MEMORY_COPY = { - 'zh-CN': { +const settingsMemoryCopyZhCn: MemorySettingsCopy = { intlLocale: 'zh-CN', text: zhText, countActive: (count, draft) => draft ? `草稿 ${count} 条生效` : `${count} 条生效`, @@ -137,8 +135,8 @@ const SETTINGS_MEMORY_COPY = { upload_conflict: '另一个记忆上传正在进行,请重试。', }, 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 过大,当前不会提供。', agentRead: '模型上下文读取未开启。' }, backupOversize: '备份过大,无法预览条目', previewOversize: '草稿过大,条目预览已暂停;保存前请先删减 MEMORY.md 内容。', previewTruncationMarker: '[本地记忆已按长度截断]', - }, - 'zh-TW': { + }; +const settingsMemoryCopyZhTw: MemorySettingsCopy = { intlLocale: 'zh-TW', text: zhTwText, countActive: (count, draft) => draft ? `草稿 ${count} 則生效` : `${count} 則生效`, @@ -180,8 +178,8 @@ const SETTINGS_MEMORY_COPY = { upload_conflict: '另一個記憶上傳正在進行,請重試。', }, 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 過大,目前不會提供。', agentRead: '模型上下文讀取未開啟。' }, backupOversize: '備份過大,無法預覽條目', previewOversize: '草稿過大,條目預覽已暫停;儲存前請先刪減 MEMORY.md 內容。', previewTruncationMarker: '[本地記憶已按長度截斷]', - }, - en: { + }; +const settingsMemoryCopyEn: MemorySettingsCopy = { intlLocale: 'en-US', text: enText, countActive: (count, draft) => draft @@ -227,8 +225,9 @@ const SETTINGS_MEMORY_COPY = { upload_conflict: 'Another memory upload is in progress. Try again.', }, 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 incognito', error: 'Read failed' }, promptBlocked: { disabled: 'Local memory is disabled.', incognito: 'Local memory is never added in incognito mode.', safeMode: 'MEMORY.md is too large and will not be added.', agentRead: 'Model context access is disabled.' }, backupOversize: 'Backup is too large to preview entries', previewOversize: 'The draft is too large, so entry preview is paused. Reduce MEMORY.md before saving.', previewTruncationMarker: '[Local memory truncated to the length limit]', - }, -} satisfies UiCatalog; + }; + +const SETTINGS_MEMORY_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': settingsMemoryCopyZhCn, 'zh-TW': settingsMemoryCopyZhTw, en: settingsMemoryCopyEn })) satisfies UiCatalog; export function getMemorySettingsCopy(locale: UiLocale): MemorySettingsCopy { return SETTINGS_MEMORY_COPY[locale]; } diff --git a/apps/desktop/src/renderer/locales/settings-navigation-copy.ts b/apps/desktop/src/renderer/locales/settings-navigation-copy.ts index 19dad1b4ebd..b564cfd82bd 100644 --- a/apps/desktop/src/renderer/locales/settings-navigation-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-navigation-copy.ts @@ -18,7 +18,7 @@ */ import type { SettingsSection } from '@maka/core/settings'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { SettingsNavGroup } from '../settings/nav-group-summary.js'; export type SettingsNavigationCopy = { @@ -26,7 +26,7 @@ export type SettingsNavigationCopy = { sections: Record; }; -const SETTINGS_NAVIGATION_COPY_BY_LOCALE = { +const SETTINGS_NAVIGATION_COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { groups: { preferences: '偏好', @@ -108,7 +108,7 @@ const SETTINGS_NAVIGATION_COPY_BY_LOCALE = { about: { label: 'About', description: 'Version, updates, and support.' }, }, }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getSettingsNavigationCopy(locale: UiLocale): SettingsNavigationCopy { return SETTINGS_NAVIGATION_COPY_BY_LOCALE[locale]; diff --git a/apps/desktop/src/renderer/locales/settings-preferences-copy.ts b/apps/desktop/src/renderer/locales/settings-preferences-copy.ts index 8e2582249d3..5039086de6f 100644 --- a/apps/desktop/src/renderer/locales/settings-preferences-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-preferences-copy.ts @@ -18,7 +18,7 @@ */ import type { AppIcon, ThemePalette, ThemePreference } from '@maka/core/settings'; -import type { UiCatalog, UiLocale, UiLocalePreference } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, type UiLocalePreference, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; type OptionCopy = { label: string; help: string }; @@ -267,9 +267,7 @@ export type SettingsPreferencesCopy = { value: string; }; }; - -const SETTINGS_PREFERENCES_COPY_BY_LOCALE = { - 'zh-CN': { +const settingsPreferencesCopyByLocaleZhCn: SettingsPreferencesCopy = { personalization: { saveFailed: '保存失败', displayName: '显示名称', displayNameHelp: 'Maka 在聊天里会以这个名字称呼你。留空就用默认的“你”。', displayNamePlaceholder: '例如:JK', displayNameUnset: '未设置,Maka 会称呼你“你”', displayNameChange: '更改', displayNameSet: '设置', interfaceLanguage: '界面语言', interfaceLanguageHelp: '选择 Maka 界面的显示语言。切换后立即生效,重启后保持。', localeOptions: [['auto', '跟随系统'], ['zh-CN', '简体中文'], ['zh-TW', '繁體中文'], ['en', 'English']], @@ -374,8 +372,8 @@ const SETTINGS_PREFERENCES_COPY_BY_LOCALE = { sourceCode: '源码', releaseNotes: '发行说明', }, password: { copyFailed: '复制失败', clipboardUnavailable: '剪贴板不可用或被系统拒绝。', copying: '复制中', copied: '已复制', copy: '复制', hide: '隐藏', show: '显示', value: '凭据值' }, - }, - 'zh-TW': { + }; +const settingsPreferencesCopyByLocaleZhTw: SettingsPreferencesCopy = { personalization: { saveFailed: '儲存失敗', displayName: '顯示名稱', displayNameHelp: 'Maka 在聊天裡會以這個名字稱呼你。留空就用預設的“你”。', displayNamePlaceholder: '例如:JK', displayNameUnset: '未設定,Maka 會稱呼你“你”', displayNameChange: '更改', displayNameSet: '設定', interfaceLanguage: '介面語言', interfaceLanguageHelp: '選擇 Maka 介面的顯示語言。切換後立即生效,重新啟動後仍會保留。', localeOptions: [['auto', '自動(跟隨系統)'], ['zh-CN', '简体中文'], ['zh-TW', '繁體中文'], ['en', 'English']], @@ -472,8 +470,8 @@ const SETTINGS_PREFERENCES_COPY_BY_LOCALE = { sourceCode: '原始碼', releaseNotes: '發行說明', }, password: { copyFailed: '複製失敗', clipboardUnavailable: '剪貼簿不可用或被系統拒絕。', copying: '複製中', copied: '已複製', copy: '複製', hide: '隱藏', show: '顯示', value: '憑據值' }, - }, - en: { + }; +const settingsPreferencesCopyByLocaleEn: SettingsPreferencesCopy = { 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 system'], ['zh-CN', 'Simplified Chinese'], ['zh-TW', 'Traditional Chinese'], ['en', 'English']], assistantTone: 'Assistant tone', assistantToneHelp: 'Up to 500 characters. This changes response style only; permission and safety rules still apply. Changes save automatically.', assistantTonePlaceholder: 'For example: technically rigorous, concise, and no emoji.', }, @@ -541,8 +539,9 @@ const SETTINGS_PREFERENCES_COPY_BY_LOCALE = { sourceCode: 'Source code', releaseNotes: 'Release notes', }, password: { copyFailed: 'Copy failed', clipboardUnavailable: 'The clipboard is unavailable or access was denied.', copying: 'Copying', copied: 'Copied', copy: 'Copy', hide: 'Hide', show: 'Show', value: 'credential value' }, - }, -} satisfies UiCatalog; + }; + +const SETTINGS_PREFERENCES_COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': settingsPreferencesCopyByLocaleZhCn, 'zh-TW': settingsPreferencesCopyByLocaleZhTw, en: settingsPreferencesCopyByLocaleEn })) satisfies UiCatalog; export function getSettingsPreferencesCopy(locale: UiLocale): SettingsPreferencesCopy { return SETTINGS_PREFERENCES_COPY_BY_LOCALE[locale]; diff --git a/apps/desktop/src/renderer/locales/settings-projects-copy.ts b/apps/desktop/src/renderer/locales/settings-projects-copy.ts index 28520bceb2d..9ca5a87c5aa 100644 --- a/apps/desktop/src/renderer/locales/settings-projects-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-projects-copy.ts @@ -18,7 +18,7 @@ */ import type { RuntimeHostServiceErrorCode } from '@maka/runtime-host/operator'; -import { type UiCatalog, type UiLocale, lookupCopy } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, lookupCopy, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export type SettingsProjectsCopy = { runtimeHost: { @@ -317,7 +317,7 @@ export type SettingsProjectsCopy = { moreActions(projectName: string): string; }; -const SETTINGS_PROJECTS_COPY_BY_LOCALE = { +const SETTINGS_PROJECTS_COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { runtimeHost: { title: 'Runtime Host', @@ -1367,7 +1367,7 @@ const SETTINGS_PROJECTS_COPY_BY_LOCALE = { 'Add a project folder and new tasks can start in it, with the sidebar grouping tasks by project.', moreActions: (projectName: string) => `More actions for ${projectName}`, }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getSettingsProjectsCopy(locale: UiLocale): SettingsProjectsCopy { return SETTINGS_PROJECTS_COPY_BY_LOCALE[locale]; diff --git a/apps/desktop/src/renderer/locales/settings-shared-copy.ts b/apps/desktop/src/renderer/locales/settings-shared-copy.ts index ad5e39913b3..39e712d7497 100644 --- a/apps/desktop/src/renderer/locales/settings-shared-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-shared-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export type SettingsSharedCopy = { modalLabel: string; @@ -67,7 +67,7 @@ export type SettingsSharedCopy = { }; }; -const SETTINGS_SHARED_COPY_BY_LOCALE = { +const SETTINGS_SHARED_COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { modalLabel: '设置', contentLabel: '设置内容', @@ -188,7 +188,7 @@ const SETTINGS_SHARED_COPY_BY_LOCALE = { reviewScheduleHelp: 'When the daily review runs, and which model writes it.', }, }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getSettingsSharedCopy(locale: UiLocale): SettingsSharedCopy { return SETTINGS_SHARED_COPY_BY_LOCALE[locale]; diff --git a/apps/desktop/src/renderer/locales/settings-subagents-copy.ts b/apps/desktop/src/renderer/locales/settings-subagents-copy.ts index a77ccfae235..ae11193bd57 100644 --- a/apps/desktop/src/renderer/locales/settings-subagents-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-subagents-copy.ts @@ -21,7 +21,7 @@ import type { SubagentProfile } from '@maka/core/subagent-settings'; import type { ThinkingLevel } from '@maka/core/model-thinking'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; type ProfileCopy = { label: string; @@ -99,7 +99,7 @@ export type SubagentSettingsCopy = { thinking: Record; }; -const SETTINGS_SUBAGENTS_COPY_BY_LOCALE = { +const SETTINGS_SUBAGENTS_COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { section: { title: '已批准的子 Agent', @@ -346,7 +346,7 @@ const SETTINGS_SUBAGENTS_COPY_BY_LOCALE = { max: 'Maximum', }, }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getSubagentSettingsCopy(locale: UiLocale): SubagentSettingsCopy { return SETTINGS_SUBAGENTS_COPY_BY_LOCALE[locale]; diff --git a/apps/desktop/src/renderer/locales/settings-tasks-copy.ts b/apps/desktop/src/renderer/locales/settings-tasks-copy.ts index 005e7eaa3c0..b3bd46c1511 100644 --- a/apps/desktop/src/renderer/locales/settings-tasks-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-tasks-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; /** * Restoring and deleting a single task speak through the rail's own row @@ -85,7 +85,7 @@ export type SettingsTasksCopy = { emptyBody: string; }; -const SETTINGS_TASKS_COPY_BY_LOCALE = { +const SETTINGS_TASKS_COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { listAria: '已归档任务', noProject: '无项目', @@ -231,7 +231,7 @@ const SETTINGS_TASKS_COPY_BY_LOCALE = { emptyTitle: 'Nothing archived', emptyBody: 'Archive a task from the rail to restore or permanently delete it here.', }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getSettingsTasksCopy(locale: UiLocale): SettingsTasksCopy { return SETTINGS_TASKS_COPY_BY_LOCALE[locale]; 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 46c54c86d09..65751f56b1a 100644 --- a/apps/desktop/src/renderer/locales/settings-test-result-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-test-result-copy.ts @@ -20,7 +20,7 @@ import type { SettingsTestResult, SettingsTestResultCode } from '@maka/core/settings'; import type { BotTestErrorCode } from '@maka/runtime/bots'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { lookupCopy } from '@maka/core/ui-locale'; type SettingsTestResultCopy = { @@ -48,9 +48,7 @@ type SettingsTestResultCopy = { >; }; }; - -const COPY = { - 'zh-CN': { +const copyZhCn: SettingsTestResultCopy = { proxy: { reachable: (endpoint, location) => ["代理配置有效", endpoint, location].filter(Boolean).join(" · "), @@ -84,8 +82,8 @@ const COPY = { wechat_ilink_credentials_incomplete: '请先完成微信扫码登录,保存 iLink bot token 与 base URL。', }, }, - }, - 'zh-TW': { + }; +const copyZhTw: SettingsTestResultCopy = { proxy: { reachable: (endpoint, location) => ["代理設定有效", endpoint, location].filter(Boolean).join(" · "), @@ -119,8 +117,8 @@ const COPY = { wechat_ilink_credentials_incomplete: '請先完成微信掃碼登入,儲存 iLink bot token 與 base URL。', }, }, - }, - en: { + }; +const copyEn: SettingsTestResultCopy = { proxy: { reachable: (endpoint, location) => ["The proxy is reachable", endpoint, location] @@ -161,8 +159,9 @@ const COPY = { wechat_ilink_credentials_incomplete: 'Complete WeChat QR sign-in first to save the iLink bot token and base URL.', }, }, - }, -} satisfies UiCatalog; + }; + +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': copyZhCn, 'zh-TW': copyZhTw, en: copyEn })) satisfies UiCatalog; export function settingsTestResultMessage( result: SettingsTestResult, diff --git a/apps/desktop/src/renderer/locales/settings-usage-copy.ts b/apps/desktop/src/renderer/locales/settings-usage-copy.ts index 8615eb1c048..bd981af8f58 100644 --- a/apps/desktop/src/renderer/locales/settings-usage-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-usage-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export type UsageSettingsCopy = { staleTitle: string; staleBody: string; loadFailed: string; capacityBody: string; retainedBody: string; @@ -38,9 +38,7 @@ export type UsageSettingsCopy = { toolEmptyTitle: string; toolEmptyBody: string; pricingEmptyBody: string; }; }; - -const SETTINGS_USAGE_COPY = { - 'zh-CN': { +const settingsUsageCopyZhCn: UsageSettingsCopy = { staleTitle: "统计已更新", staleBody: "当前显示的是之前的完整结果。请刷新后继续浏览。", loadFailed: "无法加载使用统计", capacityBody: "统计结果超出显示容量,请求未返回任何部分数据。", retainedBody: "当前仍显示上次成功加载的结果,新查询尚未生效。", saveFailed: '保存使用统计设置失败', toolbarAria: '使用统计范围与刷新', rangeAria: '使用统计时间范围', ranges: ['24h', '7天', '30天', '全部'], refreshingAria: '正在刷新使用统计', refreshAria: '刷新使用统计', summaryAria: '使用统计汇总指标', totalRequests: '模型调用', totalCost: '总费用', costHelp: '以模型供应商最终结算为准', @@ -64,8 +62,8 @@ const SETTINGS_USAGE_COPY = { toolEmptyTitle: '暂无工具调用', toolEmptyBody: '智能体调用工具后,这里会按工具聚合调用次数、成功、错误与平均耗时。', pricingEmptyBody: '未配置定价覆盖时,费用按内置模型定价表结算;在此可为特定模型登记自定义价格。', }, - }, - 'zh-TW': { + }; +const settingsUsageCopyZhTw: UsageSettingsCopy = { staleTitle: "統計已更新", staleBody: "目前顯示先前的完整結果。請重新整理後繼續瀏覽。", loadFailed: "無法載入使用統計", capacityBody: "統計結果超出顯示容量,請求未傳回任何部分資料。", retainedBody: "目前仍顯示上次成功載入的結果,新查詢尚未生效。", tokenTooltip: { total: '總計', input: '輸入', output: '輸出', cached: '快取', new: '新', hit: '命中', created: '建立' }, saveFailed: '儲存使用統計設定失敗', toolbarAria: '使用統計範圍與重新整理', rangeAria: '使用統計時間範圍', ranges: ['24h', '7天', '30天', '全部'], @@ -89,8 +87,8 @@ const SETTINGS_USAGE_COPY = { toolEmptyTitle: '暫無工具呼叫', toolEmptyBody: '智慧體呼叫工具後,這裡會按工具聚合呼叫次數、成功、錯誤與平均耗時。', pricingEmptyBody: '未設定定價覆蓋時,費用按內建模型定價表結算;在此可為特定模型登記自訂價格。', }, - }, - en: { + }; +const settingsUsageCopyEn: UsageSettingsCopy = { staleTitle: 'Usage has changed', staleBody: 'The previous complete result is still shown. Refresh to continue browsing.', loadFailed: 'Unable to load Usage', capacityBody: 'This complete result exceeds the display capacity. No partial result was loaded.', retainedBody: 'The last successfully loaded result is still shown; the new query has not taken effect.', 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', @@ -114,8 +112,9 @@ const SETTINGS_USAGE_COPY = { toolEmptyTitle: 'No tool calls', toolEmptyBody: 'After an agent calls a tool, calls, successes, errors, and average duration appear here by tool.', pricingEmptyBody: 'Without pricing overrides, costs use the built-in model pricing table. Add custom prices here for specific models.', }, - }, -} satisfies UiCatalog; + }; + +const SETTINGS_USAGE_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': settingsUsageCopyZhCn, 'zh-TW': settingsUsageCopyZhTw, en: settingsUsageCopyEn })) satisfies UiCatalog; export function getUsageSettingsCopy(locale: UiLocale): UsageSettingsCopy { return SETTINGS_USAGE_COPY[locale]; 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 0cd8730afff..3312b57ba10 100644 --- a/apps/desktop/src/renderer/locales/settings-web-search-copy.ts +++ b/apps/desktop/src/renderer/locales/settings-web-search-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { WebSearchCredentialStatus, WebSearchErrorReason } from '@maka/core/web-search'; @@ -37,7 +37,7 @@ export type WebSearchSettingsCopy = { errors: Record; }; -const SETTINGS_WEB_SEARCH_COPY = { +const SETTINGS_WEB_SEARCH_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { saveFailed: '保存联网搜索设置失败', saveStatusFailed: '保存联网搜索状态失败', keySaved: '已保存 Tavily 密钥', keySavedDetail: '可点击「测试」做一次真实请求验证。', credentialsCleared: '已清空 Tavily 凭据', credentialsClearedDetail: '联网搜索已自动关闭。', credentialValid: 'Tavily 凭据可用', resultCount: (count) => `返回 ${count} 条结果。`, @@ -89,6 +89,6 @@ const SETTINGS_WEB_SEARCH_COPY = { sources: { model: 'Source: current model connection', envWithSaved: 'Source: environment variable (saved key available as backup)', env: 'Source: environment variable', saved: 'Source: key saved on this device', none: 'Source: not configured' }, errors: { invalid_query: 'Enter a valid search query.', incognito_active: 'Web search is unavailable in incognito mode.', not_configured: 'The selected search source is not configured.', invalid_credentials: 'The search provider rejected the current credential. Update it and try again.', rate_limited: 'The search provider is receiving too many requests. Try again later.', network_error: 'The network request failed. Check your connection and try again.', timeout: 'The search request timed out. Try again.', unsupported_provider: 'The current model does not support hosted search, or Maka has not implemented its protocol yet. Select Tavily to continue.', experimental_disabled: 'The experimental web search feature is currently disabled.' }, }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getWebSearchSettingsCopy(locale: UiLocale): WebSearchSettingsCopy { return SETTINGS_WEB_SEARCH_COPY[locale]; } diff --git a/apps/desktop/src/renderer/locales/shell-copy.ts b/apps/desktop/src/renderer/locales/shell-copy.ts index 5da05d58533..cee766bf0f6 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 { type UiCatalog, type UiLocale, lookupCopy } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, lookupCopy, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { type PermissionMode } from '@maka/core/permission'; @@ -759,9 +759,7 @@ const EN_SETTINGS_SECTIONS: Record = { health: 'Health', about: 'About', }; - -const SHELL_COPY_BY_LOCALE = { - 'zh-CN': { +const shellCopyByLocaleZhCn: ShellCopy = { navigation: { settings: '设置' }, actions: { retry: '重试' }, paths: { @@ -1297,8 +1295,8 @@ const SHELL_COPY_BY_LOCALE = { graphHistoryDescription: '请在 Agent Graph 面板的运行轮次菜单中查看历史记录。', resizeWorkbar: '调整任务工作栏宽度', }, - }, - 'zh-TW': { + }; +const shellCopyByLocaleZhTw: ShellCopy = { navigation: { settings: '設定' }, actions: { retry: '重試' }, paths: { @@ -1834,8 +1832,8 @@ const SHELL_COPY_BY_LOCALE = { graphHistoryDescription: '請在 Agent Graph 面板的執行輪次選單中檢視歷史記錄。', resizeWorkbar: '調整任務工作欄寬度', }, - }, - en: { + }; +const shellCopyByLocaleEn: ShellCopy = { navigation: { settings: 'Settings' }, actions: { retry: 'Retry' }, paths: { @@ -2423,8 +2421,9 @@ const SHELL_COPY_BY_LOCALE = { graphHistoryDescription: 'Use the run menu in the Agent Graph panel to inspect history.', resizeWorkbar: 'Resize task workbar', }, - }, -} satisfies UiCatalog; + }; + +const SHELL_COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': shellCopyByLocaleZhCn, 'zh-TW': shellCopyByLocaleZhTw, en: shellCopyByLocaleEn })) satisfies UiCatalog; export function getShellCopy(locale: UiLocale): ShellCopy { return SHELL_COPY_BY_LOCALE[locale]; diff --git a/apps/desktop/src/renderer/locales/shell-remaining-copy.ts b/apps/desktop/src/renderer/locales/shell-remaining-copy.ts index 0bfaf931846..9fde15ba346 100644 --- a/apps/desktop/src/renderer/locales/shell-remaining-copy.ts +++ b/apps/desktop/src/renderer/locales/shell-remaining-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; type WidenCopy = T extends string ? string @@ -236,11 +236,11 @@ const enCopy: ShellRemainingCopy = { }, }; -const COPY = { +const COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': zhCopy, 'zh-TW': zhTwCopy, en: enCopy, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getShellRemainingCopy(locale: UiLocale): ShellRemainingCopy { return COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/storage-usage-copy.ts b/apps/desktop/src/renderer/locales/storage-usage-copy.ts index 387cb413612..86fd7c428b6 100644 --- a/apps/desktop/src/renderer/locales/storage-usage-copy.ts +++ b/apps/desktop/src/renderer/locales/storage-usage-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { StorageUsageKind } from '@maka/runtime-host/protocol'; export type StorageUsageCopy = { @@ -43,7 +43,7 @@ export type StorageUsageCopy = { taskSizeNote: string; }; -const STORAGE_USAGE_COPY = { +const STORAGE_USAGE_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { title: '存储占用', help: '当前运行时主机的数据占用。此处只读,不会删除或压缩任何内容。', @@ -140,7 +140,7 @@ const STORAGE_USAGE_COPY = { taskSizeNote: 'Task sizes are estimates. Offloaded context is counted for every task that references it, so shared content is counted more than once. Usage history is kept after a task is deleted.', }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getStorageUsageCopy(locale: UiLocale): StorageUsageCopy { return STORAGE_USAGE_COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/task-readiness-copy.ts b/apps/desktop/src/renderer/locales/task-readiness-copy.ts index 0c66a865158..54952bd13a9 100644 --- a/apps/desktop/src/renderer/locales/task-readiness-copy.ts +++ b/apps/desktop/src/renderer/locales/task-readiness-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export interface TaskReadinessCopy { readonly runtime: { @@ -32,7 +32,7 @@ export interface TaskReadinessCopy { }; } -const TASK_READINESS_COPY = { +const TASK_READINESS_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { runtime: { title: 'Maka 运行服务暂时不可用。', @@ -69,7 +69,7 @@ const TASK_READINESS_COPY = { actionLabel: { workspace_picker: 'Choose workspace', retry: 'Check again' }, }, }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getTaskReadinessCopy(locale: UiLocale): TaskReadinessCopy { return TASK_READINESS_COPY[locale]; diff --git a/apps/desktop/src/renderer/locales/work-board-error-copy.ts b/apps/desktop/src/renderer/locales/work-board-error-copy.ts index c6a28da90a6..50878169ff5 100644 --- a/apps/desktop/src/renderer/locales/work-board-error-copy.ts +++ b/apps/desktop/src/renderer/locales/work-board-error-copy.ts @@ -17,13 +17,13 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiCatalog, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { lookupCopy } from '@maka/core/ui-locale'; import type { WorkBoardErrorCode } from '../../shared/work-board-ipc.js'; type WorkBoardErrorCopy = Record; -const WORK_BOARD_ERROR_COPY = { +const WORK_BOARD_ERROR_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { invalid_input: '这项操作无效,请检查后重试', not_found: '这条事项已不存在', @@ -48,7 +48,7 @@ const WORK_BOARD_ERROR_COPY = { must_archive_first: 'Archive this item before deleting it.', unknown: 'The action failed. Try again later.', }, -} satisfies UiCatalog; +})) satisfies UiCatalog; export function getWorkBoardErrorCopy(locale: UiLocale): WorkBoardErrorCopy { return WORK_BOARD_ERROR_COPY[locale]; diff --git a/apps/desktop/src/renderer/settings/bot-chat-detail.tsx b/apps/desktop/src/renderer/settings/bot-chat-detail.tsx index d90b50f11eb..12e5624e519 100644 --- a/apps/desktop/src/renderer/settings/bot-chat-detail.tsx +++ b/apps/desktop/src/renderer/settings/bot-chat-detail.tsx @@ -17,6 +17,8 @@ * under the License. */ +import type { UiLocale } from '@maka/core/ui-locale'; + import { useEffect, useMemo, useState, type ReactNode } from 'react'; import { ICON_SIZE, ArrowLeft } from '@maka/ui/icons'; import { @@ -659,7 +661,7 @@ function BotAllowedUserIdsField(props: { ); } -function botConnectionLabel(connection: BotStatus['connection'], locale: 'zh-CN' | 'zh-TW' | 'en'): string { +function botConnectionLabel(connection: BotStatus['connection'], locale: UiLocale): 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 e4b31856830..135d7a3d8d3 100644 --- a/apps/desktop/src/renderer/settings/bot-chat-overview.tsx +++ b/apps/desktop/src/renderer/settings/bot-chat-overview.tsx @@ -17,6 +17,8 @@ * under the License. */ +import type { UiLocale } from '@maka/core/ui-locale'; + import type { ReactNode } from 'react'; import type { BotChannelSettings, BotProvider, BotMessageHandling } from '@maka/core/bot-chat-settings'; import type { BotStatus } from '@maka/runtime/bots'; @@ -159,7 +161,7 @@ function botOverviewDetail( currentError: string | undefined, fallback: string, liveOperational: boolean, - locale: 'zh-CN' | 'zh-TW' | 'en', + locale: UiLocale, ): ReactNode { const copy = getBotSettingsCopy(locale).overview; const identity = status?.identity?.username ?? status?.identity?.displayName; 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 bf14388efea..3aba4c1e4eb 100644 --- a/apps/desktop/src/renderer/settings/daily-review-settings-page.tsx +++ b/apps/desktop/src/renderer/settings/daily-review-settings-page.tsx @@ -17,6 +17,8 @@ * under the License. */ +import type { UiLocale } from '@maka/core/ui-locale'; + import { useEffect, useMemo, useState } from 'react'; import { Banner } from '@astryxdesign/core'; import type { DailyReviewConfig } from '@maka/core/daily-review'; @@ -39,7 +41,7 @@ function buildDailyReviewModelOptions( connections: readonly ProjectedLlmConnection[], currentModelKey: string, copy: DailyReviewSettingsCopy, - locale: 'zh-CN' | 'zh-TW' | 'en', + locale: UiLocale, ): Array<{ value: string; label: string }> { return [ { value: DAILY_REVIEW_DEFAULT_MODEL_VALUE, label: copy.defaultModel }, diff --git a/apps/desktop/src/renderer/settings/provider-display-copy.ts b/apps/desktop/src/renderer/settings/provider-display-copy.ts index 110802caa3a..512560c955e 100644 --- a/apps/desktop/src/renderer/settings/provider-display-copy.ts +++ b/apps/desktop/src/renderer/settings/provider-display-copy.ts @@ -19,7 +19,7 @@ import type { ProviderType } from '@maka/core/llm-connections'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import type { ShippedUiCatalog, UiCatalog, UiLocale } from '@maka/core/ui-locale'; /** * Pure-data provider introduction copy, localized zh / en. @@ -51,7 +51,7 @@ export const UNKNOWN_PROVIDER_DESCRIPTION = { 'zh-CN': '该 provider 在当前版本未注册。', 'zh-TW': '該 provider 在目前版本未註冊。', en: 'This provider is not registered in the current build.', -} satisfies UiCatalog; +} satisfies ShippedUiCatalog; export const PROVIDER_DISPLAY_COPY = { 'kimi-coding-plan': { @@ -368,9 +368,10 @@ export const PROVIDER_DISPLAY_COPY = { '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>; +} satisfies Record>; export function providerDisplay(type: ProviderType, locale: UiLocale): ProviderCopy { - const copy = (PROVIDER_DISPLAY_COPY as Partial>>)[type]?.[locale]; - return copy ?? { name: type, description: UNKNOWN_PROVIDER_DESCRIPTION[locale] }; + const catalog = (PROVIDER_DISPLAY_COPY as Partial & Partial>>>)[type]; + const copy = catalog?.[locale] ?? catalog?.en; + return copy ?? { name: type, description: (UNKNOWN_PROVIDER_DESCRIPTION as Partial>)[locale] ?? UNKNOWN_PROVIDER_DESCRIPTION.en }; } diff --git a/packages/cli/src/tui-copy-catalog.ts b/packages/cli/src/tui-copy-catalog.ts index 94278710373..89f7189951c 100644 --- a/packages/cli/src/tui-copy-catalog.ts +++ b/packages/cli/src/tui-copy-catalog.ts @@ -17,1508 +17,1576 @@ * under the License. */ -export const TUI_COPY_RESOURCES = { - todo: { - en: { - title: 'Current Todo', - markedComplete: 'marked complete', - open: '/todo to view', - usage: 'Usage: /todo', - progress: 'Todo', - unavailable: 'Todo unavailable', - empty: 'No Todo items', - loading: 'Loading Todo…', - hint: '↑↓/PgUp/PgDn scroll · Home/End · Esc close', - }, - 'zh-CN': { - title: '当前待办', - markedComplete: '标记完成', - open: '/todo 查看', - usage: '用法:/todo', - progress: '待办', - unavailable: '待办不可用', - empty: '暂无待办', - loading: '正在加载待办…', - hint: '↑↓/PgUp/PgDn 滚动 · Home/End · Esc 关闭', - }, - 'zh-TW': { - title: '目前待辦', - markedComplete: '標記完成', - open: '/todo 查看', - usage: '用法:/todo', - progress: '待辦', - unavailable: '待辦不可用', - empty: '沒有待辦', - loading: '正在載入待辦…', - hint: '↑↓/PgUp/PgDn 捲動 · Home/End · Esc 關閉', - }, +import { + defineShippedUiMessageCatalog, + resolveUiMessageCatalog, + type UiMessageShape, +} from '@maka/core/ui-locale'; + +const TUI_COPY_RESOURCES_SESSION_STATUS_EN = { + running: 'running', + waitingForUser: 'waiting for you', + permissionRequired: 'needs permission', + connectionRequired: 'needs connection', + signInRequired: 'needs sign-in', + stopped: 'stopped', +}; + +const TUI_COPY_RESOURCES_UNREAD_INDICATOR_EN = { + hint: '↓ {count, plural, one {# new line} other {# new lines}} — End to jump to latest', +}; + +const TUI_COPY_RESOURCES_PENDING_QUEUE_EN = { + steeringLabel: 'Steering:', + queuedLabel: 'Queued:', + requeueHint: 'Shift+← take queued messages back to re-edit', +}; + +const TUI_COPY_RESOURCES_SESSION_ACTIONS_EN = { + externalCatalogFailed: 'Could not read external sessions.', + externalImport: 'Import external session…', + externalImportDescription: 'from the connected Runtime Host', + externalSourceTitle: 'Choose external source', + externalSessionTitle: 'Import from {source}', + externalLoadMore: 'Load more…', + externalAllWorkspaces: 'Show all workspaces', + externalCurrentWorkspace: 'Show current workspace', + externalEmpty: 'No external sessions found.', + externalUnavailable: 'The external sessions on this page are not currently available to import.', + externalImportedCount: 'imported {count} times', + externalImportedActionsTitle: 'Choose an action for {source}', + externalOpenLatestImported: 'Open latest imported task', + externalImportAgain: 'Import again', + externalImportBusy: + 'Cannot import while a turn or another action is running. Wait for it to finish and try again.', + externalImportFailed: 'Could not import the external session.', + externalImportModelUnavailable: + 'No usable model connection is available. Configure and enable one before importing.', + externalImportSourceUnreadable: 'The external session could not be read or converted.', + externalImportLimit: 'The external session exceeds the supported {kind} limit ({max}).', + externalImportLimitTranscriptBytes: 'source file size', + externalImportLimitRecordBytes: 'single-record size', + externalImportLimitRecords: 'record count', + externalImportLimitConvertedBytes: 'converted content size', + externalImportLimitMessages: 'message count', + externalImportUncertain: + 'The import result is uncertain. Check the session list or import again; importing again creates an independent task.', + externalOpenFailed: 'Imported as {sessionId}, but Maka could not open it. Use /session to retry.', + externalOpenLatestFailed: + 'Could not open the latest imported task ({sessionId}). Use /session to retry.', + newSessionFailed: + 'Could not start a new session: a local command could not be stopped. Press Ctrl+C to stop it, then try again.', +}; + +const TUI_COPY_RESOURCES_CONNECTION_IDENTITY_EN = { + withRecovery: '{notice} {recovery}', + emptyChoiceRecovery: + 'If /model has no choices, add or enable a connection first (run /setup to add one).', + 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.', +}; + +const TUI_COPY_RESOURCES_SKILLS_EN = { + pickerTitle: 'Invoke Skill', + usage: 'Usage: /skill, or type /skill: 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', }, - 'transcript-reader': { - en: { - title: 'DETAILED TRANSCRIPT', - scope: 'loaded history', - hint: 'Esc/Ctrl+O close · Ctrl+E details · / search · ↑↓/PgUp/PgDn · Home/End', - matches: 'matches in displayed text', - back: 'return', - }, - 'zh-CN': { - title: '详细记录', - scope: '已加载历史', - hint: 'Esc/Ctrl+O 返回 · Ctrl+E 详情 · / 搜索 · ↑↓/PgUp/PgDn · Home/End', - matches: '处匹配(当前显示内容)', - back: '回到原位置', - }, - 'zh-TW': { - title: '詳細記錄', - scope: '已載入歷史', - hint: 'Esc/Ctrl+O 返回 · Ctrl+E 詳情 · / 搜尋 · ↑↓/PgUp/PgDn · Home/End', - matches: '處符合(目前顯示內容)', - back: '回到原位置', - }, +}; + +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.', + unsupportedQuotes: + 'Cannot rewind to this turn: it carries quoted excerpts, and the TUI cannot restore those into the replacement prompt yet. Rewind to an earlier plain-text turn instead.', + unsupportedAttachments: + 'Cannot rewind to this turn: it carries attachments, and the TUI cannot restore those into the replacement prompt yet. Rewind to an earlier plain-text turn instead.', + unsupportedDirectoryReferences: + 'Cannot rewind to this turn: it carries directory references, and the TUI cannot restore those into the replacement prompt yet. Rewind to an earlier plain-text turn instead.', + pickerHint: + 'Return to before the selected turn (discards it and later turns; the prompt refills the input box) · enter select / esc cancel', +}; + +const TUI_COPY_RESOURCES_PRIMARY_GUIDANCE_EN = { + welcome: { + tagline: 'Get things done together', + start: 'Type a message to start', + session: 'Switch or resume a session', + model: 'Switch models', + setup: 'Set up a model provider', }, - 'host-owner': { - en: { - unavailable: 'Runtime Host owner controls are unavailable.', - usage: 'Usage: /host [status|stop|restart] or /update [latest|next|version]', - pickerTitle: 'Runtime Host owner action', - cancel: 'Cancel', - cancelDescription: 'Keep this Host and its work running', - safe: 'Safe handoff', - safeDescription: 'Continue only at a verified safe boundary', - interrupt: 'Interrupt active work', - interruptDescription: 'May stop work from every attached Surface', - pending: 'A Host owner action is already pending.', - disconnected: 'The TUI has already disconnected.', - foreign: - 'This TUI is attached to another Host. Run its owner controls on that Host; /exit only disconnects.', - notOwner: - 'This npm-global installation does not own the local Host. Use its actual operator; /exit only disconnects.', - hostChanged: 'The local Host changed or requires its managed-service operator.', - actionSummary: '{action}: {name}', - action_stop: 'Stop', - action_restart: 'Restart', - action_update: 'Update', - owner: 'Owner: {owner}', - root: 'Root: {root}', - epoch: 'Epoch: {epoch}', - composition: 'Composition: {composition} ({revision})', - activity: 'Connections: {connections}; operations: {operations}; residencies: {residencies}', - unknownActivity: 'Activity is unknown. Other Surfaces and autonomous tasks may be affected.', - updateSafeOnly: 'Update supports safe handoff only.', - invalidTarget: 'Update target must be latest, next, or an exact release version.', - updateRequiresSafe: - 'TUI update uses safe handoff only. Stop active work explicitly before updating.', - installationChanged: 'The npm-global installation changed. Reopen Maka before retrying.', - ownerChanged: 'The local Host installation owner changed. Reopen Maka before retrying.', - retirementHostChanged: - 'The Host changed before owner retirement; no replacement was interrupted.', - activityChanged: - 'Host activity changed after confirmation. Reopen Maka and confirm the new work before retrying.', - stillActive: - 'The Host still has active work. Reopen Maka and retry /host {action}; interruption requires a new confirmation.', - restartFailed: 'Host restart did not complete: {detail}', - status: '{name}: {state}', - state_starting: 'Starting', - state_containing: 'Containing', - state_recovering: 'Recovering', - state_ready: 'Ready', - state_draining: 'Draining', - localOwnerHint: - 'Owner controls require this npm-global installation to own the Host. /exit only disconnects.', - attachedOwnerHint: - 'Attached Host: lifecycle changes require its operator. /exit only disconnects.', - }, - 'zh-CN': { - unavailable: 'Runtime Host 所有者控制不可用。', - usage: '用法:/host [status|stop|restart] 或 /update [latest|next|version]', - pickerTitle: 'Runtime Host 所有者操作', - cancel: '取消', - cancelDescription: '保持此 Host 及其任务运行', - safe: '安全交接', - safeDescription: '仅在已验证的安全边界继续', - interrupt: '中断活动任务', - interruptDescription: '可能停止所有已连接界面的任务', - pending: '已有 Host 所有者操作等待执行。', - disconnected: 'TUI 已断开连接。', - foreign: '此 TUI 连接的是其他 Host。请在该 Host 上使用其所有者控制;/exit 仅断开连接。', - notOwner: - '此 npm-global 安装不是本地 Host 的所有者。请使用实际所有者的控制工具;/exit 仅断开连接。', - hostChanged: '本地 Host 已变更,或需要由其托管服务控制工具操作。', - actionSummary: '{action}:{name}', - action_stop: '停止', - action_restart: '重启', - action_update: '更新', - owner: '所有者:{owner}', - root: '根标识:{root}', - epoch: '实例标识:{epoch}', - composition: '运行组成:{composition}({revision})', - activity: '连接数:{connections};操作数:{operations};常驻任务:{residencies}', - unknownActivity: '活动状态未知。其他界面和自主任务可能受到影响。', - updateSafeOnly: '更新仅支持安全交接。', - invalidTarget: '更新目标必须为 latest、next 或确切的发布版本。', - updateRequiresSafe: 'TUI 更新仅使用安全交接。更新前请先显式停止活动任务。', - installationChanged: 'npm-global 安装已变更。请重新打开 Maka 后重试。', - ownerChanged: '本地 Host 的安装所有者已变更。请重新打开 Maka 后重试。', - retirementHostChanged: '停止旧实例前 Host 已变更;未中断替代实例。', - activityChanged: '确认后 Host 活动已变更。请重新打开 Maka,确认新的任务后重试。', - stillActive: 'Host 仍有活动任务。请重新打开 Maka 并重试 /host {action};中断需要重新确认。', - restartFailed: 'Host 重启未完成:{detail}', - status: '{name}:{state}', - state_starting: '启动中', - state_containing: '故障隔离中', - state_recovering: '恢复中', - state_ready: '就绪', - state_draining: '排空中', - localOwnerHint: '仅当此 npm-global 安装拥有该 Host 时才能使用所有者控制。/exit 仅断开连接。', - attachedOwnerHint: '已连接的 Host:生命周期变更需由其所有者操作。/exit 仅断开连接。', + commands: { + compact: 'Compact session context', + context: 'Show latest request context usage', + copy: 'Copy the last reply (or /copy all) to the clipboard', + exit: 'Disconnect this TUI; leave the attached Host running', + host: 'Inspect the Host or stop/restart its local owner', + update: 'Update the npm-global CLI and its local Host', + goal: 'Show autonomous goal status', + graph: 'Show, enable, disable, or run one Graph turn', + help: 'Show commands and keybindings', + mcp: 'Manage client-owned MCP servers and tool publication status', + model: 'Select model', + move: 'Move current session to another directory', + new: 'Start a new session', + permissions: 'Set session permissions', + recap: 'One-sentence recap of the session so far', + rename: 'Rename current session', + resume: 'Resume latest interrupted run at a safe boundary', + rewind: 'Rewind to an earlier turn', + session: 'Resume session', + setup: 'Set up a model provider', + side: 'Open a temporary side conversation', + skill: 'Invoke a skill (or type /skill: inline)', + swarm: 'Show, enable, disable, or run one Swarm turn', + thinking: 'Set thinking level', + todo: 'Show the current session Todo list', + transcript: 'Browse the full transcript inside Maka', + }, + help: { + commandsHeading: 'Commands', + userCommand: ' ! — run one shell command visible only to you', + keybindingsHeading: 'Keybindings', + keybindings: [ + ' Ctrl+O — open detailed transcript (Ctrl+E toggles details inside)', + ' Ctrl+T — expand or collapse all thinking in view', + ' Scroll the transcript with your terminal or trackpad', + ' Enter (during a turn) — steer: inject a message into the running turn', + ' Tab (during a turn) — queue a message for the next turn', + ' Shift+← — take queued messages back into the editor to re-edit', + ' Esc Esc (during a turn) — interrupt the turn', + ' Esc Esc (when idle) — rewind to an earlier turn', + ' Ctrl+C — stop the turn, clear input, or press twice to exit', + ' Ctrl+D — exit when input is empty', + ], + }, +}; + +const TUI_COPY_RESOURCES_PICKERS_EN = { + modelPickerTitle: 'Select Model', + modelSwitchCacheWarning: + '\u26a0 Switching models may rebuild the prompt cache; the next request may be slower or cost more.', + modelSearchHint: + 'Search models / providers / connections · ↑↓ select · Enter confirm · Esc cancel', + searchLabel: 'Search', + noMatchingModels: 'No matching models', + resumeSessionTitle: 'Resume Session', + sessionScopeCurrent: 'Current', + sessionScopeAll: 'All', + sessionSearchHint: + 'Search name / id / path / model / connection · Tab scope · ↑↓ select · Enter open · Esc close', + noMatchingSessions: 'No matching sessions', + resumeCandidateCheckFailed: 'Could not check whether any sessions can be resumed.', + resumeAvailabilityNotice: + 'An interrupted run is available — use /resume to continue from a safe boundary.', + resumeStartingNotice: 'Resuming from the latest safe boundary…', + resumeUnavailableNotice: 'Safe-boundary resume is unavailable on this runtime.', + resumeCatalogIncompleteNotice: + 'Session discovery stopped before the full catalog was checked. Showing the sessions found so far.', + selectPickerHint: '↑↓ select · Enter confirm · Esc close', + providerConfigured: 'configured', + addAccount: 'add account', + thinkingLevels: { + off: 'Off', + minimal: 'Minimal', + low: 'Low', + medium: 'Medium', + high: 'High', + xhigh: 'Extra high', + max: 'Maximum', + }, + defaultThinkingLevel: 'Default', + thinkingPickerTitle: 'Select Thinking Level', + currentMarker: 'current', + defaultMarker: 'default', + setupTitle: 'Set Up Provider', + baseUrlLabel: 'Base URL', + apiKeyLabel: 'API key', + connectionNameLabel: 'Name', + connectionSlugLabel: 'Slug', + identityHint: + 'Name this connection, or keep the defaults to auto-assign · Enter continues · Esc returns', + identitySlugInvalid: 'Slug must be 2–64 characters: lowercase letters, digits, and hyphens', + identityNameTooLong: 'Name must be 256 characters or fewer', + onboardingUnavailable: + 'Onboarding is unavailable: this environment does not provide a setup surface.', + onboardingRequestFailed: 'Could not reach the Runtime Host. Try again.', + onboardingRejections: { + provider_unsupported: 'This provider does not support API-key onboarding.', + connection_not_found: 'This connection no longer exists. Reopen /setup and try again.', + credential_not_configured: 'An API key is required.', + base_url_not_configured: 'A Base URL is required for this provider.', + catalog_full: 'The connection catalog is full.', + model_unavailable: 'A selected model is no longer available. Verify and select again.', + slug_taken: 'That slug is already taken. Pick another, or clear it to auto-assign.', + superseded: 'This connection changed. Reopen /setup and try again.', + }, + onboardingFailures: { + auth: 'Provider authentication failed. Check the API key and try again.', + timeout: 'Provider verification timed out. Try again.', + provider_unavailable: 'The provider is unavailable. Try again later.', + network: 'A network error interrupted provider verification. Try again.', + invalid_response: 'The provider returned an invalid response. Try again.', + unknown: 'Provider verification failed. Try again.', + }, + oauthFailures: { + capability_unavailable: 'This client cannot present OAuth sign-in.', + authorization_failed: 'Authorization did not complete. Try again.', + provider_rejected: 'The provider rejected this account.', + slug_taken: 'That slug is already taken. Pick another, or clear it to auto-assign.', + credential_changed: 'The credential changed during sign-in. Try again.', + connection_changed: 'The connection changed during sign-in. Reopen /setup.', + persistence_failed: 'The signed-in account could not be saved. Try again.', + internal_failure: 'OAuth sign-in failed. Try again.', + connection_not_found: 'This connection no longer exists. Reopen /setup.', + operation_conflict: 'Another OAuth login is active, or this identity is unavailable.', + unavailable: 'Could not reach the Runtime Host. Try again.', + }, + accountSavedRefreshFailed: + 'Account saved, but the model list has not refreshed. Restart Maka to reload it.', + listProvidersFailed: 'Could not read configured connections.', + noConfigurableProviders: 'No configurable providers are available.', + baseUrlRequired: 'Enter a Base URL', + baseUrlInvalid: 'Base URL is not a valid URL', + baseUrlProtocol: 'Base URL must use http or https', + baseUrlCredentials: 'Base URL cannot include credentials', + baseUrlQuery: 'Base URL cannot include a query or fragment', + baseUrlTooLong: 'Base URL cannot exceed 2048 bytes', + selectModelBeforeSaving: 'Select at least one model before saving', + reuseBaseUrlHint: + 'Leave blank to reuse the saved Base URL, or enter a replacement · Esc returns to provider selection', + enterBaseUrlHint: 'Enter the relay Base URL (http/https) · Esc returns to provider selection', + continueAction: 'Enter to continue', + providerSearchHint: 'Search providers · ↑↓ select · Enter confirm · Esc cancel', + noMatchingProviders: 'No matching providers', + keyHints: { + reuse: { + baseUrl: + 'Leave blank to reuse the saved key, or enter a new key to rotate it · Esc returns to Base URL', + provider: + 'Leave blank to reuse the saved key, or enter a new key to rotate it · Esc returns to provider selection', + // The identity step only exists for create targets, so a `reuse` + // hint never lands here; the provider text keeps the record total. + identity: + 'Leave blank to reuse the saved key, or enter a new key to rotate it · Esc returns to provider selection', }, - 'zh-TW': { - unavailable: 'Runtime Host 擁有者控制無法使用。', - usage: '用法:/host [status|stop|restart] 或 /update [latest|next|version]', - pickerTitle: 'Runtime Host 擁有者操作', - cancel: '取消', - cancelDescription: '保持此 Host 及其工作執行', - safe: '安全交接', - safeDescription: '僅在已驗證的安全邊界繼續', - interrupt: '中斷進行中的工作', - interruptDescription: '可能停止所有已連線介面的工作', - pending: '已有 Host 擁有者操作等待執行。', - disconnected: 'TUI 已中斷連線。', - foreign: '此 TUI 連線的是其他 Host。請在該 Host 上使用其擁有者控制;/exit 僅中斷連線。', - notOwner: - '此 npm-global 安裝不是本機 Host 的擁有者。請使用實際擁有者的控制工具;/exit 僅中斷連線。', - hostChanged: '本機 Host 已變更,或需要由其託管服務控制工具操作。', - actionSummary: '{action}:{name}', - action_stop: '停止', - action_restart: '重新啟動', - action_update: '更新', - owner: '擁有者:{owner}', - root: '根識別碼:{root}', - epoch: '執行個體識別碼:{epoch}', - composition: '執行組成:{composition}({revision})', - activity: '連線數:{connections};操作數:{operations};常駐工作:{residencies}', - unknownActivity: '活動狀態未知。其他介面和自主工作可能受到影響。', - updateSafeOnly: '更新僅支援安全交接。', - invalidTarget: '更新目標必須為 latest、next 或確切的發行版本。', - updateRequiresSafe: 'TUI 更新僅使用安全交接。更新前請先明確停止進行中的工作。', - installationChanged: 'npm-global 安裝已變更。請重新開啟 Maka 後重試。', - ownerChanged: '本機 Host 的安裝擁有者已變更。請重新開啟 Maka 後重試。', - retirementHostChanged: '停止舊執行個體前 Host 已變更;未中斷替代執行個體。', - activityChanged: '確認後 Host 活動已變更。請重新開啟 Maka,確認新的工作後重試。', - stillActive: - 'Host 仍有進行中的工作。請重新開啟 Maka 並重試 /host {action};中斷需要重新確認。', - restartFailed: 'Host 重新啟動未完成:{detail}', - status: '{name}:{state}', - state_starting: '啟動中', - state_containing: '故障隔離中', - state_recovering: '復原中', - state_ready: '就緒', - state_draining: '排空中', - localOwnerHint: '僅當此 npm-global 安裝擁有該 Host 時才能使用擁有者控制。/exit 僅中斷連線。', - attachedOwnerHint: '已連線的 Host:生命週期變更需由其擁有者操作。/exit 僅中斷連線。', + enter: { + baseUrl: 'Enter an API key · stored only on this machine · Esc returns to Base URL', + provider: + 'Enter an API key · stored only on this machine · Esc returns to provider selection', + identity: 'Enter an API key · stored only on this machine · Esc returns to connection naming', }, }, - copy: { - en: { - copiedLast: - 'Sent the last reply to the terminal via OSC 52 · {count, plural, one {# char} other {# chars}} — it reaches the clipboard only if your terminal (and tmux, if used) allows OSC 52 writes.', - copiedAll: - 'Sent the conversation to the terminal via OSC 52 · {count, plural, one {# char} other {# chars}} — it reaches the clipboard only if your terminal (and tmux, if used) allows OSC 52 writes.', - tooLarge: - 'Too large to copy over OSC 52 · {bytes} bytes exceeds the {limit}-byte limit; a larger payload is silently dropped by the terminal.', - nothingToCopy: 'Nothing to copy yet.', - usage: 'Usage: /copy [all]', - roleUser: 'You:', - roleAssistant: 'Maka:', - roleGoalContinuation: 'Goal continuation (autonomous):', - roleLegacyAutomation: 'Legacy automation (history only):', + submitAction: 'Enter to submit', + verifyingKey: 'Verifying key…', + oauthHint: 'Open the device page and enter the sign-in code · Esc returns', + oauthBackHint: 'Esc returns to provider selection', + oauthCodeLabel: 'Sign-in code', + oauthStarting: 'Preparing sign-in…', + oauthWaiting: 'Waiting for browser authorization…', + oauthCancelling: 'Cancelling sign-in…', + oauthUnconfirmed: 'Sign-in result is not confirmed; it may have completed', + oauthRecheckAction: 'Enter checks the sign-in result · Esc closes setup', + oauthLoadingModels: 'Signed in · Loading models…', + oauthContinueToModels: 'Signed in · Enter to continue to models', + oauthRetryAction: 'Enter retry · Esc returns', + oauthRetryModelsAction: 'Signed in · Enter retries model loading', + modelSelectionHint: 'Search models · ↑↓ select · Space toggle · Enter save · Esc back', + selectedModels: '{count} selected', + selectedModelsAndSave: '{count} selected · Enter to save', + saving: 'Saving…', + complete: 'Complete', + enabledModels: 'Models enabled: {count}', + closeAction: 'Enter to close', + thinkingUnsupported: 'This model does not support changing the thinking level.', +}; + +const TUI_COPY_RESOURCES_FORM_INTERACTION_EN = { + requestedBy: 'Requested by {detail}', + sensitiveWarning: 'Do not enter passwords, API keys, access tokens, or payment details.', + required: 'required', + optional: 'optional', + omitted: 'omitted', + empty: 'empty', + invalid: "Value does not meet this field's constraints", + minimumLength: 'At least {minimum} characters', + maximumLength: 'At most {maximum} characters', + lengthRange: '{minimum}–{maximum} characters', + minimumValue: 'Minimum {minimum}', + maximumValue: 'Maximum {maximum}', + valueRange: 'Range {minimum}–{maximum}', + minimumItems: 'Select at least {minimum}', + maximumItems: 'Select at most {maximum}', + itemRange: 'Select {minimum}–{maximum}', + format: 'Format: {format}', + reviewHint: '↑↓ field · Enter edit · Space include/omit · s submit · d decline · Esc cancel', + textHint: 'Type a value · Enter review · Esc review', + choiceHint: '↑↓ select · Enter review · Esc review', + multiHint: '↑↓ move · Space toggle · Enter review · Esc review', + trueValue: 'true', + falseValue: 'false', + selectedCount: '{count} selected', +}; + +const TUI_COPY_RESOURCES_MCP_STATUS_EN = { + title: 'MCP SERVERS', + footer: { + back: 'Esc back', + diagnostic: '↑/↓ scroll · Esc back', + readOnly: '↑/↓ scroll · q/Esc close', + manage: + 'a Add · Enter Edit · Space Enable/disable · t Test · r Reconnect · d Remove · Esc Close', + managePublication: + 'a Add · Enter Edit · Space Enable/disable · t Test · r Reconnect · d Remove · p Set provider credential · x Remove credential · Esc Close', + }, + unavailableTitle: 'This TUI is not connected to a local MCP control plane.', + unavailableDetail: + 'Client MCP tool association for remote Runtime Hosts is planned for a later release.', + loading: 'Loading mcp.json and discovering tools…', + loadError: 'MCP configuration could not be loaded; no tools were published to the Runtime Host.', + invalidConfigFile: + 'Invalid JSON in {path}. The file is unchanged. Quit maka, back up and repair this file before retrying.', + noServers: 'No MCP servers are configured. Press a to add one.', + publication: { + waiting: 'waiting to publish', + host_unavailable: 'Runtime Host reconnecting', + credential_required: 'provider credential required', + credential_rejected: 'provider credential rejected', + provider_conflict: 'provider active in another TUI', + target_mismatch: 'provider target mismatch', + publishing: 'publishing', + published: 'published', + not_published: 'not published', + error: 'publication failed', + }, + serverState: { + disabled: 'disabled', + disconnected: 'disconnected', + connecting: 'connecting', + connected: 'connected', + 'needs-auth': 'needs-auth', + error: 'error', + }, + 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', + 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…', }, - 'zh-CN': { - copiedLast: - '已将最后一条回复发送到终端 · {count} 个字符——仅当终端(以及 tmux,如使用)允许写入剪贴板时才会真正复制。', - copiedAll: - '已将整段对话发送到终端 · {count} 个字符——仅当终端(以及 tmux,如使用)允许写入剪贴板时才会真正复制。', - tooLarge: - '内容过大,无法复制 · {bytes} 字节超过 {limit} 字节上限;更大的内容会被终端静默丢弃。', - nothingToCopy: '暂无可复制的内容。', - usage: '用法:/copy [all]', - roleUser: '你:', - roleAssistant: 'Maka:', - roleGoalContinuation: '目标续跑(自主):', - roleLegacyAutomation: '旧版自动化(仅历史):', + 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', }, - 'zh-TW': { - copiedLast: - '已將最後一則回覆傳送至終端機 · {count} 個字元——僅當終端機(以及 tmux,如有使用)允許寫入剪貼簿時才會真正複製。', - copiedAll: - '已將整段對話傳送至終端機 · {count} 個字元——僅當終端機(以及 tmux,如有使用)允許寫入剪貼簿時才會真正複製。', - tooLarge: - '內容過大,無法複製 · {bytes} 位元組超過 {limit} 位元組上限;更大的內容會被終端機直接捨棄。', - nothingToCopy: '目前沒有可複製的內容。', - usage: '用法:/copy [all]', - roleUser: '你:', - roleAssistant: 'Maka:', - roleGoalContinuation: 'Goal 繼續執行(自主):', - roleLegacyAutomation: '舊版自動化(僅歷史):', + 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.', + 'commit-unknown': + 'Configuration published; crash durability is uncertain. Check synchronization before retrying.', + '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.', }, }, - 'mcp-status': { - en: { - title: 'MCP SERVERS', - footer: { - back: 'Esc back', - diagnostic: '↑/↓ scroll · Esc back', - readOnly: '↑/↓ scroll · q/Esc close', - manage: - 'a Add · Enter Edit · Space Enable/disable · t Test · r Reconnect · d Remove · Esc Close', - managePublication: - 'a Add · Enter Edit · Space Enable/disable · t Test · r Reconnect · d Remove · p Set provider credential · x Remove credential · Esc Close', - }, - unavailableTitle: 'This TUI is not connected to a local MCP control plane.', - unavailableDetail: - 'Client MCP tool association for remote Runtime Hosts is planned for a later release.', - loading: 'Loading mcp.json and discovering tools…', - loadError: - 'MCP configuration could not be loaded; no tools were published to the Runtime Host.', - invalidConfigFile: - 'Invalid JSON in {path}. The file is unchanged. Quit maka, back up and repair this file before retrying.', - noServers: 'No MCP servers are configured. Press a to add one.', - publication: { - waiting: 'waiting to publish', - host_unavailable: 'Runtime Host reconnecting', - credential_required: 'provider credential required', - credential_rejected: 'provider credential rejected', - provider_conflict: 'provider active in another TUI', - target_mismatch: 'provider target mismatch', - publishing: 'publishing', - published: 'published', - not_published: 'not published', - error: 'publication failed', +}; + +const TUI_COPY_RESOURCES_COPY_EN = { + copiedLast: + 'Sent the last reply to the terminal via OSC 52 · {count, plural, one {# char} other {# chars}} — it reaches the clipboard only if your terminal (and tmux, if used) allows OSC 52 writes.', + copiedAll: + 'Sent the conversation to the terminal via OSC 52 · {count, plural, one {# char} other {# chars}} — it reaches the clipboard only if your terminal (and tmux, if used) allows OSC 52 writes.', + tooLarge: + 'Too large to copy over OSC 52 · {bytes} bytes exceeds the {limit}-byte limit; a larger payload is silently dropped by the terminal.', + nothingToCopy: 'Nothing to copy yet.', + usage: 'Usage: /copy [all]', + roleUser: 'You:', + roleAssistant: 'Maka:', + roleGoalContinuation: 'Goal continuation (autonomous):', + roleLegacyAutomation: 'Legacy automation (history only):', +}; + +const TUI_COPY_RESOURCES_HOST_OWNER_EN = { + unavailable: 'Runtime Host owner controls are unavailable.', + usage: 'Usage: /host [status|stop|restart] or /update [latest|next|version]', + pickerTitle: 'Runtime Host owner action', + cancel: 'Cancel', + cancelDescription: 'Keep this Host and its work running', + safe: 'Safe handoff', + safeDescription: 'Continue only at a verified safe boundary', + interrupt: 'Interrupt active work', + interruptDescription: 'May stop work from every attached Surface', + pending: 'A Host owner action is already pending.', + disconnected: 'The TUI has already disconnected.', + foreign: + 'This TUI is attached to another Host. Run its owner controls on that Host; /exit only disconnects.', + notOwner: + 'This npm-global installation does not own the local Host. Use its actual operator; /exit only disconnects.', + hostChanged: 'The local Host changed or requires its managed-service operator.', + actionSummary: '{action}: {name}', + action_stop: 'Stop', + action_restart: 'Restart', + action_update: 'Update', + owner: 'Owner: {owner}', + root: 'Root: {root}', + epoch: 'Epoch: {epoch}', + composition: 'Composition: {composition} ({revision})', + activity: 'Connections: {connections}; operations: {operations}; residencies: {residencies}', + unknownActivity: 'Activity is unknown. Other Surfaces and autonomous tasks may be affected.', + updateSafeOnly: 'Update supports safe handoff only.', + invalidTarget: 'Update target must be latest, next, or an exact release version.', + updateRequiresSafe: + 'TUI update uses safe handoff only. Stop active work explicitly before updating.', + installationChanged: 'The npm-global installation changed. Reopen Maka before retrying.', + ownerChanged: 'The local Host installation owner changed. Reopen Maka before retrying.', + retirementHostChanged: + 'The Host changed before owner retirement; no replacement was interrupted.', + activityChanged: + 'Host activity changed after confirmation. Reopen Maka and confirm the new work before retrying.', + stillActive: + 'The Host still has active work. Reopen Maka and retry /host {action}; interruption requires a new confirmation.', + restartFailed: 'Host restart did not complete: {detail}', + status: '{name}: {state}', + state_starting: 'Starting', + state_containing: 'Containing', + state_recovering: 'Recovering', + state_ready: 'Ready', + state_draining: 'Draining', + localOwnerHint: + 'Owner controls require this npm-global installation to own the Host. /exit only disconnects.', + attachedOwnerHint: + 'Attached Host: lifecycle changes require its operator. /exit only disconnects.', +}; + +const TUI_COPY_RESOURCES_TRANSCRIPT_READER_EN = { + title: 'DETAILED TRANSCRIPT', + scope: 'loaded history', + hint: 'Esc/Ctrl+O close · Ctrl+E details · / search · ↑↓/PgUp/PgDn · Home/End', + matches: 'matches in displayed text', + back: 'return', +}; + +const TUI_COPY_RESOURCES_TODO_EN = { + title: 'Current Todo', + markedComplete: 'marked complete', + open: '/todo to view', + usage: 'Usage: /todo', + progress: 'Todo', + unavailable: 'Todo unavailable', + empty: 'No Todo items', + loading: 'Loading Todo…', + hint: '↑↓/PgUp/PgDn scroll · Home/End · Esc close', +}; + +export const TUI_COPY_RESOURCES = { + todo: resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_TODO_EN, + 'zh-CN': { + title: '当前待办', + markedComplete: '标记完成', + open: '/todo 查看', + usage: '用法:/todo', + progress: '待办', + unavailable: '待办不可用', + empty: '暂无待办', + loading: '正在加载待办…', + hint: '↑↓/PgUp/PgDn 滚动 · Home/End · Esc 关闭', }, - serverState: { - disabled: 'disabled', - disconnected: 'disconnected', - connecting: 'connecting', - connected: 'connected', - 'needs-auth': 'needs-auth', - error: 'error', + 'zh-TW': { + title: '目前待辦', + markedComplete: '標記完成', + open: '/todo 查看', + usage: '用法:/todo', + progress: '待辦', + unavailable: '待辦不可用', + empty: '沒有待辦', + loading: '正在載入待辦…', + hint: '↑↓/PgUp/PgDn 捲動 · Home/End · Esc 關閉', }, - 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', - 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', + }), + ), + 'transcript-reader': resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()( + { + en: TUI_COPY_RESOURCES_TRANSCRIPT_READER_EN, + 'zh-CN': { + title: '详细记录', + scope: '已加载历史', + hint: 'Esc/Ctrl+O 返回 · Ctrl+E 详情 · / 搜索 · ↑↓/PgUp/PgDn · Home/End', + matches: '处匹配(当前显示内容)', + 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.', - 'commit-unknown': - 'Configuration published; crash durability is uncertain. Check synchronization before retrying.', - '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-TW': { + title: '詳細記錄', + scope: '已載入歷史', + hint: 'Esc/Ctrl+O 返回 · Ctrl+E 詳情 · / 搜尋 · ↑↓/PgUp/PgDn · Home/End', + matches: '處符合(目前顯示內容)', + back: '回到原位置', }, }, - }, - 'zh-CN': { - title: 'MCP 服务器', - footer: { - back: 'Esc 返回', - diagnostic: '↑/↓ 滚动 · Esc 返回', - readOnly: '↑/↓ 滚动 · q/Esc 关闭', - manage: 'a 添加 · Enter 编辑 · Space 启用/停用 · t 测试 · r 重连 · d 删除 · Esc 关闭', - managePublication: - 'a 添加 · Enter 编辑 · Space 启用/停用 · t 测试 · r 重连 · d 删除 · p 设置 Provider 凭据 · x 删除凭据 · Esc 关闭', + ), + ), + 'host-owner': resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_HOST_OWNER_EN, + 'zh-CN': { + unavailable: 'Runtime Host 所有者控制不可用。', + usage: '用法:/host [status|stop|restart] 或 /update [latest|next|version]', + pickerTitle: 'Runtime Host 所有者操作', + cancel: '取消', + cancelDescription: '保持此 Host 及其任务运行', + safe: '安全交接', + safeDescription: '仅在已验证的安全边界继续', + interrupt: '中断活动任务', + interruptDescription: '可能停止所有已连接界面的任务', + pending: '已有 Host 所有者操作等待执行。', + disconnected: 'TUI 已断开连接。', + foreign: '此 TUI 连接的是其他 Host。请在该 Host 上使用其所有者控制;/exit 仅断开连接。', + notOwner: + '此 npm-global 安装不是本地 Host 的所有者。请使用实际所有者的控制工具;/exit 仅断开连接。', + hostChanged: '本地 Host 已变更,或需要由其托管服务控制工具操作。', + actionSummary: '{action}:{name}', + action_stop: '停止', + action_restart: '重启', + action_update: '更新', + owner: '所有者:{owner}', + root: '根标识:{root}', + epoch: '实例标识:{epoch}', + composition: '运行组成:{composition}({revision})', + activity: '连接数:{connections};操作数:{operations};常驻任务:{residencies}', + unknownActivity: '活动状态未知。其他界面和自主任务可能受到影响。', + updateSafeOnly: '更新仅支持安全交接。', + invalidTarget: '更新目标必须为 latest、next 或确切的发布版本。', + updateRequiresSafe: 'TUI 更新仅使用安全交接。更新前请先显式停止活动任务。', + installationChanged: 'npm-global 安装已变更。请重新打开 Maka 后重试。', + ownerChanged: '本地 Host 的安装所有者已变更。请重新打开 Maka 后重试。', + retirementHostChanged: '停止旧实例前 Host 已变更;未中断替代实例。', + activityChanged: '确认后 Host 活动已变更。请重新打开 Maka,确认新的任务后重试。', + stillActive: 'Host 仍有活动任务。请重新打开 Maka 并重试 /host {action};中断需要重新确认。', + restartFailed: 'Host 重启未完成:{detail}', + status: '{name}:{state}', + state_starting: '启动中', + state_containing: '故障隔离中', + state_recovering: '恢复中', + state_ready: '就绪', + state_draining: '排空中', + localOwnerHint: + '仅当此 npm-global 安装拥有该 Host 时才能使用所有者控制。/exit 仅断开连接。', + attachedOwnerHint: '已连接的 Host:生命周期变更需由其所有者操作。/exit 仅断开连接。', + }, + 'zh-TW': { + unavailable: 'Runtime Host 擁有者控制無法使用。', + usage: '用法:/host [status|stop|restart] 或 /update [latest|next|version]', + pickerTitle: 'Runtime Host 擁有者操作', + cancel: '取消', + cancelDescription: '保持此 Host 及其工作執行', + safe: '安全交接', + safeDescription: '僅在已驗證的安全邊界繼續', + interrupt: '中斷進行中的工作', + interruptDescription: '可能停止所有已連線介面的工作', + pending: '已有 Host 擁有者操作等待執行。', + disconnected: 'TUI 已中斷連線。', + foreign: '此 TUI 連線的是其他 Host。請在該 Host 上使用其擁有者控制;/exit 僅中斷連線。', + notOwner: + '此 npm-global 安裝不是本機 Host 的擁有者。請使用實際擁有者的控制工具;/exit 僅中斷連線。', + hostChanged: '本機 Host 已變更,或需要由其託管服務控制工具操作。', + actionSummary: '{action}:{name}', + action_stop: '停止', + action_restart: '重新啟動', + action_update: '更新', + owner: '擁有者:{owner}', + root: '根識別碼:{root}', + epoch: '執行個體識別碼:{epoch}', + composition: '執行組成:{composition}({revision})', + activity: '連線數:{connections};操作數:{operations};常駐工作:{residencies}', + unknownActivity: '活動狀態未知。其他介面和自主工作可能受到影響。', + updateSafeOnly: '更新僅支援安全交接。', + invalidTarget: '更新目標必須為 latest、next 或確切的發行版本。', + updateRequiresSafe: 'TUI 更新僅使用安全交接。更新前請先明確停止進行中的工作。', + installationChanged: 'npm-global 安裝已變更。請重新開啟 Maka 後重試。', + ownerChanged: '本機 Host 的安裝擁有者已變更。請重新開啟 Maka 後重試。', + retirementHostChanged: '停止舊執行個體前 Host 已變更;未中斷替代執行個體。', + activityChanged: '確認後 Host 活動已變更。請重新開啟 Maka,確認新的工作後重試。', + stillActive: + 'Host 仍有進行中的工作。請重新開啟 Maka 並重試 /host {action};中斷需要重新確認。', + restartFailed: 'Host 重新啟動未完成:{detail}', + status: '{name}:{state}', + state_starting: '啟動中', + state_containing: '故障隔離中', + state_recovering: '復原中', + state_ready: '就緒', + state_draining: '排空中', + localOwnerHint: + '僅當此 npm-global 安裝擁有該 Host 時才能使用擁有者控制。/exit 僅中斷連線。', + attachedOwnerHint: '已連線的 Host:生命週期變更需由其擁有者操作。/exit 僅中斷連線。', }, - unavailableTitle: '当前 TUI 未连接本地 MCP 控制面。', - unavailableDetail: '远程 Runtime Host 的客户端 MCP 工具关联将在后续版本提供。', - loading: '正在读取 mcp.json 并发现工具…', - loadError: '无法读取或应用 MCP 配置;没有向 Runtime Host 发布工具。', - invalidConfigFile: - '{path} 中的 JSON 无效,文件未被修改。请退出 maka,备份并修复此文件后重试。', - 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: '发布失败', + }), + ), + copy: resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_COPY_EN, + 'zh-CN': { + copiedLast: + '已将最后一条回复发送到终端 · {count} 个字符——仅当终端(以及 tmux,如使用)允许写入剪贴板时才会真正复制。', + copiedAll: + '已将整段对话发送到终端 · {count} 个字符——仅当终端(以及 tmux,如使用)允许写入剪贴板时才会真正复制。', + tooLarge: + '内容过大,无法复制 · {bytes} 字节超过 {limit} 字节上限;更大的内容会被终端静默丢弃。', + nothingToCopy: '暂无可复制的内容。', + usage: '用法:/copy [all]', + roleUser: '你:', + roleAssistant: 'Maka:', + roleGoalContinuation: '目标续跑(自主):', + roleLegacyAutomation: '旧版自动化(仅历史):', }, - serverState: { - disabled: '已停用', - disconnected: '未连接', - connecting: '连接中', - connected: '已连接', - 'needs-auth': '需要登录', - error: '错误', + 'zh-TW': { + copiedLast: + '已將最後一則回覆傳送至終端機 · {count} 個字元——僅當終端機(以及 tmux,如有使用)允許寫入剪貼簿時才會真正複製。', + copiedAll: + '已將整段對話傳送至終端機 · {count} 個字元——僅當終端機(以及 tmux,如有使用)允許寫入剪貼簿時才會真正複製。', + tooLarge: + '內容過大,無法複製 · {bytes} 位元組超過 {limit} 位元組上限;更大的內容會被終端機直接捨棄。', + nothingToCopy: '目前沒有可複製的內容。', + usage: '用法:/copy [all]', + roleUser: '你:', + roleAssistant: 'Maka:', + roleGoalContinuation: 'Goal 繼續執行(自主):', + roleLegacyAutomation: '舊版自動化(僅歷史):', }, - 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 配置…', + }), + ), + 'mcp-status': resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_MCP_STATUS_EN, + 'zh-CN': { + title: 'MCP 服务器', + footer: { + back: 'Esc 返回', + diagnostic: '↑/↓ 滚动 · Esc 返回', + readOnly: '↑/↓ 滚动 · q/Esc 关闭', + manage: 'a 添加 · Enter 编辑 · Space 启用/停用 · t 测试 · r 重连 · d 删除 · Esc 关闭', + managePublication: + 'a 添加 · Enter 编辑 · Space 启用/停用 · t 测试 · r 重连 · d 删除 · p 设置 Provider 凭据 · x 删除凭据 · Esc 关闭', }, - inputLabels: { - server_id: '服务器 ID', - command: '命令', - args: '参数', - url: 'Streamable HTTP URL', - cwd: '工作目录', - env: '环境变量', - headers: '请求头', - edit: '编辑服务器 JSON', - import: '粘贴 MCP JSON', - publication_credential: 'Provider 凭据', + unavailableTitle: '当前 TUI 未连接本地 MCP 控制面。', + unavailableDetail: '远程 Runtime Host 的客户端 MCP 工具关联将在后续版本提供。', + loading: '正在读取 mcp.json 并发现工具…', + loadError: '无法读取或应用 MCP 配置;没有向 Runtime Host 发布工具。', + invalidConfigFile: + '{path} 中的 JSON 无效,文件未被修改。请退出 maka,备份并修复此文件后重试。', + 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: '发布失败', }, - hints: { - args: 'JSON string array, 可留空', - map: 'JSON string map, 可留空', - optional: '可留空', - submit: 'Enter 提交 · Esc 返回', - credential: '仅保存到本机凭据存储;不会写入 profile、参数或对话 · Enter 提交 · Esc 返回', + serverState: { + disabled: '已停用', + disconnected: '未连接', + connecting: '连接中', + connected: '已连接', + 'needs-auth': '需要登录', + error: '错误', }, - results: { - exists: '该服务器 ID 已存在。', - stale_config: 'MCP 配置已变化,请重试。', - stale_edit: '该服务器已变化,请重新打开后编辑。', - stale_import: '导入项已变化,请重新预览。', - missing: '该服务器已不存在。', - closed: 'MCP 控制器已关闭。', - 'invalid-config': '服务器配置无效。', - 'credential-cleanup-failed': '无法删除旧凭据,配置未修改。', - 'publication-credential-failed': '无法保存或应用 Provider 凭据。', - 'persist-failed': '无法保存配置。', - 'commit-unknown': '配置已发布,但无法确认崩溃后的持久性。请先检查同步状态再重试。', - '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 发布。', + 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 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': '无法保存配置。', + 'commit-unknown': '配置已发布,但无法确认崩溃后的持久性。请先检查同步状态再重试。', + '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 发布。', + }, }, }, - }, - 'zh-TW': { - title: 'MCP 伺服器', - footer: { - back: 'Esc 返回', - diagnostic: '↑/↓ 捲動 · 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 發佈任何工具。', - invalidConfigFile: - '{path} 中的 JSON 無效,檔案未被修改。請退出 maka,備份並修復此檔案後重試。', - 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 設定…', + 'zh-TW': { + title: 'MCP 伺服器', + footer: { + back: 'Esc 返回', + diagnostic: '↑/↓ 捲動 · Esc 返回', + readOnly: '↑/↓ 捲動 · q/Esc 關閉', + manage: 'a 新增 · Enter 編輯 · Space 啟用/停用 · t 測試 · r 重新連線 · d 刪除 · Esc 關閉', + managePublication: + 'a 新增 · Enter 編輯 · Space 啟用/停用 · t 測試 · r 重新連線 · d 刪除 · p 設定 Provider 認證資料 · x 刪除認證資料 · Esc 關閉', }, - inputLabels: { - server_id: '伺服器 ID', - command: '命令', - args: '參數', - url: 'Streamable HTTP URL', - cwd: '工作目錄', - env: '環境變數', - headers: '請求標頭', - edit: '編輯伺服器 JSON', - import: '貼上 MCP JSON', - publication_credential: 'Provider 認證資料', + unavailableTitle: '目前 TUI 未連線至本機 MCP 控制介面。', + unavailableDetail: '遠端 Runtime Host 的用戶端 MCP 工具關聯將於後續版本提供。', + loading: '正在讀取 mcp.json 並探索工具…', + loadError: '無法讀取或套用 MCP 設定;未向 Runtime Host 發佈任何工具。', + invalidConfigFile: + '{path} 中的 JSON 無效,檔案未被修改。請退出 maka,備份並修復此檔案後重試。', + 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: '發佈失敗', }, - hints: { - args: 'JSON 字串陣列,可留空', - map: 'JSON 字串對映,可留空', - optional: '可留空', - submit: 'Enter 送出 · Esc 返回', - credential: - '僅儲存於本機認證資料存放區;不會寫入設定檔、參數或聊天內容 · Enter 送出 · Esc 返回', + serverState: { + disabled: '已停用', + disconnected: '未連線', + connecting: '連線中', + connected: '已連線', + 'needs-auth': '需要登入', + error: '錯誤', }, - results: { - exists: '該伺服器 ID 已存在。', - stale_config: 'MCP 設定已變更,請重試。', - stale_edit: '該伺服器已變更,請重新開啟後編輯。', - stale_import: '匯入項目已變更,請重新預覽。', - missing: '該伺服器已不存在。', - closed: 'MCP 控制器已關閉。', - 'invalid-config': '伺服器設定無效。', - 'credential-cleanup-failed': '無法移除已儲存的認證資料;設定未變更。', - 'publication-credential-failed': '無法儲存或套用 Provider 認證資料。', - 'persist-failed': '無法儲存設定。', - 'commit-unknown': '設定已發布,但無法確認當機後的持久性。請先檢查同步狀態再重試。', - '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 發佈。', + 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': '無法儲存設定。', + 'commit-unknown': '設定已發布,但無法確認當機後的持久性。請先檢查同步狀態再重試。', + '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: { - requestedBy: 'Requested by {detail}', - sensitiveWarning: 'Do not enter passwords, API keys, access tokens, or payment details.', - required: 'required', - optional: 'optional', - omitted: 'omitted', - empty: 'empty', - invalid: "Value does not meet this field's constraints", - minimumLength: 'At least {minimum} characters', - maximumLength: 'At most {maximum} characters', - lengthRange: '{minimum}–{maximum} characters', - minimumValue: 'Minimum {minimum}', - maximumValue: 'Maximum {maximum}', - valueRange: 'Range {minimum}–{maximum}', - minimumItems: 'Select at least {minimum}', - maximumItems: 'Select at most {maximum}', - itemRange: 'Select {minimum}–{maximum}', - format: 'Format: {format}', - reviewHint: '↑↓ field · Enter edit · Space include/omit · s submit · d decline · Esc cancel', - textHint: 'Type a value · Enter review · Esc review', - choiceHint: '↑↓ select · Enter review · Esc review', - multiHint: '↑↓ move · Space toggle · Enter review · Esc review', - trueValue: 'true', - falseValue: 'false', - selectedCount: '{count} selected', - }, - 'zh-CN': { - 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} 项', - }, - '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: { - modelPickerTitle: 'Select Model', - modelSwitchCacheWarning: - '\u26a0 Switching models may rebuild the prompt cache; the next request may be slower or cost more.', - modelSearchHint: - 'Search models / providers / connections · ↑↓ select · Enter confirm · Esc cancel', - searchLabel: 'Search', - noMatchingModels: 'No matching models', - resumeSessionTitle: 'Resume Session', - sessionScopeCurrent: 'Current', - sessionScopeAll: 'All', - sessionSearchHint: - 'Search name / id / path / model / connection · Tab scope · ↑↓ select · Enter open · Esc close', - noMatchingSessions: 'No matching sessions', - resumeCandidateCheckFailed: 'Could not check whether any sessions can be resumed.', - resumeAvailabilityNotice: - 'An interrupted run is available — use /resume to continue from a safe boundary.', - resumeStartingNotice: 'Resuming from the latest safe boundary…', - resumeUnavailableNotice: 'Safe-boundary resume is unavailable on this runtime.', - resumeCatalogIncompleteNotice: - 'Session discovery stopped before the full catalog was checked. Showing the sessions found so far.', - selectPickerHint: '↑↓ select · Enter confirm · Esc close', - providerConfigured: 'configured', - addAccount: 'add account', - thinkingLevels: { - off: 'Off', - minimal: 'Minimal', - low: 'Low', - medium: 'Medium', - high: 'High', - xhigh: 'Extra high', - max: 'Maximum', + }), + ), + 'form-interaction': resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_FORM_INTERACTION_EN, + 'zh-CN': { + 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} 项', }, - defaultThinkingLevel: 'Default', - thinkingPickerTitle: 'Select Thinking Level', - currentMarker: 'current', - defaultMarker: 'default', - setupTitle: 'Set Up Provider', - baseUrlLabel: 'Base URL', - apiKeyLabel: 'API key', - connectionNameLabel: 'Name', - connectionSlugLabel: 'Slug', - identityHint: - 'Name this connection, or keep the defaults to auto-assign · Enter continues · Esc returns', - identitySlugInvalid: 'Slug must be 2–64 characters: lowercase letters, digits, and hyphens', - identityNameTooLong: 'Name must be 256 characters or fewer', - onboardingUnavailable: - 'Onboarding is unavailable: this environment does not provide a setup surface.', - onboardingRequestFailed: 'Could not reach the Runtime Host. Try again.', - onboardingRejections: { - provider_unsupported: 'This provider does not support API-key onboarding.', - connection_not_found: 'This connection no longer exists. Reopen /setup and try again.', - credential_not_configured: 'An API key is required.', - base_url_not_configured: 'A Base URL is required for this provider.', - catalog_full: 'The connection catalog is full.', - model_unavailable: 'A selected model is no longer available. Verify and select again.', - slug_taken: 'That slug is already taken. Pick another, or clear it to auto-assign.', - superseded: 'This connection changed. Reopen /setup and try again.', + '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} 項', }, - onboardingFailures: { - auth: 'Provider authentication failed. Check the API key and try again.', - timeout: 'Provider verification timed out. Try again.', - provider_unavailable: 'The provider is unavailable. Try again later.', - network: 'A network error interrupted provider verification. Try again.', - invalid_response: 'The provider returned an invalid response. Try again.', - unknown: 'Provider verification failed. Try again.', - }, - oauthFailures: { - capability_unavailable: 'This client cannot present OAuth sign-in.', - authorization_failed: 'Authorization did not complete. Try again.', - provider_rejected: 'The provider rejected this account.', - slug_taken: 'That slug is already taken. Pick another, or clear it to auto-assign.', - credential_changed: 'The credential changed during sign-in. Try again.', - connection_changed: 'The connection changed during sign-in. Reopen /setup.', - persistence_failed: 'The signed-in account could not be saved. Try again.', - internal_failure: 'OAuth sign-in failed. Try again.', - connection_not_found: 'This connection no longer exists. Reopen /setup.', - operation_conflict: 'Another OAuth login is active, or this identity is unavailable.', - unavailable: 'Could not reach the Runtime Host. Try again.', - }, - accountSavedRefreshFailed: - 'Account saved, but the model list has not refreshed. Restart Maka to reload it.', - listProvidersFailed: 'Could not read configured connections.', - noConfigurableProviders: 'No configurable providers are available.', - baseUrlRequired: 'Enter a Base URL', - baseUrlInvalid: 'Base URL is not a valid URL', - baseUrlProtocol: 'Base URL must use http or https', - baseUrlCredentials: 'Base URL cannot include credentials', - baseUrlQuery: 'Base URL cannot include a query or fragment', - baseUrlTooLong: 'Base URL cannot exceed 2048 bytes', - selectModelBeforeSaving: 'Select at least one model before saving', - reuseBaseUrlHint: - 'Leave blank to reuse the saved Base URL, or enter a replacement · Esc returns to provider selection', - enterBaseUrlHint: 'Enter the relay Base URL (http/https) · Esc returns to provider selection', - continueAction: 'Enter to continue', - providerSearchHint: 'Search providers · ↑↓ select · Enter confirm · Esc cancel', - noMatchingProviders: 'No matching providers', - keyHints: { - reuse: { - baseUrl: - 'Leave blank to reuse the saved key, or enter a new key to rotate it · Esc returns to Base URL', - provider: - 'Leave blank to reuse the saved key, or enter a new key to rotate it · Esc returns to provider selection', - // The identity step only exists for create targets, so a `reuse` - // hint never lands here; the provider text keeps the record total. - identity: - 'Leave blank to reuse the saved key, or enter a new key to rotate it · Esc returns to provider selection', + }), + ), + pickers: resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_PICKERS_EN, + 'zh-CN': { + modelPickerTitle: '选择模型', + modelSwitchCacheWarning: + '\u26a0 切换模型可能需要重建提示缓存;下一次请求可能更慢或成本更高。', + modelSearchHint: '搜索模型 / 服务商 / 连接 · ↑↓ 选择 · Enter 确认 · Esc 取消', + searchLabel: '搜索', + noMatchingModels: '没有匹配的模型', + resumeSessionTitle: '恢复会话', + sessionScopeCurrent: '当前目录', + sessionScopeAll: '全部', + sessionSearchHint: + '搜索名称 / ID / 路径 / 模型 / 连接 · Tab 切换范围 · ↑↓ 选择 · Enter 打开 · Esc 关闭', + noMatchingSessions: '没有匹配的会话', + resumeCandidateCheckFailed: '无法检查是否有可恢复的会话。', + resumeAvailabilityNotice: '有可恢复的中断会话,可使用 /resume 从安全边界继续。', + resumeStartingNotice: '正在从最新的安全边界恢复……', + resumeUnavailableNotice: '当前运行环境不支持安全边界恢复。', + resumeCatalogIncompleteNotice: '会话目录尚未检查完整。当前显示已找到的会话。', + selectPickerHint: '↑↓ 选择 · Enter 确认 · Esc 关闭', + providerConfigured: '已设置', + addAccount: '添加账号', + thinkingLevels: { + off: '关', + minimal: '最小', + low: '低', + medium: '中', + high: '高', + xhigh: '超高', + max: '最高', }, - enter: { - baseUrl: 'Enter an API key · stored only on this machine · Esc returns to Base URL', - provider: - 'Enter an API key · stored only on this machine · Esc returns to provider selection', - identity: - 'Enter an API key · stored only on this machine · Esc returns to connection naming', + 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: '服务商验证失败,请重试。', + }, + oauthFailures: { + capability_unavailable: '当前客户端无法呈现 OAuth 登录。', + authorization_failed: '授权未完成,请重试。', + provider_rejected: '服务商拒绝了该账号。', + slug_taken: '该标识已被占用。请更换,或清空后自动分配。', + credential_changed: '登录期间凭据已变化,请重试。', + connection_changed: '登录期间连接已变化,请重新打开 /setup。', + persistence_failed: '无法保存已登录的账号,请重试。', + internal_failure: 'OAuth 登录失败,请重试。', + connection_not_found: '该连接已不存在,请重新打开 /setup。', + operation_conflict: '已有 OAuth 登录正在进行,或该连接标识不可用。', + unavailable: '无法连接 Runtime Host,请重试。', + }, + accountSavedRefreshFailed: '账号已保存,但模型列表暂未刷新。重启 Maka 后会重新载入。', + listProvidersFailed: '无法读取已配置的连接。', + noConfigurableProviders: '没有可配置的模型提供商。', + 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…', + oauthHint: '打开设备登录页面并输入验证码 · Esc 返回', + oauthBackHint: 'Esc 返回选择服务商', + oauthCodeLabel: '登录验证码', + oauthStarting: '正在准备登录…', + oauthWaiting: '正在等待浏览器授权…', + oauthCancelling: '正在取消登录…', + oauthUnconfirmed: '登录结果尚未确认,可能已经完成', + oauthRecheckAction: 'Enter 查询登录结果 · Esc 关闭设置', + oauthLoadingModels: '登录成功 · 正在加载模型…', + oauthContinueToModels: '已登录 · Enter 继续选择模型', + oauthRetryAction: 'Enter 重试 · Esc 返回', + oauthRetryModelsAction: '已登录 · Enter 重试加载模型', + modelSelectionHint: '搜索模型,↑↓ 选择 · Space 切换 · Enter 保存 · Esc 返回', + selectedModels: '已选 {count}', + selectedModelsAndSave: '已选 {count} · Enter 保存', + saving: '正在保存…', + complete: '完成', + enabledModels: '已启用模型:{count}', + closeAction: 'Enter 关闭', + thinkingUnsupported: '当前模型不支持思考级别切换。', }, - submitAction: 'Enter to submit', - verifyingKey: 'Verifying key…', - oauthHint: 'Open the device page and enter the sign-in code · Esc returns', - oauthBackHint: 'Esc returns to provider selection', - oauthCodeLabel: 'Sign-in code', - oauthStarting: 'Preparing sign-in…', - oauthWaiting: 'Waiting for browser authorization…', - oauthCancelling: 'Cancelling sign-in…', - oauthUnconfirmed: 'Sign-in result is not confirmed; it may have completed', - oauthRecheckAction: 'Enter checks the sign-in result · Esc closes setup', - oauthLoadingModels: 'Signed in · Loading models…', - oauthContinueToModels: 'Signed in · Enter to continue to models', - oauthRetryAction: 'Enter retry · Esc returns', - oauthRetryModelsAction: 'Signed in · Enter retries model loading', - modelSelectionHint: 'Search models · ↑↓ select · Space toggle · Enter save · Esc back', - selectedModels: '{count} selected', - selectedModelsAndSave: '{count} selected · Enter to save', - saving: 'Saving…', - complete: 'Complete', - enabledModels: 'Models enabled: {count}', - closeAction: 'Enter to close', - thinkingUnsupported: 'This model does not support changing the thinking level.', - }, - 'zh-CN': { - modelPickerTitle: '选择模型', - modelSwitchCacheWarning: - '\u26a0 切换模型可能需要重建提示缓存;下一次请求可能更慢或成本更高。', - modelSearchHint: '搜索模型 / 服务商 / 连接 · ↑↓ 选择 · Enter 确认 · Esc 取消', - searchLabel: '搜索', - noMatchingModels: '没有匹配的模型', - resumeSessionTitle: '恢复会话', - sessionScopeCurrent: '当前目录', - sessionScopeAll: '全部', - sessionSearchHint: - '搜索名称 / ID / 路径 / 模型 / 连接 · Tab 切换范围 · ↑↓ 选择 · Enter 打开 · Esc 关闭', - noMatchingSessions: '没有匹配的会话', - resumeCandidateCheckFailed: '无法检查是否有可恢复的会话。', - resumeAvailabilityNotice: '有可恢复的中断会话,可使用 /resume 从安全边界继续。', - resumeStartingNotice: '正在从最新的安全边界恢复……', - resumeUnavailableNotice: '当前运行环境不支持安全边界恢复。', - resumeCatalogIncompleteNotice: '会话目录尚未检查完整。当前显示已找到的会话。', - 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: '服务商验证失败,请重试。', - }, - oauthFailures: { - capability_unavailable: '当前客户端无法呈现 OAuth 登录。', - authorization_failed: '授权未完成,请重试。', - provider_rejected: '服务商拒绝了该账号。', - slug_taken: '该标识已被占用。请更换,或清空后自动分配。', - credential_changed: '登录期间凭据已变化,请重试。', - connection_changed: '登录期间连接已变化,请重新打开 /setup。', - persistence_failed: '无法保存已登录的账号,请重试。', - internal_failure: 'OAuth 登录失败,请重试。', - connection_not_found: '该连接已不存在,请重新打开 /setup。', - operation_conflict: '已有 OAuth 登录正在进行,或该连接标识不可用。', - unavailable: '无法连接 Runtime Host,请重试。', - }, - accountSavedRefreshFailed: '账号已保存,但模型列表暂未刷新。重启 Maka 后会重新载入。', - listProvidersFailed: '无法读取已配置的连接。', - noConfigurableProviders: '没有可配置的模型提供商。', - 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 返回选择服务商', + 'zh-TW': { + modelPickerTitle: '選擇模型', + modelSwitchCacheWarning: + '\u26a0 切換模型可能需要重建提示快取;下一次請求可能較慢或成本較高。', + modelSearchHint: '搜尋模型 / 服務商 / 連線 · ↑↓ 選擇 · Enter 確認 · Esc 取消', + searchLabel: '搜尋', + noMatchingModels: '沒有符合的模型', + resumeSessionTitle: '恢復會話', + sessionScopeCurrent: '目前目錄', + sessionScopeAll: '全部', + sessionSearchHint: + '搜尋名稱 / ID / 路徑 / 模型 / 連線 · Tab 切換範圍 · ↑↓ 選取 · Enter 開啟 · Esc 關閉', + noMatchingSessions: '沒有符合的會話', + resumeCandidateCheckFailed: '無法檢查是否有可恢復的會話。', + resumeAvailabilityNotice: '有可恢復的中斷會話,可使用 /resume 從安全邊界繼續。', + resumeStartingNotice: '正在從最新的安全邊界恢復……', + resumeUnavailableNotice: '目前執行環境不支援安全邊界恢復。', + resumeCatalogIncompleteNotice: '會話目錄尚未檢查完整。目前顯示已找到的會話。', + selectPickerHint: '↑↓ 選擇 · Enter 確認 · Esc 關閉', + providerConfigured: '已設定', + addAccount: '新增帳號', + thinkingLevels: { + off: '關', + minimal: '最小', + low: '低', + medium: '中', + high: '高', + xhigh: '超高', + max: '最高', }, - enter: { - baseUrl: '输入 API key · 仅本机存储 · Esc 返回 Base URL', - provider: '输入 API key · 仅本机存储 · Esc 返回选择服务商', - identity: '输入 API key · 仅本机存储 · Esc 返回命名连接', + 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: '服務商驗證失敗,請重試。', + }, + oauthFailures: { + capability_unavailable: '目前客戶端無法顯示 OAuth 登入。', + authorization_failed: '授權未完成,請重試。', + provider_rejected: '服務商拒絕了此帳號。', + slug_taken: '該識別碼已被佔用。請更換,或清空後自動分配。', + credential_changed: '登入期間憑證已變更,請重試。', + connection_changed: '登入期間連線已變更,請重新開啟 /setup。', + persistence_failed: '無法儲存已登入的帳號,請重試。', + internal_failure: 'OAuth 登入失敗,請重試。', + connection_not_found: '此連線已不存在,請重新開啟 /setup。', + operation_conflict: '已有 OAuth 登入正在進行,或該連線識別碼無法使用。', + unavailable: '無法連線 Runtime Host,請重試。', + }, + accountSavedRefreshFailed: + '帳號已儲存,但模型清單尚未重新整理。重新啟動 Maka 後會重新載入。', + listProvidersFailed: '無法讀取已設定的連線。', + noConfigurableProviders: '沒有可設定的模型服務商。', + 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…', + oauthHint: '開啟裝置登入頁面並輸入驗證碼 · Esc 返回', + oauthBackHint: 'Esc 返回選擇服務商', + oauthCodeLabel: '登入驗證碼', + oauthStarting: '正在準備登入…', + oauthWaiting: '正在等待瀏覽器授權…', + oauthCancelling: '正在取消登入…', + oauthUnconfirmed: '登入結果尚未確認,可能已經完成', + oauthRecheckAction: 'Enter 查詢登入結果 · Esc 關閉設定', + oauthLoadingModels: '登入成功 · 正在載入模型…', + oauthContinueToModels: '已登入 · Enter 繼續選擇模型', + oauthRetryAction: 'Enter 重試 · Esc 返回', + oauthRetryModelsAction: '已登入 · Enter 重試載入模型', + modelSelectionHint: '搜尋模型,↑↓ 選擇 · Space 切換 · Enter 儲存 · Esc 返回', + selectedModels: '已選 {count}', + selectedModelsAndSave: '已選 {count} · Enter 儲存', + saving: '正在儲存…', + complete: '完成', + enabledModels: '已啟用模型:{count}', + closeAction: 'Enter 關閉', + thinkingUnsupported: '目前模型不支援思考級別切換。', }, - submitAction: 'Enter 提交', - verifyingKey: '正在验证 key…', - oauthHint: '打开设备登录页面并输入验证码 · Esc 返回', - oauthBackHint: 'Esc 返回选择服务商', - oauthCodeLabel: '登录验证码', - oauthStarting: '正在准备登录…', - oauthWaiting: '正在等待浏览器授权…', - oauthCancelling: '正在取消登录…', - oauthUnconfirmed: '登录结果尚未确认,可能已经完成', - oauthRecheckAction: 'Enter 查询登录结果 · Esc 关闭设置', - oauthLoadingModels: '登录成功 · 正在加载模型…', - oauthContinueToModels: '已登录 · Enter 继续选择模型', - oauthRetryAction: 'Enter 重试 · Esc 返回', - oauthRetryModelsAction: '已登录 · Enter 重试加载模型', - modelSelectionHint: '搜索模型,↑↓ 选择 · Space 切换 · Enter 保存 · Esc 返回', - selectedModels: '已选 {count}', - selectedModelsAndSave: '已选 {count} · Enter 保存', - saving: '正在保存…', - complete: '完成', - enabledModels: '已启用模型:{count}', - closeAction: 'Enter 关闭', - thinkingUnsupported: '当前模型不支持思考级别切换。', - }, - 'zh-TW': { - modelPickerTitle: '選擇模型', - modelSwitchCacheWarning: - '\u26a0 切換模型可能需要重建提示快取;下一次請求可能較慢或成本較高。', - modelSearchHint: '搜尋模型 / 服務商 / 連線 · ↑↓ 選擇 · Enter 確認 · Esc 取消', - searchLabel: '搜尋', - noMatchingModels: '沒有符合的模型', - resumeSessionTitle: '恢復會話', - sessionScopeCurrent: '目前目錄', - sessionScopeAll: '全部', - sessionSearchHint: - '搜尋名稱 / ID / 路徑 / 模型 / 連線 · Tab 切換範圍 · ↑↓ 選取 · Enter 開啟 · Esc 關閉', - noMatchingSessions: '沒有符合的會話', - resumeCandidateCheckFailed: '無法檢查是否有可恢復的會話。', - resumeAvailabilityNotice: '有可恢復的中斷會話,可使用 /resume 從安全邊界繼續。', - resumeStartingNotice: '正在從最新的安全邊界恢復……', - resumeUnavailableNotice: '目前執行環境不支援安全邊界恢復。', - resumeCatalogIncompleteNotice: '會話目錄尚未檢查完整。目前顯示已找到的會話。', - selectPickerHint: '↑↓ 選擇 · Enter 確認 · Esc 關閉', - providerConfigured: '已設定', - addAccount: '新增帳號', - thinkingLevels: { - off: '關', - minimal: '最小', - low: '低', - medium: '中', - high: '高', - xhigh: '超高', - max: '最高', + }), + ), + 'primary-guidance': resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_PRIMARY_GUIDANCE_EN, + 'zh-CN': { + welcome: { + tagline: '陪你把事做完', + start: '输入消息开始对话', + session: '切换或恢复会话', + model: '切换模型', + setup: '配置模型提供商', + }, + commands: { + compact: '压缩会话上下文', + context: '查看最近一次请求的上下文用量', + copy: '复制最后一条回复(或 /copy all)到剪贴板', + exit: '断开此 TUI,保留已连接的 Host', + host: '查看 Host 状态或停止、重启本地所有者', + update: '更新 npm 全局 CLI 及其本地 Host', + goal: '查看自主目标状态', + graph: '查看、启用、停用 Graph 模式,或执行一次 Graph 任务', + help: '查看命令和快捷键', + mcp: '管理客户端 MCP 服务器和工具发布状态', + model: '选择模型', + move: '将当前会话移到其他目录', + new: '新建会话', + permissions: '设置会话权限', + recap: '用一句话总结当前会话', + rename: '重命名当前会话', + resume: '从安全边界恢复最近一次中断的执行', + rewind: '回退到较早的对话轮次', + session: '切换或恢复会话', + setup: '配置模型提供商', + side: '打开临时 Side Conversation', + skill: '调用 Skill(也可直接输入 /skill:)', + swarm: '查看、启用、停用 Swarm 模式,或执行一次 Swarm 任务', + thinking: '设置思考级别', + todo: '查看当前会话待办清单', + transcript: '在 Maka 内浏览完整对话记录', + }, + help: { + commandsHeading: '命令', + userCommand: ' ! — 执行一次仅用户可见的 shell 命令', + keybindingsHeading: '快捷键', + keybindings: [ + ' Ctrl+O — 打开详细记录(其中 Ctrl+E 切换详情)', + ' Ctrl+T — 展开或折叠视图中的所有思考块', + ' 使用终端或触控板滚动对话记录', + ' Enter(任务运行中)— 将消息注入当前任务', + ' Tab(任务运行中)— 将消息排入下一轮', + ' Shift+← — 将已排队消息取回编辑器重新编辑', + ' Esc Esc(任务运行中)— 中断当前任务', + ' Esc Esc(空闲时)— 回退到较早的轮次', + ' Ctrl+C — 停止任务、清空输入,或连续按两次退出', + ' Ctrl+D — 输入为空时退出', + ], + }, }, - 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 後重試。', + 'zh-TW': { + welcome: { + tagline: '陪你把事做完', + start: '輸入訊息開始對話', + session: '切換或恢復會話', + model: '切換模型', + setup: '設定模型服務商', + }, + commands: { + compact: '壓縮會話上下文', + context: '檢視最近一次請求的上下文用量', + copy: '複製最後一則回覆(或 /copy all)到剪貼簿', + exit: '中斷此 TUI,保留已連線的 Host', + host: '查看 Host 狀態或停止、重新啟動本機擁有者', + update: '更新 npm 全域 CLI 及其本機 Host', + goal: '檢視自主目標狀態', + graph: '檢視、啟用、停用 Graph 模式,或執行一次 Graph 任務', + help: '檢視命令和快捷鍵', + mcp: '管理用戶端 MCP 伺服器與工具發佈狀態', + model: '選擇模型', + move: '將目前會話移到其他目錄', + new: '建立會話', + permissions: '設定會話權限', + recap: '用一句話總結目前會話', + rename: '重新命名目前會話', + resume: '從安全邊界恢復最近一次中斷的執行', + rewind: '回退到較早的對話輪次', + session: '切換或恢復會話', + setup: '設定模型服務商', + side: '開啟臨時 Side Conversation', + skill: '呼叫 Skill(也可直接輸入 /skill:)', + swarm: '檢視、啟用、停用 Swarm 模式,或執行一次 Swarm 任務', + thinking: '設定思考級別', + todo: '檢視目前會話待辦清單', + transcript: '在 Maka 內瀏覽完整對話記錄', + }, + help: { + commandsHeading: '命令', + userCommand: ' ! — 執行一次僅使用者可見的 shell 命令', + keybindingsHeading: '快捷鍵', + keybindings: [ + ' Ctrl+O — 開啟詳細記錄(其中 Ctrl+E 切換詳情)', + ' Ctrl+T — 展開或摺疊檢視中的所有思考區塊', + ' 使用終端機或觸控板捲動對話記錄', + ' Enter(任務執行中)— 將訊息注入目前任務', + ' Tab(任務執行中)— 將訊息排入下一輪', + ' Shift+← — 將已排隊訊息取回編輯器重新編輯', + ' Esc Esc(任務執行中)— 中斷目前任務', + ' Esc Esc(空閒時)— 回退到較早的輪次', + ' Ctrl+C — 停止任務、清空輸入,或連續按兩次退出', + ' Ctrl+D — 輸入為空時退出', + ], + }, }, - onboardingFailures: { - auth: '服務商身分驗證失敗,請檢查 API key 後重試。', - timeout: '服務商驗證逾時,請重試。', - provider_unavailable: '服務商目前無法使用,請稍後重試。', - network: '網路錯誤中斷了服務商驗證,請重試。', - invalid_response: '服務商傳回無效回應,請重試。', - unknown: '服務商驗證失敗,請重試。', + }), + ), + rewind: resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_REWIND_EN, + 'zh-CN': { + pickerTitle: '回退', + pending: '正在回退到该轮之前…', + doneRefilled: + '已回退到该轮之前(分支为新任务,原任务保留),该轮 prompt 已回填输入框,可修改后重新发送。', + doneKeptDraft: + '已回退到该轮之前(分支为新任务,原任务保留)。输入框已有未发送内容,未覆盖;该轮 prompt 已存入输入历史,可按 ↑ 找回。', + noTargets: '没有可回退的轮次。', + busy: '无法回退:当前有正在进行的操作 — 请等待其完成,或中断(Esc)后重试。', + unsupportedQuotes: + '无法回退到这一轮:它携带引用摘录,TUI 暂时无法把它们还原进替换 prompt。请改为回退到更早的纯文本轮次。', + unsupportedAttachments: + '无法回退到这一轮:它携带附件,TUI 暂时无法把它们还原进替换 prompt。请改为回退到更早的纯文本轮次。', + unsupportedDirectoryReferences: + '无法回退到这一轮:它携带目录引用,TUI 暂时无法把它们还原进替换 prompt。请改为回退到更早的纯文本轮次。', + pickerHint: '回到选定轮次之前(丢弃该轮及之后,prompt 回填输入框) · enter 选择 / esc 取消', }, - oauthFailures: { - capability_unavailable: '目前客戶端無法顯示 OAuth 登入。', - authorization_failed: '授權未完成,請重試。', - provider_rejected: '服務商拒絕了此帳號。', - slug_taken: '該識別碼已被佔用。請更換,或清空後自動分配。', - credential_changed: '登入期間憑證已變更,請重試。', - connection_changed: '登入期間連線已變更,請重新開啟 /setup。', - persistence_failed: '無法儲存已登入的帳號,請重試。', - internal_failure: 'OAuth 登入失敗,請重試。', - connection_not_found: '此連線已不存在,請重新開啟 /setup。', - operation_conflict: '已有 OAuth 登入正在進行,或該連線識別碼無法使用。', - unavailable: '無法連線 Runtime Host,請重試。', + 'zh-TW': { + pickerTitle: '回退', + pending: '正在回退到該輪之前…', + doneRefilled: + '已回退到該輪之前(分支為新任務,原任務保留),該輪 prompt 已回填輸入框,可修改後重新傳送。', + doneKeptDraft: + '已回退到該輪之前(分支為新任務,原任務保留)。輸入框已有未傳送內容,未覆蓋;該輪 prompt 已存入輸入歷史,可按 ↑ 找回。', + noTargets: '沒有可回退的輪次。', + busy: '無法回退:目前有正在進行的操作 — 請等待完成,或中斷(Esc)後重試。', + unsupportedQuotes: + '無法回退到這一輪:它攜帶引用摘錄,TUI 暫時無法把它們還原進替換 prompt。請改為回退到更早的純文字輪次。', + unsupportedAttachments: + '無法回退到這一輪:它攜帶附件,TUI 暫時無法把它們還原進替換 prompt。請改為回退到更早的純文字輪次。', + unsupportedDirectoryReferences: + '無法回退到這一輪:它攜帶目錄參照,TUI 暫時無法把它們還原進替換 prompt。請改為回退到更早的純文字輪次。', + pickerHint: '回到選定輪次之前(捨棄該輪及之後,prompt 回填輸入框) · enter 選擇 / esc 取消', }, - accountSavedRefreshFailed: '帳號已儲存,但模型清單尚未重新整理。重新啟動 Maka 後會重新載入。', - listProvidersFailed: '無法讀取已設定的連線。', - noConfigurableProviders: '沒有可設定的模型服務商。', - 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 返回命名連線', + }), + ), + skills: resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_SKILLS_EN, + 'zh-CN': { + pickerTitle: '调用技能', + usage: 'Usage: /skill,或直接在消息中输入 /skill:', + 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: '调用请求过多', }, }, - submitAction: 'Enter 送出', - verifyingKey: '正在驗證 key…', - oauthHint: '開啟裝置登入頁面並輸入驗證碼 · Esc 返回', - oauthBackHint: 'Esc 返回選擇服務商', - oauthCodeLabel: '登入驗證碼', - oauthStarting: '正在準備登入…', - oauthWaiting: '正在等待瀏覽器授權…', - oauthCancelling: '正在取消登入…', - oauthUnconfirmed: '登入結果尚未確認,可能已經完成', - oauthRecheckAction: 'Enter 查詢登入結果 · Esc 關閉設定', - oauthLoadingModels: '登入成功 · 正在載入模型…', - oauthContinueToModels: '已登入 · Enter 繼續選擇模型', - oauthRetryAction: 'Enter 重試 · Esc 返回', - oauthRetryModelsAction: '已登入 · Enter 重試載入模型', - modelSelectionHint: '搜尋模型,↑↓ 選擇 · Space 切換 · Enter 儲存 · Esc 返回', - selectedModels: '已選 {count}', - selectedModelsAndSave: '已選 {count} · Enter 儲存', - saving: '正在儲存…', - complete: '完成', - enabledModels: '已啟用模型:{count}', - closeAction: 'Enter 關閉', - thinkingUnsupported: '目前模型不支援思考級別切換。', - }, - }, - 'primary-guidance': { - en: { - welcome: { - tagline: 'Get things done together', - start: 'Type a message to start', - session: 'Switch or resume a session', - model: 'Switch models', - setup: 'Set up a model provider', - }, - commands: { - compact: 'Compact session context', - context: 'Show latest request context usage', - copy: 'Copy the last reply (or /copy all) to the clipboard', - exit: 'Disconnect this TUI; leave the attached Host running', - host: 'Inspect the Host or stop/restart its local owner', - update: 'Update the npm-global CLI and its local Host', - goal: 'Show autonomous goal status', - graph: 'Show, enable, disable, or run one Graph turn', - help: 'Show commands and keybindings', - mcp: 'Manage client-owned MCP servers and tool publication status', - model: 'Select model', - move: 'Move current session to another directory', - new: 'Start a new session', - permissions: 'Set session permissions', - recap: 'One-sentence recap of the session so far', - rename: 'Rename current session', - resume: 'Resume latest interrupted run at a safe boundary', - rewind: 'Rewind to an earlier turn', - session: 'Resume session', - setup: 'Set up a model provider', - side: 'Open a temporary side conversation', - skill: 'Invoke a skill (or type /skill: inline)', - swarm: 'Show, enable, disable, or run one Swarm turn', - thinking: 'Set thinking level', - todo: 'Show the current session Todo list', - transcript: 'Browse the full transcript inside Maka', + 'zh-TW': { + pickerTitle: '呼叫技能', + usage: 'Usage: /skill,或直接在訊息中輸入 /skill:', + 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: '呼叫請求過多', + }, }, - help: { - commandsHeading: 'Commands', - userCommand: ' ! — run one shell command visible only to you', - keybindingsHeading: 'Keybindings', - keybindings: [ - ' Ctrl+O — open detailed transcript (Ctrl+E toggles details inside)', - ' Ctrl+T — expand or collapse all thinking in view', - ' Scroll the transcript with your terminal or trackpad', - ' Enter (during a turn) — steer: inject a message into the running turn', - ' Tab (during a turn) — queue a message for the next turn', - ' Shift+← — take queued messages back into the editor to re-edit', - ' Esc Esc (during a turn) — interrupt the turn', - ' Esc Esc (when idle) — rewind to an earlier turn', - ' Ctrl+C — stop the turn, clear input, or press twice to exit', - ' Ctrl+D — exit when input is empty', - ], + }), + ), + 'connection-identity': resolveUiMessageCatalog( + defineShippedUiMessageCatalog< + UiMessageShape + >()({ + en: TUI_COPY_RESOURCES_CONNECTION_IDENTITY_EN, + 'zh-CN': { + withRecovery: '{notice}{recovery}', + emptyChoiceRecovery: '如果 /model 没有可选项,请先添加或启用连接(可运行 /setup 添加)。', + confirmAccount: '此任务来自旧版本,需要确认一次账号。运行 /model 选择现有账号和模型。', + accountDeleted: '原账号已删除;运行 /model 选择新账号和模型后继续。', + identityMismatch: '任务保存的账号身份与当前连接不一致;运行 /model 重新选择账号和模型。', + accountDisabled: '原账号已停用;请启用该账号,或运行 /model 选择新账号和模型。', }, - }, - 'zh-CN': { - welcome: { - tagline: '陪你把事做完', - start: '输入消息开始对话', - session: '切换或恢复会话', - model: '切换模型', - setup: '配置模型提供商', + 'zh-TW': { + withRecovery: '{notice}{recovery}', + emptyChoiceRecovery: '如果 /model 沒有可選項,請先新增或啟用連線(可執行 /setup 新增)。', + confirmAccount: '此任務來自舊版本,需要確認一次帳號。執行 /model 選擇現有帳號和模型。', + accountDeleted: '原帳號已刪除;執行 /model 選擇新帳號和模型後繼續。', + identityMismatch: '任務儲存的帳號身分與目前連線不一致;執行 /model 重新選擇帳號和模型。', + accountDisabled: '原帳號已停用;請啟用該帳號,或執行 /model 選擇新帳號和模型。', }, - commands: { - compact: '压缩会话上下文', - context: '查看最近一次请求的上下文用量', - copy: '复制最后一条回复(或 /copy all)到剪贴板', - exit: '断开此 TUI,保留已连接的 Host', - host: '查看 Host 状态或停止、重启本地所有者', - update: '更新 npm 全局 CLI 及其本地 Host', - goal: '查看自主目标状态', - graph: '查看、启用、停用 Graph 模式,或执行一次 Graph 任务', - help: '查看命令和快捷键', - mcp: '管理客户端 MCP 服务器和工具发布状态', - model: '选择模型', - move: '将当前会话移到其他目录', - new: '新建会话', - permissions: '设置会话权限', - recap: '用一句话总结当前会话', - rename: '重命名当前会话', - resume: '从安全边界恢复最近一次中断的执行', - rewind: '回退到较早的对话轮次', - session: '切换或恢复会话', - setup: '配置模型提供商', - side: '打开临时 Side Conversation', - skill: '调用 Skill(也可直接输入 /skill:)', - swarm: '查看、启用、停用 Swarm 模式,或执行一次 Swarm 任务', - thinking: '设置思考级别', - todo: '查看当前会话待办清单', - transcript: '在 Maka 内浏览完整对话记录', + }), + ), + 'session-actions': resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_SESSION_ACTIONS_EN, + 'zh-CN': { + externalCatalogFailed: '无法读取外部会话目录。', + externalImport: '导入外部会话…', + externalImportDescription: '来自当前连接的 Runtime Host', + externalSourceTitle: '选择外部来源', + externalSessionTitle: '从 {source} 导入', + externalLoadMore: '加载更多…', + externalAllWorkspaces: '显示所有工作区', + externalCurrentWorkspace: '仅显示当前工作区', + externalEmpty: '没有找到外部会话。', + externalUnavailable: '当前页的外部会话暂时不可导入。', + externalImportedCount: '已导入 {count} 次', + externalImportedActionsTitle: '选择「{source}」的操作', + externalOpenLatestImported: '打开最近导入的任务', + externalImportAgain: '再次导入', + externalImportBusy: '当前有回合或其他操作正在运行。请等待完成后重试。', + externalImportFailed: '无法导入外部会话。', + externalImportModelUnavailable: '没有可用的模型连接。请先配置并启用一个连接再导入。', + externalImportSourceUnreadable: '无法读取或转换该外部会话。', + externalImportLimit: '外部会话超过支持的 {kind} 上限({max})。', + externalImportLimitTranscriptBytes: '源文件大小', + externalImportLimitRecordBytes: '单条记录大小', + externalImportLimitRecords: '记录数量', + externalImportLimitConvertedBytes: '转换后内容大小', + externalImportLimitMessages: '消息数量', + externalImportUncertain: + '导入结果不确定。可以检查任务列表,也可以再次导入;再次导入会创建独立任务。', + externalOpenFailed: '已导入为 {sessionId},但 Maka 无法打开。请用 /session 重试。', + externalOpenLatestFailed: '无法打开最近导入的任务({sessionId})。请用 /session 重试。', + newSessionFailed: '无法开始新会话:无法停止本地命令。请按 Ctrl+C 停止命令后重试。', }, - help: { - commandsHeading: '命令', - userCommand: ' ! — 执行一次仅用户可见的 shell 命令', - keybindingsHeading: '快捷键', - keybindings: [ - ' Ctrl+O — 打开详细记录(其中 Ctrl+E 切换详情)', - ' Ctrl+T — 展开或折叠视图中的所有思考块', - ' 使用终端或触控板滚动对话记录', - ' Enter(任务运行中)— 将消息注入当前任务', - ' Tab(任务运行中)— 将消息排入下一轮', - ' Shift+← — 将已排队消息取回编辑器重新编辑', - ' Esc Esc(任务运行中)— 中断当前任务', - ' Esc Esc(空闲时)— 回退到较早的轮次', - ' Ctrl+C — 停止任务、清空输入,或连续按两次退出', - ' Ctrl+D — 输入为空时退出', - ], + 'zh-TW': { + externalCatalogFailed: '無法讀取外部工作階段目錄。', + externalImport: '匯入外部工作階段…', + externalImportDescription: '來自目前連線的 Runtime Host', + externalSourceTitle: '選擇外部來源', + externalSessionTitle: '從 {source} 匯入', + externalLoadMore: '載入更多…', + externalAllWorkspaces: '顯示所有工作區', + externalCurrentWorkspace: '僅顯示目前工作區', + externalEmpty: '找不到外部工作階段。', + externalUnavailable: '目前頁的外部工作階段暫時無法匯入。', + externalImportedCount: '已匯入 {count} 次', + externalImportedActionsTitle: '選擇「{source}」的操作', + externalOpenLatestImported: '開啟最近匯入的任務', + externalImportAgain: '再次匯入', + externalImportBusy: '目前有回合或其他操作正在執行。請等待完成後重試。', + externalImportFailed: '無法匯入外部工作階段。', + externalImportModelUnavailable: '沒有可用的模型連線。請先設定並啟用一個連線再匯入。', + externalImportSourceUnreadable: '無法讀取或轉換該外部工作階段。', + externalImportLimit: '外部工作階段超過支援的 {kind} 上限({max})。', + externalImportLimitTranscriptBytes: '來源檔案大小', + externalImportLimitRecordBytes: '單筆記錄大小', + externalImportLimitRecords: '記錄數量', + externalImportLimitConvertedBytes: '轉換後內容大小', + externalImportLimitMessages: '訊息數量', + externalImportUncertain: + '匯入結果不確定。可以檢查任務列表,也可以再次匯入;再次匯入會建立獨立任務。', + externalOpenFailed: '已匯入為 {sessionId},但 Maka 無法開啟。請用 /session 重試。', + externalOpenLatestFailed: '無法開啟最近匯入的任務({sessionId})。請用 /session 重試。', + newSessionFailed: '無法開始新工作階段,請稍後重試。', }, - }, - 'zh-TW': { - welcome: { - tagline: '陪你把事做完', - start: '輸入訊息開始對話', - session: '切換或恢復會話', - model: '切換模型', - setup: '設定模型服務商', + }), + ), + 'pending-queue': resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_PENDING_QUEUE_EN, + 'zh-CN': { + steeringLabel: '注入:', + queuedLabel: '已排队:', + requeueHint: 'Shift+← 取回队列以重新编辑', }, - commands: { - compact: '壓縮會話上下文', - context: '檢視最近一次請求的上下文用量', - copy: '複製最後一則回覆(或 /copy all)到剪貼簿', - exit: '中斷此 TUI,保留已連線的 Host', - host: '查看 Host 狀態或停止、重新啟動本機擁有者', - update: '更新 npm 全域 CLI 及其本機 Host', - goal: '檢視自主目標狀態', - graph: '檢視、啟用、停用 Graph 模式,或執行一次 Graph 任務', - help: '檢視命令和快捷鍵', - mcp: '管理用戶端 MCP 伺服器與工具發佈狀態', - model: '選擇模型', - move: '將目前會話移到其他目錄', - new: '建立會話', - permissions: '設定會話權限', - recap: '用一句話總結目前會話', - rename: '重新命名目前會話', - resume: '從安全邊界恢復最近一次中斷的執行', - rewind: '回退到較早的對話輪次', - session: '切換或恢復會話', - setup: '設定模型服務商', - side: '開啟臨時 Side Conversation', - skill: '呼叫 Skill(也可直接輸入 /skill:)', - swarm: '檢視、啟用、停用 Swarm 模式,或執行一次 Swarm 任務', - thinking: '設定思考級別', - todo: '檢視目前會話待辦清單', - transcript: '在 Maka 內瀏覽完整對話記錄', + 'zh-TW': { + steeringLabel: '調整方向:', + queuedLabel: '已排入佇列:', + requeueHint: 'Shift+← 取回佇列訊息重新編輯', }, - help: { - commandsHeading: '命令', - userCommand: ' ! — 執行一次僅使用者可見的 shell 命令', - keybindingsHeading: '快捷鍵', - keybindings: [ - ' Ctrl+O — 開啟詳細記錄(其中 Ctrl+E 切換詳情)', - ' Ctrl+T — 展開或摺疊檢視中的所有思考區塊', - ' 使用終端機或觸控板捲動對話記錄', - ' Enter(任務執行中)— 將訊息注入目前任務', - ' Tab(任務執行中)— 將訊息排入下一輪', - ' Shift+← — 將已排隊訊息取回編輯器重新編輯', - ' Esc Esc(任務執行中)— 中斷目前任務', - ' Esc Esc(空閒時)— 回退到較早的輪次', - ' Ctrl+C — 停止任務、清空輸入,或連續按兩次退出', - ' Ctrl+D — 輸入為空時退出', - ], + }), + ), + 'unread-indicator': resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_UNREAD_INDICATOR_EN, + 'zh-CN': { + hint: '↓ {count, plural, other {# 行新输出 · 按 End 跳到最新}}', }, - }, - }, - 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.', - unsupportedQuotes: - 'Cannot rewind to this turn: it carries quoted excerpts, and the TUI cannot restore those into the replacement prompt yet. Rewind to an earlier plain-text turn instead.', - unsupportedAttachments: - 'Cannot rewind to this turn: it carries attachments, and the TUI cannot restore those into the replacement prompt yet. Rewind to an earlier plain-text turn instead.', - unsupportedDirectoryReferences: - 'Cannot rewind to this turn: it carries directory references, and the TUI cannot restore those into the replacement prompt yet. Rewind to an earlier plain-text turn instead.', - pickerHint: - 'Return to before the selected turn (discards it and later turns; the prompt refills the input box) · enter select / esc cancel', - }, - 'zh-CN': { - pickerTitle: '回退', - pending: '正在回退到该轮之前…', - doneRefilled: - '已回退到该轮之前(分支为新任务,原任务保留),该轮 prompt 已回填输入框,可修改后重新发送。', - doneKeptDraft: - '已回退到该轮之前(分支为新任务,原任务保留)。输入框已有未发送内容,未覆盖;该轮 prompt 已存入输入历史,可按 ↑ 找回。', - noTargets: '没有可回退的轮次。', - busy: '无法回退:当前有正在进行的操作 — 请等待其完成,或中断(Esc)后重试。', - unsupportedQuotes: - '无法回退到这一轮:它携带引用摘录,TUI 暂时无法把它们还原进替换 prompt。请改为回退到更早的纯文本轮次。', - unsupportedAttachments: - '无法回退到这一轮:它携带附件,TUI 暂时无法把它们还原进替换 prompt。请改为回退到更早的纯文本轮次。', - unsupportedDirectoryReferences: - '无法回退到这一轮:它携带目录引用,TUI 暂时无法把它们还原进替换 prompt。请改为回退到更早的纯文本轮次。', - pickerHint: '回到选定轮次之前(丢弃该轮及之后,prompt 回填输入框) · enter 选择 / esc 取消', - }, - 'zh-TW': { - pickerTitle: '回退', - pending: '正在回退到該輪之前…', - doneRefilled: - '已回退到該輪之前(分支為新任務,原任務保留),該輪 prompt 已回填輸入框,可修改後重新傳送。', - doneKeptDraft: - '已回退到該輪之前(分支為新任務,原任務保留)。輸入框已有未傳送內容,未覆蓋;該輪 prompt 已存入輸入歷史,可按 ↑ 找回。', - noTargets: '沒有可回退的輪次。', - busy: '無法回退:目前有正在進行的操作 — 請等待完成,或中斷(Esc)後重試。', - unsupportedQuotes: - '無法回退到這一輪:它攜帶引用摘錄,TUI 暫時無法把它們還原進替換 prompt。請改為回退到更早的純文字輪次。', - unsupportedAttachments: - '無法回退到這一輪:它攜帶附件,TUI 暫時無法把它們還原進替換 prompt。請改為回退到更早的純文字輪次。', - unsupportedDirectoryReferences: - '無法回退到這一輪:它攜帶目錄參照,TUI 暫時無法把它們還原進替換 prompt。請改為回退到更早的純文字輪次。', - pickerHint: '回到選定輪次之前(捨棄該輪及之後,prompt 回填輸入框) · enter 選擇 / esc 取消', - }, - }, - skills: { - en: { - pickerTitle: 'Invoke Skill', - usage: 'Usage: /skill, or type /skill: 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-TW': { + hint: '↓ {count, plural, other {# 行新輸出 · 按 End 跳到最新}}', }, - }, - 'zh-CN': { - pickerTitle: '调用技能', - usage: 'Usage: /skill,或直接在消息中输入 /skill:', - 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: '调用请求过多', + }), + ), + 'session-status': resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + en: TUI_COPY_RESOURCES_SESSION_STATUS_EN, + 'zh-CN': { + running: '进行中', + waitingForUser: '等你确认', + permissionRequired: '需要权限', + connectionRequired: '需要连接', + signInRequired: '需要重新登录', + stopped: '已中止', }, - }, - 'zh-TW': { - pickerTitle: '呼叫技能', - usage: 'Usage: /skill,或直接在訊息中輸入 /skill:', - 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: '呼叫請求過多', + 'zh-TW': { + running: '進行中', + waitingForUser: '等你確認', + permissionRequired: '需要權限', + connectionRequired: '需要連線', + signInRequired: '需要重新登入', + stopped: '已中止', }, - }, - }, - 'connection-identity': { - en: { - withRecovery: '{notice} {recovery}', - emptyChoiceRecovery: - 'If /model has no choices, add or enable a connection first (run /setup to add one).', - 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-CN': { - withRecovery: '{notice}{recovery}', - emptyChoiceRecovery: '如果 /model 没有可选项,请先添加或启用连接(可运行 /setup 添加)。', - confirmAccount: '此任务来自旧版本,需要确认一次账号。运行 /model 选择现有账号和模型。', - accountDeleted: '原账号已删除;运行 /model 选择新账号和模型后继续。', - identityMismatch: '任务保存的账号身份与当前连接不一致;运行 /model 重新选择账号和模型。', - accountDisabled: '原账号已停用;请启用该账号,或运行 /model 选择新账号和模型。', - }, - 'zh-TW': { - withRecovery: '{notice}{recovery}', - emptyChoiceRecovery: '如果 /model 沒有可選項,請先新增或啟用連線(可執行 /setup 新增)。', - confirmAccount: '此任務來自舊版本,需要確認一次帳號。執行 /model 選擇現有帳號和模型。', - accountDeleted: '原帳號已刪除;執行 /model 選擇新帳號和模型後繼續。', - identityMismatch: '任務儲存的帳號身分與目前連線不一致;執行 /model 重新選擇帳號和模型。', - accountDisabled: '原帳號已停用;請啟用該帳號,或執行 /model 選擇新帳號和模型。', - }, - }, - 'session-actions': { - en: { - externalCatalogFailed: 'Could not read external sessions.', - externalImport: 'Import external session…', - externalImportDescription: 'from the connected Runtime Host', - externalSourceTitle: 'Choose external source', - externalSessionTitle: 'Import from {source}', - externalLoadMore: 'Load more…', - externalAllWorkspaces: 'Show all workspaces', - externalCurrentWorkspace: 'Show current workspace', - externalEmpty: 'No external sessions found.', - externalUnavailable: - 'The external sessions on this page are not currently available to import.', - externalImportedCount: 'imported {count} times', - externalImportedActionsTitle: 'Choose an action for {source}', - externalOpenLatestImported: 'Open latest imported task', - externalImportAgain: 'Import again', - externalImportBusy: - 'Cannot import while a turn or another action is running. Wait for it to finish and try again.', - externalImportFailed: 'Could not import the external session.', - externalImportModelUnavailable: - 'No usable model connection is available. Configure and enable one before importing.', - externalImportSourceUnreadable: 'The external session could not be read or converted.', - externalImportLimit: 'The external session exceeds the supported {kind} limit ({max}).', - externalImportLimitTranscriptBytes: 'source file size', - externalImportLimitRecordBytes: 'single-record size', - externalImportLimitRecords: 'record count', - externalImportLimitConvertedBytes: 'converted content size', - externalImportLimitMessages: 'message count', - externalImportUncertain: - 'The import result is uncertain. Check the session list or import again; importing again creates an independent task.', - externalOpenFailed: - 'Imported as {sessionId}, but Maka could not open it. Use /session to retry.', - externalOpenLatestFailed: - 'Could not open the latest imported task ({sessionId}). Use /session to retry.', - newSessionFailed: - 'Could not start a new session: a local command could not be stopped. Press Ctrl+C to stop it, then try again.', - }, - 'zh-CN': { - externalCatalogFailed: '无法读取外部会话目录。', - externalImport: '导入外部会话…', - externalImportDescription: '来自当前连接的 Runtime Host', - externalSourceTitle: '选择外部来源', - externalSessionTitle: '从 {source} 导入', - externalLoadMore: '加载更多…', - externalAllWorkspaces: '显示所有工作区', - externalCurrentWorkspace: '仅显示当前工作区', - externalEmpty: '没有找到外部会话。', - externalUnavailable: '当前页的外部会话暂时不可导入。', - externalImportedCount: '已导入 {count} 次', - externalImportedActionsTitle: '选择「{source}」的操作', - externalOpenLatestImported: '打开最近导入的任务', - externalImportAgain: '再次导入', - externalImportBusy: '当前有回合或其他操作正在运行。请等待完成后重试。', - externalImportFailed: '无法导入外部会话。', - externalImportModelUnavailable: '没有可用的模型连接。请先配置并启用一个连接再导入。', - externalImportSourceUnreadable: '无法读取或转换该外部会话。', - externalImportLimit: '外部会话超过支持的 {kind} 上限({max})。', - externalImportLimitTranscriptBytes: '源文件大小', - externalImportLimitRecordBytes: '单条记录大小', - externalImportLimitRecords: '记录数量', - externalImportLimitConvertedBytes: '转换后内容大小', - externalImportLimitMessages: '消息数量', - externalImportUncertain: - '导入结果不确定。可以检查任务列表,也可以再次导入;再次导入会创建独立任务。', - externalOpenFailed: '已导入为 {sessionId},但 Maka 无法打开。请用 /session 重试。', - externalOpenLatestFailed: '无法打开最近导入的任务({sessionId})。请用 /session 重试。', - newSessionFailed: '无法开始新会话:无法停止本地命令。请按 Ctrl+C 停止命令后重试。', - }, - 'zh-TW': { - externalCatalogFailed: '無法讀取外部工作階段目錄。', - externalImport: '匯入外部工作階段…', - externalImportDescription: '來自目前連線的 Runtime Host', - externalSourceTitle: '選擇外部來源', - externalSessionTitle: '從 {source} 匯入', - externalLoadMore: '載入更多…', - externalAllWorkspaces: '顯示所有工作區', - externalCurrentWorkspace: '僅顯示目前工作區', - externalEmpty: '找不到外部工作階段。', - externalUnavailable: '目前頁的外部工作階段暫時無法匯入。', - externalImportedCount: '已匯入 {count} 次', - externalImportedActionsTitle: '選擇「{source}」的操作', - externalOpenLatestImported: '開啟最近匯入的任務', - externalImportAgain: '再次匯入', - externalImportBusy: '目前有回合或其他操作正在執行。請等待完成後重試。', - externalImportFailed: '無法匯入外部工作階段。', - externalImportModelUnavailable: '沒有可用的模型連線。請先設定並啟用一個連線再匯入。', - externalImportSourceUnreadable: '無法讀取或轉換該外部工作階段。', - externalImportLimit: '外部工作階段超過支援的 {kind} 上限({max})。', - externalImportLimitTranscriptBytes: '來源檔案大小', - externalImportLimitRecordBytes: '單筆記錄大小', - externalImportLimitRecords: '記錄數量', - externalImportLimitConvertedBytes: '轉換後內容大小', - externalImportLimitMessages: '訊息數量', - externalImportUncertain: - '匯入結果不確定。可以檢查任務列表,也可以再次匯入;再次匯入會建立獨立任務。', - externalOpenFailed: '已匯入為 {sessionId},但 Maka 無法開啟。請用 /session 重試。', - externalOpenLatestFailed: '無法開啟最近匯入的任務({sessionId})。請用 /session 重試。', - newSessionFailed: '無法開始新工作階段,請稍後重試。', - }, - }, - 'pending-queue': { - en: { - steeringLabel: 'Steering:', - queuedLabel: 'Queued:', - requeueHint: 'Shift+← take queued messages back to re-edit', - }, - 'zh-CN': { - steeringLabel: '注入:', - queuedLabel: '已排队:', - requeueHint: 'Shift+← 取回队列以重新编辑', - }, - 'zh-TW': { - steeringLabel: '調整方向:', - queuedLabel: '已排入佇列:', - requeueHint: 'Shift+← 取回佇列訊息重新編輯', - }, - }, - 'unread-indicator': { - en: { - hint: '↓ {count, plural, one {# new line} other {# new lines}} — End to jump to latest', - }, - 'zh-CN': { - hint: '↓ {count, plural, other {# 行新输出 · 按 End 跳到最新}}', - }, - 'zh-TW': { - hint: '↓ {count, plural, other {# 行新輸出 · 按 End 跳到最新}}', - }, - }, - 'session-status': { - en: { - running: 'running', - waitingForUser: 'waiting for you', - permissionRequired: 'needs permission', - connectionRequired: 'needs connection', - signInRequired: 'needs sign-in', - stopped: 'stopped', - }, - 'zh-CN': { - running: '进行中', - waitingForUser: '等你确认', - permissionRequired: '需要权限', - connectionRequired: '需要连接', - signInRequired: '需要重新登录', - stopped: '已中止', - }, - 'zh-TW': { - running: '進行中', - waitingForUser: '等你確認', - permissionRequired: '需要權限', - connectionRequired: '需要連線', - signInRequired: '需要重新登入', - stopped: '已中止', - }, - }, + }), + ), } as const; diff --git a/packages/core/src/__tests__/ui-locale.test.ts b/packages/core/src/__tests__/ui-locale.test.ts index 33046579254..48bc1fc95ca 100644 --- a/packages/core/src/__tests__/ui-locale.test.ts +++ b/packages/core/src/__tests__/ui-locale.test.ts @@ -20,6 +20,7 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { + defineShippedUiMessageCatalog, UI_LOCALES, defineUiMessageCatalog, formatUiMessage, @@ -80,6 +81,38 @@ describe('UI locale', () => { }); describe('UI message catalogs', () => { + it('keeps shipped translations complete and preserves optional translated fields', () => { + const catalog = defineShippedUiMessageCatalog<{ + title: string; + detail: { ready: string }; + count(n: number): string; + badge?: string; + }>()({ + en: { title: 'Status', detail: { ready: 'Ready' }, count: (n) => `${n} items` }, + 'zh-CN': { title: '状态', detail: { ready: '就绪' }, count: (n) => `${n} 项`, badge: '推荐' }, + 'zh-TW': { title: '狀態', detail: { ready: '就緒' }, count: (n) => `${n} 項`, badge: '推薦' }, + }); + const resolved = resolveUiMessageCatalog(catalog); + assert.equal(resolved['zh-CN'].badge, '推荐'); + assert.equal(resolved['zh-TW'].badge, '推薦'); + assert.equal(resolved['zh-CN'].count(2), '2 项'); + }); + + it('rejects missing shipped keys and incompatible copy function parameters at compile time', () => { + type Copy = { title: string; count(n: number): string }; + const en: Copy = { title: 'Status', count: (n) => `${n} items` }; + // @ts-expect-error Both shipped Chinese catalogs are required. + defineShippedUiMessageCatalog()({ en }); + // @ts-expect-error A shipped translation cannot omit count and silently fall back. + defineShippedUiMessageCatalog()({ en, 'zh-CN': { title: '状态' }, 'zh-TW': en }); + defineShippedUiMessageCatalog()({ + en, + // @ts-expect-error Function arguments retain their declared numeric signature. + 'zh-CN': { title: '状态', count: (n: string) => n }, + 'zh-TW': en, + }); + }); + it('falls back to complete English copy for missing translations', () => { const catalog = defineUiMessageCatalog<{ title: string; diff --git a/packages/core/src/redaction.ts b/packages/core/src/redaction.ts index 56169b2f456..33dc159a06e 100644 --- a/packages/core/src/redaction.ts +++ b/packages/core/src/redaction.ts @@ -17,7 +17,11 @@ * under the License. */ -import type { UiCatalog, UiLocale } from './ui-locale.js'; +import { + type UiLocale, + defineShippedUiMessageCatalog, + resolveUiMessageCatalog, +} from './ui-locale.js'; const SENSITIVE_KEY_SUFFIXES = new Set([ 'auth', @@ -310,29 +314,31 @@ export function classifyGeneralizedError(error: unknown): GeneralizedErrorClass /** Locale copy for each {@link GeneralizedErrorClass}; catalog authors spread * this per-locale block instead of restating the sentences. */ -export const GENERALIZED_ERROR_COPY = { - 'zh-CN': { - timeout: '请求超时', - rate_limited: '触发模型速率限制', - auth_failed: '鉴权失败', - provider_error: '模型服务返回错误', - network_error: '网络错误', - }, - 'zh-TW': { - timeout: '請求逾時', - rate_limited: '已達模型速率限制', - auth_failed: '驗證失敗', - provider_error: '模型服務傳回錯誤', - network_error: '網路錯誤', - }, - en: { - timeout: 'Request timed out', - rate_limited: 'Rate limit exceeded', - auth_failed: 'Authentication failed', - provider_error: 'Provider returned an error', - network_error: 'Network error', - }, -} satisfies UiCatalog>; +export const GENERALIZED_ERROR_COPY = resolveUiMessageCatalog( + defineShippedUiMessageCatalog>()({ + 'zh-CN': { + timeout: '请求超时', + rate_limited: '触发模型速率限制', + auth_failed: '鉴权失败', + provider_error: '模型服务返回错误', + network_error: '网络错误', + }, + 'zh-TW': { + timeout: '請求逾時', + rate_limited: '已達模型速率限制', + auth_failed: '驗證失敗', + provider_error: '模型服務傳回錯誤', + network_error: '網路錯誤', + }, + en: { + timeout: 'Request timed out', + rate_limited: 'Rate limit exceeded', + auth_failed: 'Authentication failed', + provider_error: 'Provider returned an error', + network_error: 'Network error', + }, + }), +); export function generalizedErrorMessageForLocale( error: unknown, diff --git a/packages/core/src/relative-time-policy.ts b/packages/core/src/relative-time-policy.ts index b2ee1d8d79a..bf7d3b1e374 100644 --- a/packages/core/src/relative-time-policy.ts +++ b/packages/core/src/relative-time-policy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog } from './ui-locale.js'; +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog } from './ui-locale.js'; export const RELATIVE_HORIZON_MS = 7 * 24 * 60 * 60 * 1000; export const JUST_NOW_MS = 60_000; @@ -28,11 +28,13 @@ export const MONTH_MS = 30 * DAY_MS; export const YEAR_MS = 12 * MONTH_MS; const MAX_SIDEBAR_REFRESH_MS = 24 * DAY_MS; -export const JUST_NOW: UiCatalog = { - 'zh-CN': '刚刚', - 'zh-TW': '剛剛', - en: 'just now', -}; +export const JUST_NOW = resolveUiMessageCatalog( + defineShippedUiMessageCatalog()({ + 'zh-CN': '刚刚', + 'zh-TW': '剛剛', + en: 'just now', + }), +); const SIDEBAR_TIME_BUCKETS = [ { unitMs: MINUTE_MS, maxValue: 60, suffix: 'min' }, diff --git a/packages/core/src/tool-quiet-preview.ts b/packages/core/src/tool-quiet-preview.ts index b82b8bab849..6f1720c1aad 100644 --- a/packages/core/src/tool-quiet-preview.ts +++ b/packages/core/src/tool-quiet-preview.ts @@ -17,6 +17,8 @@ * under the License. */ +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog } from './ui-locale.js'; + /** * Quiet-panel formatting for tool args + generic JSON results. * @@ -52,41 +54,43 @@ interface QuietPreviewStrings { unanswered: string; } -const STRINGS_BY_LOCALE: Record = { - 'zh-CN': { - backgroundTerminal: '后台终端交互', - empty: '(空)', - done: '已完成', - notDone: '未完成', - replacements: (n) => `${n} 处`, - written: '已写入', - bytes: (n) => `共 ${n} 字节`, - moreQuestions: (total) => (total > 1 ? ` 等 ${total} 问` : ''), - unanswered: '未回答', - }, - 'zh-TW': { - backgroundTerminal: '後臺終端互動', - empty: '(空)', - done: '已完成', - notDone: '未完成', - replacements: (n) => `${n} 處`, - written: '已寫入', - bytes: (n) => `共 ${n} 位元組`, - moreQuestions: (total) => (total > 1 ? ` 等 ${total} 問` : ''), - unanswered: '未回答', - }, - en: { - backgroundTerminal: 'Background terminal interaction', - empty: '(empty)', - done: 'done', - notDone: 'not done', - replacements: (n) => (n === 1 ? '1 replacement' : `${n} replacements`), - written: 'written', - bytes: (n) => `${n} bytes`, - moreQuestions: (total) => (total > 1 ? ` +${total - 1} more` : ''), - unanswered: 'Not answered', - }, -}; +const STRINGS_BY_LOCALE: Record = resolveUiMessageCatalog( + defineShippedUiMessageCatalog()({ + 'zh-CN': { + backgroundTerminal: '后台终端交互', + empty: '(空)', + done: '已完成', + notDone: '未完成', + replacements: (n) => `${n} 处`, + written: '已写入', + bytes: (n) => `共 ${n} 字节`, + moreQuestions: (total) => (total > 1 ? ` 等 ${total} 问` : ''), + unanswered: '未回答', + }, + 'zh-TW': { + backgroundTerminal: '後臺終端互動', + empty: '(空)', + done: '已完成', + notDone: '未完成', + replacements: (n) => `${n} 處`, + written: '已寫入', + bytes: (n) => `共 ${n} 位元組`, + moreQuestions: (total) => (total > 1 ? ` 等 ${total} 問` : ''), + unanswered: '未回答', + }, + en: { + backgroundTerminal: 'Background terminal interaction', + empty: '(empty)', + done: 'done', + notDone: 'not done', + replacements: (n) => (n === 1 ? '1 replacement' : `${n} replacements`), + written: 'written', + bytes: (n) => `${n} bytes`, + moreQuestions: (total) => (total > 1 ? ` +${total - 1} more` : ''), + unanswered: 'Not answered', + }, + }), +); function strings(locale: UiLocale): QuietPreviewStrings { return STRINGS_BY_LOCALE[locale] ?? STRINGS_BY_LOCALE['zh-CN']; diff --git a/packages/core/src/ui-locale.ts b/packages/core/src/ui-locale.ts index 2272506d792..2154afaba84 100644 --- a/packages/core/src/ui-locale.ts +++ b/packages/core/src/ui-locale.ts @@ -32,7 +32,24 @@ export const UI_LOCALE_PREFERENCES = ['auto', ...UI_LOCALES] as const; /** A catalog must carry copy for every supported resolved locale. */ export type UiCatalog = Record; -type DeepPartial = T extends readonly unknown[] +/** Existing translations remain complete while new locales roll out gradually. */ +export const SHIPPED_UI_LOCALES = ['en', 'zh-CN', 'zh-TW'] as const; +export type ShippedUiLocale = (typeof SHIPPED_UI_LOCALES)[number]; +export type ShippedUiCatalog = Record; + +/** Widen literal copy without losing function signatures or nested keys. */ +export type UiMessageShape = T extends MessageFunction + ? T + : T extends string + ? string + : T extends object + ? { [Key in keyof T]: UiMessageShape } + : T; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type MessageFunction = (...args: any[]) => unknown; + +type DeepPartial = T extends readonly unknown[] | MessageFunction ? T : T extends object ? { readonly [Key in keyof T]?: DeepPartial } @@ -44,20 +61,24 @@ export type UiMessageCatalog = { readonly [Locale in Exclude]: DeepPartial; }>; -type ExactMessageShape = Actual extends readonly unknown[] - ? NonNullable extends readonly unknown[] +type ExactMessageShape = Actual extends MessageFunction + ? NonNullable extends MessageFunction ? Actual : never - : Actual extends object - ? Exclude> extends never - ? { - readonly [Key in keyof Actual]: ExactMessageShape< - Actual[Key], - NonNullable[Key & keyof NonNullable] - >; - } + : Actual extends readonly unknown[] + ? NonNullable extends readonly unknown[] + ? Actual : never - : Actual; + : Actual extends object + ? Exclude> extends never + ? { + readonly [Key in keyof Actual]: ExactMessageShape< + Actual[Key], + NonNullable[Key & keyof NonNullable] + >; + } + : never + : Actual; const messageFormatters = new Map(); @@ -74,6 +95,20 @@ export function defineUiMessageCatalog() { ): UiMessageCatalog => catalog; } +/** Require complete shipped copy; only future locales may use partial overlays. */ +export function defineShippedUiMessageCatalog() { + return & UiMessageCatalog>( + catalog: Catalog & { + readonly [Locale in ShippedUiLocale]: ExactMessageShape; + } & { + readonly [Locale in Exclude]?: ExactMessageShape< + NonNullable, + DeepPartial + >; + }, + ): UiMessageCatalog => catalog; +} + export function resolveUiMessageCatalog(catalog: UiMessageCatalog): UiCatalog { return Object.fromEntries( UI_LOCALES.map((locale) => [ @@ -108,9 +143,12 @@ function mergeUiMessages(fallback: T, translation: DeepPartial | undefined if (translation === undefined) return fallback; if (!isMessageRecord(fallback) || !isMessageRecord(translation)) return translation as T; return Object.fromEntries( - Object.entries(fallback).map(([key, value]) => [ + [...new Set([...Object.keys(fallback), ...Object.keys(translation)])].map((key) => [ key, - mergeUiMessages(value, Object.hasOwn(translation, key) ? translation[key] : undefined), + mergeUiMessages( + fallback[key], + Object.hasOwn(translation, key) ? translation[key] : undefined, + ), ]), ) as T; } diff --git a/packages/runtime-host/src/client/host-handoff-copy.ts b/packages/runtime-host/src/client/host-handoff-copy.ts index 31021046028..3f376d0a6d7 100644 --- a/packages/runtime-host/src/client/host-handoff-copy.ts +++ b/packages/runtime-host/src/client/host-handoff-copy.ts @@ -17,7 +17,11 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { + type UiLocale, + defineShippedUiMessageCatalog, + resolveUiMessageCatalog, +} from '@maka/core/ui-locale'; import { isHostActivityIdle } from '../protocol/host-status.js'; import type { HostHandoffAction, @@ -54,202 +58,206 @@ interface HostHandoffCopy { progressDetail: string; } -const COPY = { - 'zh-CN': { - titles: { - replacement_required: '需要切换 WSL 后台服务', - busy: '后台服务仍在使用中', - activity_unknown: '需要确认是否停止后台服务', - operator_required: '后台服务需要手动更新', - repair_required: '后台服务需要修复', - retry_required: '暂时无法完成交接', +const COPY = resolveUiMessageCatalog( + defineShippedUiMessageCatalog()({ + 'zh-CN': { + titles: { + replacement_required: '需要切换 WSL 后台服务', + busy: '后台服务仍在使用中', + activity_unknown: '需要确认是否停止后台服务', + operator_required: '后台服务需要手动更新', + repair_required: '后台服务需要修复', + retry_required: '暂时无法完成交接', + }, + descriptions: (targetName: string) => ({ + replacement_required: + '当前尚未更新。选择继续后,将通过已安装的管理程序安全停止旧服务并启动选定版本;有活动工作时会另行确认。', + busy: '其它连接或正在执行的工作阻止了自动交接。停止并继续可能中断这些工作。', + activity_unknown: + '后台服务与当前客户端不兼容,且无法确认有哪些工作仍在运行。停止并继续会重新启动服务,可能中断其他窗口或设备上的工作。', + operator_required: `目前无法从这里更新后台服务。请通过 ${targetName} 上管理此服务的应用或命令更新,然后重试。`, + repair_required: + '上次启动或交接失败。请查看下方诊断信息,修复原因后再重试;如果状态没有变化,重复重试通常不会解决问题。', + retry_required: '服务状态发生了变化,或交接尚未完成。可以安全重试,不会默认中断工作。', + }), + recoveryGuidance: { + managed: + '此后台服务由安装它的 Maka 管理。如果由 Desktop 安装,请在该 Desktop 中打开此工作区,然后选择“停止旧服务并继续”;否则请使用安装它的 Maka 更新服务,再回到这里重试。', + owner: '此后台服务属于另一个 Maka 安装。请使用管理它的 Maka 安装更新,然后重试。', + installation: + '本次启动使用临时安装,无法接管现有后台服务。请使用已安装的 Maka 更新服务,然后重试。', + identity: + '无法确认旧后台服务的进程身份,因此不能安全停止它。请关闭启动它的 Maka 或通过其管理程序停止服务,然后重试。', + }, + manualBusy: + '旧服务拒绝安全停止;这可能是其他连接或后台工作所致。关闭其他客户端后可再次尝试安全停止,或明确选择中断工作并继续。', + manualOperatorTitle: '无法连接后台服务', + manualOperatorDescription: + '当前版本无法连接,且尚未执行更新。请查看诊断或更新客户端,完成后重新检查。', + activity: (connections: number, operations: number) => + `${connections} 个连接 · ${operations} 个进行中的操作`, + background: (count: number) => `${count} 个后台工作`, + backgroundUnknown: '后台工作数量未知', + waiting: { + recheck: '重新检查只会重试连接,不会更新服务。', + natural: 'Maka 会持续检查,并在可以安全继续时自动继续。', + blocked: 'Maka 会持续检查。若状态没有变化,等待或重试不会解决此问题。', + }, + repairNotice: '修复将使用当前应用配套的版本,可能替换现有服务版本。', + labels: { + cancel: '取消', + retry: '安全重试', + recheck: '重新检查', + replace: '停止旧服务并继续', + interrupt: '停止并继续', + interruptRepair: '中断并修复', + interruptRecheck: '中断工作并继续', + }, + phases: { + checking: '正在检查服务', + staging: '正在准备更新', + pausing: '正在等待安全暂停点', + retiring: '正在停止旧服务', + replacing: '正在替换服务', + verifying: '正在确认工作区已就绪', + }, + progressTitle: '正在继续打开工作区', + progressDetail: '正在完成交接或安全收尾,请稍候。', }, - descriptions: (targetName) => ({ - replacement_required: - '当前尚未更新。选择继续后,将通过已安装的管理程序安全停止旧服务并启动选定版本;有活动工作时会另行确认。', - busy: '其它连接或正在执行的工作阻止了自动交接。停止并继续可能中断这些工作。', - activity_unknown: - '后台服务与当前客户端不兼容,且无法确认有哪些工作仍在运行。停止并继续会重新启动服务,可能中断其他窗口或设备上的工作。', - operator_required: `目前无法从这里更新后台服务。请通过 ${targetName} 上管理此服务的应用或命令更新,然后重试。`, - repair_required: - '上次启动或交接失败。请查看下方诊断信息,修复原因后再重试;如果状态没有变化,重复重试通常不会解决问题。', - retry_required: '服务状态发生了变化,或交接尚未完成。可以安全重试,不会默认中断工作。', - }), - recoveryGuidance: { - managed: - '此后台服务由安装它的 Maka 管理。如果由 Desktop 安装,请在该 Desktop 中打开此工作区,然后选择“停止旧服务并继续”;否则请使用安装它的 Maka 更新服务,再回到这里重试。', - owner: '此后台服务属于另一个 Maka 安装。请使用管理它的 Maka 安装更新,然后重试。', - installation: - '本次启动使用临时安装,无法接管现有后台服务。请使用已安装的 Maka 更新服务,然后重试。', - identity: - '无法确认旧后台服务的进程身份,因此不能安全停止它。请关闭启动它的 Maka 或通过其管理程序停止服务,然后重试。', + 'zh-TW': { + titles: { + replacement_required: '需要切換 WSL 背景服務', + busy: '背景服務仍在使用中', + activity_unknown: '需要確認是否停止背景服務', + operator_required: '背景服務需要手動更新', + repair_required: '背景服務需要修復', + retry_required: '暫時無法完成交接', + }, + descriptions: (targetName: string) => ({ + replacement_required: + '目前尚未更新。選擇繼續後,將透過已安裝的管理程式安全停止舊服務並啟動選定版本;有進行中的工作時會另行確認。', + busy: '其他連線或正在執行的工作阻止了自動交接。停止並繼續可能中斷這些工作。', + activity_unknown: + '背景服務與目前用戶端不相容,且無法確認有哪些工作仍在執行。停止並繼續會重新啟動服務,可能中斷其他視窗或裝置上的工作。', + operator_required: `目前無法從這裡更新背景服務。請透過 ${targetName} 上管理此服務的應用程式或命令更新,再重試。`, + repair_required: + '上次啟動或交接失敗。請查看下方診斷資訊,修復原因後再重試;如果狀態沒有改變,重複重試通常不會解決問題。', + retry_required: '服務狀態發生了變化,或交接尚未完成。可以安全重試,不會預設中斷工作。', + }), + recoveryGuidance: { + managed: + '此背景服務由安裝它的 Maka 管理。如果由 Desktop 安裝,請在該 Desktop 中開啟此工作區,然後選擇「停止舊服務並繼續」;否則請使用安裝它的 Maka 更新服務,再回到這裡重試。', + owner: '此背景服務屬於另一個 Maka 安裝。請使用管理它的 Maka 安裝更新,再重試。', + installation: + '這次啟動使用暫存安裝,無法接管現有背景服務。請使用已安裝的 Maka 更新服務,再重試。', + identity: + '無法確認舊背景服務的處理程序身分,因此不能安全停止它。請關閉啟動它的 Maka 或透過其管理程式停止服務,再重試。', + }, + manualBusy: + '舊服務拒絕安全停止;這可能是其他連線或背景工作所致。關閉其他用戶端後可再次嘗試安全停止,或明確選擇中斷工作並繼續。', + manualOperatorTitle: '無法連線到背景服務', + manualOperatorDescription: + '目前版本無法連線,且尚未執行更新。請查看診斷或更新用戶端,完成後重新檢查。', + activity: (connections: number, operations: number) => + `${connections} 個連線 · ${operations} 個進行中的操作`, + background: (count: number) => `${count} 個背景工作`, + backgroundUnknown: '背景工作數量未知', + waiting: { + recheck: '重新檢查只會重試連線,不會更新服務。', + natural: 'Maka 會持續檢查,並在可以安全繼續時自動繼續。', + blocked: 'Maka 會持續檢查。若狀態沒有改變,等待或重試不會解決此問題。', + }, + repairNotice: '修復將使用目前應用程式配套的版本,可能替換現有服務版本。', + labels: { + cancel: '取消', + retry: '安全重試', + recheck: '重新檢查', + replace: '停止舊服務並繼續', + interrupt: '停止並繼續', + interruptRepair: '中斷並修復', + interruptRecheck: '中斷工作並繼續', + }, + phases: { + checking: '正在檢查服務', + staging: '正在準備更新', + pausing: '正在等待安全暫停點', + retiring: '正在停止舊服務', + replacing: '正在替換服務', + verifying: '正在確認工作區已就緒', + }, + progressTitle: '正在繼續開啟工作區', + progressDetail: '正在完成交接或安全收尾,請稍候。', }, - manualBusy: - '旧服务拒绝安全停止;这可能是其他连接或后台工作所致。关闭其他客户端后可再次尝试安全停止,或明确选择中断工作并继续。', - manualOperatorTitle: '无法连接后台服务', - manualOperatorDescription: - '当前版本无法连接,且尚未执行更新。请查看诊断或更新客户端,完成后重新检查。', - activity: (connections, operations) => `${connections} 个连接 · ${operations} 个进行中的操作`, - background: (count) => `${count} 个后台工作`, - backgroundUnknown: '后台工作数量未知', - waiting: { - recheck: '重新检查只会重试连接,不会更新服务。', - natural: 'Maka 会持续检查,并在可以安全继续时自动继续。', - blocked: 'Maka 会持续检查。若状态没有变化,等待或重试不会解决此问题。', + en: { + titles: { + replacement_required: 'Switch the WSL background service', + busy: 'Your background service is still in use', + activity_unknown: 'Confirm before stopping the service', + operator_required: 'The background service needs a manual update', + repair_required: 'Your background service needs repair', + retry_required: 'The handoff could not finish yet', + }, + descriptions: (targetName: string) => ({ + replacement_required: + 'No update is running. Continue to retire the old service through its installed operator and start the selected version. Interrupting active work requires a separate confirmation.', + busy: 'Other connections or work in progress prevent an automatic handoff. Stopping the service may interrupt that work.', + activity_unknown: + 'The background service is incompatible with this client, and its active work is unknown. Stop and continue restarts it and may interrupt work in other windows or devices.', + operator_required: `This client cannot update the background service here. Use its managing app or operator command on ${targetName}, then retry.`, + repair_required: + 'The last startup or handoff failed. Review the diagnostic below, correct the cause, then retry. Repeating the same action without a state change will usually fail again.', + retry_required: + 'The service changed or the handoff has not finished. A safe retry will not interrupt work by default.', + }), + recoveryGuidance: { + managed: + 'This background service is managed by the Maka installation that created it. If Desktop installed it, open this workspace there and choose Stop old service and continue. Otherwise update the service with that Maka installation, then return here and retry.', + owner: + 'Another Maka installation owns this background service. Update it using that installation, then retry.', + installation: + 'This temporary installation cannot take over the existing background service. Update it using an installed Maka, then retry.', + identity: + 'The old background process could not be identified safely. Close the Maka instance that started it or stop it through its operator, then retry.', + }, + manualBusy: + 'The old service refused safe retirement. Other connections or background work may be keeping it in use. Close other clients and try stopping safely again, or explicitly interrupt work and continue.', + manualOperatorTitle: 'Cannot connect to the background service', + manualOperatorDescription: + 'These builds cannot connect and no update is running. Check the diagnostic or update the client, then recheck.', + activity: (connections: number, operations: number) => + `${connections} connections · ${operations} operations in progress`, + background: (count: number) => `${count} background activities`, + backgroundUnknown: 'Background activity count unknown', + waiting: { + recheck: 'Recheck retries the connection without updating the service.', + natural: 'Maka keeps checking and continues automatically when it is safe.', + blocked: + 'Maka keeps checking. Waiting or retrying will not resolve this unless the service state changes.', + }, + repairNotice: + 'Repair uses the version supplied with this app and may replace the installed service version.', + labels: { + cancel: 'Cancel', + retry: 'Retry safely', + recheck: 'Recheck', + replace: 'Stop old service and continue', + interrupt: 'Stop and continue', + interruptRepair: 'Interrupt and repair', + interruptRecheck: 'Interrupt work and continue', + }, + phases: { + checking: 'Checking the service', + staging: 'Preparing the update', + pausing: 'Waiting for a safe pause point', + retiring: 'Stopping the previous service', + replacing: 'Replacing the service', + verifying: 'Verifying your workspace is ready', + }, + progressTitle: 'Continuing to your workspace', + progressDetail: 'Finishing the handoff or its safe recovery. Please wait.', }, - repairNotice: '修复将使用当前应用配套的版本,可能替换现有服务版本。', - labels: { - cancel: '取消', - retry: '安全重试', - recheck: '重新检查', - replace: '停止旧服务并继续', - interrupt: '停止并继续', - interruptRepair: '中断并修复', - interruptRecheck: '中断工作并继续', - }, - phases: { - checking: '正在检查服务', - staging: '正在准备更新', - pausing: '正在等待安全暂停点', - retiring: '正在停止旧服务', - replacing: '正在替换服务', - verifying: '正在确认工作区已就绪', - }, - progressTitle: '正在继续打开工作区', - progressDetail: '正在完成交接或安全收尾,请稍候。', - }, - 'zh-TW': { - titles: { - replacement_required: '需要切換 WSL 背景服務', - busy: '背景服務仍在使用中', - activity_unknown: '需要確認是否停止背景服務', - operator_required: '背景服務需要手動更新', - repair_required: '背景服務需要修復', - retry_required: '暫時無法完成交接', - }, - descriptions: (targetName) => ({ - replacement_required: - '目前尚未更新。選擇繼續後,將透過已安裝的管理程式安全停止舊服務並啟動選定版本;有進行中的工作時會另行確認。', - busy: '其他連線或正在執行的工作阻止了自動交接。停止並繼續可能中斷這些工作。', - activity_unknown: - '背景服務與目前用戶端不相容,且無法確認有哪些工作仍在執行。停止並繼續會重新啟動服務,可能中斷其他視窗或裝置上的工作。', - operator_required: `目前無法從這裡更新背景服務。請透過 ${targetName} 上管理此服務的應用程式或命令更新,再重試。`, - repair_required: - '上次啟動或交接失敗。請查看下方診斷資訊,修復原因後再重試;如果狀態沒有改變,重複重試通常不會解決問題。', - retry_required: '服務狀態發生了變化,或交接尚未完成。可以安全重試,不會預設中斷工作。', - }), - recoveryGuidance: { - managed: - '此背景服務由安裝它的 Maka 管理。如果由 Desktop 安裝,請在該 Desktop 中開啟此工作區,然後選擇「停止舊服務並繼續」;否則請使用安裝它的 Maka 更新服務,再回到這裡重試。', - owner: '此背景服務屬於另一個 Maka 安裝。請使用管理它的 Maka 安裝更新,再重試。', - installation: - '這次啟動使用暫存安裝,無法接管現有背景服務。請使用已安裝的 Maka 更新服務,再重試。', - identity: - '無法確認舊背景服務的處理程序身分,因此不能安全停止它。請關閉啟動它的 Maka 或透過其管理程式停止服務,再重試。', - }, - manualBusy: - '舊服務拒絕安全停止;這可能是其他連線或背景工作所致。關閉其他用戶端後可再次嘗試安全停止,或明確選擇中斷工作並繼續。', - manualOperatorTitle: '無法連線到背景服務', - manualOperatorDescription: - '目前版本無法連線,且尚未執行更新。請查看診斷或更新用戶端,完成後重新檢查。', - activity: (connections, operations) => `${connections} 個連線 · ${operations} 個進行中的操作`, - background: (count) => `${count} 個背景工作`, - backgroundUnknown: '背景工作數量未知', - waiting: { - recheck: '重新檢查只會重試連線,不會更新服務。', - natural: 'Maka 會持續檢查,並在可以安全繼續時自動繼續。', - blocked: 'Maka 會持續檢查。若狀態沒有改變,等待或重試不會解決此問題。', - }, - repairNotice: '修復將使用目前應用程式配套的版本,可能替換現有服務版本。', - labels: { - cancel: '取消', - retry: '安全重試', - recheck: '重新檢查', - replace: '停止舊服務並繼續', - interrupt: '停止並繼續', - interruptRepair: '中斷並修復', - interruptRecheck: '中斷工作並繼續', - }, - phases: { - checking: '正在檢查服務', - staging: '正在準備更新', - pausing: '正在等待安全暫停點', - retiring: '正在停止舊服務', - replacing: '正在替換服務', - verifying: '正在確認工作區已就緒', - }, - progressTitle: '正在繼續開啟工作區', - progressDetail: '正在完成交接或安全收尾,請稍候。', - }, - en: { - titles: { - replacement_required: 'Switch the WSL background service', - busy: 'Your background service is still in use', - activity_unknown: 'Confirm before stopping the service', - operator_required: 'The background service needs a manual update', - repair_required: 'Your background service needs repair', - retry_required: 'The handoff could not finish yet', - }, - descriptions: (targetName) => ({ - replacement_required: - 'No update is running. Continue to retire the old service through its installed operator and start the selected version. Interrupting active work requires a separate confirmation.', - busy: 'Other connections or work in progress prevent an automatic handoff. Stopping the service may interrupt that work.', - activity_unknown: - 'The background service is incompatible with this client, and its active work is unknown. Stop and continue restarts it and may interrupt work in other windows or devices.', - operator_required: `This client cannot update the background service here. Use its managing app or operator command on ${targetName}, then retry.`, - repair_required: - 'The last startup or handoff failed. Review the diagnostic below, correct the cause, then retry. Repeating the same action without a state change will usually fail again.', - retry_required: - 'The service changed or the handoff has not finished. A safe retry will not interrupt work by default.', - }), - recoveryGuidance: { - managed: - 'This background service is managed by the Maka installation that created it. If Desktop installed it, open this workspace there and choose Stop old service and continue. Otherwise update the service with that Maka installation, then return here and retry.', - owner: - 'Another Maka installation owns this background service. Update it using that installation, then retry.', - installation: - 'This temporary installation cannot take over the existing background service. Update it using an installed Maka, then retry.', - identity: - 'The old background process could not be identified safely. Close the Maka instance that started it or stop it through its operator, then retry.', - }, - manualBusy: - 'The old service refused safe retirement. Other connections or background work may be keeping it in use. Close other clients and try stopping safely again, or explicitly interrupt work and continue.', - manualOperatorTitle: 'Cannot connect to the background service', - manualOperatorDescription: - 'These builds cannot connect and no update is running. Check the diagnostic or update the client, then recheck.', - activity: (connections, operations) => - `${connections} connections · ${operations} operations in progress`, - background: (count) => `${count} background activities`, - backgroundUnknown: 'Background activity count unknown', - waiting: { - recheck: 'Recheck retries the connection without updating the service.', - natural: 'Maka keeps checking and continues automatically when it is safe.', - blocked: - 'Maka keeps checking. Waiting or retrying will not resolve this unless the service state changes.', - }, - repairNotice: - 'Repair uses the version supplied with this app and may replace the installed service version.', - labels: { - cancel: 'Cancel', - retry: 'Retry safely', - recheck: 'Recheck', - replace: 'Stop old service and continue', - interrupt: 'Stop and continue', - interruptRepair: 'Interrupt and repair', - interruptRecheck: 'Interrupt work and continue', - }, - phases: { - checking: 'Checking the service', - staging: 'Preparing the update', - pausing: 'Waiting for a safe pause point', - retiring: 'Stopping the previous service', - replacing: 'Replacing the service', - verifying: 'Verifying your workspace is ready', - }, - progressTitle: 'Continuing to your workspace', - progressDetail: 'Finishing the handoff or its safe recovery. Please wait.', - }, -} satisfies UiCatalog; + }), +); export interface HostHandoffPresentation { title: string; diff --git a/packages/ui/src/__tests__/tool-activity-presentation.test.ts b/packages/ui/src/__tests__/tool-activity-presentation.test.ts index e1196db7639..5d94cad4bc0 100644 --- a/packages/ui/src/__tests__/tool-activity-presentation.test.ts +++ b/packages/ui/src/__tests__/tool-activity-presentation.test.ts @@ -22,7 +22,7 @@ import { describe, it } from 'node:test'; import { createElement, type ReactNode } from 'react'; import { renderToStaticMarkup as renderReactToStaticMarkup } from 'react-dom/server'; import { computerUseModelCallArgs } from '@maka/core/computer-use'; -import { UI_LOCALES, type UiCatalog, type UiLocale } from '@maka/core/ui-locale'; +import { UI_LOCALES, type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { ToolCallDetail, ToolTrow } from '../tool-activity.js'; import type { ToolActivityItem } from '../materialize.js'; import { LocaleProvider } from '../locale-context.js'; @@ -82,11 +82,11 @@ describe('tool activity presentation', () => { assert.match(en, /Bypass mode required/); assert.match(en, /Switch and retry/); - const errorMessages = { - 'zh-CN': '需要“绕过”模式。此操作会直接控制本机应用,无法在沙箱模式下执行。', - 'zh-TW': '需要“繞過”模式。此操作會直接控制本機應用,無法在沙箱模式下執行。', - en: 'Bypass mode required. This action controls a local app directly and cannot run inside the sandbox.', - } satisfies UiCatalog; + const errorMessages = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ + 'zh-CN': '需要“绕过”模式。此操作会直接控制本机应用,无法在沙箱模式下执行。', + 'zh-TW': '需要“繞過”模式。此操作會直接控制本機應用,無法在沙箱模式下執行。', + en: 'Bypass mode required. This action controls a local app directly and cannot run inside the sandbox.', + })); for (const locale of UI_LOCALES) { const row = renderToStaticMarkup(createElement(ToolTrow, { items: [item] }), locale); assert.ok(row.includes(`title="${errorMessages[locale]}"`), `${locale}: full bypass error`); diff --git a/packages/ui/src/astryx-i18n.tsx b/packages/ui/src/astryx-i18n.tsx index d1cee514857..5740c57f8ec 100644 --- a/packages/ui/src/astryx-i18n.tsx +++ b/packages/ui/src/astryx-i18n.tsx @@ -26,7 +26,7 @@ import type { Overrides } from '@astryxdesign/core/i18n'; import { getSharedUiCopy } from './shared-ui-copy.js'; import { ASTRYX_COPY_ZH, ASTRYX_COPY_ZH_TW } from './astryx-copy.js'; import { useUiLocale } from './locale-context.js'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import type { ShippedUiCatalog, UiCatalog, UiLocale } from '@maka/core/ui-locale'; /** * Astryx ships no `zh` message catalog: its built-in strings ("Copy code", @@ -99,10 +99,10 @@ const OVERRIDES_BY_LOCALE = { }, 'zh-CN': chineseOverrides('zh-CN', ASTRYX_COPY_ZH), 'zh-TW': chineseOverrides('zh-TW', ASTRYX_COPY_ZH_TW), -} satisfies UiCatalog; +} satisfies ShippedUiCatalog; export function astryxMessageOverrides(locale: UiLocale): Overrides { - return OVERRIDES_BY_LOCALE[locale]; + return (OVERRIDES_BY_LOCALE as Partial>)[locale] ?? OVERRIDES_BY_LOCALE.en; } // The catalogues live off-barrel in astryx-copy.ts because nothing outside diff --git a/packages/ui/src/conversation-copy.ts b/packages/ui/src/conversation-copy.ts index c279edb2305..ed882c62ba4 100644 --- a/packages/ui/src/conversation-copy.ts +++ b/packages/ui/src/conversation-copy.ts @@ -21,7 +21,7 @@ import type { ProviderRetryReason } from '@maka/core/events'; import type { PermissionMode } from '@maka/core/permission'; import type { SessionBlockedReason, SessionStatus } from '@maka/core/session'; import type { ThinkingLevel } from '@maka/core/model-thinking'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { formatCompactTokenCount } from './compact-token-count.js'; export type DayPeriod = 'morning' | 'noon' | 'afternoon' | 'evening'; @@ -438,9 +438,7 @@ export interface ConversationCopy { archiveCount: (count: number) => string; }; } - -const CONVERSATION_COPY = { - 'zh-CN': { +const conversationCopyZhCn: ConversationCopy = { empty: { ariaLabel: '开始任务', surfaceAriaLabel: '新任务对话', @@ -563,8 +561,8 @@ const CONVERSATION_COPY = { blockedReason: { NO_REAL_CONNECTION: '等待配置可用模型连接', auth: '需要重新登录', permission_required: '等待权限确认', tool_failed: '工具调用失败', unknown: '运行中断,可重试' }, listAriaLabel: '任务列表', showMore: '显示更多', showMoreAriaLabel: (count) => `显示 ${count} 条更多任务`, renameAriaLabel: '重命名任务', renameProjectTitle: '重命名项目', renameSubmit: '保存', respondingAriaLabel: '正在响应', respondingTitle: '任务正在流式响应中', backgroundRunning: '子任务执行中', backgroundWaitingForUser: '子任务等待你的回应', backgroundBlocked: '子任务需要处理', staleTitle: '此任务使用的模型连接已不可用,发送时会切换到默认连接', staleAriaLabel: '任务已过期', stale: '已过期', unreadAriaLabel: '未读消息', actionsAriaLabel: (name) => `${name} 任务操作`, pin: '置顶', unpin: '取消置顶', rename: '重命名', archive: '归档', delete: '删除', moveToProject: '移动到项目', moveToNoProject: '移出项目', pinned: '置顶', recent: '最近', projects: '项目', groupByTime: '按时间', groupByProject: '按项目', groupingAriaLabel: '任务分组方式', groupingHeading: '任务', projectActionsAriaLabel: (name) => `${name} 项目操作`, projectNewTask: '新建任务', projectRename: '重命名', projectArchive: '归档', projectRestore: '恢复', projectRelink: '重新定位', projectUnavailable: '项目目录不可用', archivedProjects: '已归档项目', archivedProjectsAriaLabel: '展开已归档项目', worktreeAriaLabel: 'Git 工作树', promptRailAriaLabel: '按提问跳转', emptyPrompt: '(空提问)', jumpToPrompt: (preview) => `跳到提问:${preview}`, pickedAriaLabel: '已选中', pinCount: (count) => `置顶 ${count} 项`, unpinCount: (count) => `取消置顶 ${count} 项`, archiveCount: (count) => `归档 ${count} 项`, }, - }, - 'zh-TW': { + }; +const conversationCopyZhTw: ConversationCopy = { empty: { ariaLabel: '開始任務', surfaceAriaLabel: '新任務對話', @@ -687,8 +685,8 @@ const CONVERSATION_COPY = { blockedReason: { NO_REAL_CONNECTION: '等待設定可用模型連線', auth: '需要重新登入', permission_required: '等待權限確認', tool_failed: '工具呼叫失敗', unknown: '執行中斷,可重試' }, listAriaLabel: '任務列表', showMore: '顯示更多', showMoreAriaLabel: (count) => `顯示 ${count} 條更多工`, renameAriaLabel: '重新命名任務', renameProjectTitle: '重新命名專案', renameSubmit: '儲存', respondingAriaLabel: '正在響應', respondingTitle: '任務正在流式響應中', backgroundRunning: '子任務執行中', backgroundWaitingForUser: '子任務等待你的回應', backgroundBlocked: '子任務需要處理', staleTitle: '此任務使用的模型連線已不可用,傳送時會切換到預設連線', staleAriaLabel: '任務已過期', stale: '已過期', unreadAriaLabel: '未讀訊息', actionsAriaLabel: (name) => `${name} 任務操作`, pin: '置頂', unpin: '取消置頂', rename: '重新命名', archive: '歸檔', delete: '刪除', moveToProject: '移動到專案', moveToNoProject: '移出專案', pinned: '置頂', recent: '最近', projects: '專案', groupByTime: '按時間', groupByProject: '按專案', groupingAriaLabel: '任務分組方式', groupingHeading: '任務', projectActionsAriaLabel: (name) => `${name} 專案操作`, projectNewTask: '建立任務', projectRename: '重新命名', projectArchive: '歸檔', projectRestore: '恢復', projectRelink: '重新定位', projectUnavailable: '專案目錄不可用', archivedProjects: '已歸檔專案', archivedProjectsAriaLabel: '展開已歸檔專案', worktreeAriaLabel: 'Git 工作樹', promptRailAriaLabel: '按提問跳轉', emptyPrompt: '(空提問)', jumpToPrompt: (preview) => `跳到提問:${preview}`, pickedAriaLabel: '已選取', pinCount: (count) => `置頂 ${count} 項`, unpinCount: (count) => `取消置頂 ${count} 項`, archiveCount: (count) => `歸檔 ${count} 項`, }, - }, - en: { + }; +const conversationCopyEn: ConversationCopy = { empty: { ariaLabel: 'Start a task', surfaceAriaLabel: 'New task conversation', @@ -810,8 +808,9 @@ const CONVERSATION_COPY = { blockedReason: { NO_REAL_CONNECTION: 'Waiting for an available model connection', auth: 'Sign in again', permission_required: 'Waiting for permission', tool_failed: 'Tool call failed', unknown: 'Run interrupted; retry available' }, listAriaLabel: 'Task list', showMore: 'Show more', showMoreAriaLabel: (count) => `Show ${count} more tasks`, renameAriaLabel: 'Rename task', renameProjectTitle: 'Rename project', renameSubmit: 'Save', respondingAriaLabel: 'Responding', respondingTitle: 'This task is streaming a response', backgroundRunning: 'Subtasks running', backgroundWaitingForUser: 'Subtasks waiting for you', backgroundBlocked: 'Subtasks need attention', staleTitle: 'This task\'s model connection is unavailable; sending will switch to the default connection', staleAriaLabel: 'Stale task', stale: 'Stale', unreadAriaLabel: 'Unread messages', actionsAriaLabel: (name) => `${name} task actions`, pin: 'Pin', unpin: 'Unpin', rename: 'Rename', archive: 'Archive', delete: 'Delete', moveToProject: 'Move to project', moveToNoProject: 'Remove from project', pinned: 'Pinned', recent: 'Recent', projects: 'Projects', groupByTime: 'By time', groupByProject: 'By project', groupingAriaLabel: 'Task grouping', groupingHeading: 'Tasks', projectActionsAriaLabel: (name) => `${name} project actions`, projectNewTask: 'New task', projectRename: 'Rename', projectArchive: 'Archive', projectRestore: 'Restore', projectRelink: 'Relocate', projectUnavailable: 'Project directory unavailable', archivedProjects: 'Archived projects', archivedProjectsAriaLabel: 'Expand archived projects', worktreeAriaLabel: 'Git worktree', promptRailAriaLabel: 'Jump by prompt', emptyPrompt: '(empty prompt)', jumpToPrompt: (preview) => `Jump to prompt: ${preview}`, pickedAriaLabel: 'Selected', pinCount: (count) => `Pin ${count} tasks`, unpinCount: (count) => `Unpin ${count} tasks`, archiveCount: (count) => `Archive ${count} tasks`, }, - }, -} satisfies UiCatalog; + }; + +const CONVERSATION_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': conversationCopyZhCn, 'zh-TW': conversationCopyZhTw, en: conversationCopyEn })); export function getConversationCopy(locale: UiLocale): ConversationCopy { return CONVERSATION_COPY[locale]; diff --git a/packages/ui/src/daily-review-copy.ts b/packages/ui/src/daily-review-copy.ts index 298ba3167b2..57315d0d33f 100644 --- a/packages/ui/src/daily-review-copy.ts +++ b/packages/ui/src/daily-review-copy.ts @@ -19,7 +19,7 @@ import type { DailyReviewArchive } from '@maka/core/daily-review'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; type ArchiveSectionKey = keyof DailyReviewArchive['sections']; @@ -102,9 +102,7 @@ export interface DailyReviewCopy { requestCount: (count: number) => string; }; } - -const DAILY_REVIEW_COPY = { - 'zh-CN': { +const dailyReviewCopyZhCn: DailyReviewCopy = { archive: { section: { summary: '任务摘要', gaps: '遗漏提醒', usage: '使用洞察', code: '代码建议' }, status: { ok: '已生成', no_model: '缺少模型', no_data: '无数据', failed: '生成失败', skipped: '已跳过' }, @@ -138,8 +136,8 @@ const DAILY_REVIEW_COPY = { markdown: { separator: ':', title: (dayLabel) => `# Maka · 每日回顾 · ${dayLabel}`, conversations: '任务', requests: '模型调用', tokens: 'Token', cost: '费用', errors: '错误', activeConversations: '活跃任务', modelUsage: '模型使用', toolCalls: '工具调用', requestCount: (count) => `${count} 次`, }, - }, - 'zh-TW': { + }; +const dailyReviewCopyZhTw: DailyReviewCopy = { archive: { section: { summary: '任務摘要', gaps: '遺漏提醒', usage: '使用洞察', code: '程式碼建議' }, status: { ok: '已生成', no_model: '缺少模型', no_data: '無資料', failed: '生成失敗', skipped: '已跳過' }, @@ -173,8 +171,8 @@ const DAILY_REVIEW_COPY = { markdown: { separator: ':', title: (dayLabel) => `# Maka · 每日回顧 · ${dayLabel}`, conversations: '任務', requests: '請求', tokens: 'Token', cost: '費用', errors: '錯誤', activeConversations: '活躍任務', modelUsage: '模型使用', toolCalls: '工具呼叫', requestCount: (count) => `${count} 次`, }, - }, - en: { + }; +const dailyReviewCopyEn: DailyReviewCopy = { archive: { section: { summary: 'Task summary', gaps: 'Missed items', usage: 'Usage insights', code: 'Code suggestions' }, status: { ok: 'Generated', no_model: 'Model unavailable', no_data: 'No data', failed: 'Generation failed', skipped: 'Skipped' }, @@ -208,8 +206,9 @@ const DAILY_REVIEW_COPY = { markdown: { separator: ':', title: (dayLabel) => `# Maka · Daily review · ${dayLabel}`, conversations: 'Tasks', requests: 'Model calls', tokens: 'Tokens', cost: 'Cost', errors: 'Errors', activeConversations: 'Active tasks', modelUsage: 'Model usage', toolCalls: 'Tool calls', requestCount: (count) => `${count} ${count === 1 ? 'call' : 'calls'}`, }, - }, -} satisfies UiCatalog; + }; + +const DAILY_REVIEW_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': dailyReviewCopyZhCn, 'zh-TW': dailyReviewCopyZhTw, en: dailyReviewCopyEn })); export function getDailyReviewCopy(locale: UiLocale): DailyReviewCopy { return DAILY_REVIEW_COPY[locale]; diff --git a/packages/ui/src/executor-model-picker.tsx b/packages/ui/src/executor-model-picker.tsx index 734dca7c7fd..e63ede9f888 100644 --- a/packages/ui/src/executor-model-picker.tsx +++ b/packages/ui/src/executor-model-picker.tsx @@ -26,7 +26,7 @@ import type { } from '@maka/core/executor-catalog'; export type { ExecutorSelection } from '@maka/core/executor-catalog'; import type { ProviderType } from '@maka/core/llm-connections'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import { Settings, ICON_SIZE } from './icons.js'; import { ModelPickerPanel, ModelPickerPanelContext } from './model-picker-panel.js'; import { ComposerModelLabel } from './composer-model-label.js'; @@ -82,7 +82,7 @@ interface ExecutorCopy { modeFailed: string; } -const EXECUTOR_COPY = { +const EXECUTOR_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ en: { title: 'Executor', nativeOperations: 'This operation requires Maka. Start a new Maka task.', @@ -160,7 +160,7 @@ const EXECUTOR_COPY = { mode: '模式', modeFailed: '模式切換失敗,請重試。', }, -} satisfies UiCatalog; +})); export function executorCopy(locale: UiLocale): ExecutorCopy { return EXECUTOR_COPY[locale]; diff --git a/packages/ui/src/locale-helpers.ts b/packages/ui/src/locale-helpers.ts index 00a6045497e..b16e7884fa0 100644 --- a/packages/ui/src/locale-helpers.ts +++ b/packages/ui/src/locale-helpers.ts @@ -46,7 +46,7 @@ * dimension. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export type { UiLocale } from '@maka/core/ui-locale'; @@ -69,7 +69,7 @@ export type PromptSuggestion = { label: string; prompt: string }; * translate, draft a message) so the empty-chat surface reads as a * general assistant first, a coding assistant second. */ -const PROMPT_SUGGESTIONS_BY_LOCALE: UiCatalog = { +const PROMPT_SUGGESTIONS_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': [ { label: '总结代码库', prompt: '帮我总结当前代码库的目录结构和关键模块。' }, { label: '解释这段代码', prompt: '我贴一段代码进来,请帮我逐行解释它做什么、有没有坑:\n\n```\n\n```' }, @@ -94,7 +94,7 @@ const PROMPT_SUGGESTIONS_BY_LOCALE: UiCatalog = { { label: 'Draft message', prompt: 'Help me draft a ____ message to ____, with the goal of ____:\n\nPoints to cover:\n- \n- ' }, { label: 'Review code', prompt: 'Please review this code — readability, error handling, performance concerns:\n\n```\n\n```' }, ], -}; +})); export function getPromptSuggestions(locale: UiLocale): PromptSuggestion[] { return PROMPT_SUGGESTIONS_BY_LOCALE[locale]; diff --git a/packages/ui/src/progress-card.tsx b/packages/ui/src/progress-card.tsx index d6042914c79..206f543fd28 100644 --- a/packages/ui/src/progress-card.tsx +++ b/packages/ui/src/progress-card.tsx @@ -17,15 +17,19 @@ * under the License. */ +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog, type UiMessageShape } from '@maka/core/ui-locale'; + import type { ReactNode, Ref } from 'react'; import { IconButton } from '@astryxdesign/core'; import { MakaWordmark } from './maka-wordmark.js'; -export const progressStatusCopy = { - en: { working: 'Working', paused: 'Paused', attention: 'Needs attention', finished: 'Finished', openConversation: 'View full conversation' }, +const PROGRESSSTATUSCOPY_EN = { working: 'Working', paused: 'Paused', attention: 'Needs attention', finished: 'Finished', openConversation: 'View full conversation' }; + +export const progressStatusCopy = resolveUiMessageCatalog(defineShippedUiMessageCatalog>()({ + en: PROGRESSSTATUSCOPY_EN, 'zh-CN': { working: '正在处理', paused: '已暂停', attention: '需要查看', finished: '本轮已结束', openConversation: '查看完整对话' }, 'zh-TW': { working: '正在處理', paused: '已暫停', attention: '需要查看', finished: '本輪已結束', openConversation: '查看完整對話' }, -}; +})); type ProgressCardAction = { label: string; diff --git a/packages/ui/src/runtime-resume-copy.ts b/packages/ui/src/runtime-resume-copy.ts index 7ffb412374b..e24dfa0fd68 100644 --- a/packages/ui/src/runtime-resume-copy.ts +++ b/packages/ui/src/runtime-resume-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export interface ResumeParkToastCopy { title: string; @@ -69,7 +69,7 @@ interface ResumeParkCopy { reasons: ResumeParkReasonCopy; } -const RESUME_PARK_COPY = { +const RESUME_PARK_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { title: '暂时无法继续这一轮', fallbackDescription: '当前任务不满足继续的条件。', @@ -187,7 +187,7 @@ const RESUME_PARK_COPY = { resume_feature_disabled: 'Resuming interrupted tasks is not enabled.', }, }, -} satisfies UiCatalog; +})); export function resumeParkToastCopy(reasons: readonly string[], locale: UiLocale): ResumeParkToastCopy { const copy = RESUME_PARK_COPY[locale]; diff --git a/packages/ui/src/scheduled-task-copy.ts b/packages/ui/src/scheduled-task-copy.ts index 1dc267e75e1..6fec09b0044 100644 --- a/packages/ui/src/scheduled-task-copy.ts +++ b/packages/ui/src/scheduled-task-copy.ts @@ -18,7 +18,7 @@ */ import type { ScheduledTask, ScheduledTaskRunOutcome, ScheduledTaskStatus } from '@maka/core/scheduled-task'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { ScheduledTaskDelivery, ScheduledTaskRecurrence, @@ -161,9 +161,7 @@ export interface ScheduledTaskCopy { agentDelivery: string; }; } - -const SCHEDULED_TASK_COPY = { - 'zh-CN': { +const scheduledTaskCopyZhCn: ScheduledTaskCopy = { templates: [ { id: 'daily-download-cleanup', title: '每日下载文件夹清理', note: '请帮我整理「下载」文件夹,把截图、安装包和临时文档按类型归档,并列出可删除项。', scheduleLabel: '每天 18:30', recurrence: 'cron', cronExpression: '30 18 * * *', nextRun: { hour: 18, minute: 30 } }, { id: 'midday-reset', title: '午间充电站', note: '午休时间到了,帮我回顾上午完成了什么,并给下午列一个轻量可执行计划。', scheduleLabel: '工作日 12:30', recurrence: 'cron', cronExpression: '30 12 * * 1-5', nextRun: { hour: 12, minute: 30 } }, @@ -196,8 +194,8 @@ const SCHEDULED_TASK_COPY = { agentSourceHint: '到点后,Maka 会使用创建时的执行设置启动新任务。', agentDelivery: '交给 Agent 执行', }, - }, - 'zh-TW': { + }; +const scheduledTaskCopyZhTw: ScheduledTaskCopy = { templates: [ { id: 'daily-download-cleanup', title: '每日下載資料夾清理', note: '請幫我整理「下載」資料夾,把截圖、安裝包和臨時文件按型別歸檔,並列出可刪除項。', scheduleLabel: '每天 18:30', recurrence: 'cron', cronExpression: '30 18 * * *', nextRun: { hour: 18, minute: 30 } }, { id: 'midday-reset', title: '午間充電站', note: '午休時間到了,幫我回顧上午完成了什麼,並給下午列一個輕量可執行計劃。', scheduleLabel: '工作日 12:30', recurrence: 'cron', cronExpression: '30 12 * * 1-5', nextRun: { hour: 12, minute: 30 } }, @@ -230,8 +228,8 @@ const SCHEDULED_TASK_COPY = { agentSourceHint: '到點後,Maka 會使用建立時的執行設定啟動新任務。', agentDelivery: '交給 Agent 執行', }, - }, - en: { + }; +const scheduledTaskCopyEn: ScheduledTaskCopy = { templates: [ { id: 'daily-download-cleanup', title: 'Clean up Downloads', note: 'Organize screenshots, installers, and temporary documents in Downloads by type, then list items that can be deleted.', scheduleLabel: 'Daily at 18:30', recurrence: 'cron', cronExpression: '30 18 * * *', nextRun: { hour: 18, minute: 30 } }, { id: 'midday-reset', title: 'Midday reset', note: 'Review what I completed this morning and create a lightweight, actionable plan for the afternoon.', scheduleLabel: 'Weekdays at 12:30', recurrence: 'cron', cronExpression: '30 12 * * 1-5', nextRun: { hour: 12, minute: 30 } }, @@ -265,8 +263,9 @@ const SCHEDULED_TASK_COPY = { 'When due, Maka starts a new task using the execution settings captured at creation.', agentDelivery: 'Run via the Agent', }, - }, -} satisfies UiCatalog; + }; + +const SCHEDULED_TASK_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': scheduledTaskCopyZhCn, 'zh-TW': scheduledTaskCopyZhTw, en: scheduledTaskCopyEn })); export function getScheduledTaskCopy(locale: UiLocale): ScheduledTaskCopy { return SCHEDULED_TASK_COPY[locale]; diff --git a/packages/ui/src/session-hover-card-copy.ts b/packages/ui/src/session-hover-card-copy.ts index ffb05223430..66c9dc544ac 100644 --- a/packages/ui/src/session-hover-card-copy.ts +++ b/packages/ui/src/session-hover-card-copy.ts @@ -17,6 +17,8 @@ * under the License. */ +import { defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; + import type { UiLocale } from '@maka/core/ui-locale'; interface SessionHoverCardCopy { @@ -32,7 +34,7 @@ interface SessionHoverCardCopy { projectUnavailable: string; } -const COPY: Record = { +const COPY: Record = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { sessionDetailsLabel: (name) => `${name} 任务详情`, projectDetailsLabel: (name) => `${name} 项目详情`, @@ -69,7 +71,7 @@ const COPY: Record = { projectAvailable: 'Directory available', projectUnavailable: 'Directory unavailable', }, -}; +})); export function getSessionHoverCardCopy(locale: UiLocale): SessionHoverCardCopy { return COPY[locale]; diff --git a/packages/ui/src/shared-ui-copy.ts b/packages/ui/src/shared-ui-copy.ts index 74d2aed6366..c928a3a8e92 100644 --- a/packages/ui/src/shared-ui-copy.ts +++ b/packages/ui/src/shared-ui-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; export interface SharedUiCopy { capabilityAudit: { @@ -112,9 +112,7 @@ export interface SharedUiCopy { artifact: { unknownSize: string }; providers: { minimaxChina: string; custom: string; claudeSubscription: string }; } - -const SHARED_UI_COPY = { - 'zh-CN': { +const sharedUiCopyZhCn: SharedUiCopy = { capabilityAudit: { ariaLabel: '能力风险提示', needsAuthorization: (count) => `${count} 个来源等待授权`, @@ -193,8 +191,8 @@ const SHARED_UI_COPY = { stream: { assistantTailTruncated: '\n\n[…后续已截断]', thinkingHeadTruncated: '[…已截断早期 reasoning]\n', toolChunkTruncated: '\n[…已截断]\n' }, artifact: { unknownSize: '未知大小' }, providers: { minimaxChina: 'MiniMax 中国站', custom: '自定义', claudeSubscription: 'Claude 订阅' }, - }, - 'zh-TW': { + }; +const sharedUiCopyZhTw: SharedUiCopy = { capabilityAudit: { ariaLabel: '能力風險提示', needsAuthorization: (count) => `${count} 個來源等待授權`, @@ -273,8 +271,8 @@ const SHARED_UI_COPY = { stream: { assistantTailTruncated: '\n\n[…後續已截斷]', thinkingHeadTruncated: '[…已截斷早期 reasoning]\n', toolChunkTruncated: '\n[…已截斷]\n' }, artifact: { unknownSize: '未知大小' }, providers: { minimaxChina: 'MiniMax 中國站', custom: '自訂', claudeSubscription: 'Claude 訂閱' }, - }, - en: { + }; +const sharedUiCopyEn: SharedUiCopy = { capabilityAudit: { ariaLabel: 'Capability risks', needsAuthorization: (count) => `${count} ${count === 1 ? 'source' : 'sources'} awaiting authorization`, @@ -353,8 +351,9 @@ const SHARED_UI_COPY = { stream: { assistantTailTruncated: '\n\n[…remaining output truncated]', thinkingHeadTruncated: '[…earlier reasoning truncated]\n', toolChunkTruncated: '\n[…truncated]\n' }, artifact: { unknownSize: 'Unknown size' }, providers: { minimaxChina: 'MiniMax China', custom: 'Custom', claudeSubscription: 'Claude subscription' }, - }, -} satisfies UiCatalog; + }; + +const SHARED_UI_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': sharedUiCopyZhCn, 'zh-TW': sharedUiCopyZhTw, en: sharedUiCopyEn })); export function getSharedUiCopy(locale: UiLocale): SharedUiCopy { return SHARED_UI_COPY[locale]; diff --git a/packages/ui/src/shell-controls-copy.ts b/packages/ui/src/shell-controls-copy.ts index 3223641365c..8bc8d7ac9af 100644 --- a/packages/ui/src/shell-controls-copy.ts +++ b/packages/ui/src/shell-controls-copy.ts @@ -18,7 +18,7 @@ */ import type { RecallFailureReason } from '@maka/core/recall'; -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; /** * The failures the Search modal can show. Recall's own vocabulary, because @@ -58,7 +58,7 @@ type ShellControlsCopy = { }; }; -const SHELL_CONTROLS_COPY_BY_LOCALE = { +const SHELL_CONTROLS_COPY_BY_LOCALE = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': { shared: { close: '关闭' }, navigation: { @@ -150,7 +150,7 @@ const SHELL_CONTROLS_COPY_BY_LOCALE = { resultsLabel: 'Search results', }, }, -} satisfies UiCatalog; +})); export function getShellControlsCopy(locale: UiLocale): ShellControlsCopy { return SHELL_CONTROLS_COPY_BY_LOCALE[locale]; diff --git a/packages/ui/src/skills-copy.ts b/packages/ui/src/skills-copy.ts index 4a67307d05d..e890b6e2b92 100644 --- a/packages/ui/src/skills-copy.ts +++ b/packages/ui/src/skills-copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; import type { ManagedSkillCategory, SkillEntry } from './module-panel-types.js'; type ManagedUpdateStatus = NonNullable; @@ -122,9 +122,7 @@ export interface SkillsCopy { pathLabel: string; }; } - -const SKILLS_COPY = { - 'zh-CN': { +const skillsCopyZhCn: SkillsCopy = { categories: { '内容创作': '内容创作', '数据与AI': '数据与 AI', '设计与UI': '设计与 UI', 'DevOps与部署': 'DevOps 与部署', '文档与写作': '文档与写作', '效率工具': '效率工具', '研究与分析': '研究与分析' }, discover: { builtinFallback: '应用自带 Skill。', sourceFallback: '本地来源库 Skill。' }, install: { action: (name) => `安装 ${name}`, short: '安装' }, @@ -137,8 +135,8 @@ const SKILLS_COPY = { page: { title: '技能', actionsAria: '技能操作', installed: '已安装', discover: '发现', add: '添加', importLocal: '导入本地 Skill', searchMatches: (count) => `${count} 个匹配`, search: '搜索技能', clearSearch: '清空搜索', locations: '技能位置…', refreshing: '刷新中…', refresh: '刷新' }, locations: { labels: { 'project:maka': '项目 · Maka', 'project:agents': '项目 · Agents', 'workspace:legacy': '工作区兼容目录', 'user:maka': '用户 · Maka', 'user:agents': '用户 · Agents' }, count: (count) => `${count} 个 Skill`, missing: '创建并打开', blocked: '路径已被阻止', readFailed: '无法读取' }, detail: { enabled: '启用', idLabel: '标识', scopeLabel: '范围', toolsLabel: '工具', pathLabel: '路径' }, - }, - 'zh-TW': { + }; +const skillsCopyZhTw: SkillsCopy = { categories: { '内容创作': '內容創作', '数据与AI': '資料與 AI', '设计与UI': '設計與 UI', 'DevOps与部署': 'DevOps 與部署', '文档与写作': '文件與寫作', '效率工具': '效率工具', '研究与分析': '研究與分析' }, discover: { builtinFallback: '應用自帶 Skill。', sourceFallback: '本地來源庫 Skill。' }, install: { action: (name) => `安裝 ${name}`, short: '安裝' }, @@ -151,8 +149,8 @@ const SKILLS_COPY = { page: { title: '技能', actionsAria: '技能操作', installed: '已安裝', discover: '探索', add: '新增', importLocal: '匯入本地 Skill', searchMatches: (count) => `${count} 個符合`, search: '搜尋技能', clearSearch: '清空搜尋', locations: '技能位置…', refreshing: '重新整理中…', refresh: '重新整理' }, locations: { labels: { 'project:maka': '專案 · Maka', 'project:agents': '專案 · Agents', 'workspace:legacy': '工作區相容目錄', 'user:maka': '使用者 · Maka', 'user:agents': '使用者 · Agents' }, count: (count) => `${count} 個 Skill`, missing: '建立並開啟', blocked: '路徑已被阻止', readFailed: '無法讀取' }, detail: { enabled: '啟用', idLabel: '標識', scopeLabel: '範圍', toolsLabel: '工具', pathLabel: '路徑' }, - }, - en: { + }; +const skillsCopyEn: SkillsCopy = { categories: { '内容创作': 'Content creation', '数据与AI': 'Data & AI', '设计与UI': 'Design & UI', 'DevOps与部署': 'DevOps & deployment', '文档与写作': 'Documents & writing', '效率工具': 'Productivity', '研究与分析': 'Research & analysis' }, discover: { builtinFallback: 'Skill included with the app.', sourceFallback: 'Local source-library Skill.' }, install: { action: (name) => `Install ${name}`, short: 'Install' }, @@ -165,8 +163,9 @@ const SKILLS_COPY = { page: { title: 'Skills', actionsAria: 'Skill actions', installed: 'Installed', discover: 'Discover', add: 'Add', importLocal: 'Import local Skill', searchMatches: (count) => `${count} ${count === 1 ? 'match' : 'matches'}`, search: 'Search skills', clearSearch: 'Clear search', locations: 'Skill locations…', refreshing: 'Refreshing…', refresh: 'Refresh' }, locations: { labels: { 'project:maka': 'Project · Maka', 'project:agents': 'Project · Agents', 'workspace:legacy': 'Workspace compatibility folder', 'user:maka': 'User · Maka', 'user:agents': 'User · Agents' }, count: (count) => count === 1 ? '1 Skill' : `${count} Skills`, missing: 'Create and open', blocked: 'Path blocked', readFailed: 'Could not read' }, detail: { enabled: 'Enabled', idLabel: 'ID', scopeLabel: 'Scope', toolsLabel: 'Tools', pathLabel: 'Path' }, - }, -} satisfies UiCatalog; + }; + +const SKILLS_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': skillsCopyZhCn, 'zh-TW': skillsCopyZhTw, en: skillsCopyEn })); export function getSkillsCopy(locale: UiLocale): SkillsCopy { return SKILLS_COPY[locale]; diff --git a/packages/ui/src/tool-activity/copy.ts b/packages/ui/src/tool-activity/copy.ts index 8b042f8a714..cace115bc16 100644 --- a/packages/ui/src/tool-activity/copy.ts +++ b/packages/ui/src/tool-activity/copy.ts @@ -17,7 +17,7 @@ * under the License. */ -import type { UiCatalog, UiLocale } from '@maka/core/ui-locale'; +import { type UiLocale, defineShippedUiMessageCatalog, resolveUiMessageCatalog } from '@maka/core/ui-locale'; type BackgroundTerminalStatus = 'running' | 'completed' | 'failed' | 'timed_out' | 'cancelled' | 'orphaned'; type WebCredentialCopyKey = 'env' | 'settings' | 'missing' | 'unknown'; @@ -180,9 +180,7 @@ export interface ToolActivityCopy { readOnly: string; }; } - -const TOOL_ACTIVITY_COPY = { - 'zh-CN': { +const toolActivityCopyZhCn: ToolActivityCopy = { errorLabel: '错误', status: { sandboxBlocked: '可能被沙箱阻止', interrupted: '已中断' }, output: { redacted: '[已脱敏]', truncated: '输出已截断' }, @@ -289,8 +287,8 @@ const TOOL_ACTIVITY_COPY = { subagentStatus: { completed: '已完成', failed: '失败', cancelled: '已取消', running: '运行中', waiting_for_user: '等待用户输入' }, readOnly: '只读', }, - }, - 'zh-TW': { + }; +const toolActivityCopyZhTw: ToolActivityCopy = { errorLabel: '錯誤', status: { sandboxBlocked: '可能被沙箱阻止', interrupted: '已中斷' }, output: { redacted: '[已脫敏]', truncated: '輸出已截斷' }, @@ -397,8 +395,8 @@ const TOOL_ACTIVITY_COPY = { subagentStatus: { completed: '已完成', failed: '失敗', cancelled: '已取消', running: '執行中', waiting_for_user: '等待使用者輸入' }, readOnly: '只讀', }, - }, - en: { + }; +const toolActivityCopyEn: ToolActivityCopy = { errorLabel: 'Error', status: { sandboxBlocked: 'Possibly blocked by sandbox', interrupted: 'Interrupted' }, output: { redacted: '[Redacted]', truncated: 'Output truncated' }, @@ -502,8 +500,9 @@ const TOOL_ACTIVITY_COPY = { subagentStatus: { completed: 'Completed', failed: 'Failed', cancelled: 'Cancelled', running: 'Running', waiting_for_user: 'Waiting for user input' }, readOnly: 'Read only', }, - }, -} satisfies UiCatalog; + }; + +const TOOL_ACTIVITY_COPY = resolveUiMessageCatalog(defineShippedUiMessageCatalog()({ 'zh-CN': toolActivityCopyZhCn, 'zh-TW': toolActivityCopyZhTw, en: toolActivityCopyEn })); export function getToolActivityCopy(locale: UiLocale): ToolActivityCopy { return TOOL_ACTIVITY_COPY[locale];