From 3f9f36e92d376a2407962fd84b6034d2155d4493 Mon Sep 17 00:00:00 2001 From: 07akioni <07akioni2@gmail.com> Date: Wed, 21 Oct 2026 11:42:12 +0800 Subject: [PATCH 1/4] feat(locale): add Japanese client and desktop translations --- apps/desktop/README.i18n.yaml | 8 +- apps/desktop/README.md | 4 +- apps/desktop/README.zh.md | 4 +- apps/desktop/electron-builder.config.d.mts | 5 +- .../scripts/electron-builder-config.mjs | 6 +- apps/desktop/src/locale.ts | 179 ++++++++- apps/desktop/tests/locale.spec.ts | 12 +- apps/desktop/tests/macos-signature.spec.ts | 1 + apps/web/tests/desktop-locale.e2e.ts | 28 +- .../desktop-locale/automatic.expected.md | 76 ++-- apps/web/tests/ptc-round.e2e.ts | 25 +- apps/web/tests/support.ts | 7 +- packages/client/locale/README.i18n.yaml | 18 +- packages/client/locale/README.md | 18 +- packages/client/locale/README.zh.md | 16 +- packages/client/locale/src/client/index.ts | 15 +- packages/client/locale/src/locale-settings.ts | 4 +- packages/client/locale/src/locales/index.ts | 6 +- packages/client/locale/src/locales/ja.ts | 47 +++ .../client/locale/src/locales/settings.ts | 5 + .../client/locale/tests/apply.client.spec.ts | 34 +- .../tests/document-language.client.spec.ts | 2 +- .../src/client/guide-locales.ts | 23 ++ .../ui-agent-preset/src/client/index.ts | 5 +- .../ui-agent-preset/src/client/locales.ts | 35 +- .../tests/locales.client.spec.ts | 6 +- .../client/ui-approval/src/client/index.ts | 5 +- .../client/ui-approval/src/client/locales.ts | 9 + packages/client/ui-chat/src/client/apply.ts | 5 +- packages/client/ui-chat/src/client/locale.ts | 199 +++++++++ .../client/ui-commands/src/client/index.ts | 5 +- .../client/ui-commands/src/client/locales.ts | 32 ++ .../ui-conversation/src/client/apply.ts | 5 +- .../ui-conversation/src/client/locales.ts | 378 ++++++++++++++++++ .../src/client/index.ts | 5 +- .../src/client/locales.ts | 5 + .../ui-deliverables/src/client/index.ts | 5 +- .../ui-deliverables/src/client/locales.ts | 64 +++ .../src/client/index.ts | 17 +- .../src/client/index.ts | 5 +- .../src/client/locales.ts | 16 + packages/client/ui-goal/src/client/index.ts | 5 +- packages/client/ui-goal/src/client/locales.ts | 16 + .../ui-input-trigger/src/client/index.ts | 5 +- .../ui-input-trigger/src/client/locales.ts | 13 + packages/client/ui-jobs/src/client/index.ts | 5 +- packages/client/ui-jobs/src/client/locales.ts | 44 ++ packages/client/ui-layout/src/client/index.ts | 5 +- .../ui-layout/src/client/shortcut-locales.ts | 5 + .../ui-message-feedback/src/client/index.ts | 5 +- .../ui-message-feedback/src/client/locales.ts | 24 ++ .../ui-model-selection/src/client/index.ts | 5 +- .../ui-model-selection/src/client/locales.ts | 27 ++ .../client/ui-open-in-app/src/client/index.ts | 5 +- .../ui-open-in-app/src/client/locales.ts | 20 + .../ui-permission-presets/src/client/index.ts | 8 +- .../src/client/locales.ts | 37 ++ packages/client/ui-plan/src/client/index.ts | 5 +- packages/client/ui-plan/src/client/locales.ts | 21 + .../ui-plugin-manager/src/client/index.ts | 5 +- .../ui-plugin-manager/src/client/locales.ts | 288 +++++++++++++ .../ui-plugin-market/src/client/index.ts | 5 +- .../ui-plugin-market/src/client/locales.ts | 91 +++++ .../client/ui-reference/src/client/index.ts | 5 +- .../client/ui-reference/src/client/locales.ts | 19 + .../src/client/frequency-locales.ts | 58 +++ .../client/ui-schedule/src/client/index.ts | 10 +- .../client/ui-schedule/src/client/locales.ts | 23 +- .../src/client/task-manager-locales.ts | 141 ++++++- .../client/ui-secret-mask/src/client/index.ts | 5 +- .../ui-secret-mask/src/client/locales.ts | 6 + .../ui-settings-account/src/client/index.ts | 5 +- .../ui-settings-account/src/client/locales.ts | 62 ++- .../src/client/locales/onboarding.ts | 43 ++ .../src/client/index.ts | 5 +- .../src/client/locales.ts | 16 + .../ui-settings-general/src/client/index.ts | 5 +- .../ui-settings-general/src/client/locales.ts | 46 +++ .../ui-settings-models/src/client/index.ts | 5 +- .../ui-settings-models/src/client/locales.ts | 139 +++++++ .../src/client/index.ts | 5 +- .../src/client/locales.ts | 41 ++ .../ui-settings-plugins/src/client/index.ts | 5 +- .../ui-settings-plugins/src/client/locales.ts | 9 + .../src/client/index.ts | 5 +- .../src/client/locales.ts | 8 + .../ui-settings-shell/src/client/index.ts | 5 +- .../ui-settings-shell/src/client/locales.ts | 18 + .../ui-settings-subagent/src/client/index.ts | 5 +- .../src/client/locales.ts | 39 ++ .../src/client/index.ts | 5 +- .../src/client/locales.ts | 31 ++ .../client/ui-shortcuts/src/client/index.ts | 5 +- .../client/ui-shortcuts/src/client/locales.ts | 60 +++ .../ui-sidebar-browser/src/client/index.ts | 5 +- .../ui-sidebar-browser/src/client/locales.ts | 30 ++ .../tests/apply.client.spec.ts | 4 +- .../src/client/code/index.ts | 5 +- .../src/client/code/locales.ts | 7 + .../src/client/excel/index.ts | 5 +- .../src/client/excel/locales.ts | 19 + .../src/client/html/index.ts | 5 +- .../src/client/html/locales.ts | 8 + .../src/client/image/index.ts | 5 +- .../src/client/image/locales.ts | 12 +- .../src/client/index.ts | 5 +- .../src/client/locales.ts | 26 ++ .../src/client/markdown/index.ts | 5 +- .../src/client/markdown/locales.ts | 8 + .../src/client/office/index.ts | 5 +- .../src/client/office/locales.ts | 19 + .../src/client/pdf/index.ts | 5 +- .../src/client/pdf/locales.ts | 16 +- .../src/client/zoom/locales.ts | 10 + .../tests/apply.client.spec.ts | 4 +- .../tests/excel-registration.client.spec.ts | 4 +- .../tests/html-apply.client.spec.ts | 4 +- .../tests/image-registration.client.spec.ts | 4 +- .../tests/office-registration.client.spec.ts | 4 +- .../tests/pdf-registration.client.spec.ts | 4 +- .../ui-sidebar-files/src/client/index.ts | 5 +- .../ui-sidebar-files/src/client/locales.ts | 21 + .../tests/apply.client.spec.ts | 4 +- .../ui-sidebar-right/src/client/index.ts | 5 +- .../ui-sidebar-right/src/client/locales.ts | 39 ++ .../tests/apply.client.spec.ts | 4 +- .../ui-sidebar-terminal/src/client/index.ts | 5 +- .../ui-sidebar-terminal/src/client/locales.ts | 32 ++ .../tests/apply.client.spec.ts | 4 +- .../client/ui-sidebar/src/client/index.ts | 5 +- .../client/ui-sidebar/src/client/locales.ts | 11 + packages/client/ui-skill/src/client/index.ts | 5 +- .../client/ui-skill/src/client/locales.ts | 12 + .../tests/browser-plugin.client.spec.ts | 10 + .../client/ui-subagent/src/client/index.ts | 5 +- .../client/ui-subagent/src/client/locales.ts | 45 +++ packages/client/ui-theme/src/client/index.ts | 5 +- .../client/ui-theme/src/client/locales.ts | 29 ++ .../client/ui-trajectory/src/client/index.ts | 5 +- .../ui-trajectory/src/client/locales.ts | 196 +++++++++ .../ui-user-questions/src/client/index.ts | 5 +- .../ui-user-questions/src/client/locales.ts | 35 ++ .../ui-workflow-run/src/client/index.ts | 5 +- .../ui-workflow-run/src/client/locales.ts | 22 + .../client/ui-workspace/src/client/index.ts | 5 +- .../client/ui-workspace/src/client/locales.ts | 123 ++++++ .../src/client/locales.ts | 29 ++ .../client-ui-agent-team/src/client/mount.ts | 5 +- .../src/client/locales.ts | 8 + .../src/client/mount.ts | 5 +- .../src/client/locales.ts | 36 ++ .../src/client/mount.ts | 5 +- .../src/client/locales.ts | 113 ++++++ .../client-ui-voice-input/src/client/mount.ts | 5 +- .../experimental/cua/src/client/locales.ts | 54 +++ packages/experimental/cua/src/client/mount.ts | 5 +- .../inspector/src/client/bottom/locales.ts | 9 + .../session-inspector/src/client/index.ts | 5 +- .../session-inspector/src/client/locales.ts | 56 +++ .../src/client/api-catalog.ts | 2 +- .../extensions/ui-cordis/src/client/index.ts | 5 +- .../ui-cordis/src/client/locales.ts | 55 +++ .../session-log-export/src/client/index.ts | 5 +- .../session-log-export/src/client/locales.ts | 14 + scripts/locale-dictionary-parity.spec.ts | 64 ++- snapshots/web/ptc-round/ui-ja.expected.md | 38 ++ 166 files changed, 3931 insertions(+), 279 deletions(-) create mode 100644 packages/client/locale/src/locales/ja.ts create mode 100644 snapshots/web/ptc-round/ui-ja.expected.md diff --git a/apps/desktop/README.i18n.yaml b/apps/desktop/README.i18n.yaml index bda88b0fc6..ad36e6494c 100644 --- a/apps/desktop/README.i18n.yaml +++ b/apps/desktop/README.i18n.yaml @@ -33,8 +33,8 @@ en: 372297005b57a08b zh: 074a635ac17e4362 /deepseek-harness-desktop/installation-ownership: - en: 22153a298a6662e1 - zh: 3600803c8c9d0274 + en: b3cf09d7c901d727 + zh: 2ec34045a201d8e9 /deepseek-harness-desktop/installation-ownership/runtime-and-plugin-activation: en: a1a47b09f4a72ba4 zh: 7b1cf27f0b72cf31 @@ -45,8 +45,8 @@ en: faf7676ba18bbf2f zh: f1d7b75ad348c548 /deepseek-harness-desktop/develop/startup-onboarding: - en: a51346b8d880ddba - zh: 7913214c1c66063c + en: d87d51b901b72ecd + zh: 525da1c4d56af659 /deepseek-harness-desktop/develop/welcome-window-appearance: en: 617a89aeb28131b9 zh: 4d84bd5d86501c86 diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 45c94d62c7..b48f281820 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -150,7 +150,7 @@ The product UI retains Web actions, including "Open In..." through the shared au Update copy preserves the complete version, including prerelease suffixes, without adding v or V. When no update is available, the dialog title reports the result and the body shows the current version. -Electron chooses typed English or Chinese shell copy from its application locale and falls back to English. The macOS application bundle declares English and Simplified Chinese in `CFBundleLocalizations`, allowing macOS to match the initial application language to the user’s preferred languages. Saved Client UI language preferences still take precedence for the main interface. On Windows, the main document's language updates desktop menus, recovery and update prompts. The repository Client UI i18n gate checks desktop sources. +Electron chooses typed English, Chinese, or Japanese shell copy from its application locale and falls back to English. The macOS application bundle declares English, Simplified Chinese, and Japanese in `CFBundleLocalizations`, allowing macOS to match the initial application language to the user’s preferred languages. Saved Client UI language preferences still take precedence for the main interface. On Windows, the main document's language updates desktop menus, recovery and update prompts. The repository Client UI i18n gate checks desktop sources. Windows and macOS relay native fullscreen transitions and the state after each page load to the renderer, so overlays can adapt their caption clearance. @@ -218,7 +218,7 @@ Repeated launches and `dsh://open` keep the workspace hidden until the startup c Desktop checks configured model API-key references after the Host starts and before opening the workspace. With no configured key, the welcome window offers the API-key page. Save and continue writes through the existing credential service using the official DeepSeek provider's configured reference, then opens the workspace. Set up later opens the workspace without saving the draft or a completion setting; the next process launch checks credentials again. Back to sign in returns to the entry and clears the unsaved key and validation message. Buttons keep their labels and block competing actions while saving or opening the workspace. The Desktop preload marker suppresses the Web credential dialog, while retaining the Models settings page and the welcome notice. -The welcome window reads the shared `locale.preference` before it appears. An explicit English or Chinese choice wins; otherwise Desktop picks the first supported OS language and falls back to English. The main UI reads the same preference and OS language order through its isolated preload before mounting. Language changes in Settings update the shell’s current dictionary and menu; automatic selection writes no preference. The welcome window has no language selector. +The welcome window reads the shared `locale.preference` before it appears. An explicit English, Chinese, or Japanese choice wins; otherwise Desktop picks the first supported OS language and falls back to English. The main UI reads the same preference and OS language order through its isolated preload before mounting. Language changes in Settings update the shell’s current dictionary and menu; automatic selection writes no preference. The welcome window has no language selector. The browser-login waiting page offers a copy-link action for the current pending authorization, a loading indicator, and cancellation. Clipboard failures leave the copy action available for retry. Copy feedback resets after two seconds; the copied state disables the link until it resets. Welcome text uses Montserrat Light with system fallbacks; large action buttons keep the system font, while text buttons use Montserrat Light. English introductory copy is 24px throughout. Chinese introductory copy is 24px with a 26px product name. Login action buttons are 240px wide with 14px labels. Authorization status headings use 20px Montserrat Regular text. The API-key page uses a 20px heading and a 14px back action, with 84px between the secondary button’s bottom edge and the window bottom. diff --git a/apps/desktop/README.zh.md b/apps/desktop/README.zh.md index 0504c7004c..2cd7db2ae2 100644 --- a/apps/desktop/README.zh.md +++ b/apps/desktop/README.zh.md @@ -150,7 +150,7 @@ Electron 拥有 `$DSH_HOME/profiles/desktop`。其 `dependencies` 包含 pnpm 升级文案完整展示版本号(包括预发布后缀),不额外添加 v 或 V。无可用更新时,弹窗标题显示检查结果,正文显示当前版本。 -Electron 根据应用语言选择类型化的英文或中文 shell 文案,并回退到英文。macOS 应用包通过 `CFBundleLocalizations` 声明支持英语和简体中文,让 macOS 根据用户的首选语言匹配初始应用语言。主界面仍优先使用已保存的 Client UI 语言偏好。在 Windows 上,主文档的语言会更新桌面菜单、恢复与更新提示。仓库 Client UI i18n 检查覆盖桌面端源码。 +Electron 根据应用语言选择类型化的英文、中文或日文 shell 文案,并回退到英文。macOS 应用包通过 `CFBundleLocalizations` 声明支持英语、简体中文和日语,让 macOS 根据用户的首选语言匹配初始应用语言。主界面仍优先使用已保存的 Client UI 语言偏好。在 Windows 上,主文档的语言会更新桌面菜单、恢复与更新提示。仓库 Client UI i18n 检查覆盖桌面端源码。 Windows 和 macOS 会在原生全屏切换及每次页面加载后向渲染器同步全屏状态,让浮层调整顶栏避让距离。 @@ -218,7 +218,7 @@ API Key 输入框初始为空,并通过 `autocomplete="new-password"` 请求 C Desktop 在 Host 启动后、打开工作区前检查模型 API Key 引用是否已配置。没有已配置的密钥时,欢迎窗口提供 API Key 页面。“保存并继续”通过现有凭证服务写入 DeepSeek 官方提供方配置的引用,然后打开工作区。“稍后配置”打开工作区,但不保存草稿或完成标记;下次进程启动时会重新检查凭证。“返回登录”回到入口并清空未保存的密钥和校验提示。保存或打开工作区期间,按钮保持原文案并禁用竞争操作。Desktop preload 标记使 Web 凭证弹窗不再显示,同时保留模型设置页和欢迎须知。 -欢迎窗口在显示前读取共享的 `locale.preference`。用户明确选择的英文或中文优先;否则 Desktop 按系统语言顺序匹配支持的语言,并以英文兜底。主界面在挂载前通过隔离 preload 读取同一偏好和系统语言顺序。在设置中切换语言会更新桌面壳的当前词典和菜单;自动选择不会写入偏好。欢迎窗口不提供语言切换入口。 +欢迎窗口在显示前读取共享的 `locale.preference`。用户明确选择的英文、中文或日文优先;否则 Desktop 按系统语言顺序匹配支持的语言,并以英文兜底。主界面在挂载前通过隔离 preload 读取同一偏好和系统语言顺序。在设置中切换语言会更新桌面壳的当前词典和菜单;自动选择不会写入偏好。欢迎窗口不提供语言切换入口。 等待浏览器登录时,欢迎页提供当前待授权请求的链接复制入口、加载指示和取消操作;剪贴板写入失败后可以重试复制,复制结果提示在两秒后恢复;已复制状态下链接禁用,恢复后可再次点击。Welcome 文字使用 Montserrat Light 并回退到系统字体,底部大按钮保留系统字体,文字按钮使用 Montserrat Light。英文欢迎正文及产品名均为 24px,中文欢迎正文为 24px、产品名为 26px。登录操作按钮宽 240px,文字为 14px。授权状态标题使用 20px Montserrat Regular 字重。API Key 页的标题为 20px,返回操作为 14px,次级按钮底边距窗口底部 84px。 diff --git a/apps/desktop/electron-builder.config.d.mts b/apps/desktop/electron-builder.config.d.mts index d6a1a63bfa..45e90a4ae4 100644 --- a/apps/desktop/electron-builder.config.d.mts +++ b/apps/desktop/electron-builder.config.d.mts @@ -22,7 +22,10 @@ export interface DesktopElectronBuilderConfig { ...{ readonly from: string, readonly to: 'tray.ico' }[], ] readonly mac: { - readonly extendInfo: { readonly NSMicrophoneUsageDescription: string } + readonly extendInfo: { + readonly CFBundleLocalizations: readonly string[] + readonly NSMicrophoneUsageDescription: string + } readonly entitlements: string readonly entitlementsInherit: string readonly identity: string | undefined diff --git a/apps/desktop/scripts/electron-builder-config.mjs b/apps/desktop/scripts/electron-builder-config.mjs index 7dc39ff326..7f225bebf7 100644 --- a/apps/desktop/scripts/electron-builder-config.mjs +++ b/apps/desktop/scripts/electron-builder-config.mjs @@ -162,12 +162,14 @@ export function createElectronBuilderConfig( icon: fileURLToPath(new URL('../resources/icon-macos.png', import.meta.url)), category: 'public.app-category.developer-tools', // macOS matches the application locale against this bundle, not Electron Framework resources. - extendInfo: { CFBundleLocalizations: ['en', 'zh_CN'] }, + extendInfo: { + CFBundleLocalizations: ['en', 'zh_CN', 'ja'], + NSMicrophoneUsageDescription: 'DeepSeek Harness uses your microphone to transcribe speech into message drafts.', + }, // Ad-hoc signing keeps the modified Electron executable runnable without a Developer ID. identity: unsigned ? '-' : macOSSigning?.signingIdentity, forceCodeSigning: !unsigned, hardenedRuntime: true, - extendInfo: { NSMicrophoneUsageDescription: 'DeepSeek Harness uses your microphone to transcribe speech into message drafts.' }, entitlements: fileURLToPath(new URL('./macos-entitlements.plist', import.meta.url)), entitlementsInherit: fileURLToPath(new URL('./macos-entitlements.plist', import.meta.url)), // Prepared runtime files retain their signatures; PAK resources are sealed by their enclosing bundle. diff --git a/apps/desktop/src/locale.ts b/apps/desktop/src/locale.ts index 3c60af45d8..7a5a9682d2 100644 --- a/apps/desktop/src/locale.ts +++ b/apps/desktop/src/locale.ts @@ -1,4 +1,4 @@ -/** Typed English and Chinese copy owned by the Electron shell. */ +/** Typed English, Chinese, and Japanese copy owned by the Electron shell. */ export const en = { cliCommandMenu: 'Manage dsh Command…', @@ -343,12 +343,13 @@ export const zh = { /** Locale payload exposed to the Desktop-owned renderer. */ export interface DesktopLocale { - readonly id: 'en' | 'zh-CN' + readonly id: 'en' | 'zh-CN' | 'ja' readonly messages: DesktopMessages } /** Resolve Electron's locale to one shipped Desktop dictionary. */ export function resolveDesktopLocale(locale: string): DesktopLocale { + if (locale.toLowerCase().split('-')[0] === 'ja') return { id: 'ja', messages: ja } return locale.toLowerCase().startsWith('zh') ? { id: 'zh-CN', messages: zh } : { id: 'en', messages: en } @@ -362,10 +363,10 @@ export function resolveDesktopLocale(locale: string): DesktopLocale { */ export function resolveDesktopStartupLocale(preference: string | null, languages: readonly string[]): DesktopLocale { const selected = preference?.toLowerCase() - if (selected === 'zh' || selected === 'en') return resolveDesktopLocale(selected) + if (selected === 'zh' || selected === 'en' || selected === 'ja') return resolveDesktopLocale(selected) for (const language of languages) { const primary = language.toLowerCase().split('-')[0] - if (primary === 'zh' || primary === 'en') return resolveDesktopLocale(primary) + if (primary === 'zh' || primary === 'en' || primary === 'ja') return resolveDesktopLocale(primary) } return resolveDesktopLocale('en') } @@ -395,3 +396,173 @@ export function desktopUpdateReadyConfirmation( detail: platform === 'win32' ? messages.updateDownloadedDetailWindows : messages.updateDownloadedDetail, } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'cliCommandMenu': 'dsh コマンドを管理…', + 'cliCommandTitle': 'dsh コマンドを管理', + 'cliCommandLocation': 'デスクトップコマンド: {path}', + 'cliCommandSelected': '現在の dsh コマンド: {path}', + 'cliCommandTarget': '現在のランチャーターゲット: {path}', + 'cliCommandShadowed': '別の dsh が優先されています。デフォルトでデスクトップコマンドを使用するには、そのインストールを削除するか順序を変更してください。デスクトップコマンドはフルパスでも実行できます。', + 'cliCommandSelectionUnknown': 'シェルコマンドを確認できませんでした。エイリアスや別の dsh インストールが優先されている可能性があります。', + 'cliCommandInstalled': 'デスクトップコマンドがインストールされています。', + 'cliCommandNotInstalled': 'デスクトップコマンドをターミナルに追加します。', + 'cliCommandBroken': 'デスクトップコマンドの修復が必要です。', + 'cliCommandInstall': 'インストール', + 'cliCommandRepair': '修復', + 'cliCommandRemove': '削除', + 'cliCommandClose': '閉じる', + 'cliCommandSwitch': 'デスクトップコマンドで続行しますか?', + 'cliCommandPreserve': '既存のコマンドは保持されます。他のインストールやシェルの起動ファイルは変更されません。', + 'cliCommandContinue': '続行', + 'cliCommandInstallApp': 'dsh コマンドを管理する前に、デスクトップをアプリケーションフォルダにインストールしてください。', + 'cliCommandUpdating': 'アップデートをインストール中です。インストール完了後にコマンドを管理してください。', + 'cliCommandNewTerminal': '新しいターミナルを開いて dsh --version を実行してください。', + 'cliCommandRemoved': 'デスクトップコマンドの登録を削除しました。', + 'cliCommandPreviousRestored': '以前のランチャーを復元しました。', + 'cliCommandOtherKept': '他のコマンドインストールはそのまま残されています。', + 'cliCommandBackupKept': '以前のランチャーは次の場所に保持されています: {path}', + 'cliCommandChanged': 'このダイアログを開いている間にコマンドまたは PATH が変更されました。現在の状態を確認するには、dsh コマンドを管理を再度開いてください。', + 'cliCommandOwnershipError': 'コマンド登録またはバックアップが変更されました。無関係なコマンドは削除されていません。', + 'cliCommandFailed': 'コマンドを更新できませんでした。アプリケーションがインストールされ、保存先に書き込み権限があることを確認してから再試行してください。', + 'application': 'アプリケーション', + 'fileMenu': 'ファイル', + 'closePage': 'ページまたはウィンドウを閉じる', + 'aboutMenu': 'DeepSeek Harness について', + 'aboutProduct': 'DeepSeek Harness', + 'aboutVersion': 'バージョン V{version}', + 'hideApplication': 'DeepSeek Harness を隠す', + 'hideOtherApplications': 'ほかを隠す', + 'showAllApplications': 'すべて表示', + 'quitApplication': 'DeepSeek Harness を終了', + 'openApplication': 'DeepSeek Harness を開く', + 'quit': '終了', + 'cancel': 'キャンセル', + 'quitTitle': 'DeepSeek Harness を終了しますか?', + 'quitActiveTasks': '実行中のタスクは中断されます。', + 'quitScheduledTasks': 'アプリの終了中はスケジュールされたタスクが実行されません。', + 'quitActiveAndScheduledTasks': '実行中のタスクは中断され、アプリの終了中はスケジュールされたタスクが実行されません。', + 'backgroundNoticeBody': '実行中のタスクは継続されます。システムトレイからウィンドウを再度開けます。', + 'backgroundNoticeConfirm': '確認', + 'edit': '編集', + 'menuBar': 'アプリケーションメニュー', + 'delete': '削除', + 'undo': '元に戻す', + 'redo': 'やり直す', + 'cut': '切り取り', + 'copy': 'コピー', + 'paste': '貼り付け', + 'selectAll': 'すべて選択', + 'startupFailed': 'DeepSeek Harness を利用できません', + 'fatalSummary': 'アプリケーションを起動できなかったか、予期せず停止しました。', + 'startupAddressInUse': '別の DSH インスタンス (dsh web やデスクトップアプリなど) が実行中です。同時に起動することはできません。実行中の他の DSH インスタンスを終了してから再起動してください。', + 'diagnosticTruncated': '… エラー詳細は省略されています。', + 'reportWrittenTo': '診断レポート: {path}', + 'startupReinstallAdvice': 'アプリケーションファイルが見つからないか破損している場合は、アプリケーションを閉じて再インストールしてください。タスクは別途保存されています。', + 'exitApplication': '終了', + 'restartApplication': '再起動', + 'recoveryOperationFailed': '復旧操作に失敗しました', + 'disableThirdPartyPlugins': 'サードパーティのプラグインを無効化し、プロファイルパッチをバックアップして再起動', + 'welcomeTitle': 'DeepSeek Harness', + 'welcomeBrand': 'DeepSeek Harness', + 'welcomeTaglineBefore': 'ようこそ ', + 'welcomeTaglineBrand': 'DeepSeek Harness', + 'welcomeTaglineAfter': '', + 'welcomeDescription': '可能性を築く。知能を探求する。', + 'welcomeAuthStarting': 'ログインを開いています…', + 'welcomeAuthWaiting': 'ブラウザが自動で開きませんでしたか?', + 'welcomeAuthWaitingDescription': 'ログインリンクをコピーしてブラウザで開き、ログインしてください。', + 'welcomeAuthExchanging': 'ログインを完了しています…', + 'welcomeAuthExpired': 'ログインがタイムアウトしました', + 'welcomeAuthExpiredDescription': '続行するには再度ログインしてください', + 'welcomeAuthFailed': 'ログインを完了できませんでした。もう一度お試しください。', + 'welcomeAuthCopyLink': 'ログインリンクをコピー', + 'welcomeAuthCopied': 'コピーしました', + 'welcomeAuthCopyFailed': 'コピーできませんでした。もう一度お試しください。', + 'welcomeAuthCancel': 'キャンセル', + 'welcomeAuthRetry': '再度ログイン', + 'welcomeSignIn': 'ログイン', + 'welcomeApiKey': 'APIキーを追加', + 'welcomeKeyTitle': 'APIキーを追加して開始', + 'welcomeKeyDescription': '公式のDeepSeekモデルを設定してHarnessを使い始めましょう', + 'welcomeKeyPlaceholder': 'APIキーを入力', + 'welcomeKeySave': '保存して続行', + 'welcomeKeyLater': '後で設定', + 'welcomeKeyBack': 'ログインに戻る', + 'welcomeSessionExpired': 'アカウントからログアウトしました。再度ログインしてください。', + 'welcomeKeyBlank': 'APIキーを入力してください。', + 'welcomeKeyInvalid': '引用符、スペース、環境変数の代入を含めず、APIキーそのものを入力してください。', + 'welcomeKeyFailed': 'APIキーを保存できませんでした。もう一度お試しください。', + 'welcomeContinueFailed': 'ワークスペースを開けませんでした。もう一度お試しください。', + 'checkUpdatesMenu': 'アップデートを確認…', + 'reloadPageMenu': 'ページを再読み込み', + 'restartAppHostMenu': 'アプリとホストを再起動', + 'updateCheckFailedTitle': 'アップデートの確認に失敗しました', + 'updateCheckFailed': 'アップデートを確認できませんでした。後でもう一度お試しください。', + 'updateDownloadFailed': 'アップデートをダウンロードできませんでした。もう一度お試しください。', + 'updateInstallFailed': 'アップデートをインストールできませんでした。後でもう一度お試しください。', + 'updateCheckNetworkFailed': 'アップデートを確認できませんでした。接続を確認してもう一度お試しください。', + 'updateDownloadNetworkFailed': 'アップデートをダウンロードできませんでした。接続を確認してもう一度お試しください。', + 'updateInstallNetworkFailed': 'アップデートをインストールできませんでした。接続を確認してもう一度お試しください。', + 'unknownError': '不明なエラー', + 'updateCheckTitle': 'アップデートを確認', + 'updateCurrentDetail': '現在のバージョン: {version}', + 'updateCurrent': '最新です!', + 'updateChecking': 'アップデートを確認中…', + 'updateDownload': 'アップデートをダウンロード', + 'updateDownloadedTitle': 'バージョン {version} をインストールする準備ができました', + 'updateDownloadedDetail': 'アップデート中はアプリが終了し、完了後に自動的に再起動します。', + 'updateDownloadedDetailWindows': 'アップデート中はアプリが一時的に終了し、完了後に自動的に再起動します。\n\nアップデートには時間がかかる場合があります。インストール中はアプリを再度起動せずにお待ちください。', + 'updateClose': '閉じる', + 'updateAcknowledge': 'OK', + 'updateLater': '後でアップデート', + 'updateDownloading': 'ダウンロード中 {percent}%…', + 'updateVerifying': 'アップデートファイルを検証中…', + 'updateInstalling': '再起動の準備中…', + 'updateRetry': 'アップデートを再試行', + 'updateActiveTasks': 'タスクがまだ実行中です', + 'updateActiveTasksDetail': 'アップデートすると実行中のタスクが停止し、アプリが再起動します。続行しますか?', + 'updateStopTasks': 'タスクを停止してアップデート', + 'updateTasksChanged': '新しいタスクが開始されました。タスクを停止してアップデートするには再度確認してください。', + 'updateTasksUnavailable': 'タスクの状態を取得できません。ワークスペースの準備ができてから再度アップデートをお試しください。', + 'updateFullscreenTimeout': 'フルスクリーンを終了できませんでした。フルスクリーンを終了してからアップデートを再試行してください。', + 'updateStopFailed': 'タスクを安全に停止できませんでした。アップデートはインストールされていません。後でもう一度お試しください。', + 'updateTechnicalDetails': '技術的な詳細を表示', + 'updateTitle': 'DeepSeek Harness アップデート', + 'updateAvailable': '新しいバージョンがあります: {version}', + 'updateDetail': 'ダウンロードが完了したら、アップデートをインストールしてアプリを再起動できます。', + 'installAndRestart': 'インストールして再起動', + 'later': '後で', + 'updateFailedTitle': 'アップデートに失敗しました', + 'mandatoryTitle': '続行するにはアップデートしてください', + 'mandatoryDetail': 'このバージョンはサポートされなくなりました。続行するにはアップデートしてください。実行中のタスクは、インストールの確認と再起動を行うまで実行を継続します。', + 'mandatoryUnavailable': 'アップデートの要件を確認できませんでした。後でもう一度お試しください。', + 'policyLoginTitle': 'テスト環境にログイン', + 'policyLoginRequired': 'Feishuでログインして、このテストビルドのアップデート要件を確認します。ログインしてもアップデートのダウンロードやインストールは行われません。', + 'policyLogin': 'Feishuでログイン', + 'policyLoginFailed': 'Feishuのログインが完了しませんでした。もう一度お試しください。', + 'policyLoginLoading': 'ログインページを読み込み中…', + 'mandatoryNoRelease': '互換性のあるアップデートが見つかりませんでした。再度確認するか、サポートにお問い合わせください。', + 'mandatoryRefresh': '再確認', + 'mandatoryPage': '公式サイトからダウンロード', + 'mandatoryCopy': 'ダウンロードリンクをコピー', + 'mandatoryPageFailed': '公式ダウンロードページを開けませんでした。リンクをコピーしてブラウザで開いてください。', + 'mandatoryActionFailed': '操作に失敗しました。もう一度お試しください。アップデートが完了すると、アプリを再度使用できます。', + 'mandatoryReady': 'アップデートの準備完了', + 'mandatoryVersion': '新しいバージョン: {version}', + 'mandatoryReadyDetail': 'アップデート中にアプリが終了し、完了後に自動的に再起動します。', + 'mandatoryDeferred': '実行中のタスクはそのまま続行されます。アプリを再度使用する前にアップデートを完了してください。', + 'mandatoryContinue': 'アップデートを続行', + 'mandatoryInspecting': 'タスクを確認中…', + 'mandatoryStopping': 'タスクを停止中…', + 'mandatoryRestarting': 'まもなくアプリが再起動します。しばらくお待ちください。', + 'mandatoryDownloadFailed': 'アップデートファイルをダウンロードまたは準備できませんでした。再試行してください。', + 'mandatoryInstallFailed': 'アップデートはインストールされていません。タスクを再度確認して再試行してください。', + 'mandatoryOpenHelp': 'ページが開きませんでしたか?', + 'mandatoryReopen': '公式ダウンロードページを再度開く', + 'mandatoryCopied': 'リンクをコピーしました', + 'mandatoryCopyFailed': 'リンクをコピーできませんでした。以下から選択してコピーしてください。', + 'mandatoryAddress': 'ダウンロードリンク', + 'mandatoryNotification': 'アプリに戻り、インストールと再起動を確認してください。', +} satisfies Record diff --git a/apps/desktop/tests/locale.spec.ts b/apps/desktop/tests/locale.spec.ts index 2bdd6673bd..eb9f2ccb81 100644 --- a/apps/desktop/tests/locale.spec.ts +++ b/apps/desktop/tests/locale.spec.ts @@ -1,9 +1,11 @@ import { describe, expect, it } from 'vitest' -import { en, formatDesktopMessage, resolveDesktopLocale, resolveDesktopStartupLocale, zh } from '../src/locale.ts' +import { en, formatDesktopMessage, resolveDesktopLocale, resolveDesktopStartupLocale, ja, zh } from '../src/locale.ts' describe('desktop locale dictionaries', () => { - it('ships the same key set in English and Chinese', () => { + it('ships the same key set in English, Chinese, and Japanese', () => { expect(Object.keys(zh)).toEqual(Object.keys(en)) + expect(Object.keys(ja)).toEqual(Object.keys(en)) + expect(resolveDesktopLocale('JA-jp')).toEqual({ id: 'ja', messages: ja }) expect(resolveDesktopLocale('zh-Hans-CN').messages).toEqual(zh) expect(resolveDesktopLocale('en-US').messages).toEqual(en) expect(resolveDesktopLocale('fr-FR').messages).toEqual(en) @@ -17,11 +19,11 @@ describe('desktop locale dictionaries', () => { it('prefers an explicit supported choice, then the first supported system language', () => { expect(resolveDesktopStartupLocale('zh', ['en-US']).id).toBe('zh-CN') expect(resolveDesktopStartupLocale('EN', ['zh-CN']).id).toBe('en') - expect(resolveDesktopStartupLocale(null, ['ja-JP', 'zh-Hant', 'en-US']).id).toBe('zh-CN') + expect(resolveDesktopStartupLocale(null, ['ja-JP', 'zh-Hant', 'en-US']).id).toBe('ja') expect(resolveDesktopStartupLocale(null, ['en-US', 'zh-CN']).id).toBe('en') - expect(resolveDesktopStartupLocale(null, ['ja-JP']).id).toBe('en') + expect(resolveDesktopStartupLocale(null, ['ja-JP']).id).toBe('ja') expect(resolveDesktopStartupLocale(null, []).id).toBe('en') - expect(resolveDesktopStartupLocale('ja', ['zh-CN']).id).toBe('zh-CN') + expect(resolveDesktopStartupLocale('ja', ['zh-CN']).id).toBe('ja') }) }) diff --git a/apps/desktop/tests/macos-signature.spec.ts b/apps/desktop/tests/macos-signature.spec.ts index 0d9f486bdd..afe2092791 100644 --- a/apps/desktop/tests/macos-signature.spec.ts +++ b/apps/desktop/tests/macos-signature.spec.ts @@ -51,6 +51,7 @@ describe('desktop macOS release signature', () => { expect(config.protocols).toEqual([{ name: 'DeepSeek Harness', schemes: ['dsh'] }]) expect(portablePath(config.directories.output)).toContain('/.desktop-build/targets/mac-arm64/artifacts') expect(config.mac.extendInfo.NSMicrophoneUsageDescription).toContain('microphone') + expect(config.mac.extendInfo.CFBundleLocalizations).toEqual(['en', 'zh_CN', 'ja']) expect(config.mac.entitlementsInherit).toBe(config.mac.entitlements) const entitlements = readFileSync(config.mac.entitlements, 'utf8') for (const key of ['com.apple.security.cs.allow-jit', 'com.apple.security.cs.allow-unsigned-executable-memory', diff --git a/apps/web/tests/desktop-locale.e2e.ts b/apps/web/tests/desktop-locale.e2e.ts index 7065c95552..ad6ec8137c 100644 --- a/apps/web/tests/desktop-locale.e2e.ts +++ b/apps/web/tests/desktop-locale.e2e.ts @@ -23,7 +23,7 @@ describe.skipIf(MODE === 'record')('web e2e: native and Client locale preference const reported: string[] = [] beforeAll(async () => { - scaffold = await launchWebScaffold() + scaffold = await launchWebScaffold({ extraOverlayPath: fileURLToPath(new URL('./pin-browse-picker.overlay.yml', import.meta.url)) }) browser = await chromium.launch() page = await browser.newPage({ viewport: { width: 1440, height: 960 }, locale: 'en-US' }) tripwire = watchConsole(page) @@ -54,14 +54,14 @@ describe.skipIf(MODE === 'record')('web e2e: native and Client locale preference it('uses OS languages without saving them, then shares an explicit choice across reloads', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-desktop-locale')) await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' }) - await openSettings(page, 'zh') - const zhDialog = page.getByRole('dialog', { name: '设置', exact: true }) - await zhDialog.getByRole('button', { name: '中文', exact: true }).waitFor() - await expect.poll(() => reported).toEqual(['zh']) + await openSettings(page, 'ja') + const jaDialog = page.getByRole('dialog', { name: '設定', exact: true }) + await jaDialog.getByRole('button', { name: '日本語', exact: true }).waitFor() + await expect.poll(() => reported).toEqual(['ja']) expect(scaffold.ctx.settings.describe().find(row => row.ns === 'locale')!.value).toEqual({}) await compareOrRefreshGolden(join(SNAPSHOT_DIR, 'automatic.expected.md'), await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd, versionCapture), MODE) - await zhDialog.getByRole('button', { name: '中文', exact: true }).click() + await jaDialog.getByRole('button', { name: '日本語', exact: true }).click() await page.getByRole('menuitem', { name: 'English', exact: true }).click() const enDialog = page.getByRole('dialog', { name: 'Settings', exact: true }) await enDialog.getByRole('button', { name: 'English', exact: true }).waitFor() @@ -79,6 +79,22 @@ describe.skipIf(MODE === 'record')('web e2e: native and Client locale preference await expect.poll(() => reported).toEqual(['en']) expect(await page.evaluate(() => document.documentElement.lang)).toBe('en') expect(await page.evaluate(() => localStorage.getItem('dsh.locale'))).toBeNull() + await enDialog.getByRole('button', { name: 'English', exact: true }).click() + await page.getByRole('menuitem', { name: '日本語', exact: true }).click() + await jaDialog.getByRole('button', { name: '日本語', exact: true }).waitFor() + await expect.poll(() => readFile(join(scaffold.harnessHome, 'profiles', 'scaffold', 'cordis.patch.yml'), 'utf8')) + .toContain('preference: ja') + const japaneseReloadWarnings = tripwire.warnings.length + reported.length = 0 + await page.reload({ waitUntil: 'load' }) + acknowledgeReloadConnectionLoss(tripwire, japaneseReloadWarnings) + await openSettings(page, 'ja') + await jaDialog.getByRole('button', { name: '日本語', exact: true }).waitFor() + await expect.poll(() => reported).toEqual(['ja']) + expect(await page.evaluate(() => document.documentElement.lang)).toBe('ja') + await page.setViewportSize({ width: 390, height: 844 }) + await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) + expect(tripwire.warnings).toEqual([]) expect(tripwire.pageErrors).toEqual([]) await assertFixtureInventory(SNAPSHOT_DIR, ['automatic.expected.md', 'selected.expected.md']) diff --git a/apps/web/tests/expected/desktop-locale/automatic.expected.md b/apps/web/tests/expected/desktop-locale/automatic.expected.md index 84a652e494..7d02aedd93 100644 --- a/apps/web/tests/expected/desktop-locale/automatic.expected.md +++ b/apps/web/tests/expected/desktop-locale/automatic.expected.md @@ -1,40 +1,40 @@ -- dialog "设置": +- dialog "設定": - navigation: - - text: 设置 - - button "通用设置" - - button "模型" - - button "内置插件" - - button "Agent 预设" - - button "打开配置文件" - - button "关闭" - - text: 权限 选择新会话的默认权限模式 - - button "工作区内修改" - - text: 语言 - - button "中文" - - text: 外观 - - button "浅色" - - button "深色" - - button "跟随系统" [pressed] - - text: 字号大小 仅影响会话内容的字号 14 - - button "增大字号" - - button "减小字号" + - text: 設定 + - button "一般" + - button "モデル" + - button "組み込みプラグイン" + - button "エージェントプリセット" + - button "設定ファイルを開く" + - button "閉じる" + - text: 権限 新しいセッションのデフォルトの権限モードを選択 + - button "ワークスペース書き込み" + - text: 言語 + - button "日本語" + - text: 外観 + - button "ライト" + - button "ダーク" + - button "システム" [pressed] + - text: フォントサイズ 会話コンテンツにのみ適用 14 + - button "フォントサイズを大きくする" + - button "フォントサイズを小さくする" - text: px - - button "更多字体设置" - - text: 工作步骤展示 选择希望看到多少工具调用细节 - - button "标准" - - text: 工作步骤收起时机 选择何时自动收起工作步骤 - - button "回答结束后" - - text: 收起后显示的回复 工作步骤收起后,保留哪些助手回复 - - button "全部回复" - - text: 显示代码工作视图 开启后,显示轨迹、本轮代码差异,可选择完整的 Agent 预设切换 - - switch "显示代码工作视图" [checked] - - text: 快捷键 - - paragraph: 查看和编辑当前可用的快捷键和输入操作 - - button "编辑快捷键" - - text: 繁忙时的发送行为 智能体运行时 Enter 键和发送按钮的行为;Cmd/Ctrl+Enter 使用另一行为 - - button "排队发送" - - text: 性能与用量 选择性能与用量信息展示的详细程度 - - button "详细" - - text: 在使用官方模型 API 时上传 Session Log 帮助改进 DeepSeek 模型与产品 - - switch "在使用官方模型 API 时上传 Session Log" - - text: 当前版本:{{version}} + - button "その他のフォント設定" + - text: 作業の詳細 ツール呼び出しの表示詳細度を選択 + - button "標準" + - text: 作業詳細を折りたたむタイミング 作業詳細を自動で折りたたむタイミングを選択 + - button "完了時" + - text: 折りたたみ後の返信 作業詳細の折りたたみ時に表示するアシスタントの返信を選択 + - button "すべての返信" + - text: コーディングビューを表示 軌跡、コード差分、すべてのエージェントプリセットを表示します + - switch "コーディングビューを表示" [checked] + - text: キーボードショートカット + - paragraph: 利用可能なショートカットと入力アクションを表示・編集 + - button "ショートカットを編集" + - text: 実行中の送信動作 エージェントの実行中にEnterキーと送信ボタンが行う動作。Cmd/Ctrl+Enterではもう一方の動作になります + - button "キュー" + - text: パフォーマンスと使用状況 パフォーマンスと使用状況の表示量を選択 + - button "詳細" + - text: 公式モデル API 使用時にセッションログをアップロード DeepSeek のモデルと製品の改善にご協力ください。 + - switch "公式モデル API 使用時にセッションログをアップロード" + - text: "現在のバージョン: {{version}}" diff --git a/apps/web/tests/ptc-round.e2e.ts b/apps/web/tests/ptc-round.e2e.ts index 575b3ad645..c7fe4ebc7c 100644 --- a/apps/web/tests/ptc-round.e2e.ts +++ b/apps/web/tests/ptc-round.e2e.ts @@ -11,7 +11,7 @@ import { acknowledgeReloadConnectionLoss, captureExpandedTurnProcessAria, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts, launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold, } from './scaffold.ts' -import { connectFreshWorkspace, expandOwningTurnProcess, newEnglishPage, saveFailureShot } from './support.ts' +import { connectFreshWorkspace, expandOwningTurnProcess, newEnglishPage, openSettings, saveFailureShot } from './support.ts' const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/ptc-round/session.v3.jsonl', import.meta.url)) const UI_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/ptc-round/ui.expected.md', import.meta.url)) @@ -36,6 +36,7 @@ describe('web e2e: PTC mode round renders nested sub-calls', () => { beforeAll(async () => { scaffold = await launchWebScaffold({ agentPresets: { default: 'ptc' }, + extraOverlayPath: fileURLToPath(new URL('./pin-browse-picker.overlay.yml', import.meta.url)), compareReplaySession: true, ...(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 }), }) @@ -275,6 +276,28 @@ describe('web e2e: PTC mode round renders nested sub-calls', () => { expect(await wrap.getAttribute('aria-pressed')).toBe('true') }) + + it.skipIf(MODE === 'record')('renders the same recorded tool round in Japanese without translating model or tool data', async () => { + onTestFailed(() => saveFailureShot(page, 'web-e2e-ptc-japanese')) + await openSettings(page, 'en') + const settings = page.getByRole('dialog', { name: 'Settings', exact: true }) + await settings.getByRole('button', { name: 'English', exact: true }).click() + await page.getByRole('menuitem', { name: '日本語', exact: true }).click() + await page.getByRole('dialog', { name: '設定', exact: true }).waitFor() + await page.keyboard.press('Escape') + await page.getByRole('tab', { name: 'チャット', exact: true }).click() + await page.getByText('DONE', { exact: true }).waitFor() + expect(await page.evaluate(() => document.documentElement.lang)).toBe('ja') + const row = page.locator('[data-subcalls] [data-sample="bash"]').first() + await expandOwningTurnProcess(page, row) + if (await row.getAttribute('aria-expanded') !== 'true') await row.click() + await row.locator('xpath=..').getByText('echo CODE_ROUND_OK', { exact: true }).waitFor() + const snapshot = await captureExpandedTurnProcessAria(page, '[class*="centerCol"]', scaffold.workspaceCwd) + await compareOrRefreshGolden( + fileURLToPath(new URL('../../../snapshots/web/ptc-round/ui-ja.expected.md', import.meta.url)), snapshot, MODE, + ) + }) + it.skipIf(MODE === 'record')('stayed clean: no page errors, no reconnect churn', () => { expect(tripwire.pageErrors).toEqual([]) expect(tripwire.warnings).toEqual([]) diff --git a/apps/web/tests/support.ts b/apps/web/tests/support.ts index 450911da2b..ef7d3765c4 100644 --- a/apps/web/tests/support.ts +++ b/apps/web/tests/support.ts @@ -333,7 +333,8 @@ export function conversationContextKey(kind: string, id: string): string { * @param page - browser page with the mounted sidebar. * @param locale - current UI language. */ -export async function openSettings(page: Page, locale: 'en' | 'zh'): Promise { - await page.getByRole('button', { name: locale === 'zh' ? '账号菜单' : 'Account menu', exact: true }).click() - await page.getByRole('menuitem', { name: locale === 'zh' ? '设置' : 'Settings', exact: true }).click() +export async function openSettings(page: Page, locale: 'en' | 'zh' | 'ja'): Promise { + const labels = { en: ['Account menu', 'Settings'], zh: ['账号菜单', '设置'], ja: ['アカウントメニュー', '設定'] } as const + await page.getByRole('button', { name: labels[locale][0], exact: true }).click() + await page.getByRole('menuitem', { name: labels[locale][1], exact: true }).click() } diff --git a/packages/client/locale/README.i18n.yaml b/packages/client/locale/README.i18n.yaml index 2e2736659e..d6fe556835 100644 --- a/packages/client/locale/README.i18n.yaml +++ b/packages/client/locale/README.i18n.yaml @@ -3,14 +3,14 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/locale/README.md /: - en: d2aaa955e01c2679 - zh: 9d264cf8be1536cf + en: 5dea50fae7364d1e + zh: dc788b9ce6ffd101 /deepseek-ai-dsh-client-locale: en: 9365090d78467dea zh: 74b6fbb9769d795f /deepseek-ai-dsh-client-locale/summary: - en: d8cc5ae25160ca20 - zh: e8fc2b9fb663cdee + en: a2c282377f0a043e + zh: 2274f1ea41774549 /deepseek-ai-dsh-client-locale/table-of-contents: en: d152484eb41ac6b4 zh: bda879a892f240f0 @@ -21,8 +21,8 @@ en: 8527d77583824eff zh: 2ac53ced0a32ba19 /deepseek-ai-dsh-client-locale/use-this-package/registering-a-dictionary: - en: 9b2fcfa8cae8dc4b - zh: 293012a4d364ef59 + en: c1b4f667e35cb24b + zh: 39b6df8350986cd5 /deepseek-ai-dsh-client-locale/use-this-package/resolving-package-text: en: 3b63e84fe0fdf8f4 zh: e97b5d1b92dff7ec @@ -42,11 +42,11 @@ en: d602d957654641c6 zh: 2db822b746a6471d /deepseek-ai-dsh-client-locale/understand-the-implementation/dictionary-lookup: - en: f8dcc4035830e738 + en: 68373e745761494c zh: 8452ac63ee148c61 /deepseek-ai-dsh-client-locale/understand-the-implementation/source-map: - en: a382e032c06ced3f - zh: 419cb5f60b075388 + en: fc93a8f6804aa5bd + zh: 9d8364dc4c03d055 /deepseek-ai-dsh-client-locale/further-exploration: en: f73d411c9e90dead zh: 8dd05f13e081af88 diff --git a/packages/client/locale/README.md b/packages/client/locale/README.md index 8c52703748..b0eb13fa17 100644 --- a/packages/client/locale/README.md +++ b/packages/client/locale/README.md @@ -1,5 +1,5 @@ --- -description: "Localization for the web GUI: the zh/en preference, browser-derived fallback, typed namespace dictionaries, and the framework translation seat, for users and plugin authors." +description: "Localization for the web GUI: language preferences, browser-derived fallback, typed namespace dictionaries, and the framework translation seat, for users and plugin authors." kind: "package-reference" --- @@ -9,7 +9,7 @@ English | [中文](README.zh.md) ## Summary -Use `dsh-client-locale` to switch the web GUI between the shipped English and Chinese locales or languages added by client plugins. User selections take effect immediately; every authenticated browser persists them as `locale.preference` in `$DSH_HOME/cordis.patch.yml` through the Host settings service. New browsers use the first supported language requested by the browser until the Host read delivers a stored preference. Plugin authors add typed namespace dictionaries and translate through the public locale API; slot-rendered copy updates without a reload. +Use `dsh-client-locale` to switch the web GUI between the shipped English, Chinese, and Japanese locales or languages added by client plugins. User selections take effect immediately; every authenticated browser persists them as `locale.preference` in `$DSH_HOME/cordis.patch.yml` through the Host settings service. New browsers use the first supported language requested by the browser until the Host read delivers a stored preference. Plugin authors add typed namespace dictionaries and translate through the public locale API; slot-rendered copy updates without a reload. ## Table of Contents @@ -35,7 +35,7 @@ Native shells may provide `__DSH_LOCALE__` with an asynchronous `read()` and an ### Registering a dictionary -Call `ctx.locale.register(ns, { zh, en })` with a namespace merged into `LocaleNamespaceMap`; the compiler checks every key against the namespace's typed key union and requires both shipped locales. Consumers translate through `ctx.locale.bind(ns)` or the framework-injected `t` seat. A dictionary registered after the UI is already mounted is picked up without a remount. +Call `ctx.locale.register(ns, { zh, en })` with a namespace merged into `LocaleNamespaceMap`; the compiler checks every key against the namespace's typed key union and requires both baseline locales. Consumers translate through `ctx.locale.bind(ns)` or the framework-injected `t` seat. Japanese dictionaries check their keys against English with TypeScript. A dictionary registered after the UI is already mounted is picked up without a remount. ### Resolving package text @@ -50,13 +50,13 @@ export const inject = ['locale'] export function apply(ctx) { ctx.effect( - () => ctx.locale.addLanguage({ id: 'ja', label: '日本語', fallback: 'en' }), + () => ctx.locale.addLanguage({ id: 'fr', label: 'Français', fallback: 'en' }), 'my-locale: language', ) ctx.effect( - () => ctx.locale.register('common', 'ja', { - cancel: 'キャンセル', - close: '閉じる', + () => ctx.locale.register('common', 'fr', { + cancel: 'Annuler', + close: 'Fermer', }), 'my-locale: common dictionary', ) @@ -91,7 +91,7 @@ The provisional locale comes from the browser (`navigator.languages` matched by Document language synchronization writes `` only when its value changes; dictionary-only revisions leave the attribute untouched. -The typed object form requires complete dictionaries for both built-in locales. The per-locale form lets language packs register each namespace independently. For each key, lookup walks the active language's declared fallback chain in the requested namespace, repeats that chain in `common`, then displays the key itself. Bound translate functions retain stable identity per namespace so they can ride inject surfaces without breaking memoization. +The typed object form requires complete dictionaries for both baseline locales. The per-locale form lets language packs register each namespace independently. For each key, lookup walks the active language's declared fallback chain in the requested namespace, repeats that chain in `common`, then displays the key itself. Bound translate functions retain stable identity per namespace so they can ride inject surfaces without breaking memoization. ### Source map @@ -100,7 +100,7 @@ The typed object form requires complete dictionaries for both built-in locales. | [`src/client/index.ts`](src/client/index.ts) | `LocaleRuntime`, dictionary registry, Language row registration, `locale/change` event | | [`src/index.ts`](src/index.ts) | Node half: registers the `locale` settings namespace | | [`src/locale-settings.ts`](src/locale-settings.ts) | The durable schema for `locale.preference` | -| [`src/locales/`](src/locales/) | The shipped `zh`/`en` dictionaries | +| [`src/locales/`](src/locales/) | The shipped `zh`/`en`/`ja` dictionaries | diff --git a/packages/client/locale/README.zh.md b/packages/client/locale/README.zh.md index 0527984591..243d431829 100644 --- a/packages/client/locale/README.zh.md +++ b/packages/client/locale/README.zh.md @@ -1,5 +1,5 @@ --- -description: "面向用户与插件作者的 web GUI 本地化说明:zh/en 偏好、浏览器派生回退、类型化命名空间字典与框架翻译席位。" +description: "面向用户与插件作者的 web GUI 本地化说明:语言偏好、浏览器派生回退、类型化命名空间字典与框架翻译席位。" kind: "package-reference" --- @@ -9,7 +9,7 @@ kind: "package-reference" ## 概述 -使用 `dsh-client-locale` 可在 web GUI 中切换内置的英文和中文 locale,或 client 插件添加的语言。用户选择会立即生效;每个已认证浏览器都通过 Host settings 服务把选择以 `locale.preference` 持久化到 `$DSH_HOME/cordis.patch.yml`。全新浏览器会使用浏览器请求的第一个受支持语言,直到 Host 读取送达已存储偏好。插件作者可添加类型化命名空间字典,并通过公开 locale API 翻译;经 slot 渲染的文案无需重新加载即可随语言切换更新。 +使用 `dsh-client-locale` 可在 web GUI 中切换内置的英文、中文和日文 locale,或 client 插件添加的语言。用户选择会立即生效;每个已认证浏览器都通过 Host settings 服务把选择以 `locale.preference` 持久化到 `$DSH_HOME/cordis.patch.yml`。全新浏览器会使用浏览器请求的第一个受支持语言,直到 Host 读取送达已存储偏好。插件作者可添加类型化命名空间字典,并通过公开 locale API 翻译;经 slot 渲染的文案无需重新加载即可随语言切换更新。 ## 目录 @@ -35,7 +35,7 @@ kind: "package-reference" ### 注册字典 -用已合并进 `LocaleNamespaceMap` 的命名空间调用 `ctx.locale.register(ns, { zh, en })`;编译器会对照该命名空间的类型化键并集检查每个键,并要求两个内置 locale 齐全。消费方通过 `ctx.locale.bind(ns)` 或框架注入的 `t` 席位翻译。UI 已挂载后再注册的字典无需重新挂载即可生效。 +用已合并进 `LocaleNamespaceMap` 的命名空间调用 `ctx.locale.register(ns, { zh, en })`;编译器会对照该命名空间的类型化键并集检查每个键,并要求两个基础 locale 齐全。消费方通过 `ctx.locale.bind(ns)` 或框架注入的 `t` 席位翻译。日文字典通过 TypeScript 对照英文检查键。UI 已挂载后再注册的字典无需重新挂载即可生效。 ### 解析包文本 @@ -50,13 +50,13 @@ export const inject = ['locale'] export function apply(ctx) { ctx.effect( - () => ctx.locale.addLanguage({ id: 'ja', label: '日本語', fallback: 'en' }), + () => ctx.locale.addLanguage({ id: 'fr', label: 'Français', fallback: 'en' }), 'my-locale: language', ) ctx.effect( - () => ctx.locale.register('common', 'ja', { - cancel: 'キャンセル', - close: '閉じる', + () => ctx.locale.register('common', 'fr', { + cancel: 'Annuler', + close: 'Fermer', }), 'my-locale: common dictionary', ) @@ -100,7 +100,7 @@ export function apply(ctx) { | [`src/client/index.ts`](src/client/index.ts) | `LocaleRuntime`、字典注册表、Language 行注册、`locale/change` 事件 | | [`src/index.ts`](src/index.ts) | node 半侧:注册 `locale` 设置命名空间 | | [`src/locale-settings.ts`](src/locale-settings.ts) | `locale.preference` 的持久 schema | -| [`src/locales/`](src/locales/) | 内置的 `zh`/`en` 字典 | +| [`src/locales/`](src/locales/) | 内置的 `zh`/`en`/`ja` 字典 | diff --git a/packages/client/locale/src/client/index.ts b/packages/client/locale/src/client/index.ts index 744a5f54e6..1c2b2bc27d 100644 --- a/packages/client/locale/src/client/index.ts +++ b/packages/client/locale/src/client/index.ts @@ -20,9 +20,9 @@ import { LOCALE_ID_PATTERN, LOCALE_IDS, LOCALE_PREFERENCE_FIELD, LOCALE_SETTINGS_NAMESPACE, type BuiltInLocaleId, type LocaleId, type LocaleSettings, } from '../locale-settings.ts' -import { en, zh, type CommonKey } from '../locales/index.ts' +import { en, ja, zh, type CommonKey } from '../locales/index.ts' import { - en as settingsEn, zh as settingsZh, type SettingsLocaleKey, + en as settingsEn, ja as settingsJa, zh as settingsZh, type SettingsLocaleKey, } from '../locales/settings.ts' import type { LanguageRowInjected } from './LanguageRow.tsx' import { LanguageRow } from './LanguageRow.tsx' @@ -114,7 +114,7 @@ export const COMMON_NS = 'common' /** Namespace owning this feature's settings-row copy. */ export const SETTINGS_NS = 'settings.locale' -/** The two locales and dictionaries shipped by this package. */ +/** The baseline locales required by typed dictionary registrations. */ const BUILT_IN_LOCALE_METADATA = { zh: { label: '中文', fallback: 'en' }, en: { label: 'English' }, @@ -378,7 +378,7 @@ export class LocaleRuntime { * Register a declared namespace's dictionaries, all locales in one call — * the typed form: each dictionary is checked against the namespace's * {@link LocaleNamespaceMap} key union (a missing or extra key is a - * compile error), and every shipped locale is required (bilingual balance + * compile error), and both baseline locales are required (bilingual balance * enforced at registration). Duplicate (ns, locale) throws (single occupant; a * namespace's texts have one owner). Registration bumps the revision so * mounted outlets pick up late-arriving dictionaries. @@ -576,8 +576,11 @@ export async function apply(ctx: ClientContext): Promise { } const host = ctx.configForms.get(LOCALE_SETTINGS_NAMESPACE) const locale = new LocaleRuntime(ctx, host, bootstrap) - locale.register(COMMON_NS, { zh, en }) - locale.register(SETTINGS_NS, { zh: settingsZh, en: settingsEn }) + ctx.effect(() => locale.addLanguage({ id: 'ja', label: '日本語', fallback: 'en' }), 'locale: Japanese language') + const commonDictionaries = { zh, en, ja } + const settingsDictionaries = { zh: settingsZh, en: settingsEn, ja: settingsJa } + ctx.effect(() => locale.register(COMMON_NS, commonDictionaries), 'locale: common dictionaries') + ctx.effect(() => locale.register(SETTINGS_NS, settingsDictionaries), 'locale: settings dictionaries') ctx.provide('locale', locale) if (bridge !== undefined) { ctx.on('locale/change', (snapshot) => { bridge.onChange(snapshot.active) }) diff --git a/packages/client/locale/src/locale-settings.ts b/packages/client/locale/src/locale-settings.ts index 24808a17b6..78b1215206 100644 --- a/packages/client/locale/src/locale-settings.ts +++ b/packages/client/locale/src/locale-settings.ts @@ -11,10 +11,10 @@ export const LOCALE_PREFERENCE_FIELD = 'preference' /** Accepted BCP 47-style language ids. */ export const LOCALE_ID_PATTERN = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/u -/** Locale identifiers shipped by the browser client. */ +/** Baseline locales required by typed namespace dictionary registration. */ export const LOCALE_IDS = ['zh', 'en'] as const -/** Locale identifier shipped by the browser client. */ +/** Baseline locale required by typed namespace dictionary registration. */ export type BuiltInLocaleId = typeof LOCALE_IDS[number] /** Open locale identifier accepted from language-pack plugins. */ diff --git a/packages/client/locale/src/locales/index.ts b/packages/client/locale/src/locales/index.ts index 6ac5335f5b..a01489ccfd 100644 --- a/packages/client/locale/src/locales/index.ts +++ b/packages/client/locale/src/locales/index.ts @@ -1,8 +1,8 @@ /** - * The common-namespace dictionary pair. zh is the source of truth for the - * key set (Chinese-first repo convention); en is checked complete against it - * — a missing or extra en key is a compile error. + * Common-namespace dictionaries. Chinese defines the keys; English and + * Japanese are checked against the same keys by TypeScript. */ export { zh } from './zh.ts' export { en } from './en.ts' +export { ja } from './ja.ts' export type { CommonKey } from './zh.ts' diff --git a/packages/client/locale/src/locales/ja.ts b/packages/client/locale/src/locales/ja.ts new file mode 100644 index 0000000000..1186838c31 --- /dev/null +++ b/packages/client/locale/src/locales/ja.ts @@ -0,0 +1,47 @@ +/** Japanese common vocabulary. */ +import type { en } from './en.ts' + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'ok': 'OK', + 'cancel': 'キャンセル', + 'close': '閉じる', + 'copy': 'コピー', + 'copied': 'コピーしました', + 'codeBlock.title': 'コードブロック', + 'codeBlock.wrap': '行を折り返す', + 'codeBlock.unwrap': '行を折り返さない', + 'copy.failed': 'コピーに失敗しました', + 'copy.value': '値をコピー', + 'copy.json': 'JSONをコピー', + 'copy.path': 'プロパティパスをコピー', + 'copy.prettyJson': '整形JSONをコピー', + 'copy.compactJson': 'コンパクトJSONをコピー', + 'copy.optionsHint': '{action}。右クリックでコピーオプションを表示', + 'retry': '再試行', + 'loading': '読み込み中…', + 'load.failed': '読み込みに失敗しました', + 'submit': '送信', + 'submitting': '送信中…', + 'next': '次へ', + 'previous': '前へ', + 'skip': 'スキップ', + 'delete': '削除', + 'edit': '編集', + 'save': '保存', + 'search': '検索', + 'more': 'その他', + 'collapse': '折りたたむ', + 'expand': '展開', + 'back': '戻る', + 'brand.localBuild': 'DSHローカルビルド', + 'workspace.defaultName': 'デフォルトワークスペース', + 'unknown': '不明', + 'none': 'なし', + 'truncated': '切り詰め済み', + 'json.label': 'JSON', + 'markdown.footnotes': '脚注', + 'markdown.truncatedCharacters': '… {total}文字で切り捨て', + 'number.thousand': '{value}K', + 'number.million': '{value}M', +} satisfies Record diff --git a/packages/client/locale/src/locales/settings.ts b/packages/client/locale/src/locales/settings.ts index 0419b60095..76c5fd1ffb 100644 --- a/packages/client/locale/src/locales/settings.ts +++ b/packages/client/locale/src/locales/settings.ts @@ -12,3 +12,8 @@ export type SettingsLocaleKey = keyof typeof zh export const en = { 'language.title': 'Language', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'language.title': '言語', +} satisfies Record diff --git a/packages/client/locale/tests/apply.client.spec.ts b/packages/client/locale/tests/apply.client.spec.ts index cd8e835c46..fa6c28210b 100644 --- a/packages/client/locale/tests/apply.client.spec.ts +++ b/packages/client/locale/tests/apply.client.spec.ts @@ -2,7 +2,7 @@ * Language row registration, snapshot projection into the row store, and * recovery after an HMR collapse of the declaring entry. */ import { Context } from '@deepseek-ai/cordis' -import { afterEach, describe, expect, it, vi } from 'vitest' +import { afterEach, describe, expect, it, onTestFinished, vi } from 'vitest' import { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client' import { apply as settingsApply, inject as settingsInject } from '@deepseek-ai/dsh-client-ui-settings/client' import { TestRemote } from '@deepseek-ai/dsh-client-test-runtime' @@ -155,7 +155,7 @@ describe('locale apply', () => { const { entry, instance, face } = faceOf(b.slots) // The inject-time re-sync sealed the init window: the mirror is current. expect(instance.getSnapshot().active).toBe('en') - expect(instance.getSnapshot().options.map(o => o.id)).toEqual(['zh', 'en']) + expect(instance.getSnapshot().options.map(o => o.id)).toEqual(['zh', 'en', 'ja']) // Copy rides the standard locale seat: the entry declares the namespace. expect(entry.locale).toBe(SETTINGS_NS) expect(locale.bind(SETTINGS_NS)('language.title')).toBe('Language') @@ -176,7 +176,7 @@ describe('locale apply', () => { const languagePack = b.ctx.plugin({ inject: ['locale'], apply: packCtx => packCtx.effect( - () => packCtx.locale.addLanguage({ id: 'ja', label: '日本語', fallback: 'en' }), + () => packCtx.locale.addLanguage({ id: 'fr', label: 'Français', fallback: 'en' }), 'test language pack registration', ), }) @@ -185,10 +185,36 @@ describe('locale apply', () => { { id: 'zh', label: '中文' }, { id: 'en', label: 'English' }, { id: 'ja', label: '日本語' }, + { id: 'fr', label: 'Français' }, ]) await languagePack.dispose() - expect(instance.getSnapshot().options.map(option => option.id)).toEqual(['zh', 'en']) + expect(instance.getSnapshot().options.map(option => option.id)).toEqual(['zh', 'en', 'ja']) + }) + + + it('restores Japanese from the Host and releases its language and dictionaries on unload', async () => { + const b = await bench('ja') + onTestFinished(() => b.ctx.fiber.dispose()) + declareItems(b.slots) + const fiber = b.ctx.plugin({ inject: [...inject], apply }) + await fiber.await() + const locale = b.ctx.get('locale') as LocaleRuntime + const { instance, face } = faceOf(b.slots) + expect(instance.getSnapshot().active).toBe('ja') + expect(locale.bind(SETTINGS_NS)('language.title')).toBe('言語') + expect(locale.bind('common')('cancel')).toBe('キャンセル') + face.setLocale('en') + face.setLocale('ja') + await vi.waitFor(() => { expect(b.mutate).toHaveBeenCalledTimes(2) }) + expect(b.mutate.mock.calls.at(-1)?.[1]).toEqual([{ op: 'set', path: ['preference'], value: 'ja' }]) + await fiber.dispose() + expect(locale.getSnapshot().locales.map(option => option.id)).toEqual(['zh', 'en']) + expect(() => { locale.setLocale('ja') }).toThrow('not registered') + const remove = locale.addLanguage({ id: 'ja', label: '日本語', fallback: 'en' }) + locale.setLocale('ja') + expect(locale.bind('common')('cancel')).toBe('cancel') + remove() }) it('loads and refreshes the explicit Host preference after nonblocking activation', async () => { diff --git a/packages/client/locale/tests/document-language.client.spec.ts b/packages/client/locale/tests/document-language.client.spec.ts index 35ff220f19..9e72b5149c 100644 --- a/packages/client/locale/tests/document-language.client.spec.ts +++ b/packages/client/locale/tests/document-language.client.spec.ts @@ -103,7 +103,7 @@ describe('document language', () => { const observer = new MutationObserver(() => {}) observer.observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] }) try { - const remove = locale.addLanguage({ id: 'ja', label: '日本語', fallback: 'en' }) + const remove = locale.addLanguage({ id: 'fr', label: 'Français', fallback: 'en' }) expect(observer.takeRecords()).toHaveLength(0) remove() expect(observer.takeRecords()).toHaveLength(0) diff --git a/packages/client/ui-agent-preset/src/client/guide-locales.ts b/packages/client/ui-agent-preset/src/client/guide-locales.ts index 2d8320f298..440784f363 100644 --- a/packages/client/ui-agent-preset/src/client/guide-locales.ts +++ b/packages/client/ui-agent-preset/src/client/guide-locales.ts @@ -150,3 +150,26 @@ export const guideZh: Record = { '预期产出:可在新任务中选择的自定义模式。审查要求用于指导 Agent,实际可执行的操作仍由权限设置决定。', ].join('\n\n'), } + +/** Japanese copy, checked against the English keys. */ +export const guideJa = { + 'modeExplanation': 'モードの詳細', + 'howToUse': '使い方', + 'guideSections': 'ガイドのセクション', + 'guideExampleTask': 'タスク例', + 'guideCopy': 'コピー', + 'guideCopied': 'コピーしました', + 'guideFootnotes': '脚注', + 'guideStandardIntro': '新しいタスクを始めるときは標準モードを選びます。達成したいことを説明し、関連するファイルを示し、結果の確認方法を伝えてください。', + 'guideStandardExplanation': '### 仕組み\n\nエージェントはツールを直接呼び出してファイルの読み取りや編集、検索、ターミナルコマンドの実行を行います。スキル、計画、目標、サブエージェント、ワークフロー、コンテキストの圧縮に対応しています。\n\n### 選ぶべき場面\n\n日々のコーディング、ファイル作業、調査はまずここから始めてください。標準モードではスクリプトの作成やファイルの一括処理も行えます。PTC はツール呼び出しの構成を変えるものであり、一括処理に必須ではありません。', + 'guideStandardUsage': '### バグを修正する\n\n> 検索フォームを二度送信すると結果が消える原因を調べてください。修正して関連するテストを実行し、原因と変更内容を説明してください。\n\n期待される出力: コードの変更、関連するテスト結果、原因の説明。\n\n### プロジェクトのメモを整理する\n\n> このプロジェクト内の Markdown メモを読み、合意された決定と未解決の課題を、元ファイルへのリンク付きで要約してください。\n\n期待される出力: 元のメモと照合できる参照付きの要約。', + 'guidePtcIntro': '新しいタスクを始めるときは PTC モードを選びます。入力ファイル、処理ルール、出力形式を指定してください。コードはエージェントが書きます。', + 'guidePtcExplanation': '### ツールの呼び出され方\n\nPTC は Programmatic Tool Calling の略です。この組み込みプリセットでは、エージェントは run_code を使い、生成された SDK 経由でツールを呼び出す TypeScript プログラムを書きます。プログラムではループ、条件分岐、エラー処理、必要に応じて並行呼び出しを利用できます。\n\n### モデルに届くもの\n\nツールの結果はまずプログラムに届き、そこでフィルタリングや結合ができます。モデルが受け取るのはプログラムが出力または返した内容です。画像の結果は別途添付されます。ネストされたツール呼び出しも記録され、ツール権限の対象のままです。\n\n### 標準モードとの比較\n\nどちらのモードもコーディングや一括処理のタスクを扱えます。標準モードは個々のツールを直接公開し、PTC はツール呼び出しをコードで整理します。現在の PTC プリセットではワークフローツールが無効になっています。速度とトークン消費はタスクやプログラムの結果の扱い方によって変わります。', + 'guidePtcUsage': '### 設定ファイルのセットを検証する\n\n> configs/ 内のすべての JSON ファイルを検査してください。schema.json に基づき、必須フィールドの欠落と無効な値を一覧にしてください。問題ごとに 1 行の CSV を保存してください。読み取れないファイルもレポートに含め、残りの検査は続けてください。元のファイルは変更しないでください。\n\n期待される出力: 問題の要約と CSV レポート。プログラムは同じ検査を繰り返し、個々の失敗に対処し、結果を集約できます。\n\n### エラーログを要約する\n\n> logs/ 内のログファイルを分析してください。エラーをサービスとエラー種別でグループ化し、頻度上位 10 グループと各 1 件の例を示してください。完全な件数を CSV に保存してください。\n\n期待される出力: 上位のエラーグループと完全な件数表。中間データは要約がモデルに届く前にプログラム内で集約できます。', + 'guideMinimalIntro': '新しいタスクには最小モードを選びます。比較を行う場合は、モデル、権限、入力、開始時のワークスペース状態を実行間で一定に保ってください。', + 'guideMinimalExplanation': '### 含まれるもの\n\n永続的なシェルツール 1 つ、read_image、固定のシステムプロンプト。画像の読み取りには画像対応モデルが必要です。古い履歴はコンテキストの負荷に応じて自動的に圧縮されます。エージェントがアイドル状態のときに /compact を使えば手動で圧縮できます。この組み込みプリセットはスキル、計画、標準のランタイムコンテキストを読み込みません。\n\n### 選ぶべき場面\n\n実験や比較のベースラインとして使用します。シェルコマンド経由でファイルの読み取りやスクリプトの実行はできますが、長いタスクを管理するための組み込み手段は少なくなります。ツールが少ないからといって、初心者にとって必ずしも簡単になるわけではありません。', + 'guideMinimalUsage': '### 小さなバグ修正で性能を比較する\n\n> このプロジェクトのテストを実行し、失敗の原因を見つけて最小限の修正をしてください。関連するテストを再度実行し、結果を報告してください。\n\n同じ開始状態から、標準モードと最小モードで同じタスクを別々に実行します。タスクの完了状況、ツール呼び出し、生じた変更を比較してください。最小モードはターミナルコマンドを通じて作業を行います。', + 'guideCordisIntro': '新しいタスクにはクリエイターモードを選びます。実現したい機能、表示される場所、確認方法を説明してください。', + 'guideCordisExplanation': '### 作成できるもの\n\nクリエイターモードには標準的なタスクツールに加え、ランタイムの検査、永続的なプラグイン管理、Cordis プラグインやエージェントプリセットの作成ガイドが含まれます。機能や UI を追加するプラグインを作成することも、特定の作業向けにツールとプロンプトを組み合わせたプリセットを作成することもできます。\n\n### プラグインとモード\n\nプラグインはツール、サービス接続、UI エントリなど、DSH に機能を追加します。モードはエージェントプリセットであり、ツールを選択し、タスク内でのエージェントの動作を定義します。プラグインはカスタムプリセットに含めることができます。\n\n### 結果を反映させる方法\n\nソースコードを生成するだけでなく、エージェントにインストールと確認を依頼してください。プラグインは変更内容によっては即座に読み込まれるか、再起動が必要になる場合があります。新しく作成したプリセットは、新しいタスクを始めるときに選択されます。', + 'guideCordisUsage': '### UI を追加する\n\n> サイドバーにプロジェクトメモのエントリを追加する DSH プラグインを作成してください。このワークスペース内の Markdown ファイルを閲覧でき、選択したメモをプレビューできるようにしてください。インストールしてページが開くことを確認してください。\n\n期待される出力: 動作するエントリとプレビューページを備えた、インストール済みのプラグイン。残っている有効化手順があればそれも含みます。\n\n### ツールを追加する\n\n> このプロジェクトのテストレポートを読み、失敗したテストを要約するツールを備えたプラグインを作成してください。登録し、サンプルのレポートで確認してください。\n\n期待される出力: 呼び出し可能なツールを備えたプラグインと、確認済みのサンプル呼び出し。\n\n### 独自のモードを作成する\n\n> 標準モードをベースに「コードレビュー」モードを作成してください。潜在的なバグとテストの不足を優先し、ファイルパスと行番号を示し、ファイルを変更する前に確認を求めるようにしてください。選択可能なプリセットとして保存してください。\n\n期待される出力: 新しいタスク用のカスタムプリセット。これらのレビュー指示はエージェントを導くものであり、実行できる操作は権限設定によって決まります。', +} satisfies Record diff --git a/packages/client/ui-agent-preset/src/client/index.ts b/packages/client/ui-agent-preset/src/client/index.ts index 6988c1cc4f..3cd120549e 100644 --- a/packages/client/ui-agent-preset/src/client/index.ts +++ b/packages/client/ui-agent-preset/src/client/index.ts @@ -42,7 +42,7 @@ import { AgentPresetSection } from './AgentPresetSection.tsx' import type { AgentPresetSectionInjected } from './AgentPresetSection.tsx' import { AgentPresetSeatController, type AgentPresetStage } from './seat-store.ts' import { AgentPresetSectionController } from './section-store.ts' -import { en, zh, type AgentPresetSettingsKey } from './locales.ts' +import { en, ja, zh, type AgentPresetSettingsKey } from './locales.ts' import { AGENT_PRESET_SETTINGS_NS, AgentPresetSettingsController } from './settings-store.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { @@ -127,7 +127,8 @@ export function apply(ctx: ClientContext): void { return binding === undefined ? undefined : seatFor(binding) } - ctx.effect(() => ctx.locale.register('settings.agentPreset', { zh, en }), 'ui-agent-preset: settings row dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register('settings.agentPreset', dictionaries), 'ui-agent-preset: settings row dictionaries') ctx.effect(() => { let requested = 0 diff --git a/packages/client/ui-agent-preset/src/client/locales.ts b/packages/client/ui-agent-preset/src/client/locales.ts index 49178ca524..00fce156ee 100644 --- a/packages/client/ui-agent-preset/src/client/locales.ts +++ b/packages/client/ui-agent-preset/src/client/locales.ts @@ -1,6 +1,6 @@ /** Locale bundles for the agent-preset hero chip, header label, and management section. */ -import { guideEn, guideZh, type PresetGuideKey } from './guide-locales.ts' +import { guideEn, guideJa, guideZh, type PresetGuideKey } from './guide-locales.ts' /** Locale keys these surfaces render. */ export type AgentPresetSettingsKey = @@ -125,3 +125,36 @@ export const zh: Record = { // Settings plugin list inlines the same fold over this plugin's dictionaries. export { isBuiltInPreset, presetDisplayText } from '@deepseek-ai/dsh-agent-preset-registry/display' export type { PresetDisplaySource, PresetDisplayText } from '@deepseek-ai/dsh-agent-preset-registry/display' + +/** Japanese copy, checked against the English keys. */ +export const ja = { + ...guideJa, + 'builtInGroup': '組み込み', + 'customGroup': 'カスタム', + 'sectionIntro': 'エージェントのツールと動作を選択します。日々のタスクには標準モードを、DSH に機能を追加するにはクリエイターモードを使用してください。', + 'seatHint': '新しいタスクで使用するエージェントプリセットを選択', + 'headerHint': 'このタスクの開始時に選択されたエージェントプリセット', + 'nav': 'エージェントプリセット', + 'setDefault': '新しいタスクの既定に設定', + 'view': '設定を表示', + 'presetStandardName': '標準モード', + 'presetStandardDescription': 'コード、ファイル、情報を扱います。検索、編集、ターミナルコマンドなどのツールを必要に応じて利用でき、ほとんどのタスクに適しています。', + 'presetPtcName': 'PTC モード', + 'presetPtcDescription': '標準モードのすべての機能を含みます。ツールを一括で呼び出し、その結果を絞り込み、整理、重複排除、集計、要約するタスクに適しています。', + 'presetMinimalName': '最小モード', + 'presetMinimalDescription': 'エージェントはターミナルと画像読み取りを使用し、コンテキスト圧縮にも対応します。基本的な性能のテストや比較に役立ちます。', + 'presetCordisName': 'クリエイターモード', + 'presetCordisDescription': '会話を通じて DSH をカスタマイズします。機能や UI を追加するプラグインをエージェントに書かせたり、ツールとプロンプトを組み合わせて独自のモードを作成したりできます。', + 'inUse': '新しいタスクの既定', + 'noDescription': '説明はありません。', + 'brokenBadge': '読み込みに失敗', + 'switchRefused': '{name} に切り替えられませんでした: {reason}', + 'standardUnavailable': '標準モードは利用できません。復元するか、利用可能な別のモードを選択してください。', + 'close': '閉じる', + 'creatorDraft': 'エージェントにプリセット作成を手伝ってもらう', + 'createPlugin': 'エージェントにプラグインを作成してもらう', + 'createPluginDescription': 'クリエイターモードに入り、独自の DSH プラグインを作成', + 'createPluginChecking': 'クリエイターモードが利用可能か確認中', + 'createPluginUnavailable': '一時的に利用できません。このメニューを開き直して再試行してください', + 'createPluginMissing': 'この構成にはクリエイターモードが含まれていません', +} satisfies Record diff --git a/packages/client/ui-agent-preset/tests/locales.client.spec.ts b/packages/client/ui-agent-preset/tests/locales.client.spec.ts index 8c643accfc..1a2fb3412a 100644 --- a/packages/client/ui-agent-preset/tests/locales.client.spec.ts +++ b/packages/client/ui-agent-preset/tests/locales.client.spec.ts @@ -1,7 +1,7 @@ /** Web-localized copy for the four shipped presets and declared copy for every other row. */ import { describe, expect, it } from 'vitest' -import { en, presetDisplayText, zh } from '../src/client/locales.ts' +import { en, ja, presetDisplayText, zh } from '../src/client/locales.ts' const translate = (bundle: typeof en) => (key: keyof typeof en): string => bundle[key] @@ -11,13 +11,15 @@ describe('preset display copy', () => { ['ptc', 'presetPtcName', 'presetPtcDescription'], ['minimal', 'presetMinimalName', 'presetMinimalDescription'], ['cordis', 'presetCordisName', 'presetCordisDescription'], - ] as const)('localizes the shipped %s preset in English and Chinese', (id, nameKey, descriptionKey) => { + ] as const)('localizes the shipped %s preset in English, Chinese, and Japanese', (id, nameKey, descriptionKey) => { const preset = { id } expect(presetDisplayText(preset, translate(en))) .toEqual({ name: en[nameKey], description: en[descriptionKey] }) expect(presetDisplayText(preset, translate(zh))) .toEqual({ name: zh[nameKey], description: zh[descriptionKey] }) + expect(presetDisplayText(preset, translate(ja))) + .toEqual({ name: ja[nameKey], description: ja[descriptionKey] }) }) it('keeps declared metadata untranslated for named and unknown presets', () => { diff --git a/packages/client/ui-approval/src/client/index.ts b/packages/client/ui-approval/src/client/index.ts index ca6d674e36..abbbdbad8a 100644 --- a/packages/client/ui-approval/src/client/index.ts +++ b/packages/client/ui-approval/src/client/index.ts @@ -10,7 +10,7 @@ import type {} from '@deepseek-ai/dsh-client-locale/client' import type { ShortcutCommandId } from '@deepseek-ai/dsh-client-shortcuts/client' import { ApprovalPanel } from './ApprovalPanel.tsx' import { PendingApproval } from './contract/slots.ts' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' export type { ApprovalComposerProps, @@ -75,7 +75,8 @@ async function answerApproval( * @param ctx - Client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-approval: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-approval: dictionaries') ctx.inject(['shortcuts'], (scope) => { const t = ctx.locale.bind(NS) scope.effect(() => scope.shortcuts.registerFixed({ diff --git a/packages/client/ui-approval/src/client/locales.ts b/packages/client/ui-approval/src/client/locales.ts index e28f2ce74a..3ef7a5c771 100644 --- a/packages/client/ui-approval/src/client/locales.ts +++ b/packages/client/ui-approval/src/client/locales.ts @@ -20,3 +20,12 @@ export const en = { reject: 'Reject', allowOnce: 'Allow once', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'waiting': '承認待ち', + 'detail.aria': '承認の詳細', + 'escalation': 'ツール {toolName} が特権実行を要求しています', + 'reject': '拒否', + 'allowOnce': '1 回だけ許可', +} satisfies Record diff --git a/packages/client/ui-chat/src/client/apply.ts b/packages/client/ui-chat/src/client/apply.ts index 6432e3e09e..cb9415d72a 100644 --- a/packages/client/ui-chat/src/client/apply.ts +++ b/packages/client/ui-chat/src/client/apply.ts @@ -36,7 +36,7 @@ import { UserContent } from './chat/UserContent.tsx' import { ActivityPill, UsagePill } from './chat/StatsPills.tsx' import { registerConversationNodes } from './conversation-nodes/register.ts' import { QuotaNoticeHost } from './chat/QuotaNoticeHost.tsx' -import { en, NS, zh } from './locale.ts' +import { en, ja, NS, zh } from './locale.ts' import { TranscriptViewRow, type TranscriptViewRowInjected } from './settings/TranscriptViewRow.tsx' import { createChatStore } from './stores.ts' import { TranscriptViewPolicy } from './transcript-view.ts' @@ -133,7 +133,8 @@ export function apply(ctx: Context): void { resolve: binding => ({ hooks: { chat: chatSource(binding) } }), }) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-chat: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-chat: dictionaries') const t = ctx.locale.bind(NS) const chatStore = createChatStore() const chatScrollPositions = new Map() diff --git a/packages/client/ui-chat/src/client/locale.ts b/packages/client/ui-chat/src/client/locale.ts index 7a950132e2..c5ffa1e87c 100644 --- a/packages/client/ui-chat/src/client/locale.ts +++ b/packages/client/ui-chat/src/client/locale.ts @@ -403,3 +403,202 @@ export const en = { 'clock.md': '{m}/{d}', 'clock.ymd': '{y}-{m}-{d}', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'message.stepProcess.thinking': 'リクエストを分析中', + 'message.stepProcess.read': 'ファイルを読み取り中', + 'message.stepProcess.readImage': '画像を読み取り中', + 'message.stepProcess.write': 'ファイルを書き込み中', + 'message.stepProcess.search': 'コードを検索中', + 'message.stepProcess.edit': 'ファイルを編集中', + 'message.stepProcess.commands': 'コマンドを実行中', + 'message.stepProcess.code': 'コードを実行中', + 'message.stepProcess.webSearch': 'ウェブを検索中', + 'message.stepProcess.webFetch': 'ウェブページを閲覧中', + 'message.stepProcess.subagents': 'サブエージェントを調整中', + 'message.stepProcess.plan': '計画を更新中', + 'message.stepProcess.questions': '操作を待機中', + 'message.stepProcess.tools': 'ツールを呼び出し中', + 'message.stepProcess.prepare.read': 'ファイル読み取りの準備中', + 'message.stepProcess.prepare.readImage': '画像読み取りの準備中', + 'message.stepProcess.prepare.write': 'ファイル書き込みの準備中', + 'message.stepProcess.prepare.search': 'コード検索の準備中', + 'message.stepProcess.prepare.edit': 'ファイル編集の準備中', + 'message.stepProcess.prepare.commands': 'コマンド実行の準備中', + 'message.stepProcess.prepare.code': 'コード実行の準備中', + 'message.stepProcess.prepare.webSearch': 'ウェブ検索の準備中', + 'message.stepProcess.prepare.webFetch': 'ウェブページ閲覧の準備中', + 'message.stepProcess.prepare.subagents': 'サブエージェント調整の準備中', + 'message.stepProcess.prepare.plan': '計画更新の準備中', + 'message.stepProcess.prepare.questions': '質問の準備中', + 'message.stepProcess.prepare.tools': 'ツール呼び出しの準備中', + 'message.stepProcess.done.thinking': '分析完了', + 'message.stepProcess.done.read': 'ファイルを読み取りました', + 'message.stepProcess.done.readImage': '画像を読み取りました', + 'message.stepProcess.done.write': 'ファイルを書き込みました', + 'message.stepProcess.done.search': 'コードを検索しました', + 'message.stepProcess.done.edit': 'ファイルを編集しました', + 'message.stepProcess.done.commands': 'コマンドを実行しました', + 'message.stepProcess.done.code': 'コードを実行しました', + 'message.stepProcess.done.webSearch': 'ウェブを検索しました', + 'message.stepProcess.done.webFetch': 'ウェブページを閲覧しました', + 'message.stepProcess.done.subagents': 'サブエージェントを調整しました', + 'message.stepProcess.done.plan': '計画を更新しました', + 'message.stepProcess.done.questions': '質問しました', + 'message.stepProcess.done.tools': 'ツールを呼び出しました', + 'message.stepProcess.joinTwo': '{first}と{second}', + 'message.stepProcess.comma': '、', + 'message.stepProcess.sharedPrefix': '', + 'message.stepProcess.more': '{title}など', + 'message.trigger.request': '実行が要求されました', + 'message.trigger.goal': '目標を継続中', + 'message.trigger.agent': 'タスクメッセージを受信しました', + 'message.trigger.team': 'チームメッセージを受信しました', + 'message.trigger.subagent': 'サブタスクの状態が更新されました', + 'message.trigger.github': 'GitHubイベントを受信しました', + 'message.trigger.webhook': '外部イベントを受信しました', + 'message.trigger.schedule': '自動化タスク', + 'message.trigger.job': 'バックグラウンドタスクが更新されました', + 'message.trigger.plugin': 'プラグインの状態が更新されました', + 'message.trigger.explanation': 'この通知がこの応答のトリガーとなりました。', + 'message.turnProcess.worked': '完了', + 'message.turnProcess.took': '完了まで ', + 'message.turnProcess.failed': '失敗', + 'view.chat': 'チャット', + 'image.open': '画像を全画面表示', + 'image.loading': '画像を読み込み中…', + 'image.failed': '画像プレビューを利用できません', + 'image.dialog': '画像プレビュー', + 'image.close': '画像プレビューを閉じる', + 'number.groupSeparator': ',', + 'duration.compactSeconds': '{seconds}秒', + 'duration.compactMinutes': '{minutes}分{seconds}秒', + 'duration.milliseconds': '{milliseconds}ms', + 'stats.counts': '{turns}ターン {steps}ステップ', + 'stats.cacheHit': 'キャッシュヒット {percent}%', + 'stats.dialog.title': 'セッション統計', + 'stats.dialog.usageTitle': 'トークン使用量', + 'stats.dialog.llmTime': 'LLM時間', + 'stats.dialog.toolTime': 'ツール時間', + 'stats.dialog.ttft': '平均初回トークン時間(TTFT)', + 'stats.dialog.speed': '毎秒トークン数(TPS)', + 'chat.loadingHistory': '履歴を読み込み中…', + 'chat.loadError': '履歴の読み込みに失敗しました: {message}({code})', + 'chat.loadOlder': '以前を読み込む', + 'chat.toBottom': '最下部へ戻る', + 'chat.deepDiving': '深掘り中', + 'chat.deepDivingFor': '{duration} の間深く分析中 ···', + 'chat.turnNavigation.label': 'ターン移動', + 'chat.turnNavigation.jump': 'ターン {turn} へ移動', + 'chat.turnNavigation.jumpLoad': '読み込んでターン {turn} へ移動', + 'chat.turnNavigation.turn': 'ターン {turn}', + 'settings.performance.title': 'パフォーマンスと使用状況', + 'settings.performance.description': 'パフォーマンスと使用状況の表示量を選択', + 'settings.performance.compact': 'コンパクト', + 'settings.performance.detailed': '詳細', + 'settings.links.title': 'チャットリンクを開く場所', + 'settings.links.description': 'Web リンクを開く場所を選択', + 'settings.links.sidebar': 'アプリ内サイドバー', + 'settings.links.newTab': '既定のブラウザー', + 'settings.transcript.title': '作業の詳細', + 'settings.transcript.description': 'ツール呼び出しの表示詳細度を選択', + 'settings.collapse.title': '作業詳細を折りたたむタイミング', + 'settings.collapse.description': '作業詳細を自動で折りたたむタイミングを選択', + 'settings.collapse.completion': '完了時', + 'settings.collapse.nextInput': '次のメッセージ送信時', + 'settings.foldedReplies.title': '折りたたみ後の返信', + 'settings.foldedReplies.description': '作業詳細の折りたたみ時に表示するアシスタントの返信を選択', + 'settings.foldedReplies.all': 'すべての返信', + 'settings.foldedReplies.final': '最終回答のみ', + 'settings.transcript.compact': 'コンパクト', + 'settings.transcript.standard': '標準', + 'settings.transcript.detailed': '詳細', + 'settings.transcript.verbose': '冗長', + 'fileOpen.title': 'ファイルを開けませんでした', + 'fileOpen.unknown': 'このファイルを開けませんでした', + 'message.extraBlock': '追加のコンテンツブロック', + 'message.systemPrompt': 'システムプロンプト', + 'message.systemPromptUpdate': 'システムプロンプトの更新', + 'message.toolAdded': 'ツールを追加: {name}', + 'message.toolRemoved': 'ツールを削除: {name}', + 'message.toolsAdded': '追加: {names}', + 'message.toolsAddedCount': '{count} 件追加', + 'message.toolsChanged': '{added} 件追加、{removed} 件削除', + 'message.toolsRemoved': '削除: {names}', + 'message.toolsRemovedCount': '{count} 件削除', + 'message.toolsUpdated': 'ツールを更新しました', + 'message.contextInjection': 'コンテキスト注入', + 'message.contextRecall': 'セッションの呼び出し', + 'message.referenceSummary': '参照セッション · {labels}', + 'message.referenceSeparator': ', ', + 'message.context.instructions.loaded': '読み込み済み', + 'message.context.instructions.added': '追加済み', + 'message.context.instructions.updated': '更新済み', + 'message.context.instructions.removed': '削除済み', + 'message.context.catalog.replaced': '置換カタログ', + 'message.context.catalog.more': '… 他 {count} 件', + 'message.context.snapshot.supersedes': '以前のスナップショットを置き換えます', + 'message.context.relay.from': 'セッション {session} から', + 'message.context.recall.counts': '{retained} 件保持 · {omitted} 件除外', + 'message.context.recall.truncated': '切り詰め済み', + 'message.compaction': 'コンテキストを圧縮しました', + 'message.compaction.running': 'コンテキストを圧縮中…', + 'message.compaction.completed': '履歴 {items} 件を圧縮しました(約 {tokens} トークン)', + 'message.compaction.expand': '圧縮の要約を表示', + 'message.compaction.unavailable': '圧縮の要約は利用できません', + 'message.compaction.commandTitle': 'compact', + 'message.think': '思考', + 'message.unknownSurface': '不明なサーフェスイベント: {type}', + 'message.unknownBlock': '不明なコンテンツブロック', + 'message.turnProcess.toolCalls.one': 'ツール呼び出し {count} 件', + 'message.turnProcess.toolCalls.other': 'ツール呼び出し {count} 件', + 'message.turnProcess.messages.one': 'メッセージ {count} 件', + 'message.turnProcess.messages.other': 'メッセージ {count} 件', + 'message.turnProcess.subagents.one': 'サブエージェント {count} 件', + 'message.turnProcess.subagents.other': 'サブエージェント {count} 件', + 'message.turnProcess.thoughtForAWhile': 'しばらく思考しました', + 'message.turnProcess.separator': ' · ', + 'message.stopped': '停止しました', + 'message.branch': '新しい会話に分岐', + 'message.branchUnavailable': '完了したターンの最後のメッセージでのみ利用できます', + 'message.retry.active': 'モデルリクエストを再試行中', + 'message.retry.cancelled': 'モデルリクエストの再試行をキャンセルしました', + 'message.retry.started': 'モデルリクエストを再試行しました', + 'message.retry.scheduled': 'モデルリクエストの再試行待ちです', + 'message.retry.status': '{label} ({retry}/{maximum}) · {seconds}秒', + 'message.retry.delay': '再試行までの待機時間: ', + 'message.retry.failure': '失敗理由: ', + 'message.failure.auth': 'APIキーが無効です', + 'message.taskStopped': 'タスクを停止しました', + 'message.failure.accountSignedOut': 'DeepSeekからログアウトしたため停止しました。', + 'message.failure.interrupted': 'このターンが完了する前にDSHプロセスが終了したため(強制終了、クラッシュ、シャットダウン中のタイムアウトなど)、DSHの再起動時にターンは閉じられました。未完了のツール呼び出しの結果は不明です。', + 'message.failure.accountSignInRequired': 'DeepSeekにログインし、リクエスト先がアカウント認証に対応していることを確認してください。', + 'message.failure.quota': 'リクエストのクォータを使い切りました。', + 'message.turnError': 'このターンは失敗しました', + 'message.maxTokens': '出力トークンの上限に達しました', + 'message.maxTokens.hint': '応答が途中で打ち切られました。それまでの出力は会話に保持されています。「続けて」と送信するとモデルが再開します。', + 'message.tokensPerSecond': '{tps} tok/s', + 'message.turnUsage.title': 'ターンの使用量', + 'message.turnUsage.consumed': '使用量 {total}', + 'message.turnUsage.model': 'プロバイダー / モデル', + 'message.turnUsage.cacheHit': 'キャッシュヒット', + 'message.turnUsage.input': '未キャッシュの入力', + 'message.turnUsage.cacheRead': 'キャッシュ済み入力', + 'message.turnUsage.cacheWrite': 'キャッシュ書き込み', + 'message.turnUsage.output': '出力', + 'message.turnUsage.reasoning': ' (推論 {tokens})', + 'message.turnUsage.count': '{count} tok', + 'duration.secondUnit': '秒', + 'duration.minuteUnit': '分 ', + 'duration.hourUnit': '時間 ', + 'command.running': '実行中…', + 'command.failed': 'コマンドが失敗しました', + 'command.done': '完了', + 'command.title': 'コマンド', + 'row.running': '実行中', + 'row.failed': '失敗', + 'json.truncated': '… 省略表示、全{total}文字', + 'clock.md': '{m}/{d}', + 'clock.ymd': '{y}-{m}-{d}', +} satisfies Record diff --git a/packages/client/ui-commands/src/client/index.ts b/packages/client/ui-commands/src/client/index.ts index a629bd3240..5518a927ff 100644 --- a/packages/client/ui-commands/src/client/index.ts +++ b/packages/client/ui-commands/src/client/index.ts @@ -18,7 +18,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-session/client' import { CommandUiRuntime } from './service.ts' import type { PopupSelectInjected } from './PopupSelectView.tsx' import { PopupSelectView } from './PopupSelectView.tsx' -import { en, zh, type CommandKey } from './locales.ts' +import { en, ja, zh, type CommandKey } from './locales.ts' export { CommandUiRuntime } from './service.ts' export { CommandDirectory } from './directory.ts' @@ -56,7 +56,8 @@ export const inject = ['inputTriggers', 'sessions', 'remote', 'remote.commands', * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-commands: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-commands: dictionaries') ctx.plugin(CommandUiRuntime) ctx.inject(['slots', 'commandUi', 'sessions'], (scope: ClientContext) => { const command = scope.commandUi diff --git a/packages/client/ui-commands/src/client/locales.ts b/packages/client/ui-commands/src/client/locales.ts index fd986c2a1f..de34e25cb7 100644 --- a/packages/client/ui-commands/src/client/locales.ts +++ b/packages/client/ui-commands/src/client/locales.ts @@ -71,3 +71,35 @@ export const en = { 'listbox.aria': '/{command} matches', 'notice.attachmentsUnsupported': '/{command} does not accept attachments; remove them first', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'section.add': '追加', + 'section.commands': 'コマンド', + 'label.goal': '目標', + 'label.plan': '計画', + 'label.feedback': 'フィードバック', + 'label.compact': '圧縮', + 'label.permission': '権限', + 'label.export': 'エクスポート', + 'description.goal': '長時間実行するタスクの目標を設定または表示', + 'description.plan': '計画モードに入るまたは終了する', + 'description.feedback': 'このセッションに関するフィードバックを記録', + 'description.compact': '古い会話履歴を圧縮', + 'description.permission': '権限プリセットを切り替える (サンドボックスモード + 承認ポリシー)', + 'description.export': 'このセッションログを ZIP アーカイブとしてダウンロード', + 'token.goal': 'goal', + 'token.plan': 'plan', + 'token.feedback': 'feedback', + 'token.compact': 'compact', + 'token.permission': 'permission', + 'token.export': 'export', + 'search.placeholder': '検索…', + 'search.aria': 'オプションを絞り込む', + 'status.loading': 'オプションを読み込み中…', + 'status.applying': '適用中…', + 'status.empty': 'オプションなし', + 'overlay.aria': '/{command} のオプション', + 'listbox.aria': '/{command} の一致項目', + 'notice.attachmentsUnsupported': '/{command} は添付ファイルを受け付けません。先に削除してください', +} satisfies Record diff --git a/packages/client/ui-conversation/src/client/apply.ts b/packages/client/ui-conversation/src/client/apply.ts index 6594c95812..8c375e9aad 100644 --- a/packages/client/ui-conversation/src/client/apply.ts +++ b/packages/client/ui-conversation/src/client/apply.ts @@ -46,7 +46,7 @@ import { todoDockEntry } from './skeleton/TodoPanel.tsx' import { installStopShortcut } from './stop-shortcut.ts' import { TRAJECTORY_VIEW_ID, resolveActiveView } from './view-selection.ts' import { createViewPins } from './view-pins.ts' -import { en, NS, zh, type ConversationKey } from './locales.ts' +import { en, ja, NS, zh, type ConversationKey } from './locales.ts' import { CONVERSATION_SETTINGS_NAMESPACE, type ConversationSettings } from '../submission-settings.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { @@ -185,7 +185,8 @@ export function apply(ctx: Context, config: Config = Config({})): void { yield slots.register({ name: 'conversation.user.text.node', key: 'session', priority: 100 }, SessionReference) }) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-conversation: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-conversation: dictionaries') const t = ctx.locale.bind(NS) const conversationStore = createConversationStore() const submissionPolicy = new ComposerSubmissionPolicy( diff --git a/packages/client/ui-conversation/src/client/locales.ts b/packages/client/ui-conversation/src/client/locales.ts index 5a221bc9a1..15e086848c 100644 --- a/packages/client/ui-conversation/src/client/locales.ts +++ b/packages/client/ui-conversation/src/client/locales.ts @@ -767,3 +767,381 @@ export const en = { 'terminal.sendInput': '(send input)', 'terminal.session': 'Terminal {sessionId}', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'shortcut.newline': '改行', + 'shortcut.complementary': '補助のキュー / ステア操作を使用', + 'shortcut.slash': 'コマンドメニューを開く', + 'shortcut.mention': '参照メニューを開く', + 'hint.plan': '計画を生成するにはタスクを説明してください', + 'hint.goal': '長時間実行するタスクの目標を説明してください', + 'hint.goal.active': '目標アクティブ — 編集 / 一時停止 / 再開 / クリア', + 'placeholder.plan': '計画を生成するにはタスクを説明してください', + 'placeholder.default': 'メッセージを送信するかタスクを実行、/ コマンド、@ ファイルやセッション', + 'placeholder.unavailable': 'セッションを利用できません', + 'placeholder.parentOffline': '親セッションがオフラインです。送信はできませんが、実行は停止できます', + 'placeholder.hero': '作りたいものを説明してください。/ コマンド、@ ファイルやセッション', + 'placeholder.workspace': '開始するワークスペースを選択', + 'placeholder.steerQueue': 'Cmd/Ctrl+Enterでキュー内のすべてのメッセージをステア', + 'input.commands': 'ファイルを追加またはコマンドを実行', + 'input.file': 'ファイル', + 'input.stop': '生成を停止', + 'input.send': 'メッセージを送信', + 'input.send.queue': 'メッセージをキューに追加', + 'input.send.steer': 'メッセージをステア', + 'attachment.pending': '保留中の添付ファイル', + 'attachment.scrollLeft': '添付ファイルを左にスクロール', + 'attachment.scrollRight': '添付ファイルを右にスクロール', + 'attachment.dropTitle': 'ファイルや画像をここにドラッグして追加', + 'attachment.dropDesc': '画像の上限: 最大{count}枚、各{size}', + 'attachment.dropBlocked': '現在ファイルや画像を追加できません', + 'attachment.directoryDesktopOnly': 'フォルダはデスクトップアプリでのみ追加できます。ブラウザでは個別のファイルを追加してください', + 'attachment.pathUnavailable': 'フォルダのパスを取得できませんでした。もう一度ドラッグしてください', + 'attachment.pathUnsupported': 'パスに参照できない文字が含まれています。名前を変更して再試行してください', + 'image.pending': '保留中の画像', + 'image.openOriginal': '元の画像を表示', + 'image.openOriginalLabel': '{label}、クリックで元の画像を表示', + 'image.remove': '画像{name}を削除', + 'image.original': '元の画像', + 'image.label': '画像', + 'image.loadFailed': '画像を読み込めませんでした。クリックで再試行', + 'image.loading': '画像を読み込み中…', + 'image.preview': '元の画像のプレビュー', + 'image.closePreview': '元の画像のプレビューを閉じる', + 'image.unsupportedType': '対応している画像形式はPNG、JPG、WebP、GIFのみです', + 'image.tooMany': '1つのメッセージに最大{count}枚の画像を含められます', + 'image.fileTooLarge': '各画像は{size}未満である必要があります', + 'image.totalTooLarge': '画像の合計が{size}を超えています。一部を削除して再試行してください', + 'image.tooManyPixels': '画像の解像度が高すぎます。圧縮して再試行してください', + 'image.dimensionTooLarge': '画像の各辺は最大{size}pxです。縮小して再試行してください', + 'image.modelUnsupported': '現在のモデルは画像に対応していません。対応モデルに切り替えてください', + 'image.sendFailed': '画像の送信に失敗しました({reason})。再度追加して再試行してください', + 'file.pending': '保留中のファイル', + 'file.remove': 'ファイル{name}を削除', + 'file.uploading': 'アップロード中…', + 'file.uploadFailed': 'アップロードに失敗しました。クリックで再試行', + 'file.retry': '{name}のアップロードを再試行', + 'file.stillUploading': 'ファイルをアップロード中です。完了後に送信してください', + 'file.sessionUnavailable': 'セッションを利用できないため、ファイルをアップロードできません', + 'file.notStaged': 'ファイルのアップロードが完了していません。再度追加して再試行してください', + 'file.label': 'ファイル', + 'context.aria': 'コンテキストの{percent}を使用中', + 'context.used': 'コンテキスト使用量', + 'context.system': 'システムプロンプト', + 'context.tools': 'ツール定義', + 'context.messages': 'メッセージ', + 'settings.enter.title': '実行中の送信動作', + 'settings.enter.description': 'エージェントの実行中にEnterキーと送信ボタンが行う動作。Cmd/Ctrl+Enterではもう一方の動作になります', + 'settings.enter.queue': 'キュー', + 'settings.enter.steer': 'ステア', + 'hero.headline': '未知への挑戦', + 'hero.preview': 'プレビュー', + 'hero.chooseWorkspace': 'ワークスペースを選択', + 'session.hierarchy': 'セッション階層', + 'session.moreViews': 'その他の会話ビュー', + 'todo.title': 'ToDo', + 'todo.progress.done': '{done}件完了', + 'todo.progress.active': '{active}件進行中', + 'todo.progress.pending': '{pending}件保留中', + 'todo.status.completed': '完了', + 'todo.status.inProgress': '進行中', + 'todo.status.pending': '保留中', + 'todo.rowTitle': 'ToDoリストを更新', + 'tool.title.createGoal': '目標を作成', + 'tool.title.getGoal': '目標を表示', + 'tool.title.updateGoal': '目標を更新', + 'tool.preparing.content': '{kilobytes}KB', + 'tool.title.createSchedule': 'リマインダーを作成', + 'tool.title.listSchedules': 'リマインダーを一覧表示', + 'tool.title.deleteSchedule': 'リマインダーを削除', + 'tool.title.updateSchedule': 'リマインダーを更新', + 'detail.state': 'ステータス', + 'detail.todo.completed': '完了', + 'detail.todo.in_progress': '進行中', + 'detail.todo.pending': '保留中', + 'detail.todo.empty': 'ToDoリストは空です', + 'todo.diff.initial': '初期リスト', + 'todo.diff.compare': '前回のリストからの変更', + 'todo.diff.unavailable': '前回のリストは利用できません', + 'todo.diff.noChanges': 'リストに変更はありません', + 'todo.diff.added': '{count} 件追加', + 'todo.diff.updated': '{count} 件更新', + 'todo.diff.removed': '{count} 件削除', + 'todo.diff.unchanged': '{count} 件変更なし', + 'todo.diff.addedItem': '追加', + 'todo.diff.updatedItem': 'ステータス変更', + 'todo.diff.movedItem': '順序変更', + 'todo.diff.removedItem': '削除', + 'detail.goal.empty': '目標なし', + 'detail.goal.active': 'アクティブ', + 'detail.goal.disarmed': '継続待ち', + 'detail.goal.paused': '一時停止', + 'detail.goal.blocked': 'ブロック中', + 'detail.goal.complete': '完了', + 'detail.goal.rounds': 'ラウンド', + 'detail.goal.reason': 'ブロッカー', + 'detail.days': '{count} 日', + 'detail.hours': '{count} 時間', + 'detail.minutes': '{count} 分', + 'detail.seconds': '{count} 秒', + 'detail.schedule.once': '1回のみ', + 'detail.schedule.every': '{interval} ごと', + 'detail.schedule.when': 'スケジュール設定', + 'detail.schedule.frequency': '繰り返し', + 'detail.schedule.scheduled': 'スケジュール済み', + 'detail.schedule.overdue': '期限超過、セッション再開待ち', + 'detail.schedule.empty': 'リマインダーなし', + 'detail.schedule.deleted': '削除済み', + 'detail.schedule.count': '{count} 件のリマインダー', + 'detail.schedule.daily': '毎日 {time} ({zone})', + 'detail.schedule.weekly': '毎週 {days} の {time} ({zone})', + 'detail.schedule.cron': 'Cron {expression} ({zone})', + 'detail.weekday.1': '月', + 'detail.weekday.2': '火', + 'detail.weekday.3': '水', + 'detail.weekday.4': '木', + 'detail.weekday.5': '金', + 'detail.weekday.6': '土', + 'detail.weekday.7': '日', + 'detail.weekday.join': ', ', + 'tool.title.inspectProviders': 'プロバイダーを検査', + 'tool.title.queryRuntime': 'ランタイムを照会', + 'tool.title.inspectPlugins': 'プラグインを検査', + 'tool.title.workflow': 'ワークフローを実行', + 'tool.title.ralph': 'ralph ループを実行', + 'tool.title.readEvent': 'イベントを読み取り', + 'tool.title.searchEvents': 'イベントを検索', + 'tool.title.traceEvent': 'イベントをトレース', + 'tool.title.searchSessions': 'セッションを検索', + 'tool.title.traceSession': 'セッションをトレース', + 'tool.title.listModels': 'モデルを一覧表示', + 'tool.title.subagent': 'サブエージェントを作成', + 'tool.title.listAgents': 'サブエージェントを一覧表示', + 'tool.title.sendMessage': 'メッセージを送信', + 'tool.title.interruptAgent': 'エージェントを中断', + 'tool.title.listJobs': 'バックグラウンドジョブを一覧表示', + 'tool.title.readJob': 'ジョブ出力を読み取り', + 'tool.title.killJob': 'バックグラウンドジョブをキャンセル', + 'tool.title.openTerminal': 'ターミナルを開く', + 'tool.title.readTerminal': 'ターミナルを読み取り', + 'tool.title.listTerminals': 'ターミナルを一覧表示', + 'tool.title.signalTerminal': 'ターミナルにシグナル送信', + 'tool.title.closeTerminal': 'ターミナルを閉じる', + 'tool.title.lsp': 'コードシンボルを照会', + 'tool.title.findDefinition': '定義を検索', + 'tool.title.findReferences': '参照を検索', + 'tool.title.findImplementation': '実装を検索', + 'tool.title.hoverSymbol': 'シンボルを検査', + 'tool.title.spawnTeammate': 'チームメイトを作成', + 'tool.title.createTeamTask': 'チームタスクを作成', + 'tool.title.getTeamTask': 'チームタスクを読み取り', + 'tool.title.updateTeamTask': 'チームタスクを更新', + 'tool.title.listTeamTasks': 'チームタスクを一覧表示', + 'tool.title.waitAgent': 'サブエージェントを待機', + 'detail.recordedResult': '記録された結果', + 'detail.empty': '結果なし', + 'detail.none': 'なし', + 'detail.yes': 'はい', + 'detail.no': 'いいえ', + 'detail.moreInInspect': 'Inspect で残り {count} 件を表示できます', + 'detail.status.running': '実行中', + 'detail.status.idle': '待機中', + 'detail.status.ready': '準備完了', + 'detail.status.inactive': '非アクティブ', + 'detail.status.provisioning': 'プロビジョニング中', + 'detail.status.failed': '失敗', + 'detail.status.completed': '完了', + 'detail.status.deleted': '削除済み', + 'detail.status.killed': 'キャンセル済み', + 'detail.status.accepted': '承認済み', + 'detail.status.queued': 'キュー待ち', + 'detail.status.exited': '終了', + 'detail.field.id': 'ID', + 'detail.field.revision': 'リビジョン', + 'detail.field.platform': 'プラットフォーム', + 'detail.field.provider': 'プロバイダー', + 'detail.field.model': 'モデル', + 'detail.field.role': 'ロール', + 'detail.field.context': 'コンテキスト', + 'detail.field.owner': 'オーナー', + 'detail.field.ready': '準備完了', + 'detail.field.dependencies': '依存関係', + 'detail.field.writeScopes': '書き込みスコープ', + 'detail.field.warnings': '警告', + 'detail.field.diagnostics': '診断', + 'detail.field.methods': 'メソッド', + 'detail.field.inputSchema': '入力スキーマ', + 'detail.field.outputSchema': '出力スキーマ', + 'detail.field.currentPackage': '現在のパッケージ', + 'detail.field.nextPackage': '次のパッケージ', + 'detail.field.latestRun': '最新の実行', + 'detail.field.packages': 'パッケージ', + 'detail.field.registrations': '登録', + 'detail.field.props': 'Props', + 'detail.field.data': 'データ', + 'detail.field.source': 'ソース', + 'detail.field.content': '内容', + 'detail.field.message': 'メッセージ', + 'detail.field.messageId': 'メッセージ ID', + 'detail.field.root': 'ルート', + 'detail.field.pid': 'プロセス ID', + 'detail.field.type': '種類', + 'detail.field.time': '時刻', + 'detail.field.seq': 'イベントシーケンス', + 'detail.field.turn': 'ターン', + 'detail.field.step': 'ステップ', + 'detail.field.callId': '呼び出し ID', + 'detail.field.agents': '起動したエージェント', + 'detail.field.result': '結果', + 'detail.field.parent': '親', + 'detail.field.depth': '深さ', + 'detail.field.exitCode': '終了コード', + 'detail.field.signal': 'シグナル', + 'detail.field.previousStatus': '以前のステータス', + 'detail.field.agent': 'エージェント ID', + 'detail.field.job': 'ジョブ ID', + 'detail.field.task': 'タスク', + 'detail.field.processGroup': 'プロセスグループ', + 'detail.field.availability': '可用性', + 'detail.field.bestMatch': '最適な一致', + 'detail.field.target': '対象イベント', + 'detail.field.surface': 'レコードステータス', + 'detail.agents.count': '{count} エージェント', + 'detail.jobs.count': '{count} バックグラウンドジョブ', + 'detail.terminals.count': '{count} ターミナル', + 'detail.tasks.count': '{count} チームタスク', + 'detail.tasks.nextPage': 'さらにタスクがあります。次のカーソルは {cursor} です', + 'detail.locations.count': '{count} 件の場所', + 'detail.location': '{line} 行目、{column} 列目', + 'detail.receipt.delivered': 'メッセージを配信しました', + 'detail.receipt.interrupt': '中断を要求しました', + 'detail.receipt.started': '開始済み', + 'detail.receipt.cancel': 'キャンセルを要求しました', + 'detail.receipt.alreadyFinished': '既に完了', + 'detail.receipt.signal': 'シグナル送信済み', + 'detail.receipt.closed': 'クローズ済み', + 'detail.receipt.closing': 'クローズ中', + 'detail.wait.noProgress': 'アクティブなサブエージェントなし', + 'detail.wait.title': 'サブエージェントのアクティビティ', + 'detail.wait.timeout': '待機がタイムアウトしました', + 'detail.wait.changed': '変更を検出', + 'detail.agent.reply': 'エージェントの応答', + 'detail.models.title': '利用可能なモデル', + 'detail.output.lines': '{total} 行中 {begin}–{end} 行', + 'detail.output.truncated': '出力は切り詰められています', + 'detail.providers.count': '{count} 件の inspect プロバイダー', + 'detail.plugins.count': '{count} 件の動的プラグイン', + 'detail.workflow.script': 'ワークフロースクリプト', + 'detail.ralph.reportedComplete': 'ワーカーが完了を報告', + 'detail.ralph.reportedBlocker': 'ワーカーがブロッカーを報告', + 'detail.ralph.limit': 'ラウンド上限に達しました', + 'detail.report.nextSteps': '残りの作業', + 'detail.trace.replacedBy': '置き換え元', + 'detail.trace.replacementChain': '置き換えチェーン', + 'detail.trace.replaces': '置き換えられたイベント', + 'detail.trace.sources': 'ソースイベント', + 'detail.trace.derived': '派生イベント', + 'detail.trace.ancestors': '祖先セッション', + 'detail.trace.descendants': '子孫セッション', + 'detail.matches.count': '{count} 件の一致', + 'detail.matches.capped': '結果の上限に達しました。さらに絞り込んでください', + 'detail.event.neighbors': '周辺のイベント', + 'todo.completed': '{done}/{total} 完了', + 'command.attachmentsUnsupported': '/{command} は添付ファイルを受け付けません。先に削除してください', + 'ask.rowTitle': '質問する', + 'ask.waiting': '待機中', + 'ask.pending': '継続中。引き続き回答できます', + 'ask.pendingDetail': 'これらの保留中の質問は、コンポーザーから引き続き回答できます。', + 'ask.reopen': '回答', + 'ask.review': '回答を表示', + 'ask.closed': 'クローズ済み', + 'ask.closedDetail': 'この質問はクローズされました。結果は下の会話にあります。', + 'ask.cancelled': 'キャンセル済み', + 'ask.cancelledDetail': 'この質問セットは回答が送信される前にキャンセルされました。', + 'ask.interrupted': '中断済み', + 'ask.interruptedDetail': 'この質問セットは回答が送信される前に中断されました。', + 'ask.answered': '{answered}/{total} 回答済み', + 'ask.skipped': '未回答', + 'bash.running': '実行中', + 'bash.failed': '失敗', + 'bash.stopped': '停止', + 'row.running': '実行中', + 'row.preparing': 'ツール呼び出しを準備中', + 'row.failed': '失敗', + 'row.stopped': '停止', + 'row.commandArguments': 'コマンド引数', + 'row.input': 'IN', + 'row.output': 'OUT', + 'row.inspect': '検査', + 'row.imageSize': '{width}x{height}px、{bytes} バイト', + 'row.imageSizeDownscaled': '{width}x{height}px({originalWidth}x{originalHeight}px から縮小)、{bytes} バイト', + 'tool.title.search': '検索', + 'tool.title.read': '読み取り', + 'tool.title.bash': 'Bash', + 'tool.title.write': '書き込み', + 'tool.title.edit': '編集', + 'tool.title.code': 'コード', + 'tool.title.generic': 'ツール呼び出し', + 'tool.title.inspect': 'Cordis 環境を照会', + 'tool.title.runCordis': 'Cordis プラグインを実行', + 'tool.title.stopCordis': 'Cordis プラグインを停止', + 'tool.title.removeCordis': 'Cordis プラグインを削除', + 'tool.title.pwsh': 'Pwsh', + 'tool.title.readImage': '画像を読み取り', + 'tool.title.grep': 'Grep', + 'tool.title.glob': 'Glob', + 'tool.title.webSearch': '検索', + 'tool.title.webFetch': '取得', + 'tool.autoReviewRejected': '自動レビューにより拒否されました', + 'tool.autoReviewNotExecuted': 'ツールは実行されませんでした。理由: {reason}', + 'tool.autoReviewReasonFallback': '自動レビューがこの操作を承認しませんでした', + 'diff.collapseAria': '差分を折りたたむ', + 'diff.expandAria': '差分の残り{count}行を表示', + 'diff.expandRest': '… 残り{count}行', + 'read.window': '{total}行中{shown}行を表示', + 'read.collapseAria': '内容を折りたたむ', + 'read.expandAria': '残り{count}行を表示', + 'read.expandRest': '… 残り{count}行', + 'search.paths': '{shown}件のパス', + 'search.paths.truncated': '{total}件中{shown}件のパスを表示', + 'search.matches': '{shown}件の一致 · {files}ファイル', + 'search.matches.truncated': '{total}件中{shown}件の一致を表示 · {files}ファイル', + 'search.noResults': '結果なし', + 'search.collapseAria': '結果を折りたたむ', + 'search.expandAria': '残り{count}行の結果を表示', + 'search.expandRest': '… 残り{count}行', + 'web.noResults': '結果が見つかりません', + 'web.sourcesTruncated': 'ソース一覧を切り詰めました', + 'web.http': 'HTTP', + 'web.contentTruncated': '内容を切り詰めました', + 'details.running': '実行中…', + 'queue.count': '{n}件のキュー済みメッセージ', + 'queue.sending': '送信中…', + 'queue.image': 'キュー済みメッセージの画像', + 'queue.file': 'キュー済みファイル {name}', + 'queue.edit': 'キュー済みメッセージを編集', + 'queue.edit.unsupported': 'テキスト以外の内容を含むため、まだ編集できません', + 'queue.save': 'キュー済みメッセージを保存', + 'queue.cancelEdit': '編集をキャンセル', + 'queue.remove': 'キュー済みメッセージを削除', + 'queue.steer': 'キュー済みメッセージをステア', + 'queue.steer.unavailable': 'ステアはエージェントの実行中のみ利用できます', + 'error.sessionInUse': 'このセッションはすでに使用中です。別の実行中のDSHインスタンス(dsh webやデスクトップアプリなど)が使用している可能性があります。他のDSHインスタンスを終了して再試行してください。', + 'queue.editFailed': '編集に失敗しました。このメッセージはすでに送信が開始されている可能性があります。', + 'queue.removeFailed': '削除に失敗しました。このメッセージはすでに送信が開始されている可能性があります。', + 'queue.steerFailed': 'ステアに失敗しました。再試行してください。', + 'terminal.signal': 'シグナル {signal}', + 'terminal.exitCode': '終了コード {code}', + 'terminal.noExitCode': '終了コードなし', + 'terminal.commandLine': 'コマンドライン {n}', + 'terminal.running': '実行中', + 'terminal.failed': '失敗', + 'terminal.done': '完了', + 'terminal.noOutput': '出力なし', + 'terminal.collapseAria': '出力を折りたたむ', + 'terminal.expandAria': '残り{n}行の出力を表示', + 'terminal.expandRest': '… 残り{n}行', + 'terminal.sendInput': '(入力を送信)', + 'terminal.session': 'ターミナル {sessionId}', +} satisfies Record diff --git a/packages/client/ui-deepseek-model-label-1017/src/client/index.ts b/packages/client/ui-deepseek-model-label-1017/src/client/index.ts index 63505299f6..d7de8644d1 100644 --- a/packages/client/ui-deepseek-model-label-1017/src/client/index.ts +++ b/packages/client/ui-deepseek-model-label-1017/src/client/index.ts @@ -4,7 +4,7 @@ import type {} from '@deepseek-ai/dsh-client-locale/client' import type {} from '@deepseek-ai/dsh-client-ui-model-selection/client' import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' import { DeepSeekModelLabel } from './DeepSeekModelLabel.tsx' -import { en, zh, type LabelKey } from './locales.ts' +import { en, ja, zh, type LabelKey } from './locales.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { @@ -22,7 +22,8 @@ export const inject = ['slots', 'locale'] */ export function apply(ctx: Context): void { const namespace = 'deepseek-model-label-1017' - ctx.effect(() => ctx.locale.register(namespace, { zh, en }), 'deepseek-model-label-1017: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(namespace, dictionaries), 'deepseek-model-label-1017: dictionaries') ctx.slots.inject('conversation.input.model.label', () => ctx.slots.register({ name: 'conversation.input.model.label', locale: namespace, }, DeepSeekModelLabel)) diff --git a/packages/client/ui-deepseek-model-label-1017/src/client/locales.ts b/packages/client/ui-deepseek-model-label-1017/src/client/locales.ts index d3eb4a956e..95ca2cb796 100644 --- a/packages/client/ui-deepseek-model-label-1017/src/client/locales.ts +++ b/packages/client/ui-deepseek-model-label-1017/src/client/locales.ts @@ -8,3 +8,8 @@ export type LabelKey = keyof typeof zh /** English badge copy. */ export const en: Record = { 'badge.label': '1017' } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'badge.label': '1017', +} satisfies Record diff --git a/packages/client/ui-deliverables/src/client/index.ts b/packages/client/ui-deliverables/src/client/index.ts index bbc165e99c..e7bc597e27 100644 --- a/packages/client/ui-deliverables/src/client/index.ts +++ b/packages/client/ui-deliverables/src/client/index.ts @@ -27,7 +27,7 @@ import { DeliverablesTail, type DeliverablesInjected } from './Deliverables.tsx' import { ReviewTab, type ReviewInjected } from './ReviewTab.tsx' import { CHANGES_REVIEW_ID, changesReviewDefinition } from './review-definition.ts' import { createReviewStore } from './review-store.ts' -import { en, NS, zh, type DeliverablesKey } from './locales.ts' +import { en, ja, NS, zh, type DeliverablesKey } from './locales.ts' import { deliverablesDefinition, presentedForClosing, producedFileMentions, selectProducedFiles, } from './turn-deliverables.ts' @@ -57,7 +57,8 @@ export function apply(ctx: ClientContext): void { diffs.reset() }) ctx.uiConversation.events.register(deliverablesDefinition) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-deliverables: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-deliverables: dictionaries') ctx.slots.inject( 'conversation.chat.turnTail', () => ctx.slots.register({ diff --git a/packages/client/ui-deliverables/src/client/locales.ts b/packages/client/ui-deliverables/src/client/locales.ts index 6a1a8813d3..d5c67949ca 100644 --- a/packages/client/ui-deliverables/src/client/locales.ts +++ b/packages/client/ui-deliverables/src/client/locales.ts @@ -133,3 +133,67 @@ export const en: Record = { /** Union of this namespace's dictionary keys. */ export type DeliverablesKey = keyof typeof zh + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'presented.nativeUnavailable': 'このファイルには利用可能なHostパスがありません。サイドバーでプレビューしてください。', + 'presented.revealError': 'ファイルマネージャーで表示できませんでした。再試行してください。', + 'presented.directoryError': '含まれるフォルダーを開けませんでした。再試行してください。', + 'presented.directoryOpening': '含まれるフォルダーを開いています…', + 'presented.directoryOpened': '含まれるフォルダーの表示をリクエストしました', + 'presented.revealed': 'ファイルマネージャーでの表示をリクエストしました', + 'presented.revealing': 'ファイルマネージャーで表示しています…', + 'presented.unavailable': 'このHostには、外部アプリでファイルやフォルダーを開くためのデスクトップがありません。ファイルはサイドバーで引き続きプレビューできます。', + 'presented.retry': '再試行', + 'presented.hostError': 'Hostのデスクトップ情報を読み取れませんでした', + 'presented.preview': 'サイドバーでプレビュー', + 'presented.previewButton': '{name}をサイドバーで開く', + 'presented.previewCard': '{name}をサイドバーでプレビュー', + 'presented.all': '全{count}ファイル', + 'presented.expandAria': '配信された全{count}ファイルを表示', + 'presented.collapse': '折りたたむ', + 'presented.collapseAria': '配信されたファイルを折りたたむ', + 'presented.opening': '開いています…', + 'presented.opened': '表示をリクエストしました', + 'presented.error': '開けませんでした。クリックして再試行してください。', + 'presented.file': 'ファイル', + 'row.title': 'ファイルを提示', + 'row.running': '配信中', + 'row.preparing': '成果物を準備中', + 'row.ok': '配信済み', + 'row.error': '配信に失敗しました', + 'row.stopped': '中断されました', + 'row.inspect': '呼び出しを検査', + 'changes.title': '{count}ファイルを編集しました', + 'changes.singleTitle': '{name}を編集しました', + 'changes.added': '+{count}', + 'changes.deleted': '-{count}', + 'changes.binary': 'バイナリ', + 'changes.openReview': 'このターンの変更をサイドバーで確認', + 'changes.all': '全{count}ファイル', + 'changes.expandAria': '変更された全{count}ファイルを表示', + 'changes.collapse': '折りたたむ', + 'changes.collapseAria': '変更されたファイルを折りたたむ', + 'changes.oversized': 'サイズが大きすぎます', + 'changes.viewDiff': '{name}の変更を表示', + 'review.title': 'レビュー · ターン{turn}', + 'review.selectFile': 'レビューするファイルを選択', + 'review.split': '分割ビューに切り替え', + 'review.unified': '統合ビューに切り替え', + 'review.splitAria': '分割ビュー', + 'review.wrap': '行の折り返しを有効化', + 'review.nowrap': '行の折り返しを無効化', + 'review.wrapAria': '行の折り返し', + 'review.openFile': 'ファイル全体をサイドバーで開く', + 'review.openFileAria': '{name}をサイドバーで開く', + 'diff.loading': '変更を読み込んでいます…', + 'diff.missing': 'このターンの変更内容は利用できなくなりました', + 'diff.error': '変更を読み取れませんでした', + 'diff.binary': 'バイナリファイルのため、変更を表示できません', + 'diff.oversized': 'ファイルが大きすぎるため、変更を表示できません', + 'diff.created': 'このターンで作成', + 'diff.deleted': 'このターンで削除', + 'diff.unchanged': '両方の行内容が同一です', + 'diff.coarse': '行比較がタイムアウトしたため、ファイル全体の置換として表示しています', + 'diff.truncated': '最初の{count}行を表示', +} satisfies Record diff --git a/packages/client/ui-directory-picker-browse/src/client/index.ts b/packages/client/ui-directory-picker-browse/src/client/index.ts index 9e7b2833ea..bc78900e0b 100644 --- a/packages/client/ui-directory-picker-browse/src/client/index.ts +++ b/packages/client/ui-directory-picker-browse/src/client/index.ts @@ -30,7 +30,7 @@ export const inject = ['slots', 'uiWorkspace', 'locale'] */ export function apply(ctx: ClientContext): void { ctx.effect(() => { - // The two dictionaries land as a unit: if the second registration hits a + // The dictionaries land as a unit: if the second registration hits a // rival owner of the namespace, the first rolls back before the throw — // a failed activation must not squat the namespace's other locale. const disposers: (() => void)[] = [] @@ -65,6 +65,21 @@ export function apply(ctx: ClientContext): void { 'browser.truncated': 'Too many folders to list; only the beginning is shown.', 'browser.showHidden': 'Show hidden files', }], + ['ja', { + 'browser.title': 'ワークスペースディレクトリを選択', + 'browser.home': 'ホーム', + 'browser.newFolder': '新しいフォルダ', + 'browser.folderName': 'フォルダ名', + 'browser.createIn': '「{name}」内に新しいフォルダ', + 'browser.untitledFolder': '名称未設定フォルダ', + 'browser.create': '作成', + 'browser.cancel': 'キャンセル', + 'browser.open': '開く', + 'browser.editPath': 'パスを編集', + 'browser.loading': '読み込み中…', + 'browser.truncated': 'フォルダが多すぎて一覧表示できません。先頭のみ表示しています。', + 'browser.showHidden': '隠しファイルを表示', + }], ] try { for (const [locale, dict] of dictionaries) disposers.push(ctx.locale.register(LOCALE_NS, locale, dict)) diff --git a/packages/client/ui-entry-customization/src/client/index.ts b/packages/client/ui-entry-customization/src/client/index.ts index 254c0977b1..35a62e269e 100644 --- a/packages/client/ui-entry-customization/src/client/index.ts +++ b/packages/client/ui-entry-customization/src/client/index.ts @@ -8,7 +8,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client' import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client' import type {} from '@deepseek-ai/dsh-client-ui-workspace/client' import { EntryMenuContent, type EntryMenuInjected, type EntryRegion } from './EntryMenuContent.tsx' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { @@ -25,7 +25,8 @@ export const inject = ['slots', 'locale', 'pluginNavigation', 'uiWorkspace'] * @param ctx - Browser context with slot, locale, plugin navigation, and Workspace services. */ export function apply(ctx: Context): void { - ctx.effect(() => ctx.locale.register('entryCustomization', { en, zh })) + const dictionaries = { en, zh, ja } + ctx.effect(() => ctx.locale.register('entryCustomization', dictionaries)) const t = ctx.locale.bind('entryCustomization') const injected = (region: EntryRegion): EntryMenuInjected => ({ region, diff --git a/packages/client/ui-entry-customization/src/client/locales.ts b/packages/client/ui-entry-customization/src/client/locales.ts index 5b7692411a..6693ed6fc2 100644 --- a/packages/client/ui-entry-customization/src/client/locales.ts +++ b/packages/client/ui-entry-customization/src/client/locales.ts @@ -29,3 +29,19 @@ export const zh: Record = { 'createPrompt.conversation': '创建一个 DSH 会话视图插件。', 'createPrompt.rightbar': '创建一个带右侧栏入口的 DSH 插件。', } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'pin.sidebar': '左サイドバーに固定', + 'unpin.sidebar': '左サイドバーから固定解除', + 'pin.conversation': '会話タブに固定', + 'unpin.conversation': '会話タブから固定解除', + 'pin.rightbar': '右サイドバーに固定', + 'unpin.rightbar': '右サイドバーから固定解除', + 'pinLabel': '{action}: {name}', + 'install': '他のプラグインを探す', + 'create': 'プラグインを作成', + 'createPrompt.sidebar': '左サイドバーにエントリを持つ DSH プラグインを作成します。', + 'createPrompt.conversation': 'DSH 会話ビュープラグインを作成します。', + 'createPrompt.rightbar': '右サイドバーにエントリを持つ DSH プラグインを作成します。', +} satisfies Record diff --git a/packages/client/ui-goal/src/client/index.ts b/packages/client/ui-goal/src/client/index.ts index ba15d921e2..44f1c7ec69 100644 --- a/packages/client/ui-goal/src/client/index.ts +++ b/packages/client/ui-goal/src/client/index.ts @@ -35,7 +35,7 @@ import { GoalCommandReference } from './GoalCommandReference.tsx' import { goalCommandInputDefinition } from './goal-command-input.ts' import { goalCommandReferenceRule } from './user-text-rules.ts' export type {} from './user-text-rules.ts' -import { en, zh, type GoalKey } from './locales.ts' +import { en, ja, zh, type GoalKey } from './locales.ts' export { GoalBar, GoalDock } from './GoalBar.tsx' export type { @@ -70,7 +70,8 @@ export const inject = ['slots', 'sessions', 'remote', 'remote.goals', 'locale', export function apply(ctx: ClientContext): void { ctx.uiConversation.events.register(goalCommandInputDefinition) ctx.uiConversation.userText.register(goalCommandReferenceRule) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-goal: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-goal: dictionaries') ctx.slots.inject('conversation.user.text.node', () => ctx.slots.register({ name: 'conversation.user.text.node', diff --git a/packages/client/ui-goal/src/client/locales.ts b/packages/client/ui-goal/src/client/locales.ts index 2296d4842c..b196c8c7a9 100644 --- a/packages/client/ui-goal/src/client/locales.ts +++ b/packages/client/ui-goal/src/client/locales.ts @@ -34,3 +34,19 @@ export const en = { 'action.edit': 'Edit goal', 'action.clear': 'Clear goal', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'phase.active': '進行中の目標', + 'phase.active.disarmed': '非アクティブな目標', + 'phase.paused': '一時停止中の目標', + 'phase.blocked': 'ブロックされた目標', + 'objective.aria': '目標の目的', + 'commandInput.aria': 'コマンド入力', + 'action.save': '目標を保存', + 'action.cancel': '編集をキャンセル', + 'action.pause': '目標を一時停止', + 'action.resume': '目標を再開', + 'action.edit': '目標を編集', + 'action.clear': '目標をクリア', +} satisfies Record diff --git a/packages/client/ui-input-trigger/src/client/index.ts b/packages/client/ui-input-trigger/src/client/index.ts index 9141210c0e..02e6a8d8dd 100644 --- a/packages/client/ui-input-trigger/src/client/index.ts +++ b/packages/client/ui-input-trigger/src/client/index.ts @@ -13,7 +13,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-session/client' import { InputTriggerService } from './service.ts' import type { MenuViewInjected } from './slots.ts' import { MenuView } from './MenuView.tsx' -import { en, zh, type MenuKey } from './locales.ts' +import { en, ja, zh, type MenuKey } from './locales.ts' export { InputTriggerService } from './service.ts' export { InputTriggerController } from './controller.ts' @@ -58,7 +58,8 @@ export const inject = ['sessions', 'locale'] */ export function apply(ctx: ClientContext): void { ctx.plugin(InputTriggerService) - ctx.effect(() => ctx.locale.register(MENU_NS, { zh, en }), 'ui-input-trigger: menu dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(MENU_NS, dictionaries), 'ui-input-trigger: menu dictionaries') ctx.inject(['slots', 'inputTriggers', 'sessions'], (scope: ClientContext) => { const inputTriggers = scope.inputTriggers const sessions = scope.sessions diff --git a/packages/client/ui-input-trigger/src/client/locales.ts b/packages/client/ui-input-trigger/src/client/locales.ts index 704edbd633..1b11342393 100644 --- a/packages/client/ui-input-trigger/src/client/locales.ts +++ b/packages/client/ui-input-trigger/src/client/locales.ts @@ -32,3 +32,16 @@ export const en = { 'crumbs.aria': 'Folder navigation', 'suggestions.aria': 'Trigger suggestions', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'command': 'コマンド', + 'skill': 'スキル', + 'subagent': 'サブエージェント', + 'loading': '読み込み中…', + 'drill.aria': 'フォルダを参照', + 'drill.hint': 'フォルダを参照', + 'drill.key': 'Tab', + 'crumbs.aria': 'フォルダナビゲーション', + 'suggestions.aria': '候補を表示', +} satisfies Record diff --git a/packages/client/ui-jobs/src/client/index.ts b/packages/client/ui-jobs/src/client/index.ts index 91f93a2442..9b46647964 100644 --- a/packages/client/ui-jobs/src/client/index.ts +++ b/packages/client/ui-jobs/src/client/index.ts @@ -12,7 +12,7 @@ import type {} from '@deepseek-ai/dsh-api-job-controller/client' import type {} from '@deepseek-ai/dsh-client-locale/client' import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' import type {} from '@deepseek-ai/dsh-client-ui-session/client' -import { en, NS, zh, type JobKey } from './locales.ts' +import { en, ja, NS, zh, type JobKey } from './locales.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { @@ -31,7 +31,8 @@ export const inject = ['jobs', 'slots', 'locale'] * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-jobs: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-jobs: dictionaries') ctx.slots.inject( 'conversation.session.header.actions', () => ctx.slots.register({ diff --git a/packages/client/ui-jobs/src/client/locales.ts b/packages/client/ui-jobs/src/client/locales.ts index 5db43e0514..c1aff15b1c 100644 --- a/packages/client/ui-jobs/src/client/locales.ts +++ b/packages/client/ui-jobs/src/client/locales.ts @@ -93,3 +93,47 @@ export const en: Record = { /** Key domain of the `job` namespace (zh is the source of truth). */ export type JobKey = keyof typeof zh + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'count.live.one': 'バックグラウンドジョブ {count} 件が実行中', + 'count.live.other': 'バックグラウンドジョブ {count} 件が実行中', + 'count.idle.one': 'バックグラウンドジョブ {count} 件', + 'count.idle.other': 'バックグラウンドジョブ {count} 件', + 'list.aria': 'バックグラウンドジョブ', + 'section.live': '実行中', + 'section.settledCount': '完了 {count} 件', + 'section.clear': 'クリア', + 'row.expandAria': '{label} のライブ出力を表示', + 'row.collapseAria': '{label} のライブ出力を非表示', + 'kill.stop': 'タスク {label} を停止', + 'kill.confirm': 'もう一度クリックして確定', + 'kill.confirmAction': '停止を確定', + 'kill.failed': '停止に失敗しました', + 'status.running': '実行中', + 'status.stopping': '停止中', + 'status.completed': '完了', + 'status.killed': 'キャンセル済み', + 'status.failed': '失敗', + 'duration.seconds': '{seconds}秒', + 'duration.minutes': '{minutes}分 {seconds}秒', + 'duration.hours': '{hours}時間 {minutes}分', + 'duration.title.live': '実行時間 {duration}', + 'duration.title.done': '所要時間 {duration}', + 'output.gap': '…以前の出力は破棄されました…', + 'output.error': 'ライブ出力ストリームが中断されました: {error}', + 'terminal.signal': 'シグナル {signal}', + 'terminal.exitCode': '終了コード {code}', + 'terminal.noExitCode': '終了コードなし', + 'terminal.commandLine': 'コマンドライン {n}', + 'terminal.running': '実行中', + 'terminal.failed': '失敗', + 'terminal.done': '完了', + 'terminal.copy': 'コピー', + 'terminal.copied': 'コピーしました', + 'terminal.noOutput': '(出力なし)', + 'terminal.collapse': '折りたたむ', + 'terminal.collapseAria': '出力を折りたたむ', + 'terminal.expand': '残り {n} 行を表示', + 'terminal.expandAria': '折りたたまれた出力 {n} 行を展開', +} satisfies Record diff --git a/packages/client/ui-layout/src/client/index.ts b/packages/client/ui-layout/src/client/index.ts index 3948c198b9..4e6adbaf41 100644 --- a/packages/client/ui-layout/src/client/index.ts +++ b/packages/client/ui-layout/src/client/index.ts @@ -18,7 +18,7 @@ import { AppFrame } from './AppFrame.tsx' import { createLayoutStore } from './stores.ts' import { LayoutController } from './service.ts' import type { ShortcutCommandId } from '@deepseek-ai/dsh-client-shortcuts/client' -import { en, zh } from './shortcut-locales.ts' +import { en, ja, zh } from './shortcut-locales.ts' import { ThemePresenter } from './theme-presenter.ts' // Contract exports only (export-convergence rule: cross-package consumers @@ -156,7 +156,8 @@ export const inject = ['slots', 'theme', 'locale', 'shortcuts'] * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register('shortcuts.layout', { zh, en }), 'layout: command labels') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register('shortcuts.layout', dictionaries), 'layout: command labels') const t = ctx.locale.bind('shortcuts.layout') ctx.effect(() => { diff --git a/packages/client/ui-layout/src/client/shortcut-locales.ts b/packages/client/ui-layout/src/client/shortcut-locales.ts index 7c90e1ae9a..dcccfc8641 100644 --- a/packages/client/ui-layout/src/client/shortcut-locales.ts +++ b/packages/client/ui-layout/src/client/shortcut-locales.ts @@ -2,3 +2,8 @@ export const zh = { toggle: '展开/收起左侧栏' } /** English labels for the same layout commands. */ export const en: Record = { toggle: 'Toggle left sidebar' } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'toggle': '左サイドバーを切り替え', +} satisfies Record diff --git a/packages/client/ui-message-feedback/src/client/index.ts b/packages/client/ui-message-feedback/src/client/index.ts index 4f26c3bb0d..a251d153fe 100644 --- a/packages/client/ui-message-feedback/src/client/index.ts +++ b/packages/client/ui-message-feedback/src/client/index.ts @@ -26,7 +26,7 @@ import { FeedbackDialog } from './FeedbackDialog.tsx' import { MessageFeedbackActions } from './MessageFeedbackActions.tsx' import type { FeedbackDialogInjected, MessageFeedbackInjected } from './slots.ts' import { FeedbackSurface } from './surface.ts' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' export type { MessageFeedbackActionFailure, MessageFeedbackActionResult, MessageFeedbackStatus, @@ -66,7 +66,8 @@ export const inject = ['slots', 'remote', 'remote.messageFeedback', 'remote.sess * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-message-feedback: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-message-feedback: dictionaries') const surfaces = new Map() const surfaceFor = (sessionId: SessionId): FeedbackSurface => { diff --git a/packages/client/ui-message-feedback/src/client/locales.ts b/packages/client/ui-message-feedback/src/client/locales.ts index 808a15a9d4..f55054c71d 100644 --- a/packages/client/ui-message-feedback/src/client/locales.ts +++ b/packages/client/ui-message-feedback/src/client/locales.ts @@ -57,3 +57,27 @@ export const en = { 'error.generic': 'Could not save feedback', 'error.noteTooLarge': 'The description is too long; shorten it and submit again', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'action.like': '良い回答', + 'action.likeActive': '評価を取り消す', + 'action.dislike': '悪い回答', + 'action.dislikeActive': '評価を取り消す', + 'dialog.title': 'フィードバックを送信', + 'dialog.categories': 'フィードバックのカテゴリ', + 'dialog.detail': 'フィードバックの詳細', + 'dialog.hint': '改善のため詳細を追加してください。現在の会話ログが送信内容に含まれます。', + 'category.task-result': 'タスク結果', + 'category.instruction-following': '指示の理解と遵守', + 'category.product-interaction': '製品機能と操作', + 'category.service-stability': '安定性と速度', + 'category.resource-cost': 'リソース使用量とコスト', + 'category.security-privacy-permission': 'セキュリティ、プライバシー、権限', + 'category.other': 'その他', + 'toast.recorded': 'フィードバックありがとうございます', + 'error.conflict': 'このフィードバックは別の場所で変更されました。最新の状態を表示しています', + 'error.load': 'フィードバックを読み込めませんでした', + 'error.generic': 'フィードバックを保存できませんでした', + 'error.noteTooLarge': '説明が長すぎます。短くして再送信してください', +} satisfies Record diff --git a/packages/client/ui-model-selection/src/client/index.ts b/packages/client/ui-model-selection/src/client/index.ts index b298d2ba95..c99d6116b4 100644 --- a/packages/client/ui-model-selection/src/client/index.ts +++ b/packages/client/ui-model-selection/src/client/index.ts @@ -27,7 +27,7 @@ import type { ModelDirectoryState } from './directory.ts' import { ModelDirectoryResolver } from './service.ts' import type { ModelSelectInjected } from './slots.ts' import { ModelSelect } from './ModelSelect.tsx' -import { en, zh, type ModelKey } from './locales.ts' +import { en, ja, zh, type ModelKey } from './locales.ts' import { orderModelProviders } from './provider-order.ts' export { ModelDirectory } from './directory.ts' @@ -113,7 +113,8 @@ export const inject = ['commandUi', 'locale', 'sessions', 'slots', 'remote', 're * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-model-selection: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-model-selection: dictionaries') // Non-slot faces (the command description, the popup option builder) read // through the bound translate; the seat component reads the standard seat. diff --git a/packages/client/ui-model-selection/src/client/locales.ts b/packages/client/ui-model-selection/src/client/locales.ts index 63602c2780..4e931226ea 100644 --- a/packages/client/ui-model-selection/src/client/locales.ts +++ b/packages/client/ui-model-selection/src/client/locales.ts @@ -64,3 +64,30 @@ export const en = { 'empty.models': 'No models available.', 'empty.efforts': 'This model provides no reasoning effort levels.', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'provider.account': 'DeepSeek アカウント', + 'command.label': 'モデル', + 'command.description': 'この会話で使用するモデルを選択', + 'option.loadError': 'カタログの読み込みに失敗しました: {message}', + 'trigger.fallback': 'モデルを選択', + 'trigger.loading': 'モデルを読み込み中…', + 'trigger.selectAria': 'モデルを選択', + 'trigger.aria': 'モデルを選択、現在 {model}', + 'trigger.ariaEffort': 'モデルを選択、現在 {model}、推論強度 {effort}', + 'menu.aria': 'モデルと推論強度', + 'menu.model': 'モデル', + 'menu.effort': '強度', + 'effort.providerDefault': 'デフォルト', + 'status.loading': 'モデル一覧を更新中…', + 'error.action': 'モデル操作に失敗しました: {message}', + 'error.sessionInUse': 'このセッションは既に使用中です。別の DSH インスタンス(dsh web やデスクトップアプリなど)が実行中の可能性があります。他の DSH インスタンスを終了してから再試行してください。', + 'action.reload': '再読み込み', + 'warning.groupLoad': '{name} の読み込みに失敗しました: {message}', + 'search.placeholder': 'モデルを検索…', + 'search.clear': '検索をクリア', + 'search.empty': '一致するモデルがありません。', + 'empty.models': '利用可能なモデルがありません。', + 'empty.efforts': 'このモデルには推論強度の設定がありません。', +} satisfies Record diff --git a/packages/client/ui-open-in-app/src/client/index.ts b/packages/client/ui-open-in-app/src/client/index.ts index 146cfed2f7..e4b6f8ec6f 100644 --- a/packages/client/ui-open-in-app/src/client/index.ts +++ b/packages/client/ui-open-in-app/src/client/index.ts @@ -25,7 +25,7 @@ import { OpenInAppPathController } from './open-path.ts' import { OpenPathAction, type OpenPathInjected } from './OpenPathAction.tsx' import { FileRouteAction } from './FileRouteAction.tsx' import { OpenPathEmptyAction } from './OpenPathEmptyAction.tsx' -import { en, NS, zh, type OpenInAppKey } from './locales.ts' +import { en, ja, NS, zh, type OpenInAppKey } from './locales.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { @@ -51,7 +51,8 @@ export function apply(ctx: ClientContext): void { const controller = new OpenInAppController() void controller.load() const paths = new OpenInAppPathController(ctx.remote) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'open-in-app: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'open-in-app: dictionaries') const t = ctx.locale.bind(NS) const target = () => { if (ctx.layout.panelInfo.getSnapshot().activePanelId !== null) return undefined diff --git a/packages/client/ui-open-in-app/src/client/locales.ts b/packages/client/ui-open-in-app/src/client/locales.ts index 2e8a9204aa..f43d4746bb 100644 --- a/packages/client/ui-open-in-app/src/client/locales.ts +++ b/packages/client/ui-open-in-app/src/client/locales.ts @@ -79,3 +79,23 @@ export const en: Record = { /** Key domain of the `open-in-app` namespace (zh is the source of truth). */ export type OpenInAppKey = keyof typeof zh + +/** Japanese copy, checked against the English keys. */ +export const ja = { + ...PRODUCT_NAMES, + 'open.title': '{app}で開く', + 'path.appDefault': '{app}(デフォルト)', + 'path.appsError': 'アプリケーションを読み込めませんでした', + 'shortcut.busy': 'ワークスペースを開いています', + 'shortcut.unavailable': '現在のワークスペースまたはローカルアプリケーションを利用できません', + 'open.tooltip': 'ローカルで開く', + 'path.open': '開く', + 'path.more': 'その他の開き方', + 'path.reveal': 'ファイルの場所を表示', + 'path.openError': '開けませんでした。もう一度お試しください。', + 'path.revealError': 'ファイルの場所を表示できませんでした。もう一度お試しください。', + 'app.finder': 'Finder', + 'app.explorer': 'エクスプローラー', + 'app.filemanager': 'ファイル', + 'app.terminal': 'ターミナル', +} satisfies Record diff --git a/packages/client/ui-permission-presets/src/client/index.ts b/packages/client/ui-permission-presets/src/client/index.ts index 4541718839..608815b08b 100644 --- a/packages/client/ui-permission-presets/src/client/index.ts +++ b/packages/client/ui-permission-presets/src/client/index.ts @@ -41,7 +41,7 @@ import type { PermissionSelectInjected } from './PermissionSelect.tsx' import { PermissionRow } from './PermissionRow.tsx' import type { PermissionRowInjected } from './PermissionRow.tsx' import { - accessEn, accessZh, en, PERMISSION_ACCESS_NS, zh, + accessEn, accessJa, accessZh, en, ja, PERMISSION_ACCESS_NS, zh, } from './locales.ts' import { AUTO_REVIEW_PRESET, displayPermissionPreset, FULL_ACCESS_PRESET, @@ -113,8 +113,9 @@ function optionsOf( export function apply(ctx: ClientContext): void { const command = ctx.get('commandUi') as CommandUiContract const sessions = ctx.sessions + const accessDictionaries = { zh: accessZh, en: accessEn, ja: accessJa } ctx.effect( - () => ctx.locale.register(PERMISSION_ACCESS_NS, { zh: accessZh, en: accessEn }), + () => ctx.locale.register(PERMISSION_ACCESS_NS, accessDictionaries), 'ui-permission: current-session dictionaries', ) const t = ctx.locale.bind(PERMISSION_ACCESS_NS) @@ -141,7 +142,8 @@ export function apply(ctx: ClientContext): void { 'ui-permission: dismiss stale slash choices', ) - ctx.effect(() => ctx.locale.register('settings.permission', { zh, en }), 'ui-permission: settings row dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register('settings.permission', dictionaries), 'ui-permission: settings row dictionaries') // The shared ConfigForm mirror updates after document commits and reconnects. const controller = new PermissionPresetSettingsController( diff --git a/packages/client/ui-permission-presets/src/client/locales.ts b/packages/client/ui-permission-presets/src/client/locales.ts index 3754b8a07a..0c3b2ed852 100644 --- a/packages/client/ui-permission-presets/src/client/locales.ts +++ b/packages/client/ui-permission-presets/src/client/locales.ts @@ -82,3 +82,40 @@ export const accessEn = { 'auto.confirm.acknowledge': 'I understand these risks and want to continue', 'auto.confirm.enable': 'Enable Auto review', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': '権限', + 'description': '新しいセッションのデフォルトの権限モードを選択', + 'loading': '読み込み中', + 'unavailable': '利用不可', + 'preset.readOnly': '読み取り専用', + 'preset.workspaceWrite': 'ワークスペース書き込み', + 'preset.fullAccess': 'フルアクセス', + 'confirm.title': 'フルアクセスを有効にしますか?', + 'confirm.description': 'フルアクセスでは、新しいセッションで確認ステップが減り、機密操作・ファイル変更・外部コマンドなど多くの操作を直接実行できるようになります。以降のタスクを信頼できる場合にのみ使用してください。', + 'confirm.acknowledge': 'リスクを理解し、続行します', + 'confirm.cancel': 'キャンセル', + 'confirm.enable': 'フルアクセスを有効にする', +} satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const accessJa = { + 'mode': 'アクセスモード、現在: {name}', + 'close': '閉じる', + 'preset.readOnly': '読み取り専用', + 'preset.workspaceWrite': 'ワークスペース書き込み', + 'preset.fullAccess': 'フルアクセス', + 'confirm.title': 'フルアクセスを有効にしますか?', + 'confirm.description': 'フルアクセスでは確認ステップが減り、エージェントが機密操作・ファイル変更・外部コマンドなど多くの操作を直接実行できるようになります。現在のタスクを信頼できる場合にのみ使用してください。', + 'confirm.acknowledge': 'リスクを理解し、続行します', + 'confirm.cancel': 'キャンセル', + 'confirm.enable': 'フルアクセスを有効にする', + 'auto.label': '自動レビュー', + 'auto.badge': 'EXP', + 'auto.description': 'すべてのネイティブツール呼び出しと PTC 内部呼び出しを実験的な同一モデルでレビューしたうえで、サンドボックスなしで実行します。', + 'auto.confirm.title': '自動レビュー(実験的)を有効にしますか?', + 'auto.confirm.description': '自動レビューはサンドボックスなしで実行されます。ネイティブツール呼び出しと PTC 内部呼び出しのたびに、現在のエージェントと同じモデルが許可可否をレビューし、拒否された呼び出しはあなたが承認または却下します。この機能は実験的であり、操作を誤って許可・拒否する可能性があり、追加のトークンを消費します。', + 'auto.confirm.acknowledge': 'これらのリスクを理解し、続行します', + 'auto.confirm.enable': '自動レビューを有効にする', +} satisfies Record diff --git a/packages/client/ui-plan/src/client/index.ts b/packages/client/ui-plan/src/client/index.ts index 6dc0b66a2f..fdf5160457 100644 --- a/packages/client/ui-plan/src/client/index.ts +++ b/packages/client/ui-plan/src/client/index.ts @@ -25,7 +25,7 @@ import { planAddress, parsePlanAddress } from './plan.ts' import { isReviewPreviewAddress, reviewPreviewAddress } from './review-preview.ts' import { createPlanReviewStore } from './review-store.ts' import { PlanChip } from './PlanModeControl.tsx' -import { en, zh, type PlanKey } from './locales.ts' +import { en, ja, zh, type PlanKey } from './locales.ts' export type { PlanKey } from './locales.ts' @@ -56,7 +56,8 @@ export const inject = ['slots', 'remote', 'remote.commands', 'remote.session', ' * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-plan: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-plan: dictionaries') const previewId = '@deepseek-ai/dsh-client-ui-plan' const t = ctx.locale.bind(NS) diff --git a/packages/client/ui-plan/src/client/locales.ts b/packages/client/ui-plan/src/client/locales.ts index 56e7ca9978..331fdb5234 100644 --- a/packages/client/ui-plan/src/client/locales.ts +++ b/packages/client/ui-plan/src/client/locales.ts @@ -44,3 +44,24 @@ export const en = { 'chip.on.title': 'Plan mode on — click to turn off (/plan off)', 'chip.exitFailed': 'Failed to exit plan mode', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'chip.label': '計画', + 'preview.title': '計画', + 'preview.document': '計画 · Markdown', + 'preview.action': '開く', + 'preview.open': 'サイドバーで計画を開く', + 'preview.full': '計画全体を表示', + 'preview.openNamed': '計画を開く: {title}', + 'preview.loading': '計画を読み込み中…', + 'preview.failed': '計画を読み込めませんでした', + 'preview.invalidAddress': '計画のアドレスが無効です', + 'preview.historyUnavailable': 'セッション履歴を利用できません', + 'preview.notFound': 'この計画は見つかりませんでした', + 'preview.unavailable': '計画のプレビューを利用できません', + 'preview.expired': 'この一時的な計画プレビューは期限切れです。保留中のレビューカードから再度開いてください。', + 'chip.on.aria': '計画モードはオンです。押すとオフになります', + 'chip.on.title': '計画モードはオンです — クリックでオフ (/plan off)', + 'chip.exitFailed': '計画モードを終了できませんでした', +} satisfies Record diff --git a/packages/client/ui-plugin-manager/src/client/index.ts b/packages/client/ui-plugin-manager/src/client/index.ts index 410ae5cfd5..cdaf15c963 100644 --- a/packages/client/ui-plugin-manager/src/client/index.ts +++ b/packages/client/ui-plugin-manager/src/client/index.ts @@ -26,7 +26,7 @@ import { PluginsPanelIcon } from './PluginsPanelIcon.tsx' import { configLedgerSource } from './config-ledger.ts' import { tabLedgerSource } from './tab-ledger.ts' import { PluginManagerController } from './manager-store.ts' -import { en, zh, type PluginManagerLocaleKey } from './locales.ts' +import { en, ja, zh, type PluginManagerLocaleKey } from './locales.ts' import { createNavigationStore } from './navigation-store.ts' import type {} from './slot-contract.ts' @@ -77,7 +77,8 @@ export const inject = ['slots', 'locale', 'remote', 'remote.pluginManager', 'rem * @param ctx - the browser plugin context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-plugin-manager: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-plugin-manager: dictionaries') const t = ctx.locale.bind(NS) const controller = new PluginManagerController(ctx) ctx.effect(() => () => { controller.dispose() }, 'ui-plugin-manager: controller') diff --git a/packages/client/ui-plugin-manager/src/client/locales.ts b/packages/client/ui-plugin-manager/src/client/locales.ts index 8f9e0f549d..e4f4c60dd3 100644 --- a/packages/client/ui-plugin-manager/src/client/locales.ts +++ b/packages/client/ui-plugin-manager/src/client/locales.ts @@ -583,3 +583,291 @@ export const en = { detailFailed: 'Could not read the details.', detailFootnotes: 'Footnotes', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'panel': 'プラグイン', + 'title': 'プラグイン', + 'intro': 'プラグインのインストール、有効化、設定', + 'infoLabel': 'プラグインについて', + 'infoDescription': '公式プラグインの設定や、その他のプラグインのインストール・管理はここで行えます。組み込みプラグインの一覧と実行状態は「設定 → 組み込みプラグイン」で確認できます。', + 'loading': 'プラグインを読み込み中…', + 'error': '一部のプラグインを読み込めませんでした。ネットワークの問題が考えられます', + 'unavailable': 'このデプロイでは管理可能なプロファイルが実行されていないため、ここでプラグインのインストールや切り替えはできません。', + 'retry': '再試行', + 'refresh': '更新', + 'refreshError': '更新に失敗しました。もう一度お試しください。', + 'empty': 'プラグインはまだインストールされていません。', + 'addPlugin': 'プラグインを追加', + 'chooseAddMethod': 'プラグインの追加方法を選択', + 'installExisting': 'サードパーティのプラグインをインストール', + 'installExistingDescription': 'npm パッケージ、Git リポジトリ、ローカルディレクトリに対応', + 'restartNotice': '変更を適用するには DSH を再起動してください。{reason}', + 'restartReasonNoHmr': 'このプロファイルでは起動時にのみ変更が適用されます。', + 'restartReasonPackageMoved': 'インストール済みパッケージの解決先が別のディレクトリになったか、変更によって DSH インストールが提供するパッケージが置き換えられました。別の場所からインストールする前に、そのパッケージをアンインストールしてください。', + 'restartReasonHostModule': '変更はDSH自身が読み込んだモジュールにも及びます。', + 'restartReasonUnmanagedPlugin': '変更はプラグイン一覧外で動作しているプラグインにも及びます。', + 'restartReasonModuleComparison': 'DSHは読み込まれたプラグインとそのファイルを比較できませんでした。', + 'overriddenNotice': '{name}は保存されましたが、優先度の高い設定が上書きしているため有効になっていません', + 'bundlesTitle': 'インストール済みプラグイン', + 'basicTitle': '組み込みプラグイン設定', + 'extensionsTitle': '実験的プラグイン', + 'moreTitle': '実験的プラグイン', + 'moreAction': 'その他', + 'tabsLabel': 'プラグインビュー', + 'installedTab': 'インストール済み', + 'statusProblem': '問題', + 'statusBeta': '実験的', + 'reasonLabel': '理由', + 'metadataError': 'パッケージメタデータのエラー: {error}', + 'versionTag': 'v{version}', + 'partsLabel': 'コンポーネント', + 'sourceTitle': 'ソース', + 'sourceSpec': 'コードソース', + 'sourceBuiltIn': '組み込み', + 'sourceVersion': '現在のバージョン', + 'partsEmpty': 'このプラグインパックにはコンポーネントが含まれていません。', + 'partsCountTotal': '計{count}件', + 'partsCountRunning': '{count}件稼働中', + 'partsCountOff': '{count}件オフ', + 'partOff': 'オフ', + 'partsCountFailed': '{count}件失敗', + 'partsFilter': 'コンポーネントを絞り込む', + 'partsFilterEmpty': '一致するコンポーネントはありません。', + 'partToggle': 'コンポーネント{name}を有効化', + 'rowPhasePending': '依存関係を待機中', + 'rowPhaseLoading': '読み込み中', + 'rowPhaseActive': '稼働中', + 'rowPhaseFailed': '問題', + 'rowPhaseUnloading': 'アンロード中', + 'enableToggle': '{name}を有効化', + 'openDetail': '{name}を表示', + 'backToList': 'プラグイン一覧に戻る', + 'crumbRoot': 'プラグイン', + 'backToPackage': '{name}に戻る', + 'configureRow': '{name}を設定', + 'rowStateIdle': '未稼働', + 'uninstall': 'アンインストール', + 'uninstallLabel': '{name}をアンインストール', + 'installTitle': 'プラグインを追加', + 'installDescription': 'プラグインのパッケージ名、GitHubリポジトリのアドレス、またはローカルディレクトリのパスを入力してください。', + 'installSpecLabel': 'パッケージ名またはアドレス', + 'installSpecPlaceholder': '例: dsh-plugin-whale-pet', + 'installGuideToggle': 'インストールガイドと例', + 'installGuideHide': 'ガイドを隠す', + 'installGuideIdTitle': 'プラグインのnpmパッケージ名を入力', + 'installGuideIdExample': 'dsh-plugin-whale-pet', + 'installGuideIdHint': 'プラグインパッケージ名とはnpmパッケージ名(dsh-xxxや@author/pluginなど)です。コミュニティプラグインのREADMEのインストールコマンドにあるdsh plugin addやpnpm addの後ろの部分です。', + 'installGuideExampleLabel': '例: ', + 'installGitTemplateHint': '実際のGitリポジトリのアドレスに置き換えてください。', + 'installPathTemplateHint': 'ローカルプラグインディレクトリの実際のパスに置き換えてください。', + 'installGuideFill': '例を使用', + 'installGuideFillAria': '例{example}を使用', + 'installGuideSafety': '信頼できるプラグインのみをインストールしてください。プラグインはあなたの権限で動作し、DeepSeek Harnessを破壊したりデータを漏洩させたりする可能性があります。', + 'installUpgradeNotice': 'オンデマンドの公式プラグインは、バージョンがDSHと異なる場合に更新を提供します。その他のプラグインを更新するには、アンインストールして再インストールしてください。', + 'registryToggle': 'レジストリ', + 'registryLegend': 'プラグインのダウンロード元のnpmレジストリ', + 'registryDefault': 'デフォルトレジストリ', + 'registryOfficial': '公式npmレジストリ', + 'registryNpmmirror': '中国本土ミラー', + 'registryCustom': 'カスタムアドレス', + 'registryCustomPlaceholder': 'https://npm.example.com/', + 'registryCustomHint': 'http://またはhttps://で始まる社内・プライベートのnpmレジストリのアドレスを入力してください。ログインが必要な場合は、このマシンの~/.npmrcに認証情報を保存してください。', + 'registryCustomInvalid': 'http://またはhttps://で始まるアドレスを入力してください', + 'registryListSeparator': ', ', + 'sentenceSeparator': ' ', + 'installRun': 'インストール', + 'installChecking': '確認中…', + 'installConfirmedTitle': 'プラグインをインストール', + 'installConfirmedRefused': 'このプラグインはインストールできません', + 'installProblemInvalid': 'パッケージ名またはインストール可能なアドレスではありません: {reason}', + 'installProblemInstalled': 'このプラグインはすでにインストールされています。アップグレードするには、アンインストールしてから再インストールしてください', + 'installProblemShipped': 'このプラグインはDSHに同梱されています。DSHをアップグレードすると更新されます', + 'installProblemNotFound': '該当するプラグインが見つかりません', + 'installProblemNotPackage': 'パスが存在しないか、有効なプラグインパッケージではありません', + 'installProblemNotBundle': 'このパッケージはバンドルを宣言していないため、プラグインとしてインストールできません: {reason}', + 'installProblemNetwork': 'プラグインレジストリに接続できませんでした。ネットワークを確認して再試行してください', + 'installProblemNetworkAll': 'どのレジストリにも接続できませんでした(試行: {registries})。ネットワークまたはプロキシ設定を確認するか、レジストリを変更してください', + 'installProblemUnknown': 'プラグインを照会できませんでした: {reason}', + 'installingTitle': 'プラグインをインストール中…', + 'installedTitle': 'インストール済み', + 'statusNotInstalled': '未インストール', + 'statusMissingFiles': 'パッケージ利用不可', + 'statusUpdateAvailable': 'アップデートあり', + 'sourceNotInstalled': '未インストール', + 'sourceTargetSpec': 'このDSH用のソース', + 'sourceTargetVersion': 'このDSH用のバージョン', + 'update': 'アップデート', + 'updateLabel': '{name}をアップデート', + 'versionsTitle': 'バージョン', + 'versionsRecheck': '再確認', + 'versionsChecking': 'アップデートを確認中', + 'versionsAvailable': '{version}へアップデート可能', + 'versionsPublished': '{date}に公開', + 'versionsLatest': '最新です', + 'versionsCheckedAt': '最終確認: {time}', + 'versionsBlocked': '新しいバージョンにはDSHのアップデートが必要です', + 'versionsBlockedDetail': '{version}には{peers}が必要です。このDSHは{runtime}です', + 'versionsNoneInstallable': '直接インストールできる新しいバージョンはありません', + 'versionsNoneInstallableDetail': '新しいバージョンは非推奨、プレリリース、またはバンドル未宣言です。展開して確認してください', + 'versionsFailedNetwork': 'レジストリに接続できませんでした', + 'versionsFailedOther': 'アップデートを確認できませんでした', + 'versionsDetails': '詳細', + 'versionsNewerCount': '新しいバージョン {count} 件', + 'versionsCollapse': '折りたたむ', + 'versionsInstalledTag': 'インストール済み', + 'versionsPrereleaseTag': 'プレリリース', + 'versionsDeprecatedTag': '非推奨', + 'versionsNeedsDsh': '{peers}が必要', + 'versionsNotBundle': 'このバージョンはバンドルを宣言していないためインストールできません', + 'versionsUpdateLabel': '{version}へアップデート', + 'versionsRegistry': 'レジストリ', + 'versionsCheckedAtLabel': '確認日時', + 'versionsFallbackTag': 'フォールバック', + 'versionsFallbackHint': '{registry}に接続できませんでした。リストはフォールバックレジストリからのものです', + 'versionsPrimaryTag': 'プライマリ', + 'versionsShowPrerelease': 'プレリリースを表示', + 'versionsOmitted': 'インストール済みより新しいバージョンが他に {count} 件ありますが、表示されていません', + 'versionsProblemNetwork': '到達不能', + 'versionsProblemNotFound': '見つかりません', + 'versionsProblemUnknown': '失敗', + 'updatesAvailable': 'アップデートあり: {count}', + 'updatesNone': 'すべてのプラグインは最新です', + 'updatesFailed': '一部のプラグインを確認できませんでした', + 'updateTitle': '{name}をアップデート', + 'updateSource': 'インストール元', + 'updateDescription': 'アップデートにより{from}が{to}に置き換わります。プラグインが一時停止する場合があります。インストールに失敗した場合、{from}は保持されるか再インストールされます。', + 'updateRunning': 'アップデート中', + 'updateStepChecking': '{version}を確認', + 'updateStepRemoving': '{version}を停止して削除', + 'updateStepInstalling': '{registry}から{version}をインストール', + 'updateStepRestoring': '{version}を再インストール', + 'updateDone': '{version}にアップデートしました', + 'updateDoneDetail': '新しいバージョンが実行中です。', + 'updateRestart': '{version}をインストールしました。再起動後に有効になります', + 'updateRefused': '{version}へアップデートできません', + 'updateRemoveFailed': '{version}を削除できませんでした', + 'updateRestored': 'アップデートに失敗しました。{version}を復元しました', + 'updateRestoredDetail': '{to}のインストールに失敗しました。{from}を再インストールしました。', + 'updateKept': '{version}へアップデートできませんでした', + 'updateKeptDetail': '{from}は削除されず、引き続き使用されます。{reason}', + 'updateBroken': 'アップデートに失敗しました。プラグインはインストールされていません', + 'updateBrokenDetail': '{to}のインストールに失敗し、{from}の再インストールにも失敗しました。プラグインを追加から{name}を再インストールしてください。', + 'updateLost': 'アップデート結果が不明です', + 'updateLostDetail': 'DSHへの接続が中断されました: {reason}。プラグインリストを更新してインストール済みバージョンを確認してください。', + 'updateOutput': 'インストール出力を表示', + 'updateFinish': '完了', + 'installFailedTitle': 'プラグインをインストールできませんでした', + 'installGithubFailedTitle': 'GitHubにアクセスできません', + 'installGithubTimeoutTitle': 'GitHubへの接続がタイムアウトしました', + 'installGithubFailedDescription': '別のインストール元を試してください。', + 'installUseGithubMirror': '中国本土ミラーを使用', + 'installTryAnotherWay': '別の方法を試す', + 'installPackageLabel': 'プラグインパッケージ名', + 'installEdit': '編集', + 'installEditAria': '編集に戻る', + 'installCancelAndEdit': 'インストールをキャンセルして編集に戻る', + 'installApplyingCancellationError': 'キャンセルが確認されませんでした。インストールを適用中です。結果をお待ちください。{reason}', + 'installReconcile': 'インストール状態を確認', + 'installUnknownTitle': 'インストール結果を取得できません', + 'installUnknownDescription': 'HostにこのリクエストIDの有効なインストールがありません。再試行する前にプラグイン一覧を確認してください。', + 'installResultUnconfirmed': 'インストール結果を受信できませんでした。インストール状態を確認してください。{reason}', + 'installAwaitingAcceptance': 'Hostがインストールを承認するのを待っています。承認後、キャンセルは自動的に再試行されます。', + 'installBackgroundUnknown': 'インストール結果を取得できません。プラグイン一覧を確認してください。', + 'installCancel': 'インストールをキャンセル', + 'installCloseCancels': 'インストールをキャンセルして閉じる', + 'installViewTask': 'インストールを表示', + 'installUnconfirmedTitle': 'インストール状態が未確認です', + 'installBackgroundDone': 'インストールが完了しました。インストール詳細を表示します。', + 'installBackgroundFailed': 'インストールに失敗しました。インストール詳細を表示します。', + 'installBackgroundUnconfirmed': 'インストール状態が未確認です。インストールを表示して詳細を確認してください。', + 'installBackgroundApplying': 'インストールを適用中のためキャンセルできません。インストールの進行状況を表示します。', + 'installStarting': 'インストールを準備中…', + 'installCancelling': 'インストールを停止中…', + 'installApplying': '設定を適用しています。お待ちください…', + 'installCancelledShort': 'キャンセル済み', + 'installCancelled': 'インストールをキャンセルしました。プラグインは有効化されておらず、ダウンロードしたファイルが残る場合があります', + 'installCancelUnconfirmed': 'インストールの停止が確認されていません。キャンセルを再試行するか、インストール結果をお待ちください。{reason}', + 'installEnableNow': '今すぐ有効化', + 'installDetailsShow': 'インストール詳細を表示', + 'installDetailsHide': 'インストール詳細を隠す', + 'installVersion': 'バージョン {version}', + 'installSubjectPath': 'ローカルディレクトリ', + 'installSubjectGit': 'Gitリポジトリ', + 'installSubjectTarball': 'Tarball', + 'installLocation': '{dir} にインストール', + 'installRetry': '再試行', + 'installChangeRegistry': 'レジストリを変更', + 'installAttempt': '{previous} ではパッケージを提供できませんでした。{registry} 経由で再試行しています(レジストリ {index}/{total})', + 'installAttemptBadge': '試行 {index} · {registry}', + 'installFailureNetwork': 'ネットワーク接続に失敗しました', + 'installFailureNetworkAll': 'レジストリに接続できませんでした(試行: {registries})。ネットワークまたはプロキシ設定を確認するか、レジストリを変更して再試行してください。', + 'installFailureNetworkHost': '{host} に接続できませんでした。GitHubアドレスや.tgzリンクはレジストリ経由では取得されません。このマシンが直接またはプロキシ経由で接続できる必要があります。プラグインがnpmにも公開されている場合は、パッケージ名を入力してください。', + 'installFailureNotFound': '該当するプラグインが見つかりません', + 'installFailureNoMatchingVersion': '要求に一致するバージョンがありません', + 'installFailureDiskFull': 'ディスクの容量が不足しているため、インストールを停止しました', + 'installFailurePermission': '書き込み権限がないため、プラグインをインストールできません', + 'installFailureBuildBlocked': 'インストールを続行するには、依存関係のインストールスクリプトに許可を与える必要があります', + 'installFailureBuildBlockedManual': 'pnpmがインストールスクリプトをブロックしました。pnpm-workspace.yamlのallowBuildsで許可して再試行してください', + 'installFailureIntegrity': 'ダウンロードしたパッケージの整合性チェックに失敗しました', + 'installFailureTimeout': 'インストールがタイムアウトしました', + 'installFailurePnpmMissing': 'pnpmが見つからないため、インストールできません', + 'installFailureGeneric': 'インストール中に問題が発生しました。詳細をご確認ください', + 'terminalCommandLine': 'コマンドライン {n}', + 'terminalRunning': '実行中', + 'terminalFailed': '失敗', + 'terminalDone': '完了', + 'terminalCopy': 'コピー', + 'terminalCopied': 'コピー済み', + 'terminalNoOutput': '出力なし', + 'terminalCollapseAria': '出力を折りたたむ', + 'terminalCollapse': '折りたたむ', + 'terminalExpandAria': '残り {n} 行の出力を展開', + 'terminalExpand': '… さらに {n} 行', + 'terminalExitCode': '終了コード {code}', + 'terminalSignal': 'シグナル {signal}', + 'terminalNoExitCode': '終了コードなし', + 'installDoneNothing': '新しい依存関係はなく、インストールが完了しました。', + 'installDoneRestart': 'インストール済みです。読み込むにはDSHを再起動してください。{reason}', + 'installDoneApproved': '{names} のインストールスクリプトを許可しました', + 'installDoneOtherVersion': 'pnpm のサプライチェーンセキュリティポリシー(minimumReleaseAge)では、必須の経過時間より新しく公開されたバージョンは自動的にインストールされないため、このインストールでは最新バージョン {version} ではなく {installed} を使用しました。新しく公開されたバージョンはこの待機期間を経過しておらず、検出されていない悪意のあるコードや欠陥を含む可能性があります。{version} が必要な場合は、現在のバージョンをアンインストールしてから {exact} をインストールしてそのバージョンを要求してください。', + 'installApprovalTitle': 'インストールスクリプトには許可が必要です', + 'installApprovalDescription': 'これらのパッケージには pnpm が実行しなかったインストールスクリプトがあります。', + 'installApprovalConsequence': '許可すると、スクリプトはここであなたの権限で実行され、許可はこのプロファイルに保存されます。', + 'installApprovalCaution': '信頼できるパッケージのみ許可してください。', + 'installApproveAndRetry': 'これらのスクリプトを許可して再試行', + 'installClose': '完了', + 'close': '閉じる', + 'cancel': 'キャンセル', + 'confirmUninstallTitle': '「{name}」をアンインストールしますか?', + 'confirmUninstallDescription': 'アンインストールすると、提供している機能は利用できなくなります。', + 'confirmUninstall': 'アンインストール', + 'failedEnable': '有効化できませんでした: {reason}', + 'failedDisable': '無効化できませんでした: {reason}', + 'failedUninstall': 'アンインストールできませんでした: {reason}', + 'failedRowEnable': 'コンポーネントを有効化できませんでした: {reason}', + 'failedRowDisable': 'コンポーネントを無効化できませんでした: {reason}', + 'reasonManagementRequired': 'プラグイン管理に必要です。無効化もアンインストールもできません。', + 'reasonUnaddressable': 'プロファイルパッチでこれを一意に指定できません。', + 'reasonUnknownPlugin': 'そのようなプラグインはありません。', + 'reasonInvalidSpec': '有効なパッケージ名またはアドレスを入力してください。', + 'reasonAlreadyInstalled': 'このパッケージはすでにインストールされています。別のバージョンをインストールする前にアンインストールしてください。', + 'reasonAmbiguousInstall': '依存関係の変更から、どのパッケージがインストールされたか判断できません。', + 'reasonNotBundle': 'このパッケージはバンドルを宣言していないため、プラグインとして管理できません。', + 'reasonNotRemovable': 'このパッケージはプロファイル所有ではないか、プラグイン管理に必要です。', + 'reasonStopProfile': 'このプロファイルは HMR なしで動作します。停止してから dsh plugin でパッケージをアンインストールしてください。', + 'reasonBundleInUse': '他の設定がこのバンドルのコンポーネントをまだ使用しています。先にそれらを無効にしてください。', + 'reasonStaleApproval': '保留中のスクリプト承認が変更されました。インストールをやり直して更新してください。', + 'reasonIncompatibleVersion': '{plugin} は DSH {runtime} と互換性がありません({requirements} が必要)。実行するとクラッシュやデータ損失を引き起こす可能性があります。', + 'reasonIncompatibleVersionUnnamed': 'このプラグインは実行中の DSH バージョンと互換性がありません。実行するとクラッシュやデータ損失を引き起こす可能性があります。', + 'reasonIncompatibleInstall': 'この DSH と互換性のあるプラグインバージョンをインストールしてください。', + 'reasonIncompatibleInstalled': 'アンインストールして、この DSH と互換性のあるバージョンをインストールしてください。', + 'reasonOperationError': 'Host がエラーを報告しました。', + 'detailTitle': '詳細', + 'detailLoading': '詳細を読み込み中', + 'detailTooLarge': '詳細がサイズ上限を超えているため読み込まれませんでした。', + 'detailNetwork': '詳細の取得元に接続できませんでした。', + 'detailInvalid': 'プラグインが無効な詳細ファイルを宣言しています。', + 'detailFailed': '詳細を読み込めませんでした。', + 'detailFootnotes': '脚注', +} satisfies Record diff --git a/packages/client/ui-plugin-market/src/client/index.ts b/packages/client/ui-plugin-market/src/client/index.ts index 9d92d5da7a..4bff318749 100644 --- a/packages/client/ui-plugin-market/src/client/index.ts +++ b/packages/client/ui-plugin-market/src/client/index.ts @@ -12,7 +12,7 @@ import type {} from '@deepseek-ai/dsh-plugin-manager/types' import type {} from '@deepseek-ai/dsh-plugin-market/types' import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client' import { DiscoverSection } from './DiscoverSection.tsx' -import { en, zh, type PluginMarketLocaleKey } from './locales.ts' +import { en, ja, zh, type PluginMarketLocaleKey } from './locales.ts' import { DiscoverController } from './market-store.ts' export type { DiscoverFace, DiscoverState } from './market-store.ts' @@ -36,7 +36,8 @@ export const inject = ['slots', 'locale', 'remote', 'remote.pluginMarket', 'remo * @param ctx - the browser plugin context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-plugin-market: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-plugin-market: dictionaries') const controller = new DiscoverController(ctx, () => document.baseURI) ctx.effect(() => () => { controller.dispose() }, 'ui-plugin-market: controller') ctx.effect(() => { diff --git a/packages/client/ui-plugin-market/src/client/locales.ts b/packages/client/ui-plugin-market/src/client/locales.ts index 924ff10b43..20aada6cae 100644 --- a/packages/client/ui-plugin-market/src/client/locales.ts +++ b/packages/client/ui-plugin-market/src/client/locales.ts @@ -184,3 +184,94 @@ export const en = { detailFailed: 'Could not read the details.', footnotes: 'Footnotes', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': '見つける', + 'refresh': '更新', + 'loading': 'インデックスを読み込み中', + 'retry': '再試行', + 'close': '閉じる', + 'cancel': 'キャンセル', + 'emptyTitle': 'プラグインマーケットはまだありません', + 'emptyDescription': 'プラグインマーケットのインデックスを追加すると、ここでプラグインを閲覧してインストールできます。', + 'addFirstSource': 'インデックスソースを追加', + 'sourcesFailed': 'インデックスソースを読み取れませんでした。', + 'manageSources': 'インデックスソースを管理…', + 'marketMenu': 'プラグインマーケットを切り替え、現在は{market}', + 'sourcesTitle': 'インデックスソース', + 'sourcesDescription': 'プラグインマーケットは1つのインデックスファイルのURLとして公開されます。「見つける」ではここに一覧されたインデックスを閲覧します。', + 'sourcesLabel': '追加済みのインデックスソース', + 'sourceUrl': 'インデックスURL', + 'sourcePlaceholder': 'https://example.com/index.json', + 'addSource': '追加', + 'removeSource': '削除', + 'removeSourceNamed': '{url}を削除', + 'sourcesTrust': 'インデックスは第三者提供です。信頼できるソースのみ追加してください。インストールされたプラグインはあなたの権限で実行されます。', + 'sourceInvalid': 'ユーザー名、パスワード、#フラグメントを含まないhttpまたはhttpsのアドレスを使用してください。', + 'sourceDuplicate': 'このインデックスソースはすでに追加されています。', + 'sourceUnavailable': 'ここではインデックスソースを保存できません。', + 'sourceFailed': 'インデックスソースを保存できませんでした。', + 'readFailed': 'このインデックスを読み取れませんでした。', + 'readInvalid': 'このインデックスは有効な形式ではありません。', + 'readChanged': '読み込み中にこのインデックスが変更されました。', + 'search': 'プラグインを検索', + 'filter': 'ラベル', + 'filterCount': 'ラベル · {count}', + 'sortBy': '並び替え: {sort}', + 'sortRelevance': '一致度順', + 'sortDefault': 'デフォルト順', + 'sortPublished': '公開日', + 'switchToAscending': '昇順で並び替え', + 'switchToDescending': '降順で並び替え', + 'categoriesLabel': 'カテゴリ', + 'allCategories': 'すべて', + 'emptyIndex': 'このインデックスにはプラグインがありません。', + 'noMatches': '一致するプラグインがありません。', + 'showMore': 'さらに表示', + 'entryMeta': '{version} · {date}', + 'entryMetaAuthor': '{version} · {author} · {date}', + 'openEntry': '{name}を表示', + 'install': 'インストール', + 'installTitle': '{name}をインストール', + 'installVersionTitle': '{name} {version}をインストール', + 'openInstalled': 'インストール済みで{name}を表示', + 'statusInstalled': 'インストール済み', + 'statusUpdate': 'アップデートあり', + 'statusProvided': 'DSH が提供', + 'incompatible': 'このバージョンは実行中の DSH {runtime} と互換性がありません。DSH {range} が必要です。', + 'noCompatibleVersion': 'DSH {runtime} と互換性のあるバージョンはありません', + 'exempted': 'このバージョンは実行中の DSH と互換性がありませんが、互換性の例外が設定されています。', + 'backToList': '見つける', + 'factSource': 'ソース', + 'factPackage': 'パッケージ', + 'factRegistry': 'レジストリ', + 'factRepository': 'リポジトリ', + 'factCommit': 'コミット', + 'factDirectory': 'ディレクトリ', + 'factPublished': '公開日', + 'factAuthor': '作者', + 'factHomepage': 'ホームページ', + 'factSourceCode': 'ソースコード', + 'factCategory': 'カテゴリ', + 'factLabels': 'ラベル', + 'factMarket': 'プラグインマーケット', + 'versionsTitle': 'バージョン', + 'versionsLabel': '{name} のバージョン', + 'versionDefault': '推奨', + 'versionInstalled': 'インストール済み', + 'versionIncompatible': '非互換', + 'versionExempted': '例外適用', + 'versionPrerelease': 'プレリリース', + 'confirmTitle': '「{name}」をインストールしますか?', + 'installTrust': 'プラグインはこのマシン上であなたの権限で実行されます。提供元が信頼できることを確認してください。', + 'detailTitle': '詳細', + 'detailLoading': '詳細を読み込み中', + 'detailTooLarge': '詳細またはそのリポジトリがサイズ上限を超えたため、読み込まれませんでした。', + 'detailNetwork': '詳細の取得元に接続できませんでした。', + 'detailDenied': '詳細のリポジトリにアクセスできませんでした。Git の認証情報を確認してください。', + 'detailGitUnavailable': '詳細は Git で読み取る必要があるリポジトリにありますが、Git を実行できませんでした。Git をインストールしてから再試行してください。', + 'detailInvalid': 'プラグインが無効な詳細ファイルを宣言しています。', + 'detailFailed': '詳細を読み取れませんでした。', + 'footnotes': '脚注', +} satisfies Record diff --git a/packages/client/ui-reference/src/client/index.ts b/packages/client/ui-reference/src/client/index.ts index 6bc52b59d7..11363a313d 100644 --- a/packages/client/ui-reference/src/client/index.ts +++ b/packages/client/ui-reference/src/client/index.ts @@ -33,7 +33,7 @@ import { formatFileMention } from '@deepseek-ai/dsh-file-reference/grammar' import type { FileReferenceCandidate } from '@deepseek-ai/dsh-file-reference/types' import type { SessionReferenceMentionCandidate } from '@deepseek-ai/dsh-session-reference/types' import { abbreviateHomePath, fileAddressFor } from '@deepseek-ai/dsh-util-workspace-path' -import { en, NS, zh, type ReferenceKey } from './locales.ts' +import { en, ja, NS, zh, type ReferenceKey } from './locales.ts' declare module '@deepseek-ai/dsh-api-session-controller/client' { interface SessionReferenceSourceMap { @@ -53,7 +53,8 @@ export const inject = [ * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-reference: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-reference: dictionaries') const t = ctx.locale.bind(NS) const sessions = ctx.get('sessions') as ISessions ctx.uiConversation.userText.register(fileReferenceRule) diff --git a/packages/client/ui-reference/src/client/locales.ts b/packages/client/ui-reference/src/client/locales.ts index 2d48030d5e..af8e37175a 100644 --- a/packages/client/ui-reference/src/client/locales.ts +++ b/packages/client/ui-reference/src/client/locales.ts @@ -58,3 +58,22 @@ export const en = { 'time.months': '{n}mo', 'time.years': '{n}y', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'section.files': 'ファイルとフォルダ', + 'section.subagents': 'サブエージェント', + 'section.sessions': 'セッション', + 'candidate.noCwd': '(cwdなし)', + 'crumb.root': 'ワークスペース', + 'fileOpen.title': 'ファイルを開けませんでした', + 'fileOpen.failed': '「{path}」を開けませんでした。再試行するか、このメッセージを閉じてください。', + 'fileOpen.close': '閉じる', + 'fileOpen.retry': '再試行', + 'time.now': '今', + 'time.minutes': '{n}分', + 'time.hours': '{n}時間', + 'time.days': '{n}日', + 'time.months': '{n}か月', + 'time.years': '{n}年', +} satisfies Record diff --git a/packages/client/ui-schedule/src/client/frequency-locales.ts b/packages/client/ui-schedule/src/client/frequency-locales.ts index b05ceb3177..35bf484f5a 100644 --- a/packages/client/ui-schedule/src/client/frequency-locales.ts +++ b/packages/client/ui-schedule/src/client/frequency-locales.ts @@ -125,3 +125,61 @@ export const frequencyEn: Record = { 'relative.future': 'in {value} {unit}', 'relative.overdue': '{value} {unit} overdue', } + +/** Japanese copy, checked against the English keys. */ +export const frequencyJa = { + 'time.locale': 'ja-JP', + 'time.utcPrefix': 'UTC', + 'frequency.daily': '毎日 {time} ({timeZone})', + 'frequency.dailyLocal': '毎日 {time}', + 'frequency.weekly': '毎週 {weekdays} の {time} ({timeZone})', + 'frequency.weeklyLocal': '毎週 {weekdays} の {time}', + 'frequency.cron': 'Cron {expression} ({timeZone})', + 'frequency.cronLocal': 'Cron {expression}', + 'frequency.cronRule': '{rule} ({timeZone})', + 'cron.list.join': '、', + 'cron.part.join': ' ', + 'cron.weekday.name': '{weekday}', + 'cron.weekday.range': '{from}〜{to}', + 'cron.months': ' {months}', + 'cron.day.every': '毎日{months}', + 'cron.day.weekdays': '{weekdays}{months}', + 'cron.day.monthDays': '毎月{days}日{months}', + 'cron.day.both': '毎月{days}日または{weekdays}{months}', + 'cron.day.bothStarred': '毎月{days}日と{weekdays}{months}', + 'cron.hours.range': '{from}〜{to}', + 'cron.hours.list': '{hours}', + 'cron.time.everyMinute': '毎分', + 'cron.time.everyMinutes': '{step}分ごと', + 'cron.time.joinedEveryMinute': '毎分', + 'cron.time.joinedEveryMinutes': '{step}分ごと', + 'cron.time.everyHour': '毎時', + 'cron.time.joinedEveryHour': '毎時', + 'cron.time.everyNHours': '{count}時間ごと', + 'cron.time.joinedEveryNHours': '{count}時間ごと', + 'cron.time.hourlyAt': '毎時{minutes}分', + 'cron.time.joinedHourlyAt': '毎時{minutes}分', + 'cron.time.hoursEveryMinute': '{hours}時の毎分', + 'cron.time.hoursEveryMinutes': '{hours}時の{step}分ごと', + 'cron.time.at': '{times}に', + 'cron.time.hoursAt': '{hours}時の{minutes}分に', + 'frequency.weekday.join': '、', + 'frequency.weekday.1': '月', + 'frequency.weekday.2': '火', + 'frequency.weekday.3': '水', + 'frequency.weekday.4': '木', + 'frequency.weekday.5': '金', + 'frequency.weekday.6': '土', + 'frequency.weekday.7': '日', + 'unit.day.one': '日', + 'unit.day.other': '日', + 'unit.hour.one': '時間', + 'unit.hour.other': '時間', + 'unit.minute.one': '分', + 'unit.minute.other': '分', + 'unit.second.one': '秒', + 'unit.second.other': '秒', + 'relative.now': 'まもなく期限', + 'relative.future': '{value}{unit}後', + 'relative.overdue': '{value} {unit}超過', +} satisfies Record diff --git a/packages/client/ui-schedule/src/client/index.ts b/packages/client/ui-schedule/src/client/index.ts index 084d0edb8d..b5952710dc 100644 --- a/packages/client/ui-schedule/src/client/index.ts +++ b/packages/client/ui-schedule/src/client/index.ts @@ -58,8 +58,8 @@ import { TaskManagerPage, type TaskManagerInjected } from './TaskManagerPage.tsx import type { TaskDetailInjected } from './TaskDetail.tsx' import { TaskManagerIcon } from './TaskManagerIcon.tsx' import { sessionLinkState } from './session-link.ts' -import { en, NS, zh, type ScheduleCatalogKey } from './locales.ts' -import { en as managerEn, zh as managerZh, type TaskManagerKey } from './task-manager-locales.ts' +import { en, ja, NS, zh, type ScheduleCatalogKey } from './locales.ts' +import { en as managerEn, ja as managerJa, zh as managerZh, type TaskManagerKey } from './task-manager-locales.ts' const MANAGER_NS = 'schedule.manager' const PANEL_ID = 'schedules' as MainPanelId @@ -86,8 +86,10 @@ export const inject = [ * @param ctx - browser services used by these contributions. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-schedule: dictionaries') - ctx.effect(() => ctx.locale.register(MANAGER_NS, { zh: managerZh, en: managerEn }), 'ui-schedule: manager dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-schedule: dictionaries') + const managerDictionaries = { zh: managerZh, en: managerEn, ja: managerJa } + ctx.effect(() => ctx.locale.register(MANAGER_NS, managerDictionaries), 'ui-schedule: manager dictionaries') const t = ctx.locale.bind(MANAGER_NS) const manager: CatalogInjected = createCatalogSource({ list: () => ctx.remote.schedule.catalog(), diff --git a/packages/client/ui-schedule/src/client/locales.ts b/packages/client/ui-schedule/src/client/locales.ts index e08fa1a48f..ff04c31ce4 100644 --- a/packages/client/ui-schedule/src/client/locales.ts +++ b/packages/client/ui-schedule/src/client/locales.ts @@ -2,7 +2,7 @@ * `schedule.catalog` namespace dictionaries: the Session header catalog and the * Sidebar row mark with its hover-card task section. */ -import { frequencyEn, frequencyZh } from './frequency-locales.ts' +import { frequencyEn, frequencyJa, frequencyZh } from './frequency-locales.ts' /** Dictionary namespace owned by this plugin. */ export const NS = 'schedule.catalog' @@ -51,3 +51,24 @@ export const en: Record = { /** Key domain of the Schedule catalog namespace. */ export type ScheduleCatalogKey = keyof typeof zh + +/** Japanese copy, checked against the English keys. */ +export const ja = { + ...frequencyJa, + 'trigger.label': 'リマインダー', + 'list.loading': 'リマインダーを読み込み中…', + 'list.error': 'リマインダーを読み込めませんでした。', + 'list.retry': '再試行', + 'delete.action': '削除', + 'delete.pending': '削除中…', + 'delete.label': 'リマインダーを削除: {title}', + 'list.open': 'リマインダーの詳細を開く: {title}', + 'trigger.one': '{count}件のリマインダー', + 'trigger.other': '{count}件のリマインダー', + 'list.aria': '有効なリマインダー', + 'list.nextRun': '次回実行', + 'frequency.once': '1回のみ', + 'frequency.every': '{value}{unit}ごと', + 'mark.aria': 'スケジュールされたタスク {count}件', + 'hover.more': '他 {count}件', +} satisfies Record diff --git a/packages/client/ui-schedule/src/client/task-manager-locales.ts b/packages/client/ui-schedule/src/client/task-manager-locales.ts index 7439d9733b..7917d8dbb5 100644 --- a/packages/client/ui-schedule/src/client/task-manager-locales.ts +++ b/packages/client/ui-schedule/src/client/task-manager-locales.ts @@ -1,5 +1,5 @@ /** Copy for the retained-reminder task manager's `schedule.manager` namespace. */ -import { frequencyEn, frequencyZh } from './frequency-locales.ts' +import { frequencyEn, frequencyJa, frequencyZh } from './frequency-locales.ts' /** English task-manager dictionary and key domain. */ export const en = { @@ -283,3 +283,142 @@ export const zh: Record = { 'card.deleted': '已删除', 'tool.invoked': '已调用 {name}', } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + ...frequencyJa, + 'panel': '自動化タスク', + 'title': '自動化タスク', + 'new.action': '新規', + 'list.label': 'タスク一覧', + 'list.nextPrefix': '次回予定日時: ', + 'list.loading': 'タスクを読み込み中…', + 'list.error': 'タスクを読み込めませんでした。', + 'list.retry': '再試行', + 'list.empty': 'タスクはまだありません。セッションで作成したタスクがここに表示されます。', + 'list.emptyInactive': '無効な自動化タスクはありません', + 'list.noMatches': '一致するタスクはありません', + 'search.label': 'タスクを検索', + 'search.placeholder': '自動化タスクを検索', + 'search.clear': '検索をクリア', + 'empty.action': '新規自動化タスク', + 'statusFilter.label': 'タスクの状態', + 'statusFilter.all': 'すべて', + 'status.active': '有効', + 'status.inactive': '無効', + 'detail.label': 'タスクの詳細', + 'detail.close': '詳細を閉じる', + 'detail.more': '自動化タスクの操作', + 'detail.nextRun': '次回実行', + 'detail.tabs': 'タスク詳細ビュー', + 'detail.rule': 'ルール', + 'detail.records': '記録', + 'delivery.empty': '配信記録はありません', + 'delivery.loading': '配信記録を読み込み中…', + 'delivery.error': '配信記録を読み込めませんでした。', + 'delivery.notFound': 'このタスクは利用できなくなりました。', + 'delivery.cursorError': '記録が更新されました。', + 'delivery.retry': '再試行', + 'delivery.refresh': '更新', + 'delivery.loadMore': 'さらに読み込む', + 'delivery.expand': '展開', + 'delivery.collapse': '折りたたむ', + 'delivery.pruned': '以前の配信記録は削除されました', + 'delivery.retention': '保持ルール', + 'delivery.retentionBounds': '各タスクは過去 {days} 日間の配信記録を最大 {records} 件保持します。', + 'delivery.retentionExplanation': '新しい記録を保存すると、これらの上限を超えた古い記録は自動的に削除されます。記録を削除してもタスクは停止しません。', + 'detail.name': '自動化タスク名', + 'detail.instruction': '自動化タスクの指示', + 'detail.status': '状態', + 'detail.next': '次回予定日時', + 'detail.frequency': '頻度', + 'detail.id': 'タスク ID', + 'detail.session': 'リンクされたセッション', + 'detail.openSession': 'リンクされたセッション: 元のセッションを開く', + 'detail.openSessionTitle': 'リンクされたセッション {title}: 元のセッションを開く', + 'detail.sessionLoading': '元のセッション情報を読み込み中。', + 'detail.sessionArchived': '元のセッションはアーカイブされています。', + 'detail.sessionUnavailable': '元のセッションは利用できません。', + 'detail.missing': 'タスクを利用できません。削除された可能性があります。', + 'delivery.label': '保存済みの配信記録', + 'frequency.once': '1回のみ', + 'frequency.every': '{value}{unit}ごと', + 'timing.date': '日付', + 'timing.time': '時刻', + 'timing.hour': '時', + 'timing.minute': '分', + 'timing.second': '秒', + 'timing.prevMonth': '前月', + 'timing.nextMonth': '翌月', + 'timing.zone': 'タイムゾーン', + 'timing.zoneSearch': 'UTCオフセット、IANA ID、都市名で検索', + 'timing.zoneNoResults': '一致するタイムゾーンがありません', + 'timing.interval': '繰り返し間隔', + 'timing.intervalIncrease': '間隔を増やす', + 'timing.intervalDecrease': '間隔を減らす', + 'timing.unit.hour': '時間', + 'timing.unit.minute': '分', + 'timing.unit.second': '秒', + 'timing.intervalHint.hour': '1時間以上を指定してください。間隔はタスクの作成時または最後のルール変更時から数え、タイムゾーンには依存しません。', + 'timing.intervalHint.minute': '1分以上を指定してください。間隔はタスクの作成時または最後のルール変更時から数え、タイムゾーンには依存しません。', + 'timing.intervalHint.second': '60秒以上を指定してください。間隔はタスクの作成時または最後のルール変更時から数え、タイムゾーンには依存しません。', + 'timing.zoneNoStored': '1回のみのタスクは実行時刻のみを保存し、タイムゾーンは保存しません。日付と時刻は選択したタイムゾーンで解釈されます。', + 'timing.inactive': '無効なタスクは読み取り専用です。タイミングは編集できません。', + 'timing.conflict': '編集中にこのタスクが変更されました。下書きは保持されています。最新のルールを編集するには、キャンセルして再読み込み後に開き直してください。', + 'timing.notFound': 'このタスクは利用できなくなりました。下書きは保持されています。キャンセルするとエディターを閉じます。', + 'timing.subagentSession': 'このセッションはサブエージェントのものであり、配信リマインダーを受け取りません。', + 'timing.invalid': '有効な日付と時刻を入力するか、タイミングの各項目を確認してください。', + 'timing.invalidZone': '有効なIANAタイムゾーンを入力してください(例: Asia/Shanghai)。', + 'timing.notFuture': '未来の日付と時刻を選択してください。', + 'timing.invalidInterval': '1分以上の間隔を入力してください。', + 'timing.invalidInterval.hour': '1時間以上の間隔を入力してください。', + 'timing.invalidInterval.minute': '1分以上の間隔を入力してください。', + 'timing.invalidInterval.second': '60秒以上の間隔を入力してください。', + 'timing.error': 'タイミングの更新を確認できませんでした。下書きは保持されています。タスクを確認してから再試行してください。', + 'rule.title': '実行時刻', + 'rule.repeat': '繰り返し', + 'rule.weekday': '曜日', + 'rule.weekdayOption': '{weekday}曜日', + 'rule.unsaved': '未保存の変更', + 'rule.save': '変更を保存', + 'rule.saving': '保存中…', + 'rule.cancel': 'キャンセル', + 'rule.invalidTitle': 'タスク名は120文字以内で入力してください。', + 'rule.invalidPrompt': 'タスクの指示を入力してください。', + 'rule.once': '1回のみ', + 'rule.everyMinutes': 'N分ごと', + 'rule.everyHours': 'N時間ごと', + 'rule.everySeconds': 'N秒ごと', + 'rule.daily': '毎日', + 'rule.weekdays': '月曜日から金曜日', + 'rule.weekly': '毎週', + 'rule.cron': 'カスタム', + 'rule.cronLabel': 'Cron式', + 'rule.cronInvalid': '5フィールドのCron式を入力してください(例: 0 9 * * 1-5)。', + 'cronForm.frequency': '頻度', + 'cronForm.monthly': '毎月', + 'cronForm.weekly': '毎週', + 'cronForm.daily': '毎日', + 'cronForm.dates': '日付指定', + 'cronForm.dateOption': '{day}日', + 'cronForm.atMinute': '分', + 'cronForm.minuteIncrease': '分を増やす', + 'cronForm.minuteDecrease': '分を減らす', + 'rule.zone.system': '(システム)', + 'rule.error.conflict': '更新の保存前にこのタスクが変更されました。行には保存済みのルールが表示されています。もう一度お試しください。', + 'rule.error.notFound': 'このタスクは利用できなくなりました。', + 'rule.error.unknown': 'ルールの更新を確認できませんでした。行には保存済みのルールが表示されています。', + 'delete.action': 'タスクを削除', + 'delete.title': 'このタスクを削除しますか?', + 'delete.description': 'タスクの実行が停止し、保存済みの配信記録とともに削除されます。元のセッションとそのメッセージは残り、キューに入ったメッセージは取り消されません。', + 'delete.confirm': '削除を確定', + 'delete.cancel': 'キャンセル', + 'delete.close': '削除確認を閉じる', + 'delete.pending': '削除中…', + 'toast.deleted': 'タスクを削除しました。', + 'toast.deleteFailed': 'タスクを削除できませんでした。', + 'card.open': '開く', + 'card.openLabel': 'タスクの詳細を開く: {title}', + 'card.deleted': '削除済み', + 'tool.invoked': '{name} を実行しました', +} satisfies Record diff --git a/packages/client/ui-secret-mask/src/client/index.ts b/packages/client/ui-secret-mask/src/client/index.ts index 0250d4a569..50e24dd95e 100644 --- a/packages/client/ui-secret-mask/src/client/index.ts +++ b/packages/client/ui-secret-mask/src/client/index.ts @@ -15,7 +15,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-conversation/client' import { SecretText } from './SecretText.tsx' import { createSecretRule } from './secret-rule.ts' export type {} from './secret-rule.ts' -import { en, NS, zh } from './locales.ts' +import { en, ja, NS, zh } from './locales.ts' /** Required services: the copy, the node renderer registry, and the UserText rule registry. */ export const inject = ['locale', 'slots', 'uiConversation'] @@ -25,7 +25,8 @@ export const inject = ['locale', 'slots', 'uiConversation'] * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-secret-mask: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-secret-mask: dictionaries') ctx.uiConversation.userText.register(createSecretRule()) ctx.slots.inject('conversation.user.text.node', () => ctx.slots.register({ name: 'conversation.user.text.node', key: 'secret', priority: 100, locale: NS, diff --git a/packages/client/ui-secret-mask/src/client/locales.ts b/packages/client/ui-secret-mask/src/client/locales.ts index 5a42d5a8e2..7bf3c0915b 100644 --- a/packages/client/ui-secret-mask/src/client/locales.ts +++ b/packages/client/ui-secret-mask/src/client/locales.ts @@ -26,3 +26,9 @@ export const en = { 'secret.reveal': 'Show secret', 'secret.hide': 'Hide secret', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'secret.reveal': 'シークレットを表示', + 'secret.hide': 'シークレットを隠す', +} satisfies Record diff --git a/packages/client/ui-settings-account/src/client/index.ts b/packages/client/ui-settings-account/src/client/index.ts index 7668d46116..b85c3386d9 100644 --- a/packages/client/ui-settings-account/src/client/index.ts +++ b/packages/client/ui-settings-account/src/client/index.ts @@ -23,7 +23,7 @@ import { createPlatformPages, type PlatformPages } from './platform-pages.ts' import { AccountSection, type AccountSnapshot, type AccountSectionInjected } from './AccountSection.tsx' import { createBonusNoticeController } from './bonus-notices.ts' import { accountClientMetadata } from './client-metadata.ts' -import { en, zh, type AccountKey } from './locales.ts' +import { en, ja, zh, type AccountKey } from './locales.ts' import { AccountQuotaNotice, type AccountQuotaNoticeInjected } from './AccountQuotaNotice.tsx' import { DESKTOP_ONBOARDING_NAMESPACE, type OnboardingSettings } from '../onboarding-settings.ts' import { DesktopOnboardingController } from './onboarding-state.ts' @@ -45,7 +45,8 @@ export const inject = ['slots', 'locale', 'remote', 'remote.account', 'remote.se /** Register account UI in Desktop and plain Web clients. @param ctx - client plugin context. */ export function apply(ctx: Context): void { const desktop = 'dshDesktop' in globalThis - ctx.effect(() => ctx.locale.register('settings.account', { en, zh }), 'account: dictionaries') + const dictionaries = { en, zh, ja } + ctx.effect(() => ctx.locale.register('settings.account', dictionaries), 'account: dictionaries') const t = ctx.locale.bind('settings.account') const page = globalThis as Partial> const config = ContactConfig(page[CONTACT_CONFIG_GLOBAL] ?? {}) diff --git a/packages/client/ui-settings-account/src/client/locales.ts b/packages/client/ui-settings-account/src/client/locales.ts index b38e869eb9..c116d94fb4 100644 --- a/packages/client/ui-settings-account/src/client/locales.ts +++ b/packages/client/ui-settings-account/src/client/locales.ts @@ -1,5 +1,5 @@ /** Account settings copy, owned by the account feature. */ -import { onboardingCopy, onboardingEnglishCopy } from './locales/onboarding.ts' +import { onboardingCopy, onboardingEnglishCopy, onboardingJapaneseCopy } from './locales/onboarding.ts' /** English account dictionary. */ export const en = { @@ -71,3 +71,63 @@ export const zh: Record = { quotaTopUp: '去充值', bonusNoticeTitle: '赠金已到账', } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + ...onboardingJapaneseCopy, + 'modelSignInRequired': 'モデルを利用できません。ログインしてから再試行してください。', + 'sessionExpired': 'アカウントからログアウトしました。もう一度ログインしてください。', + 'close': '閉じる', + 'addApiKey': 'API Key を追加', + 'retry': '再ログイン', + 'loginTitle': 'はじめに', + 'loginDescription': 'DeepSeek アカウントにログインするか、API Key を追加して開始します。プロジェクトとファイルはローカルに保存されます。', + 'browserTitle': 'ログインを待機中', + 'browserPrompt': 'ページが自動で開きませんでしたか? ', + 'copyLink': 'ログインリンクをコピー', + 'copiedLink': 'リンクをコピーしました', + 'copyFailed': 'コピーに失敗しました', + 'browserDescription': 'をコピーし、ブラウザで開いてログインを完了してください。', + 'timeoutTitle': 'ログインがタイムアウトしました', + 'timeoutDescription': '続行するには再ログインしてください。', + 'failureTitle': 'ログインできませんでした', + 'platformFailed': '操作を完了できませんでした。再試行してください。', + 'platformRetry': '再試行', + 'loading': '読み込み中…', + 'backToHarness': 'DeepSeek Harness に戻る', + 'settings': '設定', + 'contactUs': 'フィードバック', + 'menu': 'アカウントメニュー', + 'nav': 'アカウント', + 'signedIn': 'DeepSeek にログイン済み', + 'signedOut': '未ログイン', + 'signIn': 'ログイン', + 'signOut': 'ログアウト', + 'signOutUnknownDescription': '実行中のタスクを確認できませんでした。ログアウトすると、このアカウントを使用しているタスクが中断される可能性があります。今すぐログアウトしますか?', + 'signOutDescription': 'ログアウトしてもデータは削除されません。このアカウントに再度ログインできます。', + 'signOutRunningDescription': '現在実行中のタスクがあります。ログアウトすると中断されます。今すぐログアウトしますか?', + 'cancel': 'キャンセル', + 'open': 'ブラウザで開く', + 'initializing': 'ログインを開始しています…', + 'waiting': 'ブラウザで続行してください', + 'completing': 'ログインを完了しています…', + 'expired': 'ログインの有効期限が切れました。もう一度お試しください。', + 'failed': '操作を完了できませんでした。もう一度お試しください。', + 'noResponse': '問題が発生しました。ネットワーク接続を確認して、もう一度お試しください。', + 'settingsSignedOutTitle': 'DeepSeek Harness にログインしていません', + 'settingsSignedOutDescription': 'DeepSeek Harness にログインして専用の API Key を取得してください', + 'signInDescription': 'DeepSeek アカウントで始めましょう。', + 'profileUnavailable': 'アカウント情報はまだ利用できません。', + 'balance': 'チャージ残高', + 'bonusBalance': '付与残高', + 'balanceUnavailable': 'Platform で表示', + 'balanceSignedOut': 'ログインして表示', + 'accountInfo': 'アカウント情報の詳細', + 'more': 'その他', + 'usage': '利用状況を表示', + 'topUp': 'チャージ', + 'quotaTitle': '残高がありません', + 'quotaDescription': '残高がない場合、DeepSeek Harness はこのアカウントで新しいタスクを開始できません。チャージしますか?後から設定 → アカウントでチャージすることもできます。', + 'quotaTopUp': 'チャージ', + 'bonusNoticeTitle': 'ボーナスが付与されました', +} satisfies Record diff --git a/packages/client/ui-settings-account/src/client/locales/onboarding.ts b/packages/client/ui-settings-account/src/client/locales/onboarding.ts index 509986446e..b5c69d263c 100644 --- a/packages/client/ui-settings-account/src/client/locales/onboarding.ts +++ b/packages/client/ui-settings-account/src/client/locales/onboarding.ts @@ -83,3 +83,46 @@ export const onboardingEnglishCopy: Record onboardingRetry: 'Retry', onboardingLoading: 'Loading settings…', } + +/** Japanese copy, checked against the English keys. */ +export const onboardingJapaneseCopy = { + 'onboardingArtworkLocale': 'en', + 'onboardingWelcome': 'ようこそ', + 'onboardingBrand': 'DeepSeek Harness', + 'onboardingIntroduction': 'DeepSeek Harness はローカルフォルダで動作し、ツールを使ってお使いのコンピューター上のファイルを読み書きします。情報の調査や整理、ドキュメントやスプレッドシートの作成、コードの作成、問題のトラブルシューティングなどに役立ちます。', + 'onboardingStart': '始める', + 'onboardingCredit': 'クレジットを追加', + 'onboardingCreditDescription': 'DeepSeek Harness は、モデルとツールが使用したトークンに基づいてクレジットを消費します。タスクを中断なく実行し続けるために、事前にクレジットを追加してください。残高はエージェントがタスクを実行している間のみ使用されます。', + 'onboardingTopUp': 'クレジットを追加', + 'onboardingLater': '次へ', + 'onboardingFundedTopUp': 'クレジットを追加', + 'onboardingPurposePrefix': '', + 'onboardingPurposeSuffix': 'にどのような作業を任せたいですか?', + 'onboardingPurposeDescription': 'お仕事のスタイルに合わせてインターフェースとツールを調整します。', + 'onboardingOffice': 'オフィス・クリエイティブ作業', + 'onboardingOfficeDescription': 'ドキュメントの編集、データの整理、プレゼンテーションの作成など', + 'onboardingDevelopment': 'コーディング・開発', + 'onboardingDevelopmentDescription': 'コードの編集、デバッグ、コマンドの実行、プロジェクトファイルの管理など', + 'onboardingContinue': '続ける', + 'onboardingProcess': 'どの程度の詳細を表示しますか?', + 'onboardingProcessDescription': 'これは進捗の表示方法を変えるだけで、DeepSeek Harness の機能には影響しません。', + 'onboardingCompact': '結果のみ', + 'onboardingCompactDescription': 'シンプルでクリーンなインターフェースで結果だけを表示します', + 'onboardingStandard': '重要な詳細', + 'onboardingStandardDescription': '結果を中心に、重要なステップとアクションのみを表示します', + 'onboardingDetailed': 'すべての詳細', + 'onboardingDetailedDescription': 'デバッグやトラブルシューティングをしやすいよう、すべてのプロセスを表示します', + 'onboardingEnter': 'アプリを開く', + 'onboardingBack': '戻る', + 'onboardingSkip': 'スキップ', + 'onboardingSkipTitle': 'セットアップをスキップしますか?', + 'onboardingSkipDescription': '進捗、パフォーマンス、利用状況の表示方法や Coding Tools の有効化は、設定 → 一般でいつでも変更できます。', + 'onboardingKeepSetting': 'セットアップを続ける', + 'onboardingNoCreditTitle': 'クレジットの追加をスキップしますか?', + 'onboardingNoCreditDescription': 'クレジットがないと DeepSeek Harness は新しいタスクを開始できません。後から設定 → アカウントで追加できます。', + 'onboardingUnderstood': '了解', + 'onboardingGoTopUp': 'クレジットを追加', + 'onboardingSaveFailed': '設定を保存できませんでした。もう一度お試しください。', + 'onboardingRetry': '再試行', + 'onboardingLoading': '設定を読み込んでいます…', +} satisfies Record diff --git a/packages/client/ui-settings-agent-loop/src/client/index.ts b/packages/client/ui-settings-agent-loop/src/client/index.ts index bfea143e2a..8f2cf8f0bb 100644 --- a/packages/client/ui-settings-agent-loop/src/client/index.ts +++ b/packages/client/ui-settings-agent-loop/src/client/index.ts @@ -16,7 +16,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' import type { Context as ClientContext } from '@deepseek-ai/cordis' import { AgentLoopCard } from './AgentLoopCard.tsx' import { AGENT_LOOP_NS, AgentLoopCardController } from './agent-loop-card-controller.ts' -import { en, zh, type AgentLoopSettingsLocaleKey } from './locales.ts' +import { en, ja, zh, type AgentLoopSettingsLocaleKey } from './locales.ts' export type { AgentLoopCardProps } from './AgentLoopCard.tsx' export type { AgentLoopCardFace, AgentLoopCardState, AgentLoopSettings } from './agent-loop-card-controller.ts' @@ -41,7 +41,8 @@ export const inject = ['slots', 'locale', 'configForms'] */ export function apply(ctx: ClientContext): void { const t = ctx.locale.bind(NS) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-agent-loop: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-agent-loop: dictionaries') const card = new AgentLoopCardController(ctx.configForms.get(AGENT_LOOP_NS)) ctx.effect(() => () => { card.dispose() }, 'ui-settings-agent-loop: form subscription') ctx.effect(() => ctx.configForms.whileServed([AGENT_LOOP_NS], () => ctx.slots.inject('plugins.item', () => ctx.slots.register({ diff --git a/packages/client/ui-settings-agent-loop/src/client/locales.ts b/packages/client/ui-settings-agent-loop/src/client/locales.ts index 3572b7134b..e00a191c35 100644 --- a/packages/client/ui-settings-agent-loop/src/client/locales.ts +++ b/packages/client/ui-settings-agent-loop/src/client/locales.ts @@ -48,3 +48,19 @@ export const zh: Record = { export function formLabels(t: (key: AgentLoopSettingsLocaleKey) => string): SettingsFormLabels { return { unavailable: t('unavailable'), readOnly: t('readOnly'), saveFailed: t('saveFailed'), save: t('save'), saving: t('saving') } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': 'エージェントループ', + 'description': 'エージェントのツール呼び出しのディスパッチ方法を制御します。', + 'maxParallel': '並列ツール呼び出し', + 'maxParallelHint': '1ステップ内で同時に実行される並列安全呼び出しの上限。', + 'overridden': '上書き済み', + 'reset': 'デフォルトにリセット', + 'readOnly': 'このデプロイメントでは設定は読み取り専用で保存されます。', + 'unavailable': 'このプラグインは読み込まれていないため、現在設定できません。', + 'save': '保存', + 'saving': '保存中…', + 'saveFailed': 'デプロイがこれらの値を受け付けませんでした。修正のために残されています。', + 'invalidNumber': '数値を入力するか、空欄のままにするとデフォルトが使用されます。', +} satisfies Record diff --git a/packages/client/ui-settings-general/src/client/index.ts b/packages/client/ui-settings-general/src/client/index.ts index 561174f90b..faf9acecbe 100644 --- a/packages/client/ui-settings-general/src/client/index.ts +++ b/packages/client/ui-settings-general/src/client/index.ts @@ -41,7 +41,7 @@ import { SettingsDocumentAction } from './SettingsDocumentAction.tsx' import type { SettingsDocumentActionInjected } from './SettingsDocumentAction.tsx' import { SettingsDocumentStore } from './settings-document-store.ts' import { HostReleasePrompt, type HostReleasePromptInjected } from './HostReleasePrompt.tsx' -import { en, zh, type SettingsKey } from './locales.ts' +import { en, ja, zh, type SettingsKey } from './locales.ts' export type { CloseLabelProps, HeaderContentProps, TriggerContentProps, @@ -88,7 +88,8 @@ export function apply(ctx: ClientContext): void { ctx.slots.inject('settings.general.item', () => ctx.slots.register({ name: 'settings.general.item', id: 'current-version', order: 100, locale: NS, }, CurrentVersionRow)) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-general: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-general: dictionaries') const connection = ctx.get('connection') as ConnectionHandle const carrier = (globalThis as typeof globalThis & { dshDesktop?: { protocolVersion: number; updates?: DesktopUpdateBridge } }).dshDesktop const desktopUpdate = new DesktopUpdateSource(carrier?.protocolVersion === 1 ? carrier.updates : undefined) diff --git a/packages/client/ui-settings-general/src/client/locales.ts b/packages/client/ui-settings-general/src/client/locales.ts index 7d189d496c..8fddbcf24a 100644 --- a/packages/client/ui-settings-general/src/client/locales.ts +++ b/packages/client/ui-settings-general/src/client/locales.ts @@ -94,3 +94,49 @@ export const en = { 'hostRelease.reload': 'Reload to load the new version', 'hostRelease.later': 'Not now', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'trigger': '設定', + 'shortcut.open': '設定を開く', + 'desktop.update.available': '更新', + 'desktop.update.checking': '更新を確認中…', + 'desktop.update.progress': '{percent}%', + 'desktop.update.verifying': '更新ファイルを検証中…', + 'desktop.update.installing': '再起動の準備中…', + 'desktop.update.ready': 'インストールして再起動', + 'desktop.update.retry': '更新を再試行', + 'desktop.update.versionDetail': '{label}: {version}', + 'desktop.update.downloadDetail': '更新をダウンロード中: {percent}%\n対象バージョン: {version}', + 'desktop.update.checkFailed': '更新を確認できませんでした。しばらくしてから再試行してください。', + 'desktop.update.downloadFailed': '更新をダウンロードできませんでした。再試行してください。', + 'desktop.update.installFailed': '更新をインストールできませんでした。しばらくしてから再試行してください。', + 'desktop.update.checkNetworkFailed': '更新を確認できませんでした。接続を確認して再試行してください。', + 'desktop.update.downloadNetworkFailed': '更新をダウンロードできませんでした。接続を確認して再試行してください。', + 'desktop.update.installNetworkFailed': '更新をインストールできませんでした。接続を確認して再試行してください。', + 'desktop.update.fullscreenTimeout': '全画面表示を終了できませんでした。全画面表示を終了してから更新を再試行してください。', + 'desktop.update.stopFailed': 'タスクを安全に停止できませんでした。更新はインストールされていません。しばらくしてから再試行してください。', + 'desktop.update.tasksChanged': '新しいタスクが開始されました。タスクを停止して更新するには、もう一度確認してください。', + 'desktop.update.tasksUnavailable': 'タスクの状態を取得できません。ワークスペースの準備ができてから更新を再試行してください。', + 'title': '設定', + 'close': '閉じる', + 'openDocument': '設定ファイルを開く', + 'openDocument.error': '設定ファイルを開けませんでした', + 'general.nav': '一般', + 'general.currentVersion': '現在のバージョン: {version}', + 'developerTools.title': 'コーディングビューを表示', + 'developerTools.error': '保存できませんでした。再試行してください。', + 'developerTools.description': '軌跡、コード差分、すべてのエージェントプリセットを表示します', + 'connection.error': '切断済み', + 'connection.connecting': '再接続中', + 'connection.connected': '接続済み', + 'connection.reconnect': '切断されました。今すぐ再接続', + 'connection.restart': '再接続中、今すぐ再接続', + 'hostRelease.title': 'サービスのバージョンが変更されました', + 'hostRelease.description': 'サービスのバージョンが変更されました。このページのライブ更新は停止しています。再読み込みして新しいバージョンを読み込んでください。', + 'hostRelease.service': 'サービス', + 'hostRelease.page': 'ページ', + 'hostRelease.unknown': '不明', + 'hostRelease.reload': '再読み込みして新しいバージョンを読み込む', + 'hostRelease.later': '後で', +} satisfies Record diff --git a/packages/client/ui-settings-models/src/client/index.ts b/packages/client/ui-settings-models/src/client/index.ts index bf3c7e13e0..a881a55cc3 100644 --- a/packages/client/ui-settings-models/src/client/index.ts +++ b/packages/client/ui-settings-models/src/client/index.ts @@ -26,7 +26,7 @@ import { WelcomeNoticeStore } from './welcome-store.ts' import { ModelsSettingsStore } from './store.ts' import { createModelsOperations } from './operations.ts' import { createSettingsSchemaOperations } from './schema-operations.ts' -import { en, zh, type ModelsKey } from './locales.ts' +import { en, ja, zh, type ModelsKey } from './locales.ts' import { WELCOME_NOTICE_SETTINGS_NAMESPACE } from '../onboarding-copy.ts' import { Config, ONBOARDING_CONFIG_GLOBAL } from '../onboarding-config.ts' @@ -80,7 +80,8 @@ export function apply(ctx: ClientContext): void { const payload = page[ONBOARDING_CONFIG_GLOBAL] const configured = Config(payload === undefined ? {} : payload) const credentialOnboarding = configured.credentialOnboarding && !('dshDesktop' in globalThis) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-models: copy dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-models: copy dictionaries') const schema = createSettingsSchemaOperations(ctx.settingsSchema) // Bound once here, where the Remote namespaces are declared in this plugin's diff --git a/packages/client/ui-settings-models/src/client/locales.ts b/packages/client/ui-settings-models/src/client/locales.ts index 5640a8f248..e5874eb1d0 100644 --- a/packages/client/ui-settings-models/src/client/locales.ts +++ b/packages/client/ui-settings-models/src/client/locales.ts @@ -280,3 +280,142 @@ export const zh: { [Key in keyof typeof en]: string } = { onboardingSaving: '保存中…', keyRequired: '请输入 API 密钥后继续。', } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'nav': 'モデル', + 'deepSeekAccount': 'DeepSeekアカウント', + 'title': 'モデル', + 'intro': '以下のプロバイダーのモデルを使用するには、APIキーを入力してください。', + 'edit': '編集', + 'editProvider': '{provider}を編集', + 'remove': '削除', + 'removeProvider': '{provider}を削除', + 'deleteTitle': '{provider}を削除しますか?', + 'deleteDescription': '{provider}を削除すると、その設定が削除されます。使用している認証情報は別の場所で管理されているため、保持されます。', + 'deleteDescriptionWithCredential': '{provider}を削除すると、その設定と保存済みのAPIキーが削除されます。', + 'deleteConfirm': '{provider}を削除', + 'deleting': '{provider}を削除中…', + 'add': 'モデルプロバイダーを追加', + 'addMode': '追加方法', + 'addCatalog': 'サードパーティのモデルプロバイダー', + 'addCustom': 'カスタムモデルAPI', + 'addCatalogHint': '組み込みカタログからOpenAI、Anthropic、Kimiなどのプロバイダーを選択し、APIキーを入力してください。', + 'addCustomHint': 'ベースURL、プロトコル、モデルを指定して、リレー、セルフホストサーバー、その他のOpenAI/Anthropic互換エンドポイントに接続します。', + 'addCatalogExhausted': 'カタログ内のすべてのプロバイダーが設定済みです。', + 'addCustomUnavailable': '宣言できるAPIプロトコルがありません。', + 'provider': 'プロバイダー', + 'close': '閉じる', + 'cancel': 'キャンセル', + 'apply': '適用', + 'applying': '適用中…', + 'savedProvider': '{provider}を保存しました。', + 'credentialConfigured': 'APIキー設定済み', + 'credentialMissing': 'APIキー未設定', + 'accountSignedIn': 'ログイン済み', + 'readOnly': 'このデプロイでは設定ドキュメントは読み取り専用です。', + 'loadFailed': 'プロバイダーディレクトリの読み込みに失敗しました', + 'conflict': 'このカードを開いている間に、他のユーザーが設定を変更しました。閉じて開き直し、最新の値で編集してください。', + 'retry': '再試行', + 'keyInput': 'APIキー', + 'keyReveal': 'シークレットを表示', + 'keyHide': 'シークレットを非表示', + 'keyPlaceholder': 'APIキーを入力', + 'keyPlaceholderFallback': '空欄のままにすると環境変数を認証に使用します ({hint})', + 'keyPlaceholderSaved': '空欄のままにすると保存済みの認証情報を使用します', + 'keyFallbackMissing': '環境変数が検出されません。APIキーを入力するか、環境変数に{hint}を設定してください。', + 'keyFallbackDetected': '環境変数を検出しました。({hint})', + 'keyFallbackDetectedHidden': '環境変数を検出しました。', + 'keyFallbackUnchecked': '環境変数の認証情報の状態は確認されていません。', + 'keyHelp': '保存済みの認証情報が優先されます。以下に示す環境変数のフォールバック(この順): {env}。参照元は起動環境、$cwd/.env、$DSH_HOME/.envの順です。', + 'authHelp': '認証の詳細', + 'customized': 'カスタマイズされた設定', + 'baseUrl': 'ベースURL', + 'baseUrlDefault': 'デフォルトエンドポイントなし', + 'baseUrlEnvironment': 'プロバイダーのエンドポイント設定: {env}', + 'baseUrlConfigured': 'プロバイダー設定のエンドポイント', + 'baseUrlPlaceholder': '{url}', + 'deepSeekBaseUrl': 'https://api.deepseek.com/anthropic (デフォルトはDEEPSEEK_BASE_URLを使用)', + 'deepSeekEndpointHint': 'Anthropic Messagesと互換性のあるAPIエンドポイントを使用してください。', + 'models': 'モデル', + 'modelsInherited': 'アダプターのデフォルトを使用', + 'modelsCustomized': 'カスタマイズされたモデルカタログ', + 'resetModels': 'デフォルトに戻す', + 'model': 'モデル', + 'modelId': 'モデルID', + 'modelName': '表示名', + 'modelNamePlaceholder': '空欄の場合はモデルIDを使用', + 'contextWindow': 'コンテキストウィンドウ', + 'contextWindowPlaceholder': 'プロバイダーのデフォルトを使用', + 'maxTokens': '最大出力トークン数', + 'maxTokensPlaceholder': 'プロバイダーのデフォルトを使用', + 'modelAdvanced': 'モデルオプション', + 'modelInputTypes': '入力タイプ', + 'modelInputText': 'テキスト', + 'modelInputImage': '画像', + 'modelReasoning': '推論レベル', + 'modelReasoningLevel': 'レベル', + 'modelReasoningValue': 'API値', + 'modelReasoningHelp': '推論レベルについて', + 'modelReasoningHelpText': 'API値はリクエストの推論強度フィールド(OpenAIのreasoning_effortなど)で送信されます。デフォルトはレベル名です。モデルプロバイダーのAPIドキュメントで別の値が指定されている場合はそれに従ってください。オフは推論を無効にするため値は不要です。設定に既に記述されている値は保持され、送信されます。', + 'modelReasoningNeedsLevel': 'オフ以外のレベルを少なくとも1つ追加するか、推論レベルを無効にしてください。', + 'modelReasoningValueRequired': 'オフ以外の各レベルにはAPI値が必要で、空の値は使用できません。', + 'modelReasoningAdd': 'レベルを追加', + 'modelReasoningRemove': 'レベルを削除', + 'modelReasoningOffValuePlaceholder': '推論を無効にします', + 'addModel': 'モデルを追加', + 'removeModel': 'モデルを削除', + 'modelsEmpty': 'セレクターにモデルが表示されません。一覧にないIDも直接送信できます。', + 'keyBlank': 'APIキーを入力してください。空欄のままにすると保存済みのキーが保持されます。', + 'keyBlankNew': 'APIキーを入力してください。このプロバイダーが別の方法で認証する場合は空欄のままにしてください。', + 'keyIllegalCharacters': 'このAPIキーは有効な形式ではありません。確認してください。', + 'modelIdRequired': 'モデルIDは必須です。', + 'modelIdDuplicate': 'モデルIDは一意である必要があります。', + 'modelNameInvalid': '表示名は空にできません。', + 'modelContextInvalid': 'コンテキストウィンドウは正の数値(131072、256K、1Mなど)で指定してください。', + 'modelMaxTokensInvalid': '最大出力トークン数は正の数値(8192、64K、1Mなど)で指定してください。', + 'advancedHint': 'その他のフィールドはcordis.patch.ymlにあります。そのセクションを直接編集してください。', + 'modelCapacityInvalid': '容量は数値で指定し、末尾にKまたはMを付けられます。', + 'modelDuplicate': '各モデルIDは1回のみ指定できます。', + 'fetchModels': '利用可能なモデルを取得', + 'fetching': 'プロバイダーに問い合わせ中…', + 'fetchNeedsBaseUrl': '先にベースURLを入力してから取得してください。', + 'fetchEmpty': 'プロバイダーはモデルを一覧表示しませんでした。手動で追加してください。', + 'fetchTitle': 'モデルを選択', + 'fetchDescription': 'このプロバイダーで利用可能なモデルです。チェックしたモデルが一覧になります。', + 'fetchSearch': 'モデルを検索', + 'fetchNoMatches': '一致するモデルがありません。', + 'fetchSelectAll': 'すべて選択', + 'fetchDeselectAll': 'すべて解除', + 'fetchAdopt': '一覧を更新', + 'fetchKeepOne': '少なくとも1つのモデルをチェックしたままにしてください。', + 'customTag': 'カスタム', + 'customRoute': 'プロバイダーID', + 'customRouteHint': 'リクエスト時および認証情報名としてこのプロバイダーを一意に識別する、小文字で始まる小文字の識別子です。', + 'customRouteInvalid': '小文字で始め、その後は小文字・数字・ハイフンを使用してください。', + 'customRouteTaken': 'このIDは既に別のプロバイダーで使用されています。', + 'customDisplayName': '表示名', + 'customApi': 'APIプロトコル', + 'customApiUnset': '未選択', + 'protocolOpenAiCompletions': 'OpenAI Chat Completions', + 'protocolOpenAiResponses': 'OpenAI Responses', + 'protocolAnthropicMessages': 'Anthropic Messages', + 'customNeedsBaseUrl': 'カスタムプロバイダーにはベースURLが必要です。', + 'customBaseUrlInvalid': '有効なHTTPまたはHTTPSのURLを入力してください。', + 'customNeedsModels': 'カスタムプロバイダーには少なくとも1つのモデルが必要です。', + 'customBaseUrlPlaceholder': 'https://gateway.example/v1', + 'customAnthropicBaseUrlPlaceholder': 'https://gateway.example', + 'settingsPathUnresolvable': '解決できない設定パス', + 'create': 'プロバイダーを作成', + 'creating': '作成中…', + 'welcomeTitle': 'プレビューに関するお知らせ', + 'welcomeBody': 'DeepSeek Harness 0.2はまだプレビュー版であり、多くの領域で継続的な改善と洗練が必要です。開発者とユーザーの皆さまからのフィードバックとご提案を歓迎します。新しいデスクトップアプリは幅広いユーザーを対象としていますが、開発者向けの高度な機能は設定で有効にできます。DeepSeek Harnessの製品機能とプラグインAPIは今後も急速な反復と進化を続け、徐々に安定していく見込みです。\n\nオープンソースで再利用可能かつ組み合わせ可能なインフラストラクチャを基盤に、世界中のユーザーと開発者と共に知能の限界を探求していくことを楽しみにしています。皆さまがDeepSeek Harnessでアイデアを形にし、コミュニティに参加してプラグインエコシステムを豊かにしてくださることを歓迎します。', + 'welcomeContinue': '続ける', + 'welcomeError': '確認を保存できませんでした。もう一度お試しください。', + 'onboardingTitle': 'APIキーを追加して始める', + 'onboardingDescription': '公式のDeepSeekプロバイダーを設定して開発を始めましょう。', + 'onboardingLater': '後で設定', + 'onboardingSave': '保存して続ける', + 'onboardingSaving': '保存中…', + 'keyRequired': '続けるにはAPIキーを入力してください。', +} satisfies Record diff --git a/packages/client/ui-settings-plugin-inventory/src/client/index.ts b/packages/client/ui-settings-plugin-inventory/src/client/index.ts index 5c8b0fd7a0..f70c9c9e93 100644 --- a/packages/client/ui-settings-plugin-inventory/src/client/index.ts +++ b/packages/client/ui-settings-plugin-inventory/src/client/index.ts @@ -11,7 +11,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-agent-preset/client' // Inline-safe shared fold: shipped ids map to dictionary keys in one home. import { presetDisplayText } from '@deepseek-ai/dsh-agent-preset-registry/display' import { PluginInventorySettingsTab, type PluginInventorySettingsTabInjected } from './PluginInventorySettingsTab.tsx' -import { en, zh, type PluginInventoryLocaleKey } from './locales.ts' +import { en, ja, zh, type PluginInventoryLocaleKey } from './locales.ts' export type { PluginInventorySettingsTabInjected, PluginInventorySettingsTabProps } from './PluginInventorySettingsTab.tsx' export type { PluginInventoryLocaleKey } from './locales.ts' @@ -31,7 +31,8 @@ export const inject = ['slots', 'locale', 'remote', 'remote.pluginInventory', 'm /** Contribute the lazy inventory tab to the Plugins settings section. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-plugin-inventory: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-plugin-inventory: dictionaries') const t = ctx.locale.bind(NS) const list: PluginInventorySettingsTabInjected['list'] = async () => { diff --git a/packages/client/ui-settings-plugin-inventory/src/client/locales.ts b/packages/client/ui-settings-plugin-inventory/src/client/locales.ts index d535c07a9a..391896bcc0 100644 --- a/packages/client/ui-settings-plugin-inventory/src/client/locales.ts +++ b/packages/client/ui-settings-plugin-inventory/src/client/locales.ts @@ -84,3 +84,44 @@ export const en = { failed: 'Failed to start', unloading: 'Unloading', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'tab': 'プラグイン一覧', + 'loading': 'プラグインを読み込み中…', + 'clientSyncing': 'このページでプラグインを同期中…', + 'clientSyncFailed': '一部のプラグインをこのページで同期できませんでした。ホスト側の有効化状態は変更されていません。', + 'clientSyncRetry': 'このページを再試行', + 'error': 'プラグインは一時的に利用できません。', + 'metadataError': 'パッケージメタデータのエラー: {error}', + 'retry': '再試行', + 'search': 'プラグインを検索', + 'empty': '利用可能なプラグインはありません。', + 'emptySearch': '一致するプラグインはありません。', + 'presetTitle': 'セッションプラグイン', + 'presetSubtitle': 'エージェントプリセットによりセッションごとに構成されます', + 'switcherLabel': '確認するエージェントプリセットを選択', + 'presetOptionDefault': '{name} (デフォルト)', + 'presetOptionBroken': '{name} (読み込み失敗)', + 'globalTitle': 'グローバルプラグイン', + 'globalSubtitle': 'システムとすべてのセッションで共有', + 'presetProvidedDetail': 'グローバルでは無効。エージェントプリセットがセッションごとに提供します', + 'enabledIn': '有効化先', + 'matchesInOtherPresets': '他のプリセットにさらに{count}件一致: ', + 'enabledTag': '有効', + 'disabledTag': '無効', + 'conditionalTag': '条件付き', + 'presetEnabledTag': 'プリセット経由', + 'failedTag': '失敗', + 'moduleLabel': 'モジュール', + 'fromPreset': '提供元', + 'condition': '無効になる条件', + 'configuration': '構成', + 'runtime': 'ステータス', + 'unobserved': '停止中', + 'pending': '依存関係の待機中', + 'loadingPhase': '読み込み中', + 'active': '実行中', + 'failed': '起動に失敗', + 'unloading': 'アンロード中', +} satisfies Record diff --git a/packages/client/ui-settings-plugins/src/client/index.ts b/packages/client/ui-settings-plugins/src/client/index.ts index 1a4f2feff0..38773409d8 100644 --- a/packages/client/ui-settings-plugins/src/client/index.ts +++ b/packages/client/ui-settings-plugins/src/client/index.ts @@ -18,7 +18,7 @@ import type { Context as ClientContext } from '@deepseek-ai/cordis' import { resolveSlotLabel } from '@deepseek-ai/dsh-client-ui-slots' import { PluginsSettingsSection } from './PluginsSettingsSection.tsx' import type { PluginsSettingsSectionInjected, PluginsSettingsTabEntry } from './PluginsSettingsSection.tsx' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' export type { PluginsSettingsSectionInjected, PluginsSettingsSectionProps } from './PluginsSettingsSection.tsx' @@ -34,7 +34,8 @@ export const inject = ['slots', 'locale'] */ export function apply(ctx: ClientContext): void { const t = ctx.locale.bind(NS) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-plugins: section dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-plugins: section dictionaries') let tabsVersion = -1 let tabsRevision = -1 diff --git a/packages/client/ui-settings-plugins/src/client/locales.ts b/packages/client/ui-settings-plugins/src/client/locales.ts index 0e82c56625..9d32663ee5 100644 --- a/packages/client/ui-settings-plugins/src/client/locales.ts +++ b/packages/client/ui-settings-plugins/src/client/locales.ts @@ -20,3 +20,12 @@ export const zh: Record = { tabs: '插件视图', empty: '本部署没有开放任何插件视图。', } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'nav': '組み込みプラグイン', + 'title': '組み込みプラグイン', + 'intro': 'このデプロイメントに含まれるプラグインを確認します。', + 'tabs': 'プラグインビュー', + 'empty': 'このデプロイメントにはプラグインビューがありません。', +} satisfies Record diff --git a/packages/client/ui-settings-session-log/src/client/index.ts b/packages/client/ui-settings-session-log/src/client/index.ts index 05863f3fba..12269e3d04 100644 --- a/packages/client/ui-settings-session-log/src/client/index.ts +++ b/packages/client/ui-settings-session-log/src/client/index.ts @@ -6,7 +6,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-layout/client' import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' import { UploadPreference, type UploadSettings } from './upload-preference.ts' import { UploadRow, UploadToast, type UploadInjected } from './UploadRow.tsx' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { @@ -25,7 +25,8 @@ export const inject = ['slots', 'locale', 'configForms'] export function apply(ctx: Context): void { const locale = 'settings.sessionLog' const namespace = 'session-log-deepseek' - ctx.effect(() => ctx.locale.register(locale, { en, zh })) + const dictionaries = { en, zh, ja } + ctx.effect(() => ctx.locale.register(locale, dictionaries)) const form = ctx.configForms.get(namespace) const preference = new UploadPreference(form) const face = (): UploadInjected => ({ diff --git a/packages/client/ui-settings-session-log/src/client/locales.ts b/packages/client/ui-settings-session-log/src/client/locales.ts index accdd2058a..99b1d2f815 100644 --- a/packages/client/ui-settings-session-log/src/client/locales.ts +++ b/packages/client/ui-settings-session-log/src/client/locales.ts @@ -13,3 +13,11 @@ export const zh: Record = { saved: '设置已保存', failed: '无法保存设置', } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': '公式モデル API 使用時にセッションログをアップロード', + 'description': 'DeepSeek のモデルと製品の改善にご協力ください。', + 'saved': '設定を保存しました', + 'failed': '設定を保存できませんでした', +} satisfies Record diff --git a/packages/client/ui-settings-shell/src/client/index.ts b/packages/client/ui-settings-shell/src/client/index.ts index 83e1bbdc33..dc150c7d23 100644 --- a/packages/client/ui-settings-shell/src/client/index.ts +++ b/packages/client/ui-settings-shell/src/client/index.ts @@ -17,7 +17,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' import type { Context as ClientContext } from '@deepseek-ai/cordis' import { ShellCard } from './ShellCard.tsx' import { BASH_NS, PWSH_NS, ShellCardController } from './shell-card-controller.ts' -import { en, zh, type ShellSettingsLocaleKey } from './locales.ts' +import { en, ja, zh, type ShellSettingsLocaleKey } from './locales.ts' export type { ShellCardProps } from './ShellCard.tsx' export type { ShellCardFace, ShellCardState, ShellSettings } from './shell-card-controller.ts' @@ -42,7 +42,8 @@ export const inject = ['slots', 'locale', 'configForms'] */ export function apply(ctx: ClientContext): void { const t = ctx.locale.bind(NS) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-shell: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-shell: dictionaries') // The base bundle composes exactly one shell executor per platform; the page binds the served one. const bash = new ShellCardController(ctx.configForms.get(BASH_NS)) const pwsh = new ShellCardController(ctx.configForms.get(PWSH_NS)) diff --git a/packages/client/ui-settings-shell/src/client/locales.ts b/packages/client/ui-settings-shell/src/client/locales.ts index 040dddb21d..74980e77fc 100644 --- a/packages/client/ui-settings-shell/src/client/locales.ts +++ b/packages/client/ui-settings-shell/src/client/locales.ts @@ -53,3 +53,21 @@ export const zh: Record = { export function formLabels(t: (key: ShellSettingsLocaleKey) => string): SettingsFormLabels { return { unavailable: t('unavailable'), readOnly: t('readOnly'), saveFailed: t('saveFailed'), save: t('save'), saving: t('saving') } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': 'シェル', + 'description': '各コマンドの実行時間と出力量を制限します。', + 'timeoutMs': 'コマンドのタイムアウト (ms)', + 'timeoutMsHint': '1つのコマンドが強制終了されるまでに実行できる時間。', + 'maxOutputBytes': 'ストリームごとの出力上限 (バイト)', + 'maxOutputBytesHint': 'この上限を超えた出力は失われず、一時ファイルに書き出されます。', + 'overridden': '上書き済み', + 'reset': 'デフォルトに戻す', + 'readOnly': 'このデプロイでは設定が読み取り専用で保存されます。', + 'unavailable': 'このプラグインは読み込まれていないため、現在設定できません。', + 'save': '保存', + 'saving': '保存中…', + 'saveFailed': 'デプロイがこれらの値を受け付けませんでした。修正のため残されています。', + 'invalidNumber': '数値を入力するか、空欄のままにするとデフォルトが使用されます。', +} satisfies Record diff --git a/packages/client/ui-settings-subagent/src/client/index.ts b/packages/client/ui-settings-subagent/src/client/index.ts index c9cff6b4b6..51d8fb29a5 100644 --- a/packages/client/ui-settings-subagent/src/client/index.ts +++ b/packages/client/ui-settings-subagent/src/client/index.ts @@ -23,7 +23,7 @@ import { SubagentLimitsCardController } from './subagent-limits-card-controller. import { SUBAGENT_MODEL_SELECTION_NS, SubagentModelSelectionCardController, } from './subagent-model-selection-card-controller.ts' -import { en, zh, type SubagentSettingsLocaleKey } from './locales.ts' +import { en, ja, zh, type SubagentSettingsLocaleKey } from './locales.ts' export type { SubagentCardProps } from './SubagentCard.tsx' export type { SubagentCardFace } from './subagent-card-controller.ts' @@ -58,7 +58,8 @@ export const inject = ['slots', 'locale', 'remote', 'remote.session', 'configFor */ export function apply(ctx: ClientContext): void { const t = ctx.locale.bind(NS) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-subagent: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-subagent: dictionaries') const limits = new SubagentLimitsCardController(ctx.configForms.get(SUBAGENT_NS)) ctx.effect(() => () => { limits.dispose() }, 'ui-settings-subagent: limits form subscription') const models = new SubagentModelSelectionCardController( diff --git a/packages/client/ui-settings-subagent/src/client/locales.ts b/packages/client/ui-settings-subagent/src/client/locales.ts index 069350d721..d92185f3dd 100644 --- a/packages/client/ui-settings-subagent/src/client/locales.ts +++ b/packages/client/ui-settings-subagent/src/client/locales.ts @@ -107,3 +107,42 @@ export const zh: Record = { export function formLabels(t: (key: SubagentSettingsLocaleKey) => string): SettingsFormLabels { return { unavailable: t('unavailable'), readOnly: t('readOnly'), saveFailed: t('saveFailed'), save: t('save'), saving: t('saving') } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'overridden': '上書き済み', + 'reset': 'デフォルトに戻す', + 'readOnly': 'このデプロイメントでは設定は読み取り専用で保存されます。', + 'unavailable': 'このプラグインは読み込まれていないため、現在は設定できません。', + 'save': '保存', + 'saving': '保存中…', + 'saveFailed': 'デプロイメントがこれらの値を受け付けませんでした。修正のため残されています。', + 'subagentTitle': 'サブエージェント', + 'subagentDescription': 'サブエージェントの再帰深度、数、モデルを設定します。', + 'subagentLimitsTitle': '制限', + 'subagentMaxDepth': '最大再帰深度', + 'subagentDepthHelpLabel': '最大再帰深度について', + 'subagentDepthHelp': 'エージェントが作成できるサブエージェントの階層数を制限します。', + 'subagentDepthZero': 'サブエージェントを無効化', + 'subagentDepthOne': 'メインエージェントのみサブエージェントを作成可能', + 'subagentDepthOverride': 'ツールが独自の最大再帰深度を定義している場合、その設定が優先されます。', + 'subagentMaxActive': 'サブエージェントの並列数制限', + 'subagentCapacityHelpLabel': 'サブエージェントの並列数制限について', + 'subagentCapacityHelp': '同じメインエージェント配下の全再帰階層にわたる、稼働中のサブエージェントの合計数です。メインエージェントは含まれません。上限に達すると新しい開始リクエストは拒否されます。', + 'subagentDepthInvalid': '0以上の整数を入力してください。', + 'subagentCapacityInvalid': '1以上の整数を入力してください。', + 'subagentModelSelectionTitle': 'モデル選択', + 'subagentModelSelectionToggle': 'エージェントによるサブエージェントのモデル選択を許可', + 'subagentModelSelectionChoose': '有効にすると、エージェントは以下の許可されたモデルからサブエージェントごとにプロバイダー、モデル、推論強度を選択できます。新しいセッションにのみ適用されます。', + 'subagentModelSelectionAllowed': 'エージェントが選択可能なモデル', + 'subagentModelSelectionLoading': 'モデルを読み込み中…', + 'subagentModelSelectionLoadFailed': 'モデルを読み込めませんでした。', + 'subagentModelSelectionRetry': '再試行', + 'subagentModelSelectionPartial': '一部のモデルプロバイダーを読み込めませんでした。保存済みの選択肢は引き続き削除できます。', + 'subagentModelSelectionUnavailable': '現在利用不可', + 'subagentModelSelectionUnavailableGroup': '保存済みだが現在利用不可', + 'subagentModelSelectionEmpty': '現在、モデルを公開しているモデルプロバイダーはありません。', + 'subagentModelSelectionRequired': '保存する前に少なくとも1つのモデルを選択してください。', + 'subagentModelSelectionConflict': '設定が別の場所で変更されました。下書きを破棄して再試行してください。', + 'subagentModelSelectionOff': 'サブエージェントは設定済みのデフォルトを使用するか、親エージェントのモデルを継承します。保存済みのモデル選択は保持されます。', +} satisfies Record diff --git a/packages/client/ui-settings-web-search/src/client/index.ts b/packages/client/ui-settings-web-search/src/client/index.ts index 75bd8a6311..dc81de3fe3 100644 --- a/packages/client/ui-settings-web-search/src/client/index.ts +++ b/packages/client/ui-settings-web-search/src/client/index.ts @@ -22,7 +22,7 @@ import type {} from '@deepseek-ai/dsh-api-remotes/client' import type { Context as ClientContext } from '@deepseek-ai/cordis' import { WebSearchCard } from './WebSearchCard.tsx' import { WEB_API_NS, WEB_NS, WEB_SEARCH_NS, WebSearchCardController } from './web-search-card-controller.ts' -import { en, zh, type WebSearchSettingsLocaleKey } from './locales.ts' +import { en, ja, zh, type WebSearchSettingsLocaleKey } from './locales.ts' export type { WebSearchCardProps } from './WebSearchCard.tsx' export type { @@ -49,7 +49,8 @@ export const inject = ['slots', 'locale', 'remote', 'remote.credentials', 'confi */ export function apply(ctx: ClientContext): void { const t = ctx.locale.bind(NS) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-settings-web-search: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-web-search: dictionaries') const card = new WebSearchCardController({ web: ctx.configForms.get(WEB_NS), messages: ctx.configForms.get(WEB_SEARCH_NS), api: ctx.configForms.get(WEB_API_NS), }, ctx) diff --git a/packages/client/ui-settings-web-search/src/client/locales.ts b/packages/client/ui-settings-web-search/src/client/locales.ts index 2ff7c123b2..65d21ae8f0 100644 --- a/packages/client/ui-settings-web-search/src/client/locales.ts +++ b/packages/client/ui-settings-web-search/src/client/locales.ts @@ -80,3 +80,34 @@ export const zh: Record = { export function formLabels(t: (key: WebSearchSettingsLocaleKey) => string): SettingsFormLabels { return { unavailable: t('unavailable'), readOnly: t('readOnly'), saveFailed: t('saveFailed'), save: t('save'), saving: t('saving') } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': 'Web 検索', + 'description': 'DeepSeek 検索プロバイダーを設定します。', + 'useSearchApi': '新しい Web Search API を使用する', + 'useSearchApiHint': 'デフォルトではオフです。検索は DeepSeek Messages 経由で実行されます。新しい Web Search API の機能と料金については、', + 'searchGuide': '検索と取得のガイド', + 'searchGuideUrl': 'https://api-docs.deepseek.com/guides/search', + 'apiKey': 'API キー', + 'apiKeyHint': '設定ファイルの外に保存されます。現在のキーを維持するには空欄のままにしてください。', + 'apiKeySet': 'キーが設定されています。', + 'apiKeyUnset': 'キーが設定されていません。DeepSeek Account のモデルを使用する会話のみ、デフォルトのエンドポイント経由で検索できます。', + 'baseUrl': 'Messages API ベース URL', + 'baseUrlHint': 'https://api.deepseek.com/anthropic/v1 など、API プレフィックスを含めてください。プロバイダーは /messages を追加します。デフォルトに戻すには空欄にしてください。', + 'searchApiBaseUrl': 'Search / Fetch API ベース URL', + 'searchApiBaseUrlHint': 'https://api.deepseek.com/v1/tools など、API プレフィックスを含めてください。プロバイダーは /search または /fetch を追加します。デフォルトに戻すには空欄にしてください。', + 'maxUses': 'リクエストあたりの最大検索回数', + 'maxUsesHint': '1 回の Messages リクエスト内で補助モデルが実行できる検索回数です。Messages API にのみ適用されます。', + 'count': '検索あたりの結果数', + 'countHint': 'クエリごとに返される結果数: 1〜50 の整数、デフォルトは 10 です。これは結果数であり、検索呼び出し数ではありません。web_search は自身の結果上限を超えるソースを保持しません。', + 'overridden': '上書き済み', + 'reset': 'デフォルトに戻す', + 'readOnly': 'このデプロイでは設定は読み取り専用で保存されます。', + 'unavailable': 'このプラグインは読み込まれていないため、現在設定できません。', + 'save': '保存', + 'saving': '保存中…', + 'saveFailed': 'デプロイがこれらの値を受け入れませんでした。修正用に残されています。', + 'invalidNumber': '数値を入力するか、デフォルトを使用するには空欄にしてください。', + 'invalidCount': '1〜50 の整数を入力するか、デフォルトを使用するには空欄にしてください。', +} satisfies Record diff --git a/packages/client/ui-shortcuts/src/client/index.ts b/packages/client/ui-shortcuts/src/client/index.ts index 9aa3547ae2..3ebe888235 100644 --- a/packages/client/ui-shortcuts/src/client/index.ts +++ b/packages/client/ui-shortcuts/src/client/index.ts @@ -8,7 +8,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-layout/client' import type {} from '@deepseek-ai/dsh-client-ui-settings/client' import { createShortcutsStore } from './store.ts' import { ShortcutReference, ShortcutsRow } from './Reference.tsx' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' import { fixedCommands } from './fixed.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { @@ -26,7 +26,8 @@ export const inject = ['shortcuts', 'locale', 'slots'] * @param ctx - plugin-owned client context. */ export function apply(ctx: Context): void { - ctx.effect(() => ctx.locale.register('shortcuts', { zh, en }), 'shortcuts: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register('shortcuts', dictionaries), 'shortcuts: dictionaries') const t = ctx.locale.bind('shortcuts') const handle = createShortcutsStore() const instance = handle.create() diff --git a/packages/client/ui-shortcuts/src/client/locales.ts b/packages/client/ui-shortcuts/src/client/locales.ts index 13cd61d4ba..e96e4457da 100644 --- a/packages/client/ui-shortcuts/src/client/locales.ts +++ b/packages/client/ui-shortcuts/src/client/locales.ts @@ -68,3 +68,63 @@ export const en: Record = { empty: 'No matching shortcuts', move: 'Move menu selection', select: 'Select menu item', dismiss: 'Close menu or top dialog', } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'edit-label': '{command}のショートカットを編集', + 'record': 'ショートカットを押してください', + 'record-help': 'キーを離すと保存されます。Tab でアクション間を移動、Esc でキャンセルします。', + 'web-help': 'ブラウザの組み合わせ: Mod+/、Mod+Shift+,、Mod+Shift+.。Mod は Mac では Command、それ以外では Ctrl です。', + 'unsupported-key': 'このキーはサポートされていません。', + 'reserved': 'この組み合わせはシステム操作またはテキスト編集操作に予約されています。', + 'modifier-required': '組み合わせに Command、Ctrl、または Alt を含めてください。', + 'too-many-keys': '修飾キー以外のキーは最大 2 つまでです。キーを離して再試行してください。', + 'macos-web-help': 'Command+/、Command+,、Command+バックスラッシュ、Control+バッククォート、Command+Option+キー、または Command+Shift+キーを使用してください。3 つまたは 4 つの異なる修飾キーを使った組み合わせもサポートされています。ブラウザやシステムのショートカットはページに届かない場合があります。', + 'windows-web-help': 'Ctrl+/、Ctrl+,、Ctrl+Alt+キー、または Ctrl+Shift+キーを使用してください。3 つまたは 4 つの異なる修飾キーを使った組み合わせもサポートされています。ブラウザやシステムのショートカットはページに届かない場合があります。', + 'unsupported-browser': 'このブラウザはこの組み合わせにまだ対応していません。', + 'conflict': '「{commands}」で既に使用されています', + 'saved': '変更済み', + 'clear': '削除', + 'retry-save': '保存を再試行', + 'reset': 'デフォルトに戻す', + 'reset-all': 'すべてデフォルトに戻す', + 'modified-count': '{count} 件がカスタマイズ済み', + 'reset-title': 'すべてのショートカットをデフォルトに戻しますか?', + 'reset-description': 'このプラットフォームのショートカットをデフォルトに戻します。変更または削除されたすべてのショートカットが復元されます。他のプラットフォームには影響しません。', + 'cancel': 'キャンセル', + 'reset-saved': 'デフォルトのショートカットを復元しました', + 'close-confirmation': '確認を閉じる', + 'reset-failed': 'デフォルトを復元できませんでした。ショートカットは変更されていません。再試行してください。', + 'review': '最新の構成を確認しました', + 'stale': 'ショートカット構成または利用可能なコマンドが変更されました。保存する前に最新のバインドを確認してください。', + 'write-failed': '保存できませんでした。以前のショートカットと現在の下書きは保持されています。再試行してください。', + 'not-ready': 'ショートカットの準備ができていません。もう一度お試しください。', + 'read': '{location} を読み取れませんでした。アクセス権限を確認してから、{reload}してください。', + 'invalid': '{location} のショートカット構成が破損しています。この構成をバックアップして修復してから、{reload}してください。', + 'future': '{location} のショートカット構成は新しいバージョンで作成されています。Harness をアップグレードして再試行してください。', + 'web-document': 'このサイトの localStorage エントリ dsh.keybindings.v1', + 'desktop-document': 'userData/keybindings.json', + 'web-reload': 'ページを再読み込み', + 'desktop-reload': 'Harness を再起動', + 'using-defaults': 'デフォルトのバインドが有効です。', + 'using-accepted': '最後に正常に読み取られたバインドが有効なままです。', + 'native-failed': 'デスクトップでのキー記録を保護できませんでした。記録を終了して再試行してください。', + 'global-hint': 'どこからでも開く', + 'clear-search': '検索をクリア', + 'title': 'キーボードショートカット', + 'open': 'キーボードショートカットを開く', + 'settings': 'キーボードショートカット', + 'view': 'ショートカットを編集', + 'description': '利用可能なショートカットと入力アクションを表示・編集', + 'search': 'ショートカットを検索', + 'close': 'キーボードショートカットを閉じる', + 'application': 'アプリケーション', + 'input': 'メッセージ入力', + 'menus': 'メニューとダイアログ', + 'approval': '承認エリア', + 'unbound': 'ショートカットなし', + 'empty': '一致するショートカットがありません', + 'move': 'メニュー選択を移動', + 'select': 'メニュー項目を選択', + 'dismiss': 'メニューまたは最上位ダイアログを閉じる', +} satisfies Record diff --git a/packages/client/ui-sidebar-browser/src/client/index.ts b/packages/client/ui-sidebar-browser/src/client/index.ts index d44226eb13..92d93ba1b1 100644 --- a/packages/client/ui-sidebar-browser/src/client/index.ts +++ b/packages/client/ui-sidebar-browser/src/client/index.ts @@ -16,7 +16,7 @@ import type { DesktopBrowserBridge } from '../types.ts' import { browserWorkspace } from './electron/workspace.ts' import type { BrowserPageFactory } from './browser/BrowserPage.ts' import { BROWSER_ID, browserDefinition } from './definition.tsx' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' import { createBrowserStore } from './browser/store.ts' export type { BrowserBodyProps } from './view/BrowserBody.tsx' @@ -70,7 +70,8 @@ export function apply(ctx: Context): void { dshDesktop?: { readonly protocolVersion: number; readonly browser?: DesktopBrowserBridge } }).dshDesktop const desktop = carrier?.protocolVersion === 1 ? carrier.browser : undefined - ctx.effect(() => ctx.locale.register(namespace, { zh, en }), 'ui-sidebar-browser.copy') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(namespace, dictionaries), 'ui-sidebar-browser.copy') ctx.effect(() => ctx.sidebarRightTabs.register({ ...browserDefinition(t), keepMounted: desktop !== undefined }), 'ui-sidebar-browser.type') const installFrames = (scope: Context, factory: (sessionId: BrowserBodyProps['sessionId']) => BrowserPageFactory): void => { const controllers = new Map() diff --git a/packages/client/ui-sidebar-browser/src/client/locales.ts b/packages/client/ui-sidebar-browser/src/client/locales.ts index acf00a50e2..a5222248ae 100644 --- a/packages/client/ui-sidebar-browser/src/client/locales.ts +++ b/packages/client/ui-sidebar-browser/src/client/locales.ts @@ -67,3 +67,33 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { sidebarBrowser: SidebarBrowserKey } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'type.label': 'ブラウザ', + 'guide.title': 'ブラウザ', + 'guide.description': 'Webページを閲覧', + 'shortcut.noSession': '先にセッションを開いてください', + 'address.placeholder': 'HTTP(S)アドレスを入力', + 'address.changed': 'URLが変更されました', + 'back': '戻る', + 'forward': '進む', + 'reload': '再読み込み', + 'go': '移動', + 'external': 'システムブラウザで開く', + 'sandbox.disable': 'サンドボックス制限を無効化', + 'sandbox.enable': 'サンドボックス制限を復元', + 'sandbox.warning': 'サンドボックス制限が無効です。ページはトップレベルのアプリを遷移し、ダウンロード、モーダルダイアログ、入力ロックを使用できます。', + 'start': 'HTTP(S)アドレスを入力して閲覧を開始', + 'loading': '開いています…', + 'restore.previous': '前回開いたページ', + 'restore.action': 'ページを復元', + 'error.empty': 'アドレスを入力してください。', + 'error.invalid': 'アドレスが無効か、長すぎます。', + 'error.protocol': 'サポートされるのはHTTPとHTTPSのアドレスのみです。ローカルファイルにはドキュメントプレビューを使用してください。', + 'error.credentials': 'アドレスにユーザー名やパスワードを含めることはできません。', + 'error.application-origin': '埋め込みブラウザはDSHアプリ自体を開けません。', + 'load.failed': 'ページを読み込めませんでした。再読み込みするか、システムブラウザで開いてください。', + 'load.failed.detail': 'ページの読み込みに失敗しました({code}): {description}', + 'address.unknown': 'ページが遷移しました。このキャリアは新しいURLを読み取れません。', +} satisfies Record diff --git a/packages/client/ui-sidebar-browser/tests/apply.client.spec.ts b/packages/client/ui-sidebar-browser/tests/apply.client.spec.ts index 67167dd7da..68f338a40d 100644 --- a/packages/client/ui-sidebar-browser/tests/apply.client.spec.ts +++ b/packages/client/ui-sidebar-browser/tests/apply.client.spec.ts @@ -11,7 +11,7 @@ import { BrowserTitle } from '../src/client/view/BrowserTitle.tsx' import type { BrowserInjected } from '../src/client/browser/BrowserController.ts' import { BROWSER_ID, BROWSER_KIND } from '../src/client/definition.tsx' import { apply, inject } from '../src/client/index.ts' -import { en, zh } from '../src/client/locales.ts' +import { en, ja, zh } from '../src/client/locales.ts' import { createBrowserStore } from '../src/client/browser/store.ts' import type { TabId } from '@deepseek-ai/dsh-client-ui-dockkit' import type { DesktopBrowserBridge, DesktopBrowserLeaseId } from '../src/types.ts' @@ -125,7 +125,7 @@ describe('ui-sidebar-browser apply', () => { expect(definition?.title('sidebar://browser')).toBe('type.label') expect(definition?.guide?.map(entry => [entry.order, entry.title(), entry.description?.()])) .toEqual([[30, 'guide.title', 'guide.description']]) - expect(dictionaries.get('sidebarBrowser')).toEqual({ zh, en }) + expect(dictionaries.get('sidebarBrowser')).toEqual({ zh, en, ja }) expect(dictionaries.get('sidebarBrowser')).toMatchObject({ zh: { 'guide.description': '浏览网页' }, en: { 'guide.description': 'Browse web pages' }, diff --git a/packages/client/ui-sidebar-documentpreview/src/client/code/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/code/index.ts index 47094e0c03..b3d4d32241 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/code/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/code/index.ts @@ -3,14 +3,15 @@ import type { Context } from '@deepseek-ai/cordis' import { CODE_HIGHLIGHT_EXTENSIONS } from '@deepseek-ai/dsh-client-ui-primitives' import type {} from '../index.ts' import { CodeBody } from './CodeBody.tsx' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' const ID = '@deepseek-ai/dsh-client-ui-sidebar-documentpreview/code' const NS = 'sidebarCodePreview' /** @param ctx - owning plugin context. Register localized metadata and the matching keyed document body. */ export function apply(ctx: Context): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en })) + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries)) const t = ctx.locale.bind(NS) ctx.effect(() => ctx.documentPreviews.register({ id: ID, diff --git a/packages/client/ui-sidebar-documentpreview/src/client/code/locales.ts b/packages/client/ui-sidebar-documentpreview/src/client/code/locales.ts index e251d7f97c..0173b40bff 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/code/locales.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/code/locales.ts @@ -21,3 +21,10 @@ export const en = { copy: 'Copy', copied: 'Copied', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': 'コード', + 'copy': 'コピー', + 'copied': 'コピーしました', +} satisfies Record diff --git a/packages/client/ui-sidebar-documentpreview/src/client/excel/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/excel/index.ts index 61e0380397..d53d99c179 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/excel/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/excel/index.ts @@ -3,7 +3,7 @@ import type { Context } from '@deepseek-ai/cordis' import type {} from '../index.ts' import type { Config } from '../../config.ts' import { LazyExcelBody } from './LazyExcelBody.tsx' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' /** * Register the browser Excel viewer and its lifecycle-owned slot. @@ -12,7 +12,8 @@ import { en, zh } from './locales.ts' */ export function apply(ctx: Context, limits: Config['excel']): void { const id = '@deepseek-ai/dsh-client-ui-sidebar-documentpreview/excel' - ctx.effect(() => ctx.locale.register('sidebarExcel', { zh, en })) + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register('sidebarExcel', dictionaries)) const t = ctx.locale.bind('sidebarExcel') ctx.effect(() => ctx.documentPreviews.register({ id, extensions: ['xlsx', 'xls', 'csv', 'tsv'], binaryExtensions: ['xlsx', 'xls'], priority: 'builtin', diff --git a/packages/client/ui-sidebar-documentpreview/src/client/excel/locales.ts b/packages/client/ui-sidebar-documentpreview/src/client/excel/locales.ts index 79b97690a4..2a448c0660 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/excel/locales.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/excel/locales.ts @@ -31,3 +31,22 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { sidebarExcel: ExcelPreviewKey } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': 'スプレッドシート', + 'language': 'ja-JP', + 'loading': 'ドキュメントをレンダリング中...', + 'invalid': 'このスプレッドシートを開けませんでした。形式、内容、またはパスワード保護を確認してください。', + 'tooLarge': 'このブックはプレビューのサイズ上限を超えています。', + 'timeout': 'このブックを開く際にタイムアウトしました。より小さいファイルをお試しください。', + 'encoding': 'このテキストエンコーディングを読み取れませんでした。ファイルをBOM付きのUTF-8またはUTF-16で保存して再試行してください。', + 'formulaWarning': 'このブックには数式が含まれています。表示される結果が欠落または不正確な場合があります。', + 'unsupportedNotice': 'このプレビューはこのブックの{features}をサポートしていません。完全な表示にはシステムアプリケーションで開いてください。', + 'charts': 'グラフ', + 'images': '画像', + 'shapes': '図形', + 'conditionalFormatting': '条件付き書式', + 'featureSeparator': '、', + 'retry': '再試行', +} satisfies Record diff --git a/packages/client/ui-sidebar-documentpreview/src/client/html/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/html/index.ts index 5cf6d53762..77e3686208 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/html/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/html/index.ts @@ -6,7 +6,7 @@ import { hostFileOf } from '../rpc.ts' import { HtmlBody } from './HtmlBody.tsx' import type { HtmlBodyInjected } from './HtmlBody.tsx' import type {} from '@deepseek-ai/dsh-client-ui-settings/client' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' /** HTML implementation identity, shared by metadata and the keyed slot. */ export const HTML_BODY_ID = '@deepseek-ai/dsh-client-ui-sidebar-documentpreview/html' @@ -26,7 +26,8 @@ export function htmlBodyDefinition(title: () => string): DocumentPreviewDefiniti */ export function apply(ctx: Context): void { const t = ctx.locale.bind('documentHtml') - ctx.effect(() => ctx.locale.register('documentHtml', { zh, en })) + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register('documentHtml', dictionaries)) ctx.effect(() => ctx.documentPreviews.register(htmlBodyDefinition(() => t('title')))) ctx.effect(() => ctx.slots.inject('sidebar.right.tab.document', () => ctx.slots.register( { diff --git a/packages/client/ui-sidebar-documentpreview/src/client/html/locales.ts b/packages/client/ui-sidebar-documentpreview/src/client/html/locales.ts index e070bafb13..1617bd42d7 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/html/locales.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/html/locales.ts @@ -23,3 +23,11 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { documentHtml: HtmlPreviewKey } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': 'HTML', + 'frame': 'HTMLドキュメントのプレビュー', + 'loading': 'ドキュメントをレンダリング中...', + 'failed': 'このHTMLドキュメントをプレビューできませんでした。', +} satisfies Record diff --git a/packages/client/ui-sidebar-documentpreview/src/client/image/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/image/index.ts index b7f86ff0ae..81bcad1172 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/image/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/image/index.ts @@ -7,7 +7,7 @@ import type { DocumentPreviewDefinition } from '../document/registry.ts' import { retainDocumentTabs } from '../document/tab-lifetime.ts' import { createZoomStore, type ZoomInjected, type ZoomStore } from '../zoom/store.ts' import { ImageBody } from './ImageBody.tsx' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' /** Image implementation identity, shared by metadata and the keyed slot. */ export const IMAGE_BODY_ID = '@deepseek-ai/dsh-client-ui-sidebar-documentpreview/image' @@ -41,7 +41,8 @@ export function imageBodyDefinition(title: () => string): DocumentPreviewDefinit */ export function apply(ctx: Context): void { const t = ctx.locale.bind('sidebarImage') - ctx.effect(() => ctx.locale.register('sidebarImage', { zh, en }), 'document-image: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register('sidebarImage', dictionaries), 'document-image: dictionaries') ctx.effect(() => ctx.documentPreviews.register(imageBodyDefinition(() => t('title'))), 'document-image: metadata') const store = createZoomStore() const retainTab = retainDocumentTabs(ctx) diff --git a/packages/client/ui-sidebar-documentpreview/src/client/image/locales.ts b/packages/client/ui-sidebar-documentpreview/src/client/image/locales.ts index a99ae19854..852635dcca 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/image/locales.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/image/locales.ts @@ -1,4 +1,4 @@ -import { zoomEn, zoomZh } from '../zoom/locales.ts' +import { zoomEn, zoomJa, zoomZh } from '../zoom/locales.ts' /** Locale-owned image renderer labels and status text. */ export const zh = { @@ -29,3 +29,13 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { sidebarImage: ImagePreviewKey } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + ...zoomJa, + 'title': '画像', + 'preview': '画像プレビュー: {name}', + 'loading': 'ドキュメントをレンダリング中...', + 'failed': 'この画像を表示できませんでした。', + 'unsupported': '画像プレビューには完全なファイル内容が必要です。', +} satisfies Record diff --git a/packages/client/ui-sidebar-documentpreview/src/client/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/index.ts index 72a26771d8..dd1186175a 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/index.ts @@ -27,7 +27,7 @@ import { TEXTPREVIEW_ID, textDefinition } from './definition.ts' import { textFace } from './face.ts' import { createReadPage } from './rpc.ts' import { createTextStore } from './store.ts' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' import { DocumentPreviewRegistry } from './document/registry.ts' import { documentTabInfoFactory } from './document/contract.ts' import { apply as registerText } from './text/index.ts' @@ -91,7 +91,8 @@ export function apply(ctx: ClientContext): void { const disposePreviews = ctx.reflect.provide('documentPreviews', previews) ctx.effect(() => disposePreviews) ctx.effect(() => ctx.sidebarRightTabs.register(textDefinition()), 'ui-sidebar-documentpreview: text type') - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-sidebar-documentpreview: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-sidebar-documentpreview: dictionaries') const store = createTextStore() const face = textFace( diff --git a/packages/client/ui-sidebar-documentpreview/src/client/locales.ts b/packages/client/ui-sidebar-documentpreview/src/client/locales.ts index f7c7c645d6..400118427e 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/locales.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/locales.ts @@ -60,3 +60,29 @@ export const en = { 'error.unavailable': 'Read failed: {message}', retry: 'Retry', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'loading': 'ドキュメントをレンダリング中…', + 'loadMore': 'さらに読み込む', + 'changed': 'ファイルが変更されました。以前の内容を表示しています。', + 'reloadNow': '再読み込み', + 'reload': 'ファイルを再度読み込む', + 'autoRefresh': '自動更新', + 'autoRefresh.enable': '自動更新を有効にする', + 'autoRefresh.disable': '自動更新を無効にする', + 'wrap.enable': '行の折り返しをオンにする', + 'wrap.disable': '行の折り返しをオフにする', + 'wrap.aria': '行の折り返し', + 'openWith': 'アプリで開く', + 'viewer.text': 'プレーンテキスト', + 'resourceUnavailable': 'ファイルリソースサービスを利用できません。', + 'rendererUnavailable': '{name} のプレビューを利用できません。', + 'unsupportedFile': 'このファイル形式のプレビューはまだ利用できません。', + 'error.notFound': 'ファイルが見つかりません。移動または削除された可能性があります。', + 'error.tooLarge': 'このページは {limit} の上限を超えているため読み込めません。', + 'error.notText': 'このファイル形式のプレビューはまだ利用できません。', + 'error.notRegularFile': '通常のファイルではないため、表示する内容がありません。', + 'error.unavailable': '読み込みに失敗しました: {message}', + 'retry': '再試行', +} satisfies Record diff --git a/packages/client/ui-sidebar-documentpreview/src/client/markdown/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/markdown/index.ts index d7eeae51fd..e60d839873 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/markdown/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/markdown/index.ts @@ -3,7 +3,7 @@ import type { Context } from '@deepseek-ai/cordis' import type {} from '../index.ts' import type { DocumentPreviewDefinition } from '../document/registry.ts' import { MarkdownBody } from './MarkdownBody.tsx' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' /** Implementation identity shared by metadata and the document slot. */ export const MARKDOWN_BODY_ID = '@deepseek-ai/dsh-client-ui-sidebar-documentpreview/markdown' @@ -23,7 +23,8 @@ export function markdownDefinition(title: () => string): DocumentPreviewDefiniti */ export function apply(ctx: Context): void { const t = ctx.locale.bind('documentMarkdown') - ctx.effect(() => ctx.locale.register('documentMarkdown', { zh, en }), 'document-markdown: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register('documentMarkdown', dictionaries), 'document-markdown: dictionaries') ctx.effect(() => ctx.documentPreviews.register(markdownDefinition(() => t('viewer.label'))), 'document-markdown: metadata') ctx.effect(() => ctx.slots.inject('sidebar.right.tab.document', () => ctx.slots.register( { name: 'sidebar.right.tab.document', key: MARKDOWN_BODY_ID, locale: 'documentMarkdown' }, MarkdownBody, diff --git a/packages/client/ui-sidebar-documentpreview/src/client/markdown/locales.ts b/packages/client/ui-sidebar-documentpreview/src/client/markdown/locales.ts index 9ba9ba8afb..41554ace8b 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/markdown/locales.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/markdown/locales.ts @@ -23,3 +23,11 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { documentMarkdown: MarkdownPreviewKey } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'viewer.label': 'Markdown', + 'code.copy': 'コピー', + 'code.copied': 'コピーしました', + 'footnotes': '脚注', +} satisfies Record diff --git a/packages/client/ui-sidebar-documentpreview/src/client/office/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/office/index.ts index 6afe15f289..03699c8981 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/office/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/office/index.ts @@ -7,7 +7,7 @@ import type {} from '@deepseek-ai/dsh-client-locale/client' import type {} from '@deepseek-ai/dsh-api-workspace-files/remote' import { failureLine } from '../failure-line.ts' import { documentTabInfoFactory } from '../document/contract.ts' -import { en, zh, type OfficePreviewKey } from './locales.ts' +import { en, ja, zh, type OfficePreviewKey } from './locales.ts' import { OfficePreviewCache, type ReadOfficeBytes, type ReadOfficeDocument } from './cache.ts' import { pdfBodyRegistration } from '../pdf/index.ts' import { LazyPdfBody } from '../pdf/LazyPdfBody.tsx' @@ -31,7 +31,8 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { export function apply(ctx: Context, config: Config['office']): void { const id = '@deepseek-ai/dsh-client-ui-sidebar-documentpreview/office' const extensions = ['doc', 'docx', 'ppt', 'pptx'] - ctx.effect(() => ctx.locale.register('sidebarOffice', { zh, en })) + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register('sidebarOffice', dictionaries)) const t = ctx.locale.bind('sidebarOffice') const unavailable: ReadOfficeDocument = (_file, signal) => { signal.throwIfAborted() diff --git a/packages/client/ui-sidebar-documentpreview/src/client/office/locales.ts b/packages/client/ui-sidebar-documentpreview/src/client/office/locales.ts index e65626048b..fbab4f4913 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/office/locales.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/office/locales.ts @@ -38,3 +38,22 @@ export const en = { busy: 'Office preview is busy. Try again shortly.', changed: 'The file changed while being read. Reopen the preview.', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': 'Office ドキュメント', + 'loading': 'ドキュメントをレンダリング中…', + 'retry': '再試行', + 'viewMissingFonts': '不足しているフォント: {count}。クリックして表示。', + 'missingFontsTitle': '不足しているフォント', + 'missingFontsDescription': 'これらのフォントはこのプレビューでは利用できません。テキストとレイアウトが元のドキュメントと異なる場合があります。', + 'missingFontsCount': 'フォント: {count}', + 'closeDetails': 'フォントの詳細を閉じる', + 'unavailable': 'Office プレビューを利用できません。DeepSeek Harness を実行しているコンピューターでドキュメントプレビューサービスを有効にしてください。', + 'invalid': 'この Office ファイルはプレビューできません。破損しているか、パスワード保護されているか、拡張子が誤っている可能性があります。', + 'tooLarge': 'Office ファイルまたは変換後の PDF がプレビューのサイズ上限を超えています。ファイルサイズを減らすか、プレビュー設定を調整してください。', + 'failed': 'Office の変換で使用可能な PDF が生成されませんでした。ファイルを確認して再試行してください。', + 'timeout': 'Office の変換がタイムアウトしました。再試行してください。', + 'busy': 'Office プレビューは混雑しています。しばらくしてから再試行してください。', + 'changed': '読み込み中にファイルが変更されました。プレビューを開き直してください。', +} satisfies Record diff --git a/packages/client/ui-sidebar-documentpreview/src/client/pdf/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/pdf/index.ts index 0dbe890eb0..ab5640d20f 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/pdf/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/pdf/index.ts @@ -9,7 +9,7 @@ import type { BoundActions } from '@deepseek-ai/dsh-client-store' import type { SessionId } from '@deepseek-ai/dsh-session/types' import { createPdfStore, type PdfStore } from './store.ts' import { ZoomViewport, zoomSurfaceClass } from '../zoom/ZoomViewport.tsx' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' /** PDF metadata and keyed body share this package-local implementation identity. */ export const PDF_BODY_ID = '@deepseek-ai/dsh-client-ui-sidebar-documentpreview/pdf' @@ -25,7 +25,8 @@ export function pdfBodyDefinition(title: () => string): DocumentPreviewDefinitio /** @param ctx - context carrying the locale, document registry, and slot registry. */ export function apply(ctx: Context): void { - ctx.effect(() => ctx.locale.register('sidebarPdf', { zh, en })) + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register('sidebarPdf', dictionaries)) const t = ctx.locale.bind('sidebarPdf') ctx.effect(() => ctx.documentPreviews.register(pdfBodyDefinition(() => t('title')))) const presentation = pdfBodyRegistration(ctx) diff --git a/packages/client/ui-sidebar-documentpreview/src/client/pdf/locales.ts b/packages/client/ui-sidebar-documentpreview/src/client/pdf/locales.ts index cb58bc29d3..e9de97b4d3 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/pdf/locales.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/pdf/locales.ts @@ -1,4 +1,4 @@ -import { zoomEn, zoomZh } from '../zoom/locales.ts' +import { zoomEn, zoomJa, zoomZh } from '../zoom/locales.ts' /** Copy owned by the PDF renderer. */ export const zh = { @@ -37,3 +37,17 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { sidebarPdf: PdfLocaleKey } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + ...zoomJa, + 'title': 'PDF', + 'pageImage': 'PDFページ {page}', + 'loading': 'ドキュメントをレンダリング中...', + 'rendering': 'ページをレンダリング中…', + 'failed': 'PDFを表示できません: {message}', + 'password': 'このPDFにはパスワードが必要です。パスワード保護されたプレビューはサポートされていません。', + 'workerFailed': 'PDFのレンダリング処理を続行できませんでした。再試行してください。', + 'unsupported': 'PDFのプレビューには完全なファイル内容が必要です。', + 'retry': '再試行', +} satisfies Record diff --git a/packages/client/ui-sidebar-documentpreview/src/client/zoom/locales.ts b/packages/client/ui-sidebar-documentpreview/src/client/zoom/locales.ts index 3f6655c8b7..ce263fccfd 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/zoom/locales.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/zoom/locales.ts @@ -20,3 +20,13 @@ export const zoomEn = { /** Shared zoom dictionary keys. */ export type ZoomLocaleKey = keyof typeof zoomZh + +/** Japanese copy, checked against the English keys. */ +export const zoomJa = { + 'zoomControls': 'ズーム操作', + 'zoomMenu': 'ズームを選択', + 'zoomOut': '縮小', + 'zoomIn': '拡大', + 'zoomFitWidth': '幅に合わせる', + 'zoomValue': '{percent}%', +} satisfies Record diff --git a/packages/client/ui-sidebar-documentpreview/tests/apply.client.spec.ts b/packages/client/ui-sidebar-documentpreview/tests/apply.client.spec.ts index 124acfe744..b82721e0a6 100644 --- a/packages/client/ui-sidebar-documentpreview/tests/apply.client.spec.ts +++ b/packages/client/ui-sidebar-documentpreview/tests/apply.client.spec.ts @@ -31,7 +31,7 @@ import { LazyPdfBody } from '../src/client/pdf/LazyPdfBody.tsx' import { LazyExcelBody } from '../src/client/excel/LazyExcelBody.tsx' import { PDF_BODY_ID } from '../src/client/pdf/index.ts' import { CodeBody } from '../src/client/code/CodeBody.tsx' -import { en, zh } from '../src/client/locales.ts' +import { en, ja, zh } from '../src/client/locales.ts' import type { textFace } from '../src/client/face.ts' import type { TextStore } from '../src/client/store.ts' import { FILE, SESSION, TAB_ID, byteResult, createResources, page } from './fixtures.client.ts' @@ -85,7 +85,7 @@ describe('ui-sidebar-documentpreview apply', () => { const { tabs, registered, dictionaries } = await boot() expect(tabs.get(TEXTPREVIEW_KIND)?.priority).toBe('fallback') expect(tabs.get(TEXTPREVIEW_KIND)?.id).toBe(TEXTPREVIEW_ID) - expect(dictionaries.get('sidebarDocumentPreview')).toEqual({ zh, en }) + expect(dictionaries.get('sidebarDocumentPreview')).toEqual({ zh, en, ja }) // The seat key is the implementation's id, not the kind: an extension may // take the kind over, and the seat must still find this body. expect(registered.map(entry => [entry.name, entry.key, entry.locale, entry.component])).toEqual([ diff --git a/packages/client/ui-sidebar-documentpreview/tests/excel-registration.client.spec.ts b/packages/client/ui-sidebar-documentpreview/tests/excel-registration.client.spec.ts index a2d96258c1..b5df1bdaaf 100644 --- a/packages/client/ui-sidebar-documentpreview/tests/excel-registration.client.spec.ts +++ b/packages/client/ui-sidebar-documentpreview/tests/excel-registration.client.spec.ts @@ -6,7 +6,7 @@ import { binaryDocumentPath, DocumentPreviewRegistry } from '../src/client/docum import { Config } from '../src/config.ts' import { apply } from '../src/client/excel/index.ts' import { LazyExcelBody } from '../src/client/excel/LazyExcelBody.tsx' -import { en, zh } from '../src/client/excel/locales.ts' +import { en, ja, zh } from '../src/client/excel/locales.ts' it('registers complete binary reads and removes the slot and locale on disposal', async () => { const ctx = new Context() @@ -28,7 +28,7 @@ it('registers complete binary reads and removes the slot and locale on disposal' expect(candidate.title()).toBe('Spreadsheet') expect(binaryDocumentPath(registry.getSnapshot(), path)).toBe(/\.(xlsx|xls)$/iu.test(path)) } - expect(registerLocale).toHaveBeenCalledWith('sidebarExcel', { zh, en }) + expect(registerLocale).toHaveBeenCalledWith('sidebarExcel', { zh, en, ja }) expect(register).toHaveBeenCalledOnce() expect(register.mock.calls[0]![1]).toBe(LazyExcelBody) const options = register.mock.calls[0]![0] as { inject: () => unknown } diff --git a/packages/client/ui-sidebar-documentpreview/tests/html-apply.client.spec.ts b/packages/client/ui-sidebar-documentpreview/tests/html-apply.client.spec.ts index 097f41facf..a77c9b7c6f 100644 --- a/packages/client/ui-sidebar-documentpreview/tests/html-apply.client.spec.ts +++ b/packages/client/ui-sidebar-documentpreview/tests/html-apply.client.spec.ts @@ -7,7 +7,7 @@ import { sessionFileAddress } from '@deepseek-ai/dsh-util-workspace-path' import { DocumentPreviewRegistry } from '../src/client/document/registry.ts' import { apply, HTML_BODY_ID, htmlBodyDefinition } from '../src/client/html/index.ts' import { HtmlBody } from '../src/client/html/HtmlBody.tsx' -import { en, zh } from '../src/client/html/locales.ts' +import { en, ja, zh } from '../src/client/html/locales.ts' import type { HtmlBodyProps } from '../src/client/html/HtmlBody.tsx' type Registration = { @@ -54,7 +54,7 @@ describe('HTML registration', () => { await fiber.await() expect(registry.candidates('INDEX.HTM').map(entry => entry.id)).toEqual([HTML_BODY_ID]) expect(registry.getSnapshot()[0]?.title()).toBe(en.title) - expect(dictionaries.get('documentHtml')).toEqual({ zh, en }) + expect(dictionaries.get('documentHtml')).toEqual({ zh, en, ja }) expect(register).toHaveBeenCalledOnce() const registration = register.mock.calls[0]?.[0] expect(registration).toMatchObject({ name: 'sidebar.right.tab.document', key: HTML_BODY_ID, locale: 'documentHtml' }) diff --git a/packages/client/ui-sidebar-documentpreview/tests/image-registration.client.spec.ts b/packages/client/ui-sidebar-documentpreview/tests/image-registration.client.spec.ts index 850c71c334..46b75ff751 100644 --- a/packages/client/ui-sidebar-documentpreview/tests/image-registration.client.spec.ts +++ b/packages/client/ui-sidebar-documentpreview/tests/image-registration.client.spec.ts @@ -6,7 +6,7 @@ import type { TabId } from '@deepseek-ai/dsh-client-ui-dockkit' import { DocumentPreviewRegistry } from '../src/client/document/registry.ts' import { ImageBody } from '../src/client/image/ImageBody.tsx' import { apply, BINARY_IMAGE_EXTENSIONS, IMAGE_BODY_ID, IMAGE_EXTENSIONS, imageBodyDefinition } from '../src/client/image/index.ts' -import { en, zh } from '../src/client/image/locales.ts' +import { en, ja, zh } from '../src/client/image/locales.ts' import type { ZoomInjected, ZoomStore } from '../src/client/zoom/store.ts' let dispose: (() => Promise) | undefined @@ -63,7 +63,7 @@ describe('image registration', () => { expect(registry.candidates(`ASSET.${extension}`).map(entry => entry.id)).toEqual([IMAGE_BODY_ID]) } expect(registry.getSnapshot()[0]?.title()).toBe(en.title) - expect(dictionaries.get('sidebarImage')).toEqual({ zh, en }) + expect(dictionaries.get('sidebarImage')).toEqual({ zh, en, ja }) expect(register).toHaveBeenCalledExactlyOnceWith(expect.objectContaining({ name: 'sidebar.right.tab.document', key: IMAGE_BODY_ID, locale: 'sidebarImage', }), ImageBody) diff --git a/packages/client/ui-sidebar-documentpreview/tests/office-registration.client.spec.ts b/packages/client/ui-sidebar-documentpreview/tests/office-registration.client.spec.ts index 29b1626518..334beb3da1 100644 --- a/packages/client/ui-sidebar-documentpreview/tests/office-registration.client.spec.ts +++ b/packages/client/ui-sidebar-documentpreview/tests/office-registration.client.spec.ts @@ -13,7 +13,7 @@ import { Config } from '../src/config.ts' import { OfficeBody, type OfficeBodyInjected } from '../src/client/office/OfficeBody.tsx' import type { OfficeStore } from '../src/client/office/store.ts' import type { TabId } from '@deepseek-ai/dsh-client-ui-dockkit' -import { en, zh } from '../src/client/office/locales.ts' +import { en, ja, zh } from '../src/client/office/locales.ts' import { en as documentEn } from '../src/client/locales.ts' vi.mock('../src/client/office/face.ts', async (importOriginal) => { @@ -91,7 +91,7 @@ async function harness(config: Partial = {}, missing?: 'remote it.each(['remote', 'render', 'files'] as const)('keeps Word and PowerPoint registration and guidance when %s is absent', async (missing) => { const h = await harness(undefined, missing) try { - expect(h.locale.register).toHaveBeenCalledWith('sidebarOffice', { zh, en }) + expect(h.locale.register).toHaveBeenCalledWith('sidebarOffice', { zh, en, ja }) for (const path of ['a.DOC', 'b.DOCX', 'c.PPT', 'd.pptx']) { expect(h.registry.candidates(path)[0]!.binaryExtensions).toEqual(['doc', 'docx', 'ppt', 'pptx']) expect(h.registry.candidates(path)[0]!.title()).toBe(en.title) diff --git a/packages/client/ui-sidebar-documentpreview/tests/pdf-registration.client.spec.ts b/packages/client/ui-sidebar-documentpreview/tests/pdf-registration.client.spec.ts index 87defbd8a5..a55c457e4c 100644 --- a/packages/client/ui-sidebar-documentpreview/tests/pdf-registration.client.spec.ts +++ b/packages/client/ui-sidebar-documentpreview/tests/pdf-registration.client.spec.ts @@ -16,7 +16,7 @@ const renderedPdf = vi.hoisted(() => vi.fn(() => null)) vi.mock('../src/client/pdf/pdf.tsx', () => ({ PdfBody: renderedPdf })) import { apply, PDF_BODY_ID } from '../src/client/pdf/index.ts' import { LazyPdfBody } from '../src/client/pdf/LazyPdfBody.tsx' -import { en, zh } from '../src/client/pdf/locales.ts' +import { en, ja, zh } from '../src/client/pdf/locales.ts' afterEach(() => { cleanup(); renderedPdf.mockClear() }) @@ -58,7 +58,7 @@ describe('PDF registration', () => { { id: PDF_BODY_ID, extensions: ['pdf'], priority: 'builtin', loading: 'bytes-complete', wrap: false }, ]) expect(previews.candidates('report.pdf')[0]!.title()).toBe('PDF') - expect(dictionaries.get('sidebarPdf')).toEqual({ zh, en }) + expect(dictionaries.get('sidebarPdf')).toEqual({ zh, en, ja }) expect(entries[0]).toMatchObject({ name: 'sidebar.right.tab.document', key: PDF_BODY_ID, locale: 'sidebarPdf' }) const instance = entries[0]!.store.create() const face = entries[0]!.inject('s1' as SessionId, instance.actions) diff --git a/packages/client/ui-sidebar-files/src/client/index.ts b/packages/client/ui-sidebar-files/src/client/index.ts index fb2ab7ad43..ec6de5b4d0 100644 --- a/packages/client/ui-sidebar-files/src/client/index.ts +++ b/packages/client/ui-sidebar-files/src/client/index.ts @@ -20,7 +20,7 @@ import { FILES_ID, filesDefinition } from './definition.tsx' import { createList, createWatch, filesFace } from './face.ts' import { FilesBody } from './FilesBody.tsx' import { FilesTitle } from './FilesTitle.tsx' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' import { createFilesStore } from './store.ts' export type { SidebarFilesKey } from './locales.ts' @@ -79,7 +79,8 @@ export function apply(ctx: ClientContext): void { }), 'ui-sidebar-files: shortcut') }) ctx.effect(() => ctx.sidebarRightTabs.register(filesDefinition(t)), 'ui-sidebar-files: files type') - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-sidebar-files: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-sidebar-files: dictionaries') const store = createFilesStore() const inject = filesFace(createList(ctx.remote), createWatch(ctx.remote)) diff --git a/packages/client/ui-sidebar-files/src/client/locales.ts b/packages/client/ui-sidebar-files/src/client/locales.ts index ea4b6eeaec..69bf81f8ce 100644 --- a/packages/client/ui-sidebar-files/src/client/locales.ts +++ b/packages/client/ui-sidebar-files/src/client/locales.ts @@ -62,3 +62,24 @@ export const en = { 'error.notDirectory': 'That is not a directory.', 'error.unavailable': 'Read failed: {message}', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'shortcut.noSession': '先にセッションを選択してください', + 'type.label': 'ファイル', + 'guide.title': 'ワークスペースのファイル', + 'guide.description': 'このセッションのワークスペース内のファイルを参照', + 'loading': '読み取り中…', + 'empty': '空のディレクトリ', + 'truncated': 'エントリが多すぎるため、一部のみ表示しています。', + 'noWorkspace': 'このセッションにはワークスペースディレクトリがありません。', + 'reload': '再読み込み', + 'autoRefresh': '自動更新', + 'autoRefresh.enable': '自動更新を有効にする', + 'autoRefresh.disable': '自動更新を無効にする', + 'entry.other': 'ファイルでもディレクトリでもないため、開けません。', + 'error.notFound': 'そのディレクトリは存在しません。移動または削除された可能性があります。', + 'error.outsideWorkspace': 'そのディレクトリはワークスペース外のため、サイドバーでは読み取りません。', + 'error.notDirectory': 'それはディレクトリではありません。', + 'error.unavailable': '読み取りに失敗しました: {message}', +} satisfies Record diff --git a/packages/client/ui-sidebar-files/tests/apply.client.spec.ts b/packages/client/ui-sidebar-files/tests/apply.client.spec.ts index 4620430b49..003df8b934 100644 --- a/packages/client/ui-sidebar-files/tests/apply.client.spec.ts +++ b/packages/client/ui-sidebar-files/tests/apply.client.spec.ts @@ -16,7 +16,7 @@ import { apply, inject } from '../src/client/index.ts' import { apply as hostApply } from '../src/index.ts' import { FilesBody } from '../src/client/FilesBody.tsx' import { FilesTitle } from '../src/client/FilesTitle.tsx' -import { en, zh } from '../src/client/locales.ts' +import { en, ja, zh } from '../src/client/locales.ts' interface Recorded { name: string @@ -94,7 +94,7 @@ describe('ui-sidebar-files apply', () => { expect(definition?.title('sidebar://files')).toBe('type.label') expect(definition?.guide?.map(entry => [entry.order, entry.title(), entry.description?.()])) .toEqual([[10, 'guide.title', 'guide.description']]) - expect(dictionaries.get('sidebarFiles')).toEqual({ zh, en }) + expect(dictionaries.get('sidebarFiles')).toEqual({ zh, en, ja }) // The seat key is the implementation's id, not the kind: an extension may // take the kind over, and the seat must still find this body. expect(registered.map(entry => [entry.name, entry.key, entry.locale, entry.component])).toEqual([ diff --git a/packages/client/ui-sidebar-right/src/client/index.ts b/packages/client/ui-sidebar-right/src/client/index.ts index 4b3e9f79d2..35c88b2722 100644 --- a/packages/client/ui-sidebar-right/src/client/index.ts +++ b/packages/client/ui-sidebar-right/src/client/index.ts @@ -42,7 +42,7 @@ import { SidebarSessionViews } from './session-views.ts' import { createSidebarRightController, type SidebarRightController } from './service.ts' import { SidebarRightTabRegistry } from './tab-registry.ts' import { createSidebarRightStore } from './stores.ts' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' import { GUIDE_ID, guideDefinition } from './tabs/guide/definition.ts' import { createGuidePins } from './tabs/guide/guide-pins.ts' import { guideTabInfoFactory, tabInfoFactory } from './tab-info.ts' @@ -161,7 +161,8 @@ export function apply(ctx: ClientContext): void { void disposeRegistry() }, 'ui-sidebar-right: service faces') - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-sidebar-right: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-sidebar-right: dictionaries') ctx.effect(() => registerSidebarShortcuts(ctx.shortcuts, controller, t, () => { void ctx.shortcuts.closeWindow().catch((error: unknown) => { console.error('Window close failed', error) }) }), 'ui-sidebar-right: shortcuts') diff --git a/packages/client/ui-sidebar-right/src/client/locales.ts b/packages/client/ui-sidebar-right/src/client/locales.ts index 25d1335057..f6b70b8a88 100644 --- a/packages/client/ui-sidebar-right/src/client/locales.ts +++ b/packages/client/ui-sidebar-right/src/client/locales.ts @@ -86,3 +86,42 @@ export const en = { 'tab.guide.more': 'More tools', 'tab.unavailable': 'Nothing here can view this kind of content yet.', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'command.close': '現在のページまたはウィンドウを閉じる', + 'command.refresh': '現在のページを更新', + 'command.noRefresh': 'このページは更新できません', + 'command.toggle': '右サイドバーを切り替え', + 'command.fullscreen': 'パネルの全画面表示を切り替え', + 'command.noSession': '先にセッションを選択してください', + 'command.noFocus': '先に右サイドバーのペインにフォーカスしてください', + 'command.stale': 'ページが変更されました。再度フォーカスしてください', + 'command.collapsed': '先に右サイドバーを展開してください', + 'command.float': 'この操作はフローティングパネルでは使用できません', + 'command.empty': '先にページを開いてください', + 'command.budget': 'ペインは2つまでです', + 'command.width': '分割するのに十分な幅がありません。サイドバーを広げてください', + 'chrome.expand': 'サイドバーを開く', + 'chrome.expandAria': '右サイドバーを開く', + 'chrome.collapse': 'サイドバーを折りたたむ', + 'chrome.collapseAria': '右サイドバーを折りたたむ', + 'chrome.toFullscreen': '全画面', + 'chrome.exitFullscreen': '全画面を終了', + 'dock.emptyPane': '空のペイン', + 'dock.splitPane': '分割', + 'dock.splitPaneDisabled': 'ペインは2つまでです', + 'dock.splitPaneNarrow': '分割するのに十分な幅がありません。サイドバーを広げてください', + 'dock.closeTab': '閉じる', + 'dock.addTab': '新しいタブ', + 'dock.dockFloat': 'サイドバーに戻す', + 'dock.closeFloat': '閉じる', + 'dock.drop.center': 'ここに移動', + 'dock.drop.left': '左に分割を追加', + 'dock.drop.right': '右に分割を追加', + 'dock.drop.top': '上に分割を追加', + 'dock.drop.bottom': '下に分割を追加', + 'tab.guide.title': '開始', + 'tab.guide.more': 'その他のツール', + 'tab.unavailable': 'この種類のコンテンツを表示できるものはまだありません。', +} satisfies Record diff --git a/packages/client/ui-sidebar-right/tests/apply.client.spec.ts b/packages/client/ui-sidebar-right/tests/apply.client.spec.ts index 26be970884..59c620cd3b 100644 --- a/packages/client/ui-sidebar-right/tests/apply.client.spec.ts +++ b/packages/client/ui-sidebar-right/tests/apply.client.spec.ts @@ -27,7 +27,7 @@ import { ExpandButton } from '../src/client/shell/ExpandButton.tsx' import { GuideBody } from '../src/client/tabs/guide/GuideBody.tsx' import { GuideTitle } from '../src/client/tabs/guide/GuideTitle.tsx' import { GUIDE_ID } from '../src/client/tabs/guide/definition.ts' -import { en, zh } from '../src/client/locales.ts' +import { en, ja, zh } from '../src/client/locales.ts' const SHORTCUT_CATALOG: readonly never[] = [] @@ -124,7 +124,7 @@ describe('ui-sidebar-right apply', () => { expect(ctx.sidebarRightTabs).toBeInstanceOf(SidebarRightTabRegistry) expect(ctx.sidebarRight).toBeInstanceOf(SidebarRightController) expect('adopt' in ctx.sidebarRight).toBe(false) - expect(dictionaries.get('sidebarRight')).toEqual({ zh, en }) + expect(dictionaries.get('sidebarRight')).toEqual({ zh, en, ja }) const guide = ctx.sidebarRightTabs.get('guide') expect(guide?.id).toBe(GUIDE_ID) expect(guide?.priority).toBe('builtin') diff --git a/packages/client/ui-sidebar-terminal/src/client/index.ts b/packages/client/ui-sidebar-terminal/src/client/index.ts index 98ac69d4c9..a8b790bc54 100644 --- a/packages/client/ui-sidebar-terminal/src/client/index.ts +++ b/packages/client/ui-sidebar-terminal/src/client/index.ts @@ -17,7 +17,7 @@ import { TerminalTitle } from './TerminalTitle.tsx' // import { TerminalRecovery, type TerminalRecoveryInjected } from './TerminalRecovery.tsx' // import { TerminalCleanup, type TerminalCleanupInjected } from './TerminalCleanup.tsx' import type { TerminalBodyInjected, TerminalInjected } from './face.ts' -import { en, zh } from './locales.ts' +import { en, ja, zh } from './locales.ts' /** Services needed by the terminal's two sidebar seats. */ export const inject = ['slots', 'locale', 'sidebarRight', 'sidebarRightTabs', 'webTerminals', 'theme', 'shortcuts'] @@ -67,7 +67,8 @@ export function apply(ctx: Context): void { return { status: 'handled', run: () => { ctx.sidebarRight.openTabFromTarget('terminal', target) } } }, }), 'ui-sidebar-terminal: shortcut') - ctx.effect(() => ctx.locale.register(namespace, { zh, en }), 'ui-sidebar-terminal.copy') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(namespace, dictionaries), 'ui-sidebar-terminal.copy') ctx.effect(() => ctx.sidebarRightTabs.register({ id, kind: 'terminal', multiple: true, priority: 'builtin', title: () => t('title'), guide: [{ id: 'new', order: 20, title: () => t('new'), description: () => t('description'), icon: PluginArtworkTerminal }], diff --git a/packages/client/ui-sidebar-terminal/src/client/locales.ts b/packages/client/ui-sidebar-terminal/src/client/locales.ts index a43c7d0653..7c822d6216 100644 --- a/packages/client/ui-sidebar-terminal/src/client/locales.ts +++ b/packages/client/ui-sidebar-terminal/src/client/locales.ts @@ -42,3 +42,35 @@ export const en = { invalidOutput: 'The terminal screen could not be received. Reconnect to recover it.', terminalLimit: 'The terminal limit has been reached. Close unused terminals and try again. Exited terminals also count toward the limit.', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'shortcut.noSession': '先にセッションを選択してください', + 'recoveryFailed': 'ターミナルの復元に失敗しました: {message}', + 'retryRecovery': 'ターミナルの復元を再試行', + 'shell': 'シェルを選択', + 'shellLoading': 'シェルを読み込み中…', + 'shellEmpty': '利用可能なシェルがありません', + 'description': 'セッションのワークスペースでコマンドを実行', + 'title': 'ターミナル', + 'new': '新しいターミナル', + 'loading': 'ターミナル環境を読み込み中…', + 'creating': '起動中…', + 'connecting': '接続中…', + 'disconnected': '切断されました。', + 'reconnect': '再接続', + 'readonly': 'このビューは読み取り専用です。', + 'control': '制御を取得', + 'closed': 'ターミナルが閉じられました。', + 'exited': 'プロセスが終了しました({code})', + 'failed': 'ターミナルエラー: {message}', + 'rename': 'ターミナル名', + 'unavailable': '利用不可', + 'retry': '再試行', + 'cleanupFailed': 'ターミナル「{title}」を終了できませんでした: {message}', + 'missingTerminal': 'このターミナルはすでに存在しません。新しいターミナルを開いてください。', + 'inputFull': '入力バッファがいっぱいです。再接続して再試行してください。', + 'attachmentEnded': 'ターミナル接続が終了しました。続行するには再接続してください。', + 'invalidOutput': 'ターミナル画面を受信できませんでした。再接続して復元してください。', + 'terminalLimit': 'ターミナルの上限に達しました。未使用のターミナルを閉じて再試行してください。終了したターミナルも上限に含まれます。', +} satisfies Record diff --git a/packages/client/ui-sidebar-terminal/tests/apply.client.spec.ts b/packages/client/ui-sidebar-terminal/tests/apply.client.spec.ts index 0dfe69edb6..9161768ab2 100644 --- a/packages/client/ui-sidebar-terminal/tests/apply.client.spec.ts +++ b/packages/client/ui-sidebar-terminal/tests/apply.client.spec.ts @@ -18,7 +18,7 @@ import { TerminalGuide, type TerminalGuideInjected } from '../src/client/Termina import { LazyTerminalBody } from '../src/client/LazyTerminalBody.tsx' import { TerminalTitle } from '../src/client/TerminalTitle.tsx' import type { TerminalBodyInjected } from '../src/client/face.ts' -import { en, zh } from '../src/client/locales.ts' +import { en, ja, zh } from '../src/client/locales.ts' vi.mock('@xterm/xterm', () => ({ Terminal: vi.fn() })) const SHORTCUT_CATALOG: readonly never[] = [] @@ -97,7 +97,7 @@ it('registers terminal views without recovery or cleanup slots, then releases co expect(renderToStaticMarkup(createElement(Icon, { size: 22 }))).toContain('width="22"') expect(renderToStaticMarkup(createElement(Icon))).toContain('width="36"') expect(definition.multiple).toBe(true) - expect(h.dictionaries.get('sidebarTerminal')).toEqual({ en, zh }) + expect(h.dictionaries.get('sidebarTerminal')).toEqual({ en, zh, ja }) expect(h.entries.map(entry => [entry.name, entry.component, entry.locale])).toEqual([ ['sidebar.right.tab.guide.entry', TerminalGuide, 'sidebarTerminal'], ['sidebar.right.pane.tab', LazyTerminalBody, 'sidebarTerminal'], diff --git a/packages/client/ui-sidebar/src/client/index.ts b/packages/client/ui-sidebar/src/client/index.ts index 2b3b2af0ce..3060da3377 100644 --- a/packages/client/ui-sidebar/src/client/index.ts +++ b/packages/client/ui-sidebar/src/client/index.ts @@ -14,7 +14,7 @@ import type { SidebarPanelMetadata, SidebarRootInjected } from './contract/slots import { createPanelPins } from './panel-pins.ts' import { HeaderLeadingControls } from './HeaderLeadingControls.tsx' import { SidebarRoot } from './SidebarRoot.tsx' -import { en, zh, type SidebarKey } from './locales.ts' +import { en, ja, zh, type SidebarKey } from './locales.ts' export type { SidebarBrandMarkOwnerProps, SidebarBrandNameOwnerProps, SidebarFooterActionOwnerProps, @@ -45,7 +45,8 @@ export const inject = ['slots', 'layout', 'uiWorkspace', 'locale', 'shortcuts'] */ export function apply(ctx: ClientContext): void { const workspaceNavigation = ctx.get('uiWorkspace') as unknown as WorkspaceNavigation - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-sidebar: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-sidebar: dictionaries') const panels = createSnapshotStore([]) const syncPanels = (): void => { const next = ctx.slots.entriesOfSlot('sidebar.panellist').map(({ options }) => { diff --git a/packages/client/ui-sidebar/src/client/locales.ts b/packages/client/ui-sidebar/src/client/locales.ts index ee1ab5a076..a4505397a0 100644 --- a/packages/client/ui-sidebar/src/client/locales.ts +++ b/packages/client/ui-sidebar/src/client/locales.ts @@ -24,3 +24,14 @@ export const en = { 'panels.more': 'More', 'version.copied': 'Version copied', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'session.new': '新規セッション', + 'session.new.label': '新規セッション', + 'toggle.open': 'サイドバーを開く', + 'toggle.collapse': 'サイドバーを折りたたむ', + 'panels.label': 'グローバルパネル', + 'panels.more': 'その他', + 'version.copied': 'バージョンをコピーしました', +} satisfies Record diff --git a/packages/client/ui-skill/src/client/index.ts b/packages/client/ui-skill/src/client/index.ts index c00dc119fd..82a62009c2 100644 --- a/packages/client/ui-skill/src/client/index.ts +++ b/packages/client/ui-skill/src/client/index.ts @@ -52,7 +52,7 @@ import { SkillRow } from './SkillRow.tsx' import { SkillReference, type SkillReferenceInjected } from './SkillReference.tsx' import { skillReferenceRule } from './user-text-rules.ts' export type {} from './user-text-rules.ts' -import { en, NS, zh, type SkillKey } from './locales.ts' +import { en, ja, NS, zh, type SkillKey } from './locales.ts' declare module '@deepseek-ai/dsh-api-session-controller/client' { interface SessionReferenceSourceMap { @@ -84,7 +84,8 @@ export const inject = ['inputTriggers', 'sessions', 'slots', 'locale', 'remote', * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-skill: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-skill: dictionaries') ctx.uiConversation.userText.register(skillReferenceRule) ctx.slots.inject('tool.call.toolview', () => ctx.slots.register( { name: 'tool.call.toolview', key: 'skill', locale: NS }, diff --git a/packages/client/ui-skill/src/client/locales.ts b/packages/client/ui-skill/src/client/locales.ts index 1028a827b4..be188303d6 100644 --- a/packages/client/ui-skill/src/client/locales.ts +++ b/packages/client/ui-skill/src/client/locales.ts @@ -29,3 +29,15 @@ export const en = { 'row.inspect': 'Inspect', 'menu.userOnly': 'user-only', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'row.title': 'スキル', + 'row.running': 'スキルを読み込み中', + 'row.preparing': 'スキルの読み込みを準備中', + 'row.failed': 'スキルの読み込みに失敗しました', + 'row.stopped': 'スキルの読み込みが停止しました', + 'row.instructions': '手順', + 'row.inspect': '詳細を確認', + 'menu.userOnly': 'ユーザー専用', +} satisfies Record diff --git a/packages/client/ui-skill/tests/browser-plugin.client.spec.ts b/packages/client/ui-skill/tests/browser-plugin.client.spec.ts index b426da40fd..8d589aecca 100644 --- a/packages/client/ui-skill/tests/browser-plugin.client.spec.ts +++ b/packages/client/ui-skill/tests/browser-plugin.client.spec.ts @@ -161,6 +161,16 @@ describe('apply', () => { 'row.inspect': 'Inspect', 'menu.userOnly': 'user-only', }, + ja: { + 'row.title': 'スキル', + 'row.running': 'スキルを読み込み中', + 'row.preparing': 'スキルの読み込みを準備中', + 'row.failed': 'スキルの読み込みに失敗しました', + 'row.stopped': 'スキルの読み込みが停止しました', + 'row.instructions': '手順', + 'row.inspect': '詳細を確認', + 'menu.userOnly': 'ユーザー専用', + }, }, }]) }) diff --git a/packages/client/ui-subagent/src/client/index.ts b/packages/client/ui-subagent/src/client/index.ts index 6d4220ef9c..f665371668 100644 --- a/packages/client/ui-subagent/src/client/index.ts +++ b/packages/client/ui-subagent/src/client/index.ts @@ -14,7 +14,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' import type {} from '@deepseek-ai/dsh-client-ui-session/client' import type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client' import type {} from '@deepseek-ai/dsh-client-ui-workspace/client' -import { en, NS, zh, type SubagentKey } from './locales.ts' +import { en, ja, NS, zh, type SubagentKey } from './locales.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { @@ -53,7 +53,8 @@ function selectReadOnlySubagent(owner: ComposerChainProps): SubagentReadOnlyMatc * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-subagent: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-subagent: dictionaries') ctx.inject(['resources', 'sidebarRightTabs'], (scope) => { registerSidebarChat(scope, ctx.locale.bind(NS)) }) diff --git a/packages/client/ui-subagent/src/client/locales.ts b/packages/client/ui-subagent/src/client/locales.ts index 8004ca18f8..9cd5f6d30e 100644 --- a/packages/client/ui-subagent/src/client/locales.ts +++ b/packages/client/ui-subagent/src/client/locales.ts @@ -95,3 +95,48 @@ export const en: Record = { /** Key domain of the `subagent` namespace (zh is the source of truth). */ export type SubagentKey = keyof typeof zh + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'duration.seconds': '{seconds}秒', + 'duration.minutes': '{minutes}分{seconds}秒', + 'duration.hours': '{hours}時間{minutes}分{seconds}秒', + 'duration.days': '{days}日', + 'duration.daysHours': '{days}日{hours}時間', + 'duration.months': '約{months}ヶ月', + 'duration.monthsDays': '約{months}ヶ月{days}日', + 'duration.years': '約{years}年', + 'duration.yearsMonths': '約{years}年{months}ヶ月', + 'duration.exactDays': '{days}日{hours}時間{minutes}分{seconds}秒', + 'duration.exactTitle': '合計アクティブ時間: {duration}', + 'tokens.thousand': '{value}K', + 'tokens.million': '{value}M', + 'tokens.total': '{value} tok', + 'loading.label': 'サブエージェントを読み込み中…', + 'load.error': 'サブエージェントを読み込めません', + 'catalog.migrationRequired': '移行が必要です', + 'retry': '再試行', + 'mode.external': '外部タスク', + 'mode.oneShot': '単発', + 'mode.continuable': '継続可能', + 'mode.unknown': '不明なモード', + 'readonly.unknown.body': '子セッションを確認して、継続できるかどうかを判断してください。', + 'activity.running': '実行中', + 'activity.completed': '完了', + 'activity.inactive': '停止中', + 'branch.collapse': '{label}の子孫を折りたたむ', + 'branch.expand': '{label}の子孫を展開', + 'count.total.one': 'サブエージェント{count}件', + 'count.total.other': 'サブエージェント{count}件', + 'count.running.one': '実行中のサブエージェント{count}件', + 'count.running.other': '実行中のサブエージェント{count}件', + 'switcher.aria': 'サブエージェントを切り替え: {title}', + 'tree.aria': 'サブエージェントセッション', + 'open.sidebar': 'サイドバーで開く', + 'open.sidebar.aria': '{label}をサイドバーで開く', + 'sidebar.chat': 'チャット', + 'readonly.oneShot.title': '単発サブエージェントの記録', + 'readonly.title': 'このサブエージェントは現在読み取り専用です', + 'readonly.oneShot.body': '単発タスクは追加のメッセージを受け付けません。ここで実行記録の全体を確認できます。', + 'readonly.body': '親セッションがオフラインです。再度開いてメッセージの送信を続けてください。', +} satisfies Record diff --git a/packages/client/ui-theme/src/client/index.ts b/packages/client/ui-theme/src/client/index.ts index f37bb34f4b..c8977a8df3 100644 --- a/packages/client/ui-theme/src/client/index.ts +++ b/packages/client/ui-theme/src/client/index.ts @@ -25,7 +25,7 @@ import type { FontSettingsInjected } from './FontSettingsGroup.tsx' import { FontSettingsGroup } from './FontSettingsGroup.tsx' import { createAppearanceRowStore, createFontRowStore } from './settings-store.ts' import { installThemeStyles } from './styles.ts' -import { en, zh, type ThemeKey } from './locales.ts' +import { en, ja, zh, type ThemeKey } from './locales.ts' import { DEFAULT_FONT_SIZES, DEFAULT_PREFERENCE, FONT_FAMILY_FIELDS, FONT_ROLES, FONT_SIZE_SPECS, fontFamilyVariable, isFontSize, isThemePreference, normalizeFontFamily, sectionFontSizes, @@ -544,7 +544,8 @@ export const inject = ['slots', 'locale', 'remote', 'configForms'] */ export function apply(ctx: ClientContext): void { installThemeStyles(ctx) - ctx.effect(() => ctx.locale.register(SETTINGS_NS, { zh, en }), 'ui-theme: settings row dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(SETTINGS_NS, dictionaries), 'ui-theme: settings row dictionaries') const host = ctx.configForms.get(THEME_SETTINGS_NAMESPACE) const theme = new ThemeRuntime(ctx, host) ctx.provide('theme', theme) diff --git a/packages/client/ui-theme/src/client/locales.ts b/packages/client/ui-theme/src/client/locales.ts index 7f993c05af..b34cab3e2b 100644 --- a/packages/client/ui-theme/src/client/locales.ts +++ b/packages/client/ui-theme/src/client/locales.ts @@ -60,3 +60,32 @@ export const en = { 'fontFamily.placeholder': 'Default', 'fontSize.more': 'More font settings', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'appearance.title': '外観', + 'appearance.light': 'ライト', + 'appearance.dark': 'ダーク', + 'appearance.system': 'システム', + 'fontSize.text.title': 'フォントサイズ', + 'fontSize.text.description': '会話コンテンツにのみ適用', + 'fontSize.text.increase': 'フォントサイズを大きくする', + 'fontSize.text.decrease': 'フォントサイズを小さくする', + 'fontSize.code.title': 'コードのフォントサイズ', + 'fontSize.code.description': 'コードブロック、インラインコード、ツール出力に使用', + 'fontSize.code.increase': 'コードのフォントサイズを大きくする', + 'fontSize.code.decrease': 'コードのフォントサイズを小さくする', + 'fontSize.terminal.title': 'ターミナルのフォントサイズ', + 'fontSize.terminal.description': 'サイドバーのターミナルに使用', + 'fontSize.terminal.increase': 'ターミナルのフォントサイズを大きくする', + 'fontSize.terminal.decrease': 'ターミナルのフォントサイズを小さくする', + 'fontSize.unit': 'px', + 'fontFamily.text.title': 'テキストフォント', + 'fontFamily.text.description': 'インターフェースと会話テキストに使用します。フォントはカンマで区切ります。空欄の場合はデフォルトを使用', + 'fontFamily.code.title': 'コードフォント', + 'fontFamily.code.description': 'コードブロック、インラインコード、ツール出力に使用します。フォントはカンマで区切ります。空欄の場合はデフォルトを使用', + 'fontFamily.terminal.title': 'ターミナルフォント', + 'fontFamily.terminal.description': 'サイドバーのターミナルに使用します。フォントはカンマで区切ります。空欄の場合はデフォルトを使用', + 'fontFamily.placeholder': 'デフォルト', + 'fontSize.more': 'その他のフォント設定', +} satisfies Record diff --git a/packages/client/ui-trajectory/src/client/index.ts b/packages/client/ui-trajectory/src/client/index.ts index 0b438d1aae..254f426ebc 100644 --- a/packages/client/ui-trajectory/src/client/index.ts +++ b/packages/client/ui-trajectory/src/client/index.ts @@ -16,7 +16,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' import type {} from '@deepseek-ai/dsh-client-ui-session/client' import { createTrajectoryDurationStore } from './duration-store.ts' import { createTrajectoryStringWrappingStore } from './string-wrapping-store.ts' -import { en, NS, zh } from './locales.ts' +import { en, ja, NS, zh } from './locales.ts' import { registerTrajectoryAssistantDefinition } from './trajectory-assistant-definition.ts' import { registerTrajectoryCompactionDefinitions } from './trajectory-compaction-definition.ts' import { registerTrajectoryMessageDefinitions } from './trajectory-message-definitions.ts' @@ -59,7 +59,8 @@ export function apply(ctx: Context): void { } return source } - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-trajectory: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-trajectory: dictionaries') // Registration-time text (the view tab label) reads through the bound // translate as a thunk, so it follows the active locale without // re-registration. diff --git a/packages/client/ui-trajectory/src/client/locales.ts b/packages/client/ui-trajectory/src/client/locales.ts index b13a14662a..3dc996d556 100644 --- a/packages/client/ui-trajectory/src/client/locales.ts +++ b/packages/client/ui-trajectory/src/client/locales.ts @@ -408,3 +408,199 @@ export const en: Record = { 'layout.systemPromptAndToolsUpdated': 'System Prompt and Tools Updated', 'layout.compactionInterrupted': 'Compaction was interrupted before completion.', } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'view.trajectory': '軌跡', + 'toolbar.aria': '軌跡ツールバー', + 'toolbar.duration': '所要時間', + 'toolbar.useActualDuration': '実際の所要時間を使用', + 'toolbar.useEqualWidth': '等幅の操作を使用', + 'toolbar.actualTime': '実際の時間', + 'toolbar.turns': 'ターン', + 'toolbar.expandTurns': 'ターンを展開', + 'toolbar.collapseTurns': 'ターンを折りたたむ', + 'toolbar.calls': '呼び出し', + 'toolbar.expandCalls': '呼び出しを展開', + 'toolbar.collapseCalls': '呼び出しを折りたたむ', + 'toolbar.search': '軌跡を検索', + 'toolbar.searchPlaceholder': '検索', + 'kind.system': 'システム', + 'kind.user': 'ユーザー', + 'kind.context': 'コンテキスト', + 'kind.compacted': '圧縮済み', + 'kind.message': 'メッセージ', + 'kind.assistant': 'アシスタント', + 'kind.tool': 'ツール', + 'kind.subtool': 'サブツール', + 'kind.sub': 'サブ', + 'column.input': '入力', + 'column.output': '出力', + 'column.think': '思考', + 'column.time': '時間', + 'column.model': 'モデル', + 'column.tools': 'ツール', + 'turn.label': 'ターン {turn}', + 'section.betweenTurns': 'ターン間', + 'group.message': 'メッセージ', + 'group.step': 'ステップ {step}', + 'group.compaction': '圧縮 {seq}', + 'status.failed': '失敗', + 'status.pending': '保留中', + 'status.completed': '完了', + 'timing.notAvailable': '利用不可', + 'timing.notRecorded': '未記録', + 'timing.stepStartUnavailable': 'ステップ開始時刻は利用不可', + 'timing.firstTokenUnavailable': '最初のトークンは利用不可', + 'timing.usageUnavailable': '使用状況は利用不可', + 'timing.outputTokensUnavailable': '出力トークンは利用不可', + 'timing.durationTooShort': '所要時間が短すぎます', + 'timing.showLocalTime': 'ローカル時刻を表示', + 'timing.showUnixTimestamp': 'Unixタイムスタンプを表示', + 'timing.started': '開始', + 'timing.totalDuration': '合計所要時間', + 'timing.ttft': 'TTFT', + 'timing.generation': '生成', + 'timing.throughput': 'スループット', + 'timing.duration': '所要時間', + 'timing.source': '計測ソース', + 'timing.sessionTimestamps': 'セッションのタイムスタンプ', + 'timing.sessionTimestampsRunning': 'セッションのタイムスタンプ(実行中)', + 'timing.request': 'リクエストのタイミング', + 'unit.milliseconds': '{value} ms', + 'unit.seconds': '{value} 秒', + 'unit.tokens': '{value} tok', + 'unit.tokensPerSecond': '{value} tok/s', + 'usage.tokens': 'トークン', + 'usage.reasoning': '推論', + 'usage.content': 'コンテンツ', + 'usage.notReported': '使用量は報告されていません', + 'usage.input': '入力', + 'usage.cached': 'キャッシュ済み', + 'usage.cacheCreated': 'キャッシュ作成', + 'usage.other': 'その他', + 'usage.output': '出力', + 'usage.thisRequest': '今回のリクエスト', + 'usage.sessionCumulative': 'セッション累計', + 'options.notRecorded': 'オプションは記録されていません', + 'options.json': 'リクエストオプション JSON', + 'source.unknown': '不明', + 'source.user': 'ユーザー', + 'source.plugin': 'プラグイン', + 'source.pluginNamed': 'プラグイン · {plugin}', + 'source.goal': '目標', + 'source.goalRound': '目標 · ラウンド {round}', + 'source.notRecorded': 'ソースは記録されていません', + 'source.messageJson': 'メッセージソース JSON', + 'tab.summary': '概要', + 'tab.rawOutput': '生の出力', + 'tab.preview': 'プレビュー', + 'tab.raw': '生データ', + 'tab.source': 'ソース', + 'tab.payload': 'ペイロード', + 'tab.result': '結果', + 'tab.schema': 'スキーマ', + 'tab.timing': 'タイミング', + 'tab.diff': '差分', + 'tab.systemPrompt': 'システムプロンプト', + 'tab.tools': 'ツール', + 'tab.options': 'オプション', + 'tab.usage': '使用量', + 'record.toolCallOnly': '(ツール呼び出しのみ)', + 'record.noContent': 'コンテンツなし', + 'record.noPayload': 'ペイロードは取得されていません', + 'record.noResult': '結果は取得されていません', + 'record.noOutput': '出力なし', + 'record.schemaUnavailable': 'スキーマを利用できません', + 'record.parameters': 'パラメータ', + 'record.resultJson': '結果 JSON', + 'record.json': 'JSON', + 'record.parametersJson': 'パラメータ JSON', + 'record.namedParametersJson': '{name} パラメータ JSON', + 'record.payloadJson': 'ペイロード JSON', + 'record.outputJson': '結果 JSON', + 'record.thinking': '思考', + 'record.wrapLines': '行を折り返す', + 'code.source': 'コード', + 'code.output': '出力', + 'code.copySource': 'コードをコピー', + 'code.copyOutput': '出力をコピー', + 'code.originalJson': '元の JSON', + 'code.running': '実行中…', + 'record.systemPromptMissing': 'このリクエストにシステムプロンプトはありません', + 'record.toolsMissing': 'このリクエストにツールはありません', + 'record.systemPrompt': 'システムプロンプト', + 'record.tools': 'ツール', + 'block.openSummary': 'ブロック #{index} のツール呼び出し概要を開く', + 'block.openSummaryTitle': 'ツール呼び出し概要を開く', + 'block.label': 'ブロック #{index} {type}', + 'history.loadingTrajectory': '軌跡を読み込み中…', + 'history.loadingEarlier': '以前の履歴を読み込み中…', + 'history.loadingEarlierAria': '以前の履歴を読み込み中…', + 'history.loadEarlier': '以前の履歴を読み込む', + 'history.clickToLoadEarlier': 'クリックして以前の履歴を読み込む', + 'request.label': 'リクエスト #{request}', + 'request.labelCompaction': 'リクエスト #{request} · コンパクション', + 'request.compaction': 'コンパクション · {section}', + 'request.compactionPurpose': 'コンパクション', + 'request.retryProgress': '{retry} / {maximum}', + 'request.collapsedSummary': '折りたたまれた {kind} の概要、{summary}', + 'request.collapsedTurn': 'ターン', + 'request.collapsedAssistant': 'アシスタント', + 'request.rowAria': '{request}{kind}、{content}', + 'request.rowPrefix': 'リクエスト {request}、', + 'request.rowAriaCompaction': 'リクエスト {request}、コンパクション', + 'request.noContent': 'コンテンツなし', + 'summary.toolCalls.one': 'ツール呼び出し {count} 件', + 'summary.toolCalls.other': 'ツール呼び出し {count} 件', + 'summary.steps.one': 'ステップ {count} 件', + 'summary.steps.other': 'ステップ {count} 件', + 'details.event': 'イベントの詳細', + 'details.resize': 'イベント詳細のサイズ変更', + 'details.resizeTitle': 'ドラッグでサイズ変更。ダブルクリックでリセット。', + 'details.close': '詳細を閉じる', + 'details.status': 'ステータス', + 'details.purpose': '目的', + 'details.provider': 'プロバイダー', + 'details.model': 'モデル', + 'details.toolCalls': 'ツール呼び出し', + 'details.subtoolCalls': 'サブツール呼び出し', + 'details.error': 'エラー', + 'details.lastAttemptError': '前回の試行エラー', + 'details.failure.auth': 'API キーが無効です', + 'details.retry': '再試行', + 'details.retryDelay': '再試行までの遅延', + 'details.result': '結果', + 'details.compacted': '圧縮済み', + 'details.assistantMessage': 'アシスタントメッセージ', + 'details.source': 'ソース', + 'details.hierarchy': '階層', + 'details.toolCall': 'ツール呼び出し', + 'timeline.aria': '軌跡タイムライン', + 'timeline.overviewAria': 'タイムラインの概要。横方向にドラッグしてイベントにフォーカス', + 'timeline.noTimingData': 'タイミングデータなし', + 'timeline.total': '合計 {duration}', + 'timeline.started': '開始 {time}', + 'timeline.ttftDecoding': 'TTFT {ttft} · デコード {decoding}', + 'layout.compacting': 'コンテキストを圧縮中…', + 'layout.compactionFailed': '圧縮に失敗しました', + 'layout.compacted': 'コンテキストを圧縮しました', + 'layout.toolCallOnly': 'ツール呼び出しのみ', + 'attachment.list': '添付ファイル', + 'attachment.imageName': '画像 {index}', + 'layout.imageCount': '画像 ×{count}', + 'layout.fileAttachments': 'ファイル ×{count}', + 'layout.initialSystemPrompt': '初期システムプロンプト', + 'layout.systemPromptUpdated': 'システムプロンプトを更新', + 'layout.toolsUpdated': 'ツールを更新', + 'layout.toolAdded': 'ツールを追加: {name}', + 'layout.toolRemoved': 'ツールを削除: {name}', + 'layout.toolUpdateNotice': 'ツールを更新しました', + 'layout.toolsAdded': '追加: {names}', + 'layout.toolsAddedCount': '{count} 件追加', + 'layout.toolsChanged': '{added} 件追加、{removed} 件削除', + 'layout.toolsRemoved': '削除: {names}', + 'layout.toolsRemovedCount': '{count} 件削除', + 'layout.systemPromptAndToolsUpdated': 'システムプロンプトとツールを更新', + 'layout.compactionInterrupted': '圧縮は完了前に中断されました。', +} satisfies Record diff --git a/packages/client/ui-user-questions/src/client/index.ts b/packages/client/ui-user-questions/src/client/index.ts index 8ee3962c39..5352edb540 100644 --- a/packages/client/ui-user-questions/src/client/index.ts +++ b/packages/client/ui-user-questions/src/client/index.ts @@ -30,7 +30,7 @@ import { createQuestionDraftStore } from './draft-store.ts' import { QuestionComposer } from './QuestionComposer.tsx' import { questionReplyDefinition } from './question-reply.ts' import { QuestionReplyView } from './QuestionReplyView.tsx' -import { en, zh, type QuestionKey } from './locales.ts' +import { en, ja, zh, type QuestionKey } from './locales.ts' export type { PendingQuestion, PlanReview, QuestionAnswer, QuestionComposerProps, QuestionWait, @@ -366,7 +366,8 @@ function publishContinuedQuestions(ctx: ClientContext, cards: QuestionCards): () * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-user-questions: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-user-questions: dictionaries') const questionDraftStore = createQuestionDraftStore() const registerPendingInteraction = ctx.uiSession.registerPendingInteraction( pending => pending.kind === 'plan-review' ? 2 : 1, diff --git a/packages/client/ui-user-questions/src/client/locales.ts b/packages/client/ui-user-questions/src/client/locales.ts index d45fd56b4a..95fa1c3560 100644 --- a/packages/client/ui-user-questions/src/client/locales.ts +++ b/packages/client/ui-user-questions/src/client/locales.ts @@ -72,3 +72,38 @@ export const en = { 'plan.decline': 'Refuse', 'plan.discuss': 'Request changes', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'error.incomplete': '先にこの質問に回答してください。', + 'error.unanswered': 'オプションを選択するか、カスタム回答を入力してください。', + 'error.unavailable': '現在送信できません。しばらくしてからもう一度お試しください。', + 'error.resubmit': '作業が続行される前に回答が届きませんでした。もう一度送信してください。', + 'status.sent': '返信は送信されましたが、パネルを閉じられませんでした。', + 'wait.takeTime': '時間をかける', + 'wait.countdown': '{seconds}秒後に続行', + 'wait.paused': '一時停止中 · 残り{seconds}秒', + 'wait.held': '回答するまで待機中', + 'wait.continued': '作業は続行されました — まだ回答できます', + 'review.status': '回答済み', + 'review.skipped': 'この質問はスキップされました。', + 'reply.label': '以前の保留中の質問に返信', + 'reply.open': '質問の詳細を開く', + 'reply.close': '質問の詳細を閉じる', + 'reply.answerLabel': '回答: ', + 'reply.skipped': 'スキップ済み', + 'nav.prev': '前の質問', + 'nav.next': '次の質問', + 'nav.minimize': '質問カードを折りたたむ', + 'nav.maximize': '質問カードを展開', + 'nav.cancel': 'すべての質問を閉じる', + 'nav.close': 'パネルを閉じる — ツール呼び出しから再度開けます', + 'option.recommended': '推奨', + 'custom.placeholder': '回答を入力', + 'action.skip': 'スキップ', + 'action.next': '次へ', + 'plan.header': '計画のレビュー', + 'plan.approve': '承認', + 'plan.decline': '拒否', + 'plan.discuss': '変更を依頼', +} satisfies Record diff --git a/packages/client/ui-workflow-run/src/client/index.ts b/packages/client/ui-workflow-run/src/client/index.ts index 9c0d2b4912..26f28d8670 100644 --- a/packages/client/ui-workflow-run/src/client/index.ts +++ b/packages/client/ui-workflow-run/src/client/index.ts @@ -9,7 +9,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' import type {} from '@deepseek-ai/dsh-client-ui-session/client' import type {} from '@deepseek-ai/dsh-client-ui-workspace/client' import { WorkflowRunPanel, type WorkflowRunInjected } from './WorkflowRunPanel.tsx' -import { en, NS, type WorkflowRunKey, zh } from './locales.ts' +import { en, ja, NS, type WorkflowRunKey, zh } from './locales.ts' import { workflowRunDefinition } from './workflow-definition.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { @@ -25,7 +25,8 @@ export const inject = ['uiConversation', 'uiWorkspace', 'slots', 'sessions', 'lo /** Register the workflow Definition, dictionary, and keyed Chat renderer. */ export function apply(ctx: ClientContext): void { ctx.uiConversation.events.register(workflowRunDefinition) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-workflow-run: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-workflow-run: dictionaries') ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({ name: 'conversation.chat.node', key: 'workflow-run', diff --git a/packages/client/ui-workflow-run/src/client/locales.ts b/packages/client/ui-workflow-run/src/client/locales.ts index 99d8c9691f..8243740ca8 100644 --- a/packages/client/ui-workflow-run/src/client/locales.ts +++ b/packages/client/ui-workflow-run/src/client/locales.ts @@ -49,3 +49,25 @@ export const en: Record = { /** Union of this namespace's dictionary keys. */ export type WorkflowRunKey = keyof typeof zh + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'run.title': '{name}', + 'run.members.one': '{count}人のメンバー', + 'run.members.other': '{count}人のメンバー', + 'run.empty': '開始されたメンバーはいません', + 'phase.unassigned': 'フェーズなし', + 'phase.empty': 'フェーズ名が空です', + 'statusCount.running': '実行中 {count}', + 'statusCount.completed': '完了 {count}', + 'statusCount.failed': '失敗 {count}', + 'statusCount.cancelled': 'キャンセル {count}', + 'statusCount.interrupted': '中断 {count}', + 'member.empty': 'メンバー名が空です', + 'member.open': '{name}を開く', + 'status.running': '実行中', + 'status.completed': '完了', + 'status.failed': '失敗', + 'status.cancelled': 'キャンセル', + 'status.interrupted': '中断', +} satisfies Record diff --git a/packages/client/ui-workspace/src/client/index.ts b/packages/client/ui-workspace/src/client/index.ts index 6588f17784..d68bdc8d9b 100644 --- a/packages/client/ui-workspace/src/client/index.ts +++ b/packages/client/ui-workspace/src/client/index.ts @@ -56,7 +56,7 @@ import { RowActionToast } from './session-actions/RowActionToast.tsx' import { registerToasts } from './toast/register.ts' import { SessionIdMenuItem } from './session-actions/SessionId.tsx' import { WorkspacePicker } from './WorkspacePicker.tsx' -import { en, zh, type WorkspaceKey } from './locales.ts' +import { en, ja, zh, type WorkspaceKey } from './locales.ts' export type { StartSessionOptions, UiWorkspace } from './navigation.ts' export type { @@ -132,7 +132,8 @@ export function apply(ctx: Context): void { : ctx.remoteAccess.source('workspace', workspaceId, action), }, }) - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-workspace: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-workspace: dictionaries') const shortcutControls = createWorkspaceShortcutControls() const searchSessions: WorkspaceBrowserInjected['searchSessions'] = async (query, signal) => { diff --git a/packages/client/ui-workspace/src/client/locales.ts b/packages/client/ui-workspace/src/client/locales.ts index 0214ab995f..938b893b40 100644 --- a/packages/client/ui-workspace/src/client/locales.ts +++ b/packages/client/ui-workspace/src/client/locales.ts @@ -252,3 +252,126 @@ export const en = { 'time.years': '{n}y', 'time.ago': '{t} ago', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'defaultWorkspace.failed': 'デフォルトのワークスペースを作成できません。ワークスペースを選択してフォルダーを選んでください。', + 'draft.workspaceRequired': '先にワークスペースを選択してください。', + 'draft.initializationFailed': '下書きを埋め込めませんでした。しばらくしてからもう一度お試しください。', + 'group.ungrouped': 'グループなし', + 'session.new': '新しいセッション', + 'session.untitled': '無題', + 'shortcut.noSession': '先にセッションを選択してください', + 'shortcut.noPicker': 'ディレクトリピッカーを利用できません', + 'shortcut.directoryBusy': 'ワークスペースを選択または追加しています', + 'shortcut.noCompletedTurn': 'このセッションには完了したターンがありません', + 'shortcut.forkFailed': 'セッションをフォークできませんでした。もう一度お試しください。', + 'section.workspaces': 'ワークスペース', + 'section.sessions': 'セッション', + 'viewOptions.label': '表示オプション', + 'groupBy.label': 'グループ化', + 'groupBy.workspace': 'ワークスペース', + 'groupBy.workspaceTree': 'ワークスペースツリー', + 'groupBy.flat': '1つのリスト', + 'orderBy.label': '並び順', + 'orderBy.manual': '手動', + 'orderBy.updated': '最終更新', + 'filterBy.label': 'セッションを絞り込む', + 'viewOptions.hideArchived': 'アーカイブを非表示', + 'viewOptions.showArchived': 'すべての会話(アーカイブを表示)', + 'viewOptions.onlyArchived': 'アーカイブのみ', + 'sessions.expand': 'さらに{n}件のセッションを表示', + 'sessions.collapse': '折りたたむ', + 'empty.none': 'セッションはまだありません', + 'empty.noneArchived': 'アーカイブされたセッションはまだありません', + 'empty.viewOthers': '他のセッションを表示', + 'empty.noMatches': '一致なし', + 'workspace.add': 'ワークスペースを追加', + 'search.sessions.aria': 'セッションを検索', + 'search.placeholder': 'セッション名を検索', + 'search.clear': '検索をクリア', + 'search.results.aria': '検索結果', + 'search.pending': 'セッション履歴を検索中…', + 'search.noMatches': '一致するセッションはありません', + 'search.hasMore': '最初の{n}件の結果を表示しています。検索を絞り込んでください。', + 'menu.addWorkspace': 'ワークスペースを追加…', + 'picker.loading': 'ワークスペースを読み込み中…', + 'conflict.named': '「{name}」という名前のワークスペースは既に存在します。', + 'folderError.title': 'フォルダーを開けませんでした', + 'folderError.retry': '選び直す', + 'rename': '名前を変更', + 'rename.workspace.title': 'ワークスペース名を変更', + 'rename.session.title': 'セッション名を変更', + 'field.workspaceName': 'ワークスペース名', + 'field.sessionName': 'セッション名', + 'delete.workspace': 'ワークスペースを削除', + 'delete.desc': '「{name}」をワークスペース一覧から削除します。フォルダとセッションログは保持されます。このワークスペースのセッションは「未分類」に表示されます。', + 'delete.pending': 'ワークスペースを削除中…', + 'menu.fork': 'セッションをフォーク', + 'menu.archiveSession': 'セッションをアーカイブ', + 'menu.unarchiveSession': 'セッションのアーカイブを解除', + 'menu.pinSession': 'セッションをピン留め', + 'menu.unpinSession': 'セッションのピン留めを解除', + 'menu.sessionId': 'セッションIDをコピー', + 'toast.sessionIdCopied': 'セッションIDをコピーしました', + 'toast.sessionIdCopyFailed': 'コピーに失敗しました。もう一度お試しください。', + 'row.archived': 'アーカイブ済み', + 'row.pinned': 'ピン留め済み', + 'toast.archivedNotOpenable': 'アーカイブ済みセッションは開けません。アーカイブを解除して表示してください。', + 'toast.archived': 'セッションをアーカイブしました。', + 'toast.stoppedAndArchived': 'セッションを停止してアーカイブしました。', + 'archive.confirm.title': 'このセッションを停止してアーカイブしますか?', + 'archive.confirm.desc': '「{title}」には進行中の作業があります。アーカイブするとまず停止されます。セッションは後からサイドバーの「すべての会話(アーカイブを表示)」フィルターで復元できますが、停止された作業は自動的に再開されません。', + 'archive.confirm.activity': '停止される作業', + 'archive.confirm.turn': '進行中のターン', + 'archive.confirm.subagents.one': '実行中のサブエージェント {n} 件: {names}', + 'archive.confirm.subagents.other': '実行中のサブエージェント {n} 件: {names}', + 'archive.confirm.jobs.one': 'バックグラウンドジョブ {n} 件: {names}', + 'archive.confirm.jobs.other': 'バックグラウンドジョブ {n} 件: {names}', + 'archive.confirm.schedules.one': 'スケジュールされたリマインダー {n} 件: {names}', + 'archive.confirm.schedules.other': 'スケジュールされたリマインダー {n} 件: {names}', + 'archive.confirm.other.one': 'その他の作業 {n} 件({kind})', + 'archive.confirm.other.other': 'その他の作業 {n} 件({kind})', + 'archive.confirm.listSeparator': '、', + 'archive.confirm.action': '停止してアーカイブ', + 'archive.confirm.pending': '停止してアーカイブ中…', + 'toast.archivedUndo': '元に戻す', + 'toast.archivedOr': ' または ', + 'toast.archivedFilter': 'アーカイブ済みセッションを絞り込む', + 'toast.pinFailed': 'ピン留めに失敗しました。後でもう一度お試しください。', + 'toast.unpinFailed': 'ピン留めの解除に失敗しました。後でもう一度お試しください。', + 'toast.createFailed': '新しいセッションの作成に失敗しました: {message}', + 'toast.forkRequiresOpen': 'ブランチを作成する前に、移行を完了するため元のセッションを開いてください。', + 'toast.openForkSource': '元のセッションを開く', + 'sessions.count.one': 'セッション {n} 件', + 'sessions.count.other': 'セッション {n} 件', + 'actions.workspace.aria': '{name} のワークスペース操作', + 'actions.session.aria': '{name} のセッション操作', + 'actions.archive': 'アーカイブ', + 'actions.unarchive': 'アーカイブを解除', + 'actions.pin': 'ピン留め', + 'actions.unpin': 'ピン留めを解除', + 'actions.newSession': '新しいセッション', + 'actions.newSession.aria': '{name} で新しいセッション', + 'status.running': '実行中', + 'status.subagentsRunning.one': 'サブエージェント {n} 件が実行中', + 'status.subagentsRunning.other': 'サブエージェント {n} 件が実行中', + 'status.idle': '待機中', + 'status.waitingApproval': '承認待ち', + 'status.planReview': '計画のレビュー待ち', + 'status.waitingAnswer': '回答待ち', + 'status.compact.approval': '承認', + 'status.compact.planReview': '計画レビュー', + 'status.compact.answer': '回答', + 'status.completed': '完了', + 'hover.created': '作成日時 {time}', + 'hover.copied': 'コピーしました', + 'date.ymd': '{y}-{m}-{d}', + 'time.now': '今', + 'time.minutes': '{n}分', + 'time.hours': '{n}時間', + 'time.days': '{n}日', + 'time.months': '{n}か月', + 'time.years': '{n}年', + 'time.ago': '{t}前', +} satisfies Record diff --git a/packages/experimental/client-ui-agent-team/src/client/locales.ts b/packages/experimental/client-ui-agent-team/src/client/locales.ts index fe4b853095..07f5f88b5e 100644 --- a/packages/experimental/client-ui-agent-team/src/client/locales.ts +++ b/packages/experimental/client-ui-agent-team/src/client/locales.ts @@ -63,3 +63,32 @@ export const en = { 'status.in_progress': 'In progress', 'status.completed': 'Completed', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'trigger': 'エージェントチーム', + 'loading': 'チームを読み込み中…', + 'unavailable': 'チームは利用できません', + 'failure': '保存されたチームレコードが無効です: {message}', + 'empty': '共有タスクはまだありません。会話から作成してください。', + 'roster': 'メンバー', + 'tasks': '共有タスク', + 'model': 'モデル', + 'open': 'メンバーの会話を開く', + 'current': '現在のチャット', + 'owner': 'オーナー', + 'unowned': 'オーナーなし', + 'blockedBy': 'ブロック元', + 'writeScopes': '書き込みスコープ', + 'ready': '準備完了', + 'blocked': '依存関係によりブロック中', + 'task.expand': 'もっと見る', + 'task.collapse': '折りたたむ', + 'memberStatus.running': '実行中', + 'memberStatus.inactive': '非アクティブ', + 'memberStatus.provisioning': 'プロビジョニング中', + 'memberStatus.failed': '失敗', + 'status.pending': '保留中', + 'status.in_progress': '進行中', + 'status.completed': '完了', +} satisfies Record diff --git a/packages/experimental/client-ui-agent-team/src/client/mount.ts b/packages/experimental/client-ui-agent-team/src/client/mount.ts index 94a369df04..673c95e21f 100644 --- a/packages/experimental/client-ui-agent-team/src/client/mount.ts +++ b/packages/experimental/client-ui-agent-team/src/client/mount.ts @@ -8,7 +8,7 @@ import type {} from '@deepseek-ai/dsh-client-locale/client' import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' import type {} from '@deepseek-ai/dsh-client-ui-workspace/client' import { TeamAction, type TeamActionInjected } from './TeamAction.tsx' -import { en, NS, zh, type TeamKey } from './locales.ts' +import { en, ja, NS, zh, type TeamKey } from './locales.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { @@ -27,7 +27,8 @@ export const inject = ['sessions', 'uiWorkspace', 'slots', 'locale'] * @param ctx - Client Context carrying the injected navigation, locale, slot, and Session services. */ export function registerAgentTeamUi(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'client-ui-agent-team: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'client-ui-agent-team: dictionaries') const sessions = ctx.sessions const leadSessionId = (sessionId: SessionId): SessionId => { const address = sessions.binding(sessionId)?.session.getSnapshot().subagent?.address diff --git a/packages/experimental/client-ui-claude-code-mods/src/client/locales.ts b/packages/experimental/client-ui-claude-code-mods/src/client/locales.ts index 67557bc81d..62e530c87e 100644 --- a/packages/experimental/client-ui-claude-code-mods/src/client/locales.ts +++ b/packages/experimental/client-ui-claude-code-mods/src/client/locales.ts @@ -21,3 +21,11 @@ export const en = { 'press.failed': 'The button failed: {message}', 'press.stale': 'That button belonged to an earlier drawing; the band has refreshed', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'band': 'Claude Code の改造', + 'pressing': '実行中…', + 'press.failed': 'ボタンが失敗しました: {message}', + 'press.stale': 'そのボタンは以前の描画のものです。バンドは更新されました', +} satisfies Record diff --git a/packages/experimental/client-ui-claude-code-mods/src/client/mount.ts b/packages/experimental/client-ui-claude-code-mods/src/client/mount.ts index 5485336a60..f774329825 100644 --- a/packages/experimental/client-ui-claude-code-mods/src/client/mount.ts +++ b/packages/experimental/client-ui-claude-code-mods/src/client/mount.ts @@ -11,7 +11,7 @@ import type { SurfaceSnapshot } from '@deepseek-ai/dsh-experimental-claude-code- import type { SessionId } from '@deepseek-ai/dsh-session/types' import type { TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol' import { Band, type BandInjected } from './Band.tsx' -import { en, NS, zh, type ModsBandKey } from './locales.ts' +import { en, ja, NS, zh, type ModsBandKey } from './locales.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { @@ -75,7 +75,8 @@ function watchSession(ctx: Context, sessionId: SessionId, onEnd: () => void): { } function registerUi(ctx: Context): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'client-ui-claude-code-mods: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'client-ui-claude-code-mods: dictionaries') const watches = new Map>() ctx.effect(() => () => { for (const watch of watches.values()) watch.dispose() diff --git a/packages/experimental/client-ui-cot-translation/src/client/locales.ts b/packages/experimental/client-ui-cot-translation/src/client/locales.ts index 88cc907e32..937efb98f4 100644 --- a/packages/experimental/client-ui-cot-translation/src/client/locales.ts +++ b/packages/experimental/client-ui-cot-translation/src/client/locales.ts @@ -51,3 +51,39 @@ export const zh: Record = { export function formLabels(t: (key: CotTranslationKey) => string): SettingsFormLabels { return { unavailable: t('unavailable'), readOnly: t('readOnly'), saveFailed: t('saveFailed'), save: t('save'), saving: t('saving') } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'provider': '翻訳サービス', + 'google': 'Google', + 'bing': 'Bing', + 'deepseekAccount': 'DeepSeek Flash — アカウント (有料)', + 'deepseekOfficial': 'DeepSeek Flash — 公式 API (有料)', + 'providerUnavailableOption': '{provider} — 利用不可', + 'providerUnavailable': 'このプロバイダーは利用できません。DeepSeek アカウントまたは公式 API プロバイダーを設定するか、利用可能なサービスを選択してください。', + 'paidNotice': 'キャッシュされていないフラグメントごとに、選択したアカウントまたは公式 API の認証情報を通じて、個別の有料 DeepSeek Flash リクエストが送信されます。推論を再度開くと保存済みの結果が再利用されますが、プロバイダー、言語、ソーステキスト、またはリクエスト設定を変更すると、新たな有料リクエストが送信される場合があります。', + 'anonymousNotice': 'Bing と Google は翻訳のログインや API キーが不要です。これらの匿名エンドポイントはリクエストを拒否したり制限したりする場合があります。', + 'targetLanguage': '翻訳先の言語', + 'targetLanguageHint': 'auto は UI の言語に従います。zh、en、ja などの言語コードも使用できます。', + 'privacy': '展開された推論は選択したサービスに送信されます。非公開のコードや会話の詳細が含まれている可能性があります。', + 'translation': '翻訳を表示', + 'original': '原文を表示', + 'retry': '再試行', + 'failed': '翻訳に失敗しました。原文を表示しています。', + 'translating': '推論を翻訳中', + 'invalidProvider': '翻訳サービスを選択してください。', + 'invalidLanguage': 'auto または zh、en、ja などの言語コードを入力してください。', + 'overridden': '上書き済み', + 'reset': 'デフォルトに戻す', + 'unavailable': '翻訳設定を利用できません。', + 'readOnly': 'このデプロイでは設定は読み取り専用で保存されます。', + 'saveFailed': 'このデプロイでは値が受け入れられませんでした。修正のために値はそのまま残されています。', + 'save': '保存', + 'saving': '保存中…', + 'copy': 'コピー', + 'copied': 'コピーしました', + 'codeTitle': 'コードブロック', + 'wrap': '折り返し', + 'unwrap': '折り返しを解除', + 'footnotes': '脚注', +} satisfies Record diff --git a/packages/experimental/client-ui-cot-translation/src/client/mount.ts b/packages/experimental/client-ui-cot-translation/src/client/mount.ts index c904b1788e..0e73fb1416 100644 --- a/packages/experimental/client-ui-cot-translation/src/client/mount.ts +++ b/packages/experimental/client-ui-cot-translation/src/client/mount.ts @@ -14,7 +14,7 @@ import type { CotTranslationPreferences, CotTranslationSnapshot } from '../prefe import { TranslationBody, type TranslationBodyInjected } from './TranslationBody.tsx' import { TranslationSettings } from './TranslationSettings.tsx' import { TranslationPreferencesForm } from './preferences-form.ts' -import { en, NS, zh } from './locales.ts' +import { en, ja, NS, zh } from './locales.ts' /** Services required to mount the optional Remote and UI contributions. */ export const inject = ['remote', 'slots', 'locale', 'configForms'] @@ -80,7 +80,8 @@ async function registerUi(ctx: Context, lifetime: AbortController): Promise('cot-translation') ctx.effect(() => scope.subscribe(invalidate)) - ctx.effect(() => ctx.locale.register(NS, { en, zh })) + const dictionaries = { en, zh, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries)) const form = new TranslationPreferencesForm(scope) ctx.effect(() => () => { form.dispose() }) const injected = (sessionId: SessionId): TranslationBodyInjected => ({ diff --git a/packages/experimental/client-ui-voice-input/src/client/locales.ts b/packages/experimental/client-ui-voice-input/src/client/locales.ts index 19e3a9513d..e196e86d60 100644 --- a/packages/experimental/client-ui-voice-input/src/client/locales.ts +++ b/packages/experimental/client-ui-voice-input/src/client/locales.ts @@ -214,3 +214,116 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { 'voice-input': VoiceKey } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'dictate': 'ディクテーション', + 'setupPrompt.title': '録音前に音声入力を設定してください', + 'setupPrompt.body': '音声入力が有効です。初回利用時はローカル認識モデルのダウンロードと準備が必要です。プラグインの詳細を開き、セットアップを開始する前にディスク容量、メモリ、所要時間の目安を確認してください。', + 'setupPrompt.later': '後で', + 'setupPrompt.open': 'セットアップへ', + 'setupPrompt.trigger': '音声入力のセットアップを開く', + 'setupPrompt.details': '音声プラグイン設定を開く', + 'setupPrompt.unavailableTitle': '音声認識の準備ができていません', + 'setupPrompt.unavailableBody': '音声プラグインの詳細を開き、認識状態、準備の進捗、エラーを確認してください。', + 'setup.local': 'ローカルモデルはDSHを実行しているマシンにダウンロードされます。Pythonやコンパイラは不要です。', + 'setup.disk': 'ディスク容量', + 'setup.diskValue': 'モデル、ランタイム、ダウンロードキャッシュに約{gb} GBを確保してください', + 'setup.memory': 'メモリ', + 'setup.memoryValue': 'モデル読み込み時で約{gb} GB。認識処理ではさらに使用する場合があります', + 'setup.time': '初回セットアップ', + 'setup.timeValue': 'ネットワークとマシンに応じて、おおよそ{min}〜{max}分かかります', + 'setup.estimateNote': 'これらは目安です。ネットワークが遅い場合はさらに時間がかかることがあります。ダウンロード済みデータは再利用され、アイドル状態のモデルはデフォルトでメモリを解放します。', + 'step.check': 'ローカルリソースを確認', + 'step.model': '認識モデルを準備', + 'step.vad': '音声検出モデルを準備', + 'step.verify': 'モデルファイルを検証', + 'step.load': '{name}を読み込み', + 'stepStatus.pending': '未開始', + 'stepStatus.running': '進行中', + 'stepStatus.complete': '完了', + 'stepStatus.failed': '失敗', + 'stepStatus.cancelled': 'キャンセル済み', + 'short.downloading': 'ダウンロード中', + 'short.failed': 'セットアップに失敗しました', + 'retryRecording': 'もう一度録音', + 'preparationSteps': '準備手順', + 'prepare': 'ダウンロードして準備', + 'retryPrepare': '準備を再試行', + 'cancelPrepare': '準備をキャンセル', + 'downloadProgress': 'ダウンロード進捗', + 'downloadBytes': 'ダウンロード: {completed} / {total} MB、{percent}%', + 'downloadUnknown': '{completed} MBダウンロード済み', + 'elapsed': '{seconds}秒待機中', + 'preparationFailed': '準備に失敗しました: {message}', + 'download.network': '{resource}をダウンロードできません: ダウンロードサービスへの接続に失敗しました。', + 'download.dns': '{resource}をダウンロードできません: ダウンロード先のアドレスを解決できませんでした。', + 'download.timeout': '{resource}のダウンロードがタイムアウトしました。', + 'download.certificate': '{resource}をダウンロードできません: 証明書の検証に失敗しました。', + 'download.http': '{resource}をダウンロードできません: ダウンロードサービスがHTTP {status}を返しました。', + 'download.integrity': '{resource}のダウンロードが不完全か、検証に失敗しました。', + 'download.storage': '{resource}を保存できません: ディスク容量不足または書き込み権限がありません。', + 'download.unknown': '{resource}を準備できませんでした。', + 'downloadAdvice.network': 'DSHを実行しているマシンがダウンロード元とそのモデルダウンロードサービスに到達できることを確認してください。必要に応じてそのマシンでプロキシを設定してから再試行してください。', + 'downloadAdvice.dns': 'DSHを実行しているマシンのDNSとプロキシ設定を確認してください。ダウンロード元ドメインを解決できることを確認してから再試行してください。', + 'downloadAdvice.timeout': 'DSHを実行しているマシンのネットワークまたはプロキシ接続を確認してから、後で再試行してください。検証済みのダウンロード済みファイルは保持されます。', + 'downloadAdvice.certificate': 'DSHを実行しているマシンのシステム時計、信頼された証明書、プロキシ設定を確認してから再試行してください。', + 'downloadAdvice.http': 'ダウンロード先のアドレスが利用可能で、プロキシがダウンロードサービスに到達できることを確認するか、後で再試行してください。', + 'downloadAdvice.integrity': 'ダウンロードを再試行してください。完了かつ検証済みの他のファイルは保持されます。', + 'downloadAdvice.storage': 'DSHを実行しているマシンのモデルディレクトリの空き容量と書き込み権限を確認してから再試行してください。', + 'downloadAdvice.unknown': 'DSHを実行しているマシンのネットワーク、ディスク容量、モデルディレクトリの権限を確認してから再試行してください。', + 'downloadSource': 'ダウンロード元: {source}', + 'sourceChoice': 'モデルのダウンロード元', + 'sourceAuto': '自動(推奨)', + 'sourceHuggingFace': 'Hugging Face', + 'sourceMirror': 'HF-Mirror(中国ミラー)', + 'sourceAutoHelp': '最初に応答したダウンロード元を優先し、ダウンロードに失敗した場合は別のダウンロード元を試します。', + 'sourceManualHelp': '選択したダウンロード元からのみダウンロードします。検証済みのダウンロード済みファイルは再利用されます。', + 'downloadCode': 'エラーコード: {code}', + 'reconnecting': '音声認識に接続しています…', + 'preparation.unprepared': '初回利用時にローカル認識モデルをダウンロードします。', + 'preparation.checking': 'ローカルリソースを確認しています…', + 'preparation.loading': '{name}を読み込んでいます…', + 'preparation.waking': 'ローカル音声認識を起動中…', + 'preparation.ready': 'ローカル音声認識の準備ができました。', + 'preparation.standby': 'ローカルリソースの準備が完了しました。録音を開始すると認識エンジンが起動します。', + 'preparation.cancelled': '準備をキャンセルしました。完了済みのダウンロードは保持されます。', + 'preparation.cancelling': '準備をキャンセル中…', + 'cloudReady': 'クラウド音声認識の準備ができました。', + 'discard': '文字起こしを破棄', + 'transcribingShort': '文字起こし中…', + 'wakingShort': '起動中…', + 'provider': '認識サービス', + 'language': '認識言語', + 'inputDevice': '入力デバイス', + 'systemMicrophone': 'システムデフォルト', + 'systemMicrophoneNamed': 'システムデフォルト({name})', + 'unnamedSelectedMicrophone': 'マイク', + 'unnamedMicrophone': 'マイク {number}', + 'inputLevel': 'マイク入力レベル', + 'deviceUnavailable': '利用不可', + 'deviceMissing': 'マイクを利用できません。別の入力デバイスを選択してください。', + 'auto': '自動検出', + 'zh': '中国語', + 'en': '英語', + 'yue': '広東語', + 'ja': '日本語', + 'ko': '韓国語', + 'start': '録音を開始', + 'stop': '停止して文字起こし', + 'cancel': 'キャンセル', + 'insert': 'テキストを挿入', + 'loading': '認識サービスを読み込み中…', + 'requesting': '続行するにはマイクへのアクセスを許可してください…', + 'recording': '録音中…', + 'interrupted': '録音が中断されました。もう一度お試しください。', + 'local': '音声はDSHを実行しているマシン上で認識されます。モデルのダウンロードが必要な場合、そのマシンは選択したソースとそのファイルサービスに接続できる必要があります。必要に応じてそのマシンでプロキシを設定してください。', + 'cloud': '音声は選択したクラウドサービスに送信されます。', + 'empty': '音声が認識されませんでした', + 'cancelled': '音声入力をキャンセルしました。', + 'conflict': '下書きが変更されました。文字起こしは保持されており、現在のカーソル位置に挿入できます。', + 'failed': '音声認識に失敗しました: {message}', + 'unavailable': 'このブラウザは音声を録音できません。マイクに対応したブラウザを使用してください。', + 'permission': 'マイクへのアクセスが無効になっています。ブラウザとシステムの設定で許可してください。', + 'tooLarge': '録音がサービスの上限を超えています。短い録音をお試しください。', +} satisfies Record diff --git a/packages/experimental/client-ui-voice-input/src/client/mount.ts b/packages/experimental/client-ui-voice-input/src/client/mount.ts index 7d7c1311fd..0b58060995 100644 --- a/packages/experimental/client-ui-voice-input/src/client/mount.ts +++ b/packages/experimental/client-ui-voice-input/src/client/mount.ts @@ -9,7 +9,7 @@ import type { TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol' import { VoiceInput, type VoiceInputInjected } from './VoiceInput.tsx' import { Recording } from './audio.ts' import { createMicrophoneDeviceStore } from './microphone-device.ts' -import { en, NS, zh } from './locales.ts' +import { en, ja, NS, zh } from './locales.ts' import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client' import { observeReadiness } from './readiness.ts' import { VoicePreparation } from './PreparationCard.tsx' @@ -18,7 +18,8 @@ import { VoiceSetupPrompt } from './VoiceSetupPrompt.tsx' export const inject = ['remote', 'slots', 'locale', 'pluginNavigation'] function registerUi(ctx: Context): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en })) + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries)) const recordings = new Set() const microphone = createMicrophoneDeviceStore() const readiness = observeReadiness(ctx) diff --git a/packages/experimental/cua/src/client/locales.ts b/packages/experimental/cua/src/client/locales.ts index 0702e20597..6757c384de 100644 --- a/packages/experimental/cua/src/client/locales.ts +++ b/packages/experimental/cua/src/client/locales.ts @@ -74,3 +74,57 @@ export const zh: Record = { applicationsFailed: '无法读取应用授权:{message}', iconsFailed: '无法读取应用图标:{message}', failed: '无法更新权限:{message}', queryFailed: '无法检测权限:{message}', } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': 'コンピュータ操作を承認', + 'applicationsTitle': 'Computer Useがアクセスできるアプリを管理', + 'setupTitle': '使用前にComputer Useを設定', + 'setupBody': 'Computer Useは有効です。使用する前にPTCモードに切り替え、アクセシビリティと画面収録の権限を許可してください。セットアップを完了するにはプラグインの詳細を開いてください。', + 'setupQueryFailed': 'Computer Useは有効ですが、システム権限を確認できませんでした。プラグインの詳細を開いてエラーを確認し、再試行してください。', + 'defaultTitle': '新規セッションのデフォルトモード', + 'defaultBody': 'Computer UseはPTCモードでのみ利用できます。', + 'defaultConfirm': 'PTCに設定', + 'defaultSet': 'PTCがデフォルトです', + 'defaultUnavailable': 'デフォルトモードを読み取れませんでした。設定で確認してください。', + 'defaultReadOnly': 'デフォルトモードの設定は読み取り専用です。', + 'defaultNotSaved': 'デフォルトモードは保存されませんでした。もう一度お試しください。', + 'defaultFailed': 'デフォルトモードを変更できませんでした: {message}', + 'persistentTitle': 'PTC実行モード', + 'persistentBody': 'ツール呼び出し間で変数とアプリケーションオブジェクトを保持します。切り替えるとPTCランタイムが再起動します。', + 'persistentConfirm': '永続モードを使用', + 'persistentSet': '永続モードが有効になりました', + 'persistentUnavailable': 'PTC実行モードを読み取れませんでした。設定で確認してください。', + 'persistentReadOnly': 'PTC実行設定は読み取り専用です。', + 'persistentNotSaved': 'PTC実行モードは保存されませんでした。もう一度お試しください。', + 'persistentFailed': 'PTC実行モードを変更できませんでした: {message}', + 'openSetup': '設定を開く', + 'later': '後で', + 'close': '閉じる', + 'accessibility': 'アクセシビリティ', + 'screenRecording': '画面収録', + 'accessibilityUse': 'インターフェースのコントロールを読み取り、クリックし、入力します。', + 'screenRecordingUse': '選択したアプリケーションのウィンドウをキャプチャします。', + 'accessibilityPermission': 'システム設定で、{application}にインターフェースのコントロールの読み取り、クリック、入力を許可してください。', + 'screenRecordingPermission': 'システム設定で、{application}にアプリケーションウィンドウのキャプチャを許可してください。', + 'permissionHint': 'システム設定でアプリケーションを有効にしてください。別のアプリケーションが表示された場合はmacOSのプロンプトに従ってください。', + 'permissionHintUnknown': 'macOSのプロンプトに表示されたアプリケーションにこれらの権限を許可してください。', + 'granted': '許可済み', + 'unknown': '不明', + 'request': '権限をリクエスト', + 'settings': 'システム設定を開く', + 'retry': '再試行', + 'allowedApps': '常に許可するアプリケーション', + 'blockedApps': '許可されていないアプリケーション', + 'retryApproval': '再度確認を許可', + 'retryAllowed': '次回アプリケーションを選択するときに再度確認します。アクセスは許可されていません', + 'noApps': '常に許可またはブロックされたアプリケーションはありません。', + 'revoke': '取り消し', + 'revoked': 'アプリケーションのアクセスを取り消しました', + 'requested': 'macOSのプロンプトを確認するか、プロンプトが表示されない場合はシステム設定を開いてください', + 'opened': 'システム設定を開きました', + 'applicationsFailed': 'アプリケーションのアクセスを読み込めませんでした: {message}', + 'iconsFailed': 'アプリケーションのアイコンを読み込めませんでした: {message}', + 'failed': '権限を更新できませんでした: {message}', + 'queryFailed': '権限を確認できませんでした: {message}', +} satisfies Record diff --git a/packages/experimental/cua/src/client/mount.ts b/packages/experimental/cua/src/client/mount.ts index bc43e9bd8b..8276cc3c5d 100644 --- a/packages/experimental/cua/src/client/mount.ts +++ b/packages/experimental/cua/src/client/mount.ts @@ -10,7 +10,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client' import type {} from '@deepseek-ai/dsh-client-ui-settings/client' import { PermissionController } from './controller.ts' import { PermissionPanel, PermissionSetup, PermissionToast } from './PermissionPanel.tsx' -import { en, NS, zh, type CuaPermissionLocaleKey } from './locales.ts' +import { en, ja, NS, zh, type CuaPermissionLocaleKey } from './locales.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { @@ -31,7 +31,8 @@ export const inject = ['remote', 'slots', 'locale', 'configForms'] export async function mountCuaPermissions(ctx: Context, contribution: TypertRemoteContribution): Promise<() => Promise> { const transport = ctx.plugin(async (owner) => { await owner.remote.$mount(contribution) }) const ui = ctx.inject(['remote.cuaAccess', 'slots', 'locale'], (ctx) => { - ctx.effect(() => ctx.locale.register(NS, { en, zh })) + const dictionaries = { en, zh, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries)) const controller = new PermissionController(ctx.remote.cuaAccess) const face = controller.face() const preset = ctx.configForms.get<{ default: string; selectedDefault?: string }>('agent-preset-registry') diff --git a/packages/experimental/inspector/src/client/bottom/locales.ts b/packages/experimental/inspector/src/client/bottom/locales.ts index e6d6c05711..d9c14710ce 100644 --- a/packages/experimental/inspector/src/client/bottom/locales.ts +++ b/packages/experimental/inspector/src/client/bottom/locales.ts @@ -22,3 +22,12 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { inspectorPanel: keyof typeof zh } } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'title': 'NodeJS インスペクター', + 'toggle': 'NodeJS インスペクターの表示切り替え', + 'close': '折りたたむ', + 'resize': 'NodeJS インスペクターパネルのサイズ変更', + 'frameTitle': 'NodeJS インスペクター', +} satisfies Record diff --git a/packages/experimental/session-inspector/src/client/index.ts b/packages/experimental/session-inspector/src/client/index.ts index a51c8b9b0c..5f11bb69ed 100644 --- a/packages/experimental/session-inspector/src/client/index.ts +++ b/packages/experimental/session-inspector/src/client/index.ts @@ -3,7 +3,7 @@ import type { Context } from '@deepseek-ai/cordis' import type {} from '@deepseek-ai/dsh-client-locale/client' import type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client' -import { en, NS, zh } from './locales.ts' +import { en, ja, NS, zh } from './locales.ts' import { registerInspectorTab } from './views/index.ts' /** Services required to register the localized Session-bound sidebar tab. */ @@ -14,6 +14,7 @@ export const inject = ['slots', 'locale', 'sessions', 'uiSession', 'uiConversati * @param ctx - Client plugin context owning the dictionaries and registrations. */ export function apply(ctx: Context): void { - ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'session-inspector: dictionaries') + const dictionaries = { en, zh, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'session-inspector: dictionaries') registerInspectorTab(ctx) } diff --git a/packages/experimental/session-inspector/src/client/locales.ts b/packages/experimental/session-inspector/src/client/locales.ts index 5a02c89b79..28a6956e38 100644 --- a/packages/experimental/session-inspector/src/client/locales.ts +++ b/packages/experimental/session-inspector/src/client/locales.ts @@ -124,3 +124,59 @@ export const en: Record = { 'object.more': 'Show more', 'object.navigation': 'Raw data navigation', } + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'tab.title': 'セッションログ', + 'tab.description': 'サイドバーで現在のセッションの生ログとグループ化されたチャットメッセージを分析します。', + 'view.presentation': '表示', + 'view.sessionLog': '生ログ', + 'view.chatNode': 'チャットグループ', + 'picker.pick': 'チャットビューで要素を選択', + 'picker.cancel': '選択をキャンセル (Esc)', + 'picker.instructions': 'メインチャット上でホバーしてプレビューし、クリックで選択します。Escでキャンセル。', + 'picker.noMatch': 'このテーブルに一致するレコードはありません。別の要素を選択するか、Escでキャンセルしてください。', + 'picker.noChat': '選択する前に、このセッションをメインチャットで開いてください。', + 'table.id': 'インデックス', + 'table.type': '種類', + 'filter.title': '種類を絞り込み', + 'filter.input': 'キーワードを入力', + 'filter.placeholder': '断片を入力 (例: delta)', + 'filter.help': '部分一致 (*キーワード*)。Enterで確定、Escでキャンセル。', + 'filter.candidates': '種類の候補', + 'filter.loading': '候補を検索中…', + 'filter.failed': '種類の候補を読み込めませんでした。', + 'filter.noCandidates': '候補はありません。このキーワードはそのまま適用できます。', + 'filter.clear': 'クリア', + 'filter.cancel': 'キャンセル', + 'filter.apply': 'フィルターを適用', + 'filter.context': '一致したレコードの親コンテキスト', + 'table.location': 'T/S', + 'table.time': '時刻 (UTC)', + 'table.data': 'データ', + 'table.older': '以前を読み込む', + 'table.loading': '読み込み中…', + 'table.latest': '最新を追跡', + 'table.empty': 'レコードなし', + 'table.raw': '生データ', + 'table.rawError': 'このレコードはシリアライズできませんでした。続行するには別のレコードを選択してください。', + 'table.resizeDetails': '生データパネルのサイズ変更', + 'table.close': '閉じる', + 'table.expand': '子を展開', + 'table.collapse': '子を折りたたむ', + 'table.removed': 'このレコードは削除または確定済みです。現在のレコードを選択してください。', + 'object.node': 'ノード', + 'object.nodeData': 'ノードデータ', + 'object.group': 'グループ', + 'object.groupData': 'グループデータ', + 'object.turn': 'ターン', + 'object.turnData': 'ターンデータ', + 'object.step': 'ステップ', + 'object.stepData': 'ステップデータ', + 'object.accessor': '[ゲッター / セッター、未評価]', + 'object.empty': '空', + 'object.absent': '[空のスロット]', + 'object.properties': 'その他のプロパティ', + 'object.more': 'さらに表示', + 'object.navigation': '生データのナビゲーション', +} satisfies Record diff --git a/packages/extensions/cordis-client-runner/src/client/api-catalog.ts b/packages/extensions/cordis-client-runner/src/client/api-catalog.ts index 12c8cfff42..c8b0cb84e4 100644 --- a/packages/extensions/cordis-client-runner/src/client/api-catalog.ts +++ b/packages/extensions/cordis-client-runner/src/client/api-catalog.ts @@ -152,7 +152,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ }, { signature: 'register>(ns: N, dicts: Record>): () => void', - description: 'Register a declared namespace\'s dictionaries, all locales in one call — the typed form: each dictionary is checked against the namespace\'s LocaleNamespaceMap key union (a missing or extra key is a compile error), and every shipped locale is required (bilingual balance enforced at registration). Duplicate (ns, locale) throws (single occupant; a namespace\'s texts have one owner). Registration bumps the revision so mounted outlets pick up late-arriving dictionaries.', + description: 'Register a declared namespace\'s dictionaries, all locales in one call — the typed form: each dictionary is checked against the namespace\'s LocaleNamespaceMap key union (a missing or extra key is a compile error), and both baseline locales are required (bilingual balance enforced at registration). Duplicate (ns, locale) throws (single occupant; a namespace\'s texts have one owner). Registration bumps the revision so mounted outlets pick up late-arriving dictionaries.', parameters: [{ name: 'ns', description: 'a namespace merged into LocaleNamespaceMap.' }, { name: 'dicts', description: 'complete dictionaries keyed by built-in locale id.' }], returns: 'disposer removing every locale registered by this call (idempotent).', }, diff --git a/packages/extensions/ui-cordis/src/client/index.ts b/packages/extensions/ui-cordis/src/client/index.ts index f5e43260f3..0ef99b4dd6 100644 --- a/packages/extensions/ui-cordis/src/client/index.ts +++ b/packages/extensions/ui-cordis/src/client/index.ts @@ -17,7 +17,7 @@ import { createCordisInventory } from './inventory.ts' import { CordisRunCardRegistry } from './run-card-index.ts' import type { CordisDynamicPort } from './dynamic-port.ts' import type { CordisCardFace, CordisPanelFace, CordisRunCardFace } from './slots.ts' -import { en, NS, zh } from './locales.ts' +import { en, ja, NS, zh } from './locales.ts' export type { CordisCardFace, CordisPanelFace, CordisRunCardFace, CordisToolViewOwnerProps } from './slots.ts' export type { CordisActionResult, CordisDynamicPort, CordisInventoryRow } from './dynamic-port.ts' @@ -40,7 +40,8 @@ export const inject = [ /** Mount every Cordis browser surface over the shared Host inventory. */ export function apply(ctx: ClientContext): void { - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'ui-cordis: dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-cordis: dictionaries') const port: CordisDynamicPort = { stop: async (sessionId, pluginId) => { diff --git a/packages/extensions/ui-cordis/src/client/locales.ts b/packages/extensions/ui-cordis/src/client/locales.ts index 60b061aca4..5483c81a3a 100644 --- a/packages/extensions/ui-cordis/src/client/locales.ts +++ b/packages/extensions/ui-cordis/src/client/locales.ts @@ -121,3 +121,58 @@ export const en = { 'body.copy': 'Copy', 'body.copied': 'Copied', } satisfies Record + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'row.defineTitle': 'Cordis プラグインを登録', + 'a11y.preparing': 'Cordis ツール呼び出しを準備中', + 'row.runTitle': 'Cordis プラグインを実行', + 'row.updateTitle': 'Cordis プラグインを更新', + 'row.stopTitle': 'Cordis プラグインを停止', + 'row.removeTitle': 'Cordis プラグインを削除', + 'purpose.missing': '(目的未指定)', + 'status.idle': '準備完了', + 'status.awaitingApproval': '承認待ち', + 'status.failed': '実行失敗', + 'status.clientPending': 'クライアントのアクティベート準備完了', + 'status.running': '実行中', + 'status.removed': '削除済み', + 'status.superseded': '新しい実行が利用可能', + 'run.removed': 'このパッケージはもう存在しません', + 'run.superseded': '新しい実行カードが下にあります', + 'panel.hint': '実行コントロールは設定の上の Cordis パネルにあります', + 'panel.plugins.aria': 'Cordis プラグイン', + 'panel.approvals.aria': 'Cordis の承認', + 'panel.trigger': 'Cordis プラグイン', + 'panel.runningCount': '{count} 件実行中', + 'panel.title': 'Cordis プラグイン', + 'panel.empty': 'プラグインがまだ定義されていません', + 'panel.loading': '読み込み中…', + 'panel.readFailed': 'プラグイン一覧の読み込みに失敗しました: {message}', + 'panel.group.current': 'このセッション', + 'panel.group.others': '他のセッション', + 'panel.version': 'バージョン', + 'panel.current': '現在: {packageId}', + 'panel.next': '次: {packageId}', + 'action.approve': '許可', + 'action.approveOnce': 'このバージョンのみ許可', + 'action.approvePlugin': 'このプラグインの今後のバージョンを許可', + 'action.decline': '拒否', + 'action.run': '実行', + 'action.stop': '停止', + 'action.remove': '削除', + 'action.retry': '再試行', + 'action.rollback': 'ロールバック', + 'action.inspect': '検査', + 'render.failedAbdicated': '{slot} でのレンダリングに失敗しました。デフォルト UI を復元しました:', + 'render.failedHeld': '{slot} でのレンダリングに失敗しました:', + 'a11y.defining': 'プラグインを定義中', + 'a11y.failed': '定義に失敗しました', + 'a11y.stopped': '定義が中断されました', + 'body.source': 'プラグインのソース', + 'body.hostCode': 'ホスト', + 'body.clientCode': 'クライアント', + 'body.output': '結果', + 'body.copy': 'コピー', + 'body.copied': 'コピーしました', +} satisfies Record diff --git a/packages/session-query/session-log-export/src/client/index.ts b/packages/session-query/session-log-export/src/client/index.ts index bcba0dd581..75a58c7dd5 100644 --- a/packages/session-query/session-log-export/src/client/index.ts +++ b/packages/session-query/session-log-export/src/client/index.ts @@ -11,7 +11,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-message-feedback/client' import { createSnapshotStore } from '@deepseek-ai/dsh-client-store' import { SessionLogDownloadController } from './controller.ts' import { SessionLogDownloadHeaderAction, type SessionLogDownloadHeaderInjected } from './HeaderAction.tsx' -import { en, NS, zh, type SessionLogDownloadKey } from './locales.ts' +import { en, ja, NS, zh, type SessionLogDownloadKey } from './locales.ts' declare module '@deepseek-ai/cordis' { interface Context { @@ -37,7 +37,8 @@ export function apply(ctx: ClientContext): void { const controller = new SessionLogDownloadController() ctx.provide('sessionLogDownload', controller) ctx.effect(() => async () => { await controller.dispose() }, 'session-log-download: browser download lifecycle') - ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'session-log-download: browser dictionaries') + const dictionaries = { zh, en, ja } + ctx.effect(() => ctx.locale.register(NS, dictionaries), 'session-log-download: browser dictionaries') const feedbackAvailable = createSnapshotStore(false) ctx.inject(['feedbackUi'], (scope: ClientContext) => { scope.effect(() => { diff --git a/packages/session-query/session-log-export/src/client/locales.ts b/packages/session-query/session-log-export/src/client/locales.ts index 1fd687a379..478e787018 100644 --- a/packages/session-query/session-log-export/src/client/locales.ts +++ b/packages/session-query/session-log-export/src/client/locales.ts @@ -31,3 +31,17 @@ export const en: Record = { /** Stable locale keys consumed by the shared modal. */ export type SessionLogDownloadKey = keyof typeof zh + +/** Japanese copy, checked against the English keys. */ +export const ja = { + 'header.more': 'その他の操作', + 'menu.download': 'セッションログをダウンロード', + 'menu.feedback': 'フィードバック', + 'dialog.preparingTitle': 'セッションをエクスポート中', + 'dialog.preparingDescription': 'このセッション、そのサブセッション、添付ファイルを含むZIPを準備しています。', + 'dialog.successTitle': 'セッションのダウンロードを開始しました', + 'dialog.successDescription': 'ブラウザがセッションZIPをダウンロードしています。', + 'dialog.errorTitle': 'セッションのエクスポートに失敗しました', + 'dialog.close': '閉じる', + 'dialog.commandFailed': 'セッションのエクスポートを開始できませんでした。', +} satisfies Record diff --git a/scripts/locale-dictionary-parity.spec.ts b/scripts/locale-dictionary-parity.spec.ts index b51630f105..497e762092 100644 --- a/scripts/locale-dictionary-parity.spec.ts +++ b/scripts/locale-dictionary-parity.spec.ts @@ -1,6 +1,7 @@ /** * Gate for the invariant `FALLBACK_LOCALE` rests on: every shipped dictionary - * declares the same keys in `zh` and `en`. + * declares the same keys in `zh` and `en`; Japanese dictionaries also preserve + * the English keys and literal-template placeholders. * * The locale runtime resolves a key through the active locale, then through * the single fallback locale (`en`), then surfaces the key itself. With @@ -82,6 +83,8 @@ interface Dictionary { name: string /** Declared keys, sorted. */ keys: string[] + /** Literal templates used to compare named placeholders. */ + templates: Readonly> } /** @@ -98,7 +101,7 @@ function dictionariesIn(file: string): Dictionary[] { // before parsing — the silent narrowing this gate exists to prevent. A bare // `\b(zh|en)\b` misses `zhSettings`/`accessZh`, because `\b` does not hold // between `h` and an uppercase letter. - if (!/\b(zh|en)\b|\b(zh|en)[A-Z]|(Zh|En)\b/.test(text)) return [] + if (!/\b(zh|en|ja)\b|\b(zh|en|ja)[A-Z]|(Zh|En|Ja)\b/.test(text)) return [] const source = ts.createSourceFile(file, text, ts.ScriptTarget.ESNext, true) const found: Dictionary[] = [] const rel = relative(file) @@ -125,7 +128,7 @@ function dictionariesIn(file: string): Dictionary[] { const literal = unwrap(decl.initializer) if (literal === undefined || !ts.isObjectLiteralExpression(literal)) continue if (localeOf(decl.name.text) === undefined) continue - found.push({ file: rel, name: decl.name.text, keys: keysOf(literal) }) + found.push({ file: rel, name: decl.name.text, keys: keysOf(literal), templates: templatesOf(literal) }) } } @@ -171,10 +174,10 @@ function dictionariesIn(file: string): Dictionary[] { // The namespace expression's source text identifies the pair, so the // zh and en calls for one namespace meet and calls for different // namespaces stay apart. - found.push({ file: rel, name: `${tag.text}@register:${ns.getText(source)}`, keys: keysOf(dictionary) }) + found.push({ file: rel, name: `${tag.text}@register:${ns.getText(source)}`, keys: keysOf(dictionary), templates: templatesOf(dictionary) }) } } - if (ts.isArrayLiteralExpression(node) && node.elements.length === 2) { + if (ts.isArrayLiteralExpression(node) && node.elements.length >= 2) { const site = source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1 for (const element of node.elements) { if (!ts.isArrayLiteralExpression(element) || element.elements.length !== 2) continue @@ -182,8 +185,8 @@ function dictionariesIn(file: string): Dictionary[] { const literal = unwrap(dict) if (tag === undefined || !ts.isStringLiteral(tag)) continue if (literal === undefined || !ts.isObjectLiteralExpression(literal)) continue - if (tag.text !== 'zh' && tag.text !== 'en') continue - found.push({ file: rel, name: `${tag.text}@inline:${site}`, keys: keysOf(literal) }) + if (tag.text !== 'zh' && tag.text !== 'en' && tag.text !== 'ja') continue + found.push({ file: rel, name: `${tag.text}@inline:${site}`, keys: keysOf(literal), templates: templatesOf(literal) }) } } ts.forEachChild(node, visit) @@ -192,6 +195,19 @@ function dictionariesIn(file: string): Dictionary[] { return found } + +/** Literal templates; shared spread dictionaries are checked at their own declaration. */ +function templatesOf(literal: ts.ObjectLiteralExpression): Readonly> { + const templates: Record = {} + for (const property of literal.properties) { + if (!ts.isPropertyAssignment(property) || !ts.isStringLiteralLike(property.initializer)) continue + if (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) { + templates[property.name.text] = property.initializer.text + } + } + return templates +} + /** Declared property names of an object literal, sorted. */ function keysOf(literal: ts.ObjectLiteralExpression): string[] { const keys: string[] = [] @@ -224,9 +240,9 @@ function unwrap(node: ts.Expression | undefined): ts.Expression | undefined { * @param name - export name or synthetic inline name. * @returns locale plus pair key, or undefined when the name names no locale. */ -function localeOf(name: string): { locale: 'zh' | 'en'; pair: string } | undefined { - for (const locale of ['zh', 'en'] as const) { - const other = locale === 'zh' ? 'Zh' : 'En' +function localeOf(name: string): { locale: 'zh' | 'en' | 'ja'; pair: string } | undefined { + for (const locale of ['zh', 'en', 'ja'] as const) { + const other = locale[0]!.toUpperCase() + locale.slice(1) if (name === locale) return { locale, pair: '' } // Synthetic names for inline shapes carry their own pair key after the // first ':' (the enclosing array's line, or the namespace expression). @@ -240,7 +256,7 @@ function localeOf(name: string): { locale: 'zh' | 'en'; pair: string } | undefin } describe('shipped locale dictionaries', () => { - it('declares the same keys in zh and en, so the single fallback locale always resolves', () => { + it('keeps bilingual keys aligned and Japanese keys and placeholders complete', () => { const files = sourceFiles() // Guard the discovery itself: an empty or narrowed sweep would pass every // assertion below while checking nothing. @@ -256,9 +272,9 @@ describe('shipped locale dictionaries', () => { if (dicts.length > 0) perFile.set(relative(file), dicts) } - const groups = new Map>() - const place = (key: string, locale: 'zh' | 'en', dict: Dictionary): void => { - const slot = groups.get(key) ?? new Map<'zh' | 'en', Dictionary>() + const groups = new Map>() + const place = (key: string, locale: 'zh' | 'en' | 'ja', dict: Dictionary): void => { + const slot = groups.get(key) ?? new Map<'zh' | 'en' | 'ja', Dictionary>() if (slot.has(locale)) { throw new Error(`two ${locale} dictionaries claim pair ${key}: ${slot.get(locale)?.file} and ${dict.file}`) } @@ -285,6 +301,7 @@ describe('shipped locale dictionaries', () => { const problems: string[] = [] let comparedPairs = 0 + let japaneseDictionaries = 0 for (const [key, slot] of [...groups].sort()) { const zh = slot.get('zh') const en = slot.get('en') @@ -298,11 +315,30 @@ describe('shipped locale dictionaries', () => { const enOnly = en.keys.filter(k => !zh.keys.includes(k)) if (zhOnly.length > 0) problems.push(`${zh.file} ${zh.name} has keys absent from ${en.name}: ${zhOnly.join(', ')}`) if (enOnly.length > 0) problems.push(`${en.file} ${en.name} has keys absent from ${zh.name}: ${enOnly.join(', ')}`) + const ja = slot.get('ja') + if (ja !== undefined) { + japaneseDictionaries++ + const missing = en.keys.filter(k => !ja.keys.includes(k)) + const extra = ja.keys.filter(k => !en.keys.includes(k)) + if (missing.length > 0 || extra.length > 0) { + problems.push(`${ja.file} ${ja.name}: missing [${missing.join(', ')}], extra [${extra.join(', ')}]`) + } + for (const [name, text] of Object.entries(en.templates)) { + const translated = ja.templates[name] + if (translated === undefined) continue + const placeholders = (value: string): string => (value.match(/\{\w+\}/gu) ?? []).sort().join(', ') + if (placeholders(text) !== placeholders(translated)) { + problems.push(`${ja.file} ${ja.name}.${name} changes placeholders from ${en.name}`) + } + } + } + } // The shipped dictionary count only grows; a collapse means discovery or // pairing broke, which would hide real asymmetry. expect(comparedPairs).toBeGreaterThan(25) + expect(japaneseDictionaries).toBeGreaterThan(60) expect(problems).toEqual([]) }) }) diff --git a/snapshots/web/ptc-round/ui-ja.expected.md b/snapshots/web/ptc-round/ui-ja.expected.md new file mode 100644 index 0000000000..20e010ccac --- /dev/null +++ b/snapshots/web/ptc-round/ui-ja.expected.md @@ -0,0 +1,38 @@ +- banner: + - navigation "セッション階層": "Using ONE run_code program: run" + - text: PTC モード + - button "その他の操作" + - button "右サイドバーを開く" + - tablist: + - tab "チャット" [selected] + - tab "軌跡" + - button "その他の会話ビュー" +- text: "Using ONE run_code program: run bash `echo CODE_ROUND_OK`, then read the file missing.txt catching its error in the program. Return an object with both outcomes. Then reply DONE and stop. {{clock}}" +- button "コピー" +- status: 完了 +- button "完了まで {{duration}}" [expanded] +- button "コードを実行しました、コマンドを実行しました、ファイルを読み取りました" [expanded] +- 'button "思考 The user wants me to write a single `run_code` program that:"' +- button "コード Run bash echo and catch missing file read" +- button "Bash Echo CODE_ROUND_OK" [expanded] +- text: 完了 workspace echo CODE_ROUND_OK +- button "コピー" +- text: CODE_ROUND_OK +- button "検査" +- text: 失敗 +- 'button "読み取り Error: cannot read \"{{cwd}}/workspace/missing.txt\": not found"' +- button "思考 The program ran successfully. Let me now reply DONE as instructed." +- paragraph: DONE +- button "コピー" +- button "良い回答" +- button "悪い回答" +- button "新しい会話に分岐" +- text: {{clock}} +- textbox "メッセージを送信するかタスクを実行、/ コマンド、@ ファイルやセッション" +- button "ファイルを追加またはコマンドを実行" +- 'button "アクセスモード、現在: ワークスペース書き込み"': ワークスペース書き込み +- button "モデルを選択、現在 DeepSeek-V4-Flash": DeepSeek-V4-Flash +- button "メッセージを送信" [disabled] +- button "1ターン 2ステップ · {{throughput}} tok/s": 1ターン 2ステップ{{throughput}} tok/s +- button "17.5K tok · キャッシュヒット 52%": 17.5K tokキャッシュヒット 52% +- button "コンテキストの7%を使用中": 7% From 3007a15cdda16a9411739fa3c147792250b2d9ba Mon Sep 17 00:00:00 2001 From: 07akioni <07akioni2@gmail.com> Date: Wed, 21 Oct 2026 11:44:22 +0800 Subject: [PATCH 2/4] fix(locale): align Japanese copy with current plugin settings --- packages/client/ui-plugin-manager/src/client/locales.ts | 3 +++ packages/experimental/cua/src/client/locales.ts | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/client/ui-plugin-manager/src/client/locales.ts b/packages/client/ui-plugin-manager/src/client/locales.ts index 61eea667c1..f939d61cca 100644 --- a/packages/client/ui-plugin-manager/src/client/locales.ts +++ b/packages/client/ui-plugin-manager/src/client/locales.ts @@ -646,6 +646,9 @@ export const ja = { 'rowPhaseActive': '稼働中', 'rowPhaseFailed': '問題', 'rowPhaseUnloading': 'アンロード中', + 'applyingChange': '{name} の変更を適用中', + 'installBundledTitle': 'このプラグインは DSH に同梱されています', + 'installBundledDescription': '追加のダウンロードなしで有効にできます', 'enableToggle': '{name}を有効化', 'openDetail': '{name}を表示', 'backToList': 'プラグイン一覧に戻る', diff --git a/packages/experimental/cua/src/client/locales.ts b/packages/experimental/cua/src/client/locales.ts index 7544abc4ac..7dbb09e33f 100644 --- a/packages/experimental/cua/src/client/locales.ts +++ b/packages/experimental/cua/src/client/locales.ts @@ -105,8 +105,7 @@ export const ja = { 'screenRecordingUse': '選択したアプリケーションのウィンドウをキャプチャします。', 'accessibilityPermission': 'システム設定で、{application}にインターフェースのコントロールの読み取り、クリック、入力を許可してください。', 'screenRecordingPermission': 'システム設定で、{application}にアプリケーションウィンドウのキャプチャを許可してください。', - 'permissionHint': 'システム設定でアプリケーションを有効にしてください。別のアプリケーションが表示された場合はmacOSのプロンプトに従ってください。', - 'permissionHintUnknown': 'macOSのプロンプトに表示されたアプリケーションにこれらの権限を許可してください。', + 'permissionHint': 'macOS が示すアプリの権限をシステム設定で管理してください。', 'granted': '許可済み', 'unknown': '不明', 'request': '権限をリクエスト', From 6238020202cf6291c0d1e1e8594253552d9e9e88 Mon Sep 17 00:00:00 2001 From: 07akioni <07akioni2@gmail.com> Date: Wed, 21 Oct 2026 13:42:49 +0800 Subject: [PATCH 3/4] fix(locale): type additional dictionaries and complete Japanese registration --- apps/desktop/src/locale.ts | 345 +++++++++--------- apps/web/tests/desktop-locale.e2e.ts | 41 ++- .../desktop-locale/automatic.expected.md | 76 ++-- apps/web/tests/ptc-round.e2e.ts | 2 - .../built-in-japanese/guide.i18n.yaml | 16 + .../v0.2.1-rc.2/built-in-japanese/guide.md | 18 + .../v0.2.1-rc.2/built-in-japanese/guide.zh.md | 18 + packages/client/locale/README.i18n.yaml | 6 +- packages/client/locale/README.md | 2 +- packages/client/locale/README.zh.md | 4 +- packages/client/locale/src/client/index.ts | 13 +- .../client/locale/tests/apply.client.spec.ts | 1 - .../client/locale/tests/locale.client.spec.ts | 19 + .../ui-agent-preset/src/client/index.ts | 3 +- .../client/ui-approval/src/client/index.ts | 3 +- packages/client/ui-chat/src/client/apply.ts | 3 +- .../client/ui-commands/src/client/index.ts | 3 +- .../ui-conversation/src/client/apply.ts | 3 +- .../ui-conversation/src/client/locales.ts | 34 +- .../src/client/index.ts | 3 +- .../ui-deliverables/src/client/index.ts | 3 +- .../src/client/index.ts | 3 +- packages/client/ui-goal/src/client/index.ts | 3 +- .../ui-input-trigger/src/client/index.ts | 3 +- packages/client/ui-jobs/src/client/index.ts | 3 +- packages/client/ui-layout/src/client/index.ts | 3 +- .../ui-message-feedback/src/client/index.ts | 3 +- .../ui-model-selection/src/client/index.ts | 3 +- .../client/ui-open-in-app/src/client/index.ts | 3 +- .../ui-permission-presets/src/client/index.ts | 3 +- packages/client/ui-plan/src/client/index.ts | 3 +- .../ui-plugin-manager/src/client/index.ts | 3 +- .../ui-plugin-market/src/client/index.ts | 3 +- .../client/ui-reference/src/client/index.ts | 3 +- .../src/client/frequency-locales.ts | 2 +- .../client/ui-schedule/src/client/index.ts | 3 +- .../client/ui-secret-mask/src/client/index.ts | 3 +- .../ui-settings-account/src/client/index.ts | 3 +- .../ui-settings-account/src/client/locales.ts | 8 +- .../src/client/locales/onboarding.ts | 8 +- .../src/client/index.ts | 3 +- .../ui-settings-general/src/client/index.ts | 3 +- .../ui-settings-models/src/client/index.ts | 3 +- .../src/client/index.ts | 3 +- .../ui-settings-plugins/src/client/index.ts | 3 +- .../src/client/index.ts | 3 +- .../ui-settings-shell/src/client/index.ts | 3 +- .../ui-settings-subagent/src/client/index.ts | 3 +- .../src/client/index.ts | 3 +- .../client/ui-shortcuts/src/client/index.ts | 3 +- .../ui-sidebar-browser/src/client/index.ts | 3 +- .../src/client/code/index.ts | 3 +- .../src/client/excel/index.ts | 3 +- .../src/client/html/index.ts | 3 +- .../src/client/image/index.ts | 3 +- .../src/client/index.ts | 3 +- .../src/client/markdown/index.ts | 3 +- .../src/client/office/index.ts | 3 +- .../src/client/pdf/index.ts | 3 +- .../ui-sidebar-files/src/client/index.ts | 3 +- .../ui-sidebar-right/src/client/index.ts | 3 +- .../ui-sidebar-terminal/src/client/index.ts | 3 +- .../client/ui-sidebar/src/client/index.ts | 3 +- packages/client/ui-skill/src/client/index.ts | 3 +- .../client/ui-subagent/src/client/index.ts | 3 +- packages/client/ui-theme/src/client/index.ts | 3 +- .../client/ui-trajectory/src/client/index.ts | 3 +- .../ui-user-questions/src/client/index.ts | 3 +- .../ui-workflow-run/src/client/index.ts | 3 +- .../client/ui-workspace/src/client/index.ts | 3 +- .../tests/client-headers.spec.ts | 2 +- .../client-ui-agent-team/src/client/mount.ts | 3 +- .../src/client/mount.ts | 3 +- .../src/client/mount.ts | 3 +- .../client-ui-voice-input/src/client/mount.ts | 3 +- packages/experimental/cua/src/client/mount.ts | 3 +- .../inspector/src/client/bottom/page.tsx | 4 +- .../inspector/tests/bottom.client.spec.tsx | 13 + .../session-inspector/src/client/index.ts | 3 +- .../src/client/api-catalog.ts | 4 +- .../extensions/ui-cordis/src/client/index.ts | 3 +- .../session-log-export/src/client/index.ts | 3 +- scripts/locale-dictionary-parity.spec.ts | 73 +++- 83 files changed, 497 insertions(+), 395 deletions(-) create mode 100644 docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.i18n.yaml create mode 100644 docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.md create mode 100644 docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.zh.md diff --git a/apps/desktop/src/locale.ts b/apps/desktop/src/locale.ts index 7a5a9682d2..2664e24dd8 100644 --- a/apps/desktop/src/locale.ts +++ b/apps/desktop/src/locale.ts @@ -341,6 +341,176 @@ export const zh = { mandatoryNotification: '返回应用确认安装并重启。', } as const satisfies DesktopMessages +/** Japanese copy, checked against the English keys. */ +export const ja = { + cliCommandMenu: 'dsh コマンドを管理…', + cliCommandTitle: 'dsh コマンドを管理', + cliCommandLocation: 'デスクトップコマンド: {path}', + cliCommandSelected: '現在の dsh コマンド: {path}', + cliCommandTarget: '現在のランチャーターゲット: {path}', + cliCommandShadowed: '別の dsh が優先されています。デフォルトでデスクトップコマンドを使用するには、そのインストールを削除するか順序を変更してください。デスクトップコマンドはフルパスでも実行できます。', + cliCommandSelectionUnknown: 'シェルコマンドを確認できませんでした。エイリアスや別の dsh インストールが優先されている可能性があります。', + cliCommandInstalled: 'デスクトップコマンドがインストールされています。', + cliCommandNotInstalled: 'デスクトップコマンドをターミナルに追加します。', + cliCommandBroken: 'デスクトップコマンドの修復が必要です。', + cliCommandInstall: 'インストール', + cliCommandRepair: '修復', + cliCommandRemove: '削除', + cliCommandClose: '閉じる', + cliCommandSwitch: 'デスクトップコマンドで続行しますか?', + cliCommandPreserve: '既存のコマンドは保持されます。他のインストールやシェルの起動ファイルは変更されません。', + cliCommandContinue: '続行', + cliCommandInstallApp: 'dsh コマンドを管理する前に、デスクトップをアプリケーションフォルダにインストールしてください。', + cliCommandUpdating: 'アップデートをインストール中です。インストール完了後にコマンドを管理してください。', + cliCommandNewTerminal: '新しいターミナルを開いて dsh --version を実行してください。', + cliCommandRemoved: 'デスクトップコマンドの登録を削除しました。', + cliCommandPreviousRestored: '以前のランチャーを復元しました。', + cliCommandOtherKept: '他のコマンドインストールはそのまま残されています。', + cliCommandBackupKept: '以前のランチャーは次の場所に保持されています: {path}', + cliCommandChanged: 'このダイアログを開いている間にコマンドまたは PATH が変更されました。現在の状態を確認するには、dsh コマンドを管理を再度開いてください。', + cliCommandOwnershipError: 'コマンド登録またはバックアップが変更されました。無関係なコマンドは削除されていません。', + cliCommandFailed: 'コマンドを更新できませんでした。アプリケーションがインストールされ、保存先に書き込み権限があることを確認してから再試行してください。', + application: 'アプリケーション', + fileMenu: 'ファイル', + closePage: 'ページまたはウィンドウを閉じる', + aboutMenu: 'DeepSeek Harness について', + aboutProduct: 'DeepSeek Harness', + aboutVersion: 'バージョン V{version}', + hideApplication: 'DeepSeek Harness を隠す', + hideOtherApplications: 'ほかを隠す', + showAllApplications: 'すべて表示', + quitApplication: 'DeepSeek Harness を終了', + openApplication: 'DeepSeek Harness を開く', + quit: '終了', + cancel: 'キャンセル', + quitTitle: 'DeepSeek Harness を終了しますか?', + quitActiveTasks: '実行中のタスクは中断されます。', + quitScheduledTasks: 'アプリの終了中はスケジュールされたタスクが実行されません。', + quitActiveAndScheduledTasks: '実行中のタスクは中断され、アプリの終了中はスケジュールされたタスクが実行されません。', + backgroundNoticeBody: '実行中のタスクは継続されます。システムトレイからウィンドウを再度開けます。', + backgroundNoticeConfirm: '確認', + edit: '編集', + menuBar: 'アプリケーションメニュー', + delete: '削除', + undo: '元に戻す', + redo: 'やり直す', + cut: '切り取り', + copy: 'コピー', + paste: '貼り付け', + selectAll: 'すべて選択', + startupFailed: 'DeepSeek Harness を利用できません', + fatalSummary: 'アプリケーションを起動できなかったか、予期せず停止しました。', + startupAddressInUse: '別の DSH インスタンス (dsh web やデスクトップアプリなど) が実行中です。同時に起動することはできません。実行中の他の DSH インスタンスを終了してから再起動してください。', + diagnosticTruncated: '… エラー詳細は省略されています。', + reportWrittenTo: '診断レポート: {path}', + startupReinstallAdvice: 'アプリケーションファイルが見つからないか破損している場合は、アプリケーションを閉じて再インストールしてください。タスクは別途保存されています。', + exitApplication: '終了', + restartApplication: '再起動', + recoveryOperationFailed: '復旧操作に失敗しました', + disableThirdPartyPlugins: 'サードパーティのプラグインを無効化し、プロファイルパッチをバックアップして再起動', + welcomeTitle: 'DeepSeek Harness', + welcomeBrand: 'DeepSeek Harness', + welcomeTaglineBefore: 'ようこそ ', + welcomeTaglineBrand: 'DeepSeek Harness', + welcomeTaglineAfter: '', + welcomeDescription: '可能性を築く。知能を探求する。', + welcomeAuthStarting: 'ログインを開いています…', + welcomeAuthWaiting: 'ブラウザが自動で開きませんでしたか?', + welcomeAuthWaitingDescription: 'ログインリンクをコピーしてブラウザで開き、ログインしてください。', + welcomeAuthExchanging: 'ログインを完了しています…', + welcomeAuthExpired: 'ログインがタイムアウトしました', + welcomeAuthExpiredDescription: '続行するには再度ログインしてください', + welcomeAuthFailed: 'ログインを完了できませんでした。もう一度お試しください。', + welcomeAuthCopyLink: 'ログインリンクをコピー', + welcomeAuthCopied: 'コピーしました', + welcomeAuthCopyFailed: 'コピーできませんでした。もう一度お試しください。', + welcomeAuthCancel: 'キャンセル', + welcomeAuthRetry: '再度ログイン', + welcomeSignIn: 'ログイン', + welcomeApiKey: 'APIキーを追加', + welcomeKeyTitle: 'APIキーを追加して開始', + welcomeKeyDescription: '公式のDeepSeekモデルを設定してHarnessを使い始めましょう', + welcomeKeyPlaceholder: 'APIキーを入力', + welcomeKeySave: '保存して続行', + welcomeKeyLater: '後で設定', + welcomeKeyBack: 'ログインに戻る', + welcomeSessionExpired: 'アカウントからログアウトしました。再度ログインしてください。', + welcomeKeyBlank: 'APIキーを入力してください。', + welcomeKeyInvalid: '引用符、スペース、環境変数の代入を含めず、APIキーそのものを入力してください。', + welcomeKeyFailed: 'APIキーを保存できませんでした。もう一度お試しください。', + welcomeContinueFailed: 'ワークスペースを開けませんでした。もう一度お試しください。', + checkUpdatesMenu: 'アップデートを確認…', + reloadPageMenu: 'ページを再読み込み', + restartAppHostMenu: 'アプリとホストを再起動', + updateCheckFailedTitle: 'アップデートの確認に失敗しました', + updateCheckFailed: 'アップデートを確認できませんでした。後でもう一度お試しください。', + updateDownloadFailed: 'アップデートをダウンロードできませんでした。もう一度お試しください。', + updateInstallFailed: 'アップデートをインストールできませんでした。後でもう一度お試しください。', + updateCheckNetworkFailed: 'アップデートを確認できませんでした。接続を確認してもう一度お試しください。', + updateDownloadNetworkFailed: 'アップデートをダウンロードできませんでした。接続を確認してもう一度お試しください。', + updateInstallNetworkFailed: 'アップデートをインストールできませんでした。接続を確認してもう一度お試しください。', + unknownError: '不明なエラー', + updateCheckTitle: 'アップデートを確認', + updateCurrentDetail: '現在のバージョン: {version}', + updateCurrent: '最新です!', + updateChecking: 'アップデートを確認中…', + updateDownload: 'アップデートをダウンロード', + updateDownloadedTitle: 'バージョン {version} をインストールする準備ができました', + updateDownloadedDetail: 'アップデート中はアプリが終了し、完了後に自動的に再起動します。', + updateDownloadedDetailWindows: 'アップデート中はアプリが一時的に終了し、完了後に自動的に再起動します。\n\nアップデートには時間がかかる場合があります。インストール中はアプリを再度起動せずにお待ちください。', + updateClose: '閉じる', + updateAcknowledge: 'OK', + updateLater: '後でアップデート', + updateDownloading: 'ダウンロード中 {percent}%…', + updateVerifying: 'アップデートファイルを検証中…', + updateInstalling: '再起動の準備中…', + updateRetry: 'アップデートを再試行', + updateActiveTasks: 'タスクがまだ実行中です', + updateActiveTasksDetail: 'アップデートすると実行中のタスクが停止し、アプリが再起動します。続行しますか?', + updateStopTasks: 'タスクを停止してアップデート', + updateTasksChanged: '新しいタスクが開始されました。タスクを停止してアップデートするには再度確認してください。', + updateTasksUnavailable: 'タスクの状態を取得できません。ワークスペースの準備ができてから再度アップデートをお試しください。', + updateFullscreenTimeout: 'フルスクリーンを終了できませんでした。フルスクリーンを終了してからアップデートを再試行してください。', + updateStopFailed: 'タスクを安全に停止できませんでした。アップデートはインストールされていません。後でもう一度お試しください。', + updateTechnicalDetails: '技術的な詳細を表示', + updateTitle: 'DeepSeek Harness アップデート', + updateAvailable: '新しいバージョンがあります: {version}', + updateDetail: 'ダウンロードが完了したら、アップデートをインストールしてアプリを再起動できます。', + installAndRestart: 'インストールして再起動', + later: '後で', + updateFailedTitle: 'アップデートに失敗しました', + mandatoryTitle: '続行するにはアップデートしてください', + mandatoryDetail: 'このバージョンはサポートされなくなりました。続行するにはアップデートしてください。実行中のタスクは、インストールの確認と再起動を行うまで実行を継続します。', + mandatoryUnavailable: 'アップデートの要件を確認できませんでした。後でもう一度お試しください。', + policyLoginTitle: 'テスト環境にログイン', + policyLoginRequired: 'Feishuでログインして、このテストビルドのアップデート要件を確認します。ログインしてもアップデートのダウンロードやインストールは行われません。', + policyLogin: 'Feishuでログイン', + policyLoginFailed: 'Feishuのログインが完了しませんでした。もう一度お試しください。', + policyLoginLoading: 'ログインページを読み込み中…', + mandatoryNoRelease: '互換性のあるアップデートが見つかりませんでした。再度確認するか、サポートにお問い合わせください。', + mandatoryRefresh: '再確認', + mandatoryPage: '公式サイトからダウンロード', + mandatoryCopy: 'ダウンロードリンクをコピー', + mandatoryPageFailed: '公式ダウンロードページを開けませんでした。リンクをコピーしてブラウザで開いてください。', + mandatoryActionFailed: '操作に失敗しました。もう一度お試しください。アップデートが完了すると、アプリを再度使用できます。', + mandatoryReady: 'アップデートの準備完了', + mandatoryVersion: '新しいバージョン: {version}', + mandatoryReadyDetail: 'アップデート中にアプリが終了し、完了後に自動的に再起動します。', + mandatoryDeferred: '実行中のタスクはそのまま続行されます。アプリを再度使用する前にアップデートを完了してください。', + mandatoryContinue: 'アップデートを続行', + mandatoryInspecting: 'タスクを確認中…', + mandatoryStopping: 'タスクを停止中…', + mandatoryRestarting: 'まもなくアプリが再起動します。しばらくお待ちください。', + mandatoryDownloadFailed: 'アップデートファイルをダウンロードまたは準備できませんでした。再試行してください。', + mandatoryInstallFailed: 'アップデートはインストールされていません。タスクを再度確認して再試行してください。', + mandatoryOpenHelp: 'ページが開きませんでしたか?', + mandatoryReopen: '公式ダウンロードページを再度開く', + mandatoryCopied: 'リンクをコピーしました', + mandatoryCopyFailed: 'リンクをコピーできませんでした。以下から選択してコピーしてください。', + mandatoryAddress: 'ダウンロードリンク', + mandatoryNotification: 'アプリに戻り、インストールと再起動を確認してください。', +} satisfies Record + /** Locale payload exposed to the Desktop-owned renderer. */ export interface DesktopLocale { readonly id: 'en' | 'zh-CN' | 'ja' @@ -349,8 +519,9 @@ export interface DesktopLocale { /** Resolve Electron's locale to one shipped Desktop dictionary. */ export function resolveDesktopLocale(locale: string): DesktopLocale { - if (locale.toLowerCase().split('-')[0] === 'ja') return { id: 'ja', messages: ja } - return locale.toLowerCase().startsWith('zh') + const primary = locale.toLowerCase().split('-')[0] + if (primary === 'ja') return { id: 'ja', messages: ja } + return primary === 'zh' ? { id: 'zh-CN', messages: zh } : { id: 'en', messages: en } } @@ -396,173 +567,3 @@ export function desktopUpdateReadyConfirmation( detail: platform === 'win32' ? messages.updateDownloadedDetailWindows : messages.updateDownloadedDetail, } } - -/** Japanese copy, checked against the English keys. */ -export const ja = { - 'cliCommandMenu': 'dsh コマンドを管理…', - 'cliCommandTitle': 'dsh コマンドを管理', - 'cliCommandLocation': 'デスクトップコマンド: {path}', - 'cliCommandSelected': '現在の dsh コマンド: {path}', - 'cliCommandTarget': '現在のランチャーターゲット: {path}', - 'cliCommandShadowed': '別の dsh が優先されています。デフォルトでデスクトップコマンドを使用するには、そのインストールを削除するか順序を変更してください。デスクトップコマンドはフルパスでも実行できます。', - 'cliCommandSelectionUnknown': 'シェルコマンドを確認できませんでした。エイリアスや別の dsh インストールが優先されている可能性があります。', - 'cliCommandInstalled': 'デスクトップコマンドがインストールされています。', - 'cliCommandNotInstalled': 'デスクトップコマンドをターミナルに追加します。', - 'cliCommandBroken': 'デスクトップコマンドの修復が必要です。', - 'cliCommandInstall': 'インストール', - 'cliCommandRepair': '修復', - 'cliCommandRemove': '削除', - 'cliCommandClose': '閉じる', - 'cliCommandSwitch': 'デスクトップコマンドで続行しますか?', - 'cliCommandPreserve': '既存のコマンドは保持されます。他のインストールやシェルの起動ファイルは変更されません。', - 'cliCommandContinue': '続行', - 'cliCommandInstallApp': 'dsh コマンドを管理する前に、デスクトップをアプリケーションフォルダにインストールしてください。', - 'cliCommandUpdating': 'アップデートをインストール中です。インストール完了後にコマンドを管理してください。', - 'cliCommandNewTerminal': '新しいターミナルを開いて dsh --version を実行してください。', - 'cliCommandRemoved': 'デスクトップコマンドの登録を削除しました。', - 'cliCommandPreviousRestored': '以前のランチャーを復元しました。', - 'cliCommandOtherKept': '他のコマンドインストールはそのまま残されています。', - 'cliCommandBackupKept': '以前のランチャーは次の場所に保持されています: {path}', - 'cliCommandChanged': 'このダイアログを開いている間にコマンドまたは PATH が変更されました。現在の状態を確認するには、dsh コマンドを管理を再度開いてください。', - 'cliCommandOwnershipError': 'コマンド登録またはバックアップが変更されました。無関係なコマンドは削除されていません。', - 'cliCommandFailed': 'コマンドを更新できませんでした。アプリケーションがインストールされ、保存先に書き込み権限があることを確認してから再試行してください。', - 'application': 'アプリケーション', - 'fileMenu': 'ファイル', - 'closePage': 'ページまたはウィンドウを閉じる', - 'aboutMenu': 'DeepSeek Harness について', - 'aboutProduct': 'DeepSeek Harness', - 'aboutVersion': 'バージョン V{version}', - 'hideApplication': 'DeepSeek Harness を隠す', - 'hideOtherApplications': 'ほかを隠す', - 'showAllApplications': 'すべて表示', - 'quitApplication': 'DeepSeek Harness を終了', - 'openApplication': 'DeepSeek Harness を開く', - 'quit': '終了', - 'cancel': 'キャンセル', - 'quitTitle': 'DeepSeek Harness を終了しますか?', - 'quitActiveTasks': '実行中のタスクは中断されます。', - 'quitScheduledTasks': 'アプリの終了中はスケジュールされたタスクが実行されません。', - 'quitActiveAndScheduledTasks': '実行中のタスクは中断され、アプリの終了中はスケジュールされたタスクが実行されません。', - 'backgroundNoticeBody': '実行中のタスクは継続されます。システムトレイからウィンドウを再度開けます。', - 'backgroundNoticeConfirm': '確認', - 'edit': '編集', - 'menuBar': 'アプリケーションメニュー', - 'delete': '削除', - 'undo': '元に戻す', - 'redo': 'やり直す', - 'cut': '切り取り', - 'copy': 'コピー', - 'paste': '貼り付け', - 'selectAll': 'すべて選択', - 'startupFailed': 'DeepSeek Harness を利用できません', - 'fatalSummary': 'アプリケーションを起動できなかったか、予期せず停止しました。', - 'startupAddressInUse': '別の DSH インスタンス (dsh web やデスクトップアプリなど) が実行中です。同時に起動することはできません。実行中の他の DSH インスタンスを終了してから再起動してください。', - 'diagnosticTruncated': '… エラー詳細は省略されています。', - 'reportWrittenTo': '診断レポート: {path}', - 'startupReinstallAdvice': 'アプリケーションファイルが見つからないか破損している場合は、アプリケーションを閉じて再インストールしてください。タスクは別途保存されています。', - 'exitApplication': '終了', - 'restartApplication': '再起動', - 'recoveryOperationFailed': '復旧操作に失敗しました', - 'disableThirdPartyPlugins': 'サードパーティのプラグインを無効化し、プロファイルパッチをバックアップして再起動', - 'welcomeTitle': 'DeepSeek Harness', - 'welcomeBrand': 'DeepSeek Harness', - 'welcomeTaglineBefore': 'ようこそ ', - 'welcomeTaglineBrand': 'DeepSeek Harness', - 'welcomeTaglineAfter': '', - 'welcomeDescription': '可能性を築く。知能を探求する。', - 'welcomeAuthStarting': 'ログインを開いています…', - 'welcomeAuthWaiting': 'ブラウザが自動で開きませんでしたか?', - 'welcomeAuthWaitingDescription': 'ログインリンクをコピーしてブラウザで開き、ログインしてください。', - 'welcomeAuthExchanging': 'ログインを完了しています…', - 'welcomeAuthExpired': 'ログインがタイムアウトしました', - 'welcomeAuthExpiredDescription': '続行するには再度ログインしてください', - 'welcomeAuthFailed': 'ログインを完了できませんでした。もう一度お試しください。', - 'welcomeAuthCopyLink': 'ログインリンクをコピー', - 'welcomeAuthCopied': 'コピーしました', - 'welcomeAuthCopyFailed': 'コピーできませんでした。もう一度お試しください。', - 'welcomeAuthCancel': 'キャンセル', - 'welcomeAuthRetry': '再度ログイン', - 'welcomeSignIn': 'ログイン', - 'welcomeApiKey': 'APIキーを追加', - 'welcomeKeyTitle': 'APIキーを追加して開始', - 'welcomeKeyDescription': '公式のDeepSeekモデルを設定してHarnessを使い始めましょう', - 'welcomeKeyPlaceholder': 'APIキーを入力', - 'welcomeKeySave': '保存して続行', - 'welcomeKeyLater': '後で設定', - 'welcomeKeyBack': 'ログインに戻る', - 'welcomeSessionExpired': 'アカウントからログアウトしました。再度ログインしてください。', - 'welcomeKeyBlank': 'APIキーを入力してください。', - 'welcomeKeyInvalid': '引用符、スペース、環境変数の代入を含めず、APIキーそのものを入力してください。', - 'welcomeKeyFailed': 'APIキーを保存できませんでした。もう一度お試しください。', - 'welcomeContinueFailed': 'ワークスペースを開けませんでした。もう一度お試しください。', - 'checkUpdatesMenu': 'アップデートを確認…', - 'reloadPageMenu': 'ページを再読み込み', - 'restartAppHostMenu': 'アプリとホストを再起動', - 'updateCheckFailedTitle': 'アップデートの確認に失敗しました', - 'updateCheckFailed': 'アップデートを確認できませんでした。後でもう一度お試しください。', - 'updateDownloadFailed': 'アップデートをダウンロードできませんでした。もう一度お試しください。', - 'updateInstallFailed': 'アップデートをインストールできませんでした。後でもう一度お試しください。', - 'updateCheckNetworkFailed': 'アップデートを確認できませんでした。接続を確認してもう一度お試しください。', - 'updateDownloadNetworkFailed': 'アップデートをダウンロードできませんでした。接続を確認してもう一度お試しください。', - 'updateInstallNetworkFailed': 'アップデートをインストールできませんでした。接続を確認してもう一度お試しください。', - 'unknownError': '不明なエラー', - 'updateCheckTitle': 'アップデートを確認', - 'updateCurrentDetail': '現在のバージョン: {version}', - 'updateCurrent': '最新です!', - 'updateChecking': 'アップデートを確認中…', - 'updateDownload': 'アップデートをダウンロード', - 'updateDownloadedTitle': 'バージョン {version} をインストールする準備ができました', - 'updateDownloadedDetail': 'アップデート中はアプリが終了し、完了後に自動的に再起動します。', - 'updateDownloadedDetailWindows': 'アップデート中はアプリが一時的に終了し、完了後に自動的に再起動します。\n\nアップデートには時間がかかる場合があります。インストール中はアプリを再度起動せずにお待ちください。', - 'updateClose': '閉じる', - 'updateAcknowledge': 'OK', - 'updateLater': '後でアップデート', - 'updateDownloading': 'ダウンロード中 {percent}%…', - 'updateVerifying': 'アップデートファイルを検証中…', - 'updateInstalling': '再起動の準備中…', - 'updateRetry': 'アップデートを再試行', - 'updateActiveTasks': 'タスクがまだ実行中です', - 'updateActiveTasksDetail': 'アップデートすると実行中のタスクが停止し、アプリが再起動します。続行しますか?', - 'updateStopTasks': 'タスクを停止してアップデート', - 'updateTasksChanged': '新しいタスクが開始されました。タスクを停止してアップデートするには再度確認してください。', - 'updateTasksUnavailable': 'タスクの状態を取得できません。ワークスペースの準備ができてから再度アップデートをお試しください。', - 'updateFullscreenTimeout': 'フルスクリーンを終了できませんでした。フルスクリーンを終了してからアップデートを再試行してください。', - 'updateStopFailed': 'タスクを安全に停止できませんでした。アップデートはインストールされていません。後でもう一度お試しください。', - 'updateTechnicalDetails': '技術的な詳細を表示', - 'updateTitle': 'DeepSeek Harness アップデート', - 'updateAvailable': '新しいバージョンがあります: {version}', - 'updateDetail': 'ダウンロードが完了したら、アップデートをインストールしてアプリを再起動できます。', - 'installAndRestart': 'インストールして再起動', - 'later': '後で', - 'updateFailedTitle': 'アップデートに失敗しました', - 'mandatoryTitle': '続行するにはアップデートしてください', - 'mandatoryDetail': 'このバージョンはサポートされなくなりました。続行するにはアップデートしてください。実行中のタスクは、インストールの確認と再起動を行うまで実行を継続します。', - 'mandatoryUnavailable': 'アップデートの要件を確認できませんでした。後でもう一度お試しください。', - 'policyLoginTitle': 'テスト環境にログイン', - 'policyLoginRequired': 'Feishuでログインして、このテストビルドのアップデート要件を確認します。ログインしてもアップデートのダウンロードやインストールは行われません。', - 'policyLogin': 'Feishuでログイン', - 'policyLoginFailed': 'Feishuのログインが完了しませんでした。もう一度お試しください。', - 'policyLoginLoading': 'ログインページを読み込み中…', - 'mandatoryNoRelease': '互換性のあるアップデートが見つかりませんでした。再度確認するか、サポートにお問い合わせください。', - 'mandatoryRefresh': '再確認', - 'mandatoryPage': '公式サイトからダウンロード', - 'mandatoryCopy': 'ダウンロードリンクをコピー', - 'mandatoryPageFailed': '公式ダウンロードページを開けませんでした。リンクをコピーしてブラウザで開いてください。', - 'mandatoryActionFailed': '操作に失敗しました。もう一度お試しください。アップデートが完了すると、アプリを再度使用できます。', - 'mandatoryReady': 'アップデートの準備完了', - 'mandatoryVersion': '新しいバージョン: {version}', - 'mandatoryReadyDetail': 'アップデート中にアプリが終了し、完了後に自動的に再起動します。', - 'mandatoryDeferred': '実行中のタスクはそのまま続行されます。アプリを再度使用する前にアップデートを完了してください。', - 'mandatoryContinue': 'アップデートを続行', - 'mandatoryInspecting': 'タスクを確認中…', - 'mandatoryStopping': 'タスクを停止中…', - 'mandatoryRestarting': 'まもなくアプリが再起動します。しばらくお待ちください。', - 'mandatoryDownloadFailed': 'アップデートファイルをダウンロードまたは準備できませんでした。再試行してください。', - 'mandatoryInstallFailed': 'アップデートはインストールされていません。タスクを再度確認して再試行してください。', - 'mandatoryOpenHelp': 'ページが開きませんでしたか?', - 'mandatoryReopen': '公式ダウンロードページを再度開く', - 'mandatoryCopied': 'リンクをコピーしました', - 'mandatoryCopyFailed': 'リンクをコピーできませんでした。以下から選択してコピーしてください。', - 'mandatoryAddress': 'ダウンロードリンク', - 'mandatoryNotification': 'アプリに戻り、インストールと再起動を確認してください。', -} satisfies Record diff --git a/apps/web/tests/desktop-locale.e2e.ts b/apps/web/tests/desktop-locale.e2e.ts index ad6ec8137c..71590956e9 100644 --- a/apps/web/tests/desktop-locale.e2e.ts +++ b/apps/web/tests/desktop-locale.e2e.ts @@ -21,15 +21,16 @@ describe.skipIf(MODE === 'record')('web e2e: native and Client locale preference let page: Page let tripwire: ReturnType const reported: string[] = [] + let languages = ['fr-FR', 'zh-CN', 'en-US'] beforeAll(async () => { - scaffold = await launchWebScaffold({ extraOverlayPath: fileURLToPath(new URL('./pin-browse-picker.overlay.yml', import.meta.url)) }) + scaffold = await launchWebScaffold() browser = await chromium.launch() page = await browser.newPage({ viewport: { width: 1440, height: 960 }, locale: 'en-US' }) tripwire = watchConsole(page) await page.exposeFunction('nativeLocaleRead', () => { const section = scaffold.ctx.settings.describe().find(row => row.ns === 'locale')!.value as { preference?: string } - return { languages: ['ja-JP', 'zh-CN', 'en-US'], preference: section.preference ?? null } + return { languages, preference: section.preference ?? null } }) await page.exposeFunction('nativeLocaleChanged', (locale: string) => { reported.push(locale) }) await page.addInitScript(() => { @@ -54,13 +55,23 @@ describe.skipIf(MODE === 'record')('web e2e: native and Client locale preference it('uses OS languages without saving them, then shares an explicit choice across reloads', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-desktop-locale')) await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' }) + await openSettings(page, 'zh') + const zhDialog = page.getByRole('dialog', { name: '设置', exact: true }) + await zhDialog.getByRole('button', { name: '中文', exact: true }).waitFor() + await expect.poll(() => reported).toEqual(['zh']) + expect(scaffold.ctx.settings.describe().find(row => row.ns === 'locale')!.value).toEqual({}) + await compareOrRefreshGolden(join(SNAPSHOT_DIR, 'automatic.expected.md'), + await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd, versionCapture), MODE) + languages = ['ja-JP', 'zh-CN', 'en-US'] + const automaticWarnings = tripwire.warnings.length + reported.length = 0 + await page.reload({ waitUntil: 'load' }) + acknowledgeReloadConnectionLoss(tripwire, automaticWarnings) await openSettings(page, 'ja') const jaDialog = page.getByRole('dialog', { name: '設定', exact: true }) await jaDialog.getByRole('button', { name: '日本語', exact: true }).waitFor() await expect.poll(() => reported).toEqual(['ja']) expect(scaffold.ctx.settings.describe().find(row => row.ns === 'locale')!.value).toEqual({}) - await compareOrRefreshGolden(join(SNAPSHOT_DIR, 'automatic.expected.md'), - await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd, versionCapture), MODE) await jaDialog.getByRole('button', { name: '日本語', exact: true }).click() await page.getByRole('menuitem', { name: 'English', exact: true }).click() const enDialog = page.getByRole('dialog', { name: 'Settings', exact: true }) @@ -92,8 +103,26 @@ describe.skipIf(MODE === 'record')('web e2e: native and Client locale preference await jaDialog.getByRole('button', { name: '日本語', exact: true }).waitFor() await expect.poll(() => reported).toEqual(['ja']) expect(await page.evaluate(() => document.documentElement.lang)).toBe('ja') - await page.setViewportSize({ width: 390, height: 844 }) - await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true) + const overflows = (): Promise => jaDialog.evaluate((dialog) => { + const containers = [dialog, ...dialog.querySelectorAll('*')] + .filter(element => element === dialog || ['auto', 'scroll'].includes(getComputedStyle(element).overflowX)) + return containers.filter(element => element.clientWidth > 0 && element.scrollWidth > element.clientWidth + 1).length + }) + await expect.poll(overflows).toBe(0) + await jaDialog.evaluate((dialog) => { + const options = dialog.querySelector('nav')!.nextElementSibling!.lastElementChild! + const probe = document.createElement('div') + probe.dataset.localeOverflowProbe = '' + probe.style.width = '2000px' + probe.style.height = '1px' + options.append(probe) + }) + try { + expect(await overflows()).toBeGreaterThan(0) + } finally { + await jaDialog.locator('[data-locale-overflow-probe]').evaluate((element) => { element.remove() }) + } + await expect.poll(overflows).toBe(0) expect(tripwire.warnings).toEqual([]) expect(tripwire.pageErrors).toEqual([]) diff --git a/apps/web/tests/expected/desktop-locale/automatic.expected.md b/apps/web/tests/expected/desktop-locale/automatic.expected.md index 7d02aedd93..84a652e494 100644 --- a/apps/web/tests/expected/desktop-locale/automatic.expected.md +++ b/apps/web/tests/expected/desktop-locale/automatic.expected.md @@ -1,40 +1,40 @@ -- dialog "設定": +- dialog "设置": - navigation: - - text: 設定 - - button "一般" - - button "モデル" - - button "組み込みプラグイン" - - button "エージェントプリセット" - - button "設定ファイルを開く" - - button "閉じる" - - text: 権限 新しいセッションのデフォルトの権限モードを選択 - - button "ワークスペース書き込み" - - text: 言語 - - button "日本語" - - text: 外観 - - button "ライト" - - button "ダーク" - - button "システム" [pressed] - - text: フォントサイズ 会話コンテンツにのみ適用 14 - - button "フォントサイズを大きくする" - - button "フォントサイズを小さくする" + - text: 设置 + - button "通用设置" + - button "模型" + - button "内置插件" + - button "Agent 预设" + - button "打开配置文件" + - button "关闭" + - text: 权限 选择新会话的默认权限模式 + - button "工作区内修改" + - text: 语言 + - button "中文" + - text: 外观 + - button "浅色" + - button "深色" + - button "跟随系统" [pressed] + - text: 字号大小 仅影响会话内容的字号 14 + - button "增大字号" + - button "减小字号" - text: px - - button "その他のフォント設定" - - text: 作業の詳細 ツール呼び出しの表示詳細度を選択 - - button "標準" - - text: 作業詳細を折りたたむタイミング 作業詳細を自動で折りたたむタイミングを選択 - - button "完了時" - - text: 折りたたみ後の返信 作業詳細の折りたたみ時に表示するアシスタントの返信を選択 - - button "すべての返信" - - text: コーディングビューを表示 軌跡、コード差分、すべてのエージェントプリセットを表示します - - switch "コーディングビューを表示" [checked] - - text: キーボードショートカット - - paragraph: 利用可能なショートカットと入力アクションを表示・編集 - - button "ショートカットを編集" - - text: 実行中の送信動作 エージェントの実行中にEnterキーと送信ボタンが行う動作。Cmd/Ctrl+Enterではもう一方の動作になります - - button "キュー" - - text: パフォーマンスと使用状況 パフォーマンスと使用状況の表示量を選択 - - button "詳細" - - text: 公式モデル API 使用時にセッションログをアップロード DeepSeek のモデルと製品の改善にご協力ください。 - - switch "公式モデル API 使用時にセッションログをアップロード" - - text: "現在のバージョン: {{version}}" + - button "更多字体设置" + - text: 工作步骤展示 选择希望看到多少工具调用细节 + - button "标准" + - text: 工作步骤收起时机 选择何时自动收起工作步骤 + - button "回答结束后" + - text: 收起后显示的回复 工作步骤收起后,保留哪些助手回复 + - button "全部回复" + - text: 显示代码工作视图 开启后,显示轨迹、本轮代码差异,可选择完整的 Agent 预设切换 + - switch "显示代码工作视图" [checked] + - text: 快捷键 + - paragraph: 查看和编辑当前可用的快捷键和输入操作 + - button "编辑快捷键" + - text: 繁忙时的发送行为 智能体运行时 Enter 键和发送按钮的行为;Cmd/Ctrl+Enter 使用另一行为 + - button "排队发送" + - text: 性能与用量 选择性能与用量信息展示的详细程度 + - button "详细" + - text: 在使用官方模型 API 时上传 Session Log 帮助改进 DeepSeek 模型与产品 + - switch "在使用官方模型 API 时上传 Session Log" + - text: 当前版本:{{version}} diff --git a/apps/web/tests/ptc-round.e2e.ts b/apps/web/tests/ptc-round.e2e.ts index c7fe4ebc7c..1a14c2fedc 100644 --- a/apps/web/tests/ptc-round.e2e.ts +++ b/apps/web/tests/ptc-round.e2e.ts @@ -36,7 +36,6 @@ describe('web e2e: PTC mode round renders nested sub-calls', () => { beforeAll(async () => { scaffold = await launchWebScaffold({ agentPresets: { default: 'ptc' }, - extraOverlayPath: fileURLToPath(new URL('./pin-browse-picker.overlay.yml', import.meta.url)), compareReplaySession: true, ...(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 }), }) @@ -276,7 +275,6 @@ describe('web e2e: PTC mode round renders nested sub-calls', () => { expect(await wrap.getAttribute('aria-pressed')).toBe('true') }) - it.skipIf(MODE === 'record')('renders the same recorded tool round in Japanese without translating model or tool data', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-ptc-japanese')) await openSettings(page, 'en') diff --git a/docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.i18n.yaml new file mode 100644 index 0000000000..a13ae70849 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.i18n.yaml @@ -0,0 +1,16 @@ +# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading +# section, a hash of its English and Chinese blocks outside code blocks and generated regions. +# After editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.md +/: + en: 383326881143f69b + zh: cd7ff0e2b97f9c2f +/japanese-is-a-built-in-language: + en: f97bae63a42a3627 + zh: cec418f8bf6ac168 +/japanese-is-a-built-in-language/change: + en: feb1f2d1cafdcfe0 + zh: 94d527865ec108bb +/japanese-is-a-built-in-language/migration: + en: ced2c2baad9124b4 + zh: 7b0b5b0e345994b8 diff --git a/docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.md b/docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.md new file mode 100644 index 0000000000..256e77ae05 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "Built-in Japanese registrations conflict with third-party Japanese language packs." +--- + +# Japanese is a built-in language + +English | [中文](guide.zh.md) + +## Change + +The locale plugin registers the `ja` language and Japanese dictionaries for `common` and `settings.locale`. Third-party language packs following the previous Japanese README example now fail with an already-registered language or dictionary error. + +## Migration + +1. In the language pack's client entry, remove `ctx.locale.addLanguage({ id: 'ja', ... })` and registrations for `common` and `settings.locale` under `ja`. Remove other Japanese dictionary registrations whose namespaces are now supplied by built-in plugins; keep translations for namespaces owned by your own plugins. +2. If the pack represents a distinct language variant, register a separate BCP 47-style id, such as `ja-JP`, for both the definition and its dictionaries instead of `ja`. +3. Reload the application with the pack enabled. Confirm it starts without duplicate-registration errors and that selecting the intended language displays its translations. diff --git a/docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.zh.md new file mode 100644 index 0000000000..80382ebe9e --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.2/built-in-japanese/guide.zh.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "内置日语注册会与第三方日语语言包冲突。" +--- + +# 日语成为内置语言 + +[English](guide.md) | 中文 + +## 变更 + +locale 插件注册 `ja` 语言,以及 `common` 和 `settings.locale` 的日文字典。按此前 README 日语示例编写的第三方语言包会因语言或字典已注册而加载失败。 + +## 迁移 + +1. 在语言包的 client 入口中,删除 `ctx.locale.addLanguage({ id: 'ja', ... })`,以及 `common` 和 `settings.locale` 下的 `ja` 注册。删除其他已由内置插件提供的命名空间的日语字典注册;保留自己插件所拥有命名空间的翻译。 +2. 如果语言包代表不同的语言变体,为语言定义及其字典注册独立的 BCP 47 风格 id,例如 `ja-JP`,代替 `ja`。 +3. 启用语言包并重新加载应用。确认启动时没有重复注册错误,且选择目标语言后显示对应译文。 diff --git a/packages/client/locale/README.i18n.yaml b/packages/client/locale/README.i18n.yaml index d6fe556835..813be8e1eb 100644 --- a/packages/client/locale/README.i18n.yaml +++ b/packages/client/locale/README.i18n.yaml @@ -21,8 +21,8 @@ en: 8527d77583824eff zh: 2ac53ced0a32ba19 /deepseek-ai-dsh-client-locale/use-this-package/registering-a-dictionary: - en: c1b4f667e35cb24b - zh: 39b6df8350986cd5 + en: 69b07cde5b2b4deb + zh: b8204c127b10a711 /deepseek-ai-dsh-client-locale/use-this-package/resolving-package-text: en: 3b63e84fe0fdf8f4 zh: e97b5d1b92dff7ec @@ -43,7 +43,7 @@ zh: 2db822b746a6471d /deepseek-ai-dsh-client-locale/understand-the-implementation/dictionary-lookup: en: 68373e745761494c - zh: 8452ac63ee148c61 + zh: 43a5b8c19d03268a /deepseek-ai-dsh-client-locale/understand-the-implementation/source-map: en: fc93a8f6804aa5bd zh: 9d8364dc4c03d055 diff --git a/packages/client/locale/README.md b/packages/client/locale/README.md index b0eb13fa17..1d39db9e0c 100644 --- a/packages/client/locale/README.md +++ b/packages/client/locale/README.md @@ -35,7 +35,7 @@ Native shells may provide `__DSH_LOCALE__` with an asynchronous `read()` and an ### Registering a dictionary -Call `ctx.locale.register(ns, { zh, en })` with a namespace merged into `LocaleNamespaceMap`; the compiler checks every key against the namespace's typed key union and requires both baseline locales. Consumers translate through `ctx.locale.bind(ns)` or the framework-injected `t` seat. Japanese dictionaries check their keys against English with TypeScript. A dictionary registered after the UI is already mounted is picked up without a remount. +Call `ctx.locale.register(ns, { zh, en, ja })` with a namespace merged into `LocaleNamespaceMap`; the compiler checks every key against the namespace's typed key union and requires both baseline locales (`zh` and `en`). Additional locale ids are optional, and their dictionaries are checked against the same namespace keys. Consumers translate through `ctx.locale.bind(ns)` or the framework-injected `t` seat. A dictionary registered after the UI is already mounted is picked up without a remount. ### Resolving package text diff --git a/packages/client/locale/README.zh.md b/packages/client/locale/README.zh.md index 243d431829..b4d1cac4bf 100644 --- a/packages/client/locale/README.zh.md +++ b/packages/client/locale/README.zh.md @@ -35,7 +35,7 @@ kind: "package-reference" ### 注册字典 -用已合并进 `LocaleNamespaceMap` 的命名空间调用 `ctx.locale.register(ns, { zh, en })`;编译器会对照该命名空间的类型化键并集检查每个键,并要求两个基础 locale 齐全。消费方通过 `ctx.locale.bind(ns)` 或框架注入的 `t` 席位翻译。日文字典通过 TypeScript 对照英文检查键。UI 已挂载后再注册的字典无需重新挂载即可生效。 +用已合并进 `LocaleNamespaceMap` 的命名空间调用 `ctx.locale.register(ns, { zh, en, ja })`;编译器会对照该命名空间的类型化键并集检查每个键,并要求两个基础 locale(`zh` 和 `en`)齐全。其他 locale id 为可选项,其字典同样按该命名空间的键检查。消费方通过 `ctx.locale.bind(ns)` 或框架注入的 `t` 席位翻译。UI 已挂载后再注册的字典无需重新挂载即可生效。 ### 解析包文本 @@ -91,7 +91,7 @@ export function apply(ctx) { 文档语言同步只在值变化时写入 ``;仅更新字典的 revision 不改动该属性。 -带类型的对象形式要求两个内置 locale 都有完整字典;逐 locale 形式允许语言包独立注册每个命名空间。逐键查找会先在请求命名空间中沿生效语言声明的 fallback 链查找,再在 `common` 中重复该链,最后显示键本身。绑定的翻译函数按命名空间保持稳定身份,因此可通过 inject 机制传递,且不会破坏 memoization。 +带类型的对象形式要求两个基础 locale 都有完整字典;逐 locale 形式允许语言包独立注册每个命名空间。逐键查找会先在请求命名空间中沿生效语言声明的 fallback 链查找,再在 `common` 中重复该链,最后显示键本身。绑定的翻译函数按命名空间保持稳定身份,因此可通过 inject 机制传递,且不会破坏 memoization。 ### 源码地图 diff --git a/packages/client/locale/src/client/index.ts b/packages/client/locale/src/client/index.ts index 1c2b2bc27d..c6484b8908 100644 --- a/packages/client/locale/src/client/index.ts +++ b/packages/client/locale/src/client/index.ts @@ -383,10 +383,13 @@ export class LocaleRuntime { * namespace's texts have one owner). Registration bumps the revision so * mounted outlets pick up late-arriving dictionaries. * @param ns - a namespace merged into LocaleNamespaceMap. - * @param dicts - complete dictionaries keyed by built-in locale id. + * @param dicts - complete baseline dictionaries and optional dictionaries for additional locale ids. * @returns disposer removing every locale registered by this call (idempotent). */ - register>(ns: N, dicts: Record>): () => void + register>( + ns: N, + dicts: Record> & Record>, + ): () => void /** * Single-locale untyped form for language-pack contributions and namespaces * outside the merge table. @@ -577,10 +580,8 @@ export async function apply(ctx: ClientContext): Promise { const host = ctx.configForms.get(LOCALE_SETTINGS_NAMESPACE) const locale = new LocaleRuntime(ctx, host, bootstrap) ctx.effect(() => locale.addLanguage({ id: 'ja', label: '日本語', fallback: 'en' }), 'locale: Japanese language') - const commonDictionaries = { zh, en, ja } - const settingsDictionaries = { zh: settingsZh, en: settingsEn, ja: settingsJa } - ctx.effect(() => locale.register(COMMON_NS, commonDictionaries), 'locale: common dictionaries') - ctx.effect(() => locale.register(SETTINGS_NS, settingsDictionaries), 'locale: settings dictionaries') + ctx.effect(() => locale.register(COMMON_NS, { zh, en, ja }), 'locale: common dictionaries') + ctx.effect(() => locale.register(SETTINGS_NS, { zh: settingsZh, en: settingsEn, ja: settingsJa }), 'locale: settings dictionaries') ctx.provide('locale', locale) if (bridge !== undefined) { ctx.on('locale/change', (snapshot) => { bridge.onChange(snapshot.active) }) diff --git a/packages/client/locale/tests/apply.client.spec.ts b/packages/client/locale/tests/apply.client.spec.ts index fa6c28210b..97209c4ab3 100644 --- a/packages/client/locale/tests/apply.client.spec.ts +++ b/packages/client/locale/tests/apply.client.spec.ts @@ -192,7 +192,6 @@ describe('locale apply', () => { expect(instance.getSnapshot().options.map(option => option.id)).toEqual(['zh', 'en', 'ja']) }) - it('restores Japanese from the Host and releases its language and dictionaries on unload', async () => { const b = await bench('ja') onTestFinished(() => b.ctx.fiber.dispose()) diff --git a/packages/client/locale/tests/locale.client.spec.ts b/packages/client/locale/tests/locale.client.spec.ts index 62926bb2f9..07102e3e1f 100644 --- a/packages/client/locale/tests/locale.client.spec.ts +++ b/packages/client/locale/tests/locale.client.spec.ts @@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Context } from '@deepseek-ai/cordis' import { stubConfigForm, type StubConfigForm } from '@deepseek-ai/dsh-client-test-runtime' import type { LocaleSettings, LocaleSnapshot } from '@deepseek-ai/dsh-client-locale/client' +import { zh, en, ja } from '../src/locales/settings.ts' import { FALLBACK_LOCALE, LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client' const make = (host?: StubConfigForm): { ctx: Context @@ -36,6 +37,24 @@ describe('LocaleRuntime', () => { vi.unstubAllGlobals() }) + it('checks additional dictionaries against the declared namespace and requires both baseline locales', () => { + const { svc } = make() + svc.addLanguage({ id: 'ja', label: '日本語', fallback: 'en' }) + const dispose = svc.register('settings.locale', { zh, en, ja, fr: ja }) + svc.setLocale('ja') + expect(svc.bind('settings.locale')('language.title')).toBe('言語') + dispose() + expect(svc.bind('settings.locale')('language.title')).toBe('language.title') + if (false) { + // @ts-expect-error additional locales require the declared namespace's keys + svc.register('settings.locale', { zh, en, ja: { other: '別' } }) + // @ts-expect-error additional locales cannot omit a namespace key + svc.register('settings.locale', { zh, en, ja: {} }) + // @ts-expect-error additional locales do not replace the English baseline + svc.register('settings.locale', { zh, ja }) + } + }) + it('translates through the active-locale -> en -> key chain', () => { const { svc } = make() svc.register('ns', 'zh', { hello: '你好' }) diff --git a/packages/client/ui-agent-preset/src/client/index.ts b/packages/client/ui-agent-preset/src/client/index.ts index 3cd120549e..1e347d93d0 100644 --- a/packages/client/ui-agent-preset/src/client/index.ts +++ b/packages/client/ui-agent-preset/src/client/index.ts @@ -127,8 +127,7 @@ export function apply(ctx: ClientContext): void { return binding === undefined ? undefined : seatFor(binding) } - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register('settings.agentPreset', dictionaries), 'ui-agent-preset: settings row dictionaries') + ctx.effect(() => ctx.locale.register('settings.agentPreset', { zh, en, ja }), 'ui-agent-preset: settings row dictionaries') ctx.effect(() => { let requested = 0 diff --git a/packages/client/ui-approval/src/client/index.ts b/packages/client/ui-approval/src/client/index.ts index abbbdbad8a..2f5ef19051 100644 --- a/packages/client/ui-approval/src/client/index.ts +++ b/packages/client/ui-approval/src/client/index.ts @@ -75,8 +75,7 @@ async function answerApproval( * @param ctx - Client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-approval: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-approval: dictionaries') ctx.inject(['shortcuts'], (scope) => { const t = ctx.locale.bind(NS) scope.effect(() => scope.shortcuts.registerFixed({ diff --git a/packages/client/ui-chat/src/client/apply.ts b/packages/client/ui-chat/src/client/apply.ts index cb9415d72a..c08c981599 100644 --- a/packages/client/ui-chat/src/client/apply.ts +++ b/packages/client/ui-chat/src/client/apply.ts @@ -133,8 +133,7 @@ export function apply(ctx: Context): void { resolve: binding => ({ hooks: { chat: chatSource(binding) } }), }) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-chat: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-chat: dictionaries') const t = ctx.locale.bind(NS) const chatStore = createChatStore() const chatScrollPositions = new Map() diff --git a/packages/client/ui-commands/src/client/index.ts b/packages/client/ui-commands/src/client/index.ts index 5518a927ff..fc098764a4 100644 --- a/packages/client/ui-commands/src/client/index.ts +++ b/packages/client/ui-commands/src/client/index.ts @@ -56,8 +56,7 @@ export const inject = ['inputTriggers', 'sessions', 'remote', 'remote.commands', * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-commands: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-commands: dictionaries') ctx.plugin(CommandUiRuntime) ctx.inject(['slots', 'commandUi', 'sessions'], (scope: ClientContext) => { const command = scope.commandUi diff --git a/packages/client/ui-conversation/src/client/apply.ts b/packages/client/ui-conversation/src/client/apply.ts index 8c375e9aad..9ca1c14380 100644 --- a/packages/client/ui-conversation/src/client/apply.ts +++ b/packages/client/ui-conversation/src/client/apply.ts @@ -185,8 +185,7 @@ export function apply(ctx: Context, config: Config = Config({})): void { yield slots.register({ name: 'conversation.user.text.node', key: 'session', priority: 100 }, SessionReference) }) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-conversation: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-conversation: dictionaries') const t = ctx.locale.bind(NS) const conversationStore = createConversationStore() const submissionPolicy = new ComposerSubmissionPolicy( diff --git a/packages/client/ui-conversation/src/client/locales.ts b/packages/client/ui-conversation/src/client/locales.ts index 15e086848c..5d9e9a66c3 100644 --- a/packages/client/ui-conversation/src/client/locales.ts +++ b/packages/client/ui-conversation/src/client/locales.ts @@ -771,7 +771,7 @@ export const en = { /** Japanese copy, checked against the English keys. */ export const ja = { 'shortcut.newline': '改行', - 'shortcut.complementary': '補助のキュー / ステア操作を使用', + 'shortcut.complementary': '補助のキュー / 実行中の指示変更を使用', 'shortcut.slash': 'コマンドメニューを開く', 'shortcut.mention': '参照メニューを開く', 'hint.plan': '計画を生成するにはタスクを説明してください', @@ -783,13 +783,13 @@ export const ja = { 'placeholder.parentOffline': '親セッションがオフラインです。送信はできませんが、実行は停止できます', 'placeholder.hero': '作りたいものを説明してください。/ コマンド、@ ファイルやセッション', 'placeholder.workspace': '開始するワークスペースを選択', - 'placeholder.steerQueue': 'Cmd/Ctrl+Enterでキュー内のすべてのメッセージをステア', + 'placeholder.steerQueue': 'Cmd/Ctrl+Enterでキュー内のすべてのメッセージを実行中の指示に追加', 'input.commands': 'ファイルを追加またはコマンドを実行', 'input.file': 'ファイル', 'input.stop': '生成を停止', 'input.send': 'メッセージを送信', 'input.send.queue': 'メッセージをキューに追加', - 'input.send.steer': 'メッセージをステア', + 'input.send.steer': '実行中の指示にメッセージを追加', 'attachment.pending': '保留中の添付ファイル', 'attachment.scrollLeft': '添付ファイルを左にスクロール', 'attachment.scrollRight': '添付ファイルを右にスクロール', @@ -834,7 +834,7 @@ export const ja = { 'settings.enter.title': '実行中の送信動作', 'settings.enter.description': 'エージェントの実行中にEnterキーと送信ボタンが行う動作。Cmd/Ctrl+Enterではもう一方の動作になります', 'settings.enter.queue': 'キュー', - 'settings.enter.steer': 'ステア', + 'settings.enter.steer': '指示を変更', 'hero.headline': '未知への挑戦', 'hero.preview': 'プレビュー', 'hero.chooseWorkspace': 'ワークスペースを選択', @@ -865,10 +865,10 @@ export const ja = { 'todo.diff.compare': '前回のリストからの変更', 'todo.diff.unavailable': '前回のリストは利用できません', 'todo.diff.noChanges': 'リストに変更はありません', - 'todo.diff.added': '{count} 件追加', - 'todo.diff.updated': '{count} 件更新', - 'todo.diff.removed': '{count} 件削除', - 'todo.diff.unchanged': '{count} 件変更なし', + 'todo.diff.added': '{count}件追加', + 'todo.diff.updated': '{count}件更新', + 'todo.diff.removed': '{count}件削除', + 'todo.diff.unchanged': '{count}件変更なし', 'todo.diff.addedItem': '追加', 'todo.diff.updatedItem': 'ステータス変更', 'todo.diff.movedItem': '順序変更', @@ -893,7 +893,7 @@ export const ja = { 'detail.schedule.overdue': '期限超過、セッション再開待ち', 'detail.schedule.empty': 'リマインダーなし', 'detail.schedule.deleted': '削除済み', - 'detail.schedule.count': '{count} 件のリマインダー', + 'detail.schedule.count': '{count}件のリマインダー', 'detail.schedule.daily': '毎日 {time} ({zone})', 'detail.schedule.weekly': '毎週 {days} の {time} ({zone})', 'detail.schedule.cron': 'Cron {expression} ({zone})', @@ -944,7 +944,7 @@ export const ja = { 'detail.none': 'なし', 'detail.yes': 'はい', 'detail.no': 'いいえ', - 'detail.moreInInspect': 'Inspect で残り {count} 件を表示できます', + 'detail.moreInInspect': 'Inspect で残り {count}件を表示できます', 'detail.status.running': '実行中', 'detail.status.idle': '待機中', 'detail.status.ready': '準備完了', @@ -1012,7 +1012,7 @@ export const ja = { 'detail.terminals.count': '{count} ターミナル', 'detail.tasks.count': '{count} チームタスク', 'detail.tasks.nextPage': 'さらにタスクがあります。次のカーソルは {cursor} です', - 'detail.locations.count': '{count} 件の場所', + 'detail.locations.count': '{count}件の場所', 'detail.location': '{line} 行目、{column} 列目', 'detail.receipt.delivered': 'メッセージを配信しました', 'detail.receipt.interrupt': '中断を要求しました', @@ -1030,8 +1030,8 @@ export const ja = { 'detail.models.title': '利用可能なモデル', 'detail.output.lines': '{total} 行中 {begin}–{end} 行', 'detail.output.truncated': '出力は切り詰められています', - 'detail.providers.count': '{count} 件の inspect プロバイダー', - 'detail.plugins.count': '{count} 件の動的プラグイン', + 'detail.providers.count': '{count}件の inspect プロバイダー', + 'detail.plugins.count': '{count}件の動的プラグイン', 'detail.workflow.script': 'ワークフロースクリプト', 'detail.ralph.reportedComplete': 'ワーカーが完了を報告', 'detail.ralph.reportedBlocker': 'ワーカーがブロッカーを報告', @@ -1044,7 +1044,7 @@ export const ja = { 'detail.trace.derived': '派生イベント', 'detail.trace.ancestors': '祖先セッション', 'detail.trace.descendants': '子孫セッション', - 'detail.matches.count': '{count} 件の一致', + 'detail.matches.count': '{count}件の一致', 'detail.matches.capped': '結果の上限に達しました。さらに絞り込んでください', 'detail.event.neighbors': '周辺のイベント', 'todo.completed': '{done}/{total} 完了', @@ -1125,12 +1125,12 @@ export const ja = { 'queue.save': 'キュー済みメッセージを保存', 'queue.cancelEdit': '編集をキャンセル', 'queue.remove': 'キュー済みメッセージを削除', - 'queue.steer': 'キュー済みメッセージをステア', - 'queue.steer.unavailable': 'ステアはエージェントの実行中のみ利用できます', + 'queue.steer': 'キュー内のメッセージを実行中の指示に追加', + 'queue.steer.unavailable': '指示の変更はエージェントの実行中のみ利用できます', 'error.sessionInUse': 'このセッションはすでに使用中です。別の実行中のDSHインスタンス(dsh webやデスクトップアプリなど)が使用している可能性があります。他のDSHインスタンスを終了して再試行してください。', 'queue.editFailed': '編集に失敗しました。このメッセージはすでに送信が開始されている可能性があります。', 'queue.removeFailed': '削除に失敗しました。このメッセージはすでに送信が開始されている可能性があります。', - 'queue.steerFailed': 'ステアに失敗しました。再試行してください。', + 'queue.steerFailed': '指示の変更に失敗しました。再試行してください。', 'terminal.signal': 'シグナル {signal}', 'terminal.exitCode': '終了コード {code}', 'terminal.noExitCode': '終了コードなし', diff --git a/packages/client/ui-deepseek-model-label-1017/src/client/index.ts b/packages/client/ui-deepseek-model-label-1017/src/client/index.ts index d7de8644d1..5a38858688 100644 --- a/packages/client/ui-deepseek-model-label-1017/src/client/index.ts +++ b/packages/client/ui-deepseek-model-label-1017/src/client/index.ts @@ -22,8 +22,7 @@ export const inject = ['slots', 'locale'] */ export function apply(ctx: Context): void { const namespace = 'deepseek-model-label-1017' - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(namespace, dictionaries), 'deepseek-model-label-1017: dictionaries') + ctx.effect(() => ctx.locale.register(namespace, { zh, en, ja }), 'deepseek-model-label-1017: dictionaries') ctx.slots.inject('conversation.input.model.label', () => ctx.slots.register({ name: 'conversation.input.model.label', locale: namespace, }, DeepSeekModelLabel)) diff --git a/packages/client/ui-deliverables/src/client/index.ts b/packages/client/ui-deliverables/src/client/index.ts index e7bc597e27..bff50e88cd 100644 --- a/packages/client/ui-deliverables/src/client/index.ts +++ b/packages/client/ui-deliverables/src/client/index.ts @@ -57,8 +57,7 @@ export function apply(ctx: ClientContext): void { diffs.reset() }) ctx.uiConversation.events.register(deliverablesDefinition) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-deliverables: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-deliverables: dictionaries') ctx.slots.inject( 'conversation.chat.turnTail', () => ctx.slots.register({ diff --git a/packages/client/ui-entry-customization/src/client/index.ts b/packages/client/ui-entry-customization/src/client/index.ts index 35a62e269e..8ca5d3b9ee 100644 --- a/packages/client/ui-entry-customization/src/client/index.ts +++ b/packages/client/ui-entry-customization/src/client/index.ts @@ -25,8 +25,7 @@ export const inject = ['slots', 'locale', 'pluginNavigation', 'uiWorkspace'] * @param ctx - Browser context with slot, locale, plugin navigation, and Workspace services. */ export function apply(ctx: Context): void { - const dictionaries = { en, zh, ja } - ctx.effect(() => ctx.locale.register('entryCustomization', dictionaries)) + ctx.effect(() => ctx.locale.register('entryCustomization', { en, zh, ja })) const t = ctx.locale.bind('entryCustomization') const injected = (region: EntryRegion): EntryMenuInjected => ({ region, diff --git a/packages/client/ui-goal/src/client/index.ts b/packages/client/ui-goal/src/client/index.ts index 44f1c7ec69..a38de98032 100644 --- a/packages/client/ui-goal/src/client/index.ts +++ b/packages/client/ui-goal/src/client/index.ts @@ -70,8 +70,7 @@ export const inject = ['slots', 'sessions', 'remote', 'remote.goals', 'locale', export function apply(ctx: ClientContext): void { ctx.uiConversation.events.register(goalCommandInputDefinition) ctx.uiConversation.userText.register(goalCommandReferenceRule) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-goal: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-goal: dictionaries') ctx.slots.inject('conversation.user.text.node', () => ctx.slots.register({ name: 'conversation.user.text.node', diff --git a/packages/client/ui-input-trigger/src/client/index.ts b/packages/client/ui-input-trigger/src/client/index.ts index 02e6a8d8dd..cad11a5452 100644 --- a/packages/client/ui-input-trigger/src/client/index.ts +++ b/packages/client/ui-input-trigger/src/client/index.ts @@ -58,8 +58,7 @@ export const inject = ['sessions', 'locale'] */ export function apply(ctx: ClientContext): void { ctx.plugin(InputTriggerService) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(MENU_NS, dictionaries), 'ui-input-trigger: menu dictionaries') + ctx.effect(() => ctx.locale.register(MENU_NS, { zh, en, ja }), 'ui-input-trigger: menu dictionaries') ctx.inject(['slots', 'inputTriggers', 'sessions'], (scope: ClientContext) => { const inputTriggers = scope.inputTriggers const sessions = scope.sessions diff --git a/packages/client/ui-jobs/src/client/index.ts b/packages/client/ui-jobs/src/client/index.ts index 9b46647964..eb5f4d920c 100644 --- a/packages/client/ui-jobs/src/client/index.ts +++ b/packages/client/ui-jobs/src/client/index.ts @@ -31,8 +31,7 @@ export const inject = ['jobs', 'slots', 'locale'] * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-jobs: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-jobs: dictionaries') ctx.slots.inject( 'conversation.session.header.actions', () => ctx.slots.register({ diff --git a/packages/client/ui-layout/src/client/index.ts b/packages/client/ui-layout/src/client/index.ts index 4e6adbaf41..fb66376cfb 100644 --- a/packages/client/ui-layout/src/client/index.ts +++ b/packages/client/ui-layout/src/client/index.ts @@ -156,8 +156,7 @@ export const inject = ['slots', 'theme', 'locale', 'shortcuts'] * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register('shortcuts.layout', dictionaries), 'layout: command labels') + ctx.effect(() => ctx.locale.register('shortcuts.layout', { zh, en, ja }), 'layout: command labels') const t = ctx.locale.bind('shortcuts.layout') ctx.effect(() => { diff --git a/packages/client/ui-message-feedback/src/client/index.ts b/packages/client/ui-message-feedback/src/client/index.ts index a251d153fe..7d95fe469a 100644 --- a/packages/client/ui-message-feedback/src/client/index.ts +++ b/packages/client/ui-message-feedback/src/client/index.ts @@ -66,8 +66,7 @@ export const inject = ['slots', 'remote', 'remote.messageFeedback', 'remote.sess * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-message-feedback: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-message-feedback: dictionaries') const surfaces = new Map() const surfaceFor = (sessionId: SessionId): FeedbackSurface => { diff --git a/packages/client/ui-model-selection/src/client/index.ts b/packages/client/ui-model-selection/src/client/index.ts index c99d6116b4..ae47f156ed 100644 --- a/packages/client/ui-model-selection/src/client/index.ts +++ b/packages/client/ui-model-selection/src/client/index.ts @@ -113,8 +113,7 @@ export const inject = ['commandUi', 'locale', 'sessions', 'slots', 'remote', 're * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-model-selection: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-model-selection: dictionaries') // Non-slot faces (the command description, the popup option builder) read // through the bound translate; the seat component reads the standard seat. diff --git a/packages/client/ui-open-in-app/src/client/index.ts b/packages/client/ui-open-in-app/src/client/index.ts index e4b6f8ec6f..db99a2a23e 100644 --- a/packages/client/ui-open-in-app/src/client/index.ts +++ b/packages/client/ui-open-in-app/src/client/index.ts @@ -51,8 +51,7 @@ export function apply(ctx: ClientContext): void { const controller = new OpenInAppController() void controller.load() const paths = new OpenInAppPathController(ctx.remote) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'open-in-app: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'open-in-app: dictionaries') const t = ctx.locale.bind(NS) const target = () => { if (ctx.layout.panelInfo.getSnapshot().activePanelId !== null) return undefined diff --git a/packages/client/ui-permission-presets/src/client/index.ts b/packages/client/ui-permission-presets/src/client/index.ts index 608815b08b..940b5baa3f 100644 --- a/packages/client/ui-permission-presets/src/client/index.ts +++ b/packages/client/ui-permission-presets/src/client/index.ts @@ -142,8 +142,7 @@ export function apply(ctx: ClientContext): void { 'ui-permission: dismiss stale slash choices', ) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register('settings.permission', dictionaries), 'ui-permission: settings row dictionaries') + ctx.effect(() => ctx.locale.register('settings.permission', { zh, en, ja }), 'ui-permission: settings row dictionaries') // The shared ConfigForm mirror updates after document commits and reconnects. const controller = new PermissionPresetSettingsController( diff --git a/packages/client/ui-plan/src/client/index.ts b/packages/client/ui-plan/src/client/index.ts index fdf5160457..1b226f8a37 100644 --- a/packages/client/ui-plan/src/client/index.ts +++ b/packages/client/ui-plan/src/client/index.ts @@ -56,8 +56,7 @@ export const inject = ['slots', 'remote', 'remote.commands', 'remote.session', ' * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-plan: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-plan: dictionaries') const previewId = '@deepseek-ai/dsh-client-ui-plan' const t = ctx.locale.bind(NS) diff --git a/packages/client/ui-plugin-manager/src/client/index.ts b/packages/client/ui-plugin-manager/src/client/index.ts index 0830df0425..52506f9fb7 100644 --- a/packages/client/ui-plugin-manager/src/client/index.ts +++ b/packages/client/ui-plugin-manager/src/client/index.ts @@ -88,8 +88,7 @@ export const inject = ['slots', 'locale', 'remote', 'remote.pluginManager', 'rem * @param ctx - the browser plugin context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-plugin-manager: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-plugin-manager: dictionaries') const t = ctx.locale.bind(NS) const controller = new PluginManagerController(ctx) ctx.effect(() => () => { controller.dispose() }, 'ui-plugin-manager: controller') diff --git a/packages/client/ui-plugin-market/src/client/index.ts b/packages/client/ui-plugin-market/src/client/index.ts index 4bff318749..c1aab2c746 100644 --- a/packages/client/ui-plugin-market/src/client/index.ts +++ b/packages/client/ui-plugin-market/src/client/index.ts @@ -36,8 +36,7 @@ export const inject = ['slots', 'locale', 'remote', 'remote.pluginMarket', 'remo * @param ctx - the browser plugin context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-plugin-market: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-plugin-market: dictionaries') const controller = new DiscoverController(ctx, () => document.baseURI) ctx.effect(() => () => { controller.dispose() }, 'ui-plugin-market: controller') ctx.effect(() => { diff --git a/packages/client/ui-reference/src/client/index.ts b/packages/client/ui-reference/src/client/index.ts index 11363a313d..4cfd6e8677 100644 --- a/packages/client/ui-reference/src/client/index.ts +++ b/packages/client/ui-reference/src/client/index.ts @@ -53,8 +53,7 @@ export const inject = [ * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-reference: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-reference: dictionaries') const t = ctx.locale.bind(NS) const sessions = ctx.get('sessions') as ISessions ctx.uiConversation.userText.register(fileReferenceRule) diff --git a/packages/client/ui-schedule/src/client/frequency-locales.ts b/packages/client/ui-schedule/src/client/frequency-locales.ts index 35bf484f5a..d09bd39d30 100644 --- a/packages/client/ui-schedule/src/client/frequency-locales.ts +++ b/packages/client/ui-schedule/src/client/frequency-locales.ts @@ -181,5 +181,5 @@ export const frequencyJa = { 'unit.second.other': '秒', 'relative.now': 'まもなく期限', 'relative.future': '{value}{unit}後', - 'relative.overdue': '{value} {unit}超過', + 'relative.overdue': '{value}{unit}超過', } satisfies Record diff --git a/packages/client/ui-schedule/src/client/index.ts b/packages/client/ui-schedule/src/client/index.ts index b5952710dc..4aa83ae1a8 100644 --- a/packages/client/ui-schedule/src/client/index.ts +++ b/packages/client/ui-schedule/src/client/index.ts @@ -86,8 +86,7 @@ export const inject = [ * @param ctx - browser services used by these contributions. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-schedule: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-schedule: dictionaries') const managerDictionaries = { zh: managerZh, en: managerEn, ja: managerJa } ctx.effect(() => ctx.locale.register(MANAGER_NS, managerDictionaries), 'ui-schedule: manager dictionaries') const t = ctx.locale.bind(MANAGER_NS) diff --git a/packages/client/ui-secret-mask/src/client/index.ts b/packages/client/ui-secret-mask/src/client/index.ts index 50e24dd95e..4346d37bf2 100644 --- a/packages/client/ui-secret-mask/src/client/index.ts +++ b/packages/client/ui-secret-mask/src/client/index.ts @@ -25,8 +25,7 @@ export const inject = ['locale', 'slots', 'uiConversation'] * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-secret-mask: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-secret-mask: dictionaries') ctx.uiConversation.userText.register(createSecretRule()) ctx.slots.inject('conversation.user.text.node', () => ctx.slots.register({ name: 'conversation.user.text.node', key: 'secret', priority: 100, locale: NS, diff --git a/packages/client/ui-settings-account/src/client/index.ts b/packages/client/ui-settings-account/src/client/index.ts index b85c3386d9..4847b9b6e9 100644 --- a/packages/client/ui-settings-account/src/client/index.ts +++ b/packages/client/ui-settings-account/src/client/index.ts @@ -45,8 +45,7 @@ export const inject = ['slots', 'locale', 'remote', 'remote.account', 'remote.se /** Register account UI in Desktop and plain Web clients. @param ctx - client plugin context. */ export function apply(ctx: Context): void { const desktop = 'dshDesktop' in globalThis - const dictionaries = { en, zh, ja } - ctx.effect(() => ctx.locale.register('settings.account', dictionaries), 'account: dictionaries') + ctx.effect(() => ctx.locale.register('settings.account', { en, zh, ja }), 'account: dictionaries') const t = ctx.locale.bind('settings.account') const page = globalThis as Partial> const config = ContactConfig(page[CONTACT_CONFIG_GLOBAL] ?? {}) diff --git a/packages/client/ui-settings-account/src/client/locales.ts b/packages/client/ui-settings-account/src/client/locales.ts index c116d94fb4..5244c0743f 100644 --- a/packages/client/ui-settings-account/src/client/locales.ts +++ b/packages/client/ui-settings-account/src/client/locales.ts @@ -1,11 +1,11 @@ /** Account settings copy, owned by the account feature. */ -import { onboardingCopy, onboardingEnglishCopy, onboardingJapaneseCopy } from './locales/onboarding.ts' +import { onboardingZh, onboardingEn, onboardingJa } from './locales/onboarding.ts' /** English account dictionary. */ export const en = { modelSignInRequired: 'Model unavailable. Please sign in and try again.', sessionExpired: 'You have signed out of your account, please log in again.', - ...onboardingEnglishCopy, + ...onboardingEn, close: 'Close', addApiKey: 'Add API Key', retry: 'Sign in again', loginTitle: 'Get started', loginDescription: 'Sign in to your DeepSeek account or add an API Key to get started. Your projects and files are stored locally.', browserTitle: 'Waiting for sign in', browserPrompt: 'Page did not open automatically? ', copyLink: 'Copy sign-in link', copiedLink: 'Link copied', copyFailed: 'Copy failed', @@ -41,7 +41,7 @@ export type AccountKey = keyof typeof en export const zh: Record = { modelSignInRequired: '当前模型暂不可用,请登录后再试', sessionExpired: '登录信息已失效,请重新登录', - ...onboardingCopy, + ...onboardingZh, close: '关闭', addApiKey: '添加 API Key', retry: '重新登录', loginTitle: '开始使用', loginDescription: '登录 DeepSeek 账号,或添加 API Key,即可开始使用。你的项目和文件保存在本地。', browserTitle: '等待登录', browserPrompt: '没有自动打开新页面?', copyLink: '复制登录链接', copiedLink: '链接已复制', copyFailed: '复制失败', @@ -74,7 +74,7 @@ export const zh: Record = { /** Japanese copy, checked against the English keys. */ export const ja = { - ...onboardingJapaneseCopy, + ...onboardingJa, 'modelSignInRequired': 'モデルを利用できません。ログインしてから再試行してください。', 'sessionExpired': 'アカウントからログアウトしました。もう一度ログインしてください。', 'close': '閉じる', diff --git a/packages/client/ui-settings-account/src/client/locales/onboarding.ts b/packages/client/ui-settings-account/src/client/locales/onboarding.ts index b5c69d263c..977837e933 100644 --- a/packages/client/ui-settings-account/src/client/locales/onboarding.ts +++ b/packages/client/ui-settings-account/src/client/locales/onboarding.ts @@ -1,5 +1,5 @@ /** Chinese first-run copy from the onboarding requirements. */ -export const onboardingCopy = { +export const onboardingZh = { onboardingArtworkLocale: 'zh', onboardingWelcome: '欢迎使用', onboardingBrand: 'DeepSeek Harness', @@ -42,7 +42,7 @@ export const onboardingCopy = { } as const /** English first-run copy with the same keys as the Chinese dictionary. */ -export const onboardingEnglishCopy: Record = { +export const onboardingEn: Record = { onboardingArtworkLocale: 'en', onboardingWelcome: 'Welcome to', onboardingBrand: 'DeepSeek Harness', @@ -85,7 +85,7 @@ export const onboardingEnglishCopy: Record } /** Japanese copy, checked against the English keys. */ -export const onboardingJapaneseCopy = { +export const onboardingJa = { 'onboardingArtworkLocale': 'en', 'onboardingWelcome': 'ようこそ', 'onboardingBrand': 'DeepSeek Harness', @@ -125,4 +125,4 @@ export const onboardingJapaneseCopy = { 'onboardingSaveFailed': '設定を保存できませんでした。もう一度お試しください。', 'onboardingRetry': '再試行', 'onboardingLoading': '設定を読み込んでいます…', -} satisfies Record +} satisfies Record diff --git a/packages/client/ui-settings-agent-loop/src/client/index.ts b/packages/client/ui-settings-agent-loop/src/client/index.ts index 8f2cf8f0bb..29ebe0e132 100644 --- a/packages/client/ui-settings-agent-loop/src/client/index.ts +++ b/packages/client/ui-settings-agent-loop/src/client/index.ts @@ -41,8 +41,7 @@ export const inject = ['slots', 'locale', 'configForms'] */ export function apply(ctx: ClientContext): void { const t = ctx.locale.bind(NS) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-agent-loop: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-settings-agent-loop: dictionaries') const card = new AgentLoopCardController(ctx.configForms.get(AGENT_LOOP_NS)) ctx.effect(() => () => { card.dispose() }, 'ui-settings-agent-loop: form subscription') ctx.effect(() => ctx.configForms.whileServed([AGENT_LOOP_NS], () => ctx.slots.inject('plugins.item', () => ctx.slots.register({ diff --git a/packages/client/ui-settings-general/src/client/index.ts b/packages/client/ui-settings-general/src/client/index.ts index faf9acecbe..9ae4ec2fff 100644 --- a/packages/client/ui-settings-general/src/client/index.ts +++ b/packages/client/ui-settings-general/src/client/index.ts @@ -88,8 +88,7 @@ export function apply(ctx: ClientContext): void { ctx.slots.inject('settings.general.item', () => ctx.slots.register({ name: 'settings.general.item', id: 'current-version', order: 100, locale: NS, }, CurrentVersionRow)) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-general: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-settings-general: dictionaries') const connection = ctx.get('connection') as ConnectionHandle const carrier = (globalThis as typeof globalThis & { dshDesktop?: { protocolVersion: number; updates?: DesktopUpdateBridge } }).dshDesktop const desktopUpdate = new DesktopUpdateSource(carrier?.protocolVersion === 1 ? carrier.updates : undefined) diff --git a/packages/client/ui-settings-models/src/client/index.ts b/packages/client/ui-settings-models/src/client/index.ts index a881a55cc3..37ad6919fe 100644 --- a/packages/client/ui-settings-models/src/client/index.ts +++ b/packages/client/ui-settings-models/src/client/index.ts @@ -80,8 +80,7 @@ export function apply(ctx: ClientContext): void { const payload = page[ONBOARDING_CONFIG_GLOBAL] const configured = Config(payload === undefined ? {} : payload) const credentialOnboarding = configured.credentialOnboarding && !('dshDesktop' in globalThis) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-models: copy dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-settings-models: copy dictionaries') const schema = createSettingsSchemaOperations(ctx.settingsSchema) // Bound once here, where the Remote namespaces are declared in this plugin's diff --git a/packages/client/ui-settings-plugin-inventory/src/client/index.ts b/packages/client/ui-settings-plugin-inventory/src/client/index.ts index f70c9c9e93..2532d406cd 100644 --- a/packages/client/ui-settings-plugin-inventory/src/client/index.ts +++ b/packages/client/ui-settings-plugin-inventory/src/client/index.ts @@ -31,8 +31,7 @@ export const inject = ['slots', 'locale', 'remote', 'remote.pluginInventory', 'm /** Contribute the lazy inventory tab to the Plugins settings section. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-plugin-inventory: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-settings-plugin-inventory: dictionaries') const t = ctx.locale.bind(NS) const list: PluginInventorySettingsTabInjected['list'] = async () => { diff --git a/packages/client/ui-settings-plugins/src/client/index.ts b/packages/client/ui-settings-plugins/src/client/index.ts index 38773409d8..4897b7c503 100644 --- a/packages/client/ui-settings-plugins/src/client/index.ts +++ b/packages/client/ui-settings-plugins/src/client/index.ts @@ -34,8 +34,7 @@ export const inject = ['slots', 'locale'] */ export function apply(ctx: ClientContext): void { const t = ctx.locale.bind(NS) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-plugins: section dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-settings-plugins: section dictionaries') let tabsVersion = -1 let tabsRevision = -1 diff --git a/packages/client/ui-settings-session-log/src/client/index.ts b/packages/client/ui-settings-session-log/src/client/index.ts index 12269e3d04..6e749043cb 100644 --- a/packages/client/ui-settings-session-log/src/client/index.ts +++ b/packages/client/ui-settings-session-log/src/client/index.ts @@ -25,8 +25,7 @@ export const inject = ['slots', 'locale', 'configForms'] export function apply(ctx: Context): void { const locale = 'settings.sessionLog' const namespace = 'session-log-deepseek' - const dictionaries = { en, zh, ja } - ctx.effect(() => ctx.locale.register(locale, dictionaries)) + ctx.effect(() => ctx.locale.register(locale, { en, zh, ja })) const form = ctx.configForms.get(namespace) const preference = new UploadPreference(form) const face = (): UploadInjected => ({ diff --git a/packages/client/ui-settings-shell/src/client/index.ts b/packages/client/ui-settings-shell/src/client/index.ts index dc150c7d23..55f69d6b09 100644 --- a/packages/client/ui-settings-shell/src/client/index.ts +++ b/packages/client/ui-settings-shell/src/client/index.ts @@ -42,8 +42,7 @@ export const inject = ['slots', 'locale', 'configForms'] */ export function apply(ctx: ClientContext): void { const t = ctx.locale.bind(NS) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-shell: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-settings-shell: dictionaries') // The base bundle composes exactly one shell executor per platform; the page binds the served one. const bash = new ShellCardController(ctx.configForms.get(BASH_NS)) const pwsh = new ShellCardController(ctx.configForms.get(PWSH_NS)) diff --git a/packages/client/ui-settings-subagent/src/client/index.ts b/packages/client/ui-settings-subagent/src/client/index.ts index 51d8fb29a5..42811181ee 100644 --- a/packages/client/ui-settings-subagent/src/client/index.ts +++ b/packages/client/ui-settings-subagent/src/client/index.ts @@ -58,8 +58,7 @@ export const inject = ['slots', 'locale', 'remote', 'remote.session', 'configFor */ export function apply(ctx: ClientContext): void { const t = ctx.locale.bind(NS) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-subagent: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-settings-subagent: dictionaries') const limits = new SubagentLimitsCardController(ctx.configForms.get(SUBAGENT_NS)) ctx.effect(() => () => { limits.dispose() }, 'ui-settings-subagent: limits form subscription') const models = new SubagentModelSelectionCardController( diff --git a/packages/client/ui-settings-web-search/src/client/index.ts b/packages/client/ui-settings-web-search/src/client/index.ts index dc81de3fe3..461237a165 100644 --- a/packages/client/ui-settings-web-search/src/client/index.ts +++ b/packages/client/ui-settings-web-search/src/client/index.ts @@ -49,8 +49,7 @@ export const inject = ['slots', 'locale', 'remote', 'remote.credentials', 'confi */ export function apply(ctx: ClientContext): void { const t = ctx.locale.bind(NS) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-settings-web-search: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-settings-web-search: dictionaries') const card = new WebSearchCardController({ web: ctx.configForms.get(WEB_NS), messages: ctx.configForms.get(WEB_SEARCH_NS), api: ctx.configForms.get(WEB_API_NS), }, ctx) diff --git a/packages/client/ui-shortcuts/src/client/index.ts b/packages/client/ui-shortcuts/src/client/index.ts index 3ebe888235..9e0d508833 100644 --- a/packages/client/ui-shortcuts/src/client/index.ts +++ b/packages/client/ui-shortcuts/src/client/index.ts @@ -26,8 +26,7 @@ export const inject = ['shortcuts', 'locale', 'slots'] * @param ctx - plugin-owned client context. */ export function apply(ctx: Context): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register('shortcuts', dictionaries), 'shortcuts: dictionaries') + ctx.effect(() => ctx.locale.register('shortcuts', { zh, en, ja }), 'shortcuts: dictionaries') const t = ctx.locale.bind('shortcuts') const handle = createShortcutsStore() const instance = handle.create() diff --git a/packages/client/ui-sidebar-browser/src/client/index.ts b/packages/client/ui-sidebar-browser/src/client/index.ts index 92d93ba1b1..e3ac92a9db 100644 --- a/packages/client/ui-sidebar-browser/src/client/index.ts +++ b/packages/client/ui-sidebar-browser/src/client/index.ts @@ -70,8 +70,7 @@ export function apply(ctx: Context): void { dshDesktop?: { readonly protocolVersion: number; readonly browser?: DesktopBrowserBridge } }).dshDesktop const desktop = carrier?.protocolVersion === 1 ? carrier.browser : undefined - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(namespace, dictionaries), 'ui-sidebar-browser.copy') + ctx.effect(() => ctx.locale.register(namespace, { zh, en, ja }), 'ui-sidebar-browser.copy') ctx.effect(() => ctx.sidebarRightTabs.register({ ...browserDefinition(t), keepMounted: desktop !== undefined }), 'ui-sidebar-browser.type') const installFrames = (scope: Context, factory: (sessionId: BrowserBodyProps['sessionId']) => BrowserPageFactory): void => { const controllers = new Map() diff --git a/packages/client/ui-sidebar-documentpreview/src/client/code/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/code/index.ts index b3d4d32241..3008802e74 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/code/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/code/index.ts @@ -10,8 +10,7 @@ const NS = 'sidebarCodePreview' /** @param ctx - owning plugin context. Register localized metadata and the matching keyed document body. */ export function apply(ctx: Context): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries)) + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja })) const t = ctx.locale.bind(NS) ctx.effect(() => ctx.documentPreviews.register({ id: ID, diff --git a/packages/client/ui-sidebar-documentpreview/src/client/excel/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/excel/index.ts index d53d99c179..4a2771723f 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/excel/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/excel/index.ts @@ -12,8 +12,7 @@ import { en, ja, zh } from './locales.ts' */ export function apply(ctx: Context, limits: Config['excel']): void { const id = '@deepseek-ai/dsh-client-ui-sidebar-documentpreview/excel' - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register('sidebarExcel', dictionaries)) + ctx.effect(() => ctx.locale.register('sidebarExcel', { zh, en, ja })) const t = ctx.locale.bind('sidebarExcel') ctx.effect(() => ctx.documentPreviews.register({ id, extensions: ['xlsx', 'xls', 'csv', 'tsv'], binaryExtensions: ['xlsx', 'xls'], priority: 'builtin', diff --git a/packages/client/ui-sidebar-documentpreview/src/client/html/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/html/index.ts index 77e3686208..056efb2de9 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/html/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/html/index.ts @@ -26,8 +26,7 @@ export function htmlBodyDefinition(title: () => string): DocumentPreviewDefiniti */ export function apply(ctx: Context): void { const t = ctx.locale.bind('documentHtml') - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register('documentHtml', dictionaries)) + ctx.effect(() => ctx.locale.register('documentHtml', { zh, en, ja })) ctx.effect(() => ctx.documentPreviews.register(htmlBodyDefinition(() => t('title')))) ctx.effect(() => ctx.slots.inject('sidebar.right.tab.document', () => ctx.slots.register( { diff --git a/packages/client/ui-sidebar-documentpreview/src/client/image/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/image/index.ts index 81bcad1172..2ee287007c 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/image/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/image/index.ts @@ -41,8 +41,7 @@ export function imageBodyDefinition(title: () => string): DocumentPreviewDefinit */ export function apply(ctx: Context): void { const t = ctx.locale.bind('sidebarImage') - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register('sidebarImage', dictionaries), 'document-image: dictionaries') + ctx.effect(() => ctx.locale.register('sidebarImage', { zh, en, ja }), 'document-image: dictionaries') ctx.effect(() => ctx.documentPreviews.register(imageBodyDefinition(() => t('title'))), 'document-image: metadata') const store = createZoomStore() const retainTab = retainDocumentTabs(ctx) diff --git a/packages/client/ui-sidebar-documentpreview/src/client/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/index.ts index dd1186175a..e7880dd50d 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/index.ts @@ -91,8 +91,7 @@ export function apply(ctx: ClientContext): void { const disposePreviews = ctx.reflect.provide('documentPreviews', previews) ctx.effect(() => disposePreviews) ctx.effect(() => ctx.sidebarRightTabs.register(textDefinition()), 'ui-sidebar-documentpreview: text type') - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-sidebar-documentpreview: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-sidebar-documentpreview: dictionaries') const store = createTextStore() const face = textFace( diff --git a/packages/client/ui-sidebar-documentpreview/src/client/markdown/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/markdown/index.ts index e60d839873..a3d68990c0 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/markdown/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/markdown/index.ts @@ -23,8 +23,7 @@ export function markdownDefinition(title: () => string): DocumentPreviewDefiniti */ export function apply(ctx: Context): void { const t = ctx.locale.bind('documentMarkdown') - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register('documentMarkdown', dictionaries), 'document-markdown: dictionaries') + ctx.effect(() => ctx.locale.register('documentMarkdown', { zh, en, ja }), 'document-markdown: dictionaries') ctx.effect(() => ctx.documentPreviews.register(markdownDefinition(() => t('viewer.label'))), 'document-markdown: metadata') ctx.effect(() => ctx.slots.inject('sidebar.right.tab.document', () => ctx.slots.register( { name: 'sidebar.right.tab.document', key: MARKDOWN_BODY_ID, locale: 'documentMarkdown' }, MarkdownBody, diff --git a/packages/client/ui-sidebar-documentpreview/src/client/office/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/office/index.ts index 03699c8981..350585f7ed 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/office/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/office/index.ts @@ -31,8 +31,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { export function apply(ctx: Context, config: Config['office']): void { const id = '@deepseek-ai/dsh-client-ui-sidebar-documentpreview/office' const extensions = ['doc', 'docx', 'ppt', 'pptx'] - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register('sidebarOffice', dictionaries)) + ctx.effect(() => ctx.locale.register('sidebarOffice', { zh, en, ja })) const t = ctx.locale.bind('sidebarOffice') const unavailable: ReadOfficeDocument = (_file, signal) => { signal.throwIfAborted() diff --git a/packages/client/ui-sidebar-documentpreview/src/client/pdf/index.ts b/packages/client/ui-sidebar-documentpreview/src/client/pdf/index.ts index ab5640d20f..ee099bb22a 100644 --- a/packages/client/ui-sidebar-documentpreview/src/client/pdf/index.ts +++ b/packages/client/ui-sidebar-documentpreview/src/client/pdf/index.ts @@ -25,8 +25,7 @@ export function pdfBodyDefinition(title: () => string): DocumentPreviewDefinitio /** @param ctx - context carrying the locale, document registry, and slot registry. */ export function apply(ctx: Context): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register('sidebarPdf', dictionaries)) + ctx.effect(() => ctx.locale.register('sidebarPdf', { zh, en, ja })) const t = ctx.locale.bind('sidebarPdf') ctx.effect(() => ctx.documentPreviews.register(pdfBodyDefinition(() => t('title')))) const presentation = pdfBodyRegistration(ctx) diff --git a/packages/client/ui-sidebar-files/src/client/index.ts b/packages/client/ui-sidebar-files/src/client/index.ts index ec6de5b4d0..0b140c0e4e 100644 --- a/packages/client/ui-sidebar-files/src/client/index.ts +++ b/packages/client/ui-sidebar-files/src/client/index.ts @@ -79,8 +79,7 @@ export function apply(ctx: ClientContext): void { }), 'ui-sidebar-files: shortcut') }) ctx.effect(() => ctx.sidebarRightTabs.register(filesDefinition(t)), 'ui-sidebar-files: files type') - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-sidebar-files: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-sidebar-files: dictionaries') const store = createFilesStore() const inject = filesFace(createList(ctx.remote), createWatch(ctx.remote)) diff --git a/packages/client/ui-sidebar-right/src/client/index.ts b/packages/client/ui-sidebar-right/src/client/index.ts index 35c88b2722..cb6a2f6e3f 100644 --- a/packages/client/ui-sidebar-right/src/client/index.ts +++ b/packages/client/ui-sidebar-right/src/client/index.ts @@ -161,8 +161,7 @@ export function apply(ctx: ClientContext): void { void disposeRegistry() }, 'ui-sidebar-right: service faces') - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-sidebar-right: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-sidebar-right: dictionaries') ctx.effect(() => registerSidebarShortcuts(ctx.shortcuts, controller, t, () => { void ctx.shortcuts.closeWindow().catch((error: unknown) => { console.error('Window close failed', error) }) }), 'ui-sidebar-right: shortcuts') diff --git a/packages/client/ui-sidebar-terminal/src/client/index.ts b/packages/client/ui-sidebar-terminal/src/client/index.ts index a8b790bc54..e355b4844c 100644 --- a/packages/client/ui-sidebar-terminal/src/client/index.ts +++ b/packages/client/ui-sidebar-terminal/src/client/index.ts @@ -67,8 +67,7 @@ export function apply(ctx: Context): void { return { status: 'handled', run: () => { ctx.sidebarRight.openTabFromTarget('terminal', target) } } }, }), 'ui-sidebar-terminal: shortcut') - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(namespace, dictionaries), 'ui-sidebar-terminal.copy') + ctx.effect(() => ctx.locale.register(namespace, { zh, en, ja }), 'ui-sidebar-terminal.copy') ctx.effect(() => ctx.sidebarRightTabs.register({ id, kind: 'terminal', multiple: true, priority: 'builtin', title: () => t('title'), guide: [{ id: 'new', order: 20, title: () => t('new'), description: () => t('description'), icon: PluginArtworkTerminal }], diff --git a/packages/client/ui-sidebar/src/client/index.ts b/packages/client/ui-sidebar/src/client/index.ts index 3060da3377..ed6aecb67e 100644 --- a/packages/client/ui-sidebar/src/client/index.ts +++ b/packages/client/ui-sidebar/src/client/index.ts @@ -45,8 +45,7 @@ export const inject = ['slots', 'layout', 'uiWorkspace', 'locale', 'shortcuts'] */ export function apply(ctx: ClientContext): void { const workspaceNavigation = ctx.get('uiWorkspace') as unknown as WorkspaceNavigation - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-sidebar: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-sidebar: dictionaries') const panels = createSnapshotStore([]) const syncPanels = (): void => { const next = ctx.slots.entriesOfSlot('sidebar.panellist').map(({ options }) => { diff --git a/packages/client/ui-skill/src/client/index.ts b/packages/client/ui-skill/src/client/index.ts index 82a62009c2..0356035c26 100644 --- a/packages/client/ui-skill/src/client/index.ts +++ b/packages/client/ui-skill/src/client/index.ts @@ -84,8 +84,7 @@ export const inject = ['inputTriggers', 'sessions', 'slots', 'locale', 'remote', * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-skill: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-skill: dictionaries') ctx.uiConversation.userText.register(skillReferenceRule) ctx.slots.inject('tool.call.toolview', () => ctx.slots.register( { name: 'tool.call.toolview', key: 'skill', locale: NS }, diff --git a/packages/client/ui-subagent/src/client/index.ts b/packages/client/ui-subagent/src/client/index.ts index f665371668..51f052672e 100644 --- a/packages/client/ui-subagent/src/client/index.ts +++ b/packages/client/ui-subagent/src/client/index.ts @@ -53,8 +53,7 @@ function selectReadOnlySubagent(owner: ComposerChainProps): SubagentReadOnlyMatc * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-subagent: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-subagent: dictionaries') ctx.inject(['resources', 'sidebarRightTabs'], (scope) => { registerSidebarChat(scope, ctx.locale.bind(NS)) }) diff --git a/packages/client/ui-theme/src/client/index.ts b/packages/client/ui-theme/src/client/index.ts index c8977a8df3..c00c0ce26e 100644 --- a/packages/client/ui-theme/src/client/index.ts +++ b/packages/client/ui-theme/src/client/index.ts @@ -544,8 +544,7 @@ export const inject = ['slots', 'locale', 'remote', 'configForms'] */ export function apply(ctx: ClientContext): void { installThemeStyles(ctx) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(SETTINGS_NS, dictionaries), 'ui-theme: settings row dictionaries') + ctx.effect(() => ctx.locale.register(SETTINGS_NS, { zh, en, ja }), 'ui-theme: settings row dictionaries') const host = ctx.configForms.get(THEME_SETTINGS_NAMESPACE) const theme = new ThemeRuntime(ctx, host) ctx.provide('theme', theme) diff --git a/packages/client/ui-trajectory/src/client/index.ts b/packages/client/ui-trajectory/src/client/index.ts index 254f426ebc..05975721ad 100644 --- a/packages/client/ui-trajectory/src/client/index.ts +++ b/packages/client/ui-trajectory/src/client/index.ts @@ -59,8 +59,7 @@ export function apply(ctx: Context): void { } return source } - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-trajectory: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-trajectory: dictionaries') // Registration-time text (the view tab label) reads through the bound // translate as a thunk, so it follows the active locale without // re-registration. diff --git a/packages/client/ui-user-questions/src/client/index.ts b/packages/client/ui-user-questions/src/client/index.ts index 5352edb540..2d04df1b4b 100644 --- a/packages/client/ui-user-questions/src/client/index.ts +++ b/packages/client/ui-user-questions/src/client/index.ts @@ -366,8 +366,7 @@ function publishContinuedQuestions(ctx: ClientContext, cards: QuestionCards): () * @param ctx - client root context. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-user-questions: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-user-questions: dictionaries') const questionDraftStore = createQuestionDraftStore() const registerPendingInteraction = ctx.uiSession.registerPendingInteraction( pending => pending.kind === 'plan-review' ? 2 : 1, diff --git a/packages/client/ui-workflow-run/src/client/index.ts b/packages/client/ui-workflow-run/src/client/index.ts index 26f28d8670..b3da13c944 100644 --- a/packages/client/ui-workflow-run/src/client/index.ts +++ b/packages/client/ui-workflow-run/src/client/index.ts @@ -25,8 +25,7 @@ export const inject = ['uiConversation', 'uiWorkspace', 'slots', 'sessions', 'lo /** Register the workflow Definition, dictionary, and keyed Chat renderer. */ export function apply(ctx: ClientContext): void { ctx.uiConversation.events.register(workflowRunDefinition) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-workflow-run: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-workflow-run: dictionaries') ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({ name: 'conversation.chat.node', key: 'workflow-run', diff --git a/packages/client/ui-workspace/src/client/index.ts b/packages/client/ui-workspace/src/client/index.ts index d68bdc8d9b..94199e9111 100644 --- a/packages/client/ui-workspace/src/client/index.ts +++ b/packages/client/ui-workspace/src/client/index.ts @@ -132,8 +132,7 @@ export function apply(ctx: Context): void { : ctx.remoteAccess.source('workspace', workspaceId, action), }, }) - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-workspace: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-workspace: dictionaries') const shortcutControls = createWorkspaceShortcutControls() const searchSessions: WorkspaceBrowserInjected['searchSessions'] = async (query, signal) => { diff --git a/packages/credentials/deepseek-account/tests/client-headers.spec.ts b/packages/credentials/deepseek-account/tests/client-headers.spec.ts index c4e401d940..d46d936e44 100644 --- a/packages/credentials/deepseek-account/tests/client-headers.spec.ts +++ b/packages/credentials/deepseek-account/tests/client-headers.spec.ts @@ -16,5 +16,5 @@ it('selects zh_CN only for a Chinese primary language subtag', () => { platformClientHeaders(null, { version: 'v', locale: value, timezoneOffsetSeconds: 0 })['x-client-locale'] expect(locale('zh')).toBe('zh_CN') expect(locale('ZH_hans')).toBe('zh_CN') - for (const value of ['en', 'en-US', 'fr-FR', 'English', '', 'zhx']) expect(locale(value)).toBe('en_US') + for (const value of ['ja', 'ja-JP', 'en', 'en-US', 'fr-FR', 'English', '', 'zhx']) expect(locale(value)).toBe('en_US') }) diff --git a/packages/experimental/client-ui-agent-team/src/client/mount.ts b/packages/experimental/client-ui-agent-team/src/client/mount.ts index 673c95e21f..a3c044b596 100644 --- a/packages/experimental/client-ui-agent-team/src/client/mount.ts +++ b/packages/experimental/client-ui-agent-team/src/client/mount.ts @@ -27,8 +27,7 @@ export const inject = ['sessions', 'uiWorkspace', 'slots', 'locale'] * @param ctx - Client Context carrying the injected navigation, locale, slot, and Session services. */ export function registerAgentTeamUi(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'client-ui-agent-team: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'client-ui-agent-team: dictionaries') const sessions = ctx.sessions const leadSessionId = (sessionId: SessionId): SessionId => { const address = sessions.binding(sessionId)?.session.getSnapshot().subagent?.address diff --git a/packages/experimental/client-ui-claude-code-mods/src/client/mount.ts b/packages/experimental/client-ui-claude-code-mods/src/client/mount.ts index f774329825..498e4a2d28 100644 --- a/packages/experimental/client-ui-claude-code-mods/src/client/mount.ts +++ b/packages/experimental/client-ui-claude-code-mods/src/client/mount.ts @@ -75,8 +75,7 @@ function watchSession(ctx: Context, sessionId: SessionId, onEnd: () => void): { } function registerUi(ctx: Context): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'client-ui-claude-code-mods: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'client-ui-claude-code-mods: dictionaries') const watches = new Map>() ctx.effect(() => () => { for (const watch of watches.values()) watch.dispose() diff --git a/packages/experimental/client-ui-cot-translation/src/client/mount.ts b/packages/experimental/client-ui-cot-translation/src/client/mount.ts index 0e73fb1416..10e81f52fa 100644 --- a/packages/experimental/client-ui-cot-translation/src/client/mount.ts +++ b/packages/experimental/client-ui-cot-translation/src/client/mount.ts @@ -80,8 +80,7 @@ async function registerUi(ctx: Context, lifetime: AbortController): Promise('cot-translation') ctx.effect(() => scope.subscribe(invalidate)) - const dictionaries = { en, zh, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries)) + ctx.effect(() => ctx.locale.register(NS, { en, zh, ja })) const form = new TranslationPreferencesForm(scope) ctx.effect(() => () => { form.dispose() }) const injected = (sessionId: SessionId): TranslationBodyInjected => ({ diff --git a/packages/experimental/client-ui-voice-input/src/client/mount.ts b/packages/experimental/client-ui-voice-input/src/client/mount.ts index 0b58060995..d79d281def 100644 --- a/packages/experimental/client-ui-voice-input/src/client/mount.ts +++ b/packages/experimental/client-ui-voice-input/src/client/mount.ts @@ -18,8 +18,7 @@ import { VoiceSetupPrompt } from './VoiceSetupPrompt.tsx' export const inject = ['remote', 'slots', 'locale', 'pluginNavigation'] function registerUi(ctx: Context): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries)) + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja })) const recordings = new Set() const microphone = createMicrophoneDeviceStore() const readiness = observeReadiness(ctx) diff --git a/packages/experimental/cua/src/client/mount.ts b/packages/experimental/cua/src/client/mount.ts index e4fcaed411..cd38c5e3c5 100644 --- a/packages/experimental/cua/src/client/mount.ts +++ b/packages/experimental/cua/src/client/mount.ts @@ -31,8 +31,7 @@ export const inject = ['remote', 'slots', 'locale', 'configForms'] export async function mountCuaPermissions(ctx: Context, contribution: TypertRemoteContribution): Promise<() => Promise> { const transport = ctx.plugin(async (owner) => { await owner.remote.$mount(contribution) }) const ui = ctx.inject(['remote.cuaAccess', 'slots', 'locale'], (ctx) => { - const dictionaries = { en, zh, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries)) + ctx.effect(() => ctx.locale.register(NS, { en, zh, ja })) const controller = new PermissionController(ctx.remote.cuaAccess) const face = controller.face() ctx.inject(['pluginChanges'], (scope) => { diff --git a/packages/experimental/inspector/src/client/bottom/page.tsx b/packages/experimental/inspector/src/client/bottom/page.tsx index fdab0d36fa..affe4c00e0 100644 --- a/packages/experimental/inspector/src/client/bottom/page.tsx +++ b/packages/experimental/inspector/src/client/bottom/page.tsx @@ -12,7 +12,7 @@ import type {} from '@deepseek-ai/dsh-client-locale/client' import type { InspectorSourceId } from '../../shared/bridge/ids.ts' import { bindInspectorKeyboard } from './keyboard.ts' import { InspectorResizeHandle } from './resize.tsx' -import { zh, en } from './locales.ts' +import { zh, en, ja } from './locales.ts' import css from './page.module.css' const ID = 'inspector.toggle' as ShortcutCommandId @@ -58,7 +58,7 @@ function InspectorPage({ t, frontendUrl, close, bindFrame, usePanel }: export function registerInspectorPage(ctx: Context, sourceId: InspectorSourceId): void { const query = new URLSearchParams({ disableLocaleInfoBar: 'true', clientSourceId: sourceId }) const frontendUrl = `inspector/devtools/devtools_app.html?${query}` - ctx.effect(() => ctx.locale.register('inspectorPanel', { zh, en })) + ctx.effect(() => ctx.locale.register('inspectorPanel', { zh, en, ja })) const t = ctx.locale.bind('inspectorPanel') ctx.slots.inject('shell.bottom', function* () { const panel = createSnapshotStore('unopened') diff --git a/packages/experimental/inspector/tests/bottom.client.spec.tsx b/packages/experimental/inspector/tests/bottom.client.spec.tsx index da27215e2a..c48f8c37c8 100644 --- a/packages/experimental/inspector/tests/bottom.client.spec.tsx +++ b/packages/experimental/inspector/tests/bottom.client.spec.tsx @@ -188,3 +188,16 @@ it('removes the command and frame on unload and starts a fresh closed panel on r press() expect(body.view.getByTitle('NodeJS Inspector')).not.toBe(frame) }) + +it('uses Japanese panel and shortcut labels through the production registration', async () => { + const { runtime, locale, body } = await setup() + act(() => { + locale.addLanguage({ id: 'ja', label: '日本語', fallback: 'en' }) + locale.setLocale('ja') + }) + press() + expect(body.view.getByRole('button', { name: '折りたたむ' })).toBeTruthy() + expect(body.view.getByTitle('NodeJS インスペクター')).toBeTruthy() + expect(body.container.querySelector('section')?.getAttribute('aria-label')).toBe('NodeJS インスペクター') + expect(runtime.ctx.shortcuts.catalog.getSnapshot().find(row => row.id === ID)?.label).toBe('NodeJS インスペクターの表示切り替え') +}) diff --git a/packages/experimental/session-inspector/src/client/index.ts b/packages/experimental/session-inspector/src/client/index.ts index 5f11bb69ed..966f0cafcf 100644 --- a/packages/experimental/session-inspector/src/client/index.ts +++ b/packages/experimental/session-inspector/src/client/index.ts @@ -14,7 +14,6 @@ export const inject = ['slots', 'locale', 'sessions', 'uiSession', 'uiConversati * @param ctx - Client plugin context owning the dictionaries and registrations. */ export function apply(ctx: Context): void { - const dictionaries = { en, zh, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'session-inspector: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { en, zh, ja }), 'session-inspector: dictionaries') registerInspectorTab(ctx) } diff --git a/packages/extensions/cordis-client-runner/src/client/api-catalog.ts b/packages/extensions/cordis-client-runner/src/client/api-catalog.ts index c8b0cb84e4..0fb8d1a663 100644 --- a/packages/extensions/cordis-client-runner/src/client/api-catalog.ts +++ b/packages/extensions/cordis-client-runner/src/client/api-catalog.ts @@ -151,9 +151,9 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [ throws: ['when fields are malformed, the id is occupied, or the fallback target is unknown or creates a cycle.'], }, { - signature: 'register>(ns: N, dicts: Record>): () => void', + signature: 'register>( ns: N, dicts: Record> & Record>, ): () => void', description: 'Register a declared namespace\'s dictionaries, all locales in one call — the typed form: each dictionary is checked against the namespace\'s LocaleNamespaceMap key union (a missing or extra key is a compile error), and both baseline locales are required (bilingual balance enforced at registration). Duplicate (ns, locale) throws (single occupant; a namespace\'s texts have one owner). Registration bumps the revision so mounted outlets pick up late-arriving dictionaries.', - parameters: [{ name: 'ns', description: 'a namespace merged into LocaleNamespaceMap.' }, { name: 'dicts', description: 'complete dictionaries keyed by built-in locale id.' }], + parameters: [{ name: 'ns', description: 'a namespace merged into LocaleNamespaceMap.' }, { name: 'dicts', description: 'complete baseline dictionaries and optional dictionaries for additional locale ids.' }], returns: 'disposer removing every locale registered by this call (idempotent).', }, { diff --git a/packages/extensions/ui-cordis/src/client/index.ts b/packages/extensions/ui-cordis/src/client/index.ts index 0ef99b4dd6..26eb254437 100644 --- a/packages/extensions/ui-cordis/src/client/index.ts +++ b/packages/extensions/ui-cordis/src/client/index.ts @@ -40,8 +40,7 @@ export const inject = [ /** Mount every Cordis browser surface over the shared Host inventory. */ export function apply(ctx: ClientContext): void { - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'ui-cordis: dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'ui-cordis: dictionaries') const port: CordisDynamicPort = { stop: async (sessionId, pluginId) => { diff --git a/packages/session-query/session-log-export/src/client/index.ts b/packages/session-query/session-log-export/src/client/index.ts index 75a58c7dd5..a8de48efb3 100644 --- a/packages/session-query/session-log-export/src/client/index.ts +++ b/packages/session-query/session-log-export/src/client/index.ts @@ -37,8 +37,7 @@ export function apply(ctx: ClientContext): void { const controller = new SessionLogDownloadController() ctx.provide('sessionLogDownload', controller) ctx.effect(() => async () => { await controller.dispose() }, 'session-log-download: browser download lifecycle') - const dictionaries = { zh, en, ja } - ctx.effect(() => ctx.locale.register(NS, dictionaries), 'session-log-download: browser dictionaries') + ctx.effect(() => ctx.locale.register(NS, { zh, en, ja }), 'session-log-download: browser dictionaries') const feedbackAvailable = createSnapshotStore(false) ctx.inject(['feedbackUi'], (scope: ClientContext) => { scope.effect(() => { diff --git a/scripts/locale-dictionary-parity.spec.ts b/scripts/locale-dictionary-parity.spec.ts index 497e762092..e777cb5101 100644 --- a/scripts/locale-dictionary-parity.spec.ts +++ b/scripts/locale-dictionary-parity.spec.ts @@ -1,7 +1,7 @@ /** * Gate for the invariant `FALLBACK_LOCALE` rests on: every shipped dictionary - * declares the same keys in `zh` and `en`; Japanese dictionaries also preserve - * the English keys and literal-template placeholders. + * declares the same keys in `zh`, `en`, and `ja`; Japanese dictionaries also + * preserve the English literal-template placeholders. * * The locale runtime resolves a key through the active locale, then through * the single fallback locale (`en`), then surfaces the key itself. With @@ -34,7 +34,7 @@ function relative(file: string): string { return file.slice(root.length).replaceAll('\\', '/') } -/** Every `.ts` source file under each workspace package's `src`, excluding declarations. */ +/** Every TypeScript source file under each workspace package's `src`, excluding declarations. */ function sourceFiles(): string[] { const files: string[] = [] const packagesRoot = resolve(root, 'packages') @@ -71,7 +71,7 @@ function walk(dir: string, out: string[]): void { for (const entry of readEntries(dir)) { const full = resolve(dir, entry.name) if (entry.isDirectory()) walk(full, out) - else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) out.push(full) + else if ((/\.tsx?$/u.test(entry.name)) && !entry.name.endsWith('.d.ts')) out.push(full) } } @@ -153,7 +153,7 @@ function dictionariesIn(file: string): Dictionary[] { if (name === 'register' && node.arguments.length >= 3) { const [ns, tag, dict] = node.arguments if (ns === undefined || tag === undefined || !ts.isStringLiteral(tag)) return - if (tag.text !== 'zh' && tag.text !== 'en') return + if (tag.text !== 'zh' && tag.text !== 'en' && tag.text !== 'ja') return const raw = unwrap(dict) const literal = raw !== undefined && ts.isIdentifier(raw) ? (() => { @@ -195,7 +195,6 @@ function dictionariesIn(file: string): Dictionary[] { return found } - /** Literal templates; shared spread dictionaries are checked at their own declaration. */ function templatesOf(literal: ts.ObjectLiteralExpression): Readonly> { const templates: Record = {} @@ -255,7 +254,61 @@ function localeOf(name: string): { locale: 'zh' | 'en' | 'ja'; pair: string } | return undefined } +/** + * Check that each object-form registration supplies its Japanese dictionary. + * @param file - absolute source path. + * @returns registration sites missing Japanese, or using an unresolved local identifier. + */ +function registrationProblems(file: string): string[] { + const text = readFileSync(file, 'utf8') + if (!text.includes('.register(')) return [] + const source = ts.createSourceFile(file, text, ts.ScriptTarget.ESNext, true) + const locals = new Map() + const imported = new Set() + const collect = (node: ts.Node): void => { + if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer !== undefined) { + locals.set(node.name.text, node.initializer) + } + if (ts.isImportSpecifier(node)) imported.add(node.name.text) + ts.forEachChild(node, collect) + } + collect(source) + const problems: string[] = [] + const visit = (node: ts.Node): void => { + if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) + && node.expression.name.text === 'register' && node.arguments.length === 2) { + const argument = unwrap(node.arguments[1]) + const dicts = argument !== undefined && ts.isIdentifier(argument) + ? unwrap(locals.get(argument.text)) : argument + if (dicts !== undefined && ts.isObjectLiteralExpression(dicts)) { + const entries = new Map() + for (const prop of dicts.properties) { + if (ts.isShorthandPropertyAssignment(prop)) entries.set(prop.name.text, prop.name) + else if (ts.isPropertyAssignment(prop) && (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) { + entries.set(prop.name.text, prop.initializer) + } + } + if (entries.has('zh') && entries.has('en')) { + const ja = entries.get('ja') + const site = `${relative(file)}:${source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1}` + if (ja === undefined) problems.push(`${site}: registration has no Japanese dictionary`) + else if (ts.isIdentifier(ja) && !imported.has(ja.text) && !locals.has(ja.text)) { + problems.push(`${site}: Japanese dictionary ${ja.text} is not imported or declared`) + } + } + } + } + ts.forEachChild(node, visit) + } + visit(source) + return problems +} + describe('shipped locale dictionaries', () => { + it('registers Japanese alongside every object-form bilingual dictionary', () => { + expect(sourceFiles().flatMap(registrationProblems)).toEqual([]) + }) + it('keeps bilingual keys aligned and Japanese keys and placeholders complete', () => { const files = sourceFiles() // Guard the discovery itself: an empty or narrowed sweep would pass every @@ -301,7 +354,6 @@ describe('shipped locale dictionaries', () => { const problems: string[] = [] let comparedPairs = 0 - let japaneseDictionaries = 0 for (const [key, slot] of [...groups].sort()) { const zh = slot.get('zh') const en = slot.get('en') @@ -316,8 +368,9 @@ describe('shipped locale dictionaries', () => { if (zhOnly.length > 0) problems.push(`${zh.file} ${zh.name} has keys absent from ${en.name}: ${zhOnly.join(', ')}`) if (enOnly.length > 0) problems.push(`${en.file} ${en.name} has keys absent from ${zh.name}: ${enOnly.join(', ')}`) const ja = slot.get('ja') - if (ja !== undefined) { - japaneseDictionaries++ + if (ja === undefined) { + problems.push(`${key} has no Japanese dictionary`) + } else { const missing = en.keys.filter(k => !ja.keys.includes(k)) const extra = ja.keys.filter(k => !en.keys.includes(k)) if (missing.length > 0 || extra.length > 0) { @@ -332,13 +385,11 @@ describe('shipped locale dictionaries', () => { } } } - } // The shipped dictionary count only grows; a collapse means discovery or // pairing broke, which would hide real asymmetry. expect(comparedPairs).toBeGreaterThan(25) - expect(japaneseDictionaries).toBeGreaterThan(60) expect(problems).toEqual([]) }) }) From 93b5d592299f49929a927489130367b95f97bdf7 Mon Sep 17 00:00:00 2001 From: 07akioni <07akioni2@gmail.com> Date: Wed, 21 Oct 2026 13:49:38 +0800 Subject: [PATCH 4/4] fix(locale): translate current approval choices and preset guidance --- packages/client/ui-agent-preset/src/client/guide-locales.ts | 6 +++--- packages/client/ui-approval/src/client/locales.ts | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/client/ui-agent-preset/src/client/guide-locales.ts b/packages/client/ui-agent-preset/src/client/guide-locales.ts index 3ecdfb9ee2..ff5a5b5529 100644 --- a/packages/client/ui-agent-preset/src/client/guide-locales.ts +++ b/packages/client/ui-agent-preset/src/client/guide-locales.ts @@ -161,15 +161,15 @@ export const guideJa = { 'guideCopied': 'コピーしました', 'guideFootnotes': '脚注', 'guideStandardIntro': '新しいタスクを始めるときは標準モードを選びます。達成したいことを説明し、関連するファイルを示し、結果の確認方法を伝えてください。', - 'guideStandardExplanation': '### 仕組み\n\nエージェントはツールを直接呼び出してファイルの読み取りや編集、検索、ターミナルコマンドの実行を行います。スキル、計画、目標、サブエージェント、ワークフロー、コンテキストの圧縮に対応しています。\n\n### 選ぶべき場面\n\n日々のコーディング、ファイル作業、調査はまずここから始めてください。標準モードではスクリプトの作成やファイルの一括処理も行えます。PTC はツール呼び出しの構成を変えるものであり、一括処理に必須ではありません。', + 'guideStandardExplanation': '### 仕組み\n\nエージェントはツールを直接呼び出してファイルの読み取りや編集、検索、ターミナルコマンドの実行を行います。スキル、計画、目標、サブエージェント、ワークフロー、プラグイン管理、コンテキストの圧縮に対応しています。\n\n### 選ぶべき場面\n\n日々のコーディング、ファイル作業、調査はまずここから始めてください。標準モードではスクリプトの作成やファイルの一括処理も行えます。PTC はツール呼び出しの構成を変えるものであり、一括処理に必須ではありません。', 'guideStandardUsage': '### バグを修正する\n\n> 検索フォームを二度送信すると結果が消える原因を調べてください。修正して関連するテストを実行し、原因と変更内容を説明してください。\n\n期待される出力: コードの変更、関連するテスト結果、原因の説明。\n\n### プロジェクトのメモを整理する\n\n> このプロジェクト内の Markdown メモを読み、合意された決定と未解決の課題を、元ファイルへのリンク付きで要約してください。\n\n期待される出力: 元のメモと照合できる参照付きの要約。', 'guidePtcIntro': '新しいタスクを始めるときは PTC モードを選びます。入力ファイル、処理ルール、出力形式を指定してください。コードはエージェントが書きます。', - 'guidePtcExplanation': '### ツールの呼び出され方\n\nPTC は Programmatic Tool Calling の略です。この組み込みプリセットでは、エージェントは run_code を使い、生成された SDK 経由でツールを呼び出す TypeScript プログラムを書きます。プログラムではループ、条件分岐、エラー処理、必要に応じて並行呼び出しを利用できます。\n\n### モデルに届くもの\n\nツールの結果はまずプログラムに届き、そこでフィルタリングや結合ができます。モデルが受け取るのはプログラムが出力または返した内容です。画像の結果は別途添付されます。ネストされたツール呼び出しも記録され、ツール権限の対象のままです。\n\n### 標準モードとの比較\n\nどちらのモードもコーディングや一括処理のタスクを扱えます。標準モードは個々のツールを直接公開し、PTC はツール呼び出しをコードで整理します。現在の PTC プリセットではワークフローツールが無効になっています。速度とトークン消費はタスクやプログラムの結果の扱い方によって変わります。', + 'guidePtcExplanation': '### ツールの呼び出され方\n\nPTC は Programmatic Tool Calling の略です。この組み込みプリセットでは、エージェントは run_code を使い、生成された SDK 経由でツールを呼び出す TypeScript プログラムを書きます。プログラムではループ、条件分岐、エラー処理、必要に応じて並行呼び出しを利用できます。\n\n### モデルに届くもの\n\nツールの結果はまずプログラムに届き、そこでフィルタリングや結合ができます。モデルが受け取るのはプログラムが出力または返した内容です。画像の結果は別途添付されます。ネストされたツール呼び出しも記録され、ツール権限の対象のままです。\n\n### 標準モードとの比較\n\nどちらのモードもコーディングや一括処理のタスクを扱えます。標準モードは個々のツールを直接公開し、PTC はツール呼び出しをコードで整理します。現在の PTC プリセットではワークフローとプラグイン管理ツールが無効になっています。速度とトークン消費はタスクやプログラムの結果の扱い方によって変わります。', 'guidePtcUsage': '### 設定ファイルのセットを検証する\n\n> configs/ 内のすべての JSON ファイルを検査してください。schema.json に基づき、必須フィールドの欠落と無効な値を一覧にしてください。問題ごとに 1 行の CSV を保存してください。読み取れないファイルもレポートに含め、残りの検査は続けてください。元のファイルは変更しないでください。\n\n期待される出力: 問題の要約と CSV レポート。プログラムは同じ検査を繰り返し、個々の失敗に対処し、結果を集約できます。\n\n### エラーログを要約する\n\n> logs/ 内のログファイルを分析してください。エラーをサービスとエラー種別でグループ化し、頻度上位 10 グループと各 1 件の例を示してください。完全な件数を CSV に保存してください。\n\n期待される出力: 上位のエラーグループと完全な件数表。中間データは要約がモデルに届く前にプログラム内で集約できます。', 'guideMinimalIntro': '新しいタスクには最小モードを選びます。比較を行う場合は、モデル、権限、入力、開始時のワークスペース状態を実行間で一定に保ってください。', 'guideMinimalExplanation': '### 含まれるもの\n\n永続的なシェルツール 1 つ、read_image、固定のシステムプロンプト。画像の読み取りには画像対応モデルが必要です。古い履歴はコンテキストの負荷に応じて自動的に圧縮されます。エージェントがアイドル状態のときに /compact を使えば手動で圧縮できます。この組み込みプリセットはスキル、計画、標準のランタイムコンテキストを読み込みません。\n\n### 選ぶべき場面\n\n実験や比較のベースラインとして使用します。シェルコマンド経由でファイルの読み取りやスクリプトの実行はできますが、長いタスクを管理するための組み込み手段は少なくなります。ツールが少ないからといって、初心者にとって必ずしも簡単になるわけではありません。', 'guideMinimalUsage': '### 小さなバグ修正で性能を比較する\n\n> このプロジェクトのテストを実行し、失敗の原因を見つけて最小限の修正をしてください。関連するテストを再度実行し、結果を報告してください。\n\n同じ開始状態から、標準モードと最小モードで同じタスクを別々に実行します。タスクの完了状況、ツール呼び出し、生じた変更を比較してください。最小モードはターミナルコマンドを通じて作業を行います。', 'guideCordisIntro': '新しいタスクにはクリエイターモードを選びます。実現したい機能、表示される場所、確認方法を説明してください。', - 'guideCordisExplanation': '### 作成できるもの\n\nクリエイターモードには標準的なタスクツールに加え、ランタイムの検査、永続的なプラグイン管理、Cordis プラグインやエージェントプリセットの作成ガイドが含まれます。機能や UI を追加するプラグインを作成することも、特定の作業向けにツールとプロンプトを組み合わせたプリセットを作成することもできます。\n\n### プラグインとモード\n\nプラグインはツール、サービス接続、UI エントリなど、DSH に機能を追加します。モードはエージェントプリセットであり、ツールを選択し、タスク内でのエージェントの動作を定義します。プラグインはカスタムプリセットに含めることができます。\n\n### 結果を反映させる方法\n\nソースコードを生成するだけでなく、エージェントにインストールと確認を依頼してください。プラグインは変更内容によっては即座に読み込まれるか、再起動が必要になる場合があります。新しく作成したプリセットは、新しいタスクを始めるときに選択されます。', + 'guideCordisExplanation': '### 作成できるもの\n\nクリエイターモードには標準的なタスクツールに加え、ランタイムの検査、Cordis プラグインやエージェントプリセットの作成ガイドが含まれます。機能や UI を追加するプラグインを作成することも、特定の作業向けにツールとプロンプトを組み合わせたプリセットを作成することもできます。\n\n### プラグインとモード\n\nプラグインはツール、サービス接続、UI エントリなど、DSH に機能を追加します。モードはエージェントプリセットであり、ツールを選択し、タスク内でのエージェントの動作を定義します。プラグインはカスタムプリセットに含めることができます。\n\n### 結果を反映させる方法\n\nソースコードを生成するだけでなく、エージェントにインストールと確認を依頼してください。プラグインは変更内容によっては即座に読み込まれるか、再起動が必要になる場合があります。新しく作成したプリセットは、新しいタスクを始めるときに選択されます。', 'guideCordisUsage': '### UI を追加する\n\n> サイドバーにプロジェクトメモのエントリを追加する DSH プラグインを作成してください。このワークスペース内の Markdown ファイルを閲覧でき、選択したメモをプレビューできるようにしてください。インストールしてページが開くことを確認してください。\n\n期待される出力: 動作するエントリとプレビューページを備えた、インストール済みのプラグイン。残っている有効化手順があればそれも含みます。\n\n### ツールを追加する\n\n> このプロジェクトのテストレポートを読み、失敗したテストを要約するツールを備えたプラグインを作成してください。登録し、サンプルのレポートで確認してください。\n\n期待される出力: 呼び出し可能なツールを備えたプラグインと、確認済みのサンプル呼び出し。\n\n### 独自のモードを作成する\n\n> 標準モードをベースに「コードレビュー」モードを作成してください。潜在的なバグとテストの不足を優先し、ファイルパスと行番号を示し、ファイルを変更する前に確認を求めるようにしてください。選択可能なプリセットとして保存してください。\n\n期待される出力: 新しいタスク用のカスタムプリセット。これらのレビュー指示はエージェントを導くものであり、実行できる操作は権限設定によって決まります。', } satisfies Record diff --git a/packages/client/ui-approval/src/client/locales.ts b/packages/client/ui-approval/src/client/locales.ts index c380c47289..c8e540fd49 100644 --- a/packages/client/ui-approval/src/client/locales.ts +++ b/packages/client/ui-approval/src/client/locales.ts @@ -31,5 +31,7 @@ export const ja = { 'detail.aria': '承認の詳細', 'escalation': 'ツール {toolName} が特権実行を要求しています', 'reject': '拒否', + 'allow': '許可', 'allowOnce': '1 回だけ許可', + 'allowAlways': '常に許可', } satisfies Record