dsh-plugin-update 0.5.7 → 0.6.0

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/dist/panel.js CHANGED
@@ -10,92 +10,56 @@ import {
10
10
  yankedBannerHTML
11
11
  } from "./changelog.js";
12
12
  import { LOG_EVENT_CALL, LOG_EVENT_CALL_FAIL, LOG_EVENT_INSTALL_EXEC } from "./log-events.js";
13
+ import { copyText } from "./bilingual.js";
14
+ import { normalizeLangTag, resolveLang, subscribeLang } from "./lang.js";
13
15
  function normalizePanelTheme(value) {
14
- return value === "archive" || value === "d5-paper" ? "archive" : "default";
16
+ return value === "archive" ? "archive" : "default";
15
17
  }
16
- const BLOCKED_COPY = {
17
- "unknown-profile": {
18
- title: "\u4F7F\u7528\u8303\u56F4\u6216\u63D2\u4EF6\u4F4D\u7F6E\u8BA4\u4E0D\u51FA",
19
- action: "\u91CD\u5F00\u5BBF\u4E3B\u518D\u67E5\u4E00\u6B21\uFF1B\u4E00\u76F4\u8FD9\u6837\u5C31\u628A\u7248\u672C\u53F7\u4E0E\u65E5\u5FD7\u4EA4\u7ED9\u63D2\u4EF6\u4F5C\u8005\uFF1B\u8FD9\u79CD\u60C5\u5F62\u4E0D\u7ED9\u624B\u5DE5\u547D\u4EE4"
20
- },
21
- "source-install": {
22
- title: "\u5F53\u524D\u662F\u4ECE\u6E90\u7801\u88C5\u7684\uFF0C\u4E0D\u662F\u6309\u7248\u672C\u53F7\u88C5\u7684",
23
- action: "\u8FD9\u79CD\u60C5\u5F62\u4E0D\u7ED9\u624B\u5DE5\u547D\u4EE4\uFF1B\u60F3\u8D70\u66F4\u65B0\u5148\u6309\u7248\u672C\u53F7\u91CD\u88C5\u4E00\u6B21"
24
- },
25
- "invalid-installation": {
26
- title: "\u5DF2\u88C5\u7684\u5305\u4E0D\u5B8C\u6574\uFF08\u540D\u5B57\u5BF9\u4E0D\u4E0A\u3001\u7248\u672C\u975E\u6CD5\u3001\u5165\u53E3\u6587\u4EF6\u7F3A\u5931\uFF09",
27
- action: "\u91CD\u88C5\u5F53\u524D\u7248\u672C\uFF0C\u4FEE\u597D\u5DF2\u88C5\u76EE\u5F55\u518D\u67E5\u66F4\u65B0"
28
- },
29
- "installation-changed": {
30
- title: "\u5B89\u88C5\u4F4D\u7F6E\u5728\u4F7F\u7528\u4E2D\u9014\u53D8\u4E86\uFF08\u6362\u4E86\u76EE\u5F55\u6216\u6362\u4E86\u5305\uFF09",
31
- action: "\u91CD\u65B0\u6253\u5F00\u5BBF\u4E3B\u518D\u67E5\u4E00\u6B21\uFF1B\u8FD8\u51FA\u73B0\u5C31\u91CD\u88C5"
32
- },
33
- "pending-restart": {
34
- title: "\u65B0\u7248\u5DF2\u88C5\u5230\u78C1\u76D8\uFF0C\u6B63\u5728\u8DD1\u7684\u8FD8\u662F\u65E7\u7248",
35
- action: "\u91CD\u542F\u5BBF\u4E3B\uFF0C\u8BA9\u65B0\u7248\u8DD1\u8D77\u6765\uFF1B\u8FD9\u662F\u6B63\u5E38\u7EC8\u6001\uFF0C\u4E0D\u662F\u5931\u8D25"
36
- },
37
- "registry-conflict": {
38
- title: "\u672C\u5730\u58F0\u660E\u7684\u7248\u672C\u4E0E\u78C1\u76D8\u5B9E\u9645\u7248\u672C\u4E92\u76F8\u77DB\u76FE",
39
- action: "\u6253\u5F00\u4F7F\u7528\u8303\u56F4\u7684\u6E05\u5355\u6587\u4EF6\uFF0C\u628A\u76EE\u6807\u5305\u540D\u90A3\u4E00\u884C\u6539\u6210\u7248\u672C\u53F7\u518D\u8BD5"
40
- },
41
- "incompatible-node": {
42
- title: "\u65B0\u7248\u8981\u6C42\u7684 Node \u4E0E\u5F53\u524D\u8FD0\u884C\u7684\u5BF9\u4E0D\u4E0A",
43
- action: "\u5148\u5347\u7EA7 Node \u5230 22 \u6216\u66F4\u9AD8\uFF0C\u518D\u67E5\u66F4\u65B0"
44
- },
45
- "recovery-required": {
46
- title: "\u4E0A\u6B21\u5B89\u88C5\u88AB\u6253\u65AD\uFF0C\u7559\u4E0B\u4E00\u4E2A\u534A\u622A\u4EFB\u52A1",
47
- action: "\u91CD\u65B0\u70B9\u4E00\u6B21\u5B89\u88C5\uFF1B\u4E00\u76F4\u51FA\u73B0\u5C31\u6309\u7B2C 6 \u8282\u6392\u9519"
48
- }
18
+ const BLOCKED_KEYS = {
19
+ "unknown-profile": { title: "panel.blocked.unknown-profile.title", action: "panel.blocked.unknown-profile.action" },
20
+ "source-install": { title: "panel.blocked.source-install.title", action: "panel.blocked.source-install.action" },
21
+ "invalid-installation": { title: "panel.blocked.invalid-installation.title", action: "panel.blocked.invalid-installation.action" },
22
+ "installation-changed": { title: "panel.blocked.installation-changed.title", action: "panel.blocked.installation-changed.action" },
23
+ "pending-restart": { title: "panel.blocked.pending-restart.title", action: "panel.blocked.pending-restart.action" },
24
+ "registry-conflict": { title: "panel.blocked.registry-conflict.title", action: "panel.blocked.registry-conflict.action" },
25
+ "incompatible-node": { title: "panel.blocked.incompatible-node.title", action: "panel.blocked.incompatible-node.action" },
26
+ "recovery-required": { title: "panel.blocked.recovery-required.title", action: "panel.blocked.recovery-required.action" }
49
27
  };
50
- function blockedCopy(reason) {
28
+ function blockedCopy(reason, lang) {
51
29
  if (reason === null || reason === void 0) return null;
52
- return BLOCKED_COPY[reason] ?? null;
30
+ const keys = BLOCKED_KEYS[reason];
31
+ if (!keys) return null;
32
+ const l = normalizeLangTag(lang ?? "zh");
33
+ return { title: copyText(keys.title, l), action: copyText(keys.action, l) };
53
34
  }
54
- const PHONE_FAILURE_COPY = {
55
- "check-failed": {
56
- zh: "\u67E5\u65B0\u7248\u6CA1\u6210\u529F\uFF08\u8054\u7F51\u3001\u6E90\u3001\u9650\u6D41\u90FD\u53EF\u80FD\uFF09",
57
- act: "\u8FC7\u4E00\u4F1A\u513F\u518D\u67E5\u4E00\u6B21\uFF1B\u4E00\u76F4\u5931\u8D25\u5C31\u628A\u590D\u5236\u8BCA\u65AD\u4EA4\u7ED9\u63D2\u4EF6\u4F5C\u8005"
58
- },
59
- "invalid-release": {
60
- zh: "\u62FF\u5230\u7684\u53D1\u5E03\u4FE1\u606F\u4E0D\u5408\u6CD5\uFF08\u7248\u672C\u53F7\u975E\u6CD5\u6216\u5185\u5BB9\u5BF9\u4E0D\u4E0A\uFF09",
61
- act: "\u68C0\u67E5\u6E05\u5355\u6587\u4EF6\u91CC\u7684\u5305\u540D\u4E0E\u7248\u672C\u5199\u6CD5\uFF0C\u518D\u67E5\u4E00\u6B21"
62
- },
63
- "check-expired": {
64
- zh: "\u51ED\u8BC1\u8FC7\u671F\u4E86\uFF0C\u5B89\u88C5\u8BF7\u6C42\u88AB\u62D2",
65
- act: "\u91CD\u65B0\u67E5\u4E00\u6B21\u65B0\u7248\u518D\u70B9\u5B89\u88C5\uFF0C\u4E0D\u8981\u91CD\u8BD5\u65E7\u7F16\u53F7"
66
- },
67
- "update-busy": {
68
- zh: "\u540C\u4E00\u4F7F\u7528\u8303\u56F4\u6B63\u5728\u88C5\u53E6\u4E00\u4E2A",
69
- act: "\u7B49\u5F53\u524D\u4EFB\u52A1\u79BB\u5F00 installing/verifying \u518D\u70B9\uFF1B\u6392\u961F\u4E2D\u53BB\u67E5\u72B6\u6001\u770B\u4F4D\u7F6E"
70
- },
71
- "install-failed": {
72
- zh: "\u88C5\u4E0D\u4E0A\uFF08\u8BE6\u89C1\u8BCA\u65AD\u6458\u8981\uFF09",
73
- act: "\u5148\u770B\u590D\u5236\u8BCA\u65AD\uFF1B\u5B98\u65B9\u684C\u9762\u7248\u628A\u8FD9\u6BB5\u4EA4\u7ED9\u63D2\u4EF6\u4F5C\u8005"
74
- },
75
- internal: {
76
- zh: "\u51FA\u4E86\u70B9\u95EE\u9898\uFF0C\u8BA4\u4E0D\u51FA\u5177\u4F53\u539F\u56E0",
77
- act: "\u5148\u91CD\u8BD5\u4E00\u6B21\uFF1B\u4E00\u76F4\u8FD9\u6837\u5C31\u628A\u590D\u5236\u8BCA\u65AD\u4EA4\u7ED9\u63D2\u4EF6\u4F5C\u8005"
78
- }
35
+ const PHONE_FAILURE_KEYS = {
36
+ "check-failed": { title: "panel.failure.check-failed.title", action: "panel.failure.check-failed.action" },
37
+ "invalid-release": { title: "panel.failure.invalid-release.title", action: "panel.failure.invalid-release.action" },
38
+ "check-expired": { title: "panel.failure.check-expired.title", action: "panel.failure.check-expired.action" },
39
+ "update-busy": { title: "panel.failure.update-busy.title", action: "panel.failure.update-busy.action" },
40
+ "install-failed": { title: "panel.failure.install-failed.title", action: "panel.failure.install-failed.action" },
41
+ internal: { title: "panel.failure.internal.title", action: "panel.failure.internal.action" }
79
42
  };
80
- const UNKNOWN_FAILURE_COPY = {
81
- zh: "\u51FA\u4E86\u70B9\u95EE\u9898\uFF0C\u8BA4\u4E0D\u51FA\u5177\u4F53\u539F\u56E0",
82
- act: "\u5148\u91CD\u8BD5\u4E00\u6B21\uFF1B\u4E00\u76F4\u8FD9\u6837\u5C31\u628A\u590D\u5236\u8BCA\u65AD\u4EA4\u7ED9\u63D2\u4EF6\u4F5C\u8005\uFF08\u5E26\u4E0A\u4F60\u770B\u5230\u7684\u7801\uFF09"
43
+ const UNKNOWN_FAILURE_KEYS = {
44
+ title: "panel.failure.unknown.title",
45
+ action: "panel.failure.unknown.action"
83
46
  };
84
47
  function isKnownFailureCode(code) {
85
48
  if (typeof code !== "string" || !code) return false;
86
49
  const c = code.trim();
87
50
  if (!c) return false;
88
- return c in BLOCKED_COPY || c in PHONE_FAILURE_COPY;
51
+ return c in BLOCKED_KEYS || c in PHONE_FAILURE_KEYS;
89
52
  }
90
- function failureCopy(code) {
53
+ function failureCopy(code, lang) {
91
54
  if (typeof code !== "string") return null;
92
55
  const c = code.trim();
93
56
  if (!c) return null;
94
- const blocked = BLOCKED_COPY[c];
95
- if (blocked) return { zh: blocked.title, act: blocked.action };
96
- const phone = PHONE_FAILURE_COPY[c];
97
- if (phone) return phone;
98
- return UNKNOWN_FAILURE_COPY;
57
+ const l = normalizeLangTag(lang ?? "zh");
58
+ const blocked = BLOCKED_KEYS[c];
59
+ if (blocked) return { zh: copyText(blocked.title, l), act: copyText(blocked.action, l) };
60
+ const phone = PHONE_FAILURE_KEYS[c];
61
+ if (phone) return { zh: copyText(phone.title, l), act: copyText(phone.action, l) };
62
+ return { zh: copyText(UNKNOWN_FAILURE_KEYS.title, l), act: copyText(UNKNOWN_FAILURE_KEYS.action, l) };
99
63
  }
100
64
  function failureCodeOf(reply, fallback) {
101
65
  const kind = reply && typeof reply.errorKind === "string" ? reply.errorKind.trim() : "";
@@ -109,24 +73,27 @@ const PANEL_DIAG_MAX_CHARS = COPY_BUDGET_CHARS;
109
73
  function redactForCopy(text) {
110
74
  return sanitizeForCopy(text);
111
75
  }
112
- function buildDiagnosticText(input) {
76
+ function buildDiagnosticText(input, langOverride) {
77
+ const l = normalizeLangTag(langOverride ?? input.lang ?? "zh");
78
+ const unknown = copyText("panel.diag.copy.unknown", l);
113
79
  const code = String(input.code || "check-failed");
114
- const lines = [`[\u66F4\u65B0\u8BCA\u65AD] ${String(input.pluginId)} \u7A33\u5B9A\u7801\uFF1A${code}`];
80
+ const lines = [copyText("panel.diag.header", l, { pluginId: String(input.pluginId), code })];
115
81
  const detail = redactForCopy(input.detail ?? "");
116
- lines.push(`\u4EBA\u8BDD\uFF1A${detail || failureCopy(code)?.act || code}`);
82
+ const fallbackAct = failureCopy(code, l)?.act || code;
83
+ lines.push(copyText("panel.diag.label.human", l, { detail: detail || fallbackAct }));
117
84
  const versions = [
118
- `\u8FD0\u884C\u7248\uFF1A${input.runningVersion ?? "\u672A\u77E5"}`,
119
- `\u5DF2\u88C5\uFF1A${input.installedVersion ?? "\u672A\u77E5"}`,
120
- `\u8FDC\u7AEF\uFF1A${input.latestVersion ?? "\u672A\u67E5\u8FC7"}`
85
+ copyText("panel.diag.label.running", l, { version: input.runningVersion ?? unknown }),
86
+ copyText("panel.diag.label.installed", l, { version: input.installedVersion ?? unknown }),
87
+ copyText("panel.diag.label.latest", l, { version: input.latestVersion ?? (l === "en" ? "Not checked" : "\u672A\u67E5\u8FC7") })
121
88
  ].join(" / ");
122
89
  lines.push(versions);
123
- const queue = input.queuePosition === 0 ? "\u6B63\u5728\u5B89\u88C5" : typeof input.queuePosition === "number" ? `\u6392\u961F\u7B2C ${input.queuePosition} \u4F4D` : "\u4E0D\u5728\u961F\u5217\u91CC";
124
- const hostLine = [`\u5BBF\u4E3B\uFF1A${input.hostKind ?? "\u672A\u77E5"}`];
125
- if (typeof input.profileName === "string" && input.profileName) hostLine.push(`\u4F7F\u7528\u8303\u56F4\uFF1A${input.profileName}`);
126
- hostLine.push(`\u961F\u5217\uFF1A${queue}`);
127
- lines.push(hostLine.join(" / ") + (input.requestId ? ` / \u8BF7\u6C42\u7F16\u53F7\uFF1A${input.requestId}` : ""));
90
+ const queue = queueTextOf(input.queuePosition ?? null, l);
91
+ const hostLine = [copyText("panel.diag.label.host", l, { host: input.hostKind ?? unknown })];
92
+ if (typeof input.profileName === "string" && input.profileName) hostLine.push(copyText("panel.diag.label.profile", l, { profile: input.profileName }));
93
+ hostLine.push(copyText("panel.diag.label.queue", l, { queue }));
94
+ lines.push(hostLine.join(" / ") + (input.requestId ? ` / ${copyText("panel.diag.label.request", l, { requestId: input.requestId })}` : ""));
128
95
  const manual = redactForCopy(input.manual ?? "");
129
- if (manual) lines.push(`\u624B\u5DE5\u547D\u4EE4\uFF1A${manual}`);
96
+ if (manual) lines.push(copyText("panel.diag.label.manual", l, { manual }));
130
97
  return redactForCopy(lines.join("\n"));
131
98
  }
132
99
  const DIAG_KNOWN_TYPES = {
@@ -185,52 +152,56 @@ function pickText(value, fallback) {
185
152
  if (typeof value === "string" && value.trim()) return value.trim();
186
153
  return fallback;
187
154
  }
188
- function queueTextOf(queuePosition, _diagQueuePos) {
155
+ function queueTextOf(queuePosition, lang, _diagQueuePos) {
189
156
  void _diagQueuePos;
157
+ const l = normalizeLangTag(lang ?? "zh");
190
158
  const pos = typeof queuePosition === "number" ? queuePosition : null;
191
- if (pos === 0) return "\u6B63\u5728\u5B89\u88C5";
192
- if (typeof pos === "number") return `\u6392\u961F\u7B2C ${pos} \u4F4D`;
193
- return "\u4E0D\u5728\u961F\u5217\u91CC";
159
+ if (pos === 0) return copyText("panel.diag.queue.installing", l);
160
+ if (typeof pos === "number") return copyText("panel.diag.queue.position", l, { n: pos });
161
+ return copyText("panel.diag.queue.absent", l);
194
162
  }
195
- function buildUpdateDiagCopy(input) {
163
+ function buildUpdateDiagCopy(input, langOverride) {
164
+ const l = normalizeLangTag(langOverride ?? input.lang ?? "zh");
165
+ const unknown = copyText("panel.diag.copy.unknown", l);
166
+ const unknownPkg = copyText("panel.diag.copy.unknown-package", l);
196
167
  const rawCode = String(input.code ?? "").trim() || "internal";
197
- const copy = failureCopy(rawCode) ?? UNKNOWN_FAILURE_COPY;
168
+ const copy = failureCopy(rawCode, l) ?? failureCopy("unknown", l);
198
169
  const diag = readDiagTolerant(input.diag);
199
170
  const detailRaw = diag.detail ?? input.detail ?? "";
200
- const detail = redactForCopy(detailRaw) || "\uFF08\u672C\u56DE\u5305\u6CA1\u6709\u5E26\u8BCA\u65AD\u6458\u8981\uFF0C\u7B49\u7535\u8BDD\u4FA7 diag \u843D\u5B9A\u540E\u8865\u9F50\uFF09";
201
- const pluginName = pickText(diag.targetPackageName ?? input.pluginId, "(\u672A\u77E5\u5305)");
171
+ const detail = redactForCopy(detailRaw) || copyText("panel.diag.copy.no-detail", l);
172
+ const pluginName = pickText(diag.targetPackageName ?? input.pluginId, unknownPkg);
202
173
  const runVer = pickText(diag.runningVersion ?? input.runningVersion, "?");
203
174
  const instVer = pickText(input.installedVersion ?? diag.latestVersion, "?");
204
- const host = pickText(diag.environmentKind ?? input.hostKind, "\u672A\u77E5");
175
+ const host = pickText(diag.environmentKind ?? input.hostKind, unknown);
205
176
  const routeRaw = diag.route ?? input.route;
206
177
  const requestRaw = diag.requestId ?? input.requestId;
207
178
  const checkRaw = diag.checkId ?? input.checkId;
208
- const queue = queueTextOf(input.queuePosition ?? null);
209
- const prov = [`\u63D2\u4EF6=${pluginName}`, `\u7248\u672C=${runVer}\u2192${instVer}`, `\u5BBF\u4E3B=${host}`];
210
- prov.push(`\u4F7F\u7528\u8303\u56F4=${pickText(input.profileName, "\u672A\u77E5")}`);
211
- if (typeof routeRaw === "string" && routeRaw.trim()) prov.push(`\u8DEF\u7531=${routeRaw.trim()}`);
212
- if (diag.stage) prov.push(`\u9636\u6BB5=${diag.stage}`);
213
- if (diag.method) prov.push(`\u65B9\u6CD5=${diag.method}`);
179
+ const queue = queueTextOf(input.queuePosition ?? null, l);
180
+ const prov = [copyText("panel.diag.copy.field.plugin", l, { plugin: pluginName }), copyText("panel.diag.copy.field.version", l, { run: runVer, inst: instVer }), copyText("panel.diag.copy.field.host", l, { host })];
181
+ prov.push(copyText("panel.diag.copy.field.profile", l, { profile: pickText(input.profileName, unknown) }));
182
+ if (typeof routeRaw === "string" && routeRaw.trim()) prov.push(copyText("panel.diag.copy.field.route", l, { route: routeRaw.trim() }));
183
+ if (diag.stage) prov.push(copyText("panel.diag.copy.field.stage", l, { stage: diag.stage }));
184
+ if (diag.method) prov.push(copyText("panel.diag.copy.field.method", l, { method: diag.method }));
214
185
  if (typeof diag.httpStatus === "number") prov.push(`HTTP=${diag.httpStatus}`);
215
186
  if (typeof diag.exitCode === "number") prov.push(`exit=${diag.exitCode}`);
216
- if (typeof diag.latencyMs === "number") prov.push(`\u8017\u65F6=${diag.latencyMs}ms`);
217
- if (diag.registryHost) prov.push(`\u6E90=${diag.registryHost}`);
218
- else prov.push("\u6E90=\u672A\u77E5\uFF08\u975E\u5B98\u65B9\u6E90\u65F6\u7701\u7565\u672C\u8EAB\u5373\u4FE1\u606F\uFF09");
219
- if (diag.action) prov.push(`\u5EFA\u8BAE=${diag.action}`);
220
- if (typeof requestRaw === "string" && requestRaw.trim()) prov.push(`\u8BF7\u6C42=${requestRaw.trim()}`);
221
- if (typeof checkRaw === "string" && checkRaw.trim()) prov.push(`\u68C0\u67E5=${checkRaw.trim()}`);
222
- prov.push(`\u961F\u5217=${queue}`);
187
+ if (typeof diag.latencyMs === "number") prov.push(`${copyText("panel.diag.copy.field.latency", l, { latency: diag.latencyMs })}ms`);
188
+ if (diag.registryHost) prov.push(copyText("panel.diag.copy.field.registry", l, { host: diag.registryHost }));
189
+ else prov.push(copyText("panel.diag.copy.field.registry-unknown", l));
190
+ if (diag.action) prov.push(copyText("panel.diag.copy.field.action", l, { action: diag.action }));
191
+ if (typeof requestRaw === "string" && requestRaw.trim()) prov.push(copyText("panel.diag.copy.field.request", l, { request: requestRaw.trim() }));
192
+ if (typeof checkRaw === "string" && checkRaw.trim()) prov.push(copyText("panel.diag.copy.field.check", l, { check: checkRaw.trim() }));
193
+ prov.push(copyText("panel.diag.copy.field.queue", l, { queue }));
223
194
  const format = input.format === "line" ? "line" : "block";
224
195
  const provLine = redactForCopy(prov.join(" \xB7 "));
225
196
  if (format === "line") {
226
- const line = `[update-diag] code=${rawCode} \xB7 ${copy.zh} \xB7 \u6458\u8981=${detail} \xB7 ${provLine} \xB7 \u600E\u4E48\u529E=${copy.act}`;
197
+ const line = `[update-diag] code=${rawCode} \xB7 ${copy.zh} \xB7 ${copyText("panel.diag.copy.line.summary", l, { summary: detail })} \xB7 ${provLine} \xB7 ${copyText("panel.diag.copy.line.remedy", l, { remedy: copy.act })}`;
227
198
  return redactForCopy(line);
228
199
  }
229
200
  const block = [
230
201
  `[update-diag] ${rawCode} \u2014 ${copy.zh}`,
231
- ` \u6458\u8981\uFF1A${detail}`,
232
- ` \u6765\u6E90\uFF1A${provLine}`,
233
- ` \u600E\u4E48\u529E\uFF1A${copy.act}`
202
+ ` ${copyText("panel.diag.copy.block.summary", l, { summary: detail })}`,
203
+ ` ${copyText("panel.diag.copy.block.source", l, { source: provLine })}`,
204
+ ` ${copyText("panel.diag.copy.block.remedy", l, { remedy: copy.act })}`
234
205
  ].join("\n");
235
206
  return block;
236
207
  }
@@ -331,40 +302,47 @@ function createBrowserSkipStore(pluginId, storage, now = Date.now) {
331
302
  }
332
303
  };
333
304
  }
334
- const SEAL_BY_KIND = {
335
- loading: { text: "\u5F85\u67E5", mini: "\u67E5", tone: "ink" },
336
- idle: { text: "\u5F85\u67E5", mini: "\u67E5", tone: "ink" },
337
- update: { text: "\u53EF\u88C5", mini: "\u88C5", tone: "green" },
338
- busy: { text: "\u5B89\u88C5\u4E2D", mini: "\u88C5", tone: "yellow" },
339
- restart: { text: "\u5F85\u91CD\u542F", mini: "\u542F", tone: "yellow" },
340
- blocked: { text: "\u53D7\u963B", mini: "\u963B", tone: "red" },
341
- failed: { text: "\u53D7\u963B", mini: "\u963B", tone: "red" },
342
- done: { text: "\u5DF2\u6700\u65B0", mini: "\u5B9A", tone: "green" }
305
+ const SEAL_KEYS = {
306
+ loading: { text: "panel.seal.loading.text", mini: "panel.seal.loading.mini", tone: "ink" },
307
+ idle: { text: "panel.seal.idle.text", mini: "panel.seal.idle.mini", tone: "ink" },
308
+ update: { text: "panel.seal.update.text", mini: "panel.seal.update.mini", tone: "green" },
309
+ busy: { text: "panel.seal.busy.text", mini: "panel.seal.busy.mini", tone: "yellow" },
310
+ restart: { text: "panel.seal.restart.text", mini: "panel.seal.restart.mini", tone: "yellow" },
311
+ blocked: { text: "panel.seal.blocked.text", mini: "panel.seal.blocked.mini", tone: "red" },
312
+ failed: { text: "panel.seal.failed.text", mini: "panel.seal.failed.mini", tone: "red" },
313
+ done: { text: "panel.seal.done.text", mini: "panel.seal.done.mini", tone: "green" }
343
314
  };
315
+ function panelSealFor(kind, lang) {
316
+ const keys = SEAL_KEYS[kind] ?? SEAL_KEYS.idle;
317
+ const l = normalizeLangTag(lang ?? "zh");
318
+ return { text: copyText(keys.text, l), mini: copyText(keys.mini, l), tone: keys.tone };
319
+ }
344
320
  function messageCodeOf(message) {
345
321
  if (typeof message !== "string" || !message) return "";
346
322
  const at = message.indexOf(":");
347
323
  return (at < 0 ? message : message.slice(0, at)).trim();
348
324
  }
349
- function panelViewModel(input) {
350
- const view = panelViewModelCore(input);
351
- return { ...view, seal: SEAL_BY_KIND[view.banner.kind] ?? SEAL_BY_KIND.idle };
325
+ function panelViewModel(input, lang) {
326
+ const l = normalizeLangTag(lang ?? input.lang ?? "zh");
327
+ const view = panelViewModelCore(input, l);
328
+ return { ...view, seal: panelSealFor(view.banner.kind, l) };
352
329
  }
353
- function panelViewModelCore(input) {
330
+ function panelViewModelCore(input, lang) {
354
331
  const { snapshot, manual, queue, skippedLatest, lastError } = input;
355
332
  const errorKind = input.errorKind;
333
+ const l = normalizeLangTag(lang ?? "zh");
356
334
  if (!snapshot) {
357
335
  const earlyCode = typeof errorKind === "string" && errorKind.trim() || lastError || "";
358
336
  if (earlyCode) {
359
- const copy = failureCopy(earlyCode);
337
+ const copy = failureCopy(earlyCode, l);
360
338
  return {
361
339
  banner: {
362
340
  kind: "failed",
363
- title: `\u66F4\u65B0\u5931\u8D25\uFF08${earlyCode}\uFF09\uFF1A${copy?.zh ?? earlyCode}\u3002`,
364
- action: copy?.act || "\u590D\u5236\u8BCA\u65AD\u53D1\u7ED9\u63D2\u4EF6\u4F5C\u8005\uFF1B\u6DF1\u6316\u770B\u65E5\u5FD7\u901A\u9053\u3002"
341
+ title: copyText("panel.banner.error.title", l, { code: earlyCode, detail: copy?.zh ?? earlyCode }),
342
+ action: copy?.act || copyText("panel.banner.error.action-fallback", l)
365
343
  },
366
344
  installEnabled: false,
367
- installLabel: "\u91CD\u8BD5\u5B89\u88C5",
345
+ installLabel: copyText("panel.action.retry-install", l),
368
346
  skippedLatest: false,
369
347
  showManual: manual ? true : false,
370
348
  showReset: false,
@@ -372,9 +350,9 @@ function panelViewModelCore(input) {
372
350
  };
373
351
  }
374
352
  return {
375
- banner: { kind: "loading", title: "\u6B63\u5728\u8BFB\u53D6\u66F4\u65B0\u72B6\u6001\u2026", action: "" },
353
+ banner: { kind: "loading", title: copyText("panel.banner.loading", l), action: "" },
376
354
  installEnabled: false,
377
- installLabel: "\u5B89\u88C5\u66F4\u65B0",
355
+ installLabel: copyText("panel.action.install", l),
378
356
  skippedLatest: false,
379
357
  showManual: false,
380
358
  showReset: false,
@@ -383,16 +361,16 @@ function panelViewModelCore(input) {
383
361
  }
384
362
  const job = snapshot.job ?? null;
385
363
  const jobState = job?.state ?? null;
386
- const queueNote = queue && queue.busy ? queue.position === 0 ? "\u6B63\u5728\u5B89\u88C5\uFF08\u672C\u63D2\u4EF6\u5728\u88C5\uFF09\u3002" : typeof queue.position === "number" ? `\u524D\u65B9\u6709\u5B89\u88C5\u5728\u8FDB\u884C\uFF0C\u672C\u63D2\u4EF6\u6392\u7B2C ${queue.position} \u4F4D\uFF0C\u5230\u961F\u9996\u518D\u70B9\u5B89\u88C5\u3002` : "\u524D\u65B9\u6709\u5176\u4ED6\u63D2\u4EF6\u5728\u5B89\u88C5\uFF0C\u7A0D\u540E\u91CD\u8BD5\u3002" : null;
364
+ const queueNote = queue && queue.busy ? queue.position === 0 ? copyText("panel.queue.busy-self", l) : typeof queue.position === "number" ? copyText("panel.queue.busy-queued", l, { n: queue.position }) : copyText("panel.queue.busy-other", l) : null;
387
365
  if (skippedLatest && snapshot.latestVersion) {
388
366
  return {
389
367
  banner: {
390
368
  kind: "idle",
391
- title: `\u5DF2\u8DF3\u8FC7 ${snapshot.latestVersion}`,
392
- action: "\u70B9\u201C\u6062\u590D\u201D\u53EF\u91CD\u65B0\u63D0\u9192\u8BE5\u7248\u672C\uFF1B\u6709\u66F4\u65B0\u7684\u65B0\u7248\u672C\u4F1A\u7167\u5E38\u63D0\u9192\u3002"
369
+ title: copyText("panel.skip.skipped-title", l, { latest: snapshot.latestVersion }),
370
+ action: copyText("panel.skip.skipped-action", l)
393
371
  },
394
372
  installEnabled: false,
395
- installLabel: "\u5B89\u88C5\u66F4\u65B0",
373
+ installLabel: copyText("panel.action.install", l),
396
374
  skippedLatest: true,
397
375
  showManual: false,
398
376
  showReset: true,
@@ -404,13 +382,13 @@ function panelViewModelCore(input) {
404
382
  return {
405
383
  banner: {
406
384
  kind: "restart",
407
- // 文案照原型(d5-paper.html:329):不带 emoji——警示由横幅左侧的手绘 SVG 标承担,
385
+ // 文案照原型(archive.html:329):不带 emoji——警示由横幅左侧的手绘 SVG 标承担,
408
386
  // 印章在状态一侧,两者各司其职,不再三重标记。
409
- title: `\u65B0\u7248 ${latest} \u5DF2\u5B89\u88C5\uFF0C\u91CD\u542F\u5BBF\u4E3B\u540E\u751F\u6548\u3002`,
410
- action: BLOCKED_COPY["pending-restart"].action
387
+ title: copyText("panel.banner.restart-title", l, { latest }),
388
+ action: blockedCopy("pending-restart", l)?.action ?? ""
411
389
  },
412
390
  installEnabled: false,
413
- installLabel: "\u5B89\u88C5\u66F4\u65B0",
391
+ installLabel: copyText("panel.action.install", l),
414
392
  skippedLatest: false,
415
393
  showManual: manual ? true : false,
416
394
  showReset: false,
@@ -418,14 +396,17 @@ function panelViewModelCore(input) {
418
396
  };
419
397
  }
420
398
  if (jobState === "installing" || jobState === "verifying") {
399
+ const ver = typeof job?.targetVersion === "string" && job.targetVersion.trim() ? job.targetVersion.trim() : "";
400
+ let installingTitle = copyText("panel.banner.installing-title", l, { version: ver || " " });
401
+ if (!ver) installingTitle = installingTitle.replace(" \u2026", "\u2026").replace(" ...", "...");
421
402
  return {
422
403
  banner: {
423
404
  kind: "busy",
424
- title: `\u6B63\u5728\u5B89\u88C5${job?.targetVersion ? ` ${job.targetVersion}` : ""}\u2026\u5173\u95ED\u9762\u677F\u4E0D\u4F1A\u4E2D\u65AD\u3002`,
425
- action: "\u8FDB\u5EA6\u6309\u8F6E\u8BE2\u81EA\u52A8\u5237\u65B0\uFF1B\u91CD\u5F00\u9762\u677F 1 \u79D2\u5185\u6062\u590D\u663E\u793A\u3002"
405
+ title: installingTitle,
406
+ action: copyText("panel.banner.installing-action", l)
426
407
  },
427
408
  installEnabled: false,
428
- installLabel: "\u5B89\u88C5\u4E2D\u2026",
409
+ installLabel: copyText("panel.action.installing", l),
429
410
  skippedLatest: false,
430
411
  showManual: false,
431
412
  showReset: false,
@@ -436,15 +417,15 @@ function panelViewModelCore(input) {
436
417
  const failedCode = typeof errorKind === "string" && errorKind.trim() || lastError || jobCode || "";
437
418
  if (failedCode || jobState === "failed" || jobState === "interrupted") {
438
419
  const code = failedCode || "install-failed";
439
- const copy = failureCopy(code);
420
+ const copy = failureCopy(code, l);
440
421
  return {
441
422
  banner: {
442
423
  kind: "failed",
443
- title: `\u66F4\u65B0\u5931\u8D25\uFF08${code}\uFF09\uFF1A${copy?.zh ?? code}\u3002`,
444
- action: copy?.act || "\u590D\u5236\u8BCA\u65AD\u53D1\u7ED9\u63D2\u4EF6\u4F5C\u8005\uFF1B\u6DF1\u6316\u770B\u65E5\u5FD7\u901A\u9053\u3002"
424
+ title: copyText("panel.banner.error.title", l, { code, detail: copy?.zh ?? code }),
425
+ action: copy?.act || copyText("panel.banner.error.action-fallback", l)
445
426
  },
446
427
  installEnabled: snapshot.canInstall,
447
- installLabel: "\u91CD\u8BD5\u5B89\u88C5",
428
+ installLabel: copyText("panel.action.retry-install", l),
448
429
  skippedLatest: false,
449
430
  showManual: manual ? true : false,
450
431
  showReset: false,
@@ -452,15 +433,16 @@ function panelViewModelCore(input) {
452
433
  };
453
434
  }
454
435
  if (snapshot.blockedReason) {
455
- const copy = blockedCopy(snapshot.blockedReason);
436
+ const copy = blockedCopy(snapshot.blockedReason, l);
437
+ const stop = l === "en" ? "." : "\u3002";
456
438
  return {
457
439
  banner: {
458
440
  kind: "blocked",
459
- title: copy ? `${copy.title}\u3002` : `${snapshot.blockedReason}\u3002`,
441
+ title: copy ? `${copy.title}${stop}` : `${snapshot.blockedReason}${stop}`,
460
442
  action: copy?.action || ""
461
443
  },
462
444
  installEnabled: false,
463
- installLabel: "\u5B89\u88C5\u66F4\u65B0",
445
+ installLabel: copyText("panel.action.install", l),
464
446
  skippedLatest: false,
465
447
  showManual: manual ? true : false,
466
448
  showReset: false,
@@ -471,11 +453,11 @@ function panelViewModelCore(input) {
471
453
  return {
472
454
  banner: {
473
455
  kind: "update",
474
- title: `\u6709\u65B0\u7248 ${snapshot.latestVersion} \u53EF\u88C5\uFF08\u5F53\u524D ${snapshot.runningVersion}\uFF09\u3002`,
475
- action: "\u70B9\u5B89\u88C5\u5373\u8D70\u7CBE\u786E\u7248\u672C\u5B89\u88C5\uFF1B\u540C\u4E00\u4F7F\u7528\u8303\u56F4\u540C\u65F6\u53EA\u88C5\u4E00\u4E2A\u3002"
456
+ title: copyText("panel.banner.update-title", l, { latest: snapshot.latestVersion, running: snapshot.runningVersion }),
457
+ action: copyText("panel.banner.update-action", l)
476
458
  },
477
459
  installEnabled: true,
478
- installLabel: `\u5B89\u88C5 ${snapshot.latestVersion}`,
460
+ installLabel: copyText("panel.action.install-version", l, { latest: snapshot.latestVersion }),
479
461
  skippedLatest: false,
480
462
  showManual: manual ? true : false,
481
463
  showReset: false,
@@ -483,9 +465,9 @@ function panelViewModelCore(input) {
483
465
  };
484
466
  }
485
467
  return {
486
- banner: { kind: "done", title: "\u5DF2\u662F\u6700\u65B0\uFF0C\u65E0\u9700\u66F4\u65B0\u3002", action: "" },
468
+ banner: { kind: "done", title: copyText("panel.banner.done", l), action: "" },
487
469
  installEnabled: false,
488
- installLabel: "\u5B89\u88C5\u66F4\u65B0",
470
+ installLabel: copyText("panel.action.install", l),
489
471
  skippedLatest: false,
490
472
  showManual: false,
491
473
  showReset: false,
@@ -493,23 +475,23 @@ function panelViewModelCore(input) {
493
475
  };
494
476
  }
495
477
  const UPDATE_PANEL_CSS = [
496
- '.dsh-upd{font:14px/1.6 system-ui,"Microsoft YaHei",sans-serif;color:var(--dsh-upd-fg,#1f2937);',
497
- "background:var(--dsh-upd-bg,#ffffff);border:1px solid var(--dsh-upd-line,#e5e7eb);border-radius:8px;padding:12px 14px;max-width:560px;",
478
+ '.dsh-upd{font:14px/1.6 system-ui,"Microsoft YaHei",sans-serif;color:var(--dsh-update-text,#1f2937);',
479
+ "background:var(--dsh-update-bg,#ffffff);border:1px solid var(--dsh-update-border,#e5e7eb);border-radius:8px;padding:12px 14px;max-width:560px;",
498
480
  // 横幅配色走变量(浅色默认 + 深色覆盖,见下方 dark 媒体块):硬编码浅色会让深色下
499
481
  // 「浅底 + 浅字」读不出来(现场回归:默认主题深色模式更新横幅白底浅字)。
500
- "--dsh-upd-ok-bg:#ecfdf5;--dsh-upd-ok-line:#059669;--dsh-upd-warn-bg:#fffbeb;--dsh-upd-warn-line:#d97706;",
501
- "--dsh-upd-bad-bg:#fef2f2;--dsh-upd-bad-line:#dc2626;--dsh-upd-busy-bg:#eff6ff;--dsh-upd-busy-line:#2563eb}",
482
+ "--dsh-update-ok-bg:#ecfdf5;--dsh-update-ok-border:#059669;--dsh-update-warn-bg:#fffbeb;--dsh-update-warn-border:#d97706;",
483
+ "--dsh-update-bad-bg:#fef2f2;--dsh-update-bad-border:#dc2626;--dsh-update-busy-bg:#eff6ff;--dsh-update-busy-border:#2563eb}",
502
484
  ".dsh-upd *{box-sizing:border-box}",
503
- ".dsh-upd button{font:inherit;border:1px solid var(--dsh-upd-line,#d1d5db);border-radius:6px;background:var(--dsh-upd-btn,#f9fafb);",
485
+ ".dsh-upd button{font:inherit;border:1px solid var(--dsh-update-border-strong,#d1d5db);border-radius:var(--dsh-update-radius-button,6px);background:var(--dsh-update-button-bg,#f9fafb);",
504
486
  "color:inherit;padding:4px 12px;cursor:pointer;margin:2px 6px 2px 0}",
505
487
  ".dsh-upd button:disabled{opacity:.45;cursor:not-allowed}",
506
- ".dsh-upd button:focus-visible{outline:2px solid var(--dsh-upd-focus,#2563eb);outline-offset:1px}",
507
- '.dsh-upd button[data-primary="1"]{background:var(--dsh-upd-primary,#2563eb);border-color:var(--dsh-upd-primary,#2563eb);color:#fff}',
508
- ".dsh-upd-banner{border-left:4px solid var(--dsh-upd-line,#9ca3af);padding:6px 10px;margin:0 0 8px;background:var(--dsh-upd-soft,#f3f4f6)}",
509
- '.dsh-upd-banner[data-kind="restart"]{border-color:var(--dsh-upd-warn-line);background:var(--dsh-upd-warn-bg)}',
510
- '.dsh-upd-banner[data-kind="failed"],.dsh-upd-banner[data-kind="blocked"]{border-color:var(--dsh-upd-bad-line);background:var(--dsh-upd-bad-bg)}',
511
- '.dsh-upd-banner[data-kind="update"]{border-color:var(--dsh-upd-ok-line);background:var(--dsh-upd-ok-bg)}',
512
- '.dsh-upd-banner[data-kind="busy"]{border-color:var(--dsh-upd-busy-line);background:var(--dsh-upd-busy-bg)}',
488
+ ".dsh-upd button:focus-visible{outline:2px solid var(--dsh-update-focus,#2563eb);outline-offset:1px}",
489
+ '.dsh-upd button[data-primary="1"]{background:var(--dsh-update-primary,#2563eb);border-color:var(--dsh-update-primary,#2563eb);color:#fff}',
490
+ ".dsh-upd-banner{border-left:4px solid var(--dsh-update-border,#9ca3af);padding:6px 10px;margin:0 0 8px;background:var(--dsh-update-bg-soft,#f3f4f6)}",
491
+ '.dsh-upd-banner[data-kind="restart"]{border-color:var(--dsh-update-warn-border);background:var(--dsh-update-warn-bg)}',
492
+ '.dsh-upd-banner[data-kind="failed"],.dsh-upd-banner[data-kind="blocked"]{border-color:var(--dsh-update-bad-border);background:var(--dsh-update-bad-bg)}',
493
+ '.dsh-upd-banner[data-kind="update"]{border-color:var(--dsh-update-ok-border);background:var(--dsh-update-ok-bg)}',
494
+ '.dsh-upd-banner[data-kind="busy"]{border-color:var(--dsh-update-busy-border);background:var(--dsh-update-busy-bg)}',
513
495
  ".dsh-upd code{font-family:Consolas,Menlo,monospace;font-size:12px;word-break:break-all}",
514
496
  ".dsh-upd-manual,.dsh-upd-queue,.dsh-upd-log{margin:4px 0;font-size:13px}",
515
497
  ".dsh-upd-changelog-wrap{margin:4px 0 0;font-size:13px}",
@@ -521,71 +503,71 @@ const UPDATE_PANEL_CSS = [
521
503
  ".dsh-upd-changelog-fold{margin:4px 0}",
522
504
  ".dsh-upd-changelog-fold>summary{cursor:pointer}",
523
505
  ".dsh-upd-changelog-count{font-size:12px;opacity:.7;margin:2px 0 4px}",
524
- ".dsh-upd-changelog-yanked{border-left:4px solid var(--dsh-upd-warn-line,#d97706);background:var(--dsh-upd-warn-bg,#fffbeb);padding:6px 10px;margin:6px 0;font-size:13px}",
506
+ ".dsh-upd-changelog-yanked{border-left:4px solid var(--dsh-update-warn-border,#d97706);background:var(--dsh-update-warn-bg,#fffbeb);padding:6px 10px;margin:6px 0;font-size:13px}",
525
507
  ".dsh-upd-breaking-badge{display:inline-block;font-size:11px;font-weight:700;border:1px solid currentColor;border-radius:3px;padding:0 5px;margin-right:6px;vertical-align:baseline}",
526
508
  ".dsh-upd-changelog-security-more{margin:4px 0 6px}",
527
509
  ".dsh-upd-changelog-security-more>summary{cursor:pointer;font-size:12px;opacity:.8}",
528
510
  ".dsh-upd-changelog-more-note{font-size:12px;opacity:.7;margin:2px 0 4px}",
529
511
  ".dsh-upd-changelog-neutral{color:inherit;opacity:.8}",
530
512
  ".dsh-upd-overlay{position:fixed;inset:0;background:rgba(0,0,0,.35);display:flex;align-items:center;justify-content:center;z-index:9999}",
531
- ".dsh-upd-overlay .dsh-upd{background:var(--dsh-upd-bg,#ffffff);max-height:85vh;display:flex;flex-direction:column;overflow:hidden}",
513
+ ".dsh-upd-overlay .dsh-upd{background:var(--dsh-update-bg,#ffffff);max-height:85vh;display:flex;flex-direction:column;overflow:hidden}",
532
514
  // —— 弹窗分栏滚动:头(抬头/档案头/横幅/版本条)与尾固定,只有 01–05 章节区滚动 ——
533
515
  ".dsh-upd-body{min-height:0}",
534
- ".dsh-upd-overlay .dsh-upd-body{flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--dsh-upd-line,#e5e7eb) transparent}",
516
+ ".dsh-upd-overlay .dsh-upd-body{flex:1 1 auto;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--dsh-update-border,#e5e7eb) transparent}",
535
517
  ".dsh-upd-overlay .dsh-upd-body::-webkit-scrollbar{width:8px}",
536
- ".dsh-upd-overlay .dsh-upd-body::-webkit-scrollbar-thumb{background:var(--dsh-upd-line,#e5e7eb);border-radius:4px}",
518
+ ".dsh-upd-overlay .dsh-upd-body::-webkit-scrollbar-thumb{background:var(--dsh-update-border,#e5e7eb);border-radius:4px}",
537
519
  ".dsh-upd-overlay .dsh-upd-body::-webkit-scrollbar-track{background:transparent}",
538
- // —— 章节标题磁吸:滚动时节标题贴顶(纯 CSS sticky;背景跟随主题,D5 另覆)——
539
- ".dsh-upd-overlay .dsh-upd-body .dsh-upd-chap-head{position:sticky;top:0;z-index:1;background:var(--dsh-upd-bg,#ffffff);padding-top:2px}",
520
+ // —— 章节标题磁吸:滚动时节标题贴顶(纯 CSS sticky;背景跟随主题,Archive 另覆)——
521
+ ".dsh-upd-overlay .dsh-upd-body .dsh-upd-chap-head{position:sticky;top:0;z-index:1;background:var(--dsh-update-bg,#ffffff);padding-top:2px}",
540
522
  // —— 档案头 / 版本条 / 章节 / 进度条 / 跳过行(原型 :208-245 的新结构,默认主题给最小可用样式)——
541
523
  ".dsh-upd-head{display:flex;gap:12px;align-items:baseline;flex-wrap:wrap}",
542
- // 卷宗抬头「插件更新 / 更新档案 卷」:D5 档案卷才画,默认(最小)主题不画。
524
+ // 卷宗抬头「插件更新 / 更新档案 卷」:Archive 档案卷才画,默认(最小)主题不画。
543
525
  // 两个主题共用同一份内核 HTML(见 renderUpdatePanelHTML 的注释),画不画是皮肤决定的事。
544
526
  ".dsh-upd-masthead{display:none}",
545
527
  ".dsh-upd-name{font-weight:700}",
546
528
  ".dsh-upd-meta{font-size:12.5px;opacity:.75}",
547
- ".dsh-upd-proftag{font-family:Consolas,Menlo,monospace;font-size:11px;border:1px solid var(--dsh-upd-line,#d1d5db);border-radius:3px;padding:0 5px;margin-left:6px;letter-spacing:.06em}",
548
- ".dsh-upd-strip{display:flex;flex-wrap:wrap;margin:8px 0 0;border:1px solid var(--dsh-upd-line,#e5e7eb);border-radius:4px;overflow:hidden;font-size:12.5px}",
549
- ".dsh-upd-strip>div{flex:1 1 110px;padding:6px 10px;border-left:1px solid var(--dsh-upd-line,#e5e7eb)}",
529
+ ".dsh-upd-proftag{font-family:Consolas,Menlo,monospace;font-size:11px;border:1px solid var(--dsh-update-border,#d1d5db);border-radius:3px;padding:0 5px;margin-left:6px;letter-spacing:.06em}",
530
+ ".dsh-upd-strip{display:flex;flex-wrap:wrap;margin:8px 0 0;border:1px solid var(--dsh-update-border,#e5e7eb);border-radius:4px;overflow:hidden;font-size:12.5px}",
531
+ ".dsh-upd-strip>div{flex:1 1 110px;padding:6px 10px;border-left:1px solid var(--dsh-update-border,#e5e7eb)}",
550
532
  ".dsh-upd-strip>div:first-child{border-left:0}",
551
533
  ".dsh-upd-strip-k{display:block;font-size:11px;letter-spacing:.14em;opacity:.7}",
552
534
  ".dsh-upd-strip-v{font-family:Consolas,Menlo,monospace;font-size:12.5px}",
553
- ".dsh-upd-chapter{margin-top:14px;padding-top:10px;border-top:1px solid var(--dsh-upd-line,#e5e7eb)}",
535
+ ".dsh-upd-chapter{margin-top:14px;padding-top:10px;border-top:1px solid var(--dsh-update-border,#e5e7eb)}",
554
536
  // 右下角独立 footer 区(#47 定案 A):与第一章 actions 脱钩,右对齐,一次找到。
555
- ".dsh-upd-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-top:14px;padding-top:10px;border-top:1px solid var(--dsh-upd-line,#e5e7eb)}",
537
+ ".dsh-upd-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-top:14px;padding-top:10px;border-top:1px solid var(--dsh-update-border,#e5e7eb)}",
556
538
  ".dsh-upd-foot-note{margin-right:auto;font-size:12px;opacity:.7}",
557
539
  ".dsh-upd-footer button{margin:0}",
558
540
  ".dsh-upd-chap-head{display:flex;align-items:baseline;gap:10px;margin-bottom:6px}",
559
541
  ".dsh-upd-chap-no{font-size:13px;font-style:italic;opacity:.6}",
560
542
  ".dsh-upd-chap-title{font-size:14px;margin:0}",
561
- ".dsh-upd-chap-rule{flex:1;border-top:1px solid var(--dsh-upd-line,#e5e7eb);transform:translateY(-3px)}",
543
+ ".dsh-upd-chap-rule{flex:1;border-top:1px solid var(--dsh-update-border,#e5e7eb);transform:translateY(-3px)}",
562
544
  // 03 章标题行右端的开关(问题 3 定案:按钮形态、挪到标题行)。
563
545
  ".dsh-upd-chap-note{flex:none;font-size:12px;opacity:.75}",
564
546
  ".dsh-upd-chap-note button{margin:0}",
565
547
  // 更新队列两行键值(用户定案的设计):结构两主题共用,皮肤各自收敛。
566
548
  ".dsh-upd-qrow{display:flex;align-items:baseline;gap:10px;padding:6px 0}",
567
- ".dsh-upd-qrow+.dsh-upd-qrow{border-top:1px solid var(--dsh-upd-line,#e5e7eb)}",
549
+ ".dsh-upd-qrow+.dsh-upd-qrow{border-top:1px solid var(--dsh-update-border,#e5e7eb)}",
568
550
  ".dsh-upd-qdot{width:8px;height:8px;border-radius:50%;flex:none;align-self:center;background:currentColor;opacity:.5}",
569
- '.dsh-upd-qdot[data-tone="busy"]{background:var(--dsh-upd-warn-line,#d97706);opacity:1}',
570
- '.dsh-upd-qdot[data-tone="you"]{background:var(--dsh-upd-primary,#2563eb);opacity:1}',
551
+ '.dsh-upd-qdot[data-tone="busy"]{background:var(--dsh-update-warn-border,#d97706);opacity:1}',
552
+ '.dsh-upd-qdot[data-tone="you"]{background:var(--dsh-update-primary,#2563eb);opacity:1}',
571
553
  ".dsh-upd-qk{flex:none;width:5.5em;font-size:12px;opacity:.7}",
572
554
  ".dsh-upd-qv{font-weight:600}",
573
555
  ".dsh-upd-qn{margin-left:auto;font-size:12px;opacity:.7}",
574
556
  ".dsh-upd-qseq{font-family:Consolas,Menlo,monospace;font-size:12px;word-break:break-all}",
575
- ".dsh-upd-prog{height:8px;background:var(--dsh-upd-line,#e5e7eb);border-radius:4px;overflow:hidden;margin:10px 0 4px}",
576
- ".dsh-upd-prog-bar{display:block;height:100%;background:var(--dsh-upd-primary,#2563eb);transition:width .3s}",
557
+ ".dsh-upd-prog{height:8px;background:var(--dsh-update-border,#e5e7eb);border-radius:4px;overflow:hidden;margin:10px 0 4px}",
558
+ ".dsh-upd-prog-bar{display:block;height:100%;background:var(--dsh-update-primary,#2563eb);transition:width .3s}",
577
559
  ".dsh-upd-progtxt{font-size:12.5px;opacity:.75}",
578
560
  ".dsh-upd-skipline{font-size:13px;margin-top:8px}",
579
561
  // 骨架微光:只在首帧 loading 出现;reduced-motion 下静止占位,不断语义。
580
562
  ".dsh-upd-skv{display:inline-block;min-width:64px;border-radius:3px;color:transparent !important;user-select:none;",
581
- "background:linear-gradient(90deg,var(--dsh-upd-line,#e5e7eb) 25%,var(--dsh-upd-soft,#f3f4f6) 50%,var(--dsh-upd-line,#e5e7eb) 75%);",
563
+ "background:linear-gradient(90deg,var(--dsh-update-border,#e5e7eb) 25%,var(--dsh-update-bg-soft,#f3f4f6) 50%,var(--dsh-update-border,#e5e7eb) 75%);",
582
564
  "background-size:200% 100%;animation:dsh-upd-shimmer 1.2s linear infinite}",
583
565
  "@keyframes dsh-upd-shimmer{to{background-position:-200% 0}}",
584
566
  ".dsh-upd-tag{display:inline-block;border:1px dashed currentColor;border-radius:3px;padding:1px 8px;margin-right:8px;font-family:Consolas,Menlo,monospace;font-size:12px}",
585
567
  ".dsh-upd-err{font-size:13px;margin:0 0 6px}",
586
568
  // —— 全按钮交互反馈(#36:悬停/按下/过渡/在途忙态;浅深双主题通用写法,不碰上面的既有串)——
587
569
  ".dsh-upd button{transition:background-color .15s ease,border-color .15s ease,color .15s ease,transform .06s ease}",
588
- ".dsh-upd button:hover:not(:disabled){border-color:var(--dsh-upd-focus,#2563eb)}",
570
+ ".dsh-upd button:hover:not(:disabled){border-color:var(--dsh-update-focus,#2563eb)}",
589
571
  '.dsh-upd button[data-primary="1"]:hover:not(:disabled){filter:brightness(.93)}',
590
572
  ".dsh-upd button:active:not(:disabled){transform:translateY(1px)}",
591
573
  '.dsh-upd button[aria-busy="true"]{cursor:wait;animation:dsh-upd-pulse 1s ease-in-out infinite}',
@@ -602,72 +584,81 @@ const UPDATE_PANEL_CSS = [
602
584
  "@keyframes dsh-upd-spin{to{transform:rotate(360deg)}}",
603
585
  '@media (prefers-reduced-motion: reduce){.dsh-upd button{transition:none}.dsh-upd button:active:not(:disabled){transform:none}.dsh-upd button[aria-busy="true"]{animation:none}.dsh-upd-skv{animation:none}.dsh-upd-banner{animation:none}.dsh-upd-changelog-foldbox{transition:none}}',
604
586
  "@media (forced-colors: active){.dsh-upd-overlay .dsh-upd-body .dsh-upd-chap-head{background:Canvas}}",
605
- "@media (prefers-color-scheme: dark){.dsh-upd{--dsh-upd-fg:#e5e7eb;--dsh-upd-bg:#111827;--dsh-upd-line:#374151;",
606
- "--dsh-upd-btn:#1f2937;--dsh-upd-soft:#1f2937;--dsh-upd-primary:#3b82f6;--dsh-upd-focus:#93c5fd;",
587
+ // —— 查新版布局稳定:按钮预留宽度 + 动态区最小高度 + 锚定不漂(只稳布局,不改文案语义)——
588
+ ".dsh-upd{overflow-anchor:none}",
589
+ ".dsh-upd-body{overflow-anchor:none}",
590
+ ".dsh-upd-actions{display:flex;flex-wrap:wrap;align-items:center;min-height:34px}",
591
+ ".dsh-upd-actions button:first-child{min-width:8em;text-align:center}",
592
+ '.dsh-upd-actions button[data-primary="1"]{min-width:7em;text-align:center}',
593
+ '.dsh-upd-actions button:first-child:not([aria-busy="true"])::after{content:"";display:inline-block;width:11px;height:11px;margin-left:8px;visibility:hidden}',
594
+ ".dsh-upd-banner{min-height:1.2em}",
595
+ ".dsh-upd-strip{min-height:48px}",
596
+ "@media (prefers-color-scheme: dark){.dsh-upd{--dsh-update-text:#e5e7eb;--dsh-update-bg:#111827;--dsh-update-border:#374151;",
597
+ "--dsh-update-button-bg:#1f2937;--dsh-update-bg-soft:#1f2937;--dsh-update-primary:#3b82f6;--dsh-update-focus:#93c5fd;",
607
598
  // 横幅深色覆盖:底色用低透明度同色系(不是浅色原值),边线提亮,保证「深底浅字」可读。
608
- "--dsh-upd-ok-bg:rgba(16,185,129,.14);--dsh-upd-ok-line:#34d399;",
609
- "--dsh-upd-warn-bg:rgba(245,158,11,.16);--dsh-upd-warn-line:#fbbf24;",
610
- "--dsh-upd-bad-bg:rgba(239,68,68,.16);--dsh-upd-bad-line:#f87171;",
611
- "--dsh-upd-busy-bg:rgba(59,130,246,.16);--dsh-upd-busy-line:#60a5fa}}"
599
+ "--dsh-update-ok-bg:rgba(16,185,129,.14);--dsh-update-ok-border:#34d399;",
600
+ "--dsh-update-warn-bg:rgba(245,158,11,.16);--dsh-update-warn-border:#fbbf24;",
601
+ "--dsh-update-bad-bg:rgba(239,68,68,.16);--dsh-update-bad-border:#f87171;",
602
+ "--dsh-update-busy-bg:rgba(59,130,246,.16);--dsh-update-busy-border:#60a5fa}}"
612
603
  ].join("\n");
613
- const UPDATE_PANEL_D5_CSS = [
614
- "/* D5 \u6863\u6848\u5377\u53EF\u9009\u4E3B\u9898\uFF1A\u53EA\u6362\u989C\u8272/\u5B57\u4F53/\u95F4\u8DDD\uFF1B\u5185\u6838 DOM \u987A\u5E8F\u4E00\u5B57\u4E0D\u52A8\uFF0C\u4E0D\u65AD\u590D\u5236\u8BCA\u65AD\u3002 */",
615
- '.dsh-upd[data-theme="archive"]{--d5-bg:#f7f3ea;--d5-card:#fffdf6;--d5-ink:#1a1a1a;--d5-muted:#6f675a;',
616
- "--d5-line:#e3d9c4;--d5-line-strong:#c4b896;--d5-accent:#c8402a;--d5-accent-deep:#9c2e1d;",
617
- "--d5-ok:#1a7f37;--d5-ok-bg:#e9f4ea;--d5-warn:#8a5a00;--d5-warn-bg:#fbf0d0;",
618
- "--d5-bad:#b3261e;--d5-bad-bg:#fbe9e5;",
619
- '--d5-serif:Georgia,"Songti SC","STSong","SimSun","Noto Serif CJK SC","Source Han Serif SC",serif;',
620
- '--d5-sans:system-ui,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;',
621
- '--d5-mono:ui-monospace,"SF Mono",SFMono-Regular,Consolas,"Noto Sans Mono",monospace;',
622
- "--d5-shadow:0 1px 2px rgba(60,40,20,.08),0 12px 32px rgba(60,40,20,.10);",
623
- "font-family:var(--d5-sans);color:var(--d5-ink);background:var(--d5-card);",
624
- "border:1px solid var(--d5-line-strong);border-radius:4px;box-shadow:var(--d5-shadow)}",
604
+ const UPDATE_PANEL_ARCHIVE_CSS = [
605
+ "/* Archive \u6863\u6848\u5377\u53EF\u9009\u4E3B\u9898\uFF1A\u53EA\u6362\u989C\u8272/\u5B57\u4F53/\u95F4\u8DDD\uFF1B\u5185\u6838 DOM \u987A\u5E8F\u4E00\u5B57\u4E0D\u52A8\uFF0C\u4E0D\u65AD\u590D\u5236\u8BCA\u65AD\u3002 */",
606
+ '.dsh-upd[data-theme="archive"]{--dsh-update-bg-soft:#f7f3ea;--dsh-update-bg:#fffdf6;--dsh-update-text:#1a1a1a;--dsh-update-text-muted:#6f675a;',
607
+ "--dsh-update-border:#e3d9c4;--dsh-update-border-strong:#c4b896;--dsh-update-primary:#c8402a;--dsh-update-primary-deep:#9c2e1d;",
608
+ "--dsh-update-ok-text:#1a7f37;--dsh-update-ok-bg:#e9f4ea;--dsh-update-warn-text:#8a5a00;--dsh-update-warn-bg:#fbf0d0;",
609
+ "--dsh-update-bad-text:#b3261e;--dsh-update-bad-bg:#fbe9e5;",
610
+ '--dsh-update-font-serif:Georgia,"Songti SC","STSong","SimSun","Noto Serif CJK SC","Source Han Serif SC",serif;',
611
+ '--dsh-update-font-sans:system-ui,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;',
612
+ '--dsh-update-font-mono:ui-monospace,"SF Mono",SFMono-Regular,Consolas,"Noto Sans Mono",monospace;',
613
+ "--dsh-update-shadow:0 1px 2px rgba(60,40,20,.08),0 12px 32px rgba(60,40,20,.10);",
614
+ "font-family:var(--dsh-update-font-sans);color:var(--dsh-update-text);background:var(--dsh-update-bg);",
615
+ "border:1px solid var(--dsh-update-border-strong);border-radius:var(--dsh-update-radius-panel,4px);box-shadow:var(--dsh-update-shadow)}",
625
616
  // 按钮脸自己不透明(宿主底色未知时也读得出;卡片上渲染与 transparent 逐字同色)。
626
- '.dsh-upd[data-theme="archive"] button{border-color:var(--d5-line-strong);background:var(--d5-card);color:var(--d5-ink);border-radius:3px;font-family:var(--d5-sans)}',
627
- '.dsh-upd[data-theme="archive"] button:hover:not(:disabled){border-color:var(--d5-accent);color:var(--d5-accent)}',
628
- '.dsh-upd[data-theme="archive"] button[data-primary="1"]{background:var(--d5-accent);border-color:var(--d5-accent);color:#fff}',
629
- '.dsh-upd[data-theme="archive"] button[data-primary="1"]:hover:not(:disabled){background:var(--d5-accent-deep);color:#fff}',
630
- '.dsh-upd[data-theme="archive"] button:focus-visible{outline:2px solid var(--d5-accent);outline-offset:2px}',
631
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner{background:var(--d5-bg);border-color:var(--d5-line-strong)}',
632
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="update"]{border-color:var(--d5-ok);background:var(--d5-ok-bg)}',
633
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="busy"]{border-color:var(--d5-warn);background:var(--d5-warn-bg)}',
634
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="restart"]{border-color:var(--d5-warn);background:var(--d5-warn-bg);font-family:var(--d5-serif);border-width:2px}',
635
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="failed"],.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="blocked"]{border-color:var(--d5-bad);background:var(--d5-bad-bg)}',
636
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="done"]{border-color:var(--d5-ok);background:var(--d5-ok-bg)}',
637
- '.dsh-upd[data-theme="archive"] .dsh-upd-changelog-yanked{border-color:var(--d5-warn);background:var(--d5-warn-bg);color:var(--d5-ink)}',
638
- '.dsh-upd[data-theme="archive"] .dsh-upd-breaking-badge{color:var(--d5-accent);border-color:var(--d5-accent)}',
639
- '.dsh-upd[data-theme="archive"] .dsh-upd-changelog-count{color:var(--d5-muted)}',
640
- '.dsh-upd[data-theme="archive"] .dsh-upd-changelog-more-note{color:var(--d5-muted)}',
617
+ '.dsh-upd[data-theme="archive"] button{border-color:var(--dsh-update-border-strong);background:var(--dsh-update-button-bg);color:var(--dsh-update-text);border-radius:var(--dsh-update-radius-button,3px);font-family:var(--dsh-update-font-sans)}',
618
+ '.dsh-upd[data-theme="archive"] button:hover:not(:disabled){border-color:var(--dsh-update-primary);color:var(--dsh-update-primary)}',
619
+ '.dsh-upd[data-theme="archive"] button[data-primary="1"]{background:var(--dsh-update-primary);border-color:var(--dsh-update-primary);color:#fff}',
620
+ '.dsh-upd[data-theme="archive"] button[data-primary="1"]:hover:not(:disabled){background:var(--dsh-update-primary-deep);color:#fff}',
621
+ '.dsh-upd[data-theme="archive"] button:focus-visible{outline:2px solid var(--dsh-update-primary);outline-offset:2px}',
622
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner{background:var(--dsh-update-bg-soft);border-color:var(--dsh-update-border-strong)}',
623
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="update"]{border-color:var(--dsh-update-ok-border);background:var(--dsh-update-ok-bg)}',
624
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="busy"]{border-color:var(--dsh-update-busy-border);background:var(--dsh-update-busy-bg)}',
625
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="restart"]{border-color:var(--dsh-update-warn-border);background:var(--dsh-update-warn-bg);font-family:var(--dsh-update-font-serif);border-width:2px}',
626
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="failed"],.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="blocked"]{border-color:var(--dsh-update-bad-border);background:var(--dsh-update-bad-bg)}',
627
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="done"]{border-color:var(--dsh-update-ok-border);background:var(--dsh-update-ok-bg)}',
628
+ '.dsh-upd[data-theme="archive"] .dsh-upd-changelog-yanked{border-color:var(--dsh-update-warn-text);background:var(--dsh-update-warn-bg);color:var(--dsh-update-text)}',
629
+ '.dsh-upd[data-theme="archive"] .dsh-upd-breaking-badge{color:var(--dsh-update-primary);border-color:var(--dsh-update-primary)}',
630
+ '.dsh-upd[data-theme="archive"] .dsh-upd-changelog-count{color:var(--dsh-update-text-muted)}',
631
+ '.dsh-upd[data-theme="archive"] .dsh-upd-changelog-more-note{color:var(--dsh-update-text-muted)}',
641
632
  // —— 大印章(原型 :206 `.seal`:右上 88px、旋转 -7°、双细框;内容与色调来自根属性,不加节点)——
642
633
  '.dsh-upd[data-theme="archive"]{position:relative;padding:16px 20px 14px}',
643
634
  '.dsh-upd[data-theme="archive"]::before{content:attr(data-seal);position:absolute;top:20px;right:24px;width:88px;height:88px;',
644
635
  "display:flex;align-items:center;justify-content:center;text-align:center;letter-spacing:.18em;text-indent:.18em;line-height:1.35;",
645
- "border:3px solid currentColor;border-radius:14px;transform:rotate(-7deg);font-family:var(--d5-serif);font-weight:700;font-size:21px;",
636
+ "border:3px solid currentColor;border-radius:14px;transform:rotate(-7deg);font-family:var(--dsh-update-font-serif);font-weight:700;font-size:21px;",
646
637
  "background:color-mix(in srgb,currentColor 8%,transparent);user-select:none;pointer-events:none;",
647
- "box-shadow:inset 0 0 0 5px var(--d5-card),inset 0 0 0 6px currentColor,0 2px 6px rgba(0,0,0,.12)}",
648
- '.dsh-upd[data-theme="archive"][data-seal-tone="ink"]::before{color:var(--d5-muted)}',
649
- '.dsh-upd[data-theme="archive"][data-seal-tone="green"]::before{color:var(--d5-ok)}',
650
- '.dsh-upd[data-theme="archive"][data-seal-tone="yellow"]::before{color:var(--d5-warn)}',
651
- '.dsh-upd[data-theme="archive"][data-seal-tone="red"]::before{color:var(--d5-bad)}',
638
+ "box-shadow:inset 0 0 0 5px var(--dsh-update-bg),inset 0 0 0 6px currentColor,0 2px 6px rgba(0,0,0,.12)}",
639
+ '.dsh-upd[data-theme="archive"][data-seal-tone="ink"]::before{color:var(--dsh-update-text-muted)}',
640
+ '.dsh-upd[data-theme="archive"][data-seal-tone="green"]::before{color:var(--dsh-update-ok-text)}',
641
+ '.dsh-upd[data-theme="archive"][data-seal-tone="yellow"]::before{color:var(--dsh-update-warn-text)}',
642
+ '.dsh-upd[data-theme="archive"][data-seal-tone="red"]::before{color:var(--dsh-update-bad-text)}',
652
643
  // 印章占位:首行(横幅/状态行)右侧留出 120px,文字不许压到印章上(原型 .filehead padding-right:120px)
653
- // —— 卷宗抬头(原型 :195-203 的刊头,主题切换按钮按用户口径去掉):只有 D5 档案卷才显示 ——
654
- '.dsh-upd[data-theme="archive"] .dsh-upd-masthead{display:block;padding:0 0 8px;margin:0 0 8px;border-bottom:1px solid var(--d5-line)}',
655
- '.dsh-upd[data-theme="archive"] .dsh-upd-masthead-kicker{display:block;font-size:11px;letter-spacing:.35em;color:var(--d5-muted);margin-bottom:3px}',
656
- '.dsh-upd[data-theme="archive"] .dsh-upd-masthead-title{font-family:var(--d5-serif);font-size:26px;font-weight:700;line-height:1.2}',
657
- '.dsh-upd[data-theme="archive"] .dsh-upd-masthead-title i{color:var(--d5-accent);font-style:normal}',
644
+ // —— 卷宗抬头(原型 :195-203 的刊头,主题切换按钮按用户口径去掉):只有 Archive 档案卷才显示 ——
645
+ '.dsh-upd[data-theme="archive"] .dsh-upd-masthead{display:block;padding:0 0 8px;margin:0 0 8px;border-bottom:1px solid var(--dsh-update-border)}',
646
+ '.dsh-upd[data-theme="archive"] .dsh-upd-masthead-kicker{display:block;font-size:11px;letter-spacing:.35em;color:var(--dsh-update-text-muted);margin-bottom:3px}',
647
+ '.dsh-upd[data-theme="archive"] .dsh-upd-masthead-title{font-family:var(--dsh-update-font-serif);font-size:26px;font-weight:700;line-height:1.2}',
648
+ '.dsh-upd[data-theme="archive"] .dsh-upd-masthead-title i{color:var(--dsh-update-primary);font-style:normal}',
658
649
  // 横幅不再给大印章留 124px:实测(headless 量盒子)印章盒底边 y=109,横幅正文顶边 y=108、
659
650
  // 状态行那句在 y=159——印章只压到横幅顶部的留白带,压不到正文。留着反而把 27px 那句话挤成两行
660
651
  // (27px 单行需 428px,留白后只剩 366px)。档案头那 120px 保留:那里是真的重叠。
661
- // —— 更新队列(03 章)D5 皮肤 ——
652
+ // —— 更新队列(03 章)Archive 皮肤 ——
662
653
  '.dsh-upd[data-theme="archive"] .dsh-upd-qrow{padding:6px 0}',
663
654
  '.dsh-upd[data-theme="archive"] .dsh-upd-qk{width:66px;letter-spacing:.18em}',
664
- '.dsh-upd[data-theme="archive"] .dsh-upd-qv{font-family:var(--d5-serif);font-size:16px}',
665
- '.dsh-upd[data-theme="archive"] .dsh-upd-qn{font-family:var(--d5-mono);font-size:11.5px;color:var(--d5-muted)}',
666
- '.dsh-upd[data-theme="archive"] .dsh-upd-qseq{color:var(--d5-muted)}',
667
- '.dsh-upd[data-theme="archive"] .dsh-upd-chap-note{color:var(--d5-muted)}',
668
- '.dsh-upd[data-theme="archive"] .dsh-upd-body .dsh-upd-chap-head{background:var(--d5-card)}',
669
- '.dsh-upd-overlay .dsh-upd[data-theme="archive"] .dsh-upd-body{scrollbar-color:var(--d5-line-strong) transparent}',
670
- '.dsh-upd-overlay .dsh-upd[data-theme="archive"] .dsh-upd-body::-webkit-scrollbar-thumb{background:var(--d5-line-strong)}',
655
+ '.dsh-upd[data-theme="archive"] .dsh-upd-qv{font-family:var(--dsh-update-font-serif);font-size:16px}',
656
+ '.dsh-upd[data-theme="archive"] .dsh-upd-qn{font-family:var(--dsh-update-font-mono);font-size:11.5px;color:var(--dsh-update-text-muted)}',
657
+ '.dsh-upd[data-theme="archive"] .dsh-upd-qseq{color:var(--dsh-update-text-muted)}',
658
+ '.dsh-upd[data-theme="archive"] .dsh-upd-chap-note{color:var(--dsh-update-text-muted)}',
659
+ '.dsh-upd[data-theme="archive"] .dsh-upd-body .dsh-upd-chap-head{background:var(--dsh-update-bg)}',
660
+ '.dsh-upd-overlay .dsh-upd[data-theme="archive"] .dsh-upd-body{scrollbar-color:var(--dsh-update-border-strong) transparent}',
661
+ '.dsh-upd-overlay .dsh-upd[data-theme="archive"] .dsh-upd-body::-webkit-scrollbar-thumb{background:var(--dsh-update-border-strong)}',
671
662
  // —— 小印章(原型 :215 `.sealmini`:30px、旋转 -5°、一字)——
672
663
  // 待重启横幅一律不画印章:那一档的标记是左侧手绘 SVG(原型 :446 的 .mark 只有 SVG)。
673
664
  '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="loading"]::before,.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="idle"]::before,',
@@ -675,13 +666,13 @@ const UPDATE_PANEL_D5_CSS = [
675
666
  '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="blocked"]::before,.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="failed"]::before,',
676
667
  '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="done"]::before{content:attr(data-mini);display:inline-flex;align-items:center;justify-content:center;',
677
668
  "width:30px;height:30px;margin-right:10px;vertical-align:middle;border:2px solid currentColor;border-radius:7px;",
678
- "font-family:var(--d5-serif);font-weight:700;font-size:16px;line-height:26px;transform:rotate(-5deg);flex:none;color:var(--d5-muted)}",
679
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="update"]::before{color:var(--d5-ok)}',
680
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="busy"]::before{color:var(--d5-warn)}',
681
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="blocked"]::before,.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="failed"]::before{color:var(--d5-bad)}',
682
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="done"]::before{color:var(--d5-ok)}',
683
- '.dsh-upd[data-theme="archive"] .dsh-upd-log code{font-family:var(--d5-mono);font-size:11px;color:var(--d5-muted);border:1px solid var(--d5-line-strong);border-radius:3px;padding:0 6px;letter-spacing:.06em}',
684
- '.dsh-upd[data-theme="archive"] .dsh-upd-manual code{display:block;background:var(--d5-ink);color:var(--d5-bg);font-family:var(--d5-mono);font-size:12.5px;padding:12px 14px;border-radius:4px;white-space:pre-wrap;word-break:break-all}',
669
+ "font-family:var(--dsh-update-font-serif);font-weight:700;font-size:16px;line-height:26px;transform:rotate(-5deg);flex:none;color:var(--dsh-update-text-muted)}",
670
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="update"]::before{color:var(--dsh-update-ok-text)}',
671
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="busy"]::before{color:var(--dsh-update-warn-text)}',
672
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="blocked"]::before,.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="failed"]::before{color:var(--dsh-update-bad-text)}',
673
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="done"]::before{color:var(--dsh-update-ok-text)}',
674
+ '.dsh-upd[data-theme="archive"] .dsh-upd-log code{font-family:var(--dsh-update-font-mono);font-size:11px;color:var(--dsh-update-text-muted);border:1px solid var(--dsh-update-border-strong);border-radius:3px;padding:0 6px;letter-spacing:.06em}',
675
+ '.dsh-upd[data-theme="archive"] .dsh-upd-manual code{display:block;background:var(--dsh-update-text);color:var(--dsh-update-bg-soft);font-family:var(--dsh-update-font-mono);font-size:12.5px;padding:12px 14px;border-radius:4px;white-space:pre-wrap;word-break:break-all}',
685
676
  // 待重启标记:手绘 SVG 当**独立 flex 标记**放在文字块左侧(原型 :446 `.mark` 是独立节点),
686
677
  // 不能用行内背景——那样换行时三角会落在句子中间把话劈开(现场回归)。
687
678
  '.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="restart"]>div:first-child{display:flex;gap:10px;align-items:flex-start}',
@@ -689,45 +680,45 @@ const UPDATE_PANEL_D5_CSS = [
689
680
  'background:url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2720%27 height=%2720%27 viewBox=%270 0 24 24%27 fill=%27none%27 stroke=%27%238a5a00%27 stroke-width=%272%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27%3E%3Cpath d=%27M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z%27/%3E%3Cline x1=%2712%27 y1=%279%27 x2=%2712%27 y2=%2713%27/%3E%3Cline x1=%2712%27 y1=%2717%27 x2=%2712.01%27 y2=%2717%27/%3E%3C/svg%3E") no-repeat center/20px 20px}',
690
681
  '.dsh-upd[data-theme="archive"] .dsh-upd-banner>div:first-child{overflow:hidden;text-overflow:ellipsis}',
691
682
  '.dsh-upd[data-theme="archive"] .dsh-upd-actions{flex-wrap:wrap}',
692
- // footer 只换肤(#47 定案 A + D5 约束:不换 DOM 顺序;复制诊断永不隐藏,本串不动它)。
693
- '.dsh-upd[data-theme="archive"] .dsh-upd-footer{margin-top:14px;padding-top:10px;border-top:1px solid var(--d5-line)}',
694
- '.dsh-upd[data-theme="archive"] .dsh-upd-foot-note{color:var(--d5-muted)}',
683
+ // footer 只换肤(#47 定案 A + Archive 约束:不换 DOM 顺序;复制诊断永不隐藏,本串不动它)。
684
+ '.dsh-upd[data-theme="archive"] .dsh-upd-footer{margin-top:14px;padding-top:10px;border-top:1px solid var(--dsh-update-border)}',
685
+ '.dsh-upd[data-theme="archive"] .dsh-upd-foot-note{color:var(--dsh-update-text-muted)}',
695
686
  // —— 档案头(原型 :208-211 `.filehead`:serif 插件名 22px + 使用范围 + profile 牌;右侧留章位)——
696
687
  '.dsh-upd[data-theme="archive"] .dsh-upd-head{display:flex;gap:16px;align-items:baseline;flex-wrap:wrap;padding-right:120px}',
697
- '.dsh-upd[data-theme="archive"] .dsh-upd-name{font-family:var(--d5-serif);font-size:22px;font-weight:700}',
698
- '.dsh-upd[data-theme="archive"] .dsh-upd-meta{width:100%;font-size:12.5px;color:var(--d5-muted)}',
699
- '.dsh-upd[data-theme="archive"] .dsh-upd-meta b{color:var(--d5-ink);font-weight:600}',
700
- '.dsh-upd[data-theme="archive"] .dsh-upd-proftag{font-family:var(--d5-mono);font-size:11px;color:var(--d5-muted);border:1px solid var(--d5-line-strong);border-radius:3px;padding:0 6px;margin-left:8px;letter-spacing:.06em}',
688
+ '.dsh-upd[data-theme="archive"] .dsh-upd-name{font-family:var(--dsh-update-font-serif);font-size:22px;font-weight:700}',
689
+ '.dsh-upd[data-theme="archive"] .dsh-upd-meta{width:100%;font-size:12.5px;color:var(--dsh-update-text-muted)}',
690
+ '.dsh-upd[data-theme="archive"] .dsh-upd-meta b{color:var(--dsh-update-text);font-weight:600}',
691
+ '.dsh-upd[data-theme="archive"] .dsh-upd-proftag{font-family:var(--dsh-update-font-mono);font-size:11px;color:var(--dsh-update-text-muted);border:1px solid var(--dsh-update-border-strong);border-radius:3px;padding:0 6px;margin-left:8px;letter-spacing:.06em}',
701
692
  // —— 版本条(原型 :216 `.strip`:三格,格间一线,左上小写标签 + 等宽值)——
702
- '.dsh-upd[data-theme="archive"] .dsh-upd-strip{display:flex;flex-wrap:wrap;margin:8px 0 0;border:1px solid var(--d5-line);border-radius:4px;overflow:hidden;font-size:12.5px}',
703
- '.dsh-upd[data-theme="archive"] .dsh-upd-strip>div{flex:1 1 120px;padding:6px 10px;border-left:1px solid var(--d5-line)}',
693
+ '.dsh-upd[data-theme="archive"] .dsh-upd-strip{display:flex;flex-wrap:wrap;margin:8px 0 0;border:1px solid var(--dsh-update-border);border-radius:4px;overflow:hidden;font-size:12.5px}',
694
+ '.dsh-upd[data-theme="archive"] .dsh-upd-strip>div{flex:1 1 120px;padding:6px 10px;border-left:1px solid var(--dsh-update-border)}',
704
695
  '.dsh-upd[data-theme="archive"] .dsh-upd-strip>div:first-child{border-left:0}',
705
- '.dsh-upd[data-theme="archive"] .dsh-upd-strip-k{display:block;font-size:11px;letter-spacing:.2em;color:var(--d5-muted)}',
706
- '.dsh-upd[data-theme="archive"] .dsh-upd-strip-v{font-family:var(--d5-mono);font-size:13px}',
696
+ '.dsh-upd[data-theme="archive"] .dsh-upd-strip-k{display:block;font-size:11px;letter-spacing:.2em;color:var(--dsh-update-text-muted)}',
697
+ '.dsh-upd[data-theme="archive"] .dsh-upd-strip-v{font-family:var(--dsh-update-font-mono);font-size:13px}',
707
698
  // —— 章节(原型 :218-245:01–05 编号 + 衬线标题 + 细线)——
708
- '.dsh-upd[data-theme="archive"] .dsh-upd-chapter{margin-top:16px;padding-top:10px;border-top:1px solid var(--d5-line)}',
699
+ '.dsh-upd[data-theme="archive"] .dsh-upd-chapter{margin-top:16px;padding-top:10px;border-top:1px solid var(--dsh-update-border)}',
709
700
  '.dsh-upd[data-theme="archive"] .dsh-upd-chap-head{display:flex;align-items:baseline;gap:12px;margin-bottom:6px}',
710
- '.dsh-upd[data-theme="archive"] .dsh-upd-chap-no{font-family:var(--d5-serif);font-style:italic;font-size:15px;color:var(--d5-muted)}',
711
- '.dsh-upd[data-theme="archive"] .dsh-upd-chap-title{font-family:var(--d5-serif);font-size:17px;margin:0;letter-spacing:.1em}',
712
- '.dsh-upd[data-theme="archive"] .dsh-upd-chap-rule{flex:1;border-top:1px solid var(--d5-line);transform:translateY(-4px)}',
701
+ '.dsh-upd[data-theme="archive"] .dsh-upd-chap-no{font-family:var(--dsh-update-font-serif);font-style:italic;font-size:15px;color:var(--dsh-update-text-muted)}',
702
+ '.dsh-upd[data-theme="archive"] .dsh-upd-chap-title{font-family:var(--dsh-update-font-serif);font-size:17px;margin:0;letter-spacing:.1em}',
703
+ '.dsh-upd[data-theme="archive"] .dsh-upd-chap-rule{flex:1;border-top:1px solid var(--dsh-update-border);transform:translateY(-4px)}',
713
704
  // —— 横幅即状态行 / 待重启横幅(原型 :81-85 `.restart-banner`:2px 边框、圆角 4、内边距 12/16、衬线;右侧留章位)——
714
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner{border:2px solid var(--d5-line-strong);border-radius:4px;padding:10px 12px;font-size:14.5px;font-family:var(--d5-serif);display:flex;gap:10px;align-items:center;flex-wrap:wrap}',
705
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner{border:2px solid var(--dsh-update-border-strong);border-radius:4px;padding:10px 12px;font-size:14.5px;font-family:var(--dsh-update-font-serif);display:flex;gap:10px;align-items:center;flex-wrap:wrap}',
715
706
  '.dsh-upd[data-theme="archive"] .dsh-upd-banner>div:first-child{flex:1 1 auto;min-width:0}',
716
707
  // 状态行字号照原型 .status-line=27px(实测去掉横幅右侧占位后可写 486px > 428px,一行放得下)
717
- '.dsh-upd[data-theme="archive"] .dsh-upd-banner>div:first-child strong{font-family:var(--d5-serif);font-size:27px;font-weight:700;line-height:1.25}',
708
+ '.dsh-upd[data-theme="archive"] .dsh-upd-banner>div:first-child strong{font-family:var(--dsh-update-font-serif);font-size:27px;font-weight:700;line-height:1.25}',
718
709
  // —— 进度条 / 跳过行(原型 :132-133 `.prog`、:129-131 `.skipline .tag`)——
719
- '.dsh-upd[data-theme="archive"] .dsh-upd-prog{height:8px;background:var(--d5-line);border-radius:4px;overflow:hidden;margin:10px 0 4px}',
720
- '.dsh-upd[data-theme="archive"] .dsh-upd-prog-bar{display:block;height:100%;background:var(--d5-accent);transition:width .3s}',
721
- '.dsh-upd[data-theme="archive"] .dsh-upd-progtxt{font-size:12.5px;color:var(--d5-muted)}',
710
+ '.dsh-upd[data-theme="archive"] .dsh-upd-prog{height:8px;background:var(--dsh-update-border);border-radius:4px;overflow:hidden;margin:10px 0 4px}',
711
+ '.dsh-upd[data-theme="archive"] .dsh-upd-prog-bar{display:block;height:100%;background:var(--dsh-update-primary);transition:width .3s}',
712
+ '.dsh-upd[data-theme="archive"] .dsh-upd-progtxt{font-size:12.5px;color:var(--dsh-update-text-muted)}',
722
713
  '.dsh-upd[data-theme="archive"] .dsh-upd-skipline{font-size:13px;margin-top:8px}',
723
- '.dsh-upd[data-theme="archive"] .dsh-upd-tag{display:inline-block;border:1px dashed var(--d5-line-strong);border-radius:3px;padding:1px 8px;margin-right:8px;font-family:var(--d5-mono);font-size:12px}',
724
- '.dsh-upd[data-theme="archive"] .dsh-upd-err{font-size:13px;margin:0 0 6px;color:var(--d5-muted)}',
725
- '@media (max-width:640px){.dsh-upd[data-theme="archive"]{padding:10px 12px}.dsh-upd[data-theme="archive"]::before{top:12px;right:12px;width:56px;height:56px;font-size:15px;box-shadow:inset 0 0 0 4px var(--d5-card),inset 0 0 0 5px currentColor}.dsh-upd[data-theme="archive"] .dsh-upd-masthead-title{font-size:19px}.dsh-upd[data-theme="archive"] .dsh-upd-banner{padding-right:16px}.dsh-upd[data-theme="archive"] .dsh-upd-banner::before{width:24px;height:24px;font-size:14px;line-height:20px;flex:none}.dsh-upd[data-theme="archive"] .dsh-upd-banner>div:first-child{white-space:normal}}',
726
- '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"]{--d5-bg:#141210;--d5-card:#1e1a15;--d5-ink:#ece5d3;--d5-muted:#a89c83;',
727
- "--d5-line:#3a3226;--d5-line-strong:#5c4e3b;--d5-accent:#e0684e;--d5-accent-deep:#f0866b;",
728
- "--d5-ok:#8fd6a4;--d5-ok-bg:rgba(80,180,120,.12);--d5-warn:#e8c15a;--d5-warn-bg:rgba(232,193,90,.12);",
729
- "--d5-bad:#ef8a7d;--d5-bad-bg:rgba(239,138,125,.12);--d5-shadow:0 1px 2px rgba(0,0,0,.4),0 12px 32px rgba(0,0,0,.45)}}",
730
- '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"] button[data-primary="1"]{color:#141210}.dsh-upd[data-theme="archive"] button:focus-visible{outline-color:var(--d5-accent-deep)}}',
714
+ '.dsh-upd[data-theme="archive"] .dsh-upd-tag{display:inline-block;border:1px dashed var(--dsh-update-border-strong);border-radius:3px;padding:1px 8px;margin-right:8px;font-family:var(--dsh-update-font-mono);font-size:12px}',
715
+ '.dsh-upd[data-theme="archive"] .dsh-upd-err{font-size:13px;margin:0 0 6px;color:var(--dsh-update-text-muted)}',
716
+ '@media (max-width:640px){.dsh-upd[data-theme="archive"]{padding:10px 12px}.dsh-upd[data-theme="archive"]::before{top:12px;right:12px;width:56px;height:56px;font-size:15px;box-shadow:inset 0 0 0 4px var(--dsh-update-bg),inset 0 0 0 5px currentColor}.dsh-upd[data-theme="archive"] .dsh-upd-masthead-title{font-size:19px}.dsh-upd[data-theme="archive"] .dsh-upd-banner{padding-right:16px}.dsh-upd[data-theme="archive"] .dsh-upd-banner::before{width:24px;height:24px;font-size:14px;line-height:20px;flex:none}.dsh-upd[data-theme="archive"] .dsh-upd-banner>div:first-child{white-space:normal}}',
717
+ '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"]{--dsh-update-bg-soft:#141210;--dsh-update-bg:#1e1a15;--dsh-update-text:#ece5d3;--dsh-update-text-muted:#a89c83;',
718
+ "--dsh-update-border:#3a3226;--dsh-update-border-strong:#5c4e3b;--dsh-update-primary:#e0684e;--dsh-update-primary-deep:#f0866b;",
719
+ "--dsh-update-ok-text:#8fd6a4;--dsh-update-ok-bg:rgba(80,180,120,.12);--dsh-update-warn-text:#e8c15a;--dsh-update-warn-bg:rgba(232,193,90,.12);",
720
+ "--dsh-update-bad-text:#ef8a7d;--dsh-update-bad-bg:rgba(239,138,125,.12);--dsh-update-shadow:0 1px 2px rgba(0,0,0,.4),0 12px 32px rgba(0,0,0,.45)}}",
721
+ '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"] button[data-primary="1"]{color:#141210}.dsh-upd[data-theme="archive"] button:focus-visible{outline-color:var(--dsh-update-primary-deep)}}',
731
722
  '@media (prefers-color-scheme: dark){.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="restart"]>div:first-child::before{background-image:url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2720%27 height=%2720%27 viewBox=%270 0 24 24%27 fill=%27none%27 stroke=%27%23e8c15a%27 stroke-width=%272%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27%3E%3Cpath d=%27M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z%27/%3E%3Cline x1=%2712%27 y1=%279%27 x2=%2712%27 y2=%2713%27/%3E%3Cline x1=%2712%27 y1=%2717%27 x2=%2712.01%27 y2=%2717%27/%3E%3C/svg%3E")}}',
732
723
  '@media (forced-colors: active){.dsh-upd[data-theme="archive"]{box-shadow:none}.dsh-upd[data-theme="archive"]::before{background:none;box-shadow:none;border-color:CanvasText;color:CanvasText}.dsh-upd[data-theme="archive"] .dsh-upd-banner{border:1px solid CanvasText}.dsh-upd[data-theme="archive"] .dsh-upd-banner::before{border-color:CanvasText;color:CanvasText;background:Canvas}.dsh-upd[data-theme="archive"] button{border:1px solid ButtonText}.dsh-upd[data-theme="archive"] button[data-primary="1"]{background:ButtonFace;color:ButtonText;border-color:ButtonText}.dsh-upd[data-theme="archive"] .dsh-upd-banner[data-kind="restart"]>div:first-child::before{background-image:none;content:"\u26A0"}}',
733
724
  '@media (prefers-reduced-motion: reduce){.dsh-upd[data-theme="archive"] *{transition:none !important;animation:none !important}}'
@@ -735,58 +726,83 @@ const UPDATE_PANEL_D5_CSS = [
735
726
  function escapeHtml(text) {
736
727
  return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
737
728
  }
738
- const CHAPTER_TITLES = ["\u68C0\u67E5\u4E0E\u5B89\u88C5", "\u66F4\u65B0\u65E5\u5FD7", "\u66F4\u65B0\u961F\u5217", "\u9519\u8BEF\u4FE1\u606F", "\u624B\u5DE5\u547D\u4EE4"];
739
- function formatLatchTime(atMs) {
729
+ const CHAPTER_KEYS = [
730
+ "panel.chapter.check",
731
+ "panel.chapter.changelog",
732
+ "panel.chapter.queue",
733
+ "panel.chapter.error",
734
+ "panel.chapter.manual"
735
+ ];
736
+ function chapterTitle(index, lang) {
737
+ const l = normalizeLangTag(lang ?? "zh");
738
+ return copyText(CHAPTER_KEYS[index - 1], l);
739
+ }
740
+ function formatLatchTime(atMs, lang) {
740
741
  if (typeof atMs !== "number" || !Number.isFinite(atMs) || atMs <= 0) return null;
741
- try {
742
- const s = new Date(atMs).toLocaleTimeString("zh-CN", { hour12: false });
743
- return s ? s : null;
744
- } catch {
745
- return null;
742
+ const raw = typeof lang === "string" ? lang.trim().replace(/_/g, "-") : "";
743
+ const l = normalizeLangTag(lang ?? "zh");
744
+ const primary = l === "en" ? "en-US" : "zh-CN";
745
+ const fallback = l === "en" ? "zh-CN" : "en-US";
746
+ const candidates = [];
747
+ if (raw && raw !== primary && /^[A-Za-z]{2,3}(-[A-Za-z0-9]+)*$/.test(raw)) candidates.push(raw);
748
+ candidates.push(primary, fallback);
749
+ for (const tag of candidates) {
750
+ try {
751
+ const s = new Date(atMs).toLocaleTimeString(tag, { hour12: false });
752
+ if (s) return s;
753
+ } catch {
754
+ }
746
755
  }
756
+ return null;
747
757
  }
748
- function chapterOf(index, inner, note = "") {
758
+ function chapterOf(index, inner, note = "", lang) {
749
759
  const no = String(index).padStart(2, "0");
750
- const title = CHAPTER_TITLES[index - 1];
760
+ const l = normalizeLangTag(lang ?? "zh");
761
+ const title = chapterTitle(index, l);
751
762
  return `<section class="dsh-upd-chapter" data-chapter="${no}"><div class="dsh-upd-chap-head"><span class="dsh-upd-chap-no">${no}</span><h3 class="dsh-upd-chap-title">${escapeHtml(title)}</h3><span class="dsh-upd-chap-rule"></span>${note}</div>${inner}</section>`;
752
763
  }
753
- function skeletonStrip(loading) {
764
+ function skeletonStrip(loading, lang) {
754
765
  if (!loading) return "";
766
+ const l = normalizeLangTag(lang ?? "zh");
755
767
  const cell = (k) => `<div><span class="dsh-upd-strip-k">${escapeHtml(k)}</span><span class="dsh-upd-strip-v dsh-upd-skv" aria-hidden="true">\u2026</span></div>`;
756
- return `<div class="dsh-upd-strip" aria-hidden="true">` + cell("\u8FD0\u884C") + cell("\u78C1\u76D8") + cell("\u8FDC\u7AEF") + `</div>`;
768
+ return `<div class="dsh-upd-strip" aria-hidden="true">` + cell(copyText("panel.strip.running", l)) + cell(copyText("panel.strip.installed", l)) + cell(copyText("panel.strip.latest", l)) + `</div>`;
757
769
  }
758
- function versionStrip(snapshot) {
759
- const cell = (k, v) => `<div><span class="dsh-upd-strip-k">${escapeHtml(k)}</span><span class="dsh-upd-strip-v">${escapeHtml(v ?? "\u672A\u77E5")}</span></div>`;
770
+ function versionStrip(snapshot, lang) {
771
+ const l = normalizeLangTag(lang ?? "zh");
772
+ const unknown = copyText("panel.meta.unknown", l);
773
+ const cell = (k, v) => `<div><span class="dsh-upd-strip-k">${escapeHtml(k)}</span><span class="dsh-upd-strip-v">${escapeHtml(v ?? unknown)}</span></div>`;
760
774
  if (!snapshot) return "";
761
- return `<div class="dsh-upd-strip">` + cell("\u8FD0\u884C", snapshot.runningVersion) + cell("\u78C1\u76D8", snapshot.installedVersion) + cell("\u8FDC\u7AEF", snapshot.latestVersion) + `</div>`;
775
+ return `<div class="dsh-upd-strip">` + cell(copyText("panel.strip.running", l), snapshot.runningVersion) + cell(copyText("panel.strip.installed", l), snapshot.installedVersion) + cell(copyText("panel.strip.latest", l), snapshot.latestVersion) + `</div>`;
762
776
  }
763
- function progressBar(snapshot) {
777
+ function progressBar(snapshot, lang) {
764
778
  const job = snapshot?.job;
765
779
  if (!job) return "";
766
780
  const state = String(job.state);
767
781
  if (state !== "installing" && state !== "verifying") return "";
782
+ const l = normalizeLangTag(lang ?? "zh");
768
783
  const width = state === "installing" ? 60 : 90;
769
- const text = state === "installing" ? "\u6B63\u5728\u5B89\u88C5\u65B0\u7248\u2026" : "\u6B63\u5728\u6821\u9A8C\u5B89\u88C5\u7ED3\u679C\u2026";
784
+ const text = state === "installing" ? copyText("panel.progress.installing", l) : copyText("panel.progress.verifying", l);
770
785
  return `<div class="dsh-upd-prog" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${width}"><i class="dsh-upd-prog-bar" style="width:${width}%"></i></div><div class="dsh-upd-progtxt">${escapeHtml(text)}</div>`;
771
786
  }
772
- function renderUpdatePanelKernel(input, view) {
787
+ function renderUpdatePanelKernel(input, view, lang) {
773
788
  const { snapshot, manual, queue, mode, showOthers, pluginId, copyNotice } = input;
789
+ const l = normalizeLangTag(lang ?? input.lang ?? "zh");
774
790
  const changelogMarkdown = input.changelogMarkdown ?? null;
775
791
  const profileName = typeof input.profileName === "string" && input.profileName ? input.profileName : null;
776
792
  const b = view.banner;
777
793
  const seal = view.seal;
778
794
  const parts = [];
779
795
  parts.push(
780
- '<div class="dsh-upd-masthead"><span class="dsh-upd-masthead-kicker">\u63D2\u4EF6\u66F4\u65B0</span><span class="dsh-upd-masthead-title">\u66F4\u65B0\u6863\u6848 <i>\u5377</i></span></div>'
796
+ '<div class="dsh-upd-masthead"><span class="dsh-upd-masthead-kicker">' + escapeHtml(copyText("panel.masthead.kicker", l)) + '</span><span class="dsh-upd-masthead-title">' + escapeHtml(copyText("panel.masthead.title", l)) + " <i>" + escapeHtml(copyText("panel.masthead.volume", l)) + "</i></span></div>"
781
797
  );
782
798
  parts.push(
783
- `<div class="dsh-upd-head"><span class="dsh-upd-name">${escapeHtml(pluginId)}</span><span class="dsh-upd-meta">\u4F7F\u7528\u8303\u56F4 <b>${escapeHtml(profileName ?? "\u672A\u77E5")}</b><span class="dsh-upd-proftag">profile</span></span></div>`
799
+ `<div class="dsh-upd-head"><span class="dsh-upd-name">${escapeHtml(pluginId)}</span><span class="dsh-upd-meta">${escapeHtml(copyText("panel.meta.label", l))} <b>${escapeHtml(profileName ?? copyText("panel.meta.unknown", l))}</b><span class="dsh-upd-proftag">profile</span></span></div>`
784
800
  );
785
801
  parts.push(`<div class="dsh-upd-banner" data-kind="${b.kind}" data-mini="${escapeHtml(seal.mini)}" role="status" aria-live="polite">`);
786
802
  parts.push(`<div><strong>${escapeHtml(b.title)}</strong></div>`);
787
803
  if (b.action) parts.push(`<div>${escapeHtml(b.action)}</div>`);
788
804
  parts.push("</div>");
789
- parts.push(snapshot ? versionStrip(snapshot) : skeletonStrip(view.banner.kind === "loading"));
805
+ parts.push(snapshot ? versionStrip(snapshot, l) : skeletonStrip(view.banner.kind === "loading", l));
790
806
  const showActions = input.actions !== "none";
791
807
  const actions = [];
792
808
  if (showActions) {
@@ -795,36 +811,36 @@ function renderUpdatePanelKernel(input, view) {
795
811
  const installBusy = busyAct === "install";
796
812
  actions.push('<div class="dsh-upd-actions">');
797
813
  actions.push(
798
- (checkBusy ? `<button type="button" data-action="check" disabled aria-busy="true" title="\u6B63\u5728\u5411\u5B98\u65B9\u6E90\u67E5\u8BE2\uFF0C\u8BF7\u7A0D\u5019">\u6B63\u5728\u67E5\u65B0\u7248\u2026</button>` : `<button type="button" data-action="check" title="\u91CD\u65B0\u5411\u5B98\u65B9\u6E90\u67E5\u4E00\u6B21\u65B0\u7248\uFF08\u53EA\u8BFB\uFF0C\u4E0D\u5B89\u88C5\uFF09">\u67E5\u65B0\u7248</button>`) + (installBusy ? `<button type="button" data-action="install" data-primary="1" disabled aria-busy="true" title="\u6B63\u5728\u5B89\u88C5\uFF0C\u8BF7\u7A0D\u5019">\u6B63\u5728\u5B89\u88C5\u2026</button>` : `<button type="button" data-action="install" data-primary="1" title="\u7528\u7CBE\u786E\u7248\u672C\u5B89\u88C5\uFF1B\u540C\u4E00\u4F7F\u7528\u8303\u56F4\u540C\u65F6\u53EA\u88C5\u4E00\u4E2A"${view.installEnabled ? "" : " disabled"}>${escapeHtml(view.installLabel)}</button>`)
814
+ (checkBusy ? `<button type="button" data-action="check" disabled aria-busy="true" title="${escapeHtml(copyText("panel.action.checking-busy-title", l))}">${escapeHtml(copyText("panel.action.checking-busy", l))}</button>` : `<button type="button" data-action="check" title="${escapeHtml(copyText("panel.action.check-title", l))}">${escapeHtml(copyText("panel.action.check", l))}</button>`) + (installBusy ? `<button type="button" data-action="install" data-primary="1" disabled aria-busy="true" title="${escapeHtml(copyText("panel.action.installing-busy-title", l))}">${escapeHtml(copyText("panel.action.installing-busy", l))}</button>` : `<button type="button" data-action="install" data-primary="1" title="${escapeHtml(copyText("panel.action.install-title", l))}"${view.installEnabled ? "" : " disabled"}>${escapeHtml(view.installLabel)}</button>`)
799
815
  );
800
816
  if (snapshot?.latestVersion && !view.skippedLatest && view.banner.kind === "update") {
801
- actions.push(`<button type="button" data-action="skip" title="\u8BE5\u7248\u672C\u4E0D\u518D\u63D0\u9192\uFF1B\u6709\u66F4\u65B0\u7684\u65B0\u7248\u672C\u7167\u5E38\u63D0\u9192">\u8DF3\u8FC7\u8BE5\u7248\u672C</button>`);
817
+ actions.push(`<button type="button" data-action="skip" title="${escapeHtml(copyText("panel.action.skip-title", l))}">${escapeHtml(copyText("panel.action.skip", l))}</button>`);
802
818
  }
803
819
  if (view.showReset && snapshot?.latestVersion) {
804
- actions.push(`<button type="button" data-action="reset-skip" title="\u64A4\u9500\u8DF3\u8FC7\uFF0C\u8BE5\u7248\u672C\u91CD\u65B0\u63D0\u9192">\u6062\u590D\uFF08${escapeHtml(snapshot.latestVersion)}\uFF09</button>`);
820
+ actions.push(`<button type="button" data-action="reset-skip" title="${escapeHtml(copyText("panel.action.unskip-title", l))}">${escapeHtml(copyText("panel.action.unskip", l, { version: snapshot.latestVersion }))}</button>`);
805
821
  }
806
822
  if (view.showManual && manual) {
807
- actions.push(`<button type="button" data-action="copy-manual" title="\u590D\u5236\u624B\u5DE5\u547D\u4EE4\uFF0C\u7C98\u5230\u7EC8\u7AEF\u6574\u884C\u6267\u884C">\u590D\u5236\u624B\u5DE5\u547D\u4EE4</button>`);
823
+ actions.push(`<button type="button" data-action="copy-manual" title="${escapeHtml(copyText("panel.action.copy-manual-title", l))}">${escapeHtml(copyText("panel.action.copy-manual", l))}</button>`);
808
824
  }
809
825
  if (b.kind === "restart") {
810
- actions.push(`<button type="button" data-action="restart-hint" data-primary="1" title="\u5BBF\u4E3B\u6CA1\u6709\u81EA\u91CD\u542F\u7535\u8BDD\uFF1A\u8BF7\u624B\u52A8\u91CD\u542F\u5BBF\u4E3B">\u91CD\u542F\u5BBF\u4E3B</button>`);
826
+ actions.push(`<button type="button" data-action="restart-hint" data-primary="1" title="${escapeHtml(copyText("panel.action.restart-host-title", l))}">${escapeHtml(copyText("panel.action.restart-host", l))}</button>`);
811
827
  }
812
828
  if (b.kind === "failed") {
813
- actions.push(`<button type="button" data-action="dismiss-failure" title="\u786E\u8BA4\u5DF2\u77E5\u6653\u8BE5\u5931\u8D25\uFF1A\u56DE\u5230\u53EF\u88C5\u9875\uFF0C\u4E0B\u6B21\u67E5/\u88C5\u5C06\u91CD\u65B0\u8BC4\u4F30">\u77E5\u9053\u4E86</button>`);
829
+ actions.push(`<button type="button" data-action="dismiss-failure" title="${escapeHtml(copyText("panel.action.dismiss-title", l))}">${escapeHtml(copyText("panel.action.dismiss", l))}</button>`);
814
830
  }
815
831
  if (snapshot) {
816
- actions.push(`<button type="button" data-action="copy-diag" title="\u590D\u5236\u5DF2\u8131\u654F\u8BCA\u65AD\uFF0C\u76F4\u63A5\u7C98\u7ED9\u63D2\u4EF6\u4F5C\u8005">\u590D\u5236\u8BCA\u65AD</button>`);
832
+ actions.push(`<button type="button" data-action="copy-diag" title="${escapeHtml(copyText("panel.action.copy-diag-title", l))}">${escapeHtml(copyText("panel.action.copy-diag", l))}</button>`);
817
833
  }
818
834
  actions.push("</div>");
819
835
  }
820
- actions.push(progressBar(snapshot));
836
+ actions.push(progressBar(snapshot, l));
821
837
  if (view.skippedLatest && snapshot?.latestVersion) {
822
838
  actions.push(
823
- `<div class="dsh-upd-skipline"><span class="dsh-upd-tag">\u5DF2\u8DF3\u8FC7 ${escapeHtml(snapshot.latestVersion)}</span>\u70B9\u300C\u6062\u590D\u300D\u53EF\u64A4\u9500\uFF0C\u4E4B\u540E\u8FD9\u4E00\u7248\u8FD8\u4F1A\u518D\u63D0\u9192\u3002</div>`
839
+ `<div class="dsh-upd-skipline"><span class="dsh-upd-tag">${escapeHtml(copyText("panel.skip.line-tag", l, { version: snapshot.latestVersion }))}</span>${escapeHtml(copyText("panel.skip.line-note", l))}</div>`
824
840
  );
825
841
  }
826
842
  const chapters = [];
827
- chapters.push(chapterOf(1, actions.join("")));
843
+ chapters.push(chapterOf(1, actions.join(""), "", l));
828
844
  {
829
845
  let inner = "";
830
846
  let hasLog = false;
@@ -838,10 +854,10 @@ function renderUpdatePanelKernel(input, view) {
838
854
  snapshot.latestVersion,
839
855
  snapshot.installedVersion
840
856
  );
841
- const changelogHTML = renderChangelogHTML(ranged);
857
+ const changelogHTML = renderChangelogHTML(ranged, { lang: l });
842
858
  const fromText = String(snapshot.runningVersion ?? "");
843
859
  const toText = String(snapshot.latestVersion ?? "");
844
- const rangeTitle = fromText && toText ? `\u66F4\u65B0\u8BF4\u660E\uFF08${fromText} \u2192 ${toText}\uFF09\uFF1A` : "\u66F4\u65B0\u8BF4\u660E\uFF1A";
860
+ const rangeTitle = fromText && toText ? copyText("panel.changelog.heading-range", l, { from: fromText, to: toText }) : copyText("panel.changelog.heading", l);
845
861
  let yankedBanner = "";
846
862
  try {
847
863
  const toEntry = Array.isArray(ranged) ? ranged.find(function(e) {
@@ -852,7 +868,7 @@ function renderUpdatePanelKernel(input, view) {
852
868
  }
853
869
  }) : null;
854
870
  if (toEntry && toEntry.yanked === true && toText) {
855
- yankedBanner = yankedBannerHTML(toText);
871
+ yankedBanner = yankedBannerHTML(toText, l);
856
872
  }
857
873
  } catch {
858
874
  yankedBanner = "";
@@ -868,12 +884,12 @@ ${changelogHTML}
868
884
  }
869
885
  if (!inner) {
870
886
  inner = `<div class="dsh-upd-changelog-wrap"><div class="dsh-upd-changelog-neutral">${escapeHtml(
871
- snapshot && snapshot.latestVersion ? "\u65E5\u5FD7\u8BFB\u4E0D\u51FA\u6765\uFF0C\u5B89\u88C5\u4E0D\u53D7\u5F71\u54CD\u3002" : "\u8FD8\u6CA1\u67E5\u5230\u65B0\u7248\uFF1B\u67E5\u5230\u540E\u518D\u663E\u793A\u65E5\u5FD7\u3002"
887
+ snapshot && snapshot.latestVersion ? copyText("panel.changelog.unavailable", l) : copyText("panel.changelog.unavailable-empty", l)
872
888
  )}</div></div>`;
873
889
  }
874
890
  const logCollapsed = input.changelogCollapsed === true;
875
- const logNote = showActions && hasLog ? `<span class="dsh-upd-chap-note"><button type="button" data-action="toggle-changelog" title="\u5C55\u5F00\u6216\u6536\u8D77\u66F4\u65B0\u65E5\u5FD7">${logCollapsed ? "\u5C55\u5F00\u66F4\u65B0\u65E5\u5FD7" : "\u6536\u8D77\u66F4\u65B0\u65E5\u5FD7"}</button></span>` : "";
876
- chapters.push(chapterOf(2, inner, logNote));
891
+ const logNote = showActions && hasLog ? '<span class="dsh-upd-chap-note"><button type="button" data-action="toggle-changelog" title="' + escapeHtml(copyText("panel.changelog.toggle-title", l)) + `">${escapeHtml(logCollapsed ? copyText("panel.changelog.expand", l) : copyText("panel.changelog.collapse", l))}</button></span>` : "";
892
+ chapters.push(chapterOf(2, inner, logNote, l));
877
893
  }
878
894
  {
879
895
  const queued = !!queue && (queue.busy || queue.waiting.length > 0);
@@ -884,30 +900,32 @@ ${changelogHTML}
884
900
  const version = ownerRaw && "targetVersion" in ownerRaw && ownerRaw.targetVersion ? `@${String(ownerRaw.targetVersion)}` : "";
885
901
  const aboutSelf = named !== null && named === pluginId;
886
902
  const reveal = showOthers === true;
887
- const ownerShown = !busy ? "\u7A7A\u95F2" : named === null ? "\u5176\u4ED6\u63D2\u4EF6" : aboutSelf ? "\u672C\u63D2\u4EF6" : reveal ? named : "\u5176\u4ED6\u63D2\u4EF6";
903
+ const ownerShown = !busy ? copyText("panel.queue.state-idle", l) : named === null ? copyText("panel.queue.other", l) : aboutSelf ? copyText("panel.queue.self", l) : reveal ? named : copyText("panel.queue.other", l);
888
904
  const ownerVer = busy && named !== null && (aboutSelf || reveal) ? version : "";
889
905
  const pos = typeof queue.position === "number" ? queue.position : null;
890
- const posText = pos === null ? "\u672A\u6392\u961F" : `\u7B2C ${pos} \u4F4D`;
891
- const posNote = pos === null ? "" : pos === 1 ? "\u4E0B\u4E00\u4E2A\u5C31\u662F\u4F60" : `\u524D\u65B9 ${pos - 1} \u4E2A`;
906
+ const posText = pos === null ? copyText("panel.queue.pos-absent", l) : copyText("panel.queue.pos-n", l, { n: pos });
907
+ const posNote = pos === null ? "" : pos === 1 ? copyText("panel.queue.pos-next", l) : copyText("panel.queue.pos-ahead", l, { n: pos - 1 });
892
908
  const rows = [
893
- `<div class="dsh-upd-qrow"><span class="dsh-upd-qdot" data-tone="${busy ? "busy" : "idle"}"></span><span class="dsh-upd-qk">\u6B63\u5728\u5B89\u88C5</span><span class="dsh-upd-qv">${escapeHtml(ownerShown + ownerVer)}</span><span class="dsh-upd-qn">${busy ? "\u88C5\u5B8C\u81EA\u52A8\u8F6E\u5230\u4F60" : "\u540C\u4E00\u4F7F\u7528\u8303\u56F4\u4E00\u6B21\u53EA\u88C5\u4E00\u4E2A"}</span></div>`,
894
- `<div class="dsh-upd-qrow"><span class="dsh-upd-qdot" data-tone="you"></span><span class="dsh-upd-qk">\u4F60\u7684\u987A\u4F4D</span><span class="dsh-upd-qv">${escapeHtml(posText)}</span><span class="dsh-upd-qn">${escapeHtml(posNote)}</span></div>`
909
+ `<div class="dsh-upd-qrow"><span class="dsh-upd-qdot" data-tone="${busy ? "busy" : "idle"}"></span><span class="dsh-upd-qk">${escapeHtml(copyText("panel.queue.row-installing", l))}</span><span class="dsh-upd-qv">${escapeHtml(ownerShown + ownerVer)}</span><span class="dsh-upd-qn">${escapeHtml(busy ? copyText("panel.queue.row-installing-note", l) : copyText("panel.queue.row-idle-note", l))}</span></div>`,
910
+ `<div class="dsh-upd-qrow"><span class="dsh-upd-qdot" data-tone="you"></span><span class="dsh-upd-qk">${escapeHtml(copyText("panel.queue.row-position", l))}</span><span class="dsh-upd-qv">${escapeHtml(posText)}</span><span class="dsh-upd-qn">${escapeHtml(posNote)}</span></div>`
895
911
  ];
896
912
  if (reveal && queue.waiting.length > 0) {
897
913
  const seq = queue.waiting.map((e) => `${e.pluginId}${e.targetVersion ? `@${e.targetVersion}` : ""}`).join(" \u2192 ");
898
914
  rows.push(
899
- `<div class="dsh-upd-qrow"><span class="dsh-upd-qdot"></span><span class="dsh-upd-qk">\u6392\u961F\u987A\u5E8F</span><span class="dsh-upd-qseq">${escapeHtml(seq)}</span></div>`
915
+ `<div class="dsh-upd-qrow"><span class="dsh-upd-qdot"></span><span class="dsh-upd-qk">${escapeHtml(copyText("panel.queue.row-order", l))}</span><span class="dsh-upd-qseq">${escapeHtml(seq)}</span></div>`
900
916
  );
901
917
  }
902
- const note = showActions ? `<span class="dsh-upd-chap-note"><button type="button" data-action="toggle-queue">${reveal ? "\u9690\u85CF\u4ED6\u4EBA\u660E\u7EC6" : "\u663E\u793A\u5176\u4ED6\u63D2\u4EF6"}</button></span>` : "";
903
- chapters.push(chapterOf(3, `<div class="dsh-upd-queue">${rows.join("")}</div>`, note));
918
+ const note = showActions ? `<span class="dsh-upd-chap-note"><button type="button" data-action="toggle-queue">${escapeHtml(reveal ? copyText("panel.queue.toggle-hide", l) : copyText("panel.queue.toggle-show", l))}</button></span>` : "";
919
+ chapters.push(chapterOf(3, `<div class="dsh-upd-queue">${rows.join("")}</div>`, note, l));
904
920
  } else {
905
921
  chapters.push(
906
922
  chapterOf(
907
923
  3,
908
924
  `<div class="dsh-upd-queue"><div class="dsh-upd-changelog-neutral">${escapeHtml(
909
- view.queueNote ?? "\u5F53\u524D\u6CA1\u6709\u6392\u961F\u4EFB\u52A1\uFF0C\u540C\u4E00\u4F7F\u7528\u8303\u56F4\u4E00\u6B21\u53EA\u88C5\u4E00\u4E2A\u3002"
910
- )}</div></div>`
925
+ view.queueNote ?? copyText("panel.queue.empty", l)
926
+ )}</div></div>`,
927
+ "",
928
+ l
911
929
  )
912
930
  );
913
931
  }
@@ -919,70 +937,82 @@ ${changelogHTML}
919
937
  if (failedNow) {
920
938
  const shownCode = b.kind === "blocked" ? snapshot?.blockedReason ?? b.kind : typeof input.lastError === "string" && input.lastError.trim() ? input.lastError : "install-failed";
921
939
  errLines.push(
922
- `<div class="dsh-upd-err">\u7A33\u5B9A\u7801 <code>${escapeHtml(String(shownCode))}</code>\uFF1A\u4E0A\u4E00\u6761\u4E2D\u6587\u8BF4\u660E\u5C31\u662F\u8981\u7528\u6237\u505A\u7684\u4E8B\uFF1B\u8981\u5F80\u4E0A\u6E38\u62A5\uFF0C\u7528\u300C\u590D\u5236\u8BCA\u65AD\u300D\u6574\u6BB5\u7C98\uFF08\u5DF2\u8131\u654F\uFF09\u3002</div>`
940
+ `<div class="dsh-upd-err">${escapeHtml(copyText("panel.error.code-label", l))} <code>${escapeHtml(String(shownCode))}</code>${l === "en" ? ":" : "\uFF1A"}${escapeHtml(copyText("panel.error.code-note", l))}</div>`
923
941
  );
924
942
  if (b.kind === "failed" && failRef) {
925
943
  const keys = [];
926
- if (typeof failRef.requestId === "string" && failRef.requestId) keys.push(`\u8BF7\u6C42 <code>${escapeHtml(failRef.requestId)}</code>`);
927
- if (typeof failRef.checkId === "string" && failRef.checkId) keys.push(`\u68C0\u67E5 <code>${escapeHtml(failRef.checkId)}</code>`);
928
- const at = formatLatchTime(failRef.atMs);
929
- if (at) keys.push(`\u5931\u8D25\u4E8E ${escapeHtml(at)}`);
944
+ if (typeof failRef.requestId === "string" && failRef.requestId) keys.push(`${escapeHtml(copyText("panel.error.query-request", l))} <code>${escapeHtml(failRef.requestId)}</code>`);
945
+ if (typeof failRef.checkId === "string" && failRef.checkId) keys.push(`${escapeHtml(copyText("panel.error.query-check", l))} <code>${escapeHtml(failRef.checkId)}</code>`);
946
+ const at = formatLatchTime(failRef.atMs, input.lang ?? l);
947
+ if (at) keys.push(`${escapeHtml(copyText("panel.error.failed-at", l, { time: at }))}`);
930
948
  if (keys.length > 0) {
931
- errLines.push(`<div class="dsh-upd-err">\u672C\u6B21\u67E5\u8BE2\u952E\uFF1A${keys.join(" \xB7 ")}\uFF08\u62FF\u7740\u5B83\u4EEC\u53BB\u65E5\u5FD7\u91CC\u5BF9\uFF09\u3002</div>`);
949
+ errLines.push(`<div class="dsh-upd-err">${copyText("panel.error.query-keys", l, { keys: keys.join(" \xB7 ") })}</div>`);
932
950
  }
933
951
  if (!failRef.volatile) {
934
952
  errLines.push(
935
- `<div class="dsh-upd-err">\u8BC1\u636E\u5DF2\u51BB\u7ED3\uFF1A\u590D\u5236\u8BCA\u65AD\u91CC\u7684\u7801\u3001\u7248\u672C\u3001\u7F16\u53F7\u90FD\u53D6\u81EA\u5931\u8D25\u65F6\u523B\uFF0C\u4E0D\u968F\u8F6E\u8BE2\u5237\u65B0\uFF1B\u4E0B\u4E00\u6B21\u67E5\u65B0\u7248\u6216\u5B89\u88C5\u4F1A\u66F4\u65B0\u5B83\u3002</div>`
953
+ `<div class="dsh-upd-err">${escapeHtml(copyText("panel.error.evidence-frozen", l))}</div>`
936
954
  );
937
955
  } else {
938
956
  errLines.push(
939
- `<div class="dsh-upd-err">\u8BFB\u6570\u77AC\u6001\u5931\u8D25\uFF1A\u4E0B\u4E00\u6B21\u6210\u529F\u8BFB\u6570\u4F1A\u81EA\u52A8\u89E3\u9664\uFF1B\u4E00\u76F4\u51FA\u73B0\u518D\u6309\u7A33\u5B9A\u7801\u6392\u67E5\u3002</div>`
957
+ `<div class="dsh-upd-err">${escapeHtml(copyText("panel.error.evidence-transient", l))}</div>`
940
958
  );
941
959
  }
942
960
  }
943
961
  } else {
944
- errLines.push(`<div class="dsh-upd-changelog-neutral">\u6682\u65E0\u5931\u8D25\uFF1A\u6B64\u65F6\u590D\u5236\u8BCA\u65AD\u7ED9\u51FA\u7684\u662F\u5F53\u524D\u72B6\u6001\u5FEB\u7167\u3002</div>`);
962
+ errLines.push(`<div class="dsh-upd-changelog-neutral">${escapeHtml(copyText("panel.error.no-failure", l))}</div>`);
945
963
  }
946
964
  if (input.showLogHint !== false) {
947
965
  errLines.push(
948
- `<div class="dsh-upd-log">\u6DF1\u6316\u770B\u65E5\u5FD7\uFF1A\u6309\u63D2\u4EF6\u6807\u8BC6 <code>${escapeHtml(pluginId)}</code> \u8FC7\u6EE4 <code>${LOG_EVENT_CALL}</code>\u3001<code>${LOG_EVENT_CALL_FAIL}</code>\u3001<code>${LOG_EVENT_INSTALL_EXEC}</code> \u4E09\u4E2A\u4E8B\u4EF6\u3002</div>`
966
+ `<div class="dsh-upd-log">${copyText("panel.error.log-hint", l, {
967
+ pluginId: `<code>${escapeHtml(pluginId)}</code>`,
968
+ e1: `<code>${LOG_EVENT_CALL}</code>`,
969
+ e2: `<code>${LOG_EVENT_CALL_FAIL}</code>`,
970
+ e3: `<code>${LOG_EVENT_INSTALL_EXEC}</code>`
971
+ })}</div>`
949
972
  );
950
973
  if (b.kind === "failed" && failRef && (failRef.requestId || failRef.checkId)) {
951
974
  errLines.push(
952
- `<div class="dsh-upd-log">\u51ED\u4E0A\u9762\u7684\u8BF7\u6C42\uFF0F\u68C0\u67E5\u7F16\u53F7\u5728 <code>${LOG_EVENT_CALL_FAIL}</code> \u91CC\u5BF9\u4E0A\uFF1B\u57FA\u7EBF\u8017\u65F6\u770B <code>${LOG_EVENT_CALL}</code>\uFF0C\u6267\u884C\u7ED3\u679C\u770B <code>${LOG_EVENT_INSTALL_EXEC}</code>\u3002</div>`
975
+ `<div class="dsh-upd-log">${copyText("panel.error.log-follow", l, {
976
+ eFail: `<code>${LOG_EVENT_CALL_FAIL}</code>`,
977
+ eCall: `<code>${LOG_EVENT_CALL}</code>`,
978
+ eExec: `<code>${LOG_EVENT_INSTALL_EXEC}</code>`
979
+ })}</div>`
953
980
  );
954
981
  }
955
982
  }
956
983
  if (copyNotice) errLines.push(`<div class="dsh-upd-copy" role="status">${escapeHtml(copyNotice)}</div>`);
957
- chapters.push(chapterOf(4, errLines.join("")));
984
+ chapters.push(chapterOf(4, errLines.join(""), "", l));
958
985
  }
959
986
  chapters.push(
960
987
  chapterOf(
961
988
  5,
962
- view.showManual && manual ? `<div class="dsh-upd-manual"><div>\u624B\u5DE5\u515C\u5E95\u547D\u4EE4\uFF08\u590D\u5236\u6574\u884C\u6267\u884C\uFF09\uFF1A</div><code>${escapeHtml(manual)}</code></div>` : `<div class="dsh-upd-manual"><div class="dsh-upd-changelog-neutral">\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684\u624B\u5DE5\u547D\u4EE4\uFF08\u8BA4\u4E0D\u51FA\u4F7F\u7528\u8303\u56F4\u6216\u5C5E\u6E90\u7801\u5B89\u88C5\u65F6\u4E0D\u7ED9\uFF09\u3002</div></div>`
989
+ view.showManual && manual ? `<div class="dsh-upd-manual"><div>${escapeHtml(copyText("panel.manual.heading", l))}</div><code>${escapeHtml(manual)}</code></div>` : `<div class="dsh-upd-manual"><div class="dsh-upd-changelog-neutral">${escapeHtml(copyText("panel.manual.absent", l))}</div></div>`,
990
+ "",
991
+ l
963
992
  )
964
993
  );
965
994
  parts.push('<div class="dsh-upd-body">' + chapters.join("\n") + "</div>");
966
995
  if (showActions && mode === "dialog") {
967
996
  parts.push(
968
- `<div class="dsh-upd-footer"><span class="dsh-upd-foot-note">\u5173\u95ED\u4E0D\u5F71\u54CD\u66F4\u65B0\uFF0C\u53EF\u968F\u65F6\u56DE\u6765\u67E5\u770B</span><button type="button" data-action="close-view" title="\u5173\u95ED\u7A97\u53E3\uFF0C\u66F4\u65B0\u4E0D\u53D7\u5F71\u54CD">\u5173\u95ED</button></div>`
997
+ '<div class="dsh-upd-footer"><span class="dsh-upd-foot-note">' + escapeHtml(copyText("panel.footer.note", l)) + `</span><button type="button" data-action="close-view" title="${escapeHtml(copyText("panel.footer.close-title", l))}">${escapeHtml(copyText("panel.footer.close", l))}</button></div>`
969
998
  );
970
999
  }
971
1000
  return parts.join("\n");
972
1001
  }
973
- function renderUpdatePanelHTML(input) {
974
- const view = panelViewModel(input);
975
- const kernel = renderUpdatePanelKernel(input, view);
976
- const d5 = normalizePanelTheme(input.theme) === "archive";
977
- const attr = d5 ? ' data-theme="archive"' : "";
1002
+ function renderUpdatePanelHTML(input, lang) {
1003
+ const l = normalizeLangTag(lang ?? input.lang ?? "zh");
1004
+ const view = panelViewModel(input, l);
1005
+ const kernel = renderUpdatePanelKernel(input, view, l);
1006
+ const archive = normalizePanelTheme(input.theme) === "archive";
1007
+ const attr = archive ? ' data-theme="archive"' : "";
978
1008
  const sealAttr = ` data-seal="${escapeHtml(view.seal.text)}" data-seal-tone="${view.seal.tone}"`;
979
1009
  const body = input.mode === "dialog" ? `<div class="dsh-upd-overlay" data-mode="dialog"><div class="dsh-upd" data-mode="dialog" data-plugin="${escapeHtml(input.pluginId)}"${sealAttr}${attr}>
980
1010
  ${kernel}
981
1011
  </div></div>` : `<div class="dsh-upd" data-mode="embedded" data-plugin="${escapeHtml(input.pluginId)}"${sealAttr}${attr}>
982
1012
  ${kernel}
983
1013
  </div>`;
984
- const css = d5 ? `${UPDATE_PANEL_CSS}
985
- ${UPDATE_PANEL_D5_CSS}` : UPDATE_PANEL_CSS;
1014
+ const css = archive ? `${UPDATE_PANEL_CSS}
1015
+ ${UPDATE_PANEL_ARCHIVE_CSS}` : UPDATE_PANEL_CSS;
986
1016
  return `<style>${css}</style>
987
1017
  ${body}`;
988
1018
  }
@@ -1076,17 +1106,31 @@ function mountUpdatePanel(container, options) {
1076
1106
  if (mode !== "embedded" && mode !== "dialog") {
1077
1107
  throw new Error(`[dsh-plugin-update] \u6446\u653E\u5F62\u6001\u975E\u6CD5\uFF1A\u53EA\u6536 embedded \u6216 dialog\uFF08\u6536\u5230 ${JSON.stringify(options.mode)}\uFF09`);
1078
1108
  }
1079
- if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive" && options.theme !== "d5-paper") {
1080
- throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08d5-paper \u4E3A\u65E7\u522B\u540D\u4ECD\u53EF\u7528\uFF09\uFF08\u6536\u5230 ${JSON.stringify(options.theme)}\uFF09`);
1109
+ if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive") {
1110
+ throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08\u6536\u5230 ${JSON.stringify(options.theme)}\uFF09`);
1081
1111
  }
1082
1112
  let theme = normalizePanelTheme(options.theme ?? "default");
1113
+ const localeOpt = options.locale ?? void 0;
1114
+ if (localeOpt !== void 0 && localeOpt !== null) {
1115
+ const isStr = typeof localeOpt === "string";
1116
+ const isObj = typeof localeOpt === "object" && typeof localeOpt.getActive === "function";
1117
+ if (!isStr && !isObj) {
1118
+ throw new Error("[dsh-plugin-update] invalid locale: expected zh / en / BCP47 or { getActive(), subscribe? }");
1119
+ }
1120
+ if (isStr && !localeOpt.trim()) {
1121
+ throw new Error("[dsh-plugin-update] invalid locale: empty string");
1122
+ }
1123
+ }
1124
+ function currentLang() {
1125
+ return resolveLang(localeOpt);
1126
+ }
1083
1127
  let showOthers = options.showOthers === true;
1084
1128
  let changelogCollapsed = false;
1085
1129
  const call = options.call;
1086
1130
  const onRestartRequested = options.onRestartRequested;
1087
1131
  const onCloseRequested = typeof options.onCloseRequested === "function" ? options.onCloseRequested : null;
1088
1132
  let lastHTML = "";
1089
- const copyText = options.copyText ?? defaultCopyText;
1133
+ const copyTextOut = options.copyText ?? defaultCopyText;
1090
1134
  const skipStore = options.skipStore ?? createBrowserSkipStore(pluginId);
1091
1135
  const hostKind = typeof options.hostKind === "string" && options.hostKind ? options.hostKind : null;
1092
1136
  const profileNameOption = typeof options.profileName === "string" && options.profileName ? options.profileName : null;
@@ -1271,12 +1315,86 @@ function mountUpdatePanel(container, options) {
1271
1315
  let envHostKind = null;
1272
1316
  function sayCopy(text) {
1273
1317
  copyNotice = text;
1318
+ }
1319
+ function sayCopyKey(key, values) {
1320
+ try {
1321
+ copyNotice = copyText(key, currentLang(), values);
1322
+ } catch {
1323
+ copyNotice = copyText(key, "zh", values);
1324
+ }
1274
1325
  try {
1275
1326
  noticeExpiresAt = Date.now() + 5e3;
1276
1327
  } catch {
1277
1328
  noticeExpiresAt = 0;
1278
1329
  }
1279
1330
  }
1331
+ function setStableHTML(target, html) {
1332
+ try {
1333
+ const g = globalThis;
1334
+ const doc = g["document"];
1335
+ const el = target;
1336
+ if (!doc || typeof el.querySelectorAll !== "function") {
1337
+ target.innerHTML = html;
1338
+ return;
1339
+ }
1340
+ let focusAction = null;
1341
+ try {
1342
+ const active = doc.activeElement;
1343
+ if (active && typeof active.getAttribute === "function") {
1344
+ focusAction = active.getAttribute("data-action");
1345
+ }
1346
+ } catch {
1347
+ focusAction = null;
1348
+ }
1349
+ let scrolls = [];
1350
+ try {
1351
+ const nodes = el.querySelectorAll(".dsh-upd-body");
1352
+ if (nodes) {
1353
+ for (let i = 0; i < nodes.length; i++) {
1354
+ const n = nodes[i];
1355
+ scrolls.push(typeof n.scrollTop === "number" ? n.scrollTop : 0);
1356
+ }
1357
+ }
1358
+ } catch {
1359
+ scrolls = [];
1360
+ }
1361
+ target.innerHTML = html;
1362
+ try {
1363
+ const bodies = el.querySelectorAll(".dsh-upd-body");
1364
+ if (bodies) {
1365
+ for (let i = 0; i < bodies.length && i < scrolls.length; i++) {
1366
+ const n = bodies[i];
1367
+ try {
1368
+ n.scrollTop = scrolls[i];
1369
+ } catch {
1370
+ }
1371
+ }
1372
+ }
1373
+ } catch {
1374
+ }
1375
+ try {
1376
+ if (focusAction && typeof el.querySelector === "function") {
1377
+ const next = el.querySelector('[data-action="' + focusAction + '"]');
1378
+ if (next && typeof next.focus === "function") {
1379
+ try {
1380
+ next.focus.call(next, { preventScroll: true });
1381
+ } catch {
1382
+ try {
1383
+ next.focus.call(next);
1384
+ } catch {
1385
+ }
1386
+ }
1387
+ }
1388
+ }
1389
+ } catch {
1390
+ }
1391
+ } catch {
1392
+ try {
1393
+ target.innerHTML = html;
1394
+ } catch {
1395
+ }
1396
+ }
1397
+ }
1280
1398
  function queueArgs() {
1281
1399
  return { includeQueue: true, includeEnv: true, showOthers, ...requestId ? { requestId } : {} };
1282
1400
  }
@@ -1289,11 +1407,13 @@ function mountUpdatePanel(container, options) {
1289
1407
  }
1290
1408
  const latest = snapshot?.latestVersion ?? null;
1291
1409
  const skippedLatest = !!latest && validReleaseVersion(latest) && skipStore.has(latest);
1410
+ const langNow = currentLang();
1292
1411
  const nextHTML = renderUpdatePanelHTML({
1293
1412
  snapshot,
1294
1413
  manual,
1295
1414
  queue,
1296
1415
  busyAct,
1416
+ lang: langNow,
1297
1417
  skippedLatest,
1298
1418
  lastError: latch?.code ?? null,
1299
1419
  errorKind: latch?.kind ?? null,
@@ -1312,7 +1432,7 @@ function mountUpdatePanel(container, options) {
1312
1432
  });
1313
1433
  if (nextHTML !== lastHTML) {
1314
1434
  lastHTML = nextHTML;
1315
- container.innerHTML = nextHTML;
1435
+ setStableHTML(container, nextHTML);
1316
1436
  }
1317
1437
  }
1318
1438
  function applyStatusReply(reply, via) {
@@ -1456,7 +1576,7 @@ function mountUpdatePanel(container, options) {
1456
1576
  case "check": {
1457
1577
  if (busyAct) return;
1458
1578
  busyAct = "check";
1459
- copyNotice = "\u6B63\u5728\u67E5\u65B0\u7248\u2026";
1579
+ copyNotice = copyText("panel.toast.checking", currentLang());
1460
1580
  render();
1461
1581
  try {
1462
1582
  const reply = await call(phoneNames.updateCheck, queueArgs());
@@ -1468,7 +1588,13 @@ function mountUpdatePanel(container, options) {
1468
1588
  setLatch(userThrowLatch("check", err));
1469
1589
  } finally {
1470
1590
  busyAct = null;
1471
- if (copyNotice === "\u6B63\u5728\u67E5\u65B0\u7248\u2026") copyNotice = null;
1591
+ try {
1592
+ const zh = copyText("panel.toast.checking", "zh");
1593
+ const en = copyText("panel.toast.checking", "en");
1594
+ if (copyNotice === zh || copyNotice === en) copyNotice = null;
1595
+ } catch {
1596
+ copyNotice = null;
1597
+ }
1472
1598
  }
1473
1599
  render();
1474
1600
  maybeAutoChangelog();
@@ -1477,7 +1603,7 @@ function mountUpdatePanel(container, options) {
1477
1603
  case "install": {
1478
1604
  if (busyAct) return;
1479
1605
  busyAct = "install";
1480
- copyNotice = "\u6B63\u5728\u5B89\u88C5\u2026";
1606
+ copyNotice = copyText("panel.toast.installing", currentLang());
1481
1607
  render();
1482
1608
  try {
1483
1609
  if (!receipt) {
@@ -1528,7 +1654,13 @@ function mountUpdatePanel(container, options) {
1528
1654
  setLatch(userThrowLatch("install", err));
1529
1655
  } finally {
1530
1656
  busyAct = null;
1531
- if (copyNotice === "\u6B63\u5728\u5B89\u88C5\u2026") copyNotice = null;
1657
+ try {
1658
+ const zh = copyText("panel.toast.installing", "zh");
1659
+ const en = copyText("panel.toast.installing", "en");
1660
+ if (copyNotice === zh || copyNotice === en) copyNotice = null;
1661
+ } catch {
1662
+ copyNotice = null;
1663
+ }
1532
1664
  }
1533
1665
  render();
1534
1666
  maybeAutoChangelog();
@@ -1556,10 +1688,10 @@ function mountUpdatePanel(container, options) {
1556
1688
  case "copy-manual": {
1557
1689
  if (manual) {
1558
1690
  try {
1559
- await copyText(manual);
1560
- sayCopy("\u624B\u5DE5\u547D\u4EE4\u5DF2\u590D\u5236\uFF0C\u7C98\u5230\u7EC8\u7AEF\u6574\u884C\u6267\u884C\u5373\u53EF\u3002");
1691
+ await copyTextOut(manual);
1692
+ sayCopyKey("panel.toast.copy-manual-ok");
1561
1693
  } catch {
1562
- sayCopy("\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u4E0A\u9762\u7684\u547D\u4EE4\u3002");
1694
+ sayCopyKey("panel.toast.copy-manual-fail");
1563
1695
  }
1564
1696
  }
1565
1697
  render();
@@ -1585,10 +1717,10 @@ function mountUpdatePanel(container, options) {
1585
1717
  ].map((s) => redactForCopy(s));
1586
1718
  const text2 = diagCopyFormat === "line" ? segs.join(" \xB7 ") : segs.join("\n");
1587
1719
  try {
1588
- await copyText(text2);
1589
- sayCopy("\u5DF2\u590D\u5236\u5F53\u524D\u72B6\u6001\uFF08\u65E0\u5931\u8D25\uFF09\uFF0C\u76F4\u63A5\u7C98\u7ED9\u63D2\u4EF6\u4F5C\u8005\u5373\u53EF\uFF08\u5DF2\u8131\u654F\uFF09\u3002");
1720
+ await copyTextOut(text2);
1721
+ sayCopyKey("panel.toast.copy-state-ok");
1590
1722
  } catch {
1591
- sayCopy("\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u4E0A\u9762\u7684\u4FE1\u606F\u3002");
1723
+ sayCopyKey("panel.toast.copy-diag-fail");
1592
1724
  }
1593
1725
  render();
1594
1726
  return;
@@ -1611,20 +1743,21 @@ function mountUpdatePanel(container, options) {
1611
1743
  route: null,
1612
1744
  diag: frozen?.diag ?? null,
1613
1745
  manual,
1614
- format: diagCopyFormat
1746
+ format: diagCopyFormat,
1747
+ lang: currentLang()
1615
1748
  });
1616
1749
  try {
1617
- await copyText(text);
1618
- sayCopy("\u8BCA\u65AD\u5DF2\u590D\u5236\uFF0C\u76F4\u63A5\u7C98\u7ED9\u63D2\u4EF6\u4F5C\u8005\u5373\u53EF\uFF08\u5DF2\u8131\u654F\uFF09\u3002");
1750
+ await copyTextOut(text);
1751
+ sayCopyKey("panel.toast.copy-diag-ok");
1619
1752
  } catch {
1620
- sayCopy("\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u4E0A\u9762\u7684\u4FE1\u606F\u3002");
1753
+ sayCopyKey("panel.toast.copy-diag-fail");
1621
1754
  }
1622
1755
  render();
1623
1756
  return;
1624
1757
  }
1625
1758
  case "dismiss-failure": {
1626
1759
  clearLatch();
1627
- sayCopy("\u5DF2\u786E\u8BA4\u8BE5\u5931\u8D25\u63D0\u793A\uFF1B\u4E0B\u6B21\u67E5\u65B0\u7248\u6216\u5B89\u88C5\u5C06\u91CD\u65B0\u8BC4\u4F30\u3002");
1760
+ sayCopyKey("panel.toast.failure-dismissed");
1628
1761
  render();
1629
1762
  return;
1630
1763
  }
@@ -1644,12 +1777,12 @@ function mountUpdatePanel(container, options) {
1644
1777
  try {
1645
1778
  if (typeof onRestartRequested === "function") {
1646
1779
  await onRestartRequested();
1647
- sayCopy("\u5DF2\u6309\u8C03\u7528\u65B9\u7684\u91CD\u542F\u6D41\u7A0B\u5904\u7406\uFF1B\u91CD\u542F\u540E\u65B0\u7248\u751F\u6548\u3002");
1780
+ sayCopyKey("panel.toast.restart-delegated");
1648
1781
  } else {
1649
- sayCopy("\u672C\u5BBF\u4E3B\u672A\u63D0\u4F9B\u91CD\u542F\u5165\u53E3\uFF1A\u8BF7\u624B\u52A8\u91CD\u542F\u5BBF\u4E3B\uFF0C\u91CD\u542F\u540E\u65B0\u7248\u751F\u6548\u3002");
1782
+ sayCopyKey("panel.toast.restart-manual");
1650
1783
  }
1651
1784
  } catch {
1652
- sayCopy("\u91CD\u542F\u5165\u53E3\u8C03\u7528\u5931\u8D25\uFF1A\u8BF7\u624B\u52A8\u91CD\u542F\u5BBF\u4E3B\uFF0C\u91CD\u542F\u540E\u65B0\u7248\u751F\u6548\u3002");
1785
+ sayCopyKey("panel.toast.restart-failed");
1653
1786
  }
1654
1787
  render();
1655
1788
  return;
@@ -1668,8 +1801,8 @@ function mountUpdatePanel(container, options) {
1668
1801
  render();
1669
1802
  }
1670
1803
  async function setTheme(next) {
1671
- if (next !== "default" && next !== "archive" && next !== "d5-paper") {
1672
- throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08d5-paper \u4E3A\u65E7\u522B\u540D\u4ECD\u53EF\u7528\uFF09\uFF08\u6536\u5230 ${JSON.stringify(next)}\uFF09`);
1804
+ if (next !== "default" && next !== "archive") {
1805
+ throw new Error(`[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08\u6536\u5230 ${JSON.stringify(next)}\uFF09`);
1673
1806
  }
1674
1807
  theme = normalizePanelTheme(next);
1675
1808
  render();
@@ -1718,6 +1851,10 @@ function mountUpdatePanel(container, options) {
1718
1851
  function unmount() {
1719
1852
  if (!mounted) return;
1720
1853
  mounted = false;
1854
+ try {
1855
+ unsubLang();
1856
+ } catch {
1857
+ }
1721
1858
  autoSeq++;
1722
1859
  changelogInflight.clear();
1723
1860
  try {
@@ -1733,6 +1870,9 @@ function mountUpdatePanel(container, options) {
1733
1870
  } catch {
1734
1871
  }
1735
1872
  }
1873
+ const unsubLang = subscribeLang(() => {
1874
+ render();
1875
+ }, localeOpt);
1736
1876
  render();
1737
1877
  try {
1738
1878
  container.addEventListener?.("click", onClick);
@@ -1744,6 +1884,7 @@ function mountUpdatePanel(container, options) {
1744
1884
  }
1745
1885
  const timer = getTimer();
1746
1886
  const handle = timer.set(() => {
1887
+ if (busyAct) return;
1747
1888
  void refresh();
1748
1889
  }, pollMs);
1749
1890
  try {
@@ -1809,8 +1950,8 @@ export {
1809
1950
  PANEL_DIAG_MAX_CHARS,
1810
1951
  PANEL_POLL,
1811
1952
  QUEUE_INTENT_TTL_MS,
1953
+ UPDATE_PANEL_ARCHIVE_CSS,
1812
1954
  UPDATE_PANEL_CSS,
1813
- UPDATE_PANEL_D5_CSS,
1814
1955
  blockedCopy,
1815
1956
  buildDiagnosticText,
1816
1957
  buildChangelogPhoneName2 as buildPanelChangelogPhoneName,
@@ -1831,6 +1972,7 @@ export {
1831
1972
  mountUpdatePanel,
1832
1973
  normalizePanelTheme,
1833
1974
  normalizeQueueState,
1975
+ panelSealFor,
1834
1976
  panelViewModel,
1835
1977
  parseChangelog2 as parseChangelog,
1836
1978
  pendingAutoCheck,