@dickpy/dsh-imagegen 1.5.2 → 1.5.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.
@@ -1,33 +1,71 @@
1
- /**
2
- * Shared panel helpers: the active-dictionary pick (document-language based,
3
- * dsh-ssh precedent) bound to the dsh-imagegen interpolator, plus a small
4
- * error-message extractor. All copy stays in the locale dictionaries.
5
- */
6
-
7
- import { en, zh, type ImageGenKey } from './locales.ts'
8
-
9
- /** Template values accepted by the interpolator. */
10
- export type TranslateValues = Record<string, string | number>
11
-
12
- /** Active dictionary, picked by the document language at call time. */
13
- export function dictionary(): Record<string, string> {
14
- const lang = typeof document !== 'undefined' ? document.documentElement.lang : 'zh'
15
- return lang.toLowerCase().startsWith('en') ? { ...en } : { ...zh }
16
- }
17
-
18
- /** Translate a key with optional {name} template params (current language). */
19
- export function tt(key: ImageGenKey, values?: TranslateValues): string {
20
- const text = dictionary()[key] ?? key
21
- if (values === undefined) return text
22
- let rendered = text
23
- for (const [name, value] of Object.entries(values)) {
24
- rendered = rendered.replaceAll(`{${name}}`, String(value))
25
- }
26
- return rendered
27
- }
28
-
29
- /** Human-readable error text from an unknown thrown value. */
30
- export function errorMessage(error: unknown): string {
31
- if (error instanceof Error) return error.message
32
- return String(error)
33
- }
1
+ /**
2
+ * Shared panel helpers: the active-dictionary pick bound to the dsh-imagegen
3
+ * interpolator, the plugin locale that follows the DSH interface language
4
+ * (bridged in client/index.ts from ctx.locale the plugin ships zh / en / ru
5
+ * and registers itself as a DSH language pack for Русский), plus a small
6
+ * error-message extractor. All copy stays in the locale dictionaries.
7
+ */
8
+
9
+ import { en, ru, zh, type ImageGenKey } from './locales.ts'
10
+
11
+ /** Template values accepted by the interpolator. */
12
+ export type TranslateValues = Record<string, string | number>
13
+
14
+ /** Languages with a shipped dictionary. */
15
+ export type ImageGenLanguage = 'zh' | 'en' | 'ru'
16
+
17
+ const DICTIONARIES: Record<ImageGenLanguage, Record<string, string>> = { zh, en, ru }
18
+
19
+ /** The active DSH locale mapped onto our dictionary (module-level, one value per app). */
20
+ let activeLocale: ImageGenLanguage = 'zh'
21
+
22
+ /** Bumped on every locale change; useSyncExternalStore version. */
23
+ let languageVersion = 0
24
+
25
+ const languageListeners = new Set<() => void>()
26
+
27
+ /**
28
+ * Adopt the DSH interface language. Unknown ids (future language packs)
29
+ * resolve to English the same per-key fallback convention the host locale
30
+ * chain uses.
31
+ */
32
+ export function applyHostLocale(id: unknown): void {
33
+ const next: ImageGenLanguage = id === 'zh' || id === 'ru' ? id : 'en'
34
+ if (next === activeLocale) return
35
+ activeLocale = next
36
+ languageVersion += 1
37
+ for (const listener of [...languageListeners]) listener()
38
+ }
39
+
40
+ /** Monotonic version of the active locale (external-store snapshot). */
41
+ export function getImageGenLanguageVersion(): number {
42
+ return languageVersion
43
+ }
44
+
45
+ /** Observe locale changes; returns the unsubscriber. */
46
+ export function subscribeImageGenLanguage(listener: () => void): () => void {
47
+ languageListeners.add(listener)
48
+ return () => { languageListeners.delete(listener) }
49
+ }
50
+
51
+ /** Active dictionary for the current DSH language. */
52
+ export function dictionary(): Record<string, string> {
53
+ return DICTIONARIES[activeLocale]
54
+ }
55
+
56
+ /** Translate a key with optional {name} template params (current language). */
57
+ export function tt(key: ImageGenKey, values?: TranslateValues): string {
58
+ const text = dictionary()[key] ?? key
59
+ if (values === undefined) return text
60
+ let rendered = text
61
+ for (const [name, value] of Object.entries(values)) {
62
+ rendered = rendered.replaceAll(`{${name}}`, String(value))
63
+ }
64
+ return rendered
65
+ }
66
+
67
+ /** Human-readable error text from an unknown thrown value. */
68
+ export function errorMessage(error: unknown): string {
69
+ if (error instanceof Error) return error.message
70
+ return String(error)
71
+ }
@@ -11,18 +11,18 @@
11
11
  * whole boot when a plugin apply throws, and an external plugin must not take
12
12
  * the GUI down.
13
13
  */
14
- import type { Context as ClientContext } from '@deepseek-ai/cordis'
15
- import type { ISessions } from '@deepseek-ai/dsh-api-session-controller/client'
14
+ import type { Context as ClientContext } from '@deepseek-ai/cordis'
15
+ import type { ISessions } from '@deepseek-ai/dsh-api-session-controller/client'
16
16
  import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'
