fix: md 预览代码块复制按钮跟随 DSH i18n(MarkdownText 补传 codeLabels)
This commit is contained in:
@@ -407,7 +407,7 @@ interface BetterSidebarService {
|
||||
| **host 半无此服务** | `ctx.betterSidebar` 只在 client 侧存在;host 半需要 better-sidebar 数据走 `/sidebar/api/*` HTTP 路由 |
|
||||
| **portal 限制** | 整面板 slot 由 ui-layout 独占,外部 tab 只能进入 better-sidebar 的 portal 内部,无法全屏替换 |
|
||||
| **id 冲突** | `registerTab` / `registerFileViewer` 对重复 id 抛错;建议用包前缀(`my-plugin:xxx`) |
|
||||
| **i18n 跟随** | 侧边栏界面文案跟随 DSH 的 `ctx.locale`(`@deepseek-ai/dsh-client-locale`):词典注册在 `betterSidebar` 命名空间,语言偏好(Host-backed `locale.preference`)与浏览器语言不一致时以 DSH 为准并实时切换;locale 服务缺失时回退浏览器语言。插件自身的 `t()`(`src/client/locales.ts`)由 `apply()` 挂接服务;消费插件**不要**依赖此内部函数——标题等字段传字符串或 `() => string` 即可(i18n 友好) |
|
||||
| **i18n 跟随** | 侧边栏界面文案跟随 DSH 的 `ctx.locale`(`@deepseek-ai/dsh-client-locale`):词典注册在 `betterSidebar` 命名空间,语言偏好(Host-backed `locale.preference`)与浏览器语言不一致时以 DSH 为准并实时切换;locale 服务缺失时回退浏览器语言。插件自身的 `t()`(`src/client/locales.ts`)由 `apply()` 挂接服务;消费插件**不要**依赖此内部函数——标题等字段传字符串或 `() => string` 即可(i18n 友好)。⚠️ 渲染 DSH 的 `MarkdownText` 时必须传 `codeLabels={{ copyLabel: t('copy'), copiedLabel: t('copied') }}`——该组件 cordis-free,漏传则代码块复制按钮回退硬编码中文 |
|
||||
| **懒加载 chunk** | 内置重依赖(Univer/xterm/CodeMirror/pptx/docx)在独立 bundle(`lib/client-<name>.js`)中,经 `/sidebar/bundle` 路由按需下发;每个脚本把 factory 赋到插件自有全局注册表 `globalThis.__dshChunks__[<name>]`,由 `src/client/chunk-loader.ts` 用自定义 require(externals 经 `__DSH_MODULES__` seed 分支解析)物化——**不经过** `__ModuleLoader__` 注册;**核心 bundle 禁止静态 import `src/client/chunks/*`**(会把库拖回启动路径);对消费插件透明——懒加载只作用于内置 descriptor,`component` 契约(`(props) => ReactNode` 纯渲染函数)不变 |
|
||||
|
||||
---
|
||||
|
||||
@@ -50,6 +50,11 @@ DSH 的 i18n 系统(`@deepseek-ai/dsh-client-locale`):
|
||||
|
||||
- 无。计划与实施一致。
|
||||
- 版本号随 v0.8.2 发版提交(`package.json` + `dsh.plugin.json`)。
|
||||
- v0.8.2 后补遗:md 预览(`TextEditor.tsx`)的代码块复制按钮文案——DSH 的
|
||||
`MarkdownText`/`CodeBlock` 是 cordis-free 组件,未传 `codeLabels` 时回退到硬编码中文
|
||||
「复制/复制成功」。修复为渲染时传入 `codeLabels={{ copyLabel: t('copy'), copiedLabel: t('copied') }}`
|
||||
(对齐 DSH 聊天区 `AssistantMarkdown` 的做法),新增 `tests/markdown-copy-labels.spec.tsx`
|
||||
钉住 zh/en 跟随。
|
||||
|
||||
## 验收
|
||||
|
||||
|
||||
@@ -329,7 +329,15 @@ export function TextEditor(props: FileViewerProps) {
|
||||
onMouseUp={handlePreviewMouseUp}
|
||||
onScroll={hidePopup}
|
||||
>
|
||||
<MarkdownText text={draft ?? content ?? ''} />
|
||||
{/* The fence copy-button labels must come from this plugin's own
|
||||
dictionary: the DSH MarkdownText/CodeBlock are cordis-free and
|
||||
fall back to hardcoded Chinese otherwise (same pattern as the
|
||||
chat's AssistantMarkdown). Render-time t() keeps them following
|
||||
the active locale on live switches. */}
|
||||
<MarkdownText
|
||||
text={draft ?? content ?? ''}
|
||||
codeLabels={{ copyLabel: t('copy'), copiedLabel: t('copied') }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{html && mode === 'preview' && (
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Markdown preview copy-label spec: the fence copy buttons ("复制" / "Copy")
|
||||
* must follow the DSH locale service through `codeLabels` — the DSH
|
||||
* MarkdownText/CodeBlock are cordis-free and fall back to HARDCODED Chinese
|
||||
* when the caller omits the labels, so TextEditor must pass its own
|
||||
* dictionary copy (`t('copy')` / `t('copied')`), re-evaluated per render.
|
||||
* Rendered with renderToString (the initial preview mode never mounts
|
||||
* CodeMirror, so no DOM/effects are needed).
|
||||
*/
|
||||
import { describe, expect, it, afterEach } from 'vitest'
|
||||
import { renderToString } from 'react-dom/server'
|
||||
import { createElement } from 'react'
|
||||
import './browser-globals.ts'
|
||||
import { TextEditor } from '../src/client/TextEditor.tsx'
|
||||
import { createSidebarStore } from '../src/client/state.ts'
|
||||
import { attachLocale } from '../src/client/locales.ts'
|
||||
import type { FileViewerProps } from '../src/client/service.ts'
|
||||
|
||||
/** Minimal structural fake of the DSH LocaleService face the sidebar uses. */
|
||||
class FakeLocale {
|
||||
active: string = 'zh'
|
||||
getSnapshot(): { active: string } {
|
||||
return { active: this.active }
|
||||
}
|
||||
subscribe(_fn: () => void): () => void {
|
||||
return () => {}
|
||||
}
|
||||
register(_ns: string, _locale: string, _dict: Record<string, string>): () => void {
|
||||
return () => {}
|
||||
}
|
||||
}
|
||||
|
||||
const CTX = {} as Parameters<typeof TextEditor>[0]['ctx']
|
||||
|
||||
/** A markdown source with one fenced code block (the copy-button surface). */
|
||||
const MD_WITH_FENCE = '```ts\nconst a = 1\n```'
|
||||
|
||||
function viewerProps(overrides: Partial<FileViewerProps> = {}): FileViewerProps {
|
||||
return {
|
||||
ctx: CTX,
|
||||
store: createSidebarStore(),
|
||||
scope: { sessionId: 's1', cwd: '/p' },
|
||||
path: '/p/a/README.md',
|
||||
title: 'README.md',
|
||||
viewerId: 'markdown',
|
||||
content: MD_WITH_FENCE,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
attachLocale(undefined)
|
||||
})
|
||||
|
||||
describe('markdown preview code-block copy labels (DSH i18n following)', () => {
|
||||
it('renders the fence copy button with the zh dictionary label by default', () => {
|
||||
const locale = new FakeLocale()
|
||||
locale.active = 'zh'
|
||||
attachLocale(locale)
|
||||
const html = renderToString(createElement(TextEditor, viewerProps()))
|
||||
expect(html).toContain('复制')
|
||||
expect(html).not.toContain('Copy')
|
||||
})
|
||||
|
||||
it('follows the attached locale service live: en renders "Copy" instead of the zh label', () => {
|
||||
const locale = new FakeLocale()
|
||||
locale.active = 'en'
|
||||
attachLocale(locale)
|
||||
const html = renderToString(createElement(TextEditor, viewerProps()))
|
||||
expect(html).toContain('Copy')
|
||||
expect(html).not.toContain('复制')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user