dsh-plugin-update 0.5.8 → 0.7.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.
@@ -1,5 +1,7 @@
1
1
  // AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/panel-batch.ts
2
2
  import { assertPrefix, MIN_PANEL_POLL_MS } from "./config.js";
3
+ import { copyText as bilingualText } from "./bilingual.js";
4
+ import { normalizeLangTag, resolveLang, subscribeLang } from "./lang.js";
3
5
  import { validReleaseVersion } from "./service.js";
4
6
  import { shouldFetchChangelog } from "./changelog.js";
5
7
  import {
@@ -8,13 +10,14 @@ import {
8
10
  } from "./batch.js";
9
11
  import {
10
12
  UPDATE_PANEL_CSS,
11
- UPDATE_PANEL_D5_CSS,
13
+ UPDATE_PANEL_ARCHIVE_CSS,
12
14
  buildDiagnosticText,
13
15
  createBrowserSkipStore,
14
16
  failureCopy,
15
17
  isKnownFailureCode,
16
18
  normalizePanelTheme,
17
- renderUpdatePanelHTML
19
+ renderUpdatePanelHTML,
20
+ themeTokensStyleFor
18
21
  } from "./panel.js";
19
22
  const BATCH_PANEL_POLL = {
20
23
  defaultMs: 1500,
@@ -27,7 +30,9 @@ function buildBatchPhoneNames(prefix) {
27
30
  check: p + ".batchCheck",
28
31
  install: p + ".batchInstall",
29
32
  resume: p + ".batchResume",
30
- cancel: p + ".batchCancel"
33
+ cancel: p + ".batchCancel",
34
+ prefs: p + ".batchPrefs",
35
+ prefsSave: p + ".batchPrefsSave"
31
36
  };
32
37
  }
33
38
  function batchLedgerCounts(rows) {
@@ -53,44 +58,122 @@ function batchLedgerCounts(rows) {
53
58
  }
54
59
  return counts;
55
60
  }
56
- function batchLedgerText(counts) {
61
+ function batchLedgerText(counts, lang) {
62
+ const l = langOf(lang);
57
63
  const parts = [];
58
- if (counts.updatable > 0) parts.push(counts.updatable + " \u5BB6\u53EF\u66F4\u65B0");
59
- if (counts.installing > 0) parts.push(counts.installing + " \u5BB6\u5B89\u88C5\u4E2D");
60
- if (counts.pending > 0) parts.push(counts.pending + " \u5BB6\u5F85\u67E5");
61
- if (counts.restart > 0) parts.push(counts.restart + " \u5BB6\u5F85\u91CD\u542F");
62
- if (counts.failed > 0) parts.push(counts.failed + " \u5BB6\u5931\u8D25");
63
- if (counts.skipped > 0) parts.push(counts.skipped + " \u5BB6\u5DF2\u8DF3\u8FC7");
64
- if (counts.settled > 0) parts.push(counts.settled + " \u5BB6\u5DF2\u6700\u65B0");
65
- return parts.length > 0 ? parts.join(" \xB7 ") : "\u8FD8\u6CA1\u6709\u76EE\u6807";
64
+ if (counts.updatable > 0) parts.push(batchText("batch.summary.updatable", l, { n: String(counts.updatable) }));
65
+ if (counts.installing > 0) parts.push(batchText("batch.summary.installing", l, { n: String(counts.installing) }));
66
+ if (counts.pending > 0) parts.push(batchText("batch.summary.pending", l, { n: String(counts.pending) }));
67
+ if (counts.restart > 0) parts.push(batchText("batch.summary.restart", l, { n: String(counts.restart) }));
68
+ if (counts.failed > 0) parts.push(batchText("batch.summary.failed", l, { n: String(counts.failed) }));
69
+ if (counts.skipped > 0) parts.push(batchText("batch.summary.skipped", l, { n: String(counts.skipped) }));
70
+ if (counts.settled > 0) parts.push(batchText("batch.summary.settled", l, { n: String(counts.settled) }));
71
+ return parts.length > 0 ? parts.join(" \xB7 ") : batchText("batch.summary.empty", l);
66
72
  }
67
- function batchQueuedStatus(position) {
68
- if (typeof position !== "number" || !Number.isFinite(position) || position < 1) return "\u5DF2\u6392\u961F \xB7 \u7B49\u524D\u9762\u5B89\u88C5\u5B8C";
69
- return "\u5DF2\u6392\u961F \xB7 \u524D\u65B9 " + position + " \u4E2A";
73
+ function batchQueuedStatus(position, lang) {
74
+ const l = langOf(lang);
75
+ if (typeof position !== "number" || !Number.isFinite(position) || position < 1) return batchText("batch.row.queued-generic", l);
76
+ return batchText("batch.row.queued-n", l, { n: String(position) });
70
77
  }
71
- function batchRowStatus(row, skippedVersion) {
78
+ function knowledgeEntryOf(inventory, key) {
79
+ if (!isObject(inventory)) return null;
80
+ const entries = inventory["entries"];
81
+ if (!isObject(entries)) return null;
82
+ const item = entries[key];
83
+ if (!isObject(item)) return null;
84
+ const e = item;
85
+ const canInstallRaw = e["canInstall"];
86
+ return {
87
+ lastCheckedAt: typeof e["lastCheckedAt"] === "number" ? e["lastCheckedAt"] : 0,
88
+ installedVersion: typeof e["installedVersion"] === "string" ? e["installedVersion"] : null,
89
+ latestVersion: typeof e["latestVersion"] === "string" ? e["latestVersion"] : null,
90
+ canInstall: typeof canInstallRaw === "boolean" ? canInstallRaw : null,
91
+ error: typeof e["error"] === "string" ? e["error"] : null
92
+ };
93
+ }
94
+ function langOf(lang) {
95
+ try {
96
+ return normalizeLangTag(typeof lang === "string" ? lang : resolveLang(lang));
97
+ } catch {
98
+ return "zh";
99
+ }
100
+ }
101
+ function batchText(key, lang, values) {
102
+ try {
103
+ return bilingualText(key, langOf(lang), values);
104
+ } catch {
105
+ return "";
106
+ }
107
+ }
108
+ function hasUnfinishedRows(rows, session) {
109
+ if (isObject(session)) {
110
+ const entries = session["entries"];
111
+ if (Array.isArray(entries)) {
112
+ for (const item of entries) {
113
+ if (!isObject(item)) continue;
114
+ const phase = item["phase"];
115
+ if (typeof phase === "string" && !isTerminalPhase(asBatchPhase(phase))) return true;
116
+ }
117
+ return false;
118
+ }
119
+ }
120
+ if (rows.length === 0) return false;
121
+ return !rows.every((row) => isTerminalPhase(asBatchPhase(row.phase)));
122
+ }
123
+ function unfinishedCount(rows, session) {
124
+ if (isObject(session)) {
125
+ const entries = session["entries"];
126
+ if (Array.isArray(entries)) {
127
+ let n = 0;
128
+ for (const item of entries) {
129
+ if (!isObject(item)) continue;
130
+ const phase = item["phase"];
131
+ if (typeof phase === "string" && !isTerminalPhase(asBatchPhase(phase))) n += 1;
132
+ }
133
+ return n;
134
+ }
135
+ }
136
+ return rows.filter((row) => !isTerminalPhase(asBatchPhase(row.phase))).length;
137
+ }
138
+ function batchRowStatus(row, skippedVersion, lang) {
139
+ const l = langOf(lang);
72
140
  const skipped = typeof skippedVersion === "string" && skippedVersion ? skippedVersion : null;
73
- if (skipped !== null && asBatchPhase(row.phase) === "ready") return "\u5DF2\u8DF3\u8FC7 " + skipped;
141
+ if (skipped !== null && asBatchPhase(row.phase) === "ready") return batchText("batch.row.skipped", l, { version: skipped });
74
142
  switch (asBatchPhase(row.phase)) {
75
143
  case "pending":
76
- return "\u7B49\u5B83\uFF0C\u8F6E\u5230\u5C31\u81EA\u52A8\u67E5\u65B0\u7248";
144
+ return batchText("batch.row.wait-turn", l);
77
145
  case "checking":
78
- return "\u6B63\u5728\u67E5\u65B0\u7248\uFF0C\u7A0D\u7B49";
146
+ return batchText("batch.row.checking", l);
79
147
  case "ready":
80
- return row.targetVersion ? "\u70B9\u300C\u5B89\u88C5\u8FD9\u5BB6\u300D\u5B89\u88C5 " + row.targetVersion : "\u70B9\u300C\u5B89\u88C5\u8FD9\u5BB6\u300D\u5B89\u88C5\u65B0\u7248";
148
+ return row.targetVersion ? batchText("batch.row.cta-version", l, { version: row.targetVersion }) : batchText("batch.row.cta-generic", l);
81
149
  case "installing":
82
- return "\u6B63\u5728\u5B89\u88C5\uFF0C\u522B\u52A8";
150
+ return batchText("batch.row.installing", l);
83
151
  case "current":
84
- return "\u5DF2\u662F\u6700\u65B0\uFF0C\u4E0D\u7528\u52A8";
152
+ return batchText("batch.row.current", l);
85
153
  case "done":
86
- return row.restartRequired === true ? "\u5B89\u88C5\u597D\u4E86\uFF0C\u91CD\u542F\u5BBF\u4E3B\u624D\u751F\u6548" : "\u5B89\u88C5\u597D\u4E86\uFF0C\u4E0D\u7528\u52A8";
154
+ return row.restartRequired === true ? batchText("batch.row.done-restart", l) : batchText("batch.row.done", l);
87
155
  case "failed":
88
- return "\u5B89\u88C5\u6CA1\u6210\u529F\uFF0C\u70B9\u300C\u91CD\u8BD5\u300D\u518D\u6765\u4E00\u6B21";
156
+ return batchText("batch.row.failed-retry", l);
89
157
  case "skipped":
90
- return "\u8FD9\u4E00\u7248\u5DF2\u8DF3\u8FC7\uFF0C\u4E0D\u7528\u52A8";
158
+ return batchText("batch.row.skipped-idle", l);
91
159
  default:
92
- return "\u72B6\u6001\u8BA4\u4E0D\u51FA\uFF0C\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u91CD\u67E5\u4E00\u6B21";
160
+ return batchText("batch.row.unknown", l);
161
+ }
162
+ }
163
+ function batchRowKnowledgeText(row, entry, lang) {
164
+ if (asBatchPhase(row.phase) !== "pending" || !entry) return null;
165
+ if (entry.error) return batchText("batch.row.failed", lang);
166
+ if (entry.latestVersion && entry.installedVersion && entry.latestVersion !== entry.installedVersion) {
167
+ return batchText("batch.row.update", lang, { version: entry.latestVersion });
168
+ }
169
+ if (entry.latestVersion && entry.installedVersion && entry.latestVersion === entry.installedVersion) {
170
+ return batchText("batch.row.current", lang);
171
+ }
172
+ if (entry.latestVersion && !entry.installedVersion) {
173
+ return batchText("batch.row.update", lang, { version: entry.latestVersion });
93
174
  }
175
+ if (entry.lastCheckedAt > 0) return batchText("batch.row.current", lang);
176
+ return batchText("batch.row.never", lang);
94
177
  }
95
178
  function renderBatchPanelHTML(input) {
96
179
  const rows = Array.isArray(input.rows) ? input.rows : [];
@@ -105,121 +188,161 @@ function renderBatchPanelHTML(input) {
105
188
  const installing = counts.installing > 0;
106
189
  const macroBusy = input.inFlight === true || installing;
107
190
  const total = rows.length;
108
- const terminal = total > 0 && rows.every((row) => isTerminalPhase(asBatchPhase(row.phase)));
109
- const stalled = total > 0 && !terminal && !rows.some((row) => {
110
- const phase = asBatchPhase(row.phase);
111
- return phase === "installing" || phase === "checking";
112
- });
113
191
  const disabled = macroBusy ? " disabled" : "";
114
192
  const notice = typeof input.notice === "string" && input.notice ? input.notice : null;
115
193
  const noticeKey = typeof input.noticeKey === "string" && input.noticeKey ? input.noticeKey : null;
116
194
  const detailNoticeKey = notice !== null && noticeKey !== null && noticeKey === expandedKey ? noticeKey : null;
117
195
  const changelogs = input.changelogs && typeof input.changelogs === "object" ? input.changelogs : {};
196
+ const lang = langOf(input["lang"]);
118
197
  const ctx = {
119
198
  expanded: false,
120
199
  busy: installing,
121
200
  inFlight: input.inFlight === true,
122
201
  theme,
202
+ themeTokens: input.themeTokens ?? void 0,
123
203
  titles,
124
204
  skipped: null,
125
205
  notice: null,
126
- changelogMarkdown: null
206
+ changelogMarkdown: null,
207
+ lang,
208
+ knowledge: null,
209
+ inRound: false
127
210
  };
128
211
  const parts = [];
129
212
  parts.push('<div class="dsh-upd-batch-head">');
130
- parts.push('<div class="dsh-upd-batch-title">\u66F4\u65B0\u6863\u6848 \xB7 <i>\u603B\u8D26</i></div>');
213
+ parts.push('<div class="dsh-upd-batch-title">' + escapeHtml(batchText("batch.header.title", lang)) + " \xB7 <i>" + escapeHtml(batchText("batch.seal.ledger", lang)) + "</i></div>");
131
214
  parts.push('<div class="dsh-upd-batch-macros">');
132
- parts.push('<button type="button" data-act="check"' + disabled + ' title="\u91CD\u65B0\u8BFB\u53D6\u6279\u91CF\u72B6\u6001\uFF08\u53EA\u8BFB\uFF09">\u68C0\u67E5\u66F4\u65B0</button>');
133
- parts.push('<button type="button" data-act="install" data-primary="1"' + disabled + ' title="\u628A\u6709\u65B0\u7248\u7684\u51E0\u5BB6\u4E00\u6B21\u63D0\u4EA4\uFF1B\u540C\u4E00\u4F1A\u8BDD\u540C\u4E00\u5E42\u7B49\u7F16\u53F7">\u5168\u90E8\u66F4\u65B0</button>');
134
- if (mode === "dialog") parts.push('<button type="button" data-act="close" title="\u5173\u95ED\u7A97\u53E3\uFF0C\u6279\u91CF\u66F4\u65B0\u4E0D\u53D7\u5F71\u54CD">\u5173\u95ED</button>');
215
+ parts.push('<button type="button" data-act="check"' + disabled + ' title="' + escapeHtml(batchText("batch.action.check-title", lang)) + '">' + escapeHtml(batchText("batch.action.check", lang)) + "</button>");
216
+ parts.push('<button type="button" data-act="install" data-primary="1"' + disabled + ' title="' + escapeHtml(batchText("batch.action.install-all-title", lang)) + '">' + escapeHtml(batchText("batch.action.install-all", lang)) + "</button>");
217
+ if (mode === "dialog") parts.push('<button type="button" data-act="close" title="' + escapeHtml(batchText("batch.action.close-title", lang)) + '">' + escapeHtml(batchText("batch.action.close", lang)) + "</button>");
135
218
  parts.push("</div>");
136
219
  parts.push("</div>");
137
- const summary = loaded ? batchSummaryText(counts, rows, total, installing, lastError !== null) : "\u6B63\u5728\u8BFB\u53D6\u6279\u91CF\u66F4\u65B0\u72B6\u6001\u2026";
220
+ const summary = loaded ? batchSummaryText(counts, rows, total, installing, lastError !== null, lang) : batchText("batch.banner.loading", lang);
138
221
  parts.push('<div class="dsh-upd-batch-sum" role="status" aria-live="polite">' + escapeHtml(summary) + "</div>");
139
- parts.push(bannersHTML(rows, titles, lastError));
222
+ parts.push(bannersHTML(rows, titles, lastError, lang));
223
+ const session = isObject(input.session) ? input.session : null;
224
+ const unfinished = unfinishedCount(rows, session ?? void 0);
225
+ const showControls = total > 0 && hasUnfinishedRows(rows, session ?? void 0);
226
+ const prefs = isObject(input.prefs) ? input.prefs : null;
227
+ const checkOnOpen = prefs === null ? null : prefs["checkOnOpen"] === false ? false : true;
228
+ function roundEntryPhase(key) {
229
+ if (!session) return null;
230
+ const entries = session["entries"];
231
+ if (!Array.isArray(entries)) return null;
232
+ for (const item of entries) {
233
+ if (!isObject(item)) continue;
234
+ const e = item;
235
+ if (e["key"] === key && typeof e["phase"] === "string") return e["phase"];
236
+ }
237
+ return null;
238
+ }
140
239
  parts.push('<div class="dsh-upd-btable">');
141
240
  for (const row of rows) {
142
241
  const raw = skippedVersions[row.key];
143
242
  const skipped = typeof raw === "string" && raw ? raw : null;
144
243
  const expanded = row.key === expandedKey;
244
+ const entryPhase = roundEntryPhase(row.key);
245
+ const inRound = entryPhase !== null && !isTerminalPhase(asBatchPhase(entryPhase));
145
246
  parts.push(
146
247
  rowSetHTML(row, {
147
248
  ...ctx,
148
249
  expanded,
149
250
  skipped,
150
251
  notice: expanded && row.key === detailNoticeKey ? notice : null,
151
- changelogMarkdown: expanded && typeof changelogs[row.key] === "string" ? changelogs[row.key] : null
252
+ changelogMarkdown: expanded && typeof changelogs[row.key] === "string" ? changelogs[row.key] : null,
253
+ lang,
254
+ knowledge: asBatchPhase(row.phase) === "pending" && !inRound ? knowledgeEntryOf(input.inventory, row.key) : null,
255
+ inRound
152
256
  })
153
257
  );
154
258
  }
155
259
  parts.push("</div>");
156
260
  if (total > 0) {
157
- parts.push('<div class="dsh-upd-batch-ledger">' + escapeHtml(batchLedgerText(counts)) + "</div>");
261
+ parts.push('<div class="dsh-upd-batch-ledger">' + escapeHtml(batchLedgerText(counts, lang)) + "</div>");
158
262
  }
159
- if (total > 0 && !terminal) {
263
+ if (showControls) {
160
264
  const more = [];
161
- if (stalled) more.push('<button type="button" data-act="resume">\u63A5\u7740\u4E0A\u6B21</button>');
265
+ more.push(
266
+ '<button type="button" data-act="resume">' + escapeHtml(batchText("batch.action.resume", lang, { count: String(unfinished) })) + "</button>"
267
+ );
162
268
  const confirmCancel = input.confirmCancel === true;
269
+ const discardLabel = confirmCancel ? batchText("batch.action.confirm-discard", lang) : batchText("batch.action.discard", lang);
163
270
  more.push(
164
- '<button type="button" data-act="cancel"' + disabled + (confirmCancel ? ' data-confirm="1" title="\u518D\u70B9\u4E00\u6B21\u786E\u8BA4\u53D6\u6D88"' : ' title="\u53D6\u6D88\u8FD9\u4E00\u6279\uFF08\u8981\u70B9\u4E24\u6B21\u786E\u8BA4\uFF0C\u9632\u8BEF\u89E6\uFF09"') + ">" + (confirmCancel ? "\u786E\u8BA4\u53D6\u6D88\u8FD9\u4E00\u6279" : "\u53D6\u6D88\u8FD9\u4E00\u6279") + "</button>"
271
+ '<button type="button" data-act="cancel"' + disabled + (confirmCancel ? ' data-confirm="1" title="' + escapeHtml(batchText("batch.notice.cancel-confirm", lang)) + '"' : ' title="' + escapeHtml(batchText("batch.action.discard", lang)) + '"') + ">" + escapeHtml(discardLabel) + "</button>"
165
272
  );
166
273
  parts.push('<div class="dsh-upd-batch-more">' + more.join("") + "</div>");
274
+ parts.push('<div class="dsh-upd-batch-fact" role="note">' + escapeHtml(batchText("batch.fact.close-safe", lang)) + "</div>");
275
+ if (macroBusy) {
276
+ parts.push('<div class="dsh-upd-batch-fact" role="note">' + escapeHtml(batchText("batch.notice.busy-cancel", lang)) + "</div>");
277
+ }
278
+ }
279
+ if (prefs !== null && checkOnOpen !== null) {
280
+ parts.push(
281
+ '<div class="dsh-upd-batch-prefs"><button type="button" data-act="toggle-check-on-open" aria-pressed="' + (checkOnOpen ? "true" : "false") + '">' + escapeHtml((checkOnOpen ? "[x] " : "[ ] ") + batchText("batch.setting.check-on-open", lang)) + "</button></div>"
282
+ );
167
283
  }
168
284
  if (notice !== null && detailNoticeKey === null) {
169
285
  parts.push('<div class="dsh-upd-batch-notice" role="status">' + escapeHtml(notice) + "</div>");
170
286
  }
171
287
  const kernel = parts.join(String.fromCharCode(10));
172
- const d5 = normalizePanelTheme(theme) === "archive";
173
- const attr = d5 ? ' data-theme="archive"' : "";
174
- const seal = batchSealOf(counts);
175
- const root = '<div class="dsh-upd dsh-upd-batch" data-mode="' + mode + '" data-seal="' + escapeHtml(seal.text) + '" data-seal-tone="' + seal.tone + '"' + attr + ">\n" + kernel + "\n</div>";
288
+ const archive = normalizePanelTheme(theme) === "archive";
289
+ const attr = archive ? ' data-theme="archive"' : "";
290
+ const tokensStyle = themeTokensStyleFor(input.themeTokens ?? void 0);
291
+ const tokensAttr = tokensStyle ? ' style="' + tokensStyle + '"' : "";
292
+ const seal = batchSealOf(counts, lang);
293
+ const root = '<div class="dsh-upd dsh-upd-batch" data-mode="' + mode + '" data-seal="' + escapeHtml(seal.text) + '" data-seal-tone="' + seal.tone + '"' + attr + tokensAttr + ">\n" + kernel + "\n</div>";
176
294
  const body = mode === "dialog" ? '<div class="dsh-upd-overlay" data-mode="dialog">' + root + "</div>" : root;
177
- const css = d5 ? [UPDATE_PANEL_CSS, UPDATE_BATCH_PANEL_CSS, UPDATE_PANEL_D5_CSS, UPDATE_BATCH_PANEL_D5_CSS].join(String.fromCharCode(10)) : [UPDATE_PANEL_CSS, UPDATE_BATCH_PANEL_CSS].join(String.fromCharCode(10));
295
+ const css = archive ? [UPDATE_PANEL_CSS, UPDATE_BATCH_PANEL_CSS, UPDATE_PANEL_ARCHIVE_CSS, UPDATE_BATCH_PANEL_ARCHIVE_CSS].join(String.fromCharCode(10)) : [UPDATE_PANEL_CSS, UPDATE_BATCH_PANEL_CSS].join(String.fromCharCode(10));
178
296
  return "<style>" + css + "</style>\n" + body;
179
297
  }
180
- function batchSummaryText(counts, rows, total, installing, hasError) {
181
- if (hasError) return "\u521A\u624D\u90A3\u6B21\u6CA1\u6210\u529F\uFF1A\u770B\u4E0B\u9762\u7684\u7EA2\u6761\uFF0C\u7167\u5B83\u8BF4\u7684\u505A\u4E00\u6B21\u3002";
182
- if (total === 0) return "\u8FD8\u6CA1\u6709\u76EE\u6807\uFF1A\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u770B\u770B\u54EA\u51E0\u5BB6\u6709\u65B0\u7248\u3002";
298
+ function batchSummaryText(counts, rows, total, installing, hasError, lang) {
299
+ const l = langOf(lang);
300
+ if (hasError) return batchText("batch.hint.error", l);
301
+ if (total === 0) return batchText("batch.hint.empty", l);
183
302
  if (installing) {
184
303
  const canQueue = rows.filter((row) => asBatchPhase(row.phase) === "ready" && !isQueuedRow(row)).length;
185
- return canQueue > 0 ? "\u6B63\u5728\u5B89\u88C5 " + counts.installing + " \u5BB6\uFF1B\u8FD8\u6709 " + canQueue + " \u5BB6\u53EF\u4EE5\u70B9\u300C\u52A0\u5165\u961F\u5217\u300D\u6392\u961F\u7B49\u3002" : "\u6B63\u5728\u5B89\u88C5 " + counts.installing + " \u5BB6\uFF0C\u5B89\u88C5\u5B8C\u81EA\u52A8\u4E0B\u4E00\u5BB6\u3002";
304
+ return canQueue > 0 ? batchText("batch.hint.installing-queueable", l, { a: String(counts.installing), b: String(canQueue) }) : batchText("batch.hint.installing-auto", l, { a: String(counts.installing) });
186
305
  }
187
- if (counts.failed > 0) return counts.failed + " \u5BB6\u5B89\u88C5\u5931\u8D25\uFF1B\u7167\u4E0B\u9762\u7684\u5931\u8D25\u63D0\u793A\u9010\u5BB6\u91CD\u8BD5\u3002";
306
+ if (counts.failed > 0) return batchText("batch.hint.failed", l, { n: String(counts.failed) });
188
307
  if (counts.updatable > 0)
189
- return counts.updatable + " \u5BB6\u53EF\u66F4\u65B0\uFF1B\u70B9\u300C\u5168\u90E8\u66F4\u65B0\u300D\u4E00\u6B21\u5B89\u88C5\u5B8C\uFF0C\u4E5F\u53EF\u4EE5\u9010\u5BB6\u70B9\u300C\u5B89\u88C5\u8FD9\u5BB6\u300D\u3002";
190
- if (counts.restart > 0) return counts.restart + " \u5BB6\u5DF2\u5B89\u88C5\u597D\uFF0C\u91CD\u542F\u5BBF\u4E3B\u540E\u751F\u6548\u3002";
191
- if (counts.pending > 0) return counts.pending + " \u5BB6\u8FD8\u6CA1\u67E5\u8FC7\uFF1B\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u67E5\u4E00\u8F6E\u3002";
192
- return "\u5168\u90E8\u5DF2\u6700\u65B0\uFF0C\u6CA1\u6709\u8981\u505A\u7684\u3002";
308
+ return batchText("batch.hint.updatable", l, { n: String(counts.updatable) });
309
+ if (counts.restart > 0) return batchText("batch.hint.restart", l, { n: String(counts.restart) });
310
+ if (counts.pending > 0) return batchText("batch.hint.pending", l, { n: String(counts.pending) });
311
+ return batchText("batch.hint.done", l);
193
312
  }
194
- function batchSealOf(counts) {
195
- if (counts.failed > 0) return { text: "\u603B\u8D26", tone: "red" };
196
- if (counts.installing > 0 || counts.restart > 0) return { text: "\u603B\u8D26", tone: "yellow" };
197
- if (counts.updatable > 0) return { text: "\u603B\u8D26", tone: "green" };
198
- return { text: "\u603B\u8D26", tone: "ink" };
313
+ function batchSealOf(counts, lang) {
314
+ const text = batchText("batch.seal.ledger", langOf(lang));
315
+ if (counts.failed > 0) return { text, tone: "red" };
316
+ if (counts.installing > 0 || counts.restart > 0) return { text, tone: "yellow" };
317
+ if (counts.updatable > 0) return { text, tone: "green" };
318
+ return { text, tone: "ink" };
199
319
  }
200
- function bannersHTML(rows, titles, lastError) {
320
+ function bannersHTML(rows, titles, lastError, lang) {
321
+ const l = langOf(lang);
201
322
  const parts = [];
202
323
  if (lastError) {
203
- const copy = failureCopy(lastError);
324
+ const copy = failureCopy(lastError, l);
325
+ const detail = copy?.zh ?? failureCopy("unknown", l)?.zh ?? "";
326
+ const act = copy?.act ?? batchText("batch.banner.error-action-fallback", l);
204
327
  parts.push(
205
- '<div class="dsh-upd-banner" data-kind="failed" data-mini="\u963B" role="status"><div><strong>\u8FD9\u6B21\u6CA1\u6210\u529F\uFF08' + escapeHtml(lastError) + "\uFF09\uFF1A" + escapeHtml(copy ? copy.zh : "\u8BA4\u4E0D\u51FA\u5177\u4F53\u539F\u56E0") + "\u3002</strong></div><div>" + escapeHtml(copy ? copy.act : "\u5148\u91CD\u8BD5\u4E00\u6B21\uFF1B\u4E00\u76F4\u8FD9\u6837\u5C31\u628A\u590D\u5236\u8BCA\u65AD\u4EA4\u7ED9\u63D2\u4EF6\u4F5C\u8005\u3002") + "</div></div>"
328
+ '<div class="dsh-upd-banner" data-kind="failed" data-mini="\u963B" role="status"><div><strong>' + escapeHtml(batchText("batch.banner.error-title", l, { code: lastError, detail })) + "</strong></div><div>" + escapeHtml(act) + "</div></div>"
206
329
  );
207
330
  }
208
331
  const failed = rows.filter((row) => asBatchPhase(row.phase) === "failed" && !isQueueBusyRow(row));
209
332
  if (failed.length > 0) {
210
333
  const lines = failed.map((row) => {
211
334
  const code = row.error || "install-failed";
212
- const copy = failureCopy(code);
213
- return "<div>" + escapeHtml(titleOf(row, titles)) + "\uFF1A<code>" + escapeHtml(code) + "</code> " + escapeHtml(copy ? copy.zh : "\u8BA4\u4E0D\u51FA\u5177\u4F53\u539F\u56E0") + "</div>";
335
+ const copy = failureCopy(code, l);
336
+ return "<div>" + escapeHtml(titleOf(row, titles)) + "\uFF1A<code>" + escapeHtml(code) + "</code> " + escapeHtml(copy?.zh ?? failureCopy("unknown", l)?.zh ?? "") + "</div>";
214
337
  }).join("");
215
338
  parts.push(
216
- '<div class="dsh-upd-banner" data-kind="failed" data-mini="\u963B" role="status"><div><strong>' + failed.length + ' \u5BB6\u5B89\u88C5\u5931\u8D25\u3002</strong></div><div>\u9010\u5BB6\u70B9\u884C\u5185\u300C\u91CD\u8BD5\u300D\u518D\u6765\u4E00\u6B21\uFF1B\u4E00\u76F4\u5931\u8D25\u5C31\u628A\u590D\u5236\u8BCA\u65AD\u4EA4\u7ED9\u63D2\u4EF6\u4F5C\u8005\u3002</div><div class="dsh-upd-blist">' + lines + "</div></div>"
339
+ '<div class="dsh-upd-banner" data-kind="failed" data-mini="\u963B" role="status"><div><strong>' + escapeHtml(batchText("batch.banner.failed-title", l, { n: String(failed.length) })) + "</strong></div><div>" + escapeHtml(batchText("batch.banner.failed-action", l)) + '</div><div class="dsh-upd-blist">' + lines + "</div></div>"
217
340
  );
218
341
  }
219
342
  const restart = rows.filter((row) => row.restartRequired === true);
220
343
  if (restart.length > 0) {
221
344
  parts.push(
222
- '<div class="dsh-upd-banner" data-kind="restart" role="status"><div><strong>' + restart.length + ' \u5BB6\u5DF2\u5B89\u88C5\u597D\uFF0C\u91CD\u542F\u5BBF\u4E3B\u540E\u751F\u6548\u3002</strong></div><div>\u91CD\u542F\u5BBF\u4E3B\uFF0C\u8BA9\u65B0\u7248\u8DD1\u8D77\u6765\uFF1B\u8FD9\u662F\u6B63\u5E38\u7EC8\u6001\uFF0C\u4E0D\u662F\u5931\u8D25\u3002</div><div class="dsh-upd-blist">' + restart.map((row) => escapeHtml(titleOf(row, titles))).join("\u3001") + '</div><div><button type="button" data-act="restart" data-primary="1">\u91CD\u542F\u5BBF\u4E3B</button></div></div>'
345
+ '<div class="dsh-upd-banner" data-kind="restart" role="status"><div><strong>' + escapeHtml(batchText("batch.banner.restart-title", l, { n: String(restart.length) })) + "</strong></div><div>" + escapeHtml(batchText("batch.banner.restart-action", l)) + '</div><div class="dsh-upd-blist">' + restart.map((row) => escapeHtml(titleOf(row, titles))).join("\u3001") + '</div><div><button type="button" data-act="restart" data-primary="1">' + escapeHtml(batchText("batch.banner.restart-button", l)) + "</button></div></div>"
223
346
  );
224
347
  }
225
348
  return parts.join("");
@@ -236,35 +359,43 @@ function rowSetHTML(row, ctx) {
236
359
  const off = ctx.inFlight ? " disabled" : "";
237
360
  if (phase === "installing") {
238
361
  actions.push(
239
- '<button type="button" data-act="row-install" data-key="' + keyAttr + '" disabled>\u5B89\u88C5\u4E2D\u2026</button>'
362
+ '<button type="button" data-act="row-install" data-key="' + keyAttr + '" disabled>' + escapeHtml(batchText("batch.row-action.installing", ctx.lang)) + "</button>"
240
363
  );
241
364
  } else if (queued) {
242
365
  if (cancelQueuePhoneOf(row) !== null && queuedRequestIdOf(row) !== null) {
243
366
  actions.push(
244
- '<button type="button" data-act="row-cancel-queue" data-key="' + keyAttr + '"' + off + ">\u53D6\u6D88\u6392\u961F</button>"
367
+ '<button type="button" data-act="row-cancel-queue" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.cancel-queue", ctx.lang)) + "</button>"
245
368
  );
246
369
  }
247
370
  } else if (phase === "ready" && skipped === null) {
248
371
  actions.push(
249
- '<button type="button" data-act="row-install" data-key="' + keyAttr + '" data-primary="1"' + off + ">" + (ctx.busy ? "\u52A0\u5165\u961F\u5217" : "\u5B89\u88C5\u8FD9\u5BB6") + "</button>"
372
+ '<button type="button" data-act="row-install" data-key="' + keyAttr + '" data-primary="1"' + off + ">" + escapeHtml(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : batchText("batch.row-action.install-row", ctx.lang)) + "</button>"
250
373
  );
251
374
  } else if (phase === "ready" && skipped !== null) {
252
375
  actions.push(
253
- '<button type="button" data-act="row-resume-skip" data-key="' + keyAttr + '"' + off + ">\u6062\u590D\uFF08" + escapeHtml(skipped) + "\uFF09</button>"
376
+ '<button type="button" data-act="row-resume-skip" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.unskip", ctx.lang, { version: skipped })) + "</button>"
254
377
  );
255
378
  } else if (phase === "failed") {
256
379
  actions.push(
257
- '<button type="button" data-act="row-install" data-key="' + keyAttr + '"' + off + ">" + (ctx.busy ? "\u52A0\u5165\u961F\u5217" : "\u91CD\u8BD5") + "</button>"
380
+ '<button type="button" data-act="row-install" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : batchText("batch.row-action.retry", ctx.lang)) + "</button>"
258
381
  );
259
382
  }
260
383
  if (row.restartRequired === true) {
261
- actions.push('<button type="button" data-act="restart" data-key="' + keyAttr + '">\u91CD\u542F\u5BBF\u4E3B</button>');
384
+ actions.push('<button type="button" data-act="restart" data-key="' + keyAttr + '">' + escapeHtml(batchText("batch.row-action.restart", ctx.lang)) + "</button>");
262
385
  }
263
386
  actions.push(
264
- '<button type="button" data-act="toggle-details" data-key="' + keyAttr + '">' + (ctx.expanded ? "\u6536\u8D77" : "\u8BE6\u60C5") + "</button>"
387
+ '<button type="button" data-act="toggle-details" data-key="' + keyAttr + '">' + escapeHtml(ctx.expanded ? batchText("batch.row-action.hide-detail", ctx.lang) : batchText("batch.row-action.show-detail", ctx.lang)) + "</button>"
265
388
  );
266
- const failLine = phase === "failed" && !queued ? '<span class="dsh-upd-bfail">\u5931\u8D25 <code>' + escapeHtml(row.error || "install-failed") + "</code>\uFF1A" + escapeHtml(failureCopy(row.error || "install-failed")?.zh ?? "\u8BA4\u4E0D\u51FA\u5177\u4F53\u539F\u56E0") + "</span>" : "";
267
- const main = '<button type="button" class="dsh-upd-brow-main" data-act="toggle-details" data-key="' + keyAttr + '" aria-expanded="' + (ctx.expanded ? "true" : "false") + '"><span class="dsh-upd-updot" data-tone="' + dotToneOf(row, phase, skipped, queued) + '"></span><span class="dsh-upd-bname">' + escapeHtml(titleOf(row, ctx.titles)) + "</span>" + (version ? '<span class="dsh-upd-bver">' + escapeHtml(version) + "</span>" : "") + '<span class="dsh-upd-bstat">' + escapeHtml(queued ? batchQueuedStatus(position) : batchRowStatus(row, skipped)) + "</span>" + failLine + "</button>";
389
+ const failLine = phase === "failed" && !queued ? (() => {
390
+ const failCode = row.error || "install-failed";
391
+ const failDetail = failureCopy(failCode, ctx.lang)?.zh ?? failureCopy("unknown", ctx.lang)?.zh ?? "";
392
+ const label = batchText("batch.row.error-label", ctx.lang, { code: failCode, detail: failDetail });
393
+ const escaped = escapeHtml(label);
394
+ const codeEsc = escapeHtml(failCode);
395
+ const withCode = escaped.includes(codeEsc) ? escaped.replace(codeEsc, "<code>" + codeEsc + "</code>") : escaped;
396
+ return '<span class="dsh-upd-bfail">' + withCode + "</span>";
397
+ })() : "";
398
+ const main = '<button type="button" class="dsh-upd-brow-main" data-act="toggle-details" data-key="' + keyAttr + '" aria-expanded="' + (ctx.expanded ? "true" : "false") + '"><span class="dsh-upd-updot" data-tone="' + dotToneOf(row, phase, skipped, queued) + '"></span><span class="dsh-upd-bname">' + escapeHtml(titleOf(row, ctx.titles)) + "</span>" + (version ? '<span class="dsh-upd-bver">' + escapeHtml(version) + "</span>" : "") + '<span class="dsh-upd-bstat">' + escapeHtml(queued ? batchQueuedStatus(position, ctx.lang) : batchRowKnowledgeText(row, ctx.knowledge, ctx.lang) ?? batchRowStatus(row, skipped, ctx.lang)) + (ctx.inRound && !queued ? "\uFF08" + escapeHtml(batchText("batch.row.unfinished-tag", ctx.lang)) + "\uFF09" : "") + "</span>" + failLine + "</button>";
268
399
  const detail = ctx.expanded ? '<div class="dsh-upd-bdetail" data-key="' + keyAttr + '">' + detailActionsHTML(row, ctx) + (ctx.notice !== null ? '<div class="dsh-upd-bdetail-notice" role="status">' + escapeHtml(ctx.notice) + "</div>" : "") + detailHTML(row, ctx) + "</div>" : "";
269
400
  return '<div class="dsh-upd-brow-set" data-key="' + keyAttr + '"><div class="dsh-upd-brow" data-phase="' + escapeHtml(phase) + '" data-key="' + keyAttr + '"' + (queued ? ' data-queued="1"' : "") + ">" + main + '<span class="dsh-upd-brow-actions">' + actions.join("") + "</span></div>" + detail + "</div>";
270
401
  }
@@ -284,9 +415,10 @@ function detailHTML(row, ctx) {
284
415
  pluginId: pluginIdOf(row),
285
416
  copyNotice: null,
286
417
  theme: ctx.theme,
418
+ themeTokens: ctx.themeTokens,
287
419
  profileName: typeof row.profileName === "string" && row.profileName ? row.profileName : null,
288
420
  actions: "none"
289
- });
421
+ }, ctx.lang);
290
422
  return stripActionButtons(html);
291
423
  }
292
424
  function detailActionsHTML(row, ctx) {
@@ -297,38 +429,38 @@ function detailActionsHTML(row, ctx) {
297
429
  const off = ctx.inFlight ? " disabled" : "";
298
430
  const buttons = [];
299
431
  if (phase === "installing") {
300
- buttons.push('<button type="button" data-act="row-install" data-key="' + keyAttr + '" disabled>\u5B89\u88C5\u4E2D\u2026</button>');
432
+ buttons.push('<button type="button" data-act="row-install" data-key="' + keyAttr + '" disabled>' + escapeHtml(batchText("batch.row-action.installing", ctx.lang)) + "</button>");
301
433
  } else if (queued) {
302
434
  if (cancelQueuePhoneOf(row) !== null && queuedRequestIdOf(row) !== null) {
303
435
  buttons.push(
304
- '<button type="button" data-act="row-cancel-queue" data-key="' + keyAttr + '"' + off + ">\u53D6\u6D88\u6392\u961F</button>"
436
+ '<button type="button" data-act="row-cancel-queue" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.cancel-queue", ctx.lang)) + "</button>"
305
437
  );
306
438
  }
307
439
  } else if (phase === "failed") {
308
440
  buttons.push(
309
- '<button type="button" data-act="row-install" data-key="' + keyAttr + '"' + off + ">" + (ctx.busy ? "\u52A0\u5165\u961F\u5217" : "\u91CD\u8BD5") + "</button>"
441
+ '<button type="button" data-act="row-install" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : batchText("batch.row-action.retry", ctx.lang)) + "</button>"
310
442
  );
311
443
  } else if (phase === "ready" && ctx.skipped === null) {
312
444
  buttons.push(
313
- '<button type="button" data-act="row-install" data-key="' + keyAttr + '" data-primary="1"' + off + ">" + (ctx.busy ? "\u52A0\u5165\u961F\u5217" : "\u5B89\u88C5 " + escapeHtml(version ?? "\u65B0\u7248")) + "</button>"
445
+ '<button type="button" data-act="row-install" data-key="' + keyAttr + '" data-primary="1"' + off + ">" + escapeHtml(ctx.busy ? batchText("batch.row-action.queue", ctx.lang) : version !== null ? batchText("batch.row-action.install-version", ctx.lang, { version }) : batchText("batch.row-action.install-generic", ctx.lang)) + "</button>"
314
446
  );
315
447
  }
316
448
  if (!queued && phase === "ready" && ctx.skipped === null && version !== null) {
317
449
  buttons.push(
318
- '<button type="button" data-act="row-skip" data-key="' + keyAttr + '"' + off + ">\u8DF3\u8FC7\u8FD9\u4E00\u7248</button>"
450
+ '<button type="button" data-act="row-skip" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.skip", ctx.lang)) + "</button>"
319
451
  );
320
452
  }
321
453
  if (ctx.skipped !== null) {
322
454
  buttons.push(
323
- '<button type="button" data-act="row-resume-skip" data-key="' + keyAttr + '"' + off + ">\u6062\u590D\uFF08" + escapeHtml(ctx.skipped) + "\uFF09</button>"
455
+ '<button type="button" data-act="row-resume-skip" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.unskip", ctx.lang, { version: ctx.skipped })) + "</button>"
324
456
  );
325
457
  }
326
458
  if (typeof row.manual === "string" && row.manual) {
327
459
  buttons.push(
328
- '<button type="button" data-act="row-copy-manual" data-key="' + keyAttr + '"' + off + ">\u590D\u5236\u624B\u5DE5\u547D\u4EE4</button>"
460
+ '<button type="button" data-act="row-copy-manual" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.copy-manual", ctx.lang)) + "</button>"
329
461
  );
330
462
  }
331
- buttons.push('<button type="button" data-act="row-copy-diag" data-key="' + keyAttr + '"' + off + ">\u590D\u5236\u8BCA\u65AD</button>");
463
+ buttons.push('<button type="button" data-act="row-copy-diag" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.copy-diag", ctx.lang)) + "</button>");
332
464
  return '<span class="dsh-upd-bdetail-actions">' + buttons.join("") + "</span>";
333
465
  }
334
466
  function stripActionButtons(html) {
@@ -344,37 +476,37 @@ function dotToneOf(row, phase, skipped, queued) {
344
476
  return "idle";
345
477
  }
346
478
  const UPDATE_BATCH_PANEL_CSS = [
347
- "/* \u6279\u91CF\u9762\u677F\uFF08#25\uFF09\uFF1A\u7C7B\u540D\u6CBF\u7528 dsh-upd-* \u524D\u7F00\uFF1B\u9ED8\u8BA4\uFF08\u6700\u5C0F\uFF09\u4E3B\u9898\uFF0CD5 \u76AE\u80A4\u89C1 UPDATE_BATCH_PANEL_D5_CSS\u3002 */",
479
+ "/* \u6279\u91CF\u9762\u677F\uFF08#25\uFF09\uFF1A\u7C7B\u540D\u6CBF\u7528 dsh-upd-* \u524D\u7F00\uFF1B\u9ED8\u8BA4\uFF08\u6700\u5C0F\uFF09\u4E3B\u9898\uFF0CArchive \u76AE\u80A4\u89C1 UPDATE_BATCH_PANEL_ARCHIVE_CSS\u3002 */",
348
480
  ".dsh-upd-batch-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;flex-wrap:wrap}",
349
481
  ".dsh-upd-batch-title{font-weight:700;font-size:15px;letter-spacing:.06em}",
350
- ".dsh-upd-batch-title i{font-style:normal;color:var(--dsh-upd-primary,#2563eb)}",
482
+ ".dsh-upd-batch-title i{font-style:normal;color:var(--dsh-update-primary,#2563eb)}",
351
483
  ".dsh-upd-batch-macros{flex:none}",
352
484
  ".dsh-upd-batch-sum{margin:4px 0 8px;font-size:13.5px}",
353
485
  ".dsh-upd-btable{margin-top:2px}",
354
- ".dsh-upd-brow-set{border-top:1px solid var(--dsh-upd-line,#e5e7eb)}",
486
+ ".dsh-upd-brow-set{border-top:1px solid var(--dsh-update-border,#e5e7eb)}",
355
487
  ".dsh-upd-brow-set:first-child{border-top:0}",
356
488
  ".dsh-upd-brow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:6px 0}",
357
489
  ".dsh-upd .dsh-upd-brow-main{display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex:1 1 260px;min-width:0;",
358
490
  "text-align:left;cursor:pointer;color:inherit;font:inherit;background:transparent;border:0;padding:2px 0;margin:0}",
359
- ".dsh-upd .dsh-upd-brow-main:hover{color:var(--dsh-upd-primary,#2563eb)}",
491
+ ".dsh-upd .dsh-upd-brow-main:hover{color:var(--dsh-update-primary,#2563eb)}",
360
492
  ".dsh-upd-brow-actions{flex:none;margin-left:auto}",
361
493
  ".dsh-upd-brow-actions button{font-size:12.5px}",
362
- ".dsh-upd-updot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--dsh-upd-line,#9ca3af);opacity:.55}",
363
- '.dsh-upd-updot[data-tone="todo"]{background:var(--dsh-upd-ok-line,#059669);opacity:1}',
364
- '.dsh-upd-updot[data-tone="ok"]{background:var(--dsh-upd-ok-line,#059669);opacity:1}',
365
- '.dsh-upd-updot[data-tone="busy"]{background:var(--dsh-upd-busy-line,#2563eb);opacity:1}',
366
- '.dsh-upd-updot[data-tone="warn"]{background:var(--dsh-upd-warn-line,#d97706);opacity:1}',
367
- '.dsh-upd-updot[data-tone="bad"]{background:var(--dsh-upd-bad-line,#dc2626);opacity:1}',
494
+ ".dsh-upd-updot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--dsh-update-border,#9ca3af);opacity:.55}",
495
+ '.dsh-upd-updot[data-tone="todo"]{background:var(--dsh-update-ok-border,#059669);opacity:1}',
496
+ '.dsh-upd-updot[data-tone="ok"]{background:var(--dsh-update-ok-border,#059669);opacity:1}',
497
+ '.dsh-upd-updot[data-tone="busy"]{background:var(--dsh-update-busy-border,#2563eb);opacity:1}',
498
+ '.dsh-upd-updot[data-tone="warn"]{background:var(--dsh-update-warn-border,#d97706);opacity:1}',
499
+ '.dsh-upd-updot[data-tone="bad"]{background:var(--dsh-update-bad-border,#dc2626);opacity:1}',
368
500
  ".dsh-upd-bname{font-weight:700;flex:none}",
369
501
  ".dsh-upd-bver{font-family:Consolas,Menlo,monospace;font-size:12px;opacity:.8;flex:none}",
370
502
  ".dsh-upd-bstat{font-size:12.5px;opacity:.9}",
371
- ".dsh-upd-bfail{flex:1 1 100%;display:block;font-size:12.5px;color:var(--dsh-upd-bad-line,#dc2626)}",
503
+ ".dsh-upd-bfail{flex:1 1 100%;display:block;font-size:12.5px;color:var(--dsh-update-bad-border,#dc2626)}",
372
504
  ".dsh-upd-blist{font-size:12.5px;margin-top:2px}",
373
505
  ".dsh-upd-batch-ledger{margin:8px 0 0;font-size:13px;opacity:.85}",
374
506
  ".dsh-upd-batch-more{margin-top:6px}",
375
507
  ".dsh-upd-batch-more button{font-size:12px}",
376
508
  // 确认态:红框红字,与「关闭」等中性按钮一眼区分;disabled 照旧置灰。
377
- '.dsh-upd-batch-more button[data-confirm="1"]{border-color:var(--dsh-upd-bad-line,#dc2626);color:var(--dsh-upd-bad-line,#dc2626)}',
509
+ '.dsh-upd-batch-more button[data-confirm="1"]{border-color:var(--dsh-update-bad-border,#dc2626);color:var(--dsh-update-bad-border,#dc2626)}',
378
510
  ".dsh-upd-batch-notice{margin-top:6px;font-size:12.5px;opacity:.85}",
379
511
  ".dsh-upd-bdetail-actions{margin:0 0 6px}",
380
512
  ".dsh-upd .dsh-upd-bdetail-actions button{font-size:12.5px}",
@@ -385,21 +517,28 @@ const UPDATE_BATCH_PANEL_CSS = [
385
517
  ".dsh-upd-overlay .dsh-upd-bdetail .dsh-upd{max-height:none;overflow:visible}",
386
518
  ".dsh-upd-overlay .dsh-upd-bdetail .dsh-upd-body{overflow:visible}",
387
519
  ".dsh-upd-overlay .dsh-upd-bdetail .dsh-upd-body .dsh-upd-chap-head{position:static}",
388
- "@media (prefers-color-scheme: dark){.dsh-upd-batch-title i{color:var(--dsh-upd-focus,#93c5fd)}}",
389
- "@media (prefers-reduced-motion: reduce){.dsh-upd-batch *{transition:none !important;animation:none !important}}"
520
+ "@media (prefers-color-scheme: dark){.dsh-upd-batch-title i{color:var(--dsh-update-focus,#93c5fd)}}",
521
+ "@media (prefers-reduced-motion: reduce){.dsh-upd-batch *{transition:none !important;animation:none !important}}",
522
+ // Batch check layout stability: reserve macro width + rows/summary heights + no scroll anchoring jumps.
523
+ ".dsh-upd-batch{overflow-anchor:none}",
524
+ ".dsh-upd-btable{overflow-anchor:none}",
525
+ ".dsh-upd-batch-macros{display:flex;flex-wrap:wrap;align-items:center;gap:0}",
526
+ '.dsh-upd-batch-macros button:first-child,.dsh-upd-batch-macros button[data-primary="1"]{min-width:6em;text-align:center}',
527
+ ".dsh-upd-batch-sum,.dsh-upd-batch-ledger{min-height:1.6em}",
528
+ ".dsh-upd-brow{min-height:28px}"
390
529
  ].join(String.fromCharCode(10));
391
- const UPDATE_BATCH_PANEL_D5_CSS = [
392
- "/* \u6279\u91CF\u9762\u677F\u7684 D5 \u76AE\u80A4\uFF1A\u53EA\u6362\u989C\u8272/\u5B57\u4F53/\u95F4\u8DDD\uFF1B\u6839\u4E0A\u7684\u5927\u5370\u7AE0\u7531 UPDATE_PANEL_D5_CSS \u8D1F\u8D23\u3002 */",
530
+ const UPDATE_BATCH_PANEL_ARCHIVE_CSS = [
531
+ "/* \u6279\u91CF\u9762\u677F\u7684 Archive \u76AE\u80A4\uFF1A\u53EA\u6362\u989C\u8272/\u5B57\u4F53/\u95F4\u8DDD\uFF1B\u6839\u4E0A\u7684\u5927\u5370\u7AE0\u7531 UPDATE_PANEL_ARCHIVE_CSS \u8D1F\u8D23\u3002 */",
393
532
  '.dsh-upd[data-theme="archive"] .dsh-upd-batch-head{padding-right:120px}',
394
- '.dsh-upd[data-theme="archive"] .dsh-upd-batch-title{font-family:var(--d5-serif);font-size:21px;letter-spacing:.04em}',
395
- '.dsh-upd[data-theme="archive"] .dsh-upd-batch-title i{color:var(--d5-accent)}',
396
- '.dsh-upd[data-theme="archive"] .dsh-upd-brow-main{background:transparent;border:0;color:var(--d5-ink)}',
397
- '.dsh-upd[data-theme="archive"] .dsh-upd-brow-main:hover{color:var(--d5-accent)}',
398
- '.dsh-upd[data-theme="archive"] .dsh-upd-bname{font-family:var(--d5-serif)}',
399
- '.dsh-upd[data-theme="archive"] .dsh-upd-bver{font-family:var(--d5-mono);color:var(--d5-muted)}',
400
- '.dsh-upd[data-theme="archive"] .dsh-upd-batch-ledger{color:var(--d5-muted)}',
401
- '.dsh-upd[data-theme="archive"] .dsh-upd-batch-sum{color:var(--d5-ink)}',
402
- '.dsh-upd[data-theme="archive"] .dsh-upd-brow-set{border-top-color:var(--d5-line)}'
533
+ '.dsh-upd[data-theme="archive"] .dsh-upd-batch-title{font-family:var(--dsh-update-font-serif);font-size:21px;letter-spacing:.04em}',
534
+ '.dsh-upd[data-theme="archive"] .dsh-upd-batch-title i{color:var(--dsh-update-primary)}',
535
+ '.dsh-upd[data-theme="archive"] .dsh-upd-brow-main{background:transparent;border:0;color:var(--dsh-update-text)}',
536
+ '.dsh-upd[data-theme="archive"] .dsh-upd-brow-main:hover{color:var(--dsh-update-primary)}',
537
+ '.dsh-upd[data-theme="archive"] .dsh-upd-bname{font-family:var(--dsh-update-font-serif)}',
538
+ '.dsh-upd[data-theme="archive"] .dsh-upd-bver{font-family:var(--dsh-update-font-mono);color:var(--dsh-update-text-muted)}',
539
+ '.dsh-upd[data-theme="archive"] .dsh-upd-batch-ledger{color:var(--dsh-update-text-muted)}',
540
+ '.dsh-upd[data-theme="archive"] .dsh-upd-batch-sum{color:var(--dsh-update-text)}',
541
+ '.dsh-upd[data-theme="archive"] .dsh-upd-brow-set{border-top-color:var(--dsh-update-border)}'
403
542
  ].join(String.fromCharCode(10));
404
543
  function escapeHtml(text) {
405
544
  return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -513,14 +652,14 @@ function jobFailureOf(row) {
513
652
  const detail = message.slice(at + 1).trim();
514
653
  return { code: code || null, detail: detail || null };
515
654
  }
516
- function diagTextOf(row) {
655
+ function diagTextOf(row, lang) {
517
656
  const snapshot = isObject(row.snapshot) ? row.snapshot : null;
518
657
  const queue = isObject(row.queue) ? row.queue : null;
519
658
  const diag = isObject(row.diag) ? row.diag : null;
520
659
  const text = (value) => typeof value === "string" && value ? value : null;
521
660
  const job = jobFailureOf(row);
522
661
  const diagDetail = diag ? text(diag["detail"]) : null;
523
- const detail = diagDetail !== null ? diagDetail : job !== null && job.detail !== null ? job.detail + "\uFF08\u6765\u6E90\uFF1A\u540E\u53F0\u4EFB\u52A1\u6536\u5C3E\u8BB0\u5F55\uFF09" : null;
662
+ const detail = diagDetail !== null ? diagDetail : job !== null && job.detail !== null ? job.detail + batchText("batch.diag.source-job", langOf(lang)) : null;
524
663
  const input = {
525
664
  pluginId: pluginIdOf(row),
526
665
  code: job !== null && job.code !== null ? job.code : row.error || "check-failed",
@@ -532,7 +671,9 @@ function diagTextOf(row) {
532
671
  queuePosition: queue && typeof queue["position"] === "number" ? queue["position"] : null,
533
672
  manual: text(row.manual),
534
673
  diag: row.diag ?? null,
535
- profileName: text(row.profileName)
674
+ profileName: text(row.profileName),
675
+ // #63 透传语言(批量 en 跟随在 #64/#65 落;此处缺省 zh 零回归)。
676
+ lang: lang ?? "zh"
536
677
  };
537
678
  return buildDiagnosticText(input);
538
679
  }
@@ -622,10 +763,12 @@ function mountUpdateBatchPanel(container, options) {
622
763
  if (mode !== "embedded" && mode !== "dialog") {
623
764
  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");
624
765
  }
625
- if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive" && options.theme !== "d5-paper") {
626
- 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");
766
+ if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive") {
767
+ throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08\u6536\u5230 " + JSON.stringify(options.theme) + "\uFF09");
627
768
  }
628
769
  let theme = normalizePanelTheme(options.theme ?? "default");
770
+ themeTokensStyleFor(options.themeTokens ?? void 0);
771
+ let themeTokens = options.themeTokens;
629
772
  const call = options.call;
630
773
  const titles = options.titles && typeof options.titles === "object" ? options.titles : {};
631
774
  const onRestartRequested = options.onRestartRequested;
@@ -647,6 +790,18 @@ function mountUpdateBatchPanel(container, options) {
647
790
  let refreshing = false;
648
791
  let confirmCancelArmed = false;
649
792
  let mounted = true;
793
+ const localeOpt = options["locale"];
794
+ function currentLang() {
795
+ return langOf(localeOpt);
796
+ }
797
+ const checkOnOpenOption = options["checkOnOpen"] !== false;
798
+ const autoResumeOption = options["autoResume"] !== false;
799
+ let lastSession = null;
800
+ let lastInventory = null;
801
+ let lastPrefs = null;
802
+ let lastCheckedAt = 0;
803
+ let autoCheckDone = false;
804
+ let autoResumeDone = false;
650
805
  function changelogView() {
651
806
  const out = {};
652
807
  for (const row of rows) {
@@ -680,13 +835,13 @@ function mountUpdateBatchPanel(container, options) {
680
835
  const failKey = key + "\0" + version;
681
836
  const hasCache = false;
682
837
  const failedAt = rowFailedAt.has(failKey) ? rowFailedAt.get(failKey) : null;
683
- let nowMs = 0;
838
+ let nowMs2 = 0;
684
839
  try {
685
- nowMs = Date.now();
840
+ nowMs2 = Date.now();
686
841
  } catch {
687
- nowMs = 0;
842
+ nowMs2 = 0;
688
843
  }
689
- if (!shouldFetchChangelog({ hasCache, failedAt, now: nowMs, isManual: false })) return;
844
+ if (!shouldFetchChangelog({ hasCache, failedAt, now: nowMs2, isManual: false })) return;
690
845
  } catch {
691
846
  }
692
847
  const phone2 = changelogPhoneOf(row);
@@ -727,12 +882,84 @@ function mountUpdateBatchPanel(container, options) {
727
882
  }
728
883
  );
729
884
  }
885
+ function setStableHTML(target, html) {
886
+ try {
887
+ const g = globalThis;
888
+ const doc = g["document"];
889
+ const el = target;
890
+ if (!doc || typeof el.querySelectorAll !== "function") {
891
+ target.innerHTML = html;
892
+ return;
893
+ }
894
+ let focusAct = null;
895
+ let focusKey = null;
896
+ try {
897
+ const active = doc.activeElement;
898
+ if (active && typeof active.getAttribute === "function") {
899
+ focusAct = active.getAttribute("data-act");
900
+ focusKey = active.getAttribute("data-key");
901
+ }
902
+ } catch {
903
+ focusAct = null;
904
+ focusKey = null;
905
+ }
906
+ let scrolls = [];
907
+ try {
908
+ const nodes = el.querySelectorAll(".dsh-upd-btable");
909
+ if (nodes) {
910
+ for (let i = 0; i < nodes.length; i++) {
911
+ const n = nodes[i];
912
+ scrolls.push(typeof n.scrollTop === "number" ? n.scrollTop : 0);
913
+ }
914
+ }
915
+ } catch {
916
+ scrolls = [];
917
+ }
918
+ target.innerHTML = html;
919
+ try {
920
+ const bodies = el.querySelectorAll(".dsh-upd-btable");
921
+ if (bodies) {
922
+ for (let i = 0; i < bodies.length && i < scrolls.length; i++) {
923
+ const n = bodies[i];
924
+ try {
925
+ n.scrollTop = scrolls[i];
926
+ } catch {
927
+ }
928
+ }
929
+ }
930
+ } catch {
931
+ }
932
+ try {
933
+ if (focusAct && typeof el.querySelector === "function") {
934
+ const sel = focusKey ? '[data-act="' + focusAct + '"][data-key="' + focusKey + '"]' : '[data-act="' + focusAct + '"]';
935
+ const next = el.querySelector(sel);
936
+ if (next && typeof next.focus === "function") {
937
+ try {
938
+ next.focus.call(next, { preventScroll: true });
939
+ } catch {
940
+ try {
941
+ next.focus.call(next);
942
+ } catch {
943
+ }
944
+ }
945
+ }
946
+ }
947
+ } catch {
948
+ }
949
+ } catch {
950
+ try {
951
+ target.innerHTML = html;
952
+ } catch {
953
+ }
954
+ }
955
+ }
730
956
  let lastHTML = "";
731
957
  function render() {
732
958
  if (!mounted) return;
733
959
  const nextHTML = renderBatchPanelHTML({
734
960
  rows,
735
961
  theme,
962
+ themeTokens,
736
963
  mode,
737
964
  expandedKey,
738
965
  lastError,
@@ -743,11 +970,15 @@ function mountUpdateBatchPanel(container, options) {
743
970
  loaded,
744
971
  titles,
745
972
  confirmCancel: confirmCancelArmed,
746
- changelogs: changelogView()
973
+ changelogs: changelogView(),
974
+ session: lastSession,
975
+ inventory: lastInventory,
976
+ prefs: lastPrefs,
977
+ lang: currentLang()
747
978
  });
748
979
  if (nextHTML !== lastHTML) {
749
980
  lastHTML = nextHTML;
750
- container.innerHTML = nextHTML;
981
+ setStableHTML(container, nextHTML);
751
982
  }
752
983
  }
753
984
  function rowOfKey(key) {
@@ -789,7 +1020,7 @@ function mountUpdateBatchPanel(container, options) {
789
1020
  await copyText(text);
790
1021
  say(okNotice, key);
791
1022
  } catch {
792
- say("\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u4E0A\u9762\u7684\u4FE1\u606F\u3002", key);
1023
+ say(batchText("batch.toast.copy-fail", currentLang()), key);
793
1024
  }
794
1025
  render();
795
1026
  }
@@ -802,6 +1033,13 @@ function mountUpdateBatchPanel(container, options) {
802
1033
  const session = normalizeBatchSession(reply["session"]);
803
1034
  const parsed = readRows(reply["rows"]);
804
1035
  rows = parsed.length > 0 ? parsed : rowsFromSession(session);
1036
+ lastSession = session;
1037
+ if (isObject(reply["inventory"])) {
1038
+ lastInventory = reply["inventory"];
1039
+ const at = reply["inventory"]["updatedAt"];
1040
+ if (typeof at === "number" && Number.isFinite(at) && at > 0) lastCheckedAt = at;
1041
+ }
1042
+ if (isObject(reply["prefs"])) lastPrefs = reply["prefs"];
805
1043
  loaded = true;
806
1044
  lastError = null;
807
1045
  if (expandedKey !== null && !rows.some((row) => row.key === expandedKey)) expandedKey = null;
@@ -828,27 +1066,49 @@ function mountUpdateBatchPanel(container, options) {
828
1066
  maybeAutoRowChangelog(expandedKey);
829
1067
  }
830
1068
  async function phone(name, args, busyRowKey = null) {
831
- if (!mounted || inFlight) return;
1069
+ if (!mounted || inFlight) return null;
832
1070
  inFlight = true;
833
1071
  clearNotice();
834
1072
  render();
1073
+ let out = null;
835
1074
  try {
836
1075
  const reply = await call(name, args);
837
- if (!mounted) return;
1076
+ if (!mounted) return null;
838
1077
  if (busyRowKey !== null && isBusyReply(reply)) {
839
- say("\u524D\u9762\u8FD8\u5728\u88C5\uFF1A\u8FD9\u4E00\u5BB6\u8FD8\u6CA1\u6392\u4E0A\uFF0C\u7B49\u90A3\u5BB6\u88C5\u5B8C\u518D\u70B9\u4E00\u6B21\u3002", busyRowKey);
840
- return;
1078
+ say(batchText("batch.toast.queue-missed", currentLang()), busyRowKey);
1079
+ return null;
841
1080
  }
842
1081
  applyReply(reply);
843
1082
  rowFailedAt.clear();
1083
+ out = isObject(reply) ? reply : null;
844
1084
  } catch {
845
- if (!mounted) return;
1085
+ if (!mounted) return null;
846
1086
  lastError = "check-failed";
1087
+ out = null;
847
1088
  } finally {
848
1089
  inFlight = false;
849
1090
  if (mounted) render();
850
1091
  }
851
1092
  maybeAutoRowChangelog(expandedKey);
1093
+ return out;
1094
+ }
1095
+ function remainingCount() {
1096
+ return unfinishedCount(rows, lastSession ?? void 0);
1097
+ }
1098
+ function checkOnOpenEffective() {
1099
+ if (isObject(lastPrefs)) {
1100
+ const v = lastPrefs["checkOnOpen"];
1101
+ if (v === false) return false;
1102
+ if (v === true) return true;
1103
+ }
1104
+ return checkOnOpenOption;
1105
+ }
1106
+ function nowMs() {
1107
+ try {
1108
+ return Date.now();
1109
+ } catch {
1110
+ return 0;
1111
+ }
852
1112
  }
853
1113
  async function act(kind, key) {
854
1114
  if (!mounted) return;
@@ -858,17 +1118,26 @@ function mountUpdateBatchPanel(container, options) {
858
1118
  }
859
1119
  switch (kind) {
860
1120
  case "check":
861
- return phone(phones.check, {});
1121
+ await phone(phones.check, {});
1122
+ return;
862
1123
  case "install":
863
- return phone(phones.install, {});
1124
+ await phone(phones.install, {});
1125
+ return;
864
1126
  case "row-install": {
865
1127
  if (typeof key !== "string" || !key) return;
866
- return phone(phones.install, { keys: [key] }, key);
1128
+ await phone(phones.install, { keys: [key] }, key);
1129
+ return;
867
1130
  }
868
1131
  case "resume": {
869
- await phone(phones.resume, {});
1132
+ const reply = await phone(phones.resume, {});
870
1133
  if (!mounted) return;
871
- say("\u5DF2\u63A5\u7740\u4E0A\u6B21\u7684\u4F1A\u8BDD\u63A8\u8FDB\u4E00\u6B65\u3002");
1134
+ if (!reply || reply["ok"] !== true) return;
1135
+ if (reply["resumed"] === false) {
1136
+ say(batchText("batch.notice.no-resume", currentLang()));
1137
+ render();
1138
+ return;
1139
+ }
1140
+ say(batchText("batch.notice.resumed", currentLang(), { count: String(remainingCount()) }));
872
1141
  render();
873
1142
  return;
874
1143
  }
@@ -876,9 +1145,18 @@ function mountUpdateBatchPanel(container, options) {
876
1145
  // 鼠标误触的防护在 onClick 那一层(第一次点击只上膛)。
877
1146
  case "cancel": {
878
1147
  confirmCancelArmed = false;
879
- await phone(phones.cancel, {});
1148
+ const reply = await phone(phones.cancel, {});
1149
+ if (!mounted) return;
1150
+ if (!reply || reply["ok"] !== true) return;
1151
+ say(batchText("batch.notice.discarded", currentLang()));
1152
+ render();
1153
+ return;
1154
+ }
1155
+ case "toggle-check-on-open": {
1156
+ const next = !checkOnOpenEffective();
1157
+ const reply = await phone(phones.prefsSave, { checkOnOpen: next });
880
1158
  if (!mounted) return;
881
- say("\u5DF2\u53D6\u6D88\u8FD9\u4E00\u6279\uFF1A\u5269\u4E0B\u7684\u4E0D\u518D\u63A8\u8FDB\uFF1B\u8981\u91CD\u6765\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u3002");
1159
+ if (reply && reply["ok"] === true && isObject(reply["prefs"])) lastPrefs = reply["prefs"];
882
1160
  render();
883
1161
  return;
884
1162
  }
@@ -891,7 +1169,7 @@ function mountUpdateBatchPanel(container, options) {
891
1169
  skipStoreFor(pluginIdOf(row)).skip(version);
892
1170
  } catch {
893
1171
  }
894
- say("\u5DF2\u8DF3\u8FC7 " + version + "\uFF1A\u8FD9\u4E00\u7248\u4E0D\u518D\u63D0\u9192\uFF1B\u70B9\u300C\u6062\u590D\u300D\u53EF\u64A4\u9500\u3002", row.key);
1172
+ say(batchText("batch.toast.skipped", currentLang(), { version }), row.key);
895
1173
  render();
896
1174
  return;
897
1175
  }
@@ -903,7 +1181,7 @@ function mountUpdateBatchPanel(container, options) {
903
1181
  skipStoreFor(pluginIdOf(row)).reset(version ?? void 0);
904
1182
  } catch {
905
1183
  }
906
- say(version ? "\u5DF2\u6062\u590D " + version + "\uFF1A\u8FD9\u4E00\u7248\u4F1A\u7167\u5E38\u63D0\u9192\u3002" : "\u5DF2\u6062\u590D\u8DF3\u8FC7\u63D0\u9192\u3002", row.key);
1184
+ say(version ? batchText("batch.toast.unskipped-version", currentLang(), { version }) : batchText("batch.toast.unskipped-all", currentLang()), row.key);
907
1185
  render();
908
1186
  return;
909
1187
  }
@@ -915,7 +1193,7 @@ function mountUpdateBatchPanel(container, options) {
915
1193
  const phoneName = cancelQueuePhoneOf(row);
916
1194
  const requestId = queuedRequestIdOf(row);
917
1195
  if (phoneName === null || requestId === null) {
918
- say("\u8FD9\u4E00\u884C\u6CA1\u5E26\u53D6\u6D88\u6392\u961F\u8981\u7528\u7684\u7535\u8BDD\u540D\u6216\u7F16\u53F7\uFF0C\u6682\u4E0D\u80FD\u53D6\u6D88\uFF1A\u7B49\u4E0B\u4E00\u6B21\u5237\u65B0\u518D\u770B\u3002", row.key);
1196
+ say(batchText("batch.toast.cancel-unavailable", currentLang()), row.key);
919
1197
  render();
920
1198
  return;
921
1199
  }
@@ -925,9 +1203,9 @@ function mountUpdateBatchPanel(container, options) {
925
1203
  render();
926
1204
  try {
927
1205
  await call(phoneName, { cancelQueued: true, requestId });
928
- if (mounted) say("\u5DF2\u53D6\u6D88\u6392\u961F\uFF1A\u8FD9\u4E00\u5BB6\u4E0D\u7B49\u4E86\u3002", row.key);
1206
+ if (mounted) say(batchText("batch.toast.cancel-ok", currentLang()), row.key);
929
1207
  } catch {
930
- if (mounted) say("\u53D6\u6D88\u6392\u961F\u6CA1\u6210\u529F\uFF08\u53EF\u80FD\u5DF2\u7ECF\u5F00\u59CB\u88C5\u4E86\uFF09\uFF1A\u770B\u4E0B\u9762\u6700\u65B0\u72B6\u6001\u3002", row.key);
1208
+ if (mounted) say(batchText("batch.toast.cancel-fail", currentLang()), row.key);
931
1209
  } finally {
932
1210
  inFlight = false;
933
1211
  }
@@ -938,13 +1216,13 @@ function mountUpdateBatchPanel(container, options) {
938
1216
  const row = typeof key === "string" && key ? rowOfKey(key) : null;
939
1217
  const manual = row && typeof row.manual === "string" ? row.manual : "";
940
1218
  if (!row || !manual) return;
941
- await copyWith(manual, "\u624B\u5DE5\u547D\u4EE4\u5DF2\u590D\u5236\uFF0C\u7C98\u5230\u7EC8\u7AEF\u6574\u884C\u6267\u884C\u5373\u53EF\u3002", row.key);
1219
+ await copyWith(manual, batchText("batch.toast.copy-manual-ok", currentLang()), row.key);
942
1220
  return;
943
1221
  }
944
1222
  case "row-copy-diag": {
945
1223
  const row = typeof key === "string" && key ? rowOfKey(key) : null;
946
1224
  if (!row) return;
947
- await copyWith(diagTextOf(row), "\u8BCA\u65AD\u5DF2\u590D\u5236\uFF0C\u76F4\u63A5\u7C98\u7ED9\u63D2\u4EF6\u4F5C\u8005\u5373\u53EF\uFF08\u5DF2\u8131\u654F\uFF09\u3002", row.key);
1225
+ await copyWith(diagTextOf(row, currentLang()), batchText("batch.toast.copy-diag-ok", currentLang()), row.key);
948
1226
  return;
949
1227
  }
950
1228
  case "toggle-details": {
@@ -960,12 +1238,12 @@ function mountUpdateBatchPanel(container, options) {
960
1238
  try {
961
1239
  if (typeof onRestartRequested === "function") {
962
1240
  await onRestartRequested();
963
- say("\u5DF2\u6309\u8C03\u7528\u65B9\u7684\u91CD\u542F\u6D41\u7A0B\u5904\u7406\uFF1B\u91CD\u542F\u540E\u65B0\u7248\u751F\u6548\u3002");
1241
+ say(batchText("batch.toast.restart-delegated", currentLang()));
964
1242
  } else {
965
- say("\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");
1243
+ say(batchText("batch.toast.restart-manual", currentLang()));
966
1244
  }
967
1245
  } catch {
968
- say("\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");
1246
+ say(batchText("batch.toast.restart-failed", currentLang()));
969
1247
  }
970
1248
  render();
971
1249
  return;
@@ -997,12 +1275,17 @@ function mountUpdateBatchPanel(container, options) {
997
1275
  }
998
1276
  }
999
1277
  function setTheme(next) {
1000
- if (next !== "default" && next !== "archive" && next !== "d5-paper") {
1001
- 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");
1278
+ if (next !== "default" && next !== "archive") {
1279
+ throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08\u6536\u5230 " + JSON.stringify(next) + "\uFF09");
1002
1280
  }
1003
1281
  theme = normalizePanelTheme(next);
1004
1282
  render();
1005
1283
  }
1284
+ function setThemeTokens(next) {
1285
+ themeTokensStyleFor(next ?? void 0);
1286
+ themeTokens = next ?? void 0;
1287
+ render();
1288
+ }
1006
1289
  function setMode(next) {
1007
1290
  if (next !== "embedded" && next !== "dialog") {
1008
1291
  throw new Error("[dsh-plugin-update] \u6446\u653E\u5F62\u6001\u975E\u6CD5\uFF1A\u53EA\u6536 embedded \u6216 dialog\uFF08\u6536\u5230 " + JSON.stringify(next) + "\uFF09");
@@ -1018,7 +1301,7 @@ function mountUpdateBatchPanel(container, options) {
1018
1301
  if (!kind) return;
1019
1302
  if (kind === "cancel" && !confirmCancelArmed) {
1020
1303
  confirmCancelArmed = true;
1021
- say("\u518D\u70B9\u4E00\u6B21\u300C\u786E\u8BA4\u53D6\u6D88\u8FD9\u4E00\u6279\u300D\u624D\u771F\u7684\u53D6\u6D88\uFF1B\u70B9\u522B\u7684\u6309\u94AE\u53EF\u64A4\u9500\u8FD9\u6B21\u786E\u8BA4\u3002");
1304
+ say(batchText("batch.notice.cancel-confirm", currentLang()));
1022
1305
  render();
1023
1306
  return;
1024
1307
  }
@@ -1028,9 +1311,64 @@ function mountUpdateBatchPanel(container, options) {
1028
1311
  } catch {
1029
1312
  }
1030
1313
  }
1314
+ async function autoOnce() {
1315
+ if (!mounted) return;
1316
+ if (autoResumeOption && !autoResumeDone) {
1317
+ autoResumeDone = true;
1318
+ if (hasUnfinishedRows(rows, lastSession ?? void 0) && !inFlight && !refreshing) {
1319
+ const before = lastError;
1320
+ try {
1321
+ const reply = await call(phones.resume, {});
1322
+ if (!mounted) return;
1323
+ if (isObject(reply) && reply["ok"] === true) {
1324
+ applyReply(reply);
1325
+ const r = reply;
1326
+ if (r["resumed"] !== false) {
1327
+ say(batchText("batch.notice.auto-resumed", currentLang(), { count: String(remainingCount()) }));
1328
+ rowFailedAt.clear();
1329
+ }
1330
+ render();
1331
+ maybeAutoRowChangelog(expandedKey);
1332
+ return;
1333
+ }
1334
+ lastError = before;
1335
+ render();
1336
+ } catch {
1337
+ if (mounted) render();
1338
+ }
1339
+ }
1340
+ }
1341
+ if (!autoCheckDone) {
1342
+ autoCheckDone = true;
1343
+ if (!checkOnOpenEffective()) return;
1344
+ if (hasUnfinishedRows(rows, lastSession ?? void 0)) return;
1345
+ const stale = lastCheckedAt <= 0 || nowMs() - lastCheckedAt >= 6e4;
1346
+ if (!stale || inFlight || refreshing) return;
1347
+ const before = lastError;
1348
+ try {
1349
+ const reply = await call(phones.check, {});
1350
+ if (!mounted) return;
1351
+ if (isObject(reply) && reply["ok"] === true) {
1352
+ applyReply(reply);
1353
+ rowFailedAt.clear();
1354
+ render();
1355
+ maybeAutoRowChangelog(expandedKey);
1356
+ return;
1357
+ }
1358
+ lastError = before;
1359
+ render();
1360
+ } catch {
1361
+ if (mounted) render();
1362
+ }
1363
+ }
1364
+ }
1031
1365
  function unmount() {
1032
1366
  if (!mounted) return;
1033
1367
  mounted = false;
1368
+ try {
1369
+ unsubLang();
1370
+ } catch {
1371
+ }
1034
1372
  rowAutoSeq++;
1035
1373
  rowInflight.clear();
1036
1374
  try {
@@ -1046,6 +1384,9 @@ function mountUpdateBatchPanel(container, options) {
1046
1384
  } catch {
1047
1385
  }
1048
1386
  }
1387
+ const unsubLang = subscribeLang(() => {
1388
+ render();
1389
+ }, localeOpt);
1049
1390
  render();
1050
1391
  try {
1051
1392
  container.addEventListener?.("click", onClick);
@@ -1057,6 +1398,7 @@ function mountUpdateBatchPanel(container, options) {
1057
1398
  }
1058
1399
  const timer = getTimer();
1059
1400
  const handle = timer.set(() => {
1401
+ if (inFlight) return;
1060
1402
  void refresh();
1061
1403
  }, pollMs);
1062
1404
  try {
@@ -1064,18 +1406,23 @@ function mountUpdateBatchPanel(container, options) {
1064
1406
  if (h && typeof h.unref === "function") h.unref();
1065
1407
  } catch {
1066
1408
  }
1067
- void refresh();
1068
- return { refresh, act, setTheme, setMode, unmount };
1409
+ void refresh().then(() => {
1410
+ void autoOnce();
1411
+ });
1412
+ return { refresh, act, setTheme, setThemeTokens, setMode, unmount };
1069
1413
  }
1070
1414
  export {
1071
1415
  BATCH_PANEL_POLL,
1416
+ UPDATE_BATCH_PANEL_ARCHIVE_CSS,
1072
1417
  UPDATE_BATCH_PANEL_CSS,
1073
- UPDATE_BATCH_PANEL_D5_CSS,
1074
1418
  batchLedgerCounts,
1075
1419
  batchLedgerText,
1076
1420
  batchQueuedStatus,
1421
+ batchRowKnowledgeText,
1077
1422
  batchRowStatus,
1078
1423
  buildBatchPhoneNames,
1424
+ hasUnfinishedRows,
1079
1425
  mountUpdateBatchPanel,
1080
- renderBatchPanelHTML
1426
+ renderBatchPanelHTML,
1427
+ unfinishedCount
1081
1428
  };