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.
@@ -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,7 +10,7 @@ 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,
@@ -27,7 +29,9 @@ function buildBatchPhoneNames(prefix) {
27
29
  check: p + ".batchCheck",
28
30
  install: p + ".batchInstall",
29
31
  resume: p + ".batchResume",
30
- cancel: p + ".batchCancel"
32
+ cancel: p + ".batchCancel",
33
+ prefs: p + ".batchPrefs",
34
+ prefsSave: p + ".batchPrefsSave"
31
35
  };
32
36
  }
33
37
  function batchLedgerCounts(rows) {
@@ -53,44 +57,122 @@ function batchLedgerCounts(rows) {
53
57
  }
54
58
  return counts;
55
59
  }
56
- function batchLedgerText(counts) {
60
+ function batchLedgerText(counts, lang) {
61
+ const l = langOf(lang);
57
62
  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";
63
+ if (counts.updatable > 0) parts.push(batchText("batch.summary.updatable", l, { n: String(counts.updatable) }));
64
+ if (counts.installing > 0) parts.push(batchText("batch.summary.installing", l, { n: String(counts.installing) }));
65
+ if (counts.pending > 0) parts.push(batchText("batch.summary.pending", l, { n: String(counts.pending) }));
66
+ if (counts.restart > 0) parts.push(batchText("batch.summary.restart", l, { n: String(counts.restart) }));
67
+ if (counts.failed > 0) parts.push(batchText("batch.summary.failed", l, { n: String(counts.failed) }));
68
+ if (counts.skipped > 0) parts.push(batchText("batch.summary.skipped", l, { n: String(counts.skipped) }));
69
+ if (counts.settled > 0) parts.push(batchText("batch.summary.settled", l, { n: String(counts.settled) }));
70
+ return parts.length > 0 ? parts.join(" \xB7 ") : batchText("batch.summary.empty", l);
66
71
  }
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";
72
+ function batchQueuedStatus(position, lang) {
73
+ const l = langOf(lang);
74
+ if (typeof position !== "number" || !Number.isFinite(position) || position < 1) return batchText("batch.row.queued-generic", l);
75
+ return batchText("batch.row.queued-n", l, { n: String(position) });
70
76
  }
71
- function batchRowStatus(row, skippedVersion) {
77
+ function knowledgeEntryOf(inventory, key) {
78
+ if (!isObject(inventory)) return null;
79
+ const entries = inventory["entries"];
80
+ if (!isObject(entries)) return null;
81
+ const item = entries[key];
82
+ if (!isObject(item)) return null;
83
+ const e = item;
84
+ const canInstallRaw = e["canInstall"];
85
+ return {
86
+ lastCheckedAt: typeof e["lastCheckedAt"] === "number" ? e["lastCheckedAt"] : 0,
87
+ installedVersion: typeof e["installedVersion"] === "string" ? e["installedVersion"] : null,
88
+ latestVersion: typeof e["latestVersion"] === "string" ? e["latestVersion"] : null,
89
+ canInstall: typeof canInstallRaw === "boolean" ? canInstallRaw : null,
90
+ error: typeof e["error"] === "string" ? e["error"] : null
91
+ };
92
+ }
93
+ function langOf(lang) {
94
+ try {
95
+ return normalizeLangTag(typeof lang === "string" ? lang : resolveLang(lang));
96
+ } catch {
97
+ return "zh";
98
+ }
99
+ }
100
+ function batchText(key, lang, values) {
101
+ try {
102
+ return bilingualText(key, langOf(lang), values);
103
+ } catch {
104
+ return "";
105
+ }
106
+ }
107
+ function hasUnfinishedRows(rows, session) {
108
+ if (isObject(session)) {
109
+ const entries = session["entries"];
110
+ if (Array.isArray(entries)) {
111
+ for (const item of entries) {
112
+ if (!isObject(item)) continue;
113
+ const phase = item["phase"];
114
+ if (typeof phase === "string" && !isTerminalPhase(asBatchPhase(phase))) return true;
115
+ }
116
+ return false;
117
+ }
118
+ }
119
+ if (rows.length === 0) return false;
120
+ return !rows.every((row) => isTerminalPhase(asBatchPhase(row.phase)));
121
+ }
122
+ function unfinishedCount(rows, session) {
123
+ if (isObject(session)) {
124
+ const entries = session["entries"];
125
+ if (Array.isArray(entries)) {
126
+ let n = 0;
127
+ for (const item of entries) {
128
+ if (!isObject(item)) continue;
129
+ const phase = item["phase"];
130
+ if (typeof phase === "string" && !isTerminalPhase(asBatchPhase(phase))) n += 1;
131
+ }
132
+ return n;
133
+ }
134
+ }
135
+ return rows.filter((row) => !isTerminalPhase(asBatchPhase(row.phase))).length;
136
+ }
137
+ function batchRowStatus(row, skippedVersion, lang) {
138
+ const l = langOf(lang);
72
139
  const skipped = typeof skippedVersion === "string" && skippedVersion ? skippedVersion : null;
73
- if (skipped !== null && asBatchPhase(row.phase) === "ready") return "\u5DF2\u8DF3\u8FC7 " + skipped;
140
+ if (skipped !== null && asBatchPhase(row.phase) === "ready") return batchText("batch.row.skipped", l, { version: skipped });
74
141
  switch (asBatchPhase(row.phase)) {
75
142
  case "pending":
76
- return "\u7B49\u5B83\uFF0C\u8F6E\u5230\u5C31\u81EA\u52A8\u67E5\u65B0\u7248";
143
+ return batchText("batch.row.wait-turn", l);
77
144
  case "checking":
78
- return "\u6B63\u5728\u67E5\u65B0\u7248\uFF0C\u7A0D\u7B49";
145
+ return batchText("batch.row.checking", l);
79
146
  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";
147
+ return row.targetVersion ? batchText("batch.row.cta-version", l, { version: row.targetVersion }) : batchText("batch.row.cta-generic", l);
81
148
  case "installing":
82
- return "\u6B63\u5728\u5B89\u88C5\uFF0C\u522B\u52A8";
149
+ return batchText("batch.row.installing", l);
83
150
  case "current":
84
- return "\u5DF2\u662F\u6700\u65B0\uFF0C\u4E0D\u7528\u52A8";
151
+ return batchText("batch.row.current", l);
85
152
  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";
153
+ return row.restartRequired === true ? batchText("batch.row.done-restart", l) : batchText("batch.row.done", l);
87
154
  case "failed":
88
- return "\u5B89\u88C5\u6CA1\u6210\u529F\uFF0C\u70B9\u300C\u91CD\u8BD5\u300D\u518D\u6765\u4E00\u6B21";
155
+ return batchText("batch.row.failed-retry", l);
89
156
  case "skipped":
90
- return "\u8FD9\u4E00\u7248\u5DF2\u8DF3\u8FC7\uFF0C\u4E0D\u7528\u52A8";
157
+ return batchText("batch.row.skipped-idle", l);
91
158
  default:
92
- return "\u72B6\u6001\u8BA4\u4E0D\u51FA\uFF0C\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u91CD\u67E5\u4E00\u6B21";
159
+ return batchText("batch.row.unknown", l);
160
+ }
161
+ }
162
+ function batchRowKnowledgeText(row, entry, lang) {
163
+ if (asBatchPhase(row.phase) !== "pending" || !entry) return null;
164
+ if (entry.error) return batchText("batch.row.failed", lang);
165
+ if (entry.latestVersion && entry.installedVersion && entry.latestVersion !== entry.installedVersion) {
166
+ return batchText("batch.row.update", lang, { version: entry.latestVersion });
167
+ }
168
+ if (entry.latestVersion && entry.installedVersion && entry.latestVersion === entry.installedVersion) {
169
+ return batchText("batch.row.current", lang);
93
170
  }
171
+ if (entry.latestVersion && !entry.installedVersion) {
172
+ return batchText("batch.row.update", lang, { version: entry.latestVersion });
173
+ }
174
+ if (entry.lastCheckedAt > 0) return batchText("batch.row.current", lang);
175
+ return batchText("batch.row.never", lang);
94
176
  }
95
177
  function renderBatchPanelHTML(input) {
96
178
  const rows = Array.isArray(input.rows) ? input.rows : [];
@@ -105,16 +187,12 @@ function renderBatchPanelHTML(input) {
105
187
  const installing = counts.installing > 0;
106
188
  const macroBusy = input.inFlight === true || installing;
107
189
  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
190
  const disabled = macroBusy ? " disabled" : "";
114
191
  const notice = typeof input.notice === "string" && input.notice ? input.notice : null;
115
192
  const noticeKey = typeof input.noticeKey === "string" && input.noticeKey ? input.noticeKey : null;
116
193
  const detailNoticeKey = notice !== null && noticeKey !== null && noticeKey === expandedKey ? noticeKey : null;
117
194
  const changelogs = input.changelogs && typeof input.changelogs === "object" ? input.changelogs : {};
195
+ const lang = langOf(input["lang"]);
118
196
  const ctx = {
119
197
  expanded: false,
120
198
  busy: installing,
@@ -123,103 +201,144 @@ function renderBatchPanelHTML(input) {
123
201
  titles,
124
202
  skipped: null,
125
203
  notice: null,
126
- changelogMarkdown: null
204
+ changelogMarkdown: null,
205
+ lang,
206
+ knowledge: null,
207
+ inRound: false
127
208
  };
128
209
  const parts = [];
129
210
  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>');
211
+ 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
212
  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>');
213
+ parts.push('<button type="button" data-act="check"' + disabled + ' title="' + escapeHtml(batchText("batch.action.check-title", lang)) + '">' + escapeHtml(batchText("batch.action.check", lang)) + "</button>");
214
+ 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>");
215
+ 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
216
  parts.push("</div>");
136
217
  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";
218
+ const summary = loaded ? batchSummaryText(counts, rows, total, installing, lastError !== null, lang) : batchText("batch.banner.loading", lang);
138
219
  parts.push('<div class="dsh-upd-batch-sum" role="status" aria-live="polite">' + escapeHtml(summary) + "</div>");
139
- parts.push(bannersHTML(rows, titles, lastError));
220
+ parts.push(bannersHTML(rows, titles, lastError, lang));
221
+ const session = isObject(input.session) ? input.session : null;
222
+ const unfinished = unfinishedCount(rows, session ?? void 0);
223
+ const showControls = total > 0 && hasUnfinishedRows(rows, session ?? void 0);
224
+ const prefs = isObject(input.prefs) ? input.prefs : null;
225
+ const checkOnOpen = prefs === null ? null : prefs["checkOnOpen"] === false ? false : true;
226
+ function roundEntryPhase(key) {
227
+ if (!session) return null;
228
+ const entries = session["entries"];
229
+ if (!Array.isArray(entries)) return null;
230
+ for (const item of entries) {
231
+ if (!isObject(item)) continue;
232
+ const e = item;
233
+ if (e["key"] === key && typeof e["phase"] === "string") return e["phase"];
234
+ }
235
+ return null;
236
+ }
140
237
  parts.push('<div class="dsh-upd-btable">');
141
238
  for (const row of rows) {
142
239
  const raw = skippedVersions[row.key];
143
240
  const skipped = typeof raw === "string" && raw ? raw : null;
144
241
  const expanded = row.key === expandedKey;
242
+ const entryPhase = roundEntryPhase(row.key);
243
+ const inRound = entryPhase !== null && !isTerminalPhase(asBatchPhase(entryPhase));
145
244
  parts.push(
146
245
  rowSetHTML(row, {
147
246
  ...ctx,
148
247
  expanded,
149
248
  skipped,
150
249
  notice: expanded && row.key === detailNoticeKey ? notice : null,
151
- changelogMarkdown: expanded && typeof changelogs[row.key] === "string" ? changelogs[row.key] : null
250
+ changelogMarkdown: expanded && typeof changelogs[row.key] === "string" ? changelogs[row.key] : null,
251
+ lang,
252
+ knowledge: asBatchPhase(row.phase) === "pending" && !inRound ? knowledgeEntryOf(input.inventory, row.key) : null,
253
+ inRound
152
254
  })
153
255
  );
154
256
  }
155
257
  parts.push("</div>");
156
258
  if (total > 0) {
157
- parts.push('<div class="dsh-upd-batch-ledger">' + escapeHtml(batchLedgerText(counts)) + "</div>");
259
+ parts.push('<div class="dsh-upd-batch-ledger">' + escapeHtml(batchLedgerText(counts, lang)) + "</div>");
158
260
  }
159
- if (total > 0 && !terminal) {
261
+ if (showControls) {
160
262
  const more = [];
161
- if (stalled) more.push('<button type="button" data-act="resume">\u63A5\u7740\u4E0A\u6B21</button>');
263
+ more.push(
264
+ '<button type="button" data-act="resume">' + escapeHtml(batchText("batch.action.resume", lang, { count: String(unfinished) })) + "</button>"
265
+ );
162
266
  const confirmCancel = input.confirmCancel === true;
267
+ const discardLabel = confirmCancel ? batchText("batch.action.confirm-discard", lang) : batchText("batch.action.discard", lang);
163
268
  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>"
269
+ '<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
270
  );
166
271
  parts.push('<div class="dsh-upd-batch-more">' + more.join("") + "</div>");
272
+ parts.push('<div class="dsh-upd-batch-fact" role="note">' + escapeHtml(batchText("batch.fact.close-safe", lang)) + "</div>");
273
+ if (macroBusy) {
274
+ parts.push('<div class="dsh-upd-batch-fact" role="note">' + escapeHtml(batchText("batch.notice.busy-cancel", lang)) + "</div>");
275
+ }
276
+ }
277
+ if (prefs !== null && checkOnOpen !== null) {
278
+ parts.push(
279
+ '<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>"
280
+ );
167
281
  }
168
282
  if (notice !== null && detailNoticeKey === null) {
169
283
  parts.push('<div class="dsh-upd-batch-notice" role="status">' + escapeHtml(notice) + "</div>");
170
284
  }
171
285
  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);
286
+ const archive = normalizePanelTheme(theme) === "archive";
287
+ const attr = archive ? ' data-theme="archive"' : "";
288
+ const seal = batchSealOf(counts, lang);
175
289
  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>";
176
290
  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));
291
+ 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
292
  return "<style>" + css + "</style>\n" + body;
179
293
  }
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";
294
+ function batchSummaryText(counts, rows, total, installing, hasError, lang) {
295
+ const l = langOf(lang);
296
+ if (hasError) return batchText("batch.hint.error", l);
297
+ if (total === 0) return batchText("batch.hint.empty", l);
183
298
  if (installing) {
184
299
  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";
300
+ 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
301
  }
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";
302
+ if (counts.failed > 0) return batchText("batch.hint.failed", l, { n: String(counts.failed) });
188
303
  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";
304
+ return batchText("batch.hint.updatable", l, { n: String(counts.updatable) });
305
+ if (counts.restart > 0) return batchText("batch.hint.restart", l, { n: String(counts.restart) });
306
+ if (counts.pending > 0) return batchText("batch.hint.pending", l, { n: String(counts.pending) });
307
+ return batchText("batch.hint.done", l);
193
308
  }
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" };
309
+ function batchSealOf(counts, lang) {
310
+ const text = batchText("batch.seal.ledger", langOf(lang));
311
+ if (counts.failed > 0) return { text, tone: "red" };
312
+ if (counts.installing > 0 || counts.restart > 0) return { text, tone: "yellow" };
313
+ if (counts.updatable > 0) return { text, tone: "green" };
314
+ return { text, tone: "ink" };
199
315
  }
200
- function bannersHTML(rows, titles, lastError) {
316
+ function bannersHTML(rows, titles, lastError, lang) {
317
+ const l = langOf(lang);
201
318
  const parts = [];
202
319
  if (lastError) {
203
- const copy = failureCopy(lastError);
320
+ const copy = failureCopy(lastError, l);
321
+ const detail = copy?.zh ?? failureCopy("unknown", l)?.zh ?? "";
322
+ const act = copy?.act ?? batchText("batch.banner.error-action-fallback", l);
204
323
  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>"
324
+ '<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
325
  );
207
326
  }
208
327
  const failed = rows.filter((row) => asBatchPhase(row.phase) === "failed" && !isQueueBusyRow(row));
209
328
  if (failed.length > 0) {
210
329
  const lines = failed.map((row) => {
211
330
  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>";
331
+ const copy = failureCopy(code, l);
332
+ return "<div>" + escapeHtml(titleOf(row, titles)) + "\uFF1A<code>" + escapeHtml(code) + "</code> " + escapeHtml(copy?.zh ?? failureCopy("unknown", l)?.zh ?? "") + "</div>";
214
333
  }).join("");
215
334
  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>"
335
+ '<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
336
  );
218
337
  }
219
338
  const restart = rows.filter((row) => row.restartRequired === true);
220
339
  if (restart.length > 0) {
221
340
  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>'
341
+ '<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
342
  );
224
343
  }
