- 设置页侧边栏内容/文件预览两个网格末尾各增虚线卡片,弹出对应 kind 的插件弹窗 - 弹窗:GitHub topic 按钮(新标签页)+ 推荐插件目录(plugins-tabs.ts / plugins-viewers.ts) - 每条目「跳转」(window.open 新标签页直达仓库)与「复制」(writeClipboard + 已复制反馈) - 不做终端预填流:不关闭弹窗、不打开终端、无失败路径(install/installNotice/installError* 文案删除) - 内置 Office 预览(docx/xlsx/pptx)整体移除,chunk 白名单 5→2(terminal/editor) - 文案「内置插件」→「推荐插件」;AGENTS.md/README/设计文档同步
497 lines
24 KiB
TypeScript
497 lines
24 KiB
TypeScript
/**
|
||
* Minimal zh/en copy for the sidebar. The copy follows the DSH i18n system:
|
||
* the client apply attaches the locale service (`ctx.locale`, provided by
|
||
* `@deepseek-ai/dsh-client-locale`) through {@link attachLocale}, and
|
||
* `t()`/`isZh()` resolve the active locale from it — the Host-backed
|
||
* `locale.preference` wins over the raw browser language and switches live.
|
||
* Without an attached service (standalone/test compositions) the browser
|
||
* language is used, matching the previous behavior. The dictionaries are
|
||
* also registered into the DSH locale registry under {@link LOCALE_NS}.
|
||
*/
|
||
|
||
/** The zh dictionary (also registered into the DSH locale registry under {@link LOCALE_NS}). */
|
||
export const zh = {
|
||
explorer: '资源管理器',
|
||
git: '源代码管理',
|
||
terminal: '终端',
|
||
editor: '编辑器',
|
||
newTab: '新建标签页',
|
||
openExplorer: '资源管理器',
|
||
openGit: 'Git 面板',
|
||
newTerminal: '新终端',
|
||
terminalLimit: '终端数量已达上限 (3)',
|
||
close: '关闭',
|
||
collapse: '折叠侧边栏',
|
||
expand: '展开侧边栏',
|
||
collapseBottomPanel: '折叠底部面板',
|
||
expandBottomPanel: '展开底部面板',
|
||
terminalError: '终端连接失败',
|
||
terminalConnectFailed: '终端多次连接失败',
|
||
terminalRetry: '重试',
|
||
preview: '预览',
|
||
edit: '编辑',
|
||
refresh: '刷新',
|
||
save: '保存',
|
||
saved: '已保存',
|
||
unsaved: '未保存',
|
||
saveFailed: '保存失败',
|
||
truncation: '文件过大,仅显示前 512KB',
|
||
binary: '二进制文件,无法预览',
|
||
loading: '加载中…',
|
||
error: '加载失败',
|
||
retry: '重试',
|
||
splitLeft: '向左分栏',
|
||
splitRight: '向右分栏',
|
||
splitUp: '向上分栏',
|
||
splitDown: '向下分栏',
|
||
notRepo: '当前目录不是 git 仓库',
|
||
noChanges: '没有变更',
|
||
stage: '暂存',
|
||
unstage: '取消暂存',
|
||
stageAll: '全部暂存',
|
||
unstageAll: '全部取消暂存',
|
||
commitPlaceholder: '提交信息 (Ctrl+Enter)',
|
||
commit: '提交',
|
||
commitError: '提交失败',
|
||
branch: '分支',
|
||
checkoutError: '切换分支失败',
|
||
history: '历史',
|
||
changes: '变更',
|
||
staged: '已暂存',
|
||
unstaged: '未暂存',
|
||
cancel: '取消',
|
||
diffEmpty: '没有文本差异',
|
||
diffLoadError: '加载差异失败',
|
||
diffBinary: '二进制',
|
||
diffAdded: '新增',
|
||
diffDeleted: '删除',
|
||
diffRenamed: '重命名',
|
||
diffExpand: '展开其余 {count} 行',
|
||
diffCollapse: '收起',
|
||
discard: '放弃更改',
|
||
discardTitle: '放弃更改',
|
||
discardDesc: '将丢弃「{path}」的工作区修改(不可恢复)。',
|
||
viewCommitDiff: '查看提交差异',
|
||
copyShortHash: '复制短哈希',
|
||
copyFullHash: '复制完整哈希',
|
||
copySubject: '复制提交信息',
|
||
revertCommit: '还原此提交',
|
||
revertTitle: '还原此提交',
|
||
revertDesc: '将在当前分支创建一个反转「{subject}」的新提交。',
|
||
cherryPickCommit: '捡取此提交',
|
||
cherryPickTitle: '捡取此提交',
|
||
cherryPickDesc: '将「{subject}」的更改应用到当前分支。',
|
||
timeJustNow: '刚刚',
|
||
timeMinutesAgo: '{n} 分钟前',
|
||
timeHoursAgo: '{n} 小时前',
|
||
timeYesterday: '昨天',
|
||
loadMore: '加载更多',
|
||
historyLoadError: '加载更多历史失败',
|
||
produced: '本次产出',
|
||
producedOpen: '在侧边栏中打开',
|
||
disconnected: '终端连接断开,重连中…',
|
||
exited: '终端进程已退出',
|
||
noSession: '选择一个会话以使用侧边栏',
|
||
pluginNotLoaded: '插件未加载,标签页暂不可用:',
|
||
hiddenFiles: '隐藏文件',
|
||
parent: '上级目录',
|
||
copied: '已复制',
|
||
copy: '复制',
|
||
newFile: '新文件',
|
||
openEditor: '打开编辑器',
|
||
gitDetail: '查看变更详情',
|
||
referenceFile: '@文件',
|
||
addToConversation: '添加到对话',
|
||
copyRelative: '复制相对地址',
|
||
copyAbsolute: '复制绝对地址',
|
||
download: '下载',
|
||
settingsNav: '侧边卡片',
|
||
settingsIntro: '管理侧边卡片的显示内容与默认行为',
|
||
settingsPopupDesc: '为「{feature}」配置相关选项',
|
||
settingsDone: '完成',
|
||
settingsOpenTitle: '新会话默认打开',
|
||
settingsOpenDesc: '新建会话时自动展开侧边卡片;已存在的会话保持各自布局',
|
||
settingsWidthTitle: '默认宽度占比',
|
||
settingsWidthDesc: '新建会话时侧边卡片占窗口宽度的百分比 (20–60)',
|
||
settingsWidthSuffix: '%',
|
||
settingsOpenPathTitle: '聊天区文件在侧边栏打开',
|
||
settingsOpenPathDesc: '在聊天里点击文件链接(工具行、产物列表、文件提及)时,在侧边栏编辑器中打开,不再调用系统默认应用',
|
||
settingsSaveFailed: '保存失败',
|
||
settingsConflict: '设置已被其他窗口修改,请重试',
|
||
binaryNoPreview: '此文件类型不支持预览',
|
||
downloadToView: '下载查看',
|
||
settingsSubagentTitle: '检测到子代理时自动展开任务管理页',
|
||
settingsSubagentDesc: '当前会话产生新的子代理时,自动展开侧边栏并打开任务管理页;关闭后需手动打开',
|
||
settingsJobsTitle: '有新后台任务时自动展开后台任务页',
|
||
settingsJobsDesc: '当前会话出现新的后台任务时,自动展开侧边栏并打开后台任务页(每个新任务都会触发);关闭后需手动打开',
|
||
settingsToolsTitle: '为模型注入终端工具',
|
||
settingsToolsDesc: '开启后,模型可通过 terminal_create 等 8 个工具创建并操作侧边栏终端(默认关闭)',
|
||
settingsBottomTerminalTitle: '底部面板首次展开自动开终端',
|
||
settingsBottomTerminalDesc: '每次会话中第一次展开底部面板时,尝试在底部面板自动打开一个新终端标签(终端数量上限仍会限制;默认开启)',
|
||
settingsFontFamilyTitle: '终端字体',
|
||
settingsFontFamilyDesc: '自定义终端字体族(CSS font-family,如 "JetBrains Mono", monospace;留空跟随主题等宽字体)',
|
||
settingsFontFamilyPlaceholder: '"JetBrains Mono", monospace',
|
||
settingsFontSizeTitle: '终端字号',
|
||
settingsFontSizeDesc: '终端字号(9–32,默认 13)',
|
||
settingsFontSizeSuffix: 'px',
|
||
settingsTabsTitle: '侧边栏内容',
|
||
settingsViewersTitle: '文件预览',
|
||
settingsGeneralTitle: '常规',
|
||
settingsPopup: '功能设置',
|
||
settingsViewerCatchAll: '兜底:任意文件',
|
||
viewerImage: '图片',
|
||
viewerPdf: 'PDF',
|
||
viewerMarkdown: 'Markdown',
|
||
viewerCode: '代码',
|
||
viewerBinary: '二进制下载',
|
||
viewerHtml: 'HTML',
|
||
browser: '浏览器',
|
||
browserPlaceholder: '输入网址,例如 example.com',
|
||
browserGo: '前往',
|
||
browserBack: '后退',
|
||
browserForward: '前进',
|
||
browserStart: '输入网址开始浏览(沙箱模式)',
|
||
browserBlockedScheme: '已阻止:仅支持 http/https 链接',
|
||
browserBlockedLoopback: '已阻止:不允许在浏览器中访问本机或内部地址',
|
||
browserInvalid: '无效的网址',
|
||
browserNoSandboxWarning: '沙箱已关闭:当前页面与界面同源,拥有完整会话权限(可在设置中恢复)',
|
||
htmlNoSandboxWarning: '沙箱已关闭:此 HTML 与界面同源,可读取会话文件与内部接口(可在设置中恢复)',
|
||
sandboxStatusOn: '沙箱模式:已启用 · 页面无法访问界面数据与本地文件,登录态与第三方 Cookie 可能不可用',
|
||
sandboxUnlock: '临时解锁(不安全)',
|
||
sandboxRestore: '恢复沙箱',
|
||
settingsHtmlDefaultUnsafeTitle: 'HTML 预览默认以非沙箱模式打开(不安全)',
|
||
settingsHtmlDefaultUnsafeDesc: '开启后,每次打开 HTML 文件时预览默认处于非沙箱状态(与界面同源,可读取会话文件与内部接口);可在状态行临时恢复沙箱',
|
||
settingsHtmlSandboxTitle: '关闭 HTML 预览沙箱(不安全)',
|
||
settingsHtmlSandboxDesc: '关闭后,预览的 HTML 将与界面同源运行,可读取会话文件、本地存储并调用内部接口。仅对完全可信的文件开启',
|
||
settingsBrowserSandboxTitle: '关闭浏览器沙箱(不安全)',
|
||
settingsBrowserSandboxDesc: '关闭后,访问的任何网站都将与界面同源运行,可读取会话数据并冒充你的登录状态。仅对完全可信的站点开启',
|
||
settingsBrowserLinksTitle: '聊天区外链在侧边栏打开',
|
||
settingsBrowserLinksDesc: '开启后,点击聊天或界面中的 http/https 外链时在侧边栏浏览器中打开,不再弹出新窗口;Ctrl/Cmd 点击可临时放行',
|
||
browserOpenExternal: '在浏览器中打开',
|
||
browserEmbedBlocked: '{host} 拒绝了嵌入请求',
|
||
browserEmbedBlockedDesc: '该站点通过 X-Frame-Options / frame-ancestors 禁止在其它页面中显示,无法在侧边栏内加载。可在浏览器中直接打开',
|
||
browserEmbedAnyway: '仍然加载',
|
||
subagent: '任务管理',
|
||
openSubagent: '任务管理',
|
||
subagentMainAgent: '主代理',
|
||
subagentEmpty: '暂无子代理',
|
||
subagentEmptyDesc: '当前主代理派生的子代理将显示在这里',
|
||
subagentRunning: '运行中',
|
||
subagentInactive: '空闲',
|
||
subagentModeOneShot: '一次性',
|
||
subagentModeContinuable: '可续接',
|
||
subagentCount: '{count} 个子代理',
|
||
subagentCountRunning: '{count} 个子代理 · {running} 运行中',
|
||
subagentDiagCorrupt: '目录损坏',
|
||
subagentDiagUnsupported: '不支持的条目',
|
||
subagentDiagUnavailable: '不可用',
|
||
subagentThinking: '思考中…',
|
||
jobs: '后台任务',
|
||
jobsCount: '{count} 个后台任务',
|
||
jobsCountRunning: '{count} 个后台任务 · {running} 运行中',
|
||
jobStatusRunning: '运行中',
|
||
jobStatusStopping: '终止中',
|
||
jobStatusCompleted: '已完成',
|
||
jobStatusKilled: '已终止',
|
||
jobStatusFailed: '失败',
|
||
jobDurationSeconds: '{seconds} 秒',
|
||
jobDurationMinutes: '{minutes} 分 {seconds} 秒',
|
||
jobDurationHours: '{hours} 小时 {minutes} 分',
|
||
jobViewOutput: '查看输出',
|
||
jobHideOutput: '收起输出',
|
||
jobNoOutput: '暂无输出',
|
||
jobNotReadYet: '等待模型读取该任务的输出(模型执行 job_output 后,输出会显示在这里)',
|
||
jobOutputTruncated: '输出过长,已截断显示',
|
||
jobOutputError: '输出读取失败',
|
||
jobKill: '终止',
|
||
jobKillConfirm: '再次点击确认终止',
|
||
jobKillError: '终止失败',
|
||
addPluginsTabCard: '添加 Tab 插件',
|
||
addPluginsTabCardDesc: '注册新的侧边栏页面',
|
||
addPluginsViewerCard: '添加预览插件',
|
||
addPluginsViewerCardDesc: '注册新的文件类型预览',
|
||
addPluginsTabDesc: '侧边栏页面(Tab)可以由插件扩展。插件通过 ctx.betterSidebar 服务注册;点击「安装」复制安装命令,粘贴到 DSH 所在环境的终端执行。',
|
||
addPluginsViewerDesc: '文件预览器可以由插件扩展。插件通过 ctx.betterSidebar 服务注册;点击「安装」复制安装命令,粘贴到 DSH 所在环境的终端执行。',
|
||
addPluginsBrowseMore: '在 GitHub 上浏览更多插件(topic: dsh-better-sidebar)',
|
||
addPluginsRecommended: '推荐插件',
|
||
addPluginsEmpty: '暂未收录插件,欢迎在 GitHub topic 下发布你的插件',
|
||
openPlugin: '跳转',
|
||
copyInstall: '复制安装命令',
|
||
pluginOfficeDesc: '为 better-sidebar 编辑器提供 Office 三件套预览(.docx / .xlsx / .pptx),把重型 Office 渲染库拆出主包、按需安装',
|
||
}
|
||
|
||
/** The en dictionary (key-set-equal to zh, enforced by the type annotation). */
|
||
export const en: Record<keyof typeof zh, string> = {
|
||
explorer: 'Explorer',
|
||
git: 'Source Control',
|
||
terminal: 'Terminal',
|
||
editor: 'Editor',
|
||
newTab: 'New tab',
|
||
openExplorer: 'Explorer',
|
||
openGit: 'Git panel',
|
||
newTerminal: 'New terminal',
|
||
terminalLimit: 'Terminal limit reached (3)',
|
||
close: 'Close',
|
||
collapse: 'Collapse sidebar',
|
||
expand: 'Expand sidebar',
|
||
collapseBottomPanel: 'Collapse bottom panel',
|
||
expandBottomPanel: 'Expand bottom panel',
|
||
terminalError: 'Terminal connection failed',
|
||
terminalConnectFailed: 'Terminal failed to connect repeatedly',
|
||
terminalRetry: 'Retry',
|
||
preview: 'Preview',
|
||
edit: 'Edit',
|
||
refresh: 'Refresh',
|
||
save: 'Save',
|
||
saved: 'Saved',
|
||
unsaved: 'Unsaved',
|
||
saveFailed: 'Save failed',
|
||
truncation: 'File too large — showing the first 512KB',
|
||
binary: 'Binary file, preview unavailable',
|
||
loading: 'Loading…',
|
||
error: 'Failed to load',
|
||
retry: 'Retry',
|
||
splitLeft: 'Split left',
|
||
splitRight: 'Split right',
|
||
splitUp: 'Split up',
|
||
splitDown: 'Split down',
|
||
notRepo: 'This directory is not a git repository',
|
||
noChanges: 'No changes',
|
||
stage: 'Stage',
|
||
unstage: 'Unstage',
|
||
stageAll: 'Stage all',
|
||
unstageAll: 'Unstage all',
|
||
commitPlaceholder: 'Commit message (Ctrl+Enter)',
|
||
commit: 'Commit',
|
||
commitError: 'Commit failed',
|
||
branch: 'Branch',
|
||
checkoutError: 'Branch switch failed',
|
||
history: 'History',
|
||
changes: 'Changes',
|
||
staged: 'Staged',
|
||
unstaged: 'Unstaged',
|
||
cancel: 'Cancel',
|
||
diffEmpty: 'No text changes',
|
||
diffLoadError: 'Failed to load diff',
|
||
diffBinary: 'Binary',
|
||
diffAdded: 'Added',
|
||
diffDeleted: 'Deleted',
|
||
diffRenamed: 'Renamed',
|
||
diffExpand: 'Expand {count} more rows',
|
||
diffCollapse: 'Collapse',
|
||
discard: 'Discard changes',
|
||
discardTitle: 'Discard changes',
|
||
discardDesc: 'This discards the worktree changes of "{path}" (not recoverable).',
|
||
viewCommitDiff: 'View commit diff',
|
||
copyShortHash: 'Copy short hash',
|
||
copyFullHash: 'Copy full hash',
|
||
copySubject: 'Copy subject',
|
||
revertCommit: 'Revert commit',
|
||
revertTitle: 'Revert commit',
|
||
revertDesc: 'Create a new commit on the current branch that reverts "{subject}".',
|
||
cherryPickCommit: 'Cherry-pick commit',
|
||
cherryPickTitle: 'Cherry-pick commit',
|
||
cherryPickDesc: 'Apply the changes of "{subject}" to the current branch.',
|
||
timeJustNow: 'just now',
|
||
timeMinutesAgo: '{n} min ago',
|
||
timeHoursAgo: '{n} h ago',
|
||
timeYesterday: 'yesterday',
|
||
loadMore: 'Load more',
|
||
historyLoadError: 'Failed to load more history',
|
||
produced: 'Produced',
|
||
producedOpen: 'Open in sidebar',
|
||
disconnected: 'Terminal disconnected, reconnecting…',
|
||
exited: 'Terminal process exited',
|
||
noSession: 'Select a conversation to use the sidebar',
|
||
pluginNotLoaded: 'Plugin not loaded; tab unavailable:',
|
||
hiddenFiles: 'Hidden files',
|
||
parent: 'Parent directory',
|
||
copied: 'Copied',
|
||
copy: 'Copy',
|
||
newFile: 'New file',
|
||
openEditor: 'Open editor',
|
||
gitDetail: 'View change details',
|
||
referenceFile: '@file',
|
||
addToConversation: 'Add to conversation',
|
||
copyRelative: 'Copy relative path',
|
||
copyAbsolute: 'Copy absolute path',
|
||
download: 'Download',
|
||
settingsNav: 'Side card',
|
||
settingsIntro: 'Manage what the side card shows and how it behaves',
|
||
settingsPopupDesc: 'Configure related options for {feature}',
|
||
settingsDone: 'Done',
|
||
settingsOpenTitle: 'Open by default for new conversations',
|
||
settingsOpenDesc: 'Expand the side card automatically for brand-new conversations; existing conversations keep their own layouts',
|
||
settingsWidthTitle: 'Default width share',
|
||
settingsWidthDesc: 'The side card\'s default share of the window width for new conversations (20–60)',
|
||
settingsWidthSuffix: '%',
|
||
settingsOpenPathTitle: 'Open chat files in the sidebar',
|
||
settingsOpenPathDesc: 'Open file links in the chat (tool rows, produced files, mentions) in the sidebar editor instead of the system default app',
|
||
settingsSaveFailed: 'Failed to save',
|
||
settingsConflict: 'The setting changed in another window — please retry',
|
||
binaryNoPreview: 'This file type cannot be previewed',
|
||
downloadToView: 'Download to view',
|
||
settingsSubagentTitle: 'Auto-open the Tasks page when a subagent appears',
|
||
settingsSubagentDesc: 'Expand the side card and open the Tasks page when the current conversation spawns a new subagent; turn off to open it manually',
|
||
settingsJobsTitle: 'Auto-open the Jobs page on a new background job',
|
||
settingsJobsDesc: 'Expand the side card and open the Jobs page whenever a new background job appears for the current conversation (every new job triggers); turn off to open it manually',
|
||
settingsToolsTitle: 'Inject terminal tools for the model',
|
||
settingsToolsDesc: 'When enabled, the model can create and drive sidebar terminals through the 8 terminal_* tools (off by default)',
|
||
settingsBottomTerminalTitle: 'Auto-open a terminal on the bottom panel\'s first expansion',
|
||
settingsBottomTerminalDesc: 'When the bottom panel is expanded for the first time in a session, try to open a fresh terminal tab there (the terminal quota still applies; on by default)',
|
||
settingsFontFamilyTitle: 'Terminal font family',
|
||
settingsFontFamilyDesc: 'Custom terminal font family (a CSS font-family stack like "JetBrains Mono", monospace; leave empty to follow the theme\'s monospace font)',
|
||
settingsFontFamilyPlaceholder: '"JetBrains Mono", monospace',
|
||
settingsFontSizeTitle: 'Terminal font size',
|
||
settingsFontSizeDesc: 'Terminal font size in px (9–32, default 13)',
|
||
settingsFontSizeSuffix: 'px',
|
||
settingsTabsTitle: 'Sidebar content',
|
||
settingsViewersTitle: 'File viewers',
|
||
settingsGeneralTitle: 'General',
|
||
settingsPopup: 'Feature settings',
|
||
settingsViewerCatchAll: 'Catch-all: any file',
|
||
viewerImage: 'Image',
|
||
viewerPdf: 'PDF',
|
||
viewerMarkdown: 'Markdown',
|
||
viewerCode: 'Code',
|
||
viewerBinary: 'Binary download',
|
||
viewerHtml: 'HTML',
|
||
browser: 'Browser',
|
||
browserPlaceholder: 'Enter a URL, e.g. example.com',
|
||
browserGo: 'Go',
|
||
browserBack: 'Back',
|
||
browserForward: 'Forward',
|
||
browserStart: 'Enter a URL to start browsing (sandbox mode)',
|
||
browserBlockedScheme: 'Blocked: only http/https URLs are allowed',
|
||
browserBlockedLoopback: 'Blocked: local and internal addresses cannot be browsed here',
|
||
browserInvalid: 'Invalid URL',
|
||
browserNoSandboxWarning: 'Sandbox off: the current page runs with full GUI privileges (re-enable in settings)',
|
||
htmlNoSandboxWarning: 'Sandbox off: this HTML runs with full GUI privileges (re-enable in settings)',
|
||
sandboxStatusOn: 'Sandbox mode: on · pages cannot access the GUI\'s data or local files; logins and third-party cookies may not work',
|
||
sandboxUnlock: 'Temporarily disable (unsafe)',
|
||
sandboxRestore: 'Restore sandbox',
|
||
settingsHtmlDefaultUnsafeTitle: 'Open HTML previews unsandboxed by default (unsafe)',
|
||
settingsHtmlDefaultUnsafeDesc: 'When on, every newly opened HTML preview starts in the unsandboxed state (same origin as the GUI — it can read session files and internal APIs); the status row still offers a one-tap restore',
|
||
settingsHtmlSandboxTitle: 'Disable HTML preview sandbox (unsafe)',
|
||
settingsHtmlSandboxDesc: 'With the sandbox off, previewed HTML runs with the same origin as the GUI: it can read session files, local storage and call internal APIs. Only enable for fully trusted files',
|
||
settingsBrowserSandboxTitle: 'Disable browser sandbox (unsafe)',
|
||
settingsBrowserSandboxDesc: 'With the sandbox off, any visited site runs with the same origin as the GUI: it can read session data and act as your logged-in session. Only enable for fully trusted sites',
|
||
settingsBrowserLinksTitle: 'Open chat external links in the sidebar',
|
||
settingsBrowserLinksDesc: 'When on, clicking an http/https external link in the chat or GUI opens the sidebar browser instead of a new window; Ctrl/Cmd+click always bypasses',
|
||
browserOpenExternal: 'Open in browser',
|
||
browserEmbedBlocked: '{host} refused to be embedded',
|
||
browserEmbedBlockedDesc: 'The site forbids being displayed inside other pages (X-Frame-Options / frame-ancestors), so it cannot load in the sidebar. Open it directly in your browser instead.',
|
||
browserEmbedAnyway: 'Load anyway',
|
||
subagent: 'Tasks',
|
||
openSubagent: 'Tasks',
|
||
subagentMainAgent: 'Main agent',
|
||
subagentEmpty: 'No subagents',
|
||
subagentEmptyDesc: 'Subagents spawned under the main agent will appear here',
|
||
subagentRunning: 'Running',
|
||
subagentInactive: 'Inactive',
|
||
subagentModeOneShot: 'One-shot',
|
||
subagentModeContinuable: 'Continuable',
|
||
subagentCount: '{count} subagents',
|
||
subagentCountRunning: '{count} subagents · {running} running',
|
||
subagentDiagCorrupt: 'Corrupt',
|
||
subagentDiagUnsupported: 'Unsupported',
|
||
subagentDiagUnavailable: 'Unavailable',
|
||
subagentThinking: 'Thinking…',
|
||
jobs: 'Background jobs',
|
||
jobsCount: '{count} background jobs',
|
||
jobsCountRunning: '{count} background jobs · {running} running',
|
||
jobStatusRunning: 'Running',
|
||
jobStatusStopping: 'Stopping',
|
||
jobStatusCompleted: 'Completed',
|
||
jobStatusKilled: 'Killed',
|
||
jobStatusFailed: 'Failed',
|
||
jobDurationSeconds: '{seconds}s',
|
||
jobDurationMinutes: '{minutes}m {seconds}s',
|
||
jobDurationHours: '{hours}h {minutes}m',
|
||
jobViewOutput: 'View output',
|
||
jobHideOutput: 'Hide output',
|
||
jobNoOutput: 'No output yet',
|
||
jobNotReadYet: 'Waiting for the model to read this job; its output appears here once the model runs job_output',
|
||
jobOutputTruncated: 'Output truncated',
|
||
jobOutputError: 'Failed to read output',
|
||
jobKill: 'Kill',
|
||
jobKillConfirm: 'Click again to confirm kill',
|
||
jobKillError: 'Kill failed',
|
||
addPluginsTabCard: 'Add tab plugins',
|
||
addPluginsTabCardDesc: 'Register a new sidebar page',
|
||
addPluginsViewerCard: 'Add preview plugins',
|
||
addPluginsViewerCardDesc: 'Register a file-type preview',
|
||
addPluginsTabDesc: 'Sidebar pages (tabs) can be extended by plugins. Plugins register through the ctx.betterSidebar service; clicking Install copies the install command — paste it into a terminal where your DSH profile lives and run it.',
|
||
addPluginsViewerDesc: 'File previewers can be extended by plugins. Plugins register through the ctx.betterSidebar service; clicking Install copies the install command — paste it into a terminal where your DSH profile lives and run it.',
|
||
addPluginsBrowseMore: 'Browse more plugins on GitHub (topic: dsh-better-sidebar)',
|
||
addPluginsRecommended: 'Recommended plugins',
|
||
addPluginsEmpty: 'No plugins curated yet — publish yours under the GitHub topic',
|
||
openPlugin: 'Open',
|
||
copyInstall: 'Copy install command',
|
||
pluginOfficeDesc: 'Office-suite preview (.docx / .xlsx / .pptx) for the better-sidebar editor, keeping the heavy Office render libraries out of the core bundle',
|
||
}
|
||
|
||
/**
|
||
* The dictionary namespace this plugin owns in the DSH locale registry
|
||
* (`'sidebar'` is taken by DSH's own ui-sidebar, hence this distinct name).
|
||
*/
|
||
export const LOCALE_NS = 'betterSidebar'
|
||
|
||
/** The DSH locale service attached by the client apply (absent → browser detection). */
|
||
let localeService: { getSnapshot(): { active: string } } | undefined
|
||
|
||
/**
|
||
* Attach (or detach, with undefined) the DSH locale service. The sidebar
|
||
* mounts its own React root outside the slot system's locale seat, so the
|
||
* service rides this module-level holder: components keep calling the plain
|
||
* `t()` function, and the Sidebar root's locale subscription re-renders the
|
||
* whole tree on switches.
|
||
*/
|
||
export function attachLocale(service: { getSnapshot(): { active: string } } | undefined): void {
|
||
localeService = service
|
||
}
|
||
|
||
/**
|
||
* The active locale id ('zh' | 'en'): the DSH locale service's snapshot when
|
||
* attached, else the browser language.
|
||
*/
|
||
function activeLocale(): string {
|
||
return localeService?.getSnapshot().active
|
||
?? (typeof navigator !== 'undefined' ? navigator.language : '')
|
||
?? 'en'
|
||
}
|
||
|
||
/** Translate a copy key in the active locale (zh → zh, else en). */
|
||
export type CopyKey = keyof typeof zh
|
||
|
||
/** Translate a copy key; `{name}` placeholders interpolate from `params`. */
|
||
export function t(key: CopyKey, params?: Record<string, string | number>): string {
|
||
const dict = activeLocale().toLowerCase().startsWith('zh') ? zh : en
|
||
let text = dict[key]
|
||
if (params !== undefined) {
|
||
for (const [name, value] of Object.entries(params)) {
|
||
text = text.replaceAll(`{${name}}`, String(value))
|
||
}
|
||
}
|
||
return text
|
||
}
|
||
|
||
/** Whether the active locale is Chinese (used for selectors). */
|
||
export function isZh(): boolean {
|
||
return activeLocale().toLowerCase().startsWith('zh')
|
||
}
|
||
|
||
/** Format an ISO 8601 author date relative to now (刚刚 / N 分钟前 / N 小时前 / 昨天 / date). */
|
||
export function relativeTime(iso: string): string {
|
||
const then = Date.parse(iso)
|
||
if (Number.isNaN(then)) return iso
|
||
const seconds = Math.floor((Date.now() - then) / 1000)
|
||
if (seconds < 60) return t('timeJustNow')
|
||
if (seconds < 3600) return t('timeMinutesAgo', { n: Math.floor(seconds / 60) })
|
||
if (seconds < 86400) return t('timeHoursAgo', { n: Math.floor(seconds / 3600) })
|
||
if (seconds < 172800) return t('timeYesterday')
|
||
const date = new Date(then)
|
||
const pad = (value: number): string => String(value).padStart(2, '0')
|
||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
|
||
}
|