fix(locale): type additional dictionaries and complete Japanese registration
This commit is contained in:
1 parent
3007a15cdd
commit
6238020202
83 files changed
+497
-395
No files matched your search
+173
-172
@@ -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<keyof typeof en, string>
|
||||
|
||||
/** 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<keyof typeof en, string>
|
||||
@@ -21,15 +21,16 @@ describe.skipIf(MODE === 'record')('web e2e: native and Client locale preference
|
||||
let page: Page
|
||||
let tripwire: ReturnType<typeof watchConsole>
|
||||
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<number> => jaDialog.evaluate((dialog) => {
|
||||
const containers = [dialog, ...dialog.querySelectorAll<HTMLElement>('*')]
|
||||
.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([])
|
||||
|
||||
@@ -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}}
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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. 启用语言包并重新加载应用。确认启动时没有重复注册错误,且选择目标语言后显示对应译文。
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
文档语言同步只在值变化时写入 `<html lang>`;仅更新字典的 revision 不改动该属性。
|
||||
|
||||
带类型的对象形式要求两个内置 locale 都有完整字典;逐 locale 形式允许语言包独立注册每个命名空间。逐键查找会先在请求命名空间中沿生效语言声明的 fallback 链查找,再在 `common` 中重复该链,最后显示键本身。绑定的翻译函数按命名空间保持稳定身份,因此可通过 inject 机制传递,且不会破坏 memoization。
|
||||
带类型的对象形式要求两个基础 locale 都有完整字典;逐 locale 形式允许语言包独立注册每个命名空间。逐键查找会先在请求命名空间中沿生效语言声明的 fallback 链查找,再在 `common` 中重复该链,最后显示键本身。绑定的翻译函数按命名空间保持稳定身份,因此可通过 inject 机制传递,且不会破坏 memoization。
|
||||
|
||||
### 源码地图
|
||||
|
||||
|
||||
@@ -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<N extends Extract<keyof LocaleNamespaceMap, string>>(ns: N, dicts: Record<BuiltInLocaleId, LocaleDictOf<N>>): () => void
|
||||
register<N extends Extract<keyof LocaleNamespaceMap, string>>(
|
||||
ns: N,
|
||||
dicts: Record<BuiltInLocaleId, LocaleDictOf<N>> & Record<LocaleId, LocaleDictOf<N>>,
|
||||
): () => 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<void> {
|
||||
const host = ctx.configForms.get<LocaleSettings>(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) })
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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<LocaleSettings>): {
|
||||
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: '你好' })
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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<SessionId, ChatScrollPosition>()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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': '終了コードなし',
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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<SessionId, FeedbackSurface>()
|
||||
const surfaceFor = (sessionId: SessionId): FeedbackSurface => {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<keyof typeof frequencyEn, string>
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Record<typeof CONTACT_CONFIG_GLOBAL, unknown>>
|
||||
const config = ContactConfig(page[CONTACT_CONFIG_GLOBAL] ?? {})
|
||||
|
||||
@@ -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<AccountKey, string> = {
|
||||
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<AccountKey, string> = {
|
||||
|
||||
/** Japanese copy, checked against the English keys. */
|
||||
export const ja = {
|
||||
...onboardingJapaneseCopy,
|
||||
...onboardingJa,
|
||||
'modelSignInRequired': 'モデルを利用できません。ログインしてから再試行してください。',
|
||||
'sessionExpired': 'アカウントからログアウトしました。もう一度ログインしてください。',
|
||||
'close': '閉じる',
|
||||
|
||||
@@ -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<keyof typeof onboardingCopy, string> = {
|
||||
export const onboardingEn: Record<keyof typeof onboardingZh, string> = {
|
||||
onboardingArtworkLocale: 'en',
|
||||
onboardingWelcome: 'Welcome to',
|
||||
onboardingBrand: 'DeepSeek Harness',
|
||||
@@ -85,7 +85,7 @@ export const onboardingEnglishCopy: Record<keyof typeof onboardingCopy, string>
|
||||
}
|
||||
|
||||
/** 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<keyof typeof onboardingEnglishCopy, string>
|
||||
} satisfies Record<keyof typeof onboardingEn, string>
|
||||
@@ -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({
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<UploadSettings>(namespace)
|
||||
const preference = new UploadPreference(form)
|
||||
const face = (): UploadInjected => ({
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<BrowserBodyProps['sessionId'], BrowserInjected>()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 }],
|
||||
|
||||
@@ -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<readonly SidebarPanelMetadata[]>([])
|
||||
const syncPanels = (): void => {
|
||||
const next = ctx.slots.entriesOfSlot('sidebar.panellist').map(({ options }) => {
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
|
||||
@@ -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<ThemeSettings>(THEME_SETTINGS_NAMESPACE)
|
||||
const theme = new ThemeRuntime(ctx, host)
|
||||
ctx.provide('theme', theme)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<PendingQuestion>(
|
||||
pending => pending.kind === 'plan-review' ? 2 : 1,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
@@ -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
|
||||
|
||||
@@ -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<SessionId, ReturnType<typeof watchSession>>()
|
||||
ctx.effect(() => () => {
|
||||
for (const watch of watches.values()) watch.dispose()
|
||||
|
||||
@@ -80,8 +80,7 @@ async function registerUi(ctx: Context, lifetime: AbortController): Promise<void
|
||||
}
|
||||
const scope = ctx.configForms.get<CotTranslationPreferences>('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 => ({
|
||||
|
||||
@@ -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<Recording>()
|
||||
const microphone = createMicrophoneDeviceStore()
|
||||
const readiness = observeReadiness(ctx)
|
||||
|
||||
@@ -31,8 +31,7 @@ export const inject = ['remote', 'slots', 'locale', 'configForms']
|
||||
export async function mountCuaPermissions(ctx: Context, contribution: TypertRemoteContribution): Promise<() => Promise<void>> {
|
||||
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) => {
|
||||
|
||||
@@ -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<PanelState>('unopened')
|
||||
|
||||
@@ -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 インスペクターの表示切り替え')
|
||||
})
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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<N extends Extract<keyof LocaleNamespaceMap, string>>(ns: N, dicts: Record<BuiltInLocaleId, LocaleDictOf<N>>): () => void',
|
||||
signature: 'register<N extends Extract<keyof LocaleNamespaceMap, string>>( ns: N, dicts: Record<BuiltInLocaleId, LocaleDictOf<N>> & Record<LocaleId, LocaleDictOf<N>>, ): () => 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).',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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<Record<string, string>> {
|
||||
const templates: Record<string, string> = {}
|
||||
@@ -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<string, ts.Expression>()
|
||||
const imported = new Set<string>()
|
||||
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<string, ts.Expression>()
|
||||
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([])
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user