225
344
  return parts.join("");
@@ -236,35 +355,43 @@ function rowSetHTML(row, ctx) {
236
355
  const off = ctx.inFlight ? " disabled" : "";
237
356
  if (phase === "installing") {
238
357
  actions.push(
239
- '<button type="button" data-act="row-install" data-key="' + keyAttr + '" disabled>\u5B89\u88C5\u4E2D\u2026</button>'
358
+ '<button type="button" data-act="row-install" data-key="' + keyAttr + '" disabled>' + escapeHtml(batchText("batch.row-action.installing", ctx.lang)) + "</button>"
240
359
  );
241
360
  } else if (queued) {
242
361
  if (cancelQueuePhoneOf(row) !== null && queuedRequestIdOf(row) !== null) {
243
362
  actions.push(
244
- '<button type="button" data-act="row-cancel-queue" data-key="' + keyAttr + '"' + off + ">\u53D6\u6D88\u6392\u961F</button>"
363
+ '<button type="button" data-act="row-cancel-queue" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.cancel-queue", ctx.lang)) + "</button>"
245
364
  );
246
365
  }
247
366
  } else if (phase === "ready" && skipped === null) {
248
367
  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>"
368
+ '<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
369
  );
251
370
  } else if (phase === "ready" && skipped !== null) {
252
371
  actions.push(
253
- '<button type="button" data-act="row-resume-skip" data-key="' + keyAttr + '"' + off + ">\u6062\u590D\uFF08" + escapeHtml(skipped) + "\uFF09</button>"
372
+ '<button type="button" data-act="row-resume-skip" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.unskip", ctx.lang, { version: skipped })) + "</button>"
254
373
  );
255
374
  } else if (phase === "failed") {
256
375
  actions.push(
257
- '<button type="button" data-act="row-install" data-key="' + keyAttr + '"' + off + ">" + (ctx.busy ? "\u52A0\u5165\u961F\u5217" : "\u91CD\u8BD5") + "</button>"
376
+ '<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
377
  );
259
378
  }
