dsh-ai-courses 0.1.2 → 0.1.4

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.
Files changed (2) hide show
  1. package/lib/client.js +77 -15
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -680,6 +680,27 @@ const AIC_COURSES_CORE = (function () {
680
680
  return DEFAULT_LOCALE
681
681
  }
682
682
 
683
+ /**
684
+ * 动态 `t(key, vars)`:**每次调用现读**宿主语言。
685
+ *
686
+ * 为什么不能在 apply 时 `createT(detectLocale(...))` 一次定死:
687
+ * DSH 的 locale 插件在启动后才把 `<html lang>` 从服务端默认的 `en` 改成用户设置
688
+ * (profile patch 里 `locale.preference: zh` 的情况)。apply 时读到的还是 `en`,
689
+ * 于是整个面板一直显示英文。这里按 locale 缓存翻译器,语言变了下一次渲染就切过来。
690
+ */
691
+ function createLiveT (doc, nav) {
692
+ const cache = new Map()
693
+ return function t (key, vars) {
694
+ const locale = detectLocale(doc, nav)
695
+ let tr = cache.get(locale)
696
+ if (!tr) {
697
+ tr = createT(locale)
698
+ cache.set(locale, tr)
699
+ }
700
+ return tr(key, vars)
701
+ }
702
+ }
703
+
683
704
  /* --------------------------------------------------- 编号(绝不解析 id) */
684
705
  /** 章节的人类编号:由 `order` 派生(05 §4 / 06 §4.2)。 */
685
706
  function chapterNo (ch) {
@@ -1172,6 +1193,7 @@ const AIC_COURSES_CORE = (function () {
1172
1193
  LOCALES,
1173
1194
  DEFAULT_LOCALE,
1174
1195
  createT,
1196
+ createLiveT,
1175
1197
  detectLocale,
1176
1198
  genSpec,
1177
1199
  learnSpec,
@@ -4458,9 +4480,28 @@ function PageState (props) {
4458
4480
  )
4459
4481
  }
4460
4482
 
4483
+ /**
4484
+ * `<html lang>` 变化时强制重渲染。
4485
+ *
4486
+ * DSH 的 locale 插件在启动后才把服务端默认的 `en` 改成用户设置;`t` 本身是
4487
+ * `createLiveT`(每次调用现读),但组件需要一次 re-render 才会重新取词。
4488
+ */
4489
+ function useLocaleRefresh () {
4490
+ const [, setTick] = React.useState(0)
4491
+ React.useEffect(() => {
4492
+ if (typeof document === 'undefined' || typeof MutationObserver !== 'function') return undefined
4493
+ const el = document.documentElement
4494
+ if (!el || typeof el.getAttribute !== 'function') return undefined
4495
+ const obs = new MutationObserver(() => setTick((n) => n + 1))
4496
+ obs.observe(el, { attributes: true, attributeFilter: ['lang'] })
4497
+ return () => obs.disconnect()
4498
+ }, [])
4499
+ }
4500
+
4461
4501
  function AicCoursesApp (props) {
4462
4502
  const t = props.t
4463
4503
  const core = AIC_COURSES_CORE
4504
+ useLocaleRefresh()
4464
4505
  const [view, setView] = React.useState('library')
4465
4506
  const [courseId, setCourseId] = React.useState(null)
4466
4507
  const [kpId, setKpId] = React.useState(null)
@@ -4697,7 +4738,9 @@ function aicCoursesApply (ctx) {
4697
4738
 
4698
4739
  const doc = typeof document === 'undefined' ? null : document
4699
4740
  const nav = typeof navigator === 'undefined' ? null : navigator
4700
- const t = AIC_COURSES_CORE.createT(AIC_COURSES_CORE.detectLocale(doc, nav))
4741
+ /* 动态语言:`<html lang>` 由 DSH locale 插件在启动后改成用户设置,
4742
+ 不能在这里一次性 detectLocale 后锁死(否则中文设置下一直显示英文)。 */
4743
+ const t = AIC_COURSES_CORE.createLiveT(doc, nav)
4701
4744
 
4702
4745
  /* 样式:构建期内联(宿主不会自动加载包内 lib/styles.css —— 见 doc/03 §6 的定案),
4703
4746
  挂一个 <style> 并在停用/卸载时移除;不用 innerHTML。 */
@@ -4715,19 +4758,38 @@ function aicCoursesApply (ctx) {
4715
4758
 
4716
4759
  const disposers = []
4717
4760
 
4718
- /* 侧边栏面板图标。`id` 必须与 `main` 的 `key` 是同一字符串(宿主按 list id 寻址 main 面板,E-8);
4719
- `label` 必须是函数。 */
4720
- disposers.push(
4721
- slots.register(
4722
- { name: 'sidebar.panellist', id: 'ai-courses', order: 30, label: () => t('panel.title') },
4723
- (props) => h(CoursePanelIcon, props),
4724
- ),
4725
- )
4726
-
4727
- /* 主面板。key = 'ai-courses'(**不能**用 'course':那是 dsh-course-plugin 的格位) */
4728
- disposers.push(
4729
- slots.register({ name: 'main', key: 'ai-courses' }, () => h(AicCoursesApp, { t: t, ctx: ctx })),
4730
- )
4761
+ /* 侧边栏面板图标 / 主面板:**等槽声明后再注册**。
4762
+ `sidebar.panellist` 由 `@deepseek-ai/dsh-client-ui-sidebar` 声明;在 `dsh web` 里这个声明
4763
+ 发生在 sidebar 首次渲染时,比插件 apply 晚 —— 直接 `slots.register` 会抛
4764
+ 「slot "sidebar.panellist" is not declared」,整个 client entry 被标 failed(真机踩到)。
4765
+ 用 `slots.inject` 等声明;老宿主没有 inject 时退回直接注册。 */
4766
+ if (slots && typeof slots.inject === 'function') {
4767
+ /* `id` 必须与 `main` 的 `key` 是同一字符串(宿主按 list id 寻址 main 面板,E-8);
4768
+ `label` 必须是函数。 */
4769
+ disposers.push(
4770
+ slots.inject('sidebar.panellist', () => slots.register(
4771
+ { name: 'sidebar.panellist', id: 'ai-courses', order: 30, label: () => t('panel.title') },
4772
+ (props) => h(CoursePanelIcon, props),
4773
+ )),
4774
+ )
4775
+ /* 主面板。key = 'ai-courses'(**不能**用 'course':那是 dsh-course-plugin 的格位) */
4776
+ disposers.push(
4777
+ slots.inject('main', () => slots.register(
4778
+ { name: 'main', key: 'ai-courses' },
4779
+ () => h(AicCoursesApp, { t: t, ctx: ctx }),
4780
+ )),
4781
+ )
4782
+ } else {
4783
+ disposers.push(
4784
+ slots.register(
4785
+ { name: 'sidebar.panellist', id: 'ai-courses', order: 30, label: () => t('panel.title') },
4786
+ (props) => h(CoursePanelIcon, props),
4787
+ ),
4788
+ )
4789
+ disposers.push(
4790
+ slots.register({ name: 'main', key: 'ai-courses' }, () => h(AicCoursesApp, { t: t, ctx: ctx })),
4791
+ )
4792
+ }
4731
4793
 
4732
4794
  /* 主会话里的 `/ai-course` 实时状态卡片(conversation.chat.commandview,见 55-status-card.js):
4733
4795
  宿主 ui-chat 声明该槽,用 inject 等声明出现再注册(直接 register 会抛「未声明」)。 */
@@ -4748,7 +4810,7 @@ function aicCoursesApply (ctx) {
4748
4810
  if (styleEl && styleEl.parentNode) styleEl.parentNode.removeChild(styleEl)
4749
4811
  })
4750
4812
 
4751
- console.log('[ai-courses] 客户端已就绪(sidebar.panellist#ai-courses + main[ai-courses])')
4813
+ console.log('[ai-courses] 客户端已就绪(sidebar/main 等槽声明后注册;主会话槽已注册)')
4752
4814
  }
4753
4815
 
4754
4816
  /* ==================== src/client/99-shell-tail.js ==================== */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ai-courses",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "repository": {