@linxin666/dsh-remote-web-ui 0.3.9 → 0.3.10

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 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAelG,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAqBrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAErE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,0CAA0C;QAC1C,MAAM,EAAE,SAAS,CAAA;KAClB;IAED,UAAU,OAAO;QACf;;;;WAIG;QACH;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,4GAA4G;AAC5G,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAYD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAoQ9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAelG,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAqBrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAErE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,0CAA0C;QAC1C,MAAM,EAAE,SAAS,CAAA;KAClB;IAED,UAAU,OAAO;QACf;;;;WAIG;QACH;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,4GAA4G;AAC5G,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAYD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAyQ9C"}
@@ -85,6 +85,12 @@ export function apply(ctx) {
85
85
  };
86
86
  }
87
87
  const t = ctx.locale.bind(NS);
88
+ // Hand the `remote` translate seat to the module-scope adaptation layer:
89
+ // the whale/compact-picker labels were rendered with the English fallback
90
+ // before any dictionary existed; the wiring plus the layer's own sync tick
91
+ // re-render them in the active locale.
92
+ if (adapt !== undefined)
93
+ adapt.translate = t;
88
94
  const binder = ctx.get('webUiSettings') ?? ctx.settingsScope;
89
95
  const settingsScope = binder.bind({ namespace: REMOTE_WEB_UI_NS });