260
379
  if (row.restartRequired === true) {
261
- actions.push('<button type="button" data-act="restart" data-key="' + keyAttr + '">\u91CD\u542F\u5BBF\u4E3B</button>');
380
+ actions.push('<button type="button" data-act="restart" data-key="' + keyAttr + '">' + escapeHtml(batchText("batch.row-action.restart", ctx.lang)) + "</button>");
262
381
  }
263
382
  actions.push(
264
- '<button type="button" data-act="toggle-details" data-key="' + keyAttr + '">' + (ctx.expanded ? "\u6536\u8D77" : "\u8BE6\u60C5") + "</button>"
383
+ '<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
384
  );
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>";
385
+ const failLine = phase === "failed" && !queued ? (() => {
386
+ const failCode = row.error || "install-failed";
387
+ const failDetail = failureCopy(failCode, ctx.lang)?.zh ?? failureCopy("unknown", ctx.lang)?.zh ?? "";
388
+ const label = batchText("batch.row.error-label", ctx.lang, { code: failCode, detail: failDetail });
389
+ const escaped = escapeHtml(label);
390
+ const codeEsc = escapeHtml(failCode);
391
+ const withCode = escaped.includes(codeEsc) ? escaped.replace(codeEsc, "<code>" + codeEsc + "</code>") : escaped;
392
+ return '<span class="dsh-upd-bfail">' + withCode + "</span>";
393
+ })() : "";
394
+ 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
395
  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
