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([]) }) })