17
17
  // Type-only: pulls the locale plugin's Context merge (ctx.locale).
18
- import type {} from '@deepseek-ai/dsh-client-locale/client'
19
- import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
20
- // Type-only: pulls the LocaleNamespaceMap merge table.
18
+ import type {} from '@deepseek-ai/dsh-client-locale/client'
19
+ import type {} from '@deepseek-ai/dsh-client-ui-renderer/client'
20
+ // Type-only: pulls the LocaleNamespaceMap merge table.
21
21
  import type {} from '@deepseek-ai/dsh-client-ui-slots'
22
22
  import { ImageGenApi } from './api.ts'
23
23
  import { ImageGenController } from './controller.ts'
24
- import { tt } from './helpers.ts'
25
- import { en, zh, type ImageGenKey } from './locales.ts'
24
+ import { tt, applyHostLocale } from './helpers.ts'
25
+ import { en, ru, zh, type ImageGenKey } from './locales.ts'
26
26
  import { mountPanel } from './mount.tsx'
27
27
  import { mountSidebarEntry } from './sidebar-entry.ts'
28
28
  import { ImageGenSettingsCard, ImageGenSettingsCardController } from './SettingsCard.tsx'
@@ -68,7 +68,39 @@ export const inject = ['slots', 'locale', 'connection', 'sessions', 'conversatio
68
68
  * @param ctx - client root context (services: slots, locale, connection).
69
69
  */
70
70
  export function apply(ctx: ClientContext): void {
71
+ // The host locale service only knows zh/en dictionaries (its type is
72
+ // fixed); ru rides the untyped single-locale registration instead.
71
73
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-imagegen: dictionaries')
74
+ // Russian ships as a language pack: the dictionary lands in this plugin's
75
+ // namespace, and Русский joins the shared DSH language catalog (Settings →
76
+ // General → Language) with per-key fallback to English for host copy.
77
+ // Registration order/aggregation between register(NS, 'ru', …) and
78
+ // addLanguage differs across host builds and a duplicate throws — these
79
+ // surfaces must degrade silently, never fail the GUI boot.
80
+ ctx.effect(() => {
81
+ try {
82
+ return ctx.locale.register(NS, 'ru', ru)
83
+ } catch (error) {
84
+ console.warn('[dsh-imagegen] ru dictionary not registered:', error)
85
+ return () => {}
86
+ }
87
+ }, 'dsh-imagegen: ru dictionary')
88
+ ctx.effect(() => {
89
+ try {
90
+ if (ctx.locale.getLocale().locales.some(locale => locale.id === 'ru')) return () => {}
91
+ return ctx.locale.addLanguage({ id: 'ru', label: 'Русский', fallback: 'en' })
92
+ } catch (error) {
93
+ console.warn('[dsh-imagegen] ru language not added to the catalog:', error)
94
+ return () => {}
95
+ }
96
+ }, 'dsh-imagegen: ru language pack')
97
+ // Every plugin surface renders through tt(); bridge DSH locale switches
98
+ // into it so the whole plugin follows the interface language.
99
+ ctx.effect(() => {
100
+ const applyLocale = (): void => { applyHostLocale(ctx.locale.getLocale().active) }
101
+ applyLocale()
102
+ return ctx.locale.subscribe(applyLocale)
103
+ }, 'dsh-imagegen: follow host locale')
72
104
  registerImageToolviews(ctx)
73
105
 
74
106
  const connection = ctx.get('connection') as ConnectionHandle | undefined
@@ -119,6 +151,26 @@ export function apply(ctx: ClientContext): void {
119
151
  tt('entry.tooltip'),
120
152
  ))
121
153
  disposers.push(mountPanel(controller, api, scope, { sessions, conversation }))
154
+ // The imperative sidebar tabs render their labels once; relabel them on
155
+ // every DSH language switch so the entry follows the interface too.
156
+ disposers.push(ctx.locale.subscribe(() => {
157
+ const root = document.querySelector('[data-dsh-imagegen-sidebar-root]')
158
+ if (root === null) return
159
+ const labels: Array<[string, string, string]> = [
160
+ ['new-session', tt('entry.newSession'), tt('entry.newSessionTooltip')],
161
+ ['image', tt('entry.image'), tt('entry.tooltip')],
162
+ ]
163
+ for (const [tab, label, tooltip] of labels) {
164
+ const button = root.querySelector<HTMLButtonElement>(`[data-dsh-imagegen-tab="${tab}"]`)
165
+ if (button === null) continue
166
+ button.setAttribute('aria-label', label)
167
+ button.setAttribute('title', tooltip)
168
+ const labelSpan = button.querySelector('span:nth-child(2)')
169
+ if (labelSpan !== null) labelSpan.textContent = label
170
+ }
171
+ const tablist = root.querySelector<HTMLDivElement>('[role="tablist"][data-dsh-imagegen-session-tabs]')
172
+ tablist?.setAttribute('aria-label', tt('entry.tooltip'))
173
+ }))
122
174
  } catch (error) {
123
175
  // DOM failures degrade the studio, never the GUI.
124
176
  console.warn('[dsh-imagegen] mount failed:', error)