396
  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
397
  }
@@ -286,7 +413,7 @@ function detailHTML(row, ctx) {
286
413
  theme: ctx.theme,
287
414
  profileName: typeof row.profileName === "string" && row.profileName ? row.profileName : null,
288
415
  actions: "none"
289
- });
416
+ }, ctx.lang);
290
417
  return stripActionButtons(html);
291
418
  }
292
419
  function detailActionsHTML(row, ctx) {
@@ -297,38 +424,38 @@ function detailActionsHTML(row, ctx) {
297
424
  const off = ctx.inFlight ? " disabled" : "";
298
425
  const buttons = [];
299
426
  if (phase === "installing") {
300
- buttons.push('<button type="button" data-act="row-install" data-key="' + keyAttr + '" disabled>\u5B89\u88C5\u4E2D\u2026</button>');
427
+ buttons.push('<button type="button" data-act="row-install" data-key="' + keyAttr + '" disabled>' + escapeHtml(batchText("batch.row-action.installing", ctx.lang)) + "</button>");
301
428
  } else if (queued) {
302
429
  if (cancelQueuePhoneOf(row) !== null && queuedRequestIdOf(row) !== null) {
303
430
  buttons.push(
304
- '<button type="button" data-act="row-cancel-queue" data-key="' + keyAttr + '"' + off + ">\u53D6\u6D88\u6392\u961F</button>"
431
+ '<button type="button" data-act="row-cancel-queue" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.cancel-queue", ctx.lang)) + "</button>"
305
432
  );
306
433
  }
307
434
  } else if (phase === "failed") {
308
435
  buttons.push(
309
- '<button type="button" data-act="row-install" data-key="' + keyAttr + '"' + off + ">" + (ctx.busy ? "\u52A0\u5165\u961F\u5217" : "\u91CD\u8BD5") + "</button>"
436
+ '<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
437
  );
311
438
  } else if (phase === "ready" && ctx.skipped === null) {
312
439
  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>"
440
+ '<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
441
  );
315
442
  }
316
443
  if (!queued && phase === "ready" && ctx.skipped === null && version !== null) {
317
444
  buttons.push(
318
- '<button type="button" data-act="row-skip" data-key="' + keyAttr + '"' + off + ">\u8DF3\u8FC7\u8FD9\u4E00\u7248</button>"
445
+ '<button type="button" data-act="row-skip" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.skip", ctx.lang)) + "</button>"
319
446
  );
320
447
  }
321
448
  if (ctx.skipped !== null) {
322
449
  buttons.push(
323
- '<button type="button" data-act="row-resume-skip" data-key="' + keyAttr + '"' + off + ">\u6062\u590D\uFF08" + escapeHtml(ctx.skipped) + "\uFF09</button>"
450
+ '<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
451
  );
325
452
  }
326
453
  if (typeof row.manual === "string" && row.manual) {
327
454
  buttons.push(
328
- '<button type="button" data-act="row-copy-manual" data-key="' + keyAttr + '"' + off + ">\u590D\u5236\u624B\u5DE5\u547D\u4EE4</button>"
455
+ '<button type="button" data-act="row-copy-manual" data-key="' + keyAttr + '"' + off + ">" + escapeHtml(batchText("batch.row-action.copy-manual", ctx.lang)) + "</button>"
329
456
  );
330
457
  }
331
- buttons.push('<button type="button" data-act="row-copy-diag" data-key="' + keyAttr + '"' + off + ">\u590D\u5236\u8BCA\u65AD</button>");
458
+ 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
459
  return '<span class="dsh-upd-bdetail-actions">' + buttons.join("") + "</span>";
333
460
  }
