Merge origin/master after upstream multilingual CI fixes

This commit is contained in:
fz committed 2026-10-21 18:03:50 +08:00
commit 98bea53628
501 files changed
+3163 -155

No files matched your search

+2 -2
View File
@@ -33,8 +33,8 @@
en: 372297005b57a08b
zh: 074a635ac17e4362
/deepseek-harness-desktop/installation-ownership:
en: b3cf09d7c901d727
zh: 2ec34045a201d8e9
en: ae0384d699f55cea
zh: ffb879b1d4b08bae
/deepseek-harness-desktop/installation-ownership/runtime-and-plugin-activation:
en: a1a47b09f4a72ba4
zh: 7b1cf27f0b72cf31
+1 -1
View File
@@ -150,7 +150,7 @@ The product UI retains Web actions, including "Open In..." through the shared au
Update copy preserves the complete version, including prerelease suffixes, without adding v or V. When no update is available, the dialog title reports the result and the body shows the current version.
Electron chooses typed English, Chinese, or Japanese shell copy from its application locale and falls back to English. The macOS application bundle declares English, Simplified Chinese, and Japanese in `CFBundleLocalizations`, allowing macOS to match the initial application language to the user’s preferred languages. Saved Client UI language preferences still take precedence for the main interface. On Windows, the main document's language updates desktop menus, recovery and update prompts. The repository Client UI i18n gate checks desktop sources.
Electron chooses typed English, Chinese, or Japanese shell copy from its application locale and falls back to English. The macOS application bundle declares English, Simplified Chinese, and Japanese in `CFBundleLocalizations`, allowing macOS to match the initial application language to the user’s preferred languages. Saved Client UI language preferences still take precedence for the main interface. Windows NSIS installer resources live in one JSON file per language under `installer/locales/`; packaging generates NSIS strings and discovers the installer language list from these files. English, Simplified Chinese, and Japanese resources must have matching keys and format placeholders; `pnpm run check:i18n` validates them. The native installer test builds and exercises each configured language. Platform account pages and onboarding artwork use English for languages other than Chinese. On Windows, the main document's language updates desktop menus, recovery and update prompts. The repository Client UI i18n gate checks desktop sources.
Windows and macOS relay native fullscreen transitions and the state after each page load to the renderer, so overlays can adapt their caption clearance.
+1 -1
View File
@@ -150,7 +150,7 @@ Electron 拥有 `$DSH_HOME/profiles/desktop`。其 `dependencies` 包含 pnpm
升级文案完整展示版本号(包括预发布后缀),不额外添加 v 或 V。无可用更新时,弹窗标题显示检查结果,正文显示当前版本。
Electron 根据应用语言选择类型化的英文、中文或日文 shell 文案,并回退到英文。macOS 应用包通过 `CFBundleLocalizations` 声明支持英语、简体中文和日语,让 macOS 根据用户的首选语言匹配初始应用语言。主界面仍优先使用已保存的 Client UI 语言偏好。在 Windows 上,主文档的语言会更新桌面菜单、恢复与更新提示。仓库 Client UI i18n 检查覆盖桌面端源码。
Electron 根据应用语言选择类型化的英文、中文或日文 shell 文案,并回退到英文。macOS 应用包通过 `CFBundleLocalizations` 声明支持英语、简体中文和日语,让 macOS 根据用户的首选语言匹配初始应用语言。主界面仍优先使用已保存的 Client UI 语言偏好。 Windows NSIS 安装器在 `installer/locales/` 下按语言保存独立 JSON 资源;打包从这些文件生成 NSIS 文案和安装器语言清单。英文、简体中文和日语资源的键与格式占位符必须一致,`pnpm run check:i18n` 负责检查。原生安装器测试逐个构建并验证配置中的语言。账户平台页面和引导插图对中文以外的语言使用英文。在 Windows 上,主文档的语言会更新桌面菜单、恢复与更新提示。仓库 Client UI i18n 检查覆盖桌面端源码。
Windows 和 macOS 会在原生全屏切换及每次页面加载后向渲染器同步全屏状态,让浮层调整顶栏避让距离。
+1 -1
View File
@@ -53,7 +53,7 @@ export interface DesktopElectronBuilderConfig {
readonly perMachine: false
readonly allowElevation: false
readonly allowToChangeInstallationDirectory: false
readonly installerLanguages: readonly ['en_US', 'zh_CN']
readonly installerLanguages: readonly string[]
}
readonly beforeBuild: () => Promise<boolean>
readonly beforePack: (context: BeforePackContext) => Promise<void>
+40
View File
@@ -0,0 +1,40 @@
{
"nsisLanguage": "ENGLISH",
"strings": {
"INSTALLER_INSTALL": "Install now",
"INSTALLER_FINISH": "Finish",
"INSTALLER_NATIVE_PROGRESS": "Preparing installation… %d%%",
"INSTALLER_PROGRESS_EXTRACT": "Extracting files… %d%%",
"INSTALLER_PROGRESS_COPY": "Installing files… %d%%",
"INSTALLER_PROGRESS_REGISTER": "Finishing installation… %d%%",
"INSTALLER_PROGRESS_CLEAN": "Cleaning temporary files… %d%%",
"INSTALLER_CHOOSE_PATH": "Choose location",
"INSTALLER_BROWSE": "Browse",
"INSTALLER_LAUNCH": "Launch now",
"INSTALLER_LAUNCH_FAILED": "DeepSeek Harness could not be launched. Retry, or clear Launch now and close the installer.",
"INSTALLER_UI_ERROR": "The installer could not initialize its window. Close it and try again.",
"INSTALLER_THEME_ERROR": "Use /THEME=auto, /THEME=light or /THEME=dark.",
"INSTALLER_FAILED": "Installation did not finish. Close this installer and retry. If the problem persists, download the installer again.",
"INSTALLER_RUNNING": "DeepSeek Harness is running. Quit it from the system tray and try again.",
"INSTALLER_DIRECTORY_FAILED": "The installation directory could not be replaced.",
"INSTALLER_DIRECTORY_HINT": "Check access to the installation directory and close programs using its files, then run the installer again. Copy the error details if the problem persists.",
"INSTALLER_DIRECTORY_RESTORE_HINT": "The previous installation could not be restored. Keep the backup directory shown in the error details and contact support before installing again.",
"INSTALLER_PATH_INVALID": "Choose a full local folder path. Drive roots, system folders, links and special characters are not supported.",
"INSTALLER_PATH_OWNERSHIP": "Choose an empty folder or the registered DeepSeek Harness installation folder.",
"INSTALLER_PATH_WRITABLE": "This location is not writable. Choose a folder available to the current user.",
"INSTALLER_DISK_SPACE": "There is not enough free disk space. Choose another location.",
"INSTALLER_PER_USER": "This installer supports the current user only. Uninstall the existing all-users installation first.",
"INSTALLER_EXTRACT_FAILED": "The application files could not be extracted.",
"INSTALLER_EXTRACT_HINT": "Security software may have blocked a file, or the disk may be full. Close this installer and try again. If it keeps failing, send us the error details below.",
"INSTALLER_EXTRACT_COPY": "Copy error details",
"INSTALLER_EXTRACT_EXPAND": "Show details",
"INSTALLER_EXTRACT_COLLAPSE": "Hide details",
"INSTALLER_EXTRACT_SAVED": "The full report was saved to %s",
"INSTALLER_EXTRACT_UNSAVED": "The full report could not be saved. Use Copy error details to keep it.",
"INSTALLER_EXTRACT_COPIED": "Error details were copied to the clipboard."
},
"uninstaller": {
"welcome": "Welcome",
"finished": "has been uninstalled"
}
}
+40
View File
@@ -0,0 +1,40 @@
{
"nsisLanguage": "JAPANESE",
"strings": {
"INSTALLER_INSTALL": "今すぐインストール",
"INSTALLER_FINISH": "完了",
"INSTALLER_NATIVE_PROGRESS": "インストールを準備しています… %d%%",
"INSTALLER_PROGRESS_EXTRACT": "ファイルを展開しています… %d%%",
"INSTALLER_PROGRESS_COPY": "ファイルをインストールしています… %d%%",
"INSTALLER_PROGRESS_REGISTER": "インストールを完了しています… %d%%",
"INSTALLER_PROGRESS_CLEAN": "一時ファイルを削除しています… %d%%",
"INSTALLER_CHOOSE_PATH": "インストール先を選択",
"INSTALLER_BROWSE": "参照",
"INSTALLER_LAUNCH": "今すぐ起動",
"INSTALLER_LAUNCH_FAILED": "DeepSeek Harness を起動できませんでした。再試行するか、「今すぐ起動」のチェックを外してインストーラーを閉じてください。",
"INSTALLER_UI_ERROR": "インストーラーのウィンドウを初期化できませんでした。閉じてから再試行してください。",
"INSTALLER_THEME_ERROR": "/THEME=auto、/THEME=light、/THEME=dark のいずれかを指定してください。",
"INSTALLER_FAILED": "インストールが完了しませんでした。インストーラーを閉じて再試行してください。問題が解決しない場合は、インストーラーを再度ダウンロードしてください。",
"INSTALLER_RUNNING": "DeepSeek Harness が実行中です。システムトレイから終了して再試行してください。",
"INSTALLER_DIRECTORY_FAILED": "インストール先のディレクトリを置き換えられませんでした。",
"INSTALLER_DIRECTORY_HINT": "インストール先へのアクセス権限を確認し、そのファイルを使用しているプログラムを閉じてから、インストーラーを再実行してください。問題が続く場合はエラーの詳細をコピーしてください。",
"INSTALLER_DIRECTORY_RESTORE_HINT": "以前のインストールを復元できませんでした。エラーの詳細に表示されたバックアップディレクトリを保持し、再インストール前にサポートへお問い合わせください。",
"INSTALLER_PATH_INVALID": "ローカルフォルダーの完全なパスを選択してください。ドライブのルート、システムフォルダー、リンク、特殊文字を含むパスは使用できません。",
"INSTALLER_PATH_OWNERSHIP": "空のフォルダー、または登録済みの DeepSeek Harness インストール先を選択してください。",
"INSTALLER_PATH_WRITABLE": "この場所には書き込めません。現在のユーザーが書き込めるフォルダーを選択してください。インストーラーは管理者権限を要求しません。",
"INSTALLER_DISK_SPACE": "ディスクの空き容量が不足しています。別の場所を選択してください。",
"INSTALLER_PER_USER": "このインストーラーは現在のユーザーのみを対象としています。先にすべてのユーザー向けの既存のインストールを削除してください。",
"INSTALLER_EXTRACT_FAILED": "アプリケーションのファイルを展開できませんでした。",
"INSTALLER_EXTRACT_HINT": "セキュリティソフトがファイルをブロックしたか、ディスクの空き容量が不足している可能性があります。インストーラーを閉じて再試行してください。問題が続く場合は、以下のエラーの詳細をお送りください。",
"INSTALLER_EXTRACT_COPY": "エラーの詳細をコピー",
"INSTALLER_EXTRACT_EXPAND": "詳細を表示",
"INSTALLER_EXTRACT_COLLAPSE": "詳細を非表示",
"INSTALLER_EXTRACT_SAVED": "完全なレポートを %s に保存しました",
"INSTALLER_EXTRACT_UNSAVED": "完全なレポートを保存できませんでした。「エラーの詳細をコピー」で保存してください。",
"INSTALLER_EXTRACT_COPIED": "エラーの詳細をクリップボードにコピーしました。"
},
"uninstaller": {
"welcome": "アンインストール ウィザードへようこそ",
"finished": "アンインストールされました"
}
}
+40
View File
@@ -0,0 +1,40 @@
{
"nsisLanguage": "SIMPCHINESE",
"strings": {
"INSTALLER_INSTALL": "立即安装",
"INSTALLER_FINISH": "完成",
"INSTALLER_NATIVE_PROGRESS": "正在准备安装… %d%%",
"INSTALLER_PROGRESS_EXTRACT": "正在解压文件… %d%%",
"INSTALLER_PROGRESS_COPY": "正在安装文件… %d%%",
"INSTALLER_PROGRESS_REGISTER": "正在完成安装… %d%%",
"INSTALLER_PROGRESS_CLEAN": "正在清理临时文件… %d%%",
"INSTALLER_CHOOSE_PATH": "选择安装位置",
"INSTALLER_BROWSE": "选择",
"INSTALLER_LAUNCH": "立即启动",
"INSTALLER_LAUNCH_FAILED": "无法启动 DeepSeek Harness。请重试,或取消勾选立即启动后关闭安装程序。",
"INSTALLER_UI_ERROR": "无法初始化安装窗口,请关闭后重试。",
"INSTALLER_THEME_ERROR": "主题参数须为 /THEME=auto、/THEME=light 或 /THEME=dark。",
"INSTALLER_FAILED": "安装未完成,请关闭安装程序后重试。如仍然失败,请重新下载安装包。",
"INSTALLER_RUNNING": "DeepSeek Harness 正在运行,请先在系统托盘中退出应用后重试。",
"INSTALLER_DIRECTORY_FAILED": "无法替换安装目录。",
"INSTALLER_DIRECTORY_HINT": "请检查安装目录的访问权限,关闭占用其中内容的程序后重新运行安装器。如仍失败,请复制错误详情。",
"INSTALLER_DIRECTORY_RESTORE_HINT": "未能恢复原安装目录。请保留错误详情中列出的备份目录,联系支持人员后再安装。",
"INSTALLER_PATH_INVALID": "请选择本地磁盘上的完整文件夹路径,不能使用磁盘根目录、系统目录、链接目录或包含特殊字符的路径。",
"INSTALLER_PATH_OWNERSHIP": "请选择空文件夹,或 DeepSeek Harness 原来的安装目录。",
"INSTALLER_PATH_WRITABLE": "无法写入此位置。请选择当前用户可写入的文件夹;安装程序不会申请管理员权限。",
"INSTALLER_DISK_SPACE": "此磁盘的可用空间不足,请选择其他位置。",
"INSTALLER_PER_USER": "此安装程序仅支持当前用户。请先卸载已有的所有用户安装版本。",
"INSTALLER_EXTRACT_FAILED": "无法解压应用程序文件。",
"INSTALLER_EXTRACT_HINT": "可能是安全软件拦截了文件,或磁盘空间不足。请关闭安装程序后重试;如仍然失败,请将下方错误信息发送给我们。",
"INSTALLER_EXTRACT_COPY": "复制错误信息",
"INSTALLER_EXTRACT_EXPAND": "显示详情",
"INSTALLER_EXTRACT_COLLAPSE": "隐藏详情",
"INSTALLER_EXTRACT_SAVED": "完整错误报告已保存到 %s",
"INSTALLER_EXTRACT_UNSAVED": "无法保存完整错误报告,请使用“复制错误信息”保留。",
"INSTALLER_EXTRACT_COPIED": "错误信息已复制到剪贴板。"
},
"uninstaller": {
"welcome": "欢迎",
"finished": "已从你"
}
}
-63
View File
@@ -1,63 +0,0 @@
; NSIS owns installer localization independently of the Electron application locale.
LangString INSTALLER_INSTALL ${LANG_ENGLISH} "Install now"
LangString INSTALLER_INSTALL ${LANG_SIMPCHINESE} "立即安装"
LangString INSTALLER_FINISH ${LANG_ENGLISH} "Finish"
LangString INSTALLER_FINISH ${LANG_SIMPCHINESE} "完成"
LangString INSTALLER_NATIVE_PROGRESS ${LANG_ENGLISH} "Preparing installation… %d%%"
LangString INSTALLER_NATIVE_PROGRESS ${LANG_SIMPCHINESE} "正在准备安装… %d%%"
LangString INSTALLER_PROGRESS_EXTRACT ${LANG_ENGLISH} "Extracting files… %d%%"
LangString INSTALLER_PROGRESS_EXTRACT ${LANG_SIMPCHINESE} "正在解压文件… %d%%"
LangString INSTALLER_PROGRESS_COPY ${LANG_ENGLISH} "Installing files… %d%%"
LangString INSTALLER_PROGRESS_COPY ${LANG_SIMPCHINESE} "正在安装文件… %d%%"
LangString INSTALLER_PROGRESS_REGISTER ${LANG_ENGLISH} "Finishing installation… %d%%"
LangString INSTALLER_PROGRESS_REGISTER ${LANG_SIMPCHINESE} "正在完成安装… %d%%"
LangString INSTALLER_PROGRESS_CLEAN ${LANG_ENGLISH} "Cleaning temporary files… %d%%"
LangString INSTALLER_PROGRESS_CLEAN ${LANG_SIMPCHINESE} "正在清理临时文件… %d%%"
LangString INSTALLER_CHOOSE_PATH ${LANG_ENGLISH} "Choose location"
LangString INSTALLER_CHOOSE_PATH ${LANG_SIMPCHINESE} "选择安装位置"
LangString INSTALLER_BROWSE ${LANG_ENGLISH} "Browse"
LangString INSTALLER_BROWSE ${LANG_SIMPCHINESE} "选择"
LangString INSTALLER_LAUNCH ${LANG_ENGLISH} "Launch now"
LangString INSTALLER_LAUNCH ${LANG_SIMPCHINESE} "立即启动"
LangString INSTALLER_LAUNCH_FAILED ${LANG_ENGLISH} "DeepSeek Harness could not be launched. Retry, or clear Launch now and close the installer."
LangString INSTALLER_LAUNCH_FAILED ${LANG_SIMPCHINESE} "无法启动 DeepSeek Harness。请重试,或取消勾选立即启动后关闭安装程序。"
LangString INSTALLER_UI_ERROR ${LANG_ENGLISH} "The installer could not initialize its window. Close it and try again."
LangString INSTALLER_UI_ERROR ${LANG_SIMPCHINESE} "无法初始化安装窗口,请关闭后重试。"
LangString INSTALLER_THEME_ERROR ${LANG_ENGLISH} "Use /THEME=auto, /THEME=light or /THEME=dark."
LangString INSTALLER_THEME_ERROR ${LANG_SIMPCHINESE} "主题参数须为 /THEME=auto、/THEME=light 或 /THEME=dark。"
LangString INSTALLER_FAILED ${LANG_ENGLISH} "Installation did not finish. Close this installer and retry. If the problem persists, download the installer again."
LangString INSTALLER_FAILED ${LANG_SIMPCHINESE} "安装未完成,请关闭安装程序后重试。如仍然失败,请重新下载安装包。"
LangString INSTALLER_RUNNING ${LANG_ENGLISH} "DeepSeek Harness is running. Quit it from the system tray and try again."
LangString INSTALLER_RUNNING ${LANG_SIMPCHINESE} "DeepSeek Harness 正在运行,请先在系统托盘中退出应用后重试。"
LangString INSTALLER_DIRECTORY_FAILED ${LANG_ENGLISH} "The installation directory could not be replaced."
LangString INSTALLER_DIRECTORY_FAILED ${LANG_SIMPCHINESE} "无法替换安装目录。"
LangString INSTALLER_DIRECTORY_HINT ${LANG_ENGLISH} "Check access to the installation directory and close programs using its files, then run the installer again. Copy the error details if the problem persists."
LangString INSTALLER_DIRECTORY_HINT ${LANG_SIMPCHINESE} "请检查安装目录的访问权限,关闭占用其中内容的程序后重新运行安装器。如仍失败,请复制错误详情。"
LangString INSTALLER_DIRECTORY_RESTORE_HINT ${LANG_ENGLISH} "The previous installation could not be restored. Keep the backup directory shown in the error details and contact support before installing again."
LangString INSTALLER_DIRECTORY_RESTORE_HINT ${LANG_SIMPCHINESE} "未能恢复原安装目录。请保留错误详情中列出的备份目录,联系支持人员后再安装。"
LangString INSTALLER_PATH_INVALID ${LANG_ENGLISH} "Choose a full local folder path. Drive roots, system folders, links and special characters are not supported."
LangString INSTALLER_PATH_INVALID ${LANG_SIMPCHINESE} "请选择本地磁盘上的完整文件夹路径,不能使用磁盘根目录、系统目录、链接目录或包含特殊字符的路径。"
LangString INSTALLER_PATH_OWNERSHIP ${LANG_ENGLISH} "Choose an empty folder or the registered DeepSeek Harness installation folder."
LangString INSTALLER_PATH_OWNERSHIP ${LANG_SIMPCHINESE} "请选择空文件夹,或 DeepSeek Harness 原来的安装目录。"
LangString INSTALLER_PATH_WRITABLE ${LANG_ENGLISH} "This location is not writable. Choose a folder available to the current user."
LangString INSTALLER_PATH_WRITABLE ${LANG_SIMPCHINESE} "无法写入此位置。请选择当前用户可写入的文件夹;安装程序不会申请管理员权限。"
LangString INSTALLER_DISK_SPACE ${LANG_ENGLISH} "There is not enough free disk space. Choose another location."
LangString INSTALLER_DISK_SPACE ${LANG_SIMPCHINESE} "此磁盘的可用空间不足,请选择其他位置。"
LangString INSTALLER_PER_USER ${LANG_ENGLISH} "This installer supports the current user only. Uninstall the existing all-users installation first."
LangString INSTALLER_PER_USER ${LANG_SIMPCHINESE} "此安装程序仅支持当前用户。请先卸载已有的所有用户安装版本。"
LangString INSTALLER_EXTRACT_FAILED ${LANG_ENGLISH} "The application files could not be extracted."
LangString INSTALLER_EXTRACT_FAILED ${LANG_SIMPCHINESE} "无法解压应用程序文件。"
LangString INSTALLER_EXTRACT_HINT ${LANG_ENGLISH} "Security software may have blocked a file, or the disk may be full. Close this installer and try again. If it keeps failing, send us the error details below."
LangString INSTALLER_EXTRACT_HINT ${LANG_SIMPCHINESE} "可能是安全软件拦截了文件,或磁盘空间不足。请关闭安装程序后重试;如仍然失败,请将下方错误信息发送给我们。"
LangString INSTALLER_EXTRACT_COPY ${LANG_ENGLISH} "Copy error details"
LangString INSTALLER_EXTRACT_COPY ${LANG_SIMPCHINESE} "复制错误信息"
LangString INSTALLER_EXTRACT_EXPAND ${LANG_ENGLISH} "Show details"
LangString INSTALLER_EXTRACT_EXPAND ${LANG_SIMPCHINESE} "显示详情"
LangString INSTALLER_EXTRACT_COLLAPSE ${LANG_ENGLISH} "Hide details"
LangString INSTALLER_EXTRACT_COLLAPSE ${LANG_SIMPCHINESE} "隐藏详情"
LangString INSTALLER_EXTRACT_SAVED ${LANG_ENGLISH} "The full report was saved to %s"
LangString INSTALLER_EXTRACT_SAVED ${LANG_SIMPCHINESE} "完整错误报告已保存到 %s"
LangString INSTALLER_EXTRACT_UNSAVED ${LANG_ENGLISH} "The full report could not be saved. Use Copy error details to keep it."
LangString INSTALLER_EXTRACT_UNSAVED ${LANG_SIMPCHINESE} "无法保存完整错误报告,请使用“复制错误信息”保留。"
LangString INSTALLER_EXTRACT_COPIED ${LANG_ENGLISH} "Error details were copied to the clipboard."
LangString INSTALLER_EXTRACT_COPIED ${LANG_SIMPCHINESE} "错误信息已复制到剪贴板。"
@@ -1,7 +1,8 @@
import { readInstallerLocales, renderInstallerStrings } from './installer-locales.mjs'
import { officePackageDirectories } from '../../../scripts/libreoffice-packages.mjs'
import { X509Certificate } from 'node:crypto'
import { readFileSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { readFile, writeFile } from 'node:fs/promises'
import { join, relative, sep } from 'node:path'
import { fileURLToPath } from 'node:url'
import { execFile } from 'node:child_process'
@@ -129,6 +130,7 @@ export function createElectronBuilderConfig(
'-OutputDirectory', join(buildPaths.root, 'installer-ui')], {
env: scrubWindowsSigningEnvironment(env), windowsHide: true,
})
await writeFile(join(buildPaths.root, 'installer-ui', 'strings.nsh'), renderInstallerStrings(readInstallerLocales()), 'utf8')
if (windowsSigner !== undefined) {
await windowsSigner({ path: join(buildPaths.root, 'installer-ui', 'window-frame.dll'), hash: 'sha256', isNest: false })
}
@@ -262,7 +264,7 @@ export function createElectronBuilderConfig(
perMachine: false,
allowElevation: false,
allowToChangeInstallationDirectory: false,
installerLanguages: ['en_US', 'zh_CN'],
installerLanguages: readInstallerLocales().map(locale => locale.id),
differentialPackage: true,
},
detectUpdateChannel: false,
@@ -0,0 +1,25 @@
/** One installer language's compiler identifier, UI strings and NSIS Modern UI test expectations. */
export interface InstallerLocale {
/** electron-builder language and resource filename without its suffix. */
id: string
/** NSIS LANG_ constant suffix. */
nsisLanguage: string
/** Custom installer messages, with Windows printf placeholders preserved. */
strings: Record<string, string>
/** Substrings of the upstream Modern UI uninstaller's welcome and finished pages. */
uninstaller: Record<string, string>
}
/**
* Read complete per-language JSON resources and reject inconsistent keys or placeholders.
* @param directory - Resource directory, defaulting to Desktop installer/locales.
* @returns Validated resources, ordered by filename.
*/
export function readInstallerLocales(directory?: string): InstallerLocale[]
/**
* Generate NSIS LangString declarations from validated resources.
* @param locales - All configured languages or one isolated native-test language.
* @returns UTF-8 NSIS source with literal text escaped.
*/
export function renderInstallerStrings(locales: InstallerLocale[]): string
@@ -0,0 +1,75 @@
/** Per-language JSON resources shared by NSIS packaging, native UI tests and the i18n check. */
import { readFileSync, readdirSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
const defaultDirectory = fileURLToPath(new URL('../installer/locales/', import.meta.url))
/**
* Read and validate installer translations before generating compiler input.
* @param {string} directory - Directory containing one JSON file per installer language.
* @returns {{id: string, nsisLanguage: string, strings: Record<string, string>, uninstaller: Record<string, string>}[]} Complete language resources.
*/
export function readInstallerLocales(directory = defaultDirectory) {
const locales = readdirSync(directory).filter(name => name.endsWith('.json')).sort().map(name => {
if (!/^[a-z]{2}_[A-Z]{2}\.json$/u.test(name)) throw new Error(`${name}: invalid installer language filename`)
/** @type {unknown} */
const resource = JSON.parse(readFileSync(`${directory}/${name}`, 'utf8'))
if (typeof resource !== 'object' || resource === null || !('nsisLanguage' in resource)
|| typeof resource.nsisLanguage !== 'string' || !/^[A-Z]+$/u.test(resource.nsisLanguage)) {
throw new Error(`${name}: invalid NSIS language identifier`)
}
/** @param {unknown} value - Parsed JSON dictionary. @returns {Record<string, string>} Nonempty text values. */
const dictionary = (value) => {
if (typeof value !== 'object' || value === null || Array.isArray(value)) throw new Error(`${name}: expected text dictionary`)
/** @type {Record<string, string>} */
const result = {}
for (const [key, text] of Object.entries(value)) {
if (typeof text !== 'string' || text.trim() === '') throw new Error(`${name}: ${key} must be nonempty text`)
result[key] = text
}
return result
}
const strings = dictionary('strings' in resource ? resource.strings : undefined)
if (Object.keys(strings).some(key => !/^INSTALLER_[A-Z_]+$/u.test(key))) throw new Error(`${name}: invalid installer string key`)
return { id: name.slice(0, -5), nsisLanguage: resource.nsisLanguage, strings,
uninstaller: dictionary('uninstaller' in resource ? resource.uninstaller : undefined) }
})
for (const id of ['en_US', 'zh_CN', 'ja_JP']) {
if (!locales.some(locale => locale.id === id)) throw new Error(`installer locales: missing ${id}.json`)
}
const english = locales.find(locale => locale.id === 'en_US')
if (!english || Object.keys(english.strings).length === 0) throw new Error('installer locales: missing English strings')
if (Object.keys(english.uninstaller).sort().join(',') !== 'finished,welcome') throw new Error('installer locales: expected uninstaller welcome and finished text')
const languages = new Set()
for (const locale of locales) {
if (languages.has(locale.nsisLanguage)) throw new Error(`${locale.id}: duplicate NSIS language ${locale.nsisLanguage}`)
languages.add(locale.nsisLanguage)
for (const field of ['strings', 'uninstaller']) {
const expected = english[field]
const actual = locale[field]
if (Object.keys(expected).sort().join(',') !== Object.keys(actual).sort().join(',')) throw new Error(`${locale.id}: ${field} keys differ from English`)
for (const [key, text] of Object.entries(expected)) {
const placeholders = (/** @type {string} */ value) => (value.match(/%.?/gu) ?? []).join(',')
if (placeholders(text) !== placeholders(actual[key])) throw new Error(`${locale.id}: ${key} changes format placeholders`)
}
}
}
return locales
}
/**
* Generate guarded NSIS LangString declarations from validated JSON resources.
* @param {ReturnType<typeof readInstallerLocales>} locales - Resources for all languages or one isolated native-test build.
* @returns {string} UTF-8 NSIS source, with literal dollar signs, quotes and line breaks escaped.
*/
export function renderInstallerStrings(locales) {
const escape = (/** @type {string} */ text) => text.replace(/\$|"|\r|\n/gu, character => ({ '$': '$$', '"': '$\\"', '\r': '$\\r', '\n': '$\\n' })[character])
return '; Generated from installer/locales/*.json.\n' + locales.map(locale =>
`!ifdef LANG_${locale.nsisLanguage}\n` + Object.entries(locale.strings).map(([key, text]) =>
`LangString ${key} \${LANG_${locale.nsisLanguage}} "${escape(text)}"\n`).join('') + '!endif\n').join('')
}
if (import.meta.main) {
const locales = readInstallerLocales()
console.log(`verify-installer-locales: ${locales.length} languages have matching keys and format placeholders.`)
}
+1 -1
View File
@@ -9,7 +9,7 @@ ManifestDPIAware true
!endif
!macro customHeader
!define /ifndef INSTALLER_STRINGS_FILE "${INSTALLER_SOURCE_DIR}\strings.nsh"
!define /ifndef INSTALLER_STRINGS_FILE "${INSTALLER_BUILD_DIR}\strings.nsh"
!include "${INSTALLER_STRINGS_FILE}"
!ifdef BUILD_UNINSTALLER
BrandingText " "
@@ -1,4 +1,5 @@
/** Builds an isolated native payload through the production NSIS configuration and exercises its UI. */
import { readInstallerLocales, renderInstallerStrings } from './installer-locales.mjs'
import { execFile } from 'node:child_process'
import { randomUUID } from 'node:crypto'
import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises'
@@ -107,8 +108,8 @@ SectionEnd
const compiler = await getMakeNsisPath()
await execute(compiler.path, ['/V2', payloadSource], { ...childOptions, env: { ...childOptions.env, ...compiler.env } })
if (sign) await sign({ path: join(payload, `${productName}.exe`), hash: 'sha256', isNest: false })
const sourceStrings = await readFile(join(appRoot, 'installer', 'strings.nsh'), 'utf8')
for (const language of ['en_US', 'zh_CN']) {
for (const locale of readInstallerLocales()) {
const language = locale.id
const config = createElectronBuilderConfig()
if (sign) {
config.win.forceCodeSigning = true
@@ -117,9 +118,7 @@ SectionEnd
const languageOutput = join(output, language)
await mkdir(languageOutput)
const strings = join(languageOutput, 'strings.nsh')
const languageId = language === 'en_US' ? 'ENGLISH' : 'SIMPCHINESE'
await writeFile(strings, sourceStrings.split('\n').filter((line) =>
!line.startsWith('LangString ') || line.includes(`\${LANG_${languageId}}`)).join('\n'))
await writeFile(strings, renderInstallerStrings([locale]), 'utf8')
const include = join(languageOutput, 'include.nsh')
await writeFile(include, `!define INSTALLER_BUILD_DIR "${join(output, 'ui')}"\n!define INSTALLER_STRINGS_FILE "${strings}"\n!include "${join(appRoot, 'scripts', 'installer.nsh')}"\n`)
await build({ projectDir: appRoot, prepackaged: payload, targets: Platform.WINDOWS.createTarget(['nsis'], Arch.x64), publish: 'never',
@@ -132,7 +131,7 @@ SectionEnd
join(appRoot, 'tests', uninstallOnly ? 'windows-uninstall-smoke.ps1' : 'windows-installer-smoke.ps1'),
'-Installer', join(languageOutput, 'installer-test.exe'),
'-ProductName', productName, '-RegistryKey', guid, '-OutputDirectory', languageOutput,
...uninstallOnly ? ['-Language', languageId, '-PackageName', packageName] : []], childOptions)
'-Language', language, ...uninstallOnly ? ['-PackageName', packageName] : []], childOptions)
process.stdout.write(`${language}\n${result.stdout}`)
}
succeeded = true
@@ -0,0 +1,56 @@
/** Per-language installer resources feed both the packaging language list and the NSIS compiler. */
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { expect, it, onTestFinished } from 'vitest'
import { readInstallerLocales, renderInstallerStrings } from '../scripts/installer-locales.mjs'
function fixture(): string {
const directory = mkdtempSync(join(tmpdir(), 'dsh-installer-locales-'))
onTestFinished(() => { rmSync(directory, { recursive: true, force: true }) })
for (const id of ['en_US', 'zh_CN', 'ja_JP']) {
writeFileSync(join(directory, `${id}.json`), readFileSync(new URL(`../installer/locales/${id}.json`, import.meta.url)))
}
return directory
}
it('generates Japanese NSIS strings from the same files that select packaged languages', () => {
const locales = readInstallerLocales()
expect(locales.map(locale => locale.id)).toEqual(['en_US', 'ja_JP', 'zh_CN'])
const source = renderInstallerStrings(locales)
expect(source).toContain('LangString INSTALLER_INSTALL ${LANG_JAPANESE} "今すぐインストール"')
expect(source).toContain('LangString INSTALLER_NATIVE_PROGRESS ${LANG_JAPANESE} "インストールを準備しています… %d%%"')
expect(source).toContain('!ifdef LANG_JAPANESE\n')
expect(source.match(/^LangString /gmu)).toHaveLength(93)
const japanese = locales.filter(locale => locale.id === 'ja_JP')
expect(renderInstallerStrings(japanese)).not.toContain('${LANG_ENGLISH}')
})
it.each(['en_US', 'zh_CN', 'ja_JP'])('rejects a missing %s configuration', (id) => {
const directory = fixture()
rmSync(join(directory, `${id}.json`))
expect(() => readInstallerLocales(directory)).toThrow(`missing ${id}.json`)
})
it.each([
['missing key', (resource: ReturnType<typeof readInstallerLocales>[number]) => { delete resource.strings.INSTALLER_INSTALL }, 'keys differ'],
['extra key', (resource: ReturnType<typeof readInstallerLocales>[number]) => { resource.strings.INSTALLER_EXTRA = 'extra' }, 'keys differ'],
['empty text', (resource: ReturnType<typeof readInstallerLocales>[number]) => { resource.strings.INSTALLER_INSTALL = '' }, 'nonempty text'],
['changed placeholder', (resource: ReturnType<typeof readInstallerLocales>[number]) => { resource.strings.INSTALLER_NATIVE_PROGRESS = '%s%%' }, 'format placeholders'],
['missing percent escape', (resource: ReturnType<typeof readInstallerLocales>[number]) => { resource.strings.INSTALLER_NATIVE_PROGRESS = '%d%' }, 'format placeholders'],
['extra format specifier', (resource: ReturnType<typeof readInstallerLocales>[number]) => { resource.strings.INSTALLER_NATIVE_PROGRESS = '%d%% %q' }, 'format placeholders'],
['duplicate language', (resource: ReturnType<typeof readInstallerLocales>[number]) => { resource.nsisLanguage = 'ENGLISH' }, 'duplicate NSIS language'],
['invalid compiler token', (resource: ReturnType<typeof readInstallerLocales>[number]) => { resource.nsisLanguage = 'JA\n!error' }, 'invalid NSIS language'],
])('rejects %s', (_name, mutate, diagnostic) => {
const directory = fixture()
const resource = readInstallerLocales(directory).find(locale => locale.id === 'ja_JP')!
mutate(resource)
writeFileSync(join(directory, 'ja_JP.json'), JSON.stringify(resource))
expect(() => readInstallerLocales(directory)).toThrow(diagnostic)
})
it('escapes literal NSIS syntax without changing translated text', () => {
const locale = readInstallerLocales()[0]!
expect(renderInstallerStrings([{ ...locale, strings: { INSTALLER_INSTALL: '"$name"\nnext\rline' } }]))
.toContain('"$\\"$$name$\\"$\\nnext$\\rline"')
})
+12 -1
View File
@@ -5,13 +5,17 @@ import { AppInfo, Packager } from 'app-builder-lib'
import { expandMacro } from 'app-builder-lib/out/util/macroExpander.js'
import { describe, expect, it, vi } from 'vitest'
const { execute } = vi.hoisted(() => ({ execute: vi.fn(async () => undefined) }))
const { execute, write } = vi.hoisted(() => ({ execute: vi.fn(async () => undefined), write: vi.fn(async () => undefined) }))
vi.mock('node:child_process', async (importOriginal) => {
const original = await importOriginal<typeof import('node:child_process')>()
const { promisify } = await import('node:util')
return { ...original, execFile: Object.assign(vi.fn(), { [promisify.custom]: execute }) }
})
vi.mock('node:fs/promises', async importOriginal => ({
...await importOriginal<typeof import('node:fs/promises')>(), writeFile: write,
}))
describe('installer preparation preserves application dependencies', () => {
it.each(['win32', 'darwin'] as const)('rejects a missing production policy before signing on %s', async (platform) => {
const { createElectronBuilderConfig } = await import('../scripts/electron-builder-config.mjs')
@@ -22,6 +26,7 @@ describe('installer preparation preserves application dependencies', () => {
})
it.each(['win32', 'darwin'] as const)('preserves installer metadata and dependency ownership on %s', async (platform) => {
execute.mockClear()
write.mockClear()
const env = {
DSH_DESKTOP_APP_ID: 'com.example.installer',
DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN: 'https://policy.example.com',
@@ -49,6 +54,7 @@ describe('installer preparation preserves application dependencies', () => {
expect(appInfo.copyright).toBe(`Copyright © ${new Date().getFullYear()} DeepSeek Harness`)
if (platform === 'win32') {
expect(expandMacro(config.nsis.uninstallDisplayName, null, appInfo, {}, false)).toBe('DeepSeek Harness')
expect(config.nsis.installerLanguages).toEqual(['en_US', 'ja_JP', 'zh_CN'])
}
const aboutIcon = config.extraResources.find(resource => resource.to === 'icon.png')
expect(aboutIcon).toBeDefined()
@@ -71,6 +77,11 @@ describe('installer preparation preserves application dependencies', () => {
await packager.installAppDependencies(process.platform === 'win32' ? Platform.LINUX : Platform.WINDOWS, Arch.x64)
expect(packager.areNodeModulesHandledExternally).toBe(false)
expect(execute).toHaveBeenCalledTimes(platform === 'win32' ? 1 : 0)
expect(write).toHaveBeenCalledTimes(platform === 'win32' ? 1 : 0)
if (platform === 'win32') {
expect(write).toHaveBeenCalledWith(expect.stringMatching(/installer-ui[\\/]strings\.nsh$/u),
expect.stringContaining('LangString INSTALLER_INSTALL ${LANG_JAPANESE}'), 'utf8')
}
} finally {
vi.unstubAllEnvs()
vi.restoreAllMocks()
@@ -2,7 +2,7 @@
[CmdletBinding()]
param([Parameter(Mandatory)][string]$Installer, [Parameter(Mandatory)][string]$ProductName,
[Parameter(Mandatory)][string]$RegistryKey,
[Parameter(Mandatory)][string]$OutputDirectory)
[Parameter(Mandatory)][string]$OutputDirectory, [Parameter(Mandatory)][string]$Language)
$ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot 'windows-installer-ui.ps1')
Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes
@@ -14,12 +14,8 @@ $uninstaller = Join-Path $installPath ('Uninstall ' + $ProductName + '.exe')
$processes = [Collections.Generic.List[Diagnostics.Process]]::new()
$results = [Collections.Generic.List[string]]::new()
$expected = Get-Content (Join-Path $PSScriptRoot 'expected/windows-installer.json') -Raw | ConvertFrom-Json
$localizedCopy = @{ ENGLISH = @{}; SIMPCHINESE = @{} }
Get-Content (Join-Path $PSScriptRoot '../installer/strings.nsh') -Encoding UTF8 | ForEach-Object {
if ($_ -match '^LangString (INSTALLER_\w+) \$\{LANG_(ENGLISH|SIMPCHINESE)\} "(.*)"$') {
$localizedCopy[$Matches[2]][$Matches[1]] = $Matches[3]
}
}
$resource = Get-Content (Join-Path $PSScriptRoot "../installer/locales/$Language.json") -Encoding UTF8 -Raw | ConvertFrom-Json
$localizedCopy = @{ $Language = $resource.strings }
function Wait-Control([Diagnostics.Process]$Process, [string]$Text, [switch]$Dialog) {
$timer = [Diagnostics.Stopwatch]::StartNew()
do {
@@ -4,6 +4,7 @@ param([Parameter(Mandatory)][string]$Installer, [Parameter(Mandatory)][string]$P
[Parameter(Mandatory)][string]$RegistryKey, [Parameter(Mandatory)][string]$OutputDirectory,
[Parameter(Mandatory)][string]$Language, [Parameter(Mandatory)][string]$PackageName)
$ErrorActionPreference = 'Stop'
$copy = (Get-Content (Join-Path $PSScriptRoot "../installer/locales/$Language.json") -Encoding UTF8 -Raw | ConvertFrom-Json).uninstaller
$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory)
. (Join-Path $PSScriptRoot 'windows-installer-ui.ps1')
[InstallerCapture]::Initialize()
@@ -69,12 +70,12 @@ try {
if ($mode -eq 'home-inside') { $env:DSH_HOME = Join-Path $userDataPath 'home' } else { Remove-Item Env:DSH_HOME -ErrorAction SilentlyContinue }
$process = Start-Process -FilePath $uninstaller -ArgumentList $arguments -PassThru -WindowStyle ($(if ($mode -eq 'interactive') { 'Normal' } else { 'Hidden' }))
if ($mode -eq 'interactive') {
$welcomeText = if ($Language -eq 'ENGLISH') { 'Welcome' } else { -join [char[]]@(0x6B22, 0x8FCE) }
$welcomeText = $copy.welcome
$welcome = Wait-Text $welcomeText
$window = [InstallerCapture]::TopLevel($welcome)
[void][InstallerCapture]::SaveNative($window, (Join-Path $OutputDirectory 'uninstall-welcome.png'), $false)
[InstallerCapture]::Click([InstallerCapture]::GetDlgItem($window, 1))
$finishedText = if ($Language -eq 'ENGLISH') { 'has been uninstalled' } else { -join [char[]]@(0x5DF2, 0x4ECE, 0x4F60) }
$finishedText = $copy.finished
[void](Wait-Text $finishedText)
[InstallerCapture]::Click([InstallerCapture]::GetDlgItem($window, 1))
}
+5 -2
View File
@@ -103,14 +103,17 @@ 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 })
const overflows = (): Promise<number> => jaDialog.evaluate((dialog) => {
const containers = [dialog, ...dialog.querySelectorAll<HTMLElement>('*')]
// The narrow navigation strip scrolls horizontally by design; inspect the panel and section content.
const options = dialog.lastElementChild!
const containers = [dialog, options, ...options.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 options = dialog.lastElementChild!
const probe = document.createElement('div')
probe.dataset.localeOverflowProbe = ''
probe.style.width = '2000px'
@@ -76,10 +76,11 @@
- text: 共 2 个 · 2 运行中
- list:
- listitem:
- text: "@deepseek-ai/dsh-experimental-translator Machine translation with reusable Session results"
- text: 翻译服务 提供机器翻译,并复用会话中已保存的翻译结果。
- code: translator
- code: "@deepseek-ai/dsh-experimental-translator"
- text: 运行中
- switch "启用组件 @deepseek-ai/dsh-experimental-translator" [checked]
- switch "启用组件 翻译服务" [checked]
- listitem:
- text: 思考过程机器翻译 将展开的思考内容机器翻译为其他语言。
- code: cot-translation
@@ -73,10 +73,11 @@
- text: 共 2 个 · 2 运行中
- list:
- listitem:
- text: "@deepseek-ai/dsh-experimental-translator Machine translation with reusable Session results"
- text: 翻译服务 提供机器翻译,并复用会话中已保存的翻译结果。
- code: translator
- code: "@deepseek-ai/dsh-experimental-translator"
- text: 运行中
- switch "启用组件 @deepseek-ai/dsh-experimental-translator" [checked]
- switch "启用组件 翻译服务" [checked]
- listitem:
- text: 思考过程机器翻译 将展开的思考内容机器翻译为其他语言。
- code: cot-translation
@@ -0,0 +1,5 @@
- listitem:
- button "自動認可レビューを表示": 自動認可レビュー
- text: 実験的 各ツール呼び出しの前にモデルが認可を判断する、自動認可レビュー権限モードを提供します。
- status
- switch "自動認可レビューを有効化"
@@ -0,0 +1,32 @@
/** Japanese package resources reach the real plugin manager through Host metadata and Client locale resolution. */
import { fileURLToPath } from 'node:url'
import { chromium, type Browser } from 'playwright'
import { expect, it, onTestFailed, onTestFinished } from 'vitest'
import { captureStableAria, compareOrRefreshGolden, launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold } from './scaffold.ts'
import { saveFailureShot } from './support.ts'
it('displays Japanese plugin titles and descriptions from package metadata', async () => {
const resources: { scaffold?: WebScaffold; browser?: Browser } = {}
onTestFinished(async () => {
try { await resources.browser?.close() } finally { await resources.scaffold?.close() }
})
const scaffold = await launchWebScaffold()
resources.scaffold = scaffold
const browser = await chromium.launch()
resources.browser = browser
const page = await browser.newPage({ locale: 'ja-JP', viewport: { width: 1440, height: 960 } })
const tripwire = watchConsole(page)
onTestFailed(() => saveFailureShot(page, 'japanese-plugin-metadata'))
await page.goto(scaffold.authenticatedUrl)
await page.getByRole('button', { name: 'プラグイン', exact: true }).click()
const panel = page.locator('[data-plugin-panel]')
const title = panel.getByRole('button', { name: '自動認可レビューを表示', exact: true })
await title.waitFor()
const card = title.locator('xpath=ancestor::li')
await card.getByText('各ツール呼び出しの前にモデルが認可を判断する、自動認可レビュー権限モードを提供します。', { exact: true }).waitFor()
const expected = fileURLToPath(new URL('./expected/japanese-plugin-metadata/card.expected.md', import.meta.url))
await compareOrRefreshGolden(expected,
await captureStableAria(page, '[data-plugin-panel] li:has(button[aria-label="自動認可レビューを表示"])', scaffold.workspaceCwd), webSnapshotMode())
expect(tripwire.warnings).toEqual([])
expect(tripwire.pageErrors).toEqual([])
})
+1
View File
@@ -23,6 +23,7 @@
// this client-registered project must not also hold them (one program
// cannot see both sides of the cordis Context merges).
"exclude": [
"tests/japanese-plugin-metadata.e2e.ts",
"tests/rich-text-quotes.e2e.ts",
"tests/hover-input.e2e.ts",
"tests/collapse-timing.e2e.ts",
+2 -2
View File
@@ -21,8 +21,8 @@
en: 0894bd9f35430753
zh: 2d28090e71134edd
/cookbook-adding-a-workspace-package/5-add-optional-plugin-display-metadata:
en: cb799f5d561dd917
zh: 7739af36ef8ffb3f
en: 1238eaea376391d1
zh: c7e606f95d8980bf
/cookbook-adding-a-workspace-package/6-verify:
en: 80c7fb90b1175aae
zh: 2ba37962dbc57299
+1 -1
View File
@@ -136,7 +136,7 @@ Merge these entries into `package.json`, retaining existing runtime exports and
}
```
Keep language files together in `locale/`, with `en.json` as the discovery entry. Fields are optional; present values must be non-empty strings. English `meta.title` and `meta.description` use sentence case: capitalize only the first word, proper nouns, and product names, as in `Git worktrees` ([rule](../../.agents/skills/dsh-prose-standard/SKILL.md#required-coverage-by-prose-location)). Missing files or fields permit fallback, while malformed JSON or invalid fields produce a per-plugin diagnostic.
Keep language files together in `locale/`, with `en.json` as the discovery entry. Fields are optional; present values must be non-empty strings. English `meta.title` and `meta.description` use sentence case: capitalize only the first word, proper nouns, and product names, as in `Git worktrees` ([rule](../../.agents/skills/dsh-prose-standard/SKILL.md#required-coverage-by-prose-location)). At runtime, missing files or fields permit fallback, while malformed JSON or invalid fields produce a per-plugin diagnostic. First-party packages and plugin templates must provide `ja.json` covering every field present in `en.json` or optional `zh.json`, preserving placeholders; detail paths may point to the existing English document. Subpath resources under `locale/<subpath>/` follow the same checks. English descriptions explain user-visible behavior and end with sentence punctuation. Run `pnpm run check:i18n` to verify JSON metadata and UI dictionary completeness; the JSON checks also run in `check:all`, CI static checks, and hygiene.
Fields fall back independently before view-specific name formatting, using the existing locale language chain first:
+1 -1
View File
@@ -136,7 +136,7 @@ Append-only, prefix-stable, replacing, or independent behavior, including the ex
}
```
语言文件统一放在 `locale/`,并保留 `en.json` 作为发现入口。字段可选,填写时必须是非空字符串。英文 `meta.title` 和 `meta.description` 使用 sentence case:只大写首个单词、专有名词和产品名,例如 `Git worktrees`([rule](../../.agents/skills/dsh-prose-standard/SKILL.md#required-coverage-by-prose-location))。文件或字段缺失时允许回退,JSON 损坏或字段无效时显示单插件诊断。
语言文件统一放在 `locale/`,并保留 `en.json` 作为发现入口。字段可选,填写时必须是非空字符串。英文 `meta.title` 和 `meta.description` 使用 sentence case:只大写首个单词、专有名词和产品名,例如 `Git worktrees`([rule](../../.agents/skills/dsh-prose-standard/SKILL.md#required-coverage-by-prose-location))。运行时文件或字段缺失时允许回退,JSON 损坏或字段无效时显示单插件诊断。仓库内的包和插件模板必须提供 `ja.json`,覆盖 `en.json` 或可选 `zh.json` 中的全部字段并保留占位符;详情路径可指向现有英文文档。`locale/<subpath>/` 下的子路径资源使用相同检查。英文描述面向产品用户说明可见行为,并以句末标点结束。运行 `pnpm run check:i18n` 检查 JSON 元数据和 UI 字典完整性;JSON 检查也接入 `check:all`、CI 静态检查和 hygiene。
各字段先独立回退,再按页面规则格式化技术名称;回退优先经过现有 locale 语言链:
+3
View File
@@ -146,6 +146,9 @@
"verify-package-dependencies": "tsx scripts/verify-package-dependencies.ts",
"verify-npm-install-layout": "tsx scripts/verify-npm-install-layout.ts",
"verify-client-packages": "tsx scripts/verify-client-packages.ts",
"check:i18n": "tsx scripts/run-gates.ts i18n",
"verify-installer-locales": "node apps/desktop/scripts/installer-locales.mjs",
"verify-locale-metadata": "tsx scripts/verify-locale-metadata.ts",
"verify-client-ui-i18n": "tsx scripts/verify-client-ui-i18n.ts",
"verify-client-route-resolution": "tsx scripts/verify-client-route-resolution.ts",
"verify-no-bare-dispatcher": "tsx scripts/verify-no-bare-dispatcher.ts",
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ACP server",
"description": "Run agents through JSON-RPC over standard input and output for automation."
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ACP サーバー",
"description": "JSON-RPC の標準入出力でエージェントを操作する、自動化専用の ACP サーバーです。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ACP 服务",
"description": "通过 JSON-RPC 标准输入输出驱动智能体,仅供自动化使用。"
}
}
+2
View File
@@ -19,9 +19,11 @@
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./locale/*.json": "./locale/*.json",
"./package.json": "./package.json"
},
"files": [
"locale/*.json",
"lib/index.js",
"lib/types/**/*.d.ts"
],
@@ -0,0 +1,6 @@
{
"meta": {
"title": "アカウント操作 API",
"description": "認証済みクライアントにアカウント操作機能を提供します。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "リモート呼び出しゲートウェイ",
"description": "クライアントからの API 呼び出しを受け取り、対応するサービスに転送します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "バックグラウンドタスク監視 API",
"description": "バックグラウンドタスクの状態と出力をクライアントに継続して提供します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "Remote access control",
"description": "Control which methods non-owner connections can access and retain permission snapshots for each connection."
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "リモートアクセス制御",
"description": "所有者以外の接続にメソッド単位のリモートアクセス制御と接続ごとの権限スナップショットを提供します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "远程访问控制",
"description": "为非所有者连接提供方法级远程访问控制和各连接的权限快照。"
}
}
+2
View File
@@ -35,6 +35,7 @@
"default": "./lib/typert.remote-client.js"
},
"./src/*": "./src/*",
"./locale/*.json": "./locale/*.json",
"./package.json": "./package.json"
},
"dsh": {
@@ -54,6 +55,7 @@
"watch": "tsdown --watch"
},
"files": [
"locale/*.json",
"lib/index.js",
"lib/client.js",
"lib/types/**/*.js",
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "クライアント API 一覧",
"description": "アプリがクライアントに公開するサーバー機能をまとめます。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "セッション制御 API",
"description": "セッションの読み取り、実行制御、状態更新をクライアントに提供します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "設定読み書き API",
"description": "各種設定の読み取りと変更をクライアントに提供します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ターミナル制御 API",
"description": "セッションのターミナルの作成、入力、読み取り、復元をクライアントに提供します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ワークスペース制御 API",
"description": "ワークスペース操作と、再接続後に復元できる状態をクライアントに提供します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ワークスペースファイル API",
"description": "ワークスペース内のファイルの読み取り、ディレクトリ一覧、ファイル情報の更新を提供します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ローカル添付ファイル保存",
"description": "セッション内の画像などの添付ファイルを保存し、コンテンツ ID で読み取ります。"
}
}
@@ -7,16 +7,16 @@ export const OFFICIAL_ON_DEMAND_CATALOG: readonly OfficialBundleCatalogEntry[] =
{
packageName: '@deepseek-ai/dsh-subagent-claude-code',
meta: {
title: { 'en': 'Claude Code subagent', 'zh': 'Claude Code \u5B50\u667A\u80FD\u4F53' },
description: { 'en': 'Delegate one task to Claude Code using its native authentication and settings.', 'zh': '\u4F7F\u7528 Claude Code \u539F\u751F\u8BA4\u8BC1\u548C\u8BBE\u7F6E\u59D4\u6D3E\u5355\u4E2A\u4EFB\u52A1\u3002' },
title: { 'en': 'Claude Code subagent', 'ja': 'Claude Code \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8', 'zh': 'Claude Code \u5B50\u667A\u80FD\u4F53' },
description: { 'en': 'Delegate one task to Claude Code using its native authentication and settings.', 'ja': 'Claude Code \u306E\u30CD\u30A4\u30C6\u30A3\u30D6\u306E\u8A8D\u8A3C\u3068\u8A2D\u5B9A\u3092\u4F7F\u7528\u3057\u3066\u3001\u5358\u72EC\u306E\u30BF\u30B9\u30AF\u3092\u59D4\u6D3E\u3057\u307E\u3059\u3002', 'zh': '\u4F7F\u7528 Claude Code \u539F\u751F\u8BA4\u8BC1\u548C\u8BBE\u7F6E\u59D4\u6D3E\u5355\u4E2A\u4EFB\u52A1\u3002' },
icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzYiIGhlaWdodD0iMzYiIHZpZXdCb3g9IjAgMCAzNiAzNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIxNC45ODkzIiB5PSIxNS4xODc3IiB3aWR0aD0iMTIuMjM2NSIgaGVpZ2h0PSIxMi4yMzY1IiByeD0iMiIgZmlsbD0idXJsKCNkc2hfcGx1Z2luX2FydF9SMGEpIiBmaWxsLW9wYWNpdHk9IjAuOCI+PC9yZWN0PjxyZWN0IHg9IjguODcxMDkiIHk9IjkuMDY5NyIgd2lkdGg9IjEyLjIzNjUiIGhlaWdodD0iMTIuMjM2NSIgcng9IjIiIGZpbGw9InVybCgjZHNoX3BsdWdpbl9hcnRfUjBiKSIgZmlsbC1vcGFjaXR5PSIwLjgiPjwvcmVjdD48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImRzaF9wbHVnaW5fYXJ0X1IwYSIgeDE9IjIxLjEwNzUiIHkxPSIxNS4xODc3IiB4Mj0iMjEuMTA3NSIgeTI9IjI3LjQyNDMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBzdG9wLWNvbG9yPSIjNDVFN0E0Ij48L3N0b3A+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDU5MDlEIj48L3N0b3A+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImRzaF9wbHVnaW5fYXJ0X1IwYiIgeDE9IjE0Ljk4OTQiIHkxPSI5LjA2OTciIHgyPSIxNC45ODk0IiB5Mj0iMjEuMzA2MyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiM2OUI5RkYiPjwvc3RvcD48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzMjRERTIiPjwvc3RvcD48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48L3N2Zz4K',
},
},
{
packageName: '@deepseek-ai/dsh-subagent-codex',
meta: {
title: { 'en': 'Codex subagent', 'zh': 'Codex \u5B50\u667A\u80FD\u4F53' },
description: { 'en': 'Delegate one task to Codex using its native authentication and settings.', 'zh': '\u4F7F\u7528 Codex \u539F\u751F\u8BA4\u8BC1\u548C\u8BBE\u7F6E\u59D4\u6D3E\u5355\u4E2A\u4EFB\u52A1\u3002' },
title: { 'en': 'Codex subagent', 'ja': 'Codex \u30B5\u30D6\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8', 'zh': 'Codex \u5B50\u667A\u80FD\u4F53' },
description: { 'en': 'Delegate one task to Codex using its native authentication and settings.', 'ja': 'Codex \u306E\u30CD\u30A4\u30C6\u30A3\u30D6\u306E\u8A8D\u8A3C\u3068\u8A2D\u5B9A\u3092\u4F7F\u7528\u3057\u3066\u3001\u5358\u72EC\u306E\u30BF\u30B9\u30AF\u3092\u59D4\u6D3E\u3057\u307E\u3059\u3002', 'zh': '\u4F7F\u7528 Codex \u539F\u751F\u8BA4\u8BC1\u548C\u8BBE\u7F6E\u59D4\u6D3E\u5355\u4E2A\u4EFB\u52A1\u3002' },
icon: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzYiIGhlaWdodD0iMzYiIHZpZXdCb3g9IjAgMCAzNiAzNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIxNC45ODkzIiB5PSIxNS4xODc3IiB3aWR0aD0iMTIuMjM2NSIgaGVpZ2h0PSIxMi4yMzY1IiByeD0iMiIgZmlsbD0idXJsKCNkc2hfcGx1Z2luX2FydF9SMGEpIiBmaWxsLW9wYWNpdHk9IjAuOCI+PC9yZWN0PjxyZWN0IHg9IjguODcxMDkiIHk9IjkuMDY5NyIgd2lkdGg9IjEyLjIzNjUiIGhlaWdodD0iMTIuMjM2NSIgcng9IjIiIGZpbGw9InVybCgjZHNoX3BsdWdpbl9hcnRfUjBiKSIgZmlsbC1vcGFjaXR5PSIwLjgiPjwvcmVjdD48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImRzaF9wbHVnaW5fYXJ0X1IwYSIgeDE9IjIxLjEwNzUiIHkxPSIxNS4xODc3IiB4Mj0iMjEuMTA3NSIgeTI9IjI3LjQyNDMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBzdG9wLWNvbG9yPSIjNDVFN0E0Ij48L3N0b3A+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDU5MDlEIj48L3N0b3A+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImRzaF9wbHVnaW5fYXJ0X1IwYiIgeDE9IjE0Ljk4OTQiIHkxPSI5LjA2OTciIHgyPSIxNC45ODk0IiB5Mj0iMjEuMzA2MyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIHN0b3AtY29sb3I9IiM2OUI5RkYiPjwvc3RvcD48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzMjRERTIiPjwvc3RvcD48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48L3N2Zz4K',
},
},
@@ -0,0 +1,6 @@
{
"meta": {
"title": "プラグイン設定編集",
"description": "プラグイン設定を保存し、変更を現在の実行設定に適用します。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "プラグインホットリロード",
"description": "アプリの実行中にプラグインのコードと設定を再読み込みします。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "プラグイン管理サービス",
"description": "現在の実行設定にあるプラグインとパッケージを管理し、インストール、削除、有効化、無効化を行います。"
}
}
@@ -0,0 +1,5 @@
{
"meta": {
"title": "プラグイン管理ツール"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "Plugin market indexes",
"description": "Manage configured market indexes for plugin discovery and the plugin_market tool."
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "プラグインマーケットのインデックス",
"description": "プラグイン検索ページと plugin_market ツールで使う、設定済みのマーケットのインデックスを管理します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "插件市场索引",
"description": "为插件发现页面和 plugin_market 工具管理已配置的市场索引。"
}
}
+2
View File
@@ -35,9 +35,11 @@
"default": "./lib/typert.remote-client.js"
},
"./src/*": "./src/*",
"./locale/*.json": "./locale/*.json",
"./package.json": "./package.json"
},
"files": [
"locale/*.json",
"lib/index.js",
"lib/worker.cjs",
"lib/types/**/*.js",
@@ -0,0 +1,6 @@
{
"meta": {
"title": "Browser provider registry",
"description": "Register browser automation providers by name, with one provider per name."
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ブラウザ提供元レジストリ",
"description": "名前ごとに排他的なブラウザ操作の提供元を登録します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "浏览器服务注册",
"description": "按名称注册独占的浏览器操作提供方。"
}
}
@@ -19,6 +19,7 @@
"default": "./lib/index.js"
},
"./src/*": "./src/*",
"./locale/*.json": "./locale/*.json",
"./package.json": "./package.json",
"./brand": {
"types": "./lib/types/brand.d.ts",
@@ -26,6 +27,7 @@
}
},
"files": [
"locale/*.json",
"lib/index.js",
"lib/types/**/*.js",
"lib/types/**/*.d.ts"
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ACP profile",
"description": "Provide automation-only ACP communication and process lifecycle management over dsh-base."
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ACP プロファイル",
"description": "dsh-base 上で自動化専用の ACP 通信とプロセスのライフサイクルを提供します。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ACP 运行配置",
"description": "在 dsh-base 上提供自动化专用的 ACP 通信与进程生命周期。"
}
}
+2
View File
@@ -20,9 +20,11 @@
},
"./cordis.patch.yml": "./cordis.patch.yml",
"./src/*": "./src/*",
"./locale/*.json": "./locale/*.json",
"./package.json": "./package.json"
},
"files": [
"locale/*.json",
"lib/index.js",
"cordis.patch.yml",
"lib/types/**/*.d.ts"
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "Headless profile",
"description": "Run a single task directly with the core Agent/Session runner over dsh-base, without a Host, HTTP, or browser layer."
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ヘッドレスプロファイル",
"description": "dsh-base 上でコアの Agent/Session ランナーを直接使用して単一タスクを実行し、Host、HTTP、ブラウザのレイヤーを含みません。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "无界面运行配置",
"description": "在 dsh-base 上直接使用核心 Agent/Session 执行单次任务,不包含 Host、HTTP 或浏览器层。"
}
}
+2
View File
@@ -24,9 +24,11 @@
},
"./cordis.patch.yml": "./cordis.patch.yml",
"./src/*": "./src/*",
"./locale/*.json": "./locale/*.json",
"./package.json": "./package.json"
},
"files": [
"locale/*.json",
"lib/index.js",
"lib/startup.js",
"cordis.patch.yml",
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "SDK profile",
"description": "Provide JSON-RPC over standard input and output and manage the process lifecycle over dsh-base."
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "SDK プロファイル",
"description": "dsh-base 上で標準入出力の JSON-RPC とプロセスのライフサイクル管理を提供します。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "SDK 运行配置",
"description": "在 dsh-base 上提供标准输入输出 JSON-RPC 与进程生命周期管理。"
}
}
+2
View File
@@ -20,9 +20,11 @@
},
"./cordis.patch.yml": "./cordis.patch.yml",
"./src/*": "./src/*",
"./locale/*.json": "./locale/*.json",
"./package.json": "./package.json"
},
"files": [
"locale/*.json",
"lib/index.js",
"cordis.patch.yml",
"lib/types/**/*.d.ts"
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "Web アプリ実行サポート",
"description": "フロントエンドのページリソースを提供し、Web アプリに必要な実行設定を接続します。"
}
}
@@ -0,0 +1,5 @@
{
"meta": {
"title": "Web 起動設定"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "クライアント接続",
"description": "認証済みクライアントとの通信を確立し、接続のライフサイクルを管理します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ファイルアップロード",
"description": "ブラウザ内のファイルを現在のセッションにアップロードし、メッセージやツールで使用できるようにします。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "Web クライアントホット更新",
"description": "ブラウザ内のプラグインを同期し、コードの再ビルド後に関連する UI を更新します。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "表示言語",
"description": "表示言語の設定と各プラグインの翻訳文を管理します。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "クライアントモジュール読み込み",
"description": "ブラウザのプラグインモジュールと読み込み依存関係を管理します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "デスクトップ利用統計",
"description": "デスクトップの指定された操作イベントを収集し、製品の統計サービスに送信します。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "クライアントリソースサービス",
"description": "ファイルなどのリソースアドレスを、UI が読み取れるコンテンツに解決します。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ショートカットサービス",
"description": "アプリのショートカットを登録し、キー操作を対応する機能に振り分けます。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "エージェントプリセット UI",
"description": "現在および新規セッションのエージェントプリセットを選択し、使用するプラグインを編集します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "操作承認 UI",
"description": "承認待ちの操作を表示し、ユーザーの選択を実行中のタスクに返します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "添付ファイル表示",
"description": "メッセージ入力、会話、実行記録に添付ファイルと画像を表示します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "DeepSeek ブランド表示",
"description": "アプリのサイドバーなどに公式ブランドの要素を表示します。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "チャット表示",
"description": "チャットメッセージ、ツールの内容、関連する詳細を描画します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "コマンド選択 UI",
"description": "入力欄にスラッシュコマンドのメニューと関連する操作 UI を提供します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "会話 UI フレームワーク",
"description": "会話内容、入力欄、メッセージキュー、ビューのナビゲーションを構成します。"
}
}
@@ -1096,9 +1096,9 @@ export const ja = {
'tool.title.glob': 'Glob',
'tool.title.webSearch': '検索',
'tool.title.webFetch': '取得',
'tool.autoReviewRejected': '自動レビューにより拒否されました',
'tool.autoReviewRejected': '自動認可レビューにより拒否されました',
'tool.autoReviewNotExecuted': 'ツールは実行されませんでした。理由: {reason}',
'tool.autoReviewReasonFallback': '自動レビューがこの操作を承認しませんでした',
'tool.autoReviewReasonFallback': '自動認可レビューがこの操作を承認しませんでした',
'diff.collapseAria': '差分を折りたたむ',
'diff.expandAria': '差分の残り{count}行を表示',
'diff.expandRest': '… 残り{count}行',
@@ -0,0 +1,6 @@
{
"meta": {
"title": "DeepSeek 1017 model badge",
"description": "Add a temporary 1017 release badge to official DeepSeek Flash model names."
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "DeepSeek 1017 モデルバッジ",
"description": "公式 DeepSeek Flash モデルの名前に、一時的な 1017 リリースバッジを表示します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "DeepSeek 1017 模型标记",
"description": "为官方 DeepSeek Flash 模型名称添加临时的 1017 版本标记。"
}
}
@@ -23,8 +23,8 @@
"default": "./lib/client.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json",
"./locale/*.json": "./locale/*.json"
"./locale/*.json": "./locale/*.json",
"./package.json": "./package.json"
},
"dsh": {
"client": {
@@ -0,0 +1,6 @@
{
"meta": {
"title": "ファイル変更と成果物の表示",
"description": "変更されたファイル、内容の比較、成果物ファイルへの入口を表示します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "アプリ内ディレクトリ選択 UI",
"description": "アプリ内でワークスペースのディレクトリを参照、作成、選択します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "システムディレクトリ選択 UI",
"description": "ワークスペースの選択操作をシステムのディレクトリ選択ダイアログに接続します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "UI カスタマイズ",
"description": "左サイドバー、会話ビュー、右サイドバーのプラグイン項目をカスタマイズし、新しいプラグインをすばやく作成します。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "セッション目標バー",
"description": "入力欄の上に現在のセッションの目標と状態を表示します。"
}
}
@@ -0,0 +1,6 @@
{
"meta": {
"title": "入力候補サービス",
"description": "入力中の / と @ を認識し、対応する候補を表示して選択できます。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "バックグラウンドタスク UI",
"description": "セッション内でバックグラウンドタスクの一覧、状態、リアルタイム出力を確認します。"
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"meta": {
"title": "アプリレイアウト",
"description": "ナビゲーション、会話、サイドパネルを構成し、ドラッグによるレイアウト調整を提供します。"
}
}
Loaded 100 of 501 files, more files were not shown because too many files have changed in this diff. Show more