dsh-ask-from-text 0.1.19 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to `dsh-ask-from-text` are documented here. The format is
4
4
  based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
5
5
  project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.1.20] - 2026-11-13
8
+
9
+ ### Added
10
+
11
+ - Runtime UI localization for Chinese and English. The active DSH language is
12
+ read through `ctx.locale`; other languages fall back to English. Native
13
+ conversation and Plugin Manager metadata continue to follow DSH.
14
+
7
15
  ## [0.1.19] - 2026-11-13
8
16
 
9
17
  ### Fixed
@@ -55,6 +63,14 @@ project follows [Semantic Versioning](https://semver.org/).
55
63
 
56
64
  - README screenshots now use absolute CDN URLs so they render on the npm package page.
57
65
 
66
+ ## [0.1.20] - 2026-11-13
67
+
68
+ ### Added
69
+
70
+ - Runtime UI localization for Chinese and English. The active DSH language is
71
+ read through `ctx.locale`; other languages fall back to English. Native
72
+ conversation and Plugin Manager metadata continue to follow DSH.
73
+
58
74
  ## [0.1.19] - 2026-11-13
59
75
 
60
76
  ### Fixed
@@ -194,6 +210,7 @@ project follows [Semantic Versioning](https://semver.org/).
194
210
  [0.1.17]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.17
195
211
  [0.1.18]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.18
196
212
  [0.1.19]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.19
213
+ [0.1.20]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.20
197
214
  [0.1.13]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.13
198
215
  [0.1.14]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.14
199
216
  [0.1.15]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.15
@@ -201,3 +218,4 @@ project follows [Semantic Versioning](https://semver.org/).
201
218
  [0.1.17]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.17
202
219
  [0.1.18]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.18
203
220
  [0.1.19]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.19
221
+ [0.1.20]: https://github.com/gbn0219/dsh-ask-from-text/releases/tag/v0.1.20
package/README.md CHANGED
@@ -103,7 +103,7 @@ dsh plugin --profile web add /absolute/path/to/dsh-ask-from-text
103
103
  - 追问回答是真实的 DSH Session,可以使用思考、工具调用、Stop 和原生输入框。
104
104
  - 分支只看到父级和祖先内容,不看到兄弟分支,也不会自动获得父级后来新增的内容。
105
105
  - 分支对话会出现在普通 Session 列表中,这是正常现象。
106
- - 当前浮窗和舞台的运行时按钮文案为中文;插件管理器的标题/描述提供中英文。
106
+ - 界面文案跟随 DSH 语言:中文/英文;其他语言自动回退英文。原生对话区域继续跟随 DSH 自身的语言。
107
107
  - 目前主要面向 DSH Desktop;Web 共用同一套 UI,窄屏体验尚未专门优化。
108
108
 
109
109
  ## 常见问题
@@ -225,7 +225,7 @@ These sessions are grouped under a `追问分支` header. Click the header to co
225
225
  - A follow-up is a real DSH Session. It can use reasoning, tool calls, Stop, and the native composer.
226
226
  - A branch sees its parent and ancestors, not siblings, and does not automatically receive later turns added to its parent.
227
227
  - Branch conversations also appear in the normal Session list. This is expected.
228
- - Runtime UI labels are currently Chinese; Plugin Manager metadata is available in Chinese and English.
228
+ - Runtime UI follows the DSH language: Chinese and English are bundled; other languages fall back to English. The native conversation follows DSH.
229
229
  - DSH Desktop is the primary target. The Web client shares the same UI, but narrow-screen behavior has not been specifically polished.
230
230
 
231
231
  ### FAQ
package/client.js CHANGED
@@ -78,6 +78,13 @@ window.__ModuleLoader__.load({
78
78
  stages: '个节点',
79
79
  collapseDock: '收起列表',
80
80
  expandDock: '展开列表',
81
+ sidebarGroup: '追问分支',
82
+ context: '上下文',
83
+ contextStart: '上下文起点',
84
+ waiting: '待提问',
85
+ treeNote: '当前模型上下文:{context}。兄弟分支不在其中。',
86
+ anchorMissing: '的原文当前不可见,可能已被折叠或尚未加载',
87
+ draftWait: '在下方输入浮窗中提问',
81
88
  },
82
89
  en: {
83
90
  chip: 'Ask',
@@ -126,6 +133,13 @@ window.__ModuleLoader__.load({
126
133
  stages: 'nodes',
127
134
  collapseDock: 'Collapse list',
128
135
  expandDock: 'Expand list',
136
+ sidebarGroup: 'Branches',
137
+ context: 'Context',
138
+ contextStart: 'Context root',
139
+ waiting: 'Waiting for question',
140
+ treeNote: 'Current model context: {context}. Sibling branches are not included.',
141
+ anchorMissing: 'source text is not visible; it may be folded or not loaded yet',
142
+ draftWait: 'Ask in the input window below',
129
143
  },
130
144
  }
131
145
 
@@ -342,21 +356,21 @@ window.__ModuleLoader__.load({
342
356
  }
343
357
 
344
358
  function pathLabel(state, branch) {
345
- return `${DICT.zh.main}${pathOf(state, branch).map(item => ` › #${item.number}`).join('')}`
359
+ return `${app.t('main')}${pathOf(state, branch).map(item => ` › #${item.number}`).join('')}`
346
360
  }
347
361
 
348
362
  function branchBasis(state, branch) {
349
- if (!branch.parentBranchId) return `${DICT.zh.basedOn} ${DICT.zh.main}`
363
+ if (!branch.parentBranchId) return `${app.t('basedOn')} ${app.t('main')}`
350
364
  const parent = state.branches[branch.parentBranchId]
351
- return parent ? `${DICT.zh.basedOn} #${parent.number}` : `${DICT.zh.basedOn} ${DICT.zh.main}`
365
+ return parent ? `${app.t('basedOn')} #${parent.number}` : `${app.t('basedOn')} ${app.t('main')}`
352
366
  }
353
367
 
354
368
  function statusLabel(branch) {
355
- if (branch.status === 'sending') return DICT.zh.sending
356
- if (branch.status === 'running') return DICT.zh.running
357
- if (branch.status === 'error') return DICT.zh.failed
358
- if (branch.prompted) return DICT.zh.ready
359
- return DICT.zh.draft
369
+ if (branch.status === 'sending') return app.t('sending')
370
+ if (branch.status === 'running') return app.t('running')
371
+ if (branch.status === 'error') return app.t('failed')
372
+ if (branch.prompted) return app.t('ready')
373
+ return app.t('draft')
360
374
  }
361
375
 
362
376
  function colorVars(color) {
@@ -422,7 +436,15 @@ window.__ModuleLoader__.load({
422
436
  refs,
423
437
  chats,
424
438
  get main() { return main },
425
- t(key) { return (ctx.locale && ctx.locale.bind(NS)(key)) || DICT.zh[key] || key },
439
+ t(key, params) {
440
+ if (ctx.locale) {
441
+ const value = ctx.locale.bind(NS)(key, params)
442
+ if (value && value !== key) return value
443
+ }
444
+ const template = DICT.en[key] ?? key
445
+ if (!params) return template
446
+ return template.replace(/\{(\w+)\}/g, (match, name) => name in params ? String(params[name]) : match)
447
+ },
426
448
  subscribe(listener) {
427
449
  listeners.add(listener)
428
450
  return () => { listeners.delete(listener) }
@@ -902,7 +924,7 @@ window.__ModuleLoader__.load({
902
924
  const quote = ranges.map(part => part.quote).join('\n').trim()
903
925
  if (!quote) { clearChip(); return }
904
926
  if (quote.length > MAX_QUOTE) {
905
- notify(DICT.zh.tooLong)
927
+ notify(app.t('tooLong'))
906
928
  clearChip()
907
929
  return
908
930
  }
@@ -1102,7 +1124,7 @@ window.__ModuleLoader__.load({
1102
1124
 
1103
1125
  function serialize(quote, question) {
1104
1126
  const body = String(quote || '').trim().split(/\r?\n/).map(line => `> ${line}`).join('\n')
1105
- const text = String(question || '').trim() || DICT.zh.defaultQuestion
1127
+ const text = String(question || '').trim() || app.t('defaultQuestion')
1106
1128
  return `${body}\n\n${text}`
1107
1129
  }
1108
1130
 
@@ -1160,7 +1182,7 @@ window.__ModuleLoader__.load({
1160
1182
  await ensureReference(branch)
1161
1183
  }
1162
1184
  const reference = refs.get(branch.id)
1163
- if (!reference) throw new Error(DICT.zh.noSession)
1185
+ if (!reference) throw new Error(app.t('noSession'))
1164
1186
  const text = serialize(branch.anchor.quote, branch.question)
1165
1187
  const echo = reference.binding.session.beginSubmission({
1166
1188
  mode: 'queue',
@@ -1175,7 +1197,7 @@ window.__ModuleLoader__.load({
1175
1197
  )
1176
1198
  if (!result.ok) {
1177
1199
  branch.status = 'error'
1178
- branch.error = result.error?.message || DICT.zh.error
1200
+ branch.error = result.error?.message || app.t('error')
1179
1201
  emit()
1180
1202
  return false
1181
1203
  }
@@ -1204,7 +1226,7 @@ window.__ModuleLoader__.load({
1204
1226
  try {
1205
1227
  await ensureReference(branch)
1206
1228
  const reference = refs.get(branch.id)
1207
- if (!reference) throw new Error(DICT.zh.noSession)
1229
+ if (!reference) throw new Error(app.t('noSession'))
1208
1230
  const echo = reference.binding.session.beginSubmission({
1209
1231
  mode: 'queue',
1210
1232
  text,
@@ -1218,7 +1240,7 @@ window.__ModuleLoader__.load({
1218
1240
  )
1219
1241
  if (!result.ok) {
1220
1242
  branch.status = 'error'
1221
- branch.error = result.error?.message || DICT.zh.error
1243
+ branch.error = result.error?.message || app.t('error')
1222
1244
  emit()
1223
1245
  return false
1224
1246
  }
@@ -1310,7 +1332,7 @@ window.__ModuleLoader__.load({
1310
1332
  if (other !== header) other.remove()
1311
1333
  }
1312
1334
  if (header.nextSibling !== first) firstParent.insertBefore(header, first)
1313
- const label = `${state.sidebarCollapsed ? '▸' : '▾'} 追问分支 · ${rows.length}`
1335
+ const label = `${state.sidebarCollapsed ? '▸' : '▾'} ${app.t('sidebarGroup')} · ${rows.length}`
1314
1336
  if (header.textContent !== label) header.textContent = label
1315
1337
  for (const row of rows) row.classList.toggle('ifu-sidebar-hidden', state.sidebarCollapsed)
1316
1338
 
@@ -1424,7 +1446,7 @@ window.__ModuleLoader__.load({
1424
1446
  // giving up on this attempt.
1425
1447
  if (navigator && attempt === 0) navigator.click()
1426
1448
  if (attempt < 14) setTimeout(() => scrollBranchIntoView(branch, attempt + 1), 100)
1427
- else notify(`#${branch.number} 的原文当前不可见,可能已被折叠或尚未加载`)
1449
+ else notify(`#${branch.number} ${app.t('anchorMissing')}`)
1428
1450
  return
1429
1451
  }
1430
1452
  if (navigator && attempt === 0) navigator.click()
@@ -1852,14 +1874,14 @@ window.__ModuleLoader__.load({
1852
1874
  }
1853
1875
  return h('div', { className: 'ifu-compose' },
1854
1876
  h('div', { className: 'ifu-quote', title: branch.anchor.quote },
1855
- h('div', { className: 'ifu-quote-label' }, `${DICT.zh.quote} · ${DICT.zh.basedOn} ${branch.parentBranchId ? `#${app.state.branches[branch.parentBranchId]?.number ?? ''}` : DICT.zh.main}`),
1877
+ h('div', { className: 'ifu-quote-label' }, `${app.t('quote')} · ${app.t('basedOn')} ${branch.parentBranchId ? `#${app.state.branches[branch.parentBranchId]?.number ?? ''}` : app.t('main')}`),
1856
1878
  h('div', { className: 'ifu-quote-text' }, branch.anchor.quote.length > 220 ? `${branch.anchor.quote.slice(0, 219)}…` : branch.anchor.quote),
1857
1879
  ),
1858
1880
  h('textarea', {
1859
1881
  ref: areaRef,
1860
1882
  className: 'ifu-textarea',
1861
1883
  value: text,
1862
- placeholder: DICT.zh.empty,
1884
+ placeholder: app.t('empty'),
1863
1885
  rows: 3,
1864
1886
  onChange: event => { setText(event.target.value); app.setDraftText(branch.id, event.target.value) },
1865
1887
  onKeyDown: event => {
@@ -1871,14 +1893,14 @@ window.__ModuleLoader__.load({
1871
1893
  }),
1872
1894
  branch.error ? h('div', { className: 'ifu-error', role: 'alert' }, branch.error) : null,
1873
1895
  h('div', { className: 'ifu-compose-foot' },
1874
- h('span', { className: 'ifu-hint' }, DICT.zh.sendHint),
1896
+ h('span', { className: 'ifu-hint' }, app.t('sendHint')),
1875
1897
  h('div', { className: 'ifu-grow' }),
1876
1898
  h('button', {
1877
1899
  type: 'button',
1878
1900
  className: 'ifu-send',
1879
1901
  disabled: sending,
1880
1902
  onClick: submit,
1881
- }, sending ? DICT.zh.sendingAction : (branch.error ? DICT.zh.retry : DICT.zh.send)),
1903
+ }, sending ? app.t('sendingAction') : (branch.error ? app.t('retry') : app.t('send'))),
1882
1904
  ),
1883
1905
  )
1884
1906
  }
@@ -1887,7 +1909,7 @@ window.__ModuleLoader__.load({
1887
1909
  const { branch, SessionProvider, renderSlot } = props
1888
1910
  const reference = app.refs.get(branch.id)
1889
1911
  if (!reference) {
1890
- return h('div', { className: 'ifu-loading' }, branch.error || DICT.zh.sending)
1912
+ return h('div', { className: 'ifu-loading' }, branch.error || app.t('sending'))
1891
1913
  }
1892
1914
  return h(SessionProvider, { session: reference },
1893
1915
  h('div', {
@@ -1935,18 +1957,18 @@ window.__ModuleLoader__.load({
1935
1957
  ...colorVars(branch.color),
1936
1958
  },
1937
1959
  role: 'dialog',
1938
- 'aria-label': `${DICT.zh.branchN} #${branch.number} ${DICT.zh.input}`,
1960
+ 'aria-label': `${app.t('branchN')} #${branch.number} ${app.t('input')}`,
1939
1961
  },
1940
1962
  h('header', { className: 'ifu-head', onPointerDown: event => app.startPanelDrag(branch.id, event, 'input') },
1941
1963
  h('span', { className: 'ifu-dot' }),
1942
1964
  h('div', { className: 'ifu-title-wrap' },
1943
- h('div', { className: 'ifu-title' }, `${DICT.zh.input} · #${branch.number}`),
1965
+ h('div', { className: 'ifu-title' }, `${app.t('input')} · #${branch.number}`),
1944
1966
  h('div', { className: 'ifu-sub' }, statusLabel(branch)),
1945
1967
  ),
1946
1968
  h('div', { className: 'ifu-grow' }),
1947
- h(IconButton, { title: DICT.zh.collapse, onClick: () => app.collapse(branch.id) }, '—'),
1969
+ h(IconButton, { title: app.t('collapse'), onClick: () => app.collapse(branch.id) }, '—'),
1948
1970
  branch.sessionId ? null : h(IconButton, {
1949
- title: DICT.zh.deleteDraft,
1971
+ title: app.t('deleteDraft'),
1950
1972
  className: 'ifu-danger',
1951
1973
  onClick: () => app.deleteDraft(branch.id),
1952
1974
  }, '⌫'),
@@ -1956,7 +1978,7 @@ window.__ModuleLoader__.load({
1956
1978
  ref: areaRef,
1957
1979
  className: 'ifu-textarea',
1958
1980
  value: text,
1959
- placeholder: DICT.zh.empty,
1981
+ placeholder: app.t('empty'),
1960
1982
  rows: 3,
1961
1983
  onChange: event => { setText(event.target.value); app.setInputText(branch.id, event.target.value) },
1962
1984
  onKeyDown: event => {
@@ -1968,14 +1990,14 @@ window.__ModuleLoader__.load({
1968
1990
  }),
1969
1991
  branch.error ? h('div', { className: 'ifu-error', role: 'alert' }, branch.error) : null,
1970
1992
  h('div', { className: 'ifu-compose-foot' },
1971
- h('span', { className: 'ifu-hint' }, DICT.zh.sendHint),
1993
+ h('span', { className: 'ifu-hint' }, app.t('sendHint')),
1972
1994
  h('div', { className: 'ifu-grow' }),
1973
1995
  h('button', {
1974
1996
  type: 'button',
1975
1997
  className: 'ifu-send',
1976
1998
  disabled: sending,
1977
1999
  onClick: submit,
1978
- }, sending ? DICT.zh.sendingAction : DICT.zh.send),
2000
+ }, sending ? app.t('sendingAction') : app.t('send')),
1979
2001
  ),
1980
2002
  ),
1981
2003
  ...['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'].map(direction => h('div', {
@@ -1991,7 +2013,7 @@ window.__ModuleLoader__.load({
1991
2013
  useVersion()
1992
2014
  if (!branch.prompted) return h(InputWindow, { key: `${branch.id}-input`, branch })
1993
2015
  const parent = branch.parentBranchId ? app.state.branches[branch.parentBranchId] : null
1994
- const basis = parent ? `${DICT.zh.basedOn} #${parent.number}` : `${DICT.zh.basedOn} ${DICT.zh.main}`
2016
+ const basis = parent ? `${app.t('basedOn')} #${parent.number}` : `${app.t('basedOn')} ${app.t('main')}`
1995
2017
  const reference = app.refs.get(branch.id)
1996
2018
  const showThread = branch.prompted && reference && !branch.loading
1997
2019
  const showDraft = !branch.prompted
@@ -2012,20 +2034,20 @@ window.__ModuleLoader__.load({
2012
2034
  ...colorVars(branch.color),
2013
2035
  },
2014
2036
  role: 'dialog',
2015
- 'aria-label': `${DICT.zh.branchN} #${branch.number}`,
2037
+ 'aria-label': `${app.t('branchN')} #${branch.number}`,
2016
2038
  },
2017
2039
  h('header', { className: 'ifu-head', onPointerDown: event => app.startPanelDrag(branch.id, event) },
2018
2040
  h('span', { className: 'ifu-dot' }),
2019
2041
  h('div', { className: 'ifu-title-wrap' },
2020
- h('div', { className: 'ifu-title' }, `${DICT.zh.branchN} #${branch.number}`),
2042
+ h('div', { className: 'ifu-title' }, `${app.t('branchN')} #${branch.number}`),
2021
2043
  h('div', { className: 'ifu-sub' }, `${basis} · ${statusLabel(branch)}`),
2022
2044
  ),
2023
2045
  h('div', { className: 'ifu-grow' }),
2024
- h(IconButton, { title: DICT.zh.expand, onClick: () => app.promote(branch.id), disabled: !branch.prompted || !branch.sessionId }, '↗'),
2025
- h(IconButton, { title: DICT.zh.collapse, onClick: () => app.collapse(branch.id) }, '—'),
2046
+ h(IconButton, { title: app.t('expand'), onClick: () => app.promote(branch.id), disabled: !branch.prompted || !branch.sessionId }, '↗'),
2047
+ h(IconButton, { title: app.t('collapse'), onClick: () => app.collapse(branch.id) }, '—'),
2026
2048
  branch.sessionId || branch.prompted
2027
- ? h(IconButton, { title: DICT.zh.close, onClick: () => app.collapse(branch.id) }, '✕')
2028
- : h(IconButton, { title: DICT.zh.deleteDraft, className: 'ifu-danger', onClick: () => app.deleteDraft(branch.id) }, '⌫'),
2049
+ ? h(IconButton, { title: app.t('close'), onClick: () => app.collapse(branch.id) }, '✕')
2050
+ : h(IconButton, { title: app.t('deleteDraft'), className: 'ifu-danger', onClick: () => app.deleteDraft(branch.id) }, '⌫'),
2029
2051
  ),
2030
2052
  h('div', { className: 'ifu-panel-body' },
2031
2053
  showThread
@@ -2033,12 +2055,12 @@ window.__ModuleLoader__.load({
2033
2055
  : showDraft
2034
2056
  ? h('div', { className: 'ifu-compose-center' },
2035
2057
  h('div', { className: 'ifu-draft-quote' },
2036
- h('div', { className: 'ifu-quote-label' }, `${DICT.zh.quote} · ${basis}`),
2058
+ h('div', { className: 'ifu-quote-label' }, `${app.t('quote')} · ${basis}`),
2037
2059
  h('div', { className: 'ifu-quote-text' }, branch.anchor.quote),
2038
- h('div', { className: 'ifu-draft-wait' }, '在下方输入浮窗中提问'),
2060
+ h('div', { className: 'ifu-draft-wait' }, app.t('draftWait')),
2039
2061
  ),
2040
2062
  )
2041
- : h('div', { className: 'ifu-loading' }, branch.error || DICT.zh.sending),
2063
+ : h('div', { className: 'ifu-loading' }, branch.error || app.t('sending')),
2042
2064
  ),
2043
2065
  ...['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'].map(direction => h('div', {
2044
2066
  key: direction,
@@ -2053,11 +2075,11 @@ window.__ModuleLoader__.load({
2053
2075
  function StageQuote(props) {
2054
2076
  const { branch } = props
2055
2077
  const parent = branch.parentBranchId ? app.state.branches[branch.parentBranchId] : null
2056
- const basis = parent ? `#${parent.number}` : DICT.zh.main
2078
+ const basis = parent ? `#${parent.number}` : app.t('main')
2057
2079
  return h('div', { className: 'ifu-stage-quote', title: branch.anchor.quote },
2058
2080
  h('div', { className: 'ifu-stage-quote-label' },
2059
2081
  h('span', { className: 'ifu-dot' }),
2060
- `${DICT.zh.quote} · ${DICT.zh.basedOn} ${basis}`,
2082
+ `${app.t('quote')} · ${app.t('basedOn')} ${basis}`,
2061
2083
  ),
2062
2084
  h('div', { className: 'ifu-stage-quote-text' }, branch.anchor.quote),
2063
2085
  )
@@ -2079,7 +2101,7 @@ window.__ModuleLoader__.load({
2079
2101
  observer.observe(host, { childList: true, subtree: true, characterData: true })
2080
2102
  return () => observer.disconnect()
2081
2103
  }, [branch.id])
2082
- if (!reference) return h('div', { className: 'ifu-loading' }, branch.error || DICT.zh.sending)
2104
+ if (!reference) return h('div', { className: 'ifu-loading' }, branch.error || app.t('sending'))
2083
2105
  return h(SessionProvider, { session: reference },
2084
2106
  h('div', {
2085
2107
  ref: hostRef,
@@ -2106,24 +2128,24 @@ window.__ModuleLoader__.load({
2106
2128
  ? rows.filter(row => pathIds.has(row.branch.id))
2107
2129
  : rows
2108
2130
  const contextText = path.length > 0
2109
- ? `${DICT.zh.main}${path.map(branch => ` → #${branch.number}`).join('')}`
2110
- : DICT.zh.main
2131
+ ? `${app.t('main')}${path.map(branch => ` → #${branch.number}`).join('')}`
2132
+ : app.t('main')
2111
2133
  return h('aside', { className: 'ifu-tree', 'data-ifu-no-select': '' },
2112
2134
  h('div', { className: 'ifu-tree-head' },
2113
- h('span', null, DICT.zh.tree),
2135
+ h('span', null, app.t('tree')),
2114
2136
  h('div', { className: 'ifu-tree-tabs' },
2115
2137
  h('button', {
2116
2138
  type: 'button',
2117
2139
  className: `ifu-tree-tab ${scope === 'path' ? 'is-active' : ''}`,
2118
2140
  onClick: () => setScope('path'),
2119
- }, '路径'),
2141
+ }, app.t('path')),
2120
2142
  h('button', {
2121
2143
  type: 'button',
2122
2144
  className: `ifu-tree-tab ${scope === 'all' ? 'is-active' : ''}`,
2123
2145
  onClick: () => setScope('all'),
2124
- }, '全部'),
2146
+ }, app.t('all')),
2125
2147
  ),
2126
- h('button', { type: 'button', className: 'ifu-icon', title: DICT.zh.close, onClick: () => app.toggleTree() }, '✕'),
2148
+ h('button', { type: 'button', className: 'ifu-icon', title: app.t('close'), onClick: () => app.toggleTree() }, '✕'),
2127
2149
  ),
2128
2150
  h('div', { className: 'ifu-tree-list' },
2129
2151
  h('div', {
@@ -2138,15 +2160,15 @@ window.__ModuleLoader__.load({
2138
2160
  },
2139
2161
  },
2140
2162
  h('span', { className: 'ifu-dot' }),
2141
- h('span', { className: 'ifu-tree-root-name' }, DICT.zh.main),
2163
+ h('span', { className: 'ifu-tree-root-name' }, app.t('main')),
2142
2164
  h('span', { className: 'ifu-tree-root-tag' }, 'ROOT'),
2143
- h('span', { className: 'ifu-tree-root-meta' }, '上下文起点'),
2165
+ h('span', { className: 'ifu-tree-root-meta' }, app.t('contextStart')),
2144
2166
  ),
2145
2167
  visibleRows.map(({ branch, depth }) => {
2146
2168
  const isDraft = !branch.sessionId && !branch.prompted
2147
2169
  const parent = branch.parentBranchId ? state.branches[branch.parentBranchId] : null
2148
- const parentLabel = parent ? `${DICT.zh.main} · #${parent.number}` : DICT.zh.main
2149
- const title = branch.question || branch.anchor.quote || '待提问'
2170
+ const parentLabel = parent ? `${app.t('main')} · #${parent.number}` : app.t('main')
2171
+ const title = branch.question || branch.anchor.quote || app.t('waiting')
2150
2172
  const inPath = pathIds.has(branch.id)
2151
2173
  return h('div', {
2152
2174
  key: branch.id,
@@ -2172,34 +2194,34 @@ window.__ModuleLoader__.load({
2172
2194
  } else if (branch.prompted && branch.sessionId) app.promote(branch.id)
2173
2195
  else app.openPanel(branch.id)
2174
2196
  },
2175
- title: `${DICT.zh.switchBranch} #${branch.number}`,
2197
+ title: `${app.t('switchBranch')} #${branch.number}`,
2176
2198
  },
2177
2199
  h('div', { className: 'ifu-tree-top' },
2178
2200
  h('span', { className: 'ifu-dot' }),
2179
2201
  h('span', { className: 'ifu-tree-num' }, `#${branch.number}`),
2180
2202
  h('span', { className: 'ifu-tree-title' }, snippet(title, 36)),
2181
2203
  branch.id === activeId
2182
- ? h('span', { className: 'ifu-tree-tag' }, DICT.zh.current)
2204
+ ? h('span', { className: 'ifu-tree-tag' }, app.t('current'))
2183
2205
  : inPath
2184
- ? h('span', { className: 'ifu-tree-tag is-context' }, '上下文')
2206
+ ? h('span', { className: 'ifu-tree-tag is-context' }, app.t('context'))
2185
2207
  : null,
2186
2208
  isDraft ? h('button', {
2187
2209
  type: 'button',
2188
2210
  className: 'ifu-tree-delete',
2189
- title: DICT.zh.deleteDraft,
2211
+ title: app.t('deleteDraft'),
2190
2212
  onClick: (event) => { event.stopPropagation(); app.deleteDraft(branch.id) },
2191
2213
  }, '⌫') : null,
2192
2214
  ),
2193
2215
  h('div', { className: 'ifu-tree-quote' }, snippet(branch.anchor.quote, 72)),
2194
2216
  h('div', { className: 'ifu-tree-meta' },
2195
- h('span', null, `${DICT.zh.basedOn} ${parentLabel}`),
2217
+ h('span', null, `${app.t('basedOn')} ${parentLabel}`),
2196
2218
  h('span', null, statusLabel(branch)),
2197
2219
  ),
2198
2220
  )
2199
2221
  }),
2200
2222
  ),
2201
2223
  h('div', { className: 'ifu-tree-note' },
2202
- `当前模型上下文:${contextText}。兄弟分支不在其中。`,
2224
+ app.t('treeNote', { context: contextText }),
2203
2225
  ),
2204
2226
  )
2205
2227
  }
@@ -2223,23 +2245,23 @@ window.__ModuleLoader__.load({
2223
2245
  type: 'button',
2224
2246
  className: 'ifu-stage-back',
2225
2247
  onClick: () => index <= 0 ? app.stageClose() : app.stageGo(-1),
2226
- title: index <= 0 ? DICT.zh.main : DICT.zh.back,
2227
- }, `← ${DICT.zh.back}`),
2248
+ title: index <= 0 ? app.t('main') : app.t('back'),
2249
+ }, `← ${app.t('back')}`),
2228
2250
  h('span', { className: 'ifu-stage-count' }, `${index + 1}/${total}`),
2229
2251
  h('button', {
2230
2252
  type: 'button',
2231
2253
  className: 'ifu-stage-back',
2232
2254
  disabled: index >= total - 1,
2233
2255
  onClick: () => app.stageGo(1),
2234
- }, DICT.zh.forward, ' →'),
2256
+ }, app.t('forward'), ' →'),
2235
2257
  h('span', { className: 'ifu-stage-divider' }),
2236
2258
  h('div', { className: 'ifu-crumbs' },
2237
2259
  h('button', {
2238
2260
  type: 'button',
2239
2261
  className: 'ifu-crumb-btn',
2240
2262
  onClick: () => app.stageClose(),
2241
- title: DICT.zh.main,
2242
- }, DICT.zh.main),
2263
+ title: app.t('main'),
2264
+ }, app.t('main')),
2243
2265
  ...path.flatMap(item => [
2244
2266
  h('span', { key: `sep-${item.id}`, className: 'ifu-crumb-sep' }, '›'),
2245
2267
  h('button', {
@@ -2250,23 +2272,23 @@ window.__ModuleLoader__.load({
2250
2272
  if (item.id === branch.id) return
2251
2273
  app.promote(item.id)
2252
2274
  },
2253
- title: `${DICT.zh.switchBranch} #${item.number}`,
2275
+ title: `${app.t('switchBranch')} #${item.number}`,
2254
2276
  }, `#${item.number}`),
2255
2277
  ]),
2256
2278
  ),
2257
- h('span', { className: 'ifu-context-chip' }, `上下文 · ${path.length} ${DICT.zh.stages}`),
2279
+ h('span', { className: 'ifu-context-chip' }, `${app.t('context')} · ${path.length} ${app.t('stages')}`),
2258
2280
  h('div', { className: 'ifu-grow' }),
2259
2281
  h('span', { className: 'ifu-stage-status' }, statusLabel(branch)),
2260
- h('button', { type: 'button', className: 'ifu-stage-action', onClick: () => app.toggleTree() }, '▤ ', DICT.zh.tree),
2261
- h('button', { type: 'button', className: 'ifu-stage-action', onClick: () => app.stageRestore() }, '↘ ', DICT.zh.restore),
2262
- h('button', { type: 'button', className: 'ifu-stage-action', onClick: () => app.stageClose() }, '✕ ', DICT.zh.close),
2282
+ h('button', { type: 'button', className: 'ifu-stage-action', onClick: () => app.toggleTree() }, '▤ ', app.t('tree')),
2283
+ h('button', { type: 'button', className: 'ifu-stage-action', onClick: () => app.stageRestore() }, '↘ ', app.t('restore')),
2284
+ h('button', { type: 'button', className: 'ifu-stage-action', onClick: () => app.stageClose() }, '✕ ', app.t('close')),
2263
2285
  ),
2264
- h('div', { className: 'ifu-stage-tip' }, DICT.zh.stageHint),
2286
+ h('div', { className: 'ifu-stage-tip' }, app.t('stageHint')),
2265
2287
  h('div', { className: 'ifu-stage-body' },
2266
2288
  h(StageQuote, { branch }),
2267
2289
  reference && !branch.loading
2268
2290
  ? h(StageThread, { key: branch.id, branch, SessionProvider, renderSlot })
2269
- : h('div', { className: 'ifu-loading' }, branch.error || DICT.zh.sending),
2291
+ : h('div', { className: 'ifu-loading' }, branch.error || app.t('sending')),
2270
2292
  ),
2271
2293
  h(BranchTree),
2272
2294
  )
@@ -2403,7 +2425,7 @@ window.__ModuleLoader__.load({
2403
2425
  className: 'ifu-dock-collapsed',
2404
2426
  'data-ifu-dock': '',
2405
2427
  onClick: () => app.toggleDock(),
2406
- }, `◉ ${DICT.zh.dock} · ${branches.length}`)
2428
+ }, `◉ ${app.t('dock')} · ${branches.length}`)
2407
2429
  }
2408
2430
  const dockStyle = state.dock
2409
2431
  ? {
@@ -2417,11 +2439,11 @@ window.__ModuleLoader__.load({
2417
2439
  : {}
2418
2440
  return h('aside', { className: 'ifu-dock', 'data-ifu-dock': '', style: dockStyle },
2419
2441
  h('div', { className: 'ifu-dock-head', onPointerDown: event => app.startDockDrag(event) },
2420
- h('span', null, `${DICT.zh.dock} · ${branches.length}`),
2442
+ h('span', null, `${app.t('dock')} · ${branches.length}`),
2421
2443
  h('button', {
2422
2444
  type: 'button',
2423
2445
  className: 'ifu-dock-toggle',
2424
- title: DICT.zh.collapseDock,
2446
+ title: app.t('collapseDock'),
2425
2447
  onClick: () => app.closeDock(),
2426
2448
  }, '—'),
2427
2449
  ),
@@ -2440,7 +2462,7 @@ window.__ModuleLoader__.load({
2440
2462
  event.preventDefault()
2441
2463
  app.revealBranch(branch.id)
2442
2464
  },
2443
- title: `${DICT.zh.switchBranch}: #${branch.number}`,
2465
+ title: `${app.t('switchBranch')}: #${branch.number}`,
2444
2466
  },
2445
2467
  h('span', { className: 'ifu-dot', style: colorVars(branch.color) }),
2446
2468
  h('span', { className: 'ifu-dock-num' }, `#${branch.number}`),
@@ -2449,7 +2471,7 @@ window.__ModuleLoader__.load({
2449
2471
  isDraft ? h('button', {
2450
2472
  type: 'button',
2451
2473
  className: 'ifu-dock-delete',
2452
- title: DICT.zh.deleteDraft,
2474
+ title: app.t('deleteDraft'),
2453
2475
  onClick: (event) => { event.stopPropagation(); app.deleteDraft(branch.id) },
2454
2476
  }, '⌫') : null,
2455
2477
  )
@@ -2471,14 +2493,14 @@ window.__ModuleLoader__.load({
2471
2493
  className: 'ifu-chip',
2472
2494
  'data-ifu-chip': '',
2473
2495
  style: { left: `${chip.x}px`, top: `${chip.y}px` },
2474
- title: DICT.zh.chipHint,
2496
+ title: app.t('chipHint'),
2475
2497
  onMouseDown: event => event.preventDefault(),
2476
2498
  onClick: () => {
2477
2499
  const x = clamp(chip.x - 120, 12, window.innerWidth - PANEL_WIDTH - 12)
2478
2500
  const y = clamp(chip.y + 38, 12, window.innerHeight - 320)
2479
2501
  app.openDraft(chip.anchor, x, y)
2480
2502
  },
2481
- }, '✦ ', DICT.zh.chip)
2503
+ }, '✦ ', app.t('chip'))
2482
2504
  }
2483
2505
 
2484
2506
  function Notice() {
@@ -2707,6 +2729,7 @@ window.__ModuleLoader__.load({
2707
2729
  app = createApp(ctx)
2708
2730
  app.load()
2709
2731
  ctx.effect(() => ctx.locale.register(NS, { zh: DICT.zh, en: DICT.en }), 'inline-followup: locale')
2732
+ ctx.effect(() => ctx.on('locale/change', () => app.emit()), 'inline-followup: locale-change')
2710
2733
  ctx.effect(() => app.install(), 'inline-followup: lifetime')
2711
2734
  ctx.slots.inject('shell.overlay', () => ctx.slots.register({
2712
2735
  name: 'shell.overlay',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ask-from-text",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "type": "module",
5
5
  "description": "Ask a follow-up from selected text in a DeepSeek Harness answer, in a real Session branch with a tree of context.",
6
6
  "exports": {