334
461
  function stripActionButtons(html) {
@@ -344,37 +471,37 @@ function dotToneOf(row, phase, skipped, queued) {
344
471
  return "idle";
345
472
  }
346
473
  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 */",
474
+ "/* \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
475
  ".dsh-upd-batch-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;flex-wrap:wrap}",
349
476
  ".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)}",
477
+ ".dsh-upd-batch-title i{font-style:normal;color:var(--dsh-update-primary,#2563eb)}",
351
478
  ".dsh-upd-batch-macros{flex:none}",
352
479
  ".dsh-upd-batch-sum{margin:4px 0 8px;font-size:13.5px}",
353
480
  ".dsh-upd-btable{margin-top:2px}",
354
- ".dsh-upd-brow-set{border-top:1px solid var(--dsh-upd-line,#e5e7eb)}",
481
+ ".dsh-upd-brow-set{border-top:1px solid var(--dsh-update-border,#e5e7eb)}",
355
482
  ".dsh-upd-brow-set:first-child{border-top:0}",
356
483
  ".dsh-upd-brow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:6px 0}",
357
484
  ".dsh-upd .dsh-upd-brow-main{display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex:1 1 260px;min-width:0;",
358
485
  "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)}",
486
+ ".dsh-upd .dsh-upd-brow-main:hover{color:var(--dsh-update-primary,#2563eb)}",
360
487
  ".dsh-upd-brow-actions{flex:none;margin-left:auto}",
361
488
  ".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}',
489
+ ".dsh-upd-updot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--dsh-update-border,#9ca3af);opacity:.55}",
490
+ '.dsh-upd-updot[data-tone="todo"]{background:var(--dsh-update-ok-border,#059669);opacity:1}',
491
+ '.dsh-upd-updot[data-tone="ok"]{background:var(--dsh-update-ok-border,#059669);opacity:1}',
492
+ '.dsh-upd-updot[data-tone="busy"]{background:var(--dsh-update-busy-border,#2563eb);opacity:1}',
493
+ '.dsh-upd-updot[data-tone="warn"]{background:var(--dsh-update-warn-border,#d97706);opacity:1}',
494
+ '.dsh-upd-updot[data-tone="bad"]{background:var(--dsh-update-bad-border,#dc2626);opacity:1}',
368
495
  ".dsh-upd-bname{font-weight:700;flex:none}",
369
496
  ".dsh-upd-bver{font-family:Consolas,Menlo,monospace;font-size:12px;opacity:.8;flex:none}",
370
497
  ".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)}",
498
+ ".dsh-upd-bfail{flex:1 1 100%;display:block;font-size:12.5px;color:var(--dsh-update-bad-border,#dc2626)}",
372
499
  ".dsh-upd-blist{font-size:12.5px;margin-top:2px}",
373
500
  ".dsh-upd-batch-ledger{margin:8px 0 0;font-size:13px;opacity:.85}",
374
501
  ".dsh-upd-batch-more{margin-top:6px}",
375
502
  ".dsh-upd-batch-more button{font-size:12px}",
376
503
  // 确认态:红框红字,与「关闭」等中性按钮一眼区分;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)}',
504
+ '.dsh-upd-batch-more button[data-confirm="1"]{border-color:var(--dsh-update-bad-border,#dc2626);color:var(--dsh-update-bad-border,#dc2626)}',
378
505
  ".dsh-upd-batch-notice{margin-top:6px;font-size:12.5px;opacity:.85}",
379
506
  ".dsh-upd-bdetail-actions{margin:0 0 6px}",
380
507
  ".dsh-upd .dsh-upd-bdetail-actions button{font-size:12.5px}",
@@ -385,21 +512,28 @@ const UPDATE_BATCH_PANEL_CSS = [
385
512
  ".dsh-upd-overlay .dsh-upd-bdetail .dsh-upd{max-height:none;overflow:visible}",
386
513
  ".dsh-upd-overlay .dsh-upd-bdetail .dsh-upd-body{overflow:visible}",
387
514
  ".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}}"
515
+ "@media (prefers-color-scheme: dark){.dsh-upd-batch-title i{color:var(--dsh-update-focus,#93c5fd)}}",
516
+ "@media (prefers-reduced-motion: reduce){.dsh-upd-batch *{transition:none !important;animation:none !important}}",
517
+ // Batch check layout stability: reserve macro width + rows/summary heights + no scroll anchoring jumps.
518
+ ".dsh-upd-batch{overflow-anchor:none}",
519
+ ".dsh-upd-btable{overflow-anchor:none}",
520
+ ".dsh-upd-batch-macros{display:flex;flex-wrap:wrap;align-items:center;gap:0}",
521
+ '.dsh-upd-batch-macros button:first-child,.dsh-upd-batch-macros button[data-primary="1"]{min-width:6em;text-align:center}',
522
+ ".dsh-upd-batch-sum,.dsh-upd-batch-ledger{min-height:1.6em}",
523
+ ".dsh-upd-brow{min-height:28px}"
390
524
  ].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 */",
525
+ const UPDATE_BATCH_PANEL_ARCHIVE_CSS = [
526
+ "/* \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
527
  '.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)}'
528
+ '.dsh-upd[data-theme="archive"] .dsh-upd-batch-title{font-family:var(--dsh-update-font-serif);font-size:21px;letter-spacing:.04em}',
529
+ '.dsh-upd[data-theme="archive"] .dsh-upd-batch-title i{color:var(--dsh-update-primary)}',
530
+ '.dsh-upd[data-theme="archive"] .dsh-upd-brow-main{background:transparent;border:0;color:var(--dsh-update-text)}',
531
+ '.dsh-upd[data-theme="archive"] .dsh-upd-brow-main:hover{color:var(--dsh-update-primary)}',
532
+ '.dsh-upd[data-theme="archive"] .dsh-upd-bname{font-family:var(--dsh-update-font-serif)}',
533
+ '.dsh-upd[data-theme="archive"] .dsh-upd-bver{font-family:var(--dsh-update-font-mono);color:var(--dsh-update-text-muted)}',
534
+ '.dsh-upd[data-theme="archive"] .dsh-upd-batch-ledger{color:var(--dsh-update-text-muted)}',
535
+ '.dsh-upd[data-theme="archive"] .dsh-upd-batch-sum{color:var(--dsh-update-text)}',
536
+ '.dsh-upd[data-theme="archive"] .dsh-upd-brow-set{border-top-color:var(--dsh-update-border)}'
403
537
  ].join(String.fromCharCode(10));
404
538
  function escapeHtml(text) {
405
539
  return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -513,14 +647,14 @@ function jobFailureOf(row) {
513
647
  const detail = message.slice(at + 1).trim();
514
648
  return { code: code || null, detail: detail || null };
515
649
  }
516
- function diagTextOf(row) {
650
+ function diagTextOf(row, lang) {
517
651
  const snapshot = isObject(row.snapshot) ? row.snapshot : null;
518
652
  const queue = isObject(row.queue) ? row.queue : null;
519
653
  const diag = isObject(row.diag) ? row.diag : null;
520
654
  const text = (value) => typeof value === "string" && value ? value : null;
521
655
  const job = jobFailureOf(row);
522
656
  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;
657
+ const detail = diagDetail !== null ? diagDetail : job !== null && job.detail !== null ? job.detail + batchText("batch.diag.source-job", langOf(lang)) : null;
524
658
  const input = {
525
659
  pluginId: pluginIdOf(row),
526
660
  code: job !== null && job.code !== null ? job.code : row.error || "check-failed",
@@ -532,7 +666,9 @@ function diagTextOf(row) {
532
666
  queuePosition: queue && typeof queue["position"] === "number" ? queue["position"] : null,
533
667
  manual: text(row.manual),
534
668
  diag: row.diag ?? null,
535
- profileName: text(row.profileName)
669
+ profileName: text(row.profileName),
670
+ // #63 透传语言(批量 en 跟随在 #64/#65 落;此处缺省 zh 零回归)。
671
+ lang: lang ?? "zh"
536
672
  };
537
673
  return buildDiagnosticText(input);
538
674
  }
@@ -622,8 +758,8 @@ function mountUpdateBatchPanel(container, options) {
622
758
  if (mode !== "embedded" && mode !== "dialog") {
623
759
  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
760
  }
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");
761
+ if (options.theme !== void 0 && options.theme !== "default" && options.theme !== "archive") {
762
+ 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
763
  }
628
764
  let theme = normalizePanelTheme(options.theme ?? "default");
629
765
  const call = options.call;
@@ -647,6 +783,18 @@ function mountUpdateBatchPanel(container, options) {
647
783
  let refreshing = false;
648
784
  let confirmCancelArmed = false;
649
785
  let mounted = true;
786
+ const localeOpt = options["locale"];
787
+ function currentLang() {
788
+ return langOf(localeOpt);
789
+ }
790
+ const checkOnOpenOption = options["checkOnOpen"] !== false;
791
+ const autoResumeOption = options["autoResume"] !== false;
792
+ let lastSession = null;
793
+ let lastInventory = null;
794
+ let lastPrefs = null;
795
+ let lastCheckedAt = 0;
796
+ let autoCheckDone = false;
797
+ let autoResumeDone = false;
650
798
  function changelogView() {
651
799
  const out = {};
652
800
  for (const row of rows) {
@@ -680,13 +828,13 @@ function mountUpdateBatchPanel(container, options) {
680
828
  const failKey = key + "\0" + version;
681
829
  const hasCache = false;
682
830
  const failedAt = rowFailedAt.has(failKey) ? rowFailedAt.get(failKey) : null;
683
- let nowMs = 0;
831
+ let nowMs2 = 0;
684
832
  try {
685
- nowMs = Date.now();
833
+ nowMs2 = Date.now();
686
834
  } catch {
687
- nowMs = 0;
835
+ nowMs2 = 0;
688
836
  }
689
- if (!shouldFetchChangelog({ hasCache, failedAt, now: nowMs, isManual: false })) return;
837
+ if (!shouldFetchChangelog({ hasCache, failedAt, now: nowMs2, isManual: false })) return;
690
838
  } catch {
691
839
  }
692
840
  const phone2 = changelogPhoneOf(row);
@@ -727,6 +875,77 @@ function mountUpdateBatchPanel(container, options) {
727
875
  }
728
876
  );
729
877
  }
878
+ function setStableHTML(target, html) {
879
+ try {
880
+ const g = globalThis;
881
+ const doc = g["document"];
882
+ const el = target;
883
+ if (!doc || typeof el.querySelectorAll !== "function") {
884
+ target.innerHTML = html;
885
+ return;
886
+ }
887
+ let focusAct = null;
888
+ let focusKey = null;
889
+ try {
890
+ const active = doc.activeElement;
891
+ if (active && typeof active.getAttribute === "function") {
892
+ focusAct = active.getAttribute("data-act");
893
+ focusKey = active.getAttribute("data-key");
894
+ }
895
+ } catch {
896
+ focusAct = null;
897
+ focusKey = null;
898
+ }
899
+ let scrolls = [];
900
+ try {
901
+ const nodes = el.querySelectorAll(".dsh-upd-btable");
902
+ if (nodes) {
903
+ for (let i = 0; i < nodes.length; i++) {
904
+ const n = nodes[i];
905
+ scrolls.push(typeof n.scrollTop === "number" ? n.scrollTop : 0);
906
+ }
907
+ }
908
+ } catch {
909
+ scrolls = [];
910
+ }
911
+ target.innerHTML = html;
912
+ try {
913
+ const bodies = el.querySelectorAll(".dsh-upd-btable");
914
+ if (bodies) {
915
+ for (let i = 0; i < bodies.length && i < scrolls.length; i++) {
916
+ const n = bodies[i];
917
+ try {
918
+ n.scrollTop = scrolls[i];
919
+ } catch {
920
+ }
921
+ }
922
+ }
923
+ } catch {
924
+ }
925
+ try {
926
+ if (focusAct && typeof el.querySelector === "function") {
927
+ const sel = focusKey ? '[data-act="' + focusAct + '"][data-key="' + focusKey + '"]' : '[data-act="' + focusAct + '"]';
928
+ const next = el.querySelector(sel);
929
+ if (next && typeof next.focus === "function") {
930
+ try {
931
+ next.focus.call(next, { preventScroll: true });
932
+ } catch {
933
+ try {
934
+ next.focus.call(next);
935
+ } catch {
936
+ }
937
+ }
938
+ }
939
+ }
940
+ } catch {
941
+ }
942
+ } catch {
943
+ try {
944
+ target.innerHTML = html;
945
+ } catch {
946
+ }
947
+ }
948
+ }
730
949
  let lastHTML = "";
731
950
  function render() {
732
951
  if (!mounted) return;
@@ -743,11 +962,15 @@ function mountUpdateBatchPanel(container, options) {
743
962
  loaded,
744
963
  titles,
745
964
  confirmCancel: confirmCancelArmed,
746
- changelogs: changelogView()
965
+ changelogs: changelogView(),
966
+ session: lastSession,
967
+ inventory: lastInventory,
968
+ prefs: lastPrefs,
969
+ lang: currentLang()
747
970
  });
748
971
  if (nextHTML !== lastHTML) {
749
972
  lastHTML = nextHTML;
750
- container.innerHTML = nextHTML;
973
+ setStableHTML(container, nextHTML);
751
974
  }
752
975
  }
753
976
  function rowOfKey(key) {
@@ -789,7 +1012,7 @@ function mountUpdateBatchPanel(container, options) {
789
1012
  await copyText(text);
790
1013
  say(okNotice, key);
791
1014
  } catch {
792
- say("\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u4E0A\u9762\u7684\u4FE1\u606F\u3002", key);
1015
+ say(batchText("batch.toast.copy-fail", currentLang()), key);
793
1016
  }
794
1017
  render();
795
1018
  }
@@ -802,6 +1025,13 @@ function mountUpdateBatchPanel(container, options) {
802
1025
  const session = normalizeBatchSession(reply["session"]);
803
1026
  const parsed = readRows(reply["rows"]);
804
1027
  rows = parsed.length > 0 ? parsed : rowsFromSession(session);
1028
+ lastSession = session;
1029
+ if (isObject(reply["inventory"])) {
1030
+ lastInventory = reply["inventory"];
1031
+ const at = reply["inventory"]["updatedAt"];
1032
+ if (typeof at === "number" && Number.isFinite(at) && at > 0) lastCheckedAt = at;
1033
+ }
1034
+ if (isObject(reply["prefs"])) lastPrefs = reply["prefs"];
805
1035
  loaded = true;
806
1036
  lastError = null;
807
1037
  if (expandedKey !== null && !rows.some((row) => row.key === expandedKey)) expandedKey = null;
@@ -828,27 +1058,49 @@ function mountUpdateBatchPanel(container, options) {
828
1058
  maybeAutoRowChangelog(expandedKey);
829
1059
  }
830
1060
  async function phone(name, args, busyRowKey = null) {
831
- if (!mounted || inFlight) return;
1061
+ if (!mounted || inFlight) return null;
832
1062
  inFlight = true;
833
1063
  clearNotice();
834
1064
  render();
1065
+ let out = null;
835
1066
  try {
836
1067
  const reply = await call(name, args);
837
- if (!mounted) return;
1068
+ if (!mounted) return null;
838
1069
  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;
1070
+ say(batchText("batch.toast.queue-missed", currentLang()), busyRowKey);
1071
+ return null;
841
1072
  }
842
1073
  applyReply(reply);
843
1074
  rowFailedAt.clear();
1075
+ out = isObject(reply) ? reply : null;
844
1076
  } catch {
845
- if (!mounted) return;
1077
+ if (!mounted) return null;
846
1078
  lastError = "check-failed";
1079
+ out = null;
847
1080
  } finally {
848
1081
  inFlight = false;
849
1082
  if (mounted) render();
850
1083
  }
851
1084
  maybeAutoRowChangelog(expandedKey);
1085
+ return out;
1086
+ }
1087
+ function remainingCount() {
1088
+ return unfinishedCount(rows, lastSession ?? void 0);
1089
+ }
1090
+ function checkOnOpenEffective() {
1091
+ if (isObject(lastPrefs)) {
1092
+ const v = lastPrefs["checkOnOpen"];
1093
+ if (v === false) return false;
1094
+ if (v === true) return true;
1095
+ }
1096
+ return checkOnOpenOption;
1097
+ }
1098
+ function nowMs() {
1099
+ try {
1100
+ return Date.now();
1101
+ } catch {
1102
+ return 0;
1103
+ }
852
1104
  }
853
1105
  async function act(kind, key) {
854
1106
  if (!mounted) return;
@@ -858,17 +1110,26 @@ function mountUpdateBatchPanel(container, options) {
858
1110
  }
859
1111
  switch (kind) {
860
1112
  case "check":
861
- return phone(phones.check, {});
1113
+ await phone(phones.check, {});
1114
+ return;
862
1115
  case "install":
863
- return phone(phones.install, {});
1116
+ await phone(phones.install, {});
1117
+ return;
864
1118
  case "row-install": {
865
1119
  if (typeof key !== "string" || !key) return;
866
- return phone(phones.install, { keys: [key] }, key);
1120
+ await phone(phones.install, { keys: [key] }, key);
1121
+ return;
867
1122
  }
868
1123
  case "resume": {
869
- await phone(phones.resume, {});
1124
+ const reply = await phone(phones.resume, {});
870
1125
  if (!mounted) return;
871
- say("\u5DF2\u63A5\u7740\u4E0A\u6B21\u7684\u4F1A\u8BDD\u63A8\u8FDB\u4E00\u6B65\u3002");
1126
+ if (!reply || reply["ok"] !== true) return;
1127
+ if (reply["resumed"] === false) {
1128
+ say(batchText("batch.notice.no-resume", currentLang()));
1129
+ render();
1130
+ return;
1131
+ }
1132
+ say(batchText("batch.notice.resumed", currentLang(), { count: String(remainingCount()) }));
872
1133
  render();
873
1134
  return;
874
1135
  }
@@ -876,9 +1137,18 @@ function mountUpdateBatchPanel(container, options) {
876
1137
  // 鼠标误触的防护在 onClick 那一层(第一次点击只上膛)。
877
1138
  case "cancel": {
878
1139
  confirmCancelArmed = false;
879
- await phone(phones.cancel, {});
1140
+ const reply = await phone(phones.cancel, {});
880
1141
  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");
1142
+ if (!reply || reply["ok"] !== true) return;
1143
+ say(batchText("batch.notice.discarded", currentLang()));
1144
+ render();
1145
+ return;
1146
+ }
1147
+ case "toggle-check-on-open": {
1148
+ const next = !checkOnOpenEffective();
1149
+ const reply = await phone(phones.prefsSave, { checkOnOpen: next });
1150
+ if (!mounted) return;
1151
+ if (reply && reply["ok"] === true && isObject(reply["prefs"])) lastPrefs = reply["prefs"];
882
1152
  render();
883
1153
  return;
884
1154
  }
@@ -891,7 +1161,7 @@ function mountUpdateBatchPanel(container, options) {
891
1161
  skipStoreFor(pluginIdOf(row)).skip(version);
892
1162
  } catch {
893
1163
  }
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);
1164
+ say(batchText("batch.toast.skipped", currentLang(), { version }), row.key);
895
1165
  render();
896
1166
  return;
897
1167
  }
@@ -903,7 +1173,7 @@ function mountUpdateBatchPanel(container, options) {
903
1173
  skipStoreFor(pluginIdOf(row)).reset(version ?? void 0);
904
1174
  } catch {
905
1175
  }
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);
1176
+ say(version ? batchText("batch.toast.unskipped-version", currentLang(), { version }) : batchText("batch.toast.unskipped-all", currentLang()), row.key);
907
1177
  render();
908
1178
  return;
909
1179
  }
@@ -915,7 +1185,7 @@ function mountUpdateBatchPanel(container, options) {
915
1185
  const phoneName = cancelQueuePhoneOf(row);
916
1186
  const requestId = queuedRequestIdOf(row);
917
1187
  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);
1188
+ say(batchText("batch.toast.cancel-unavailable", currentLang()), row.key);
919
1189
  render();
920
1190
  return;
921
1191
  }
@@ -925,9 +1195,9 @@ function mountUpdateBatchPanel(container, options) {
925
1195
  render();
926
1196
  try {
927
1197
  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);
1198
+ if (mounted) say(batchText("batch.toast.cancel-ok", currentLang()), row.key);
929
1199
  } 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);
1200
+ if (mounted) say(batchText("batch.toast.cancel-fail", currentLang()), row.key);
931
1201
  } finally {
932
1202
  inFlight = false;
933
1203
  }
@@ -938,13 +1208,13 @@ function mountUpdateBatchPanel(container, options) {
938
1208
  const row = typeof key === "string" && key ? rowOfKey(key) : null;
939
1209
  const manual = row && typeof row.manual === "string" ? row.manual : "";
940
1210
  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);
1211
+ await copyWith(manual, batchText("batch.toast.copy-manual-ok", currentLang()), row.key);
942
1212
  return;
943
1213
  }