90
96
  const enabled = () => {
@@ -2,6 +2,9 @@
2
2
  /** Simplified Chinese dictionary (the key-set source of truth). */
3
3
  export declare const zh: {
4
4
  'entry.label': string;
5
+ 'mobile.whale.open': string;
6
+ 'mobile.composer.pickModel': string;
7
+ 'mobile.composer.pickEffort': string;
5
8
  title: string;
6
9
  subtitle: string;
7
10
  'card.title': string;
@@ -157,6 +160,9 @@ export type SettingsCardKey = RemoteKey;
157
160
  /** English dictionary, checked complete against the zh key set. */
158
161
  export declare const en: {
159
162
  'entry.label': string;
163
+ 'mobile.whale.open': string;
164
+ 'mobile.composer.pickModel': string;
165
+ 'mobile.composer.pickEffort': string;
160
166
  title: string;
161
167
  subtitle: string;
162
168
  'card.title': string;
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuJmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuJsB,CAAA"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0JmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0JsB,CAAA"}
@@ -2,6 +2,9 @@
2
2
  /** Simplified Chinese dictionary (the key-set source of truth). */
3
3
  export const zh = {
4
4
  'entry.label': '远程访问',
5
+ 'mobile.whale.open': '打开侧边栏',
6
+ 'mobile.composer.pickModel': '选择模型',
7
+ 'mobile.composer.pickEffort': '选择推理等级',
5
8
  'title': '远程访问',
6
9
  'subtitle': '通过手机或另一台电脑配对,远程使用同一份 Web 界面(官方界面 + 移动端适配)',
7
10
  'card.title': '设备配对',
@@ -155,6 +158,9 @@ export const zh = {
155
158
  /** English dictionary, checked complete against the zh key set. */
156
159
  export const en = {
157
160
  'entry.label': 'Remote access',
161
+ 'mobile.whale.open': 'Open sidebar',
162
+ 'mobile.composer.pickModel': 'Pick model',
163
+ 'mobile.composer.pickEffort': 'Pick reasoning effort',
158
164
  'title': 'Remote access',
159
165
  'subtitle': 'Pair a phone or another computer to share the same Web GUI (official UI + mobile adaptation)',
160
166
  'card.title': 'Pair a device',
@@ -20,6 +20,7 @@
20
20
  * reference (MIT, v47-v78) keep their version comments.
21
21
  * @module @linxin666/dsh-remote-web-ui/client/mobile-adapt
22
22
  */
23
+ import type { RemoteKey } from './locales.ts';
23
24
  /** The window global the plugin apply() wires the layout service into. */
24
25
  export interface RemoteAdaptGlobal {
25
26
  evaluate: () => void;
@@ -27,6 +28,14 @@ export interface RemoteAdaptGlobal {
27
28
  toggleSidebar: (() => void) | null;
28
29
  /** Wired by the plugin apply once ctx.layout is live. */
29
30
  closeDetails: (() => void) | null;
31
+ /**
32
+ * Wired by the plugin apply once the `remote` locale namespace is bound:
33
+ * the translate seat the injected surfaces (whale, compact picker) read
34
+ * their labels from. Null until then; label reads fall back to English
35
+ * (the SDK's universal fallback) and the 600ms sync tick re-renders the
36
+ * labels once the seat is wired.
37
+ */
38
+ translate: ((key: RemoteKey) => string) | null;
30
39
  /** Plugin master switch: false reverts the layer, true re-evaluates. */
31
40
  setEnabled(on: boolean): void;
32
41
  /** Replays a pending closeDetails once the layout face is wired (the first apply ran before the wiring). */
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;AAgOD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CA0oBvC"}
1
+ {"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;IAC9C,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;AAgOD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAyqBvC"}
@@ -252,6 +252,20 @@ export function startMobileAdapt() {
252
252
  return;
253
253
  window.__dshRemoteAdaptInstalled = true;
254
254
  const w = window;
255
+ /**
256
+ * Label lookup for the injected surfaces: the `remote` namespace seat wired
257
+ * by the plugin apply, else the English fallback (the SDK's universal
258
+ * fallback; the sync tick re-renders the labels once the seat is live).
259
+ */
260
+ const surfaceText = (key, fallback) => {
261
+ try {
262
+ const out = w.__dshRemoteAdapt?.translate?.(key);
263
+ return typeof out === 'string' && out.length > 0 ? out : fallback;
264
+ }
265
+ catch {
266
+ return fallback;
267
+ }
268
+ };
255
269
  let active = false;
256
270
  let savedViewportContent = null;
257
271
  let whaleEl = null;
@@ -397,12 +411,26 @@ export function startMobileAdapt() {
397
411
  removeCompactPicker();
398
412
  return;
399
413
  }
400
- const zh = (navigator.language ?? '').toLowerCase().startsWith('zh');
401
414
  if (document.getElementById(MODEL_BTN_ID) === null) {
402
- tools.appendChild(makeCompactButton(MODEL_BTN_ID, zh ? '选择模型' : 'Pick model', CUBE_ICON, /模型|Model/));
415
+ // The cell pattern matches the OFFICIAL picker cell text (the official
416
+ // client's own zh/en copy), not plugin-owned text.
417
+ tools.appendChild(makeCompactButton(MODEL_BTN_ID, surfaceText('mobile.composer.pickModel', 'Pick model'), CUBE_ICON, /模型|Model/)); // i18n-allow: matches official picker cell text, not plugin copy
403
418
  }
404
419
  if (document.getElementById(EFFORT_BTN_ID) === null) {
405
- tools.appendChild(makeCompactButton(EFFORT_BTN_ID, zh ? '选择推理等级' : 'Pick reasoning effort', LEVELS_ICON, /推理等级|Reasoning|Effort/i));
420
+ tools.appendChild(makeCompactButton(EFFORT_BTN_ID, surfaceText('mobile.composer.pickEffort', 'Pick reasoning effort'), LEVELS_ICON, /推理等级|Reasoning|Effort/i)); // i18n-allow: matches official picker cell text, not plugin copy
421
+ }
422
+ // Titles re-read every tick so a locale switch (or the translate seat
423
+ // arriving after this layer installed) is picked up without a reload.
424
+ for (const [id, key, fallback] of [
425
+ [MODEL_BTN_ID, 'mobile.composer.pickModel', 'Pick model'],
426
+ [EFFORT_BTN_ID, 'mobile.composer.pickEffort', 'Pick reasoning effort'],
427
+ ]) {
428
+ const btn = document.getElementById(id);
429
+ if (btn === null)
430
+ continue;
431
+ const label = surfaceText(key, fallback);
432
+ btn.title = label;
433
+ btn.setAttribute('aria-label', label);
406
434
  }
407
435
  document.body.classList.add(COMPACT_CLASS);
408
436
  }
@@ -439,9 +467,9 @@ export function startMobileAdapt() {
439
467
  whale.id = WHALE_ID;
440
468
  whale.type = 'button';
441
469
  whale.dataset.dshPlugin = 'remote-web-ui';
442
- // Outside the slots i18n system (module-scope overlay), so the label
443
- // picks the browser language directly.
444
- const whaleLabel = (navigator.language ?? '').toLowerCase().startsWith('zh') ? '打开侧边栏' : 'Open sidebar';
470
+ // The label reads the `remote` namespace (wired by the plugin apply);
471
+ // before wiring it falls back to English and the sync tick re-renders it.
472
+ const whaleLabel = surfaceText('mobile.whale.open', 'Open sidebar');
445
473
  whale.title = whaleLabel;
446
474
  whale.setAttribute('aria-label', whaleLabel);
447
475
  whale.innerHTML = `<svg viewBox="0 0 23.16 17.04" fill="none" aria-hidden="true"><path d="${FISH_PATH}" fill="currentColor"/></svg>`;
@@ -558,6 +586,12 @@ export function startMobileAdapt() {
558
586
  applyWhalePos();
559
587
  whaleEl.style.display = show ? '' : 'none';
560
588
  whaleShown = show;
589
+ // Re-read the label every tick so a locale switch is picked up live.
590
+ const whaleLabel = surfaceText('mobile.whale.open', 'Open sidebar');
591
+ if (whaleEl.title !== whaleLabel)
592
+ whaleEl.title = whaleLabel;
593
+ if (whaleEl.getAttribute('aria-label') !== whaleLabel)
594
+ whaleEl.setAttribute('aria-label', whaleLabel);
561
595
  document.body.classList.toggle(RAIL_HIDDEN_CLASS, collapsed);
562
596
  disableRowDrag();
563
597
  seatHeaderActions();
@@ -949,6 +983,7 @@ export function startMobileAdapt() {
949
983
  evaluate,
950
984
  toggleSidebar: null,
951
985
  closeDetails: null,
986
+ translate: null,
952
987
  setEnabled(on) {
953
988
  adaptEnabled = on;
954
989
  if (on)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-remote-web-ui",
3
3
  "description": "Scan-to-pair remote access for the dsh web GUI that shares one official interface: a QR beside the settings button pairs phones and PCs into the same Web GUI (a portrait-touch adaptation layer for phones, full desktop on PCs) through one-time tokens and revocable device sessions, with a LAN bind toggle, optional Cloudflare tunnel, and one-click family self-update",
4
- "version": "0.3.9",
4
+ "version": "0.3.10",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -166,6 +166,11 @@ export function apply(ctx: ClientContext): void {
166
166
  }
167
167
 
168
168
  const t = ctx.locale.bind(NS)
169
+ // Hand the `remote` translate seat to the module-scope adaptation layer:
170
+ // the whale/compact-picker labels were rendered with the English fallback
171
+ // before any dictionary existed; the wiring plus the layer's own sync tick
172
+ // re-render them in the active locale.
173
+ if (adapt !== undefined) adapt.translate = t
169
174
  const binder = ctx.get('webUiSettings') ?? ctx.settingsScope
170
175
  const settingsScope = binder.bind<RemoteSettings>({ namespace: REMOTE_WEB_UI_NS })
171
176
  const enabled = (): boolean => {
@@ -3,6 +3,9 @@
3
3
  /** Simplified Chinese dictionary (the key-set source of truth). */
4
4
  export const zh = {
5
5
  'entry.label': '远程访问',
6
+ 'mobile.whale.open': '打开侧边栏',
7
+ 'mobile.composer.pickModel': '选择模型',
8
+ 'mobile.composer.pickEffort': '选择推理等级',
6
9
  'title': '远程访问',
7
10
  'subtitle': '通过手机或另一台电脑配对,远程使用同一份 Web 界面(官方界面 + 移动端适配)',
8
11
  'card.title': '设备配对',
@@ -163,6 +166,9 @@ export type SettingsCardKey = RemoteKey
163
166
  /** English dictionary, checked complete against the zh key set. */
164
167
  export const en = {
165
168
  'entry.label': 'Remote access',
169
+ 'mobile.whale.open': 'Open sidebar',
170
+ 'mobile.composer.pickModel': 'Pick model',
171
+ 'mobile.composer.pickEffort': 'Pick reasoning effort',
166
172
  'title': 'Remote access',
167
173
  'subtitle': 'Pair a phone or another computer to share the same Web GUI (official UI + mobile adaptation)',
168
174
  'card.title': 'Pair a device',
@@ -21,6 +21,8 @@
21
21
  * @module @linxin666/dsh-remote-web-ui/client/mobile-adapt
22
22
  */
23
23
 
24
+ import type { RemoteKey } from './locales.ts'
25
+
24
26
  /** The window global the plugin apply() wires the layout service into. */
25
27
  export interface RemoteAdaptGlobal {
26
28
  evaluate: () => void
@@ -28,6 +30,14 @@ export interface RemoteAdaptGlobal {
28
30
  toggleSidebar: (() => void) | null
29
31
  /** Wired by the plugin apply once ctx.layout is live. */
30
32
  closeDetails: (() => void) | null
33
+ /**
34
+ * Wired by the plugin apply once the `remote` locale namespace is bound:
35
+ * the translate seat the injected surfaces (whale, compact picker) read
36
+ * their labels from. Null until then; label reads fall back to English
37
+ * (the SDK's universal fallback) and the 600ms sync tick re-renders the
38
+ * labels once the seat is wired.
39
+ */
40
+ translate: ((key: RemoteKey) => string) | null
31
41
  /** Plugin master switch: false reverts the layer, true re-evaluates. */
32
42
  setEnabled(on: boolean): void
33
43
  /** Replays a pending closeDetails once the layout face is wired (the first apply ran before the wiring). */
@@ -266,6 +276,19 @@ export function startMobileAdapt(): void {
266
276
  ;(window as unknown as { __dshRemoteAdaptInstalled?: boolean }).__dshRemoteAdaptInstalled = true
267
277
 
268
278
  const w = window as unknown as { __dshRemoteAdapt?: RemoteAdaptGlobal } & Record<string, unknown>
279
+ /**
280
+ * Label lookup for the injected surfaces: the `remote` namespace seat wired
281
+ * by the plugin apply, else the English fallback (the SDK's universal
282
+ * fallback; the sync tick re-renders the labels once the seat is live).
283
+ */
284
+ const surfaceText = (key: RemoteKey, fallback: string): string => {
285
+ try {
286
+ const out = w.__dshRemoteAdapt?.translate?.(key)
287
+ return typeof out === 'string' && out.length > 0 ? out : fallback
288
+ } catch {
289
+ return fallback
290
+ }
291
+ }
269
292
  let active = false
270
293
  let savedViewportContent: string | null = null
271
294
  let whaleEl: HTMLButtonElement | null = null
@@ -409,12 +432,25 @@ export function startMobileAdapt(): void {
409
432
  removeCompactPicker()
410
433
  return
411
434
  }
412
- const zh = (navigator.language ?? '').toLowerCase().startsWith('zh')
413
435
  if (document.getElementById(MODEL_BTN_ID) === null) {
414
- tools.appendChild(makeCompactButton(MODEL_BTN_ID, zh ? '选择模型' : 'Pick model', CUBE_ICON, /模型|Model/))
436
+ // The cell pattern matches the OFFICIAL picker cell text (the official
437
+ // client's own zh/en copy), not plugin-owned text.
438
+ tools.appendChild(makeCompactButton(MODEL_BTN_ID, surfaceText('mobile.composer.pickModel', 'Pick model'), CUBE_ICON, /模型|Model/)) // i18n-allow: matches official picker cell text, not plugin copy
415
439
  }
416
440
  if (document.getElementById(EFFORT_BTN_ID) === null) {
417
- tools.appendChild(makeCompactButton(EFFORT_BTN_ID, zh ? '选择推理等级' : 'Pick reasoning effort', LEVELS_ICON, /推理等级|Reasoning|Effort/i))
441
+ tools.appendChild(makeCompactButton(EFFORT_BTN_ID, surfaceText('mobile.composer.pickEffort', 'Pick reasoning effort'), LEVELS_ICON, /推理等级|Reasoning|Effort/i)) // i18n-allow: matches official picker cell text, not plugin copy
442
+ }
443
+ // Titles re-read every tick so a locale switch (or the translate seat
444
+ // arriving after this layer installed) is picked up without a reload.
445
+ for (const [id, key, fallback] of [
446
+ [MODEL_BTN_ID, 'mobile.composer.pickModel', 'Pick model'],
447
+ [EFFORT_BTN_ID, 'mobile.composer.pickEffort', 'Pick reasoning effort'],
448
+ ] as const) {
449
+ const btn = document.getElementById(id)
450
+ if (btn === null) continue
451
+ const label = surfaceText(key, fallback)
452
+ btn.title = label
453
+ btn.setAttribute('aria-label', label)
418
454
  }
419
455
  document.body.classList.add(COMPACT_CLASS)
420
456
  }
@@ -446,9 +482,9 @@ export function startMobileAdapt(): void {
446
482
  whale.id = WHALE_ID
447
483
  whale.type = 'button'
448
484
  whale.dataset.dshPlugin = 'remote-web-ui'
449
- // Outside the slots i18n system (module-scope overlay), so the label
450
- // picks the browser language directly.
451
- const whaleLabel = (navigator.language ?? '').toLowerCase().startsWith('zh') ? '打开侧边栏' : 'Open sidebar'
485
+ // The label reads the `remote` namespace (wired by the plugin apply);
486
+ // before wiring it falls back to English and the sync tick re-renders it.
487
+ const whaleLabel = surfaceText('mobile.whale.open', 'Open sidebar')
452
488
  whale.title = whaleLabel
453
489
  whale.setAttribute('aria-label', whaleLabel)
454
490
  whale.innerHTML = `<svg viewBox="0 0 23.16 17.04" fill="none" aria-hidden="true"><path d="${FISH_PATH}" fill="currentColor"/></svg>`
@@ -553,6 +589,10 @@ export function startMobileAdapt(): void {
553
589
  if (show && !whaleShown) applyWhalePos()
554
590
  whaleEl.style.display = show ? '' : 'none'
555
591
  whaleShown = show
592
+ // Re-read the label every tick so a locale switch is picked up live.
593
+ const whaleLabel = surfaceText('mobile.whale.open', 'Open sidebar')
594
+ if (whaleEl.title !== whaleLabel) whaleEl.title = whaleLabel
595
+ if (whaleEl.getAttribute('aria-label') !== whaleLabel) whaleEl.setAttribute('aria-label', whaleLabel)
556
596
  document.body.classList.toggle(RAIL_HIDDEN_CLASS, collapsed)
557
597
  disableRowDrag()
558
598
  seatHeaderActions()
@@ -898,6 +938,7 @@ export function startMobileAdapt(): void {
898
938
  evaluate,
899
939
  toggleSidebar: null,
900
940
  closeDetails: null,
941
+ translate: null,
901
942
  setEnabled(on: boolean): void {
902
943
  adaptEnabled = on
903
944
  if (on) evaluate()