944
1214
  case "row-copy-diag": {
945
1215
  const row = typeof key === "string" && key ? rowOfKey(key) : null;
946
1216
  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);
1217
+ await copyWith(diagTextOf(row, currentLang()), batchText("batch.toast.copy-diag-ok", currentLang()), row.key);
948
1218
  return;
949
1219
  }
950
1220
  case "toggle-details": {
@@ -960,12 +1230,12 @@ function mountUpdateBatchPanel(container, options) {
960
1230
  try {
961
1231
  if (typeof onRestartRequested === "function") {
962
1232
  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");
1233
+ say(batchText("batch.toast.restart-delegated", currentLang()));
964
1234
  } 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");
1235
+ say(batchText("batch.toast.restart-manual", currentLang()));
966
1236
  }
967
1237
  } 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");
1238
+ say(batchText("batch.toast.restart-failed", currentLang()));
969
1239
  }
970
1240
  render();
971
1241
  return;
@@ -997,8 +1267,8 @@ function mountUpdateBatchPanel(container, options) {
997
1267
  }
998
1268
  }
999
1269
  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");
1270
+ if (next !== "default" && next !== "archive") {
1271
+ throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 archive\uFF08\u6536\u5230 " + JSON.stringify(next) + "\uFF09");
1002
1272
  }
1003
1273
  theme = normalizePanelTheme(next);
1004
1274
  render();
@@ -1018,7 +1288,7 @@ function mountUpdateBatchPanel(container, options) {
1018
1288
  if (!kind) return;
1019
1289
  if (kind === "cancel" && !confirmCancelArmed) {
1020
1290
  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");
1291
+ say(batchText("batch.notice.cancel-confirm", currentLang()));
1022
1292
  render();
1023
1293
  return;
1024
1294
  }
@@ -1028,9 +1298,64 @@ function mountUpdateBatchPanel(container, options) {
1028
1298
  } catch {
1029
1299
  }
1030
1300
  }
1301
+ async function autoOnce() {
1302
+ if (!mounted) return;
1303
+ if (autoResumeOption && !autoResumeDone) {
1304
+ autoResumeDone = true;
1305
+ if (hasUnfinishedRows(rows, lastSession ?? void 0) && !inFlight && !refreshing) {
1306
+ const before = lastError;
1307
+ try {
1308
+ const reply = await call(phones.resume, {});
1309
+ if (!mounted) return;
1310
+ if (isObject(reply) && reply["ok"] === true) {
1311
+ applyReply(reply);
1312
+ const r = reply;
1313
+ if (r["resumed"] !== false) {
1314
+ say(batchText("batch.notice.auto-resumed", currentLang(), { count: String(remainingCount()) }));
1315
+ rowFailedAt.clear();
1316
+ }
1317
+ render();
1318
+ maybeAutoRowChangelog(expandedKey);
1319
+ return;
1320
+ }
1321
+ lastError = before;
1322
+ render();
1323
+ } catch {
1324
+ if (mounted) render();
1325
+ }
1326
+ }
1327
+ }
1328
+ if (!autoCheckDone) {
1329
+ autoCheckDone = true;
1330
+ if (!checkOnOpenEffective()) return;
1331
+ if (hasUnfinishedRows(rows, lastSession ?? void 0)) return;
1332
+ const stale = lastCheckedAt <= 0 || nowMs() - lastCheckedAt >= 6e4;
1333
+ if (!stale || inFlight || refreshing) return;
1334
+ const before = lastError;
1335
+ try {
1336
+ const reply = await call(phones.check, {});
1337
+ if (!mounted) return;
1338
+ if (isObject(reply) && reply["ok"] === true) {
1339
+ applyReply(reply);
1340
+ rowFailedAt.clear();
1341
+ render();
1342
+ maybeAutoRowChangelog(expandedKey);
1343
+ return;
1344
+ }
1345
+ lastError = before;
1346
+ render();
1347
+ } catch {
1348
+ if (mounted) render();
1349
+ }
1350
+ }
1351
+ }
1031
1352
  function unmount() {
1032
1353
  if (!mounted) return;
1033
1354
  mounted = false;
1355
+ try {
1356
+ unsubLang();
1357
+ } catch {
1358
+ }
1034
1359
  rowAutoSeq++;
1035
1360
  rowInflight.clear();
1036
1361
  try {
@@ -1046,6 +1371,9 @@ function mountUpdateBatchPanel(container, options) {
1046
1371
  } catch {
1047
1372
  }
1048
1373
  }
1374
+ const unsubLang = subscribeLang(() => {
1375
+ render();
1376
+ }, localeOpt);
1049
1377
  render();
1050
1378
  try {
1051
1379
  container.addEventListener?.("click", onClick);
@@ -1057,6 +1385,7 @@ function mountUpdateBatchPanel(container, options) {
1057
1385
  }
1058
1386
  const timer = getTimer();
1059
1387
  const handle = timer.set(() => {
1388
+ if (inFlight) return;
1060
1389
  void refresh();
1061
1390
  }, pollMs);
1062
1391
  try {
@@ -1064,18 +1393,23 @@ function mountUpdateBatchPanel(container, options) {
1064
1393
  if (h && typeof h.unref === "function") h.unref();
1065
1394
  } catch {
1066
1395
  }
1067
- void refresh();
1396
+ void refresh().then(() => {
1397
+ void autoOnce();
1398
+ });
1068
1399
  return { refresh, act, setTheme, setMode, unmount };
1069
1400
  }
1070
1401
  export {
1071
1402
  BATCH_PANEL_POLL,
1403
+ UPDATE_BATCH_PANEL_ARCHIVE_CSS,
1072
1404
  UPDATE_BATCH_PANEL_CSS,
1073
- UPDATE_BATCH_PANEL_D5_CSS,
1074
1405
  batchLedgerCounts,
1075
1406
  batchLedgerText,
1076
1407
  batchQueuedStatus,
1408
+ batchRowKnowledgeText,
1077
1409
  batchRowStatus,
1078
1410
  buildBatchPhoneNames,
1411
+ hasUnfinishedRows,
1079
1412
  mountUpdateBatchPanel,
1080
- renderBatchPanelHTML
1413
+ renderBatchPanelHTML,
1414
+ unfinishedCount
1081
1415
  };