dsh-recall-plugin 2.4.6 → 2.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -77,11 +77,11 @@
77
77
  // 变色、不套 ring。替换 V2-5 的 border-l3 环方案(当时核验的是旧版写法)。
78
78
  ".dsh-recall-btn:focus-visible,.dsh-recall-ex-chip:focus-visible,.dsh-recall-tree-toggle:focus-visible,.dsh-recall-cfg-switch:focus-visible,.dsh-recall-icon-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}",
79
79
  ".dsh-recall-cardbtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}",
80
- ".dsh-recall-ex-input:focus-visible,.dsh-recall-cfg-input:focus-visible,.dsh-recall-ex-area:focus-visible,.dsh-recall-cfg-area:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}",
80
+ ".dsh-recall-ex-input:focus-visible,.dsh-recall-cfg-input:focus-visible,.dsh-recall-cfg-select:focus-visible,.dsh-recall-ex-area:focus-visible,.dsh-recall-cfg-area:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}",
81
81
  // 输入类 hover 中间态:rest l4 → hover l3 → focus brand 三级递进,与按钮的
82
82
  // 描边 hover 逻辑同语言;:not(:focus-visible) 收口是防 hover 伪类(特异性更
83
83
  // 高)把 focus 的品牌色描边刷回 l3
84
- ".dsh-recall-ex-input:hover:not(:disabled):not(:focus-visible),.dsh-recall-cfg-input:hover:not(:disabled):not(:focus-visible),.dsh-recall-ex-area:hover:not(:disabled):not(:focus-visible),.dsh-recall-cfg-area:hover:not(:disabled):not(:focus-visible){border-color:var(--dsw-alias-border-l3)}",
84
+ ".dsh-recall-ex-input:hover:not(:disabled):not(:focus-visible),.dsh-recall-cfg-input:hover:not(:disabled):not(:focus-visible),.dsh-recall-cfg-select:hover:not(:disabled):not(:focus-visible),.dsh-recall-ex-area:hover:not(:disabled):not(:focus-visible),.dsh-recall-cfg-area:hover:not(:disabled):not(:focus-visible){border-color:var(--dsw-alias-border-l3)}",
85
85
  ".dsh-recall-toast{position:fixed;top:18px;left:50%;transform:translateX(-50%);z-index:10000;max-width:min(560px,86vw);box-sizing:border-box;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:10px 16px;font-size:13px;line-height:1.5;box-shadow:0 8px 28px rgba(0,0,0,.22);display:flex;align-items:baseline;gap:8px;opacity:0;transition:opacity .25s ease;pointer-events:auto}",
86
86
  ".dsh-recall-toast.dsh-recall-toast-in{opacity:1}",
87
87
  ".dsh-recall-toast-tag{flex:none;font-weight:600;color:var(--dsw-alias-state-error-primary)}",
@@ -244,13 +244,17 @@
244
244
  ".dsh-recall-cfg-input{flex:none;width:var(--dsh-recall-input-w);box-sizing:border-box;height:34px;font:inherit;font-size:13px;text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:0 12px}",
245
245
  // disabled 随官方 input:文字降 tertiary,不动透明度(整框变淡会让「框还在
246
246
  // 只是不可写」的语义变含糊)
247
- ".dsh-recall-cfg-input:disabled,.dsh-recall-cfg-area:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}",
247
+ ".dsh-recall-cfg-input:disabled,.dsh-recall-cfg-select:disabled,.dsh-recall-cfg-area:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}",
248
248
  // 数字输入框隐藏原生加减微调按钮(spinner):34px 高的定宽框里 spinner 挤占
249
249
  // 右侧数字区、跨引擎渲染不一(Chromium 上下箭头 / Firefox 无),与「右对齐
250
250
  // 数字」的纵向扫描相冲;隐藏后键盘 ↑↓ 与直接输入仍可用,步进语义不丢。
251
251
  // scope 到 cfg-input,不外溢影响宿主或其他插件的 number 输入。
252
252
  ".dsh-recall-cfg-input::-webkit-inner-spin-button,.dsh-recall-cfg-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}",
253
253
  ".dsh-recall-cfg-input{-moz-appearance:textfield;appearance:textfield}",
254
+ // 语言下拉(A4):与数字输入框同一套描边/底色/高度配方,差异只在文本左对齐
255
+ // 与宽度自适应——select 的选项文本长度不可控(Follow system (auto) 最长),
256
+ // 定宽会截断;min-width 与输入框同宽保住「控件列」的视觉对齐基线
257
+ ".dsh-recall-cfg-select{flex:none;box-sizing:border-box;height:34px;min-width:var(--dsh-recall-input-w);font:inherit;font-size:13px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:0 8px 0 10px}",
254
258
  ".dsh-recall-cfg-area{font-family:inherit;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3);border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;padding:6px 8px;min-height:64px;box-sizing:border-box;width:100%;grid-column:2}",
255
259
  // 布尔开关:官方设置表单的布尔字段用 role=switch 滑钮而非原生 checkbox
256
260
  //(vCGm7G_switch 逐字配方:36×20 轨道 border-l3 底、开=brand-primary、
@@ -303,6 +307,460 @@
303
307
  "@keyframes dsh-recall-pulse{0%,100%{opacity:1}50%{opacity:.45}}"
304
308
  ].join("");
305
309
 
310
+ // src/client/locales/zh.ts
311
+ var zh = {
312
+ // ---- 通用(跨模块复用的动作与连接词)----
313
+ "common.close": "\u5173\u95ED",
314
+ "common.cancel": "\u53D6\u6D88",
315
+ "common.confirm": "\u786E\u8BA4",
316
+ "common.save": "\u4FDD\u5B58",
317
+ "common.discard": "\u653E\u5F03\u4FEE\u6539",
318
+ "common.retry": "\u91CD\u8BD5",
319
+ "common.errorPrefix": "\u9519\u8BEF\uFF1A",
320
+ "common.unknownReason": "\u672A\u77E5\u539F\u56E0",
321
+ "common.opFailed": "\u64CD\u4F5C\u5931\u8D25",
322
+ // 列表连接符:中文顿号 / 英文逗号+空格(分隔符本身也是语言事实)
323
+ "common.listSep": "\u3001",
324
+ // 括号包裹模板:中英标点族不同((x) / (x),英文含前导空格),不写死在调用点
325
+ "common.paren": "\uFF08{text}\uFF09",
326
+ // ---- 变更类型(KIND_INFO 单表引用)----
327
+ "kind.modified": "\u4FEE\u6539",
328
+ "kind.restored": "\u6062\u590D",
329
+ "kind.added": "\u5220\u9664",
330
+ // ---- 用户消息内的文件卡片 ----
331
+ "file.unnamed": "\u672A\u547D\u540D\u6587\u4EF6",
332
+ // ---- 快照管理树的节点文案 ----
333
+ "tree.unknownWorkspace": "\u672A\u77E5\u5DE5\u4F5C\u533A",
334
+ "tree.deletedSession": "\uFF08\u5DF2\u5220\u9664\u4F1A\u8BDD\uFF09",
335
+ "tree.expand": "\u5C55\u5F00\uFF1A{label}",
336
+ "tree.collapse": "\u6536\u8D77\uFF1A{label}",
337
+ "tree.snapCount": "{n} \u6761",
338
+ "tree.wsCount": "{n} \u4F1A\u8BDD / {m} \u5FEB\u7167",
339
+ "tree.family.title": "\u7248\u672C\u5BB6\u65CF\uFF1A{chain}",
340
+ "tree.switch": "\u5207\u6362",
341
+ "tree.switch.title": "\u5207\u6362\u5230\u8BE5\u7248\u672C\u4F1A\u8BDD",
342
+ "tree.delete.snapshot.title": "\u5220\u9664\u8BE5\u5FEB\u7167\uFF08tag \u4E0E\u7D22\u5F15\u6761\u76EE\uFF09",
343
+ "tree.delete.snapshot.confirm": "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
344
+ "tree.delete.session.title": "\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167",
345
+ "tree.delete.session.confirm": "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
346
+ "tree.delete.workspace.title": "\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167",
347
+ "tree.delete.workspace.confirm": "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
348
+ // ---- 撤回确认面板 ----
349
+ "recall.loading": "\u6B63\u5728\u8BA1\u7B97\u53D8\u66F4\u2026",
350
+ "recall.error.title": "\u65E0\u6CD5\u56DE\u9000",
351
+ "recall.confirm.title": "\u6574\u6BB5\u56DE\u9000",
352
+ "recall.truncated.files": "\u2026\u4EC5\u663E\u793A\u524D {shown} \u6761\uFF0C\u5171 {total} \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4",
353
+ "recall.truncated.chat": "\u2026\u4EC5\u663E\u793A\u524D {shown} \u6761\uFF0C\u5171 {total} \u5904\u5DEE\u5F02",
354
+ "recall.confirm.sessionOnly": "\u9879\u76EE\u6587\u4EF6\u4FDD\u6301\u5F53\u524D\u72B6\u6001\uFF0C\u4E0D\u4F1A\u88AB\u56DE\u9000\u6216\u5220\u9664\uFF1B\u5BF9\u8BDD\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\u3002",
355
+ "recall.confirm.rollbackAt": "\u5C06\u9879\u76EE\u6062\u590D\u5230 {time} \u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002",
356
+ "recall.confirm.rollbackNoTime": "\u5C06\u9879\u76EE\u6062\u590D\u5230\u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002",
357
+ "recall.confirm.files": "\u5171 {total} \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4{summary}\u3002\u6B64\u64CD\u4F5C\u4F1A\u8986\u76D6\u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1B\u56DE\u9000\u524D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u4E00\u4EFD\u5F53\u524D\u72B6\u6001\u7684\u5B89\u5168\u5FEB\u7167\uFF08\u4E0D\u542B\u5728\u4E0B\u65B9\u6E05\u5355\u5185\uFF09\u3002",
358
+ "recall.confirm.chatBoth": "\u5BF9\u8BDD\u5C06\u4E00\u5E76\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\uFF1A\u8BE5\u6D88\u606F\u53CA\u4E4B\u540E\u7684\u5168\u90E8\u5BF9\u8BDD\u4F1A\u4ECE\u5F53\u524D\u89C6\u56FE\u79FB\u9664\uFF0C\u539F\u4F1A\u8BDD\u5F52\u6863\u4FDD\u5B58\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002",
359
+ "recall.confirm.chatFirst": "\u8BE5\u6D88\u606F\u662F\u672C\u4F1A\u8BDD\u4E2D\u7B2C\u4E00\u6761\u7528\u6237\u6D88\u606F\uFF0C\u65E0\u6CD5\u56DE\u9000\u5BF9\u8BDD\uFF1B\u786E\u8BA4\u540E\u4EC5\u56DE\u9000\u9879\u76EE\u6587\u4EF6\u3002",
360
+ "recall.confirm.refNote": "\u4EE5\u4E0B\u5DEE\u5F02\u4EC5\u4F5C\u53C2\u8003\uFF0C\u6240\u9009\u6A21\u5F0F\u4E0D\u4F1A\u6539\u52A8\u6587\u4EF6\u3002",
361
+ "recall.scope.aria": "\u64A4\u56DE\u8303\u56F4",
362
+ "recall.scope.both": "\u56DE\u9000\u6587\u4EF6\u4E0E\u5BF9\u8BDD",
363
+ "recall.scope.sessionOnly": "\u4EC5\u64A4\u56DE\u5BF9\u8BDD",
364
+ "recall.confirm.submit.both": "\u786E\u8BA4\u56DE\u9000",
365
+ "recall.confirm.submit.sessionOnly": "\u786E\u8BA4\u64A4\u56DE\u5BF9\u8BDD",
366
+ "recall.executing.both": "\u6B63\u5728\u56DE\u9000\u2026",
367
+ "recall.executing.sessionOnly": "\u6B63\u5728\u64A4\u56DE\u5BF9\u8BDD\u2026",
368
+ "recall.done.title": "\u56DE\u9000\u5B8C\u6210",
369
+ "recall.done.both.ok": "\u9879\u76EE\u6587\u4EF6\u4E0E\u5BF9\u8BDD\u5DF2\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\u3002\u65B0\u4F1A\u8BDD\u5DF2\u6253\u5F00\uFF0C\u539F\u4F1A\u8BDD\u5DF2\u5F52\u6863\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002",
370
+ "recall.done.both.partial": "\u9879\u76EE\u5DF2\u6062\u590D\u5230\u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002",
371
+ "recall.done.both.failChat": " \u5BF9\u8BDD\u56DE\u9000\u5931\u8D25\uFF1A{error}",
372
+ "recall.done.sessionOnly.ok": "\u5BF9\u8BDD\u5DF2\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\uFF0C\u9879\u76EE\u6587\u4EF6\u4FDD\u6301\u5F53\u524D\u72B6\u6001\u3002\u65B0\u4F1A\u8BDD\u5DF2\u6253\u5F00\uFF0C\u539F\u4F1A\u8BDD\u5DF2\u5F52\u6863\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002",
373
+ "recall.done.sessionOnly.fail": "\u5BF9\u8BDD\u56DE\u9000\u5931\u8D25\uFF1A{error}\u3002\u9879\u76EE\u6587\u4EF6\u672A\u505A\u4EFB\u4F55\u6539\u52A8\u3002",
374
+ "recall.chat.noChild": "\u672A\u8FD4\u56DE\u65B0\u4F1A\u8BDD",
375
+ // ---- 用户消息气泡的动作按钮 ----
376
+ "action.copy": "\u590D\u5236",
377
+ "action.copied": "\u5DF2\u590D\u5236",
378
+ "action.recall": "\u64A4\u56DE",
379
+ "action.recall.title": "\u6574\u6BB5\u56DE\u9000\uFF1A\u6587\u4EF6\u4E0E\u5BF9\u8BDD\u4E00\u5E76\u56DE\u5230\u8BE5\u6D88\u606F\u4E4B\u524D",
380
+ // ---- toast(降级提示 / 快照反馈)----
381
+ "toast.tag": "\u64A4\u56DE\u63D2\u4EF6",
382
+ "toast.skipped": "\u5FEB\u7167\u5DF2\u8DF3\u8FC7\u672A\u7EB3\u5165\u7684\u8DEF\u5F84\uFF1A{names}\uFF08\u64A4\u56DE\u4E0D\u4F1A\u6062\u590D\u6216\u5220\u9664\u8FD9\u4E9B\u8DEF\u5F84\uFF09",
383
+ "toast.skippedMore": " \u7B49 {n} \u9879",
384
+ "toast.snapshotFailed": "\u5FEB\u7167\u5931\u8D25\uFF1A{error}",
385
+ "toast.staleQueue": "\u64A4\u56DE\u524D\u7684\u4E00\u6761\u6392\u961F\u6D88\u606F\u672A\u88AB\u81EA\u52A8\u6E05\u7406\uFF0C\u53EF\u70B9\u51FB\u8BE5\u5361\u7247\u53F3\u4E0A\u89D2\u7684\u5220\u9664\u6309\u94AE\u624B\u52A8\u79FB\u9664",
386
+ "toast.fileAttachRefill": "\u88AB\u64A4\u56DE\u6D88\u606F\u91CC\u7684\u6587\u4EF6\u9644\u4EF6\u65E0\u6CD5\u81EA\u52A8\u56DE\u586B\uFF08\u5B98\u65B9\u63A5\u53E3\u53EA\u652F\u6301\u56FE\u7247\u56DE\u8BFB\uFF09\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u6587\u4EF6",
387
+ // ---- init / snapshot-info 下发的一次性说明 ----
388
+ "notice.unsupported": "\u64A4\u56DE\u63D2\u4EF6\u4EC5\u652F\u6301 Windows / Linux / macOS\uFF0C\u5F53\u524D\u5E73\u53F0\u7684\u5FEB\u7167\u4E0D\u53EF\u7528\u3002",
389
+ "notice.gitMissing": "\u672A\u68C0\u6D4B\u5230 git CLI\uFF0C\u64A4\u56DE\u529F\u80FD\u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09\u3002\u5B89\u88C5 git \u5E76\u91CD\u542F DSH \u540E\u5373\u53EF\u4F7F\u7528\u3002",
390
+ "notice.homeFallback": "home \u76EE\u5F55\u4E0D\u53EF\u5199\uFF0C\u5FEB\u7167\u5DF2\u964D\u7EA7\u5B58\u50A8\u5230\u9879\u76EE\u5185 .dsh-recall-snapshots \u76EE\u5F55\u3002",
391
+ "notice.buildRoot": "\u5F53\u524D\u5DE5\u4F5C\u533A\u4F4D\u4E8E\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\uFF08\u8DEF\u5F84\u6BB5 {seg}\uFF09\uFF0C\u5DF2\u8DF3\u8FC7\u9879\u76EE\u5FEB\u7167\uFF1B\u5982\u9700\u5728\u6B64\u76EE\u5F55\u4F7F\u7528\u64A4\u56DE\uFF0C\u8BF7\u5728\u63D2\u4EF6\u8BBE\u7F6E\u91CC\u4ECE\u300C\u57FA\u7840\u6392\u9664\u8868\u300D\u79FB\u9664\u8BE5\u9879\u3002",
392
+ // ---- 错误码文案(host 静态 message 的本地化副本;动态细节码不在表内)----
393
+ "err.STALE": "\u9884\u89C8\u540E\u9879\u76EE\u6587\u4EF6\u53D1\u751F\u4E86\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u786E\u8BA4",
394
+ "err.AGENT_BUSY": "Agent \u6B63\u5728\u8FD0\u884C\u4E2D\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u64A4\u56DE",
395
+ "err.NO_SNAPSHOT": "\u8BE5\u6D88\u606F\u6CA1\u6709\u53EF\u7528\u7684\u9879\u76EE\u5FEB\u7167",
396
+ "err.NO_STORE": "\u5FEB\u7167\u5B58\u50A8\u4E0D\u53EF\u7528",
397
+ "err.FORMAT_BLOCKED": "\u78C1\u76D8\u683C\u5F0F\u4E0D\u53D7\u652F\u6301\uFF0C\u5199\u5165\u5DF2\u6682\u505C\uFF08\u8BE6\u89C1\u6700\u8FD1\u9519\u8BEF\uFF09",
398
+ "err.UNKNOWN_PATH": "\u672A\u77E5\u7684\u6392\u9664\u6587\u4EF6\u8DEF\u5F84",
399
+ "err.EMPTY_PATCH": "\u6CA1\u6709\u53EF\u5199\u5165\u7684\u914D\u7F6E\u5B57\u6BB5",
400
+ "err.SETTINGS_UNAVAILABLE": "\u8BBE\u7F6E\u670D\u52A1\u4E0D\u53EF\u7528\uFF1A\u8BF7\u5728 profile \u7684 cordis.patch.yml \u6309 id: recall \u8986\u76D6\u914D\u7F6E",
401
+ "err.BODY_TOO_LARGE": "\u8BF7\u6C42\u4F53\u8D85\u8FC7\u5927\u5C0F\u4E0A\u9650",
402
+ "err.NO_ROOT": "\u65E0\u6CD5\u89E3\u6790\u5DE5\u4F5C\u533A",
403
+ "err.NO_SESSION": "\u7F3A\u5C11\u4F1A\u8BDD ID",
404
+ "err.UNKNOWN_OP": "\u672A\u77E5\u7684\u7BA1\u7406\u64CD\u4F5C",
405
+ "err.UNKNOWN_ENDPOINT": "\u672A\u77E5\u7684 API \u7AEF\u70B9",
406
+ "err.INDEX_CORRUPT": "\u5FEB\u7167\u7D22\u5F15\u635F\u574F",
407
+ // ---- 面板内的兜底文案(res 缺失时的最后一级回落)----
408
+ "fallback.preview": "\u65E0\u6CD5\u83B7\u53D6\u5FEB\u7167",
409
+ "fallback.rollback": "\u56DE\u9000\u5931\u8D25",
410
+ // ---- 设置页:快照管理的错误区(按 host kind 键控渲染)----
411
+ "errorKind.git": "\u672A\u68C0\u6D4B\u5230 git CLI \u6216\u7248\u672C\u8FC7\u65E7\uFF1A\u8BF7\u5B89\u88C5\u6216\u5347\u7EA7 git\uFF0C\u5B8C\u6210\u540E\u81EA\u52A8\u6062\u590D",
412
+ "errorKind.space": "\u78C1\u76D8\u7A7A\u95F4\u5DF2\u6EE1\uFF0C\u5FEB\u7167\u5199\u5165\u5931\u8D25\uFF1A\u6E05\u7406\u78C1\u76D8\u7A7A\u95F4\u540E\u81EA\u52A8\u6062\u590D",
413
+ "errorKind.permission": "\u5FEB\u7167\u76EE\u5F55\u65E0\u5199\u5165\u6743\u9650\uFF1A\u8BF7\u68C0\u67E5\u76EE\u5F55\u6743\u9650\u540E\u91CD\u8BD5",
414
+ "errorKind.lock": "\u7591\u4F3C\u591A\u4E2A DSH \u5B9E\u4F8B\u5E76\u53D1\u4F7F\u7528\u540C\u4E00\u5FEB\u7167\u5E93\uFF1A\u8BF7\u786E\u8BA4\u53EA\u542F\u52A8\u4E86\u4E00\u4E2A\uFF1B\u786E\u8BA4\u540E\u4ECD\u5931\u8D25\u65F6\uFF0C\u6309\u300C\u8BBE\u7F6E \xB7 \u63D2\u4EF6\u914D\u7F6E \xB7 \u6700\u8FD1\u9519\u8BEF\u300D\u4E2D\u7684\u8DEF\u5F84\u5220\u9664\u9501\u6587\u4EF6",
415
+ "errorKind.mkdir": "\u5FEB\u7167\u5B58\u50A8\u76EE\u5F55\u88AB\u540C\u540D\u6587\u4EF6\u5360\u7528\uFF1A\u5904\u7406\u540E\u81EA\u52A8\u6062\u590D",
416
+ // ---- 设置页:快照管理卡片 ----
417
+ "manage.busy": "\u6267\u884C\u4E2D\u2026",
418
+ "manage.deletedCount": "\u5DF2\u5220\u9664 {deleted} \u6761\u5FEB\u7167",
419
+ "manage.done.delete": "\u5DF2\u5220\u9664",
420
+ "manage.done.deleteAll": "\u5DF2\u6E05\u7A7A\u5168\u90E8\u5FEB\u7167",
421
+ "manage.done.gc": "gc \u5B8C\u6210",
422
+ "manage.confirm.all": "\u786E\u8BA4\u5220\u9664\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
423
+ "manage.countLoading": "\u5171 \u2026 \u6761\u5FEB\u7167",
424
+ "manage.countLoaded": "\u5171 {total} \u6761\u5FEB\u7167",
425
+ "manage.countShown": "\uFF08\u5F53\u524D\u663E\u793A\u6700\u65B0 {shown} \u6761\uFF09",
426
+ "manage.countEnd": "\u3002",
427
+ "manage.usageSuffix": "\uFF0C\u5168\u90E8\u5DE5\u4F5C\u533A\u5FEB\u7167\u5B58\u50A8\u5360\u7528 {size}\u3002",
428
+ "manage.health.gitTitle": "\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git",
429
+ "manage.health.gitOk": "git \u53EF\u7528",
430
+ "manage.health.gitBad": "git \u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09",
431
+ "manage.health.stores": " \xB7 \u5FEB\u7167\u5B58\u50A8\uFF1Ahome {n} \u4E2A\u5DE5\u4F5C\u533A",
432
+ "manage.health.storesFallback": "\uFF0C\u964D\u7EA7 {n} \u4E2A",
433
+ "manage.search.placeholder": "\u641C\u7D22\u5DE5\u4F5C\u533A / \u4F1A\u8BDD\u6807\u9898 / \u6D88\u606F\u5185\u5BB9 / ID",
434
+ "manage.search.aria": "\u641C\u7D22\u5FEB\u7167",
435
+ "manage.empty": "\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5FEB\u7167\u3002",
436
+ "manage.emptyFiltered": "\u65E0\u5339\u914D\u5FEB\u7167",
437
+ "manage.errors.title": "\u6700\u8FD1\u9519\u8BEF ({n})",
438
+ "manage.errors.collapse": "\u6536\u8D77",
439
+ "manage.errors.expand": "\u5C55\u5F00\u5168\u90E8 ({n})",
440
+ "manage.errors.clear": "\u6E05\u7A7A",
441
+ "manage.errors.dup": "\uFF08\xD7{n}\uFF09",
442
+ "manage.loadMore": "\u52A0\u8F7D\u66F4\u591A",
443
+ "manage.refresh": "\u5237\u65B0",
444
+ "manage.gc": "\u7ACB\u5373 gc",
445
+ "manage.gc.title": "\u7ACB\u5373\u5BF9\u5168\u90E8\u5DE5\u4F5C\u533A\u6267\u884C\u4E00\u6B21 git gc\uFF08\u538B\u7F29\u5BF9\u8C61\u5E93\u91CA\u653E\u7A7A\u95F4\uFF09",
446
+ "manage.deleteAll": "\u5168\u90E8\u5220\u9664",
447
+ "manage.deleteAll.title": "\u5220\u9664\u5168\u90E8\u5DE5\u4F5C\u533A\u7684\u6240\u6709\u5FEB\u7167\uFF1B\u4F1A\u76F4\u63A5\u6838\u5BF9\u5E76\u5220\u9664 git tag\uFF08\u5373\u4F7F\u5217\u8868\u4E3A\u7A7A\u4E5F\u53EF\u6E05\u7406\u6B8B\u7559\uFF09",
448
+ // ---- 设置页:插件配置表单 ----
449
+ "config.group.snapshot": "\u5FEB\u7167\u884C\u4E3A",
450
+ "config.group.auto": "\u81EA\u52A8\u6CBB\u7406",
451
+ "config.group.ui": "\u754C\u9762",
452
+ "config.snapshotEnabled.label": "\u542F\u7528\u5FEB\u7167",
453
+ "config.snapshotEnabled.hint": "\u5173\u95ED\u540E\u4E0D\u518D\u65B0\u5EFA\u5FEB\u7167\uFF1B\u5DF2\u6709\u5FEB\u7167\u4ECD\u53EF\u64A4\u56DE",
454
+ "config.refillDraft.label": "\u56DE\u586B\u8F93\u5165\u6846",
455
+ "config.refillDraft.hint": "\u64A4\u56DE\u540E\u628A\u6D88\u606F\u6587\u672C\u56DE\u586B\u8F93\u5165\u6846\uFF0C\u4FBF\u4E8E\u6539\u5B8C\u91CD\u53D1",
456
+ "config.archiveOriginal.label": "\u5F52\u6863\u539F\u4F1A\u8BDD",
457
+ "config.archiveOriginal.hint": "\u539F\u4F1A\u8BDD\u5F52\u6863\u9690\u85CF\uFF0C\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF1B\u5173\u95ED\u5219\u7559\u5728\u5217\u8868\u4FBF\u4E8E\u5BF9\u7167",
458
+ "config.gcSnaps.label": "gc \u89E6\u53D1\u6761\u6570",
459
+ "config.gcSnaps.hint": "\u6BCF\u79EF\u7D2F\u591A\u5C11\u6761\u5FEB\u7167\u89E6\u53D1\u4E00\u6B21 gc",
460
+ "config.gcSnaps.suffix": "\u6761",
461
+ "config.gcHours.label": "gc \u89E6\u53D1\u5C0F\u65F6",
462
+ "config.gcHours.hint": "\u8DDD\u4E0A\u6B21 gc \u8D85\u8FC7\u591A\u5C11\u5C0F\u65F6\u89E6\u53D1\uFF0C\u4E0E\u6761\u6570\u5148\u5230\u5148\u89E6\u53D1",
463
+ "config.gcHours.suffix": "\u5C0F\u65F6",
464
+ "config.maxFileBytes.label": "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650",
465
+ "config.maxFileBytes.hint": "\u8D85\u8FC7\u8BE5\u5927\u5C0F\u7684\u6587\u4EF6\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0",
466
+ "config.maxFileBytes.suffix": "MB",
467
+ "config.maxSnapshotsPerWorkspace.label": "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650",
468
+ "config.maxSnapshotsPerWorkspace.hint": "\u6BCF\u5DE5\u4F5C\u533A\u4FDD\u7559\u7684\u6700\u5927\u5FEB\u7167\u6570\uFF0C\u8D85\u9650\u5220\u9664\u6700\u65E7\u7684\uFF1B0 \u8868\u793A\u4E0D\u9650\u5236",
469
+ "config.maxSnapshotsPerWorkspace.suffix": "\u6761",
470
+ "config.retentionDays.label": "\u5FEB\u7167\u4FDD\u7559\u5929\u6570",
471
+ "config.retentionDays.hint": "\u8D85\u671F\u5FEB\u7167\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B0 \u8868\u793A\u4E0D\u542F\u7528",
472
+ "config.retentionDays.suffix": "\u5929",
473
+ "config.locale.label": "\u754C\u9762\u8BED\u8A00",
474
+ "config.locale.hint": "auto \u8DDF\u968F\u7CFB\u7EDF\u8BED\u8A00\uFF1B\u4FDD\u5B58\u540E\u751F\u6548\uFF08\u5BBF\u4E3B\u4FA7\u300C\u63D2\u4EF6\u914D\u7F6E\u300D\u81EA\u5E26\u7684\u5B57\u6BB5\u8BF4\u660E\u4ECD\u4E3A\u4E2D\u6587\uFF09",
475
+ "config.locale.auto": "\u8DDF\u968F\u7CFB\u7EDF (auto)",
476
+ "config.tag.modified": "\u5DF2\u4FEE\u6539",
477
+ "config.tag.overridden": "\u5DF2\u8986\u76D6",
478
+ "config.tag.locked": "\u73AF\u5883\u53D8\u91CF\u9501\u5B9A",
479
+ "config.tag.readonly": "\u53EA\u8BFB\u8BBE\u7F6E\u6E90",
480
+ "config.reset": "\u6062\u590D\u9ED8\u8BA4",
481
+ "config.reset.title": "\u628A\u6240\u6709\u5B57\u6BB5\u6062\u590D\u5230\u63D2\u4EF6\u51FA\u5382\u9ED8\u8BA4\u503C",
482
+ "config.advanced.title": "\u9AD8\u7EA7\uFF1A\u57FA\u7840\u6392\u9664\u8868",
483
+ "config.baseExcludes.label": "\u57FA\u7840\u6392\u9664\u8868",
484
+ "config.baseExcludes.hint": "\u5404\u5DE5\u4F5C\u533A\u5171\u4EAB\u7684\u5185\u7F6E\u89C4\u5219\uFF1Bgitignore \u8BED\u6CD5\uFF0C\u6BCF\u884C\u4E00\u6761\uFF1B\u4F18\u5148\u7EA7\u4F4E\u4E8E\u300C\u6392\u9664\u914D\u7F6E\u300D\u7684 exclude.txt",
485
+ "config.msg.loading": "\u6B63\u5728\u8BFB\u53D6\u914D\u7F6E\u2026",
486
+ "config.msg.loadFailed": "\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E",
487
+ "config.msg.noChange": "\u6CA1\u6709\u4FEE\u6539",
488
+ "config.msg.saving": "\u4FDD\u5B58\u4E2D\u2026",
489
+ "config.msg.saved": "\u5DF2\u4FDD\u5B58\u5E76\u5373\u65F6\u751F\u6548",
490
+ "config.msg.saveFailed": "\u4FDD\u5B58\u5931\u8D25",
491
+ "config.msg.resetting": "\u6062\u590D\u9ED8\u8BA4\u4E2D\u2026",
492
+ "config.msg.resetDone": "\u5DF2\u6062\u590D\u9ED8\u8BA4\u503C",
493
+ "config.msg.resetFailed": "\u6062\u590D\u9ED8\u8BA4\u5931\u8D25",
494
+ "config.err.gcSnaps": "\u5FEB\u7167\u6761\u6570\u9608\u503C\u5FC5\u987B\u662F >= 1 \u7684\u6574\u6570",
495
+ "config.err.gcHours": "gc \u5C0F\u65F6\u9608\u503C\u5FC5\u987B\u662F >= 1 \u7684\u6574\u6570",
496
+ "config.err.maxFileBytes": "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650\u81F3\u5C11 0.01 MB",
497
+ "config.err.maxSnapshots": "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650\u5FC5\u987B\u662F >= 0 \u7684\u6574\u6570\uFF080 \u8868\u793A\u4E0D\u9650\u5236\uFF09",
498
+ "config.err.retentionDays": "\u4FDD\u7559\u5929\u6570\u5FC5\u987B\u662F >= 0 \u7684\u6574\u6570\uFF080 \u8868\u793A\u4E0D\u542F\u7528\uFF09",
499
+ "config.err.locale": "\u754C\u9762\u8BED\u8A00\u5FC5\u987B\u662F auto / zh / en \u4E4B\u4E00",
500
+ // ---- 设置页:排除配置卡片 ----
501
+ "exclude.note.home": "\u6B64\u914D\u7F6E\u5168\u5C40\u5171\u4EAB\uFF0C\u5BF9\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5FEB\u7167\u751F\u6548\u3002",
502
+ "exclude.note.fallback": "home \u76EE\u5F55\u4E0D\u53EF\u5199\u65F6\u6B64\u5DE5\u4F5C\u533A\u964D\u7EA7\u5B58\u50A8\uFF0C\u6392\u9664\u914D\u7F6E\u72EC\u7ACB\u751F\u6548\u3002",
503
+ "exclude.path": "\u5B58\u50A8\u4F4D\u7F6E\uFF1A{path}",
504
+ "exclude.syntax": "gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF0C\u652F\u6301 # \u6CE8\u91CA\uFF1B\u547D\u4E2D\u9879\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0\u3002",
505
+ "exclude.area.aria": "\u5FEB\u7167\u6392\u9664\u6A21\u5F0F\u5217\u8868\uFF08gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF09",
506
+ "exclude.quick.placeholder": "\u8F93\u5165\u8DEF\u5F84\u6216\u6A21\u5F0F\uFF0C\u56DE\u8F66\u5FEB\u901F\u6DFB\u52A0",
507
+ "exclude.quick.aria": "\u5FEB\u901F\u6DFB\u52A0\u6392\u9664\u6A21\u5F0F",
508
+ "exclude.add": "\u6DFB\u52A0",
509
+ "exclude.chip.title": "\u70B9\u51FB\u8FFD\u52A0 {pattern}",
510
+ "exclude.msg.saving": "\u4FDD\u5B58\u4E2D\u2026",
511
+ "exclude.msg.saved": "\u5DF2\u4FDD\u5B58\uFF0C\u4E0B\u4E00\u6B21\u5FEB\u7167 / \u9884\u89C8 / \u56DE\u9000\u65F6\u751F\u6548",
512
+ "exclude.msg.saveFailed": "\u4FDD\u5B58\u5931\u8D25",
513
+ "exclude.loadFailed": "\u65E0\u6CD5\u8BFB\u53D6\u6392\u9664\u914D\u7F6E",
514
+ "exclude.unsupported": "\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u5FEB\u7167\u529F\u80FD\uFF0C\u6392\u9664\u914D\u7F6E\u4E0D\u53EF\u7528\u3002",
515
+ "exclude.loading": "\u6B63\u5728\u52A0\u8F7D\u6392\u9664\u914D\u7F6E\u2026",
516
+ "exclude.empty": "\u5C1A\u672A\u521B\u5EFA\u4EFB\u4F55\u5FEB\u7167\u5B58\u50A8\uFF1A\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u53EF\u7F16\u8F91\u7684\u6392\u9664\u914D\u7F6E\u3002",
517
+ // ---- 设置页:分区折叠头 ----
518
+ "section.exclude": "\u6392\u9664\u914D\u7F6E\uFF08exclude.txt\uFF09",
519
+ "section.manage": "\u5FEB\u7167\u7BA1\u7406"
520
+ };
521
+
522
+ // src/client/locales/en.ts
523
+ var en = {
524
+ // ---- 通用 ----
525
+ "common.close": "Close",
526
+ "common.cancel": "Cancel",
527
+ "common.confirm": "Confirm",
528
+ "common.save": "Save",
529
+ "common.discard": "Discard changes",
530
+ "common.retry": "Retry",
531
+ "common.errorPrefix": "Error: ",
532
+ "common.unknownReason": "unknown reason",
533
+ "common.opFailed": "Operation failed",
534
+ "common.listSep": ", ",
535
+ "common.paren": " ({text})",
536
+ // ---- 变更类型 ----
537
+ "kind.modified": "modified",
538
+ "kind.restored": "restored",
539
+ "kind.added": "deleted",
540
+ // ---- 用户消息内的文件卡片 ----
541
+ "file.unnamed": "unnamed file",
542
+ // ---- 快照管理树的节点文案 ----
543
+ "tree.unknownWorkspace": "Unknown workspace",
544
+ "tree.deletedSession": "(deleted session)",
545
+ "tree.expand": "Expand: {label}",
546
+ "tree.collapse": "Collapse: {label}",
547
+ "tree.snapCount": "{n} snapshots",
548
+ "tree.wsCount": "{n} sessions / {m} snapshots",
549
+ "tree.family.title": "Version family: {chain}",
550
+ "tree.switch": "Switch",
551
+ "tree.switch.title": "Switch to this version session",
552
+ "tree.delete.snapshot.title": "Delete this snapshot (tag and index entry)",
553
+ "tree.delete.snapshot.confirm": "Delete this snapshot? This cannot be undone.",
554
+ "tree.delete.session.title": "Delete all snapshots of this session",
555
+ "tree.delete.session.confirm": "Delete all snapshots of this session? This cannot be undone.",
556
+ "tree.delete.workspace.title": "Delete all snapshots of this workspace",
557
+ "tree.delete.workspace.confirm": "Delete all snapshots of this workspace? This cannot be undone.",
558
+ // ---- 撤回确认面板 ----
559
+ "recall.loading": "Computing changes\u2026",
560
+ "recall.error.title": "Cannot roll back",
561
+ "recall.confirm.title": "Roll back",
562
+ "recall.truncated.files": "\u2026showing the first {shown} of {total} files to change",
563
+ "recall.truncated.chat": "\u2026showing the first {shown} of {total} differences",
564
+ "recall.confirm.sessionOnly": "Project files stay as they are \u2014 nothing is reverted or deleted; only the chat rewinds to before this message.",
565
+ "recall.confirm.rollbackAt": "Restore the project to its state at {time}, when this message was sent.",
566
+ "recall.confirm.rollbackNoTime": "Restore the project to its state when this message was sent.",
567
+ // 句首空格是有意的:本句与 recall.confirm.rollbackAt 拼接成一段(前句以句点
568
+ // 收尾),英文标点族需要空格(与 common.paren / tellChat 同类;中文版不需要)
569
+ "recall.confirm.files": " {total} files will change{summary}. This overwrites current file contents; a safety snapshot of the current state is saved automatically before the rollback (not listed below).",
570
+ "recall.confirm.chatBoth": "The chat also rewinds to before this message: this message and everything after it leaves the current view, and the original session is archived (recoverable from Archive).",
571
+ "recall.confirm.chatFirst": "This is the first user message in this session, so the chat cannot rewind; only project files will be restored.",
572
+ "recall.confirm.refNote": "The differences below are for reference only; the selected mode will not touch any file.",
573
+ "recall.scope.aria": "Recall scope",
574
+ "recall.scope.both": "Files and chat",
575
+ "recall.scope.sessionOnly": "Chat only",
576
+ "recall.confirm.submit.both": "Roll back",
577
+ "recall.confirm.submit.sessionOnly": "Rewind chat",
578
+ "recall.executing.both": "Rolling back\u2026",
579
+ "recall.executing.sessionOnly": "Rewinding chat\u2026",
580
+ "recall.done.title": "Rollback complete",
581
+ "recall.done.both.ok": "Project files and the chat have been rewound to before this message. A new session is open and the original one is archived (recoverable from Archive).",
582
+ "recall.done.both.partial": "The project has been restored to its state when this message was sent.",
583
+ "recall.done.both.failChat": " Chat rewind failed: {error}",
584
+ "recall.done.sessionOnly.ok": "The chat has been rewound to before this message; project files stay as they are. A new session is open and the original one is archived (recoverable from Archive).",
585
+ "recall.done.sessionOnly.fail": "Chat rewind failed: {error}. No project files were touched.",
586
+ "recall.chat.noChild": "no new session returned",
587
+ // ---- 用户消息气泡的动作按钮 ----
588
+ "action.copy": "Copy",
589
+ "action.copied": "Copied",
590
+ "action.recall": "Recall",
591
+ "action.recall.title": "Roll back: files and chat return to before this message",
592
+ // ---- toast ----
593
+ "toast.tag": "Recall plugin",
594
+ "toast.skipped": "Snapshot skipped unlisted paths: {names} (rollback will not restore or delete them)",
595
+ "toast.skippedMore": " and {n} more",
596
+ "toast.snapshotFailed": "Snapshot failed: {error}",
597
+ "toast.staleQueue": "A queued message from before the recall could not be cleaned up automatically; remove it with the delete button on that card",
598
+ "toast.fileAttachRefill": "File attachments from the recalled message cannot be refilled automatically (the official API only reads images back); please re-select the files",
599
+ // ---- init / snapshot-info 说明 ----
600
+ "notice.unsupported": "The recall plugin supports Windows / Linux / macOS only; snapshots are unavailable on this platform.",
601
+ "notice.gitMissing": "git CLI not found, so recall is unavailable (the snapshot engine depends on git). Install git and restart DSH to enable it.",
602
+ "notice.homeFallback": "The home directory is not writable; snapshots fall back to .dsh-recall-snapshots inside the project.",
603
+ "notice.buildRoot": 'This workspace is a build-artifact directory (path segment {seg}), so project snapshots are skipped; to use recall here, remove that entry from "Base excludes" in the plugin settings.',
604
+ // ---- 错误码文案 ----
605
+ "err.STALE": "Project files changed after the preview; preview again to confirm",
606
+ "err.AGENT_BUSY": "The agent is running; stop it before recalling",
607
+ "err.NO_SNAPSHOT": "No project snapshot is available for this message",
608
+ "err.NO_STORE": "Snapshot storage is unavailable",
609
+ "err.FORMAT_BLOCKED": "Unsupported disk format; writes are paused (see Recent errors)",
610
+ "err.UNKNOWN_PATH": "Unknown exclude file path",
611
+ "err.EMPTY_PATCH": "No config field to write",
612
+ "err.SETTINGS_UNAVAILABLE": "Settings service unavailable: override the config by id: recall in the profile cordis.patch.yml",
613
+ "err.BODY_TOO_LARGE": "Request body exceeds the size limit",
614
+ "err.NO_ROOT": "Cannot resolve the workspace",
615
+ "err.NO_SESSION": "Missing session ID",
616
+ "err.UNKNOWN_OP": "Unknown manage operation",
617
+ "err.UNKNOWN_ENDPOINT": "Unknown API endpoint",
618
+ "err.INDEX_CORRUPT": "Snapshot index is corrupted",
619
+ // ---- 面板兜底 ----
620
+ "fallback.preview": "Failed to load the snapshot",
621
+ "fallback.rollback": "Rollback failed",
622
+ // ---- 设置页:最近错误(按 host kind 键控)----
623
+ "errorKind.git": "git CLI not found or too old: install or upgrade git; snapshots resume automatically afterwards",
624
+ "errorKind.space": "Disk is full, snapshot writes failed: free up disk space to resume automatically",
625
+ "errorKind.permission": "No write permission on the snapshot directory: check directory permissions and retry",
626
+ "errorKind.lock": "Possibly multiple DSH instances sharing one snapshot store: make sure only one is running; if it still fails, delete the lock file at the path shown in Settings \xB7 Plugin config \xB7 Recent errors",
627
+ "errorKind.mkdir": "The snapshot directory is occupied by a file of the same name: resolve it to resume automatically",
628
+ // ---- 设置页:快照管理卡片 ----
629
+ "manage.busy": "Working\u2026",
630
+ "manage.deletedCount": "Deleted {deleted} snapshots",
631
+ "manage.done.delete": "Deleted",
632
+ "manage.done.deleteAll": "All snapshots cleared",
633
+ "manage.done.gc": "gc finished",
634
+ "manage.confirm.all": "Delete all snapshots in every workspace? This cannot be undone.",
635
+ "manage.countLoading": "Loading snapshot count\u2026",
636
+ "manage.countLoaded": "{total} snapshots in total",
637
+ "manage.countShown": " (showing the newest {shown})",
638
+ "manage.countEnd": ".",
639
+ "manage.usageSuffix": ", using {size} across all workspaces.",
640
+ "manage.health.gitTitle": "The snapshot engine depends on git",
641
+ "manage.health.gitOk": "git available",
642
+ "manage.health.gitBad": "git unavailable (the snapshot engine depends on git)",
643
+ "manage.health.stores": " \xB7 Snapshot storage: {n} workspaces in home",
644
+ "manage.health.storesFallback": ", {n} in fallback",
645
+ "manage.search.placeholder": "Search workspace / session title / message / ID",
646
+ "manage.search.aria": "Search snapshots",
647
+ "manage.empty": "Send a message in any workspace and its snapshot will appear here.",
648
+ "manage.emptyFiltered": "No matching snapshots",
649
+ "manage.errors.title": "Recent errors ({n})",
650
+ "manage.errors.collapse": "Collapse",
651
+ "manage.errors.expand": "Show all ({n})",
652
+ "manage.errors.clear": "Clear",
653
+ "manage.errors.dup": " (\xD7{n})",
654
+ "manage.loadMore": "Load more",
655
+ "manage.refresh": "Refresh",
656
+ "manage.gc": "Run gc",
657
+ "manage.gc.title": "Run git gc once for every workspace (compacts the object store to free space)",
658
+ "manage.deleteAll": "Delete all",
659
+ "manage.deleteAll.title": "Delete all snapshots in every workspace; git tags are checked and removed directly (leftovers can be cleaned even when the list is empty)",
660
+ // ---- 设置页:插件配置表单 ----
661
+ "config.group.snapshot": "Snapshot behavior",
662
+ "config.group.auto": "Automatic maintenance",
663
+ "config.group.ui": "Interface",
664
+ "config.snapshotEnabled.label": "Enable snapshots",
665
+ "config.snapshotEnabled.hint": "When off, no new snapshots are created; existing ones can still be recalled",
666
+ "config.refillDraft.label": "Refill input box",
667
+ "config.refillDraft.hint": "Put the message text back into the input box after a recall, so you can edit and resend",
668
+ "config.archiveOriginal.label": "Archive original session",
669
+ "config.archiveOriginal.hint": "Archive and hide the original session (recoverable from Archive); when off it stays in the list for comparison",
670
+ "config.gcSnaps.label": "gc trigger count",
671
+ "config.gcSnaps.hint": "Run gc after this many snapshots accumulate",
672
+ "config.gcSnaps.suffix": "snapshots",
673
+ "config.gcHours.label": "gc trigger hours",
674
+ "config.gcHours.hint": "Run gc when this many hours passed since the last one; whichever comes first",
675
+ "config.gcHours.suffix": "hours",
676
+ "config.maxFileBytes.label": "File size limit",
677
+ "config.maxFileBytes.hint": "Files larger than this are not snapshotted and never touched by rollback",
678
+ "config.maxFileBytes.suffix": "MB",
679
+ "config.maxSnapshotsPerWorkspace.label": "Snapshot count limit",
680
+ "config.maxSnapshotsPerWorkspace.hint": "Maximum snapshots kept per workspace; the oldest are deleted beyond it; 0 means unlimited",
681
+ "config.maxSnapshotsPerWorkspace.suffix": "snapshots",
682
+ "config.retentionDays.label": "Snapshot retention days",
683
+ "config.retentionDays.hint": "Delete snapshots older than this; the oldest first; 0 disables it",
684
+ "config.retentionDays.suffix": "days",
685
+ "config.locale.label": "Language",
686
+ "config.locale.hint": "auto follows the system language; takes effect after saving (field descriptions rendered by the host-side plugin config form stay in Chinese)",
687
+ "config.locale.auto": "Follow system (auto)",
688
+ "config.tag.modified": "Modified",
689
+ "config.tag.overridden": "Overridden",
690
+ "config.tag.locked": "Locked by env var",
691
+ "config.tag.readonly": "Read-only settings source",
692
+ "config.reset": "Restore defaults",
693
+ "config.reset.title": "Restore every field to the plugin factory defaults",
694
+ "config.advanced.title": "Advanced: base excludes",
695
+ "config.baseExcludes.label": "Base excludes",
696
+ "config.baseExcludes.hint": 'Built-in rules shared by all workspaces; gitignore syntax, one per line; lower priority than exclude.txt in "Exclude config"',
697
+ "config.msg.loading": "Loading configuration\u2026",
698
+ "config.msg.loadFailed": "Failed to load configuration",
699
+ "config.msg.noChange": "No changes",
700
+ "config.msg.saving": "Saving\u2026",
701
+ "config.msg.saved": "Saved and applied immediately",
702
+ "config.msg.saveFailed": "Failed to save",
703
+ "config.msg.resetting": "Restoring defaults\u2026",
704
+ "config.msg.resetDone": "Defaults restored",
705
+ "config.msg.resetFailed": "Failed to restore defaults",
706
+ "config.err.gcSnaps": "The snapshot count threshold must be an integer >= 1",
707
+ "config.err.gcHours": "The gc hour threshold must be an integer >= 1",
708
+ "config.err.maxFileBytes": "The file size limit must be at least 0.01 MB",
709
+ "config.err.maxSnapshots": "The snapshot count limit must be an integer >= 0 (0 means unlimited)",
710
+ "config.err.retentionDays": "Retention days must be an integer >= 0 (0 disables it)",
711
+ "config.err.locale": "Language must be one of auto / zh / en",
712
+ // ---- 设置页:排除配置卡片 ----
713
+ "exclude.note.home": "This configuration is shared globally and applies to snapshots in every workspace.",
714
+ "exclude.note.fallback": "This workspace stores snapshots in the fallback location (home is not writable), so its exclude config is separate.",
715
+ "exclude.path": "Location: {path}",
716
+ "exclude.syntax": "gitignore syntax, one per line, # comments supported; matched paths are not snapshotted and never touched by rollback.",
717
+ "exclude.area.aria": "Snapshot exclude patterns (gitignore syntax, one per line)",
718
+ "exclude.quick.placeholder": "Type a path or pattern, press Enter to add",
719
+ "exclude.quick.aria": "Quickly add an exclude pattern",
720
+ "exclude.add": "Add",
721
+ "exclude.chip.title": "Click to append {pattern}",
722
+ "exclude.msg.saving": "Saving\u2026",
723
+ "exclude.msg.saved": "Saved; takes effect on the next snapshot / preview / rollback",
724
+ "exclude.msg.saveFailed": "Failed to save",
725
+ "exclude.loadFailed": "Failed to load the exclude configuration",
726
+ "exclude.unsupported": "This platform does not support snapshots, so exclude configuration is unavailable.",
727
+ "exclude.loading": "Loading exclude configuration\u2026",
728
+ "exclude.empty": "No snapshot store exists yet: send a message in any workspace and its editable exclude configuration will appear here.",
729
+ // ---- 设置页:分区折叠头 ----
730
+ "section.exclude": "Exclude config (exclude.txt)",
731
+ "section.manage": "Snapshot manager"
732
+ };
733
+
734
+ // src/client/locales/index.ts
735
+ var DICTS = { zh, en };
736
+ function resolveLocale(pref) {
737
+ if (pref === "zh" || pref === "en") return pref;
738
+ try {
739
+ const nav = typeof navigator === "undefined" ? null : navigator;
740
+ const lang = nav && typeof nav.language === "string" ? nav.language : "";
741
+ if (!lang) return "zh";
742
+ return lang.toLowerCase().indexOf("zh") === 0 ? "zh" : "en";
743
+ } catch (error) {
744
+ return "zh";
745
+ }
746
+ }
747
+ function interpolate(template, params) {
748
+ if (!params) return template;
749
+ return template.replace(/\{(\w+)\}/g, (raw, name) => {
750
+ const value = params[name];
751
+ return value === void 0 ? raw : String(value);
752
+ });
753
+ }
754
+ function translate(locale, key, params) {
755
+ const dict = DICTS[locale];
756
+ const hit = dict[key] !== void 0 ? dict[key] : zh[key];
757
+ return interpolate(hit !== void 0 ? hit : key, params);
758
+ }
759
+ var zhTranslate = (key, params) => translate("zh", key, params);
760
+ function hasTranslation(key) {
761
+ return zh[key] !== void 0;
762
+ }
763
+
306
764
  // src/client/util.ts
307
765
  function clockText(ms) {
308
766
  try {
@@ -329,15 +787,19 @@
329
787
  if (!Number.isFinite(n) || n <= 0) return "";
330
788
  return String(Math.round(n / 1048576 * 100) / 100);
331
789
  }
332
- function buildTree(list) {
790
+ function buildTree(list, t = zhTranslate) {
333
791
  const workspaces = /* @__PURE__ */ new Map();
334
792
  for (const it of list || []) {
335
793
  const rootKey = it.root || "unknown-root";
336
- if (!workspaces.has(rootKey)) workspaces.set(rootKey, { root: it.root || null, name: it.workspace || "\u672A\u77E5\u5DE5\u4F5C\u533A", sessions: /* @__PURE__ */ new Map() });
337
- const ws = workspaces.get(rootKey);
794
+ let ws = workspaces.get(rootKey);
795
+ if (!ws) {
796
+ ws = { root: it.root || null, name: it.workspace || t("tree.unknownWorkspace"), sessions: /* @__PURE__ */ new Map() };
797
+ workspaces.set(rootKey, ws);
798
+ }
338
799
  const sidKey = it.sessionId || "unknown-session";
339
800
  if (!ws.sessions.has(sidKey)) ws.sessions.set(sidKey, { root: ws.root, sessionId: it.sessionId || null, title: it.sessionTitle || null, items: [] });
340
- ws.sessions.get(sidKey).items.push(it);
801
+ const session = ws.sessions.get(sidKey);
802
+ if (session) session.items.push(it);
341
803
  }
342
804
  const wsList = Array.from(workspaces.values()).map((ws) => ({ root: ws.root, name: ws.name, sessions: Array.from(ws.sessions.values()) }));
343
805
  wsList.sort((a, b) => (a.name || "").localeCompare(b.name || ""));
@@ -358,6 +820,33 @@
358
820
  return "/api/recall/" + name;
359
821
  }
360
822
  }
823
+ function buildRecallNotify(base, o) {
824
+ if (o.outcome === "complete") {
825
+ return {
826
+ status: "complete",
827
+ sessionId: base.sessionId,
828
+ messageId: base.messageId,
829
+ scope: base.scope,
830
+ cutSeq: o.cutSeq,
831
+ childSessionId: o.childSessionId,
832
+ count: o.count,
833
+ chatReverted: o.chatReverted,
834
+ archiveRequested: o.archiveRequested
835
+ };
836
+ }
837
+ const stage = o.outcome === "fork-failed" ? "fork" : "execute";
838
+ const code = o.outcome === "execute-rejected" && typeof o.code === "string" && o.code ? o.code : void 0;
839
+ return {
840
+ status: "failed",
841
+ stage,
842
+ sessionId: base.sessionId,
843
+ messageId: base.messageId,
844
+ scope: base.scope,
845
+ cutSeq: o.cutSeq,
846
+ ...code ? { code } : {},
847
+ error: typeof o.error === "string" && o.error ? o.error : String(o.error || "recall failed")
848
+ };
849
+ }
361
850
  function useAutoDismissMessage(React, state, setState) {
362
851
  React.useEffect(() => {
363
852
  if (!state.message || state.error || state.busy) return;
@@ -368,6 +857,13 @@
368
857
  }, [state.message, state.error, state.busy]);
369
858
  }
370
859
  function buildUtil() {
860
+ let locale = resolveLocale("auto");
861
+ function t(key, params) {
862
+ return translate(locale, key, params);
863
+ }
864
+ function setLocalePref(pref) {
865
+ locale = resolveLocale(pref);
866
+ }
371
867
  function api(name, args) {
372
868
  return fetch(recallApiUrl(name), {
373
869
  method: "POST",
@@ -375,17 +871,11 @@
375
871
  body: JSON.stringify(args || {})
376
872
  }).then((r) => r.json());
377
873
  }
378
- const CODE_TEXT = {
379
- STALE: "\u9884\u89C8\u540E\u9879\u76EE\u6587\u4EF6\u53D1\u751F\u4E86\u53D8\u5316\uFF0C\u8BF7\u91CD\u65B0\u9884\u89C8\u786E\u8BA4",
380
- AGENT_BUSY: "Agent \u6B63\u5728\u8FD0\u884C\u4E2D\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u64A4\u56DE",
381
- NO_SNAPSHOT: "\u8BE5\u6D88\u606F\u6CA1\u6709\u53EF\u7528\u7684\u9879\u76EE\u5FEB\u7167",
382
- NO_STORE: "\u5FEB\u7167\u5B58\u50A8\u4E0D\u53EF\u7528",
383
- ROLLBACK_FAILED: "\u56DE\u9000\u5931\u8D25"
384
- };
385
874
  function messageFor(res, fallback) {
386
875
  if (!res) return fallback;
387
876
  const code = res.code;
388
- if (code && CODE_TEXT[String(code)]) return CODE_TEXT[String(code)];
877
+ const key = code ? "err." + String(code) : "";
878
+ if (key && hasTranslation(key)) return t(key);
389
879
  const m = res.message;
390
880
  const e = res.error;
391
881
  return String(m || e || fallback);
@@ -406,7 +896,7 @@
406
896
  el.className = "dsh-recall-toast";
407
897
  const tag = document.createElement("span");
408
898
  tag.className = "dsh-recall-toast-tag";
409
- tag.textContent = "\u64A4\u56DE\u63D2\u4EF6";
899
+ tag.textContent = t("toast.tag");
410
900
  const body = document.createElement("span");
411
901
  body.textContent = text;
412
902
  el.appendChild(tag);
@@ -444,18 +934,21 @@
444
934
  const cfg = res.config;
445
935
  if (typeof cfg.refillDraft === "boolean") pluginConfig.refillDraft = cfg.refillDraft;
446
936
  if (typeof cfg.archiveOriginal === "boolean") pluginConfig.archiveOriginal = cfg.archiveOriginal;
937
+ setLocalePref(cfg.locale);
447
938
  }
448
939
  const notice = res && res.notice;
449
940
  if (notice && notice.unsupported) {
450
- showNotice("unsupported", "\u64A4\u56DE\u63D2\u4EF6\u4EC5\u652F\u6301 Windows / Linux / macOS\uFF0C\u5F53\u524D\u5E73\u53F0\u7684\u5FEB\u7167\u4E0D\u53EF\u7528\u3002");
941
+ showNotice("unsupported", t("notice.unsupported"));
451
942
  }
452
943
  if (notice && notice.gitMissing) {
453
- showNotice("git", "\u672A\u68C0\u6D4B\u5230 git CLI\uFF0C\u64A4\u56DE\u529F\u80FD\u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09\u3002\u5B89\u88C5 git \u5E76\u91CD\u542F DSH \u540E\u5373\u53EF\u4F7F\u7528\u3002");
944
+ showNotice("git", t("notice.gitMissing"));
454
945
  }
455
946
  if (notice && notice.homeFallback) {
456
- showNotice("home", "home \u76EE\u5F55\u4E0D\u53EF\u5199\uFF0C\u5FEB\u7167\u5DF2\u964D\u7EA7\u5B58\u50A8\u5230\u9879\u76EE\u5185 .dsh-recall-snapshots \u76EE\u5F55\u3002");
947
+ showNotice("home", t("notice.homeFallback"));
457
948
  }
458
- if (notice && notice.buildRootNotice) {
949
+ if (notice && typeof notice.buildRootArtifactSeg === "string" && notice.buildRootArtifactSeg) {
950
+ showNotice("buildRoot", t("notice.buildRoot", { seg: notice.buildRootArtifactSeg }));
951
+ } else if (notice && notice.buildRootNotice) {
459
952
  showNotice("buildRoot", String(notice.buildRootNotice).slice(0, 140));
460
953
  }
461
954
  }).catch(() => {
@@ -490,9 +983,46 @@
490
983
  }
491
984
  return Promise.resolve(false);
492
985
  }
493
- return { api, messageFor, showNotice, showThrottledToast, ensureInit, clockText, writeClipboard, sizeText, bytesToMb, buildTree, pluginConfig };
986
+ return { api, messageFor, showNotice, showThrottledToast, ensureInit, clockText, writeClipboard, sizeText, bytesToMb, buildTree, t, setLocalePref, pluginConfig };
494
987
  }
495
988
 
989
+ // src/client/log.ts
990
+ function debugFlag() {
991
+ try {
992
+ return String(localStorage.getItem("dsh-recall.debug") || "");
993
+ } catch (error) {
994
+ return "";
995
+ }
996
+ }
997
+ function debugEnabled(ns) {
998
+ const raw = debugFlag();
999
+ if (!raw) return false;
1000
+ for (const part of raw.split(",")) {
1001
+ const t = part.trim();
1002
+ if (t === "*" || t === ns) return true;
1003
+ }
1004
+ return false;
1005
+ }
1006
+ function createLogger(ns) {
1007
+ const prefix = "[dsh-recall:" + ns + "]";
1008
+ return {
1009
+ error: (...args) => {
1010
+ console.error(prefix, ...args);
1011
+ },
1012
+ warn: (...args) => {
1013
+ console.warn(prefix, ...args);
1014
+ },
1015
+ info: (...args) => {
1016
+ if (debugEnabled(ns)) console.info(prefix, ...args);
1017
+ },
1018
+ debug: (...args) => {
1019
+ if (debugEnabled(ns)) console.debug(prefix, ...args);
1020
+ }
1021
+ };
1022
+ }
1023
+ var appLog = createLogger("app");
1024
+ var recallNodeLog = createLogger("recall-node");
1025
+
496
1026
  // src/client/recall-node.ts
497
1027
  function attachmentRefsFromBlocks(blocks) {
498
1028
  const refs = [];
@@ -526,26 +1056,29 @@
526
1056
  if (n < 1073741824) return (n / 1048576).toFixed(1) + "MB";
527
1057
  return (n / 1073741824).toFixed(1) + "GB";
528
1058
  }
529
- function fileCardInfo(block) {
1059
+ function fileCardInfo(block, t = zhTranslate) {
530
1060
  if (!block || block.type !== "file") return null;
531
1061
  const ref = block.attachment;
532
- const name = ref && typeof ref === "object" && typeof ref.name === "string" && ref.name !== "" ? ref.name : "\u672A\u547D\u540D\u6587\u4EF6";
1062
+ const name = ref && typeof ref === "object" && typeof ref.name === "string" && ref.name !== "" ? ref.name : t("file.unnamed");
533
1063
  return { name, ext: fileExtOf(name), size: fileCardSizeText(ref && typeof ref === "object" ? ref.bytes : void 0) };
534
1064
  }
535
1065
  var KIND_INFO = {
536
- modified: { label: "\u4FEE\u6539", cls: "modified" },
537
- restored: { label: "\u6062\u590D", cls: "restored" },
538
- added: { label: "\u5220\u9664", cls: "added" }
1066
+ modified: { labelKey: "kind.modified", cls: "modified" },
1067
+ restored: { labelKey: "kind.restored", cls: "restored" },
1068
+ added: { labelKey: "kind.added", cls: "added" }
539
1069
  };
540
- function summaryText(counts) {
1070
+ function isChangeKind(kind) {
1071
+ return typeof kind === "string" && Object.prototype.hasOwnProperty.call(KIND_INFO, kind);
1072
+ }
1073
+ function summaryText(counts, t = zhTranslate) {
541
1074
  const parts = [];
542
1075
  for (const kind of Object.keys(KIND_INFO)) {
543
- if (counts[kind] > 0) parts.push(KIND_INFO[kind].label + " " + counts[kind]);
1076
+ if (counts[kind] > 0) parts.push(t(KIND_INFO[kind].labelKey) + " " + counts[kind]);
544
1077
  }
545
1078
  return parts.join(" \xB7 ");
546
1079
  }
547
1080
  function buildRecallNode(React, util, ctx, sessionsSvc, workspacesSvc, uiWorkspaceSvc) {
548
- const { api, ensureInit, showThrottledToast, writeClipboard, clockText: clockText2, pluginConfig, messageFor } = util;
1081
+ const { api, ensureInit, showThrottledToast, writeClipboard, clockText: clockText2, pluginConfig, messageFor, t } = util;
549
1082
  function CopyIcon() {
550
1083
  return React.createElement(
551
1084
  "svg",
@@ -578,19 +1111,19 @@
578
1111
  return React.createElement(
579
1112
  "div",
580
1113
  { className: "dsh-recall-panel" },
581
- React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6B63\u5728\u8BA1\u7B97\u53D8\u66F4\u2026")
1114
+ React.createElement("div", { className: "dsh-recall-panel-title" }, t("recall.loading"))
582
1115
  );
583
1116
  }
584
1117
  if (recall.stage === "error") {
585
1118
  return React.createElement(
586
1119
  "div",
587
1120
  { className: "dsh-recall-panel" },
588
- React.createElement("div", { className: "dsh-recall-panel-title" }, "\u65E0\u6CD5\u56DE\u9000"),
1121
+ React.createElement("div", { className: "dsh-recall-panel-title" }, t("recall.error.title")),
589
1122
  React.createElement("div", { className: "dsh-recall-panel-note" }, recall.message || ""),
590
1123
  React.createElement(
591
1124
  "div",
592
1125
  { className: "dsh-recall-panel-actions" },
593
- React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u5173\u95ED")
1126
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, t("common.close"))
594
1127
  )
595
1128
  );
596
1129
  }
@@ -599,14 +1132,14 @@
599
1132
  const total = typeof recall.total === "number" ? recall.total : changes.length;
600
1133
  const counts = { modified: 0, restored: 0, added: 0 };
601
1134
  for (const c of changes) {
602
- if (c && counts[c.kind] !== void 0) counts[c.kind]++;
1135
+ if (c && isChangeKind(c.kind)) counts[c.kind]++;
603
1136
  }
604
1137
  const rows = changes.map((c, i) => {
605
1138
  const info = KIND_INFO[c.kind];
606
1139
  return React.createElement(
607
1140
  "div",
608
1141
  { className: "dsh-recall-file", key: i },
609
- React.createElement("span", { className: "dsh-recall-badge dsh-recall-badge-" + (info ? info.cls : "") }, info ? info.label : c.kind || ""),
1142
+ React.createElement("span", { className: "dsh-recall-badge dsh-recall-badge-" + (info ? info.cls : "") }, info ? t(info.labelKey) : c.kind || ""),
610
1143
  React.createElement("span", { className: "dsh-recall-rel" }, c.rel || "")
611
1144
  );
612
1145
  });
@@ -616,30 +1149,36 @@
616
1149
  rows.push(React.createElement(
617
1150
  "div",
618
1151
  { className: "dsh-recall-panel-note", key: "truncated" },
619
- sessionOnly ? "\u2026\u4EC5\u663E\u793A\u524D " + changes.length + " \u6761\uFF0C\u5171 " + total + " \u5904\u5DEE\u5F02" : "\u2026\u4EC5\u663E\u793A\u524D " + changes.length + " \u6761\uFF0C\u5171 " + total + " \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4"
1152
+ t(sessionOnly ? "recall.truncated.chat" : "recall.truncated.files", { shown: changes.length, total })
620
1153
  ));
621
1154
  }
1155
+ const summary = summaryText(counts, t);
1156
+ const rollbackLead = recall.time ? t("recall.confirm.rollbackAt", { time: clockText2(recall.time) }) : t("recall.confirm.rollbackNoTime");
1157
+ const filesLead = t("recall.confirm.files", {
1158
+ total,
1159
+ summary: summary ? t("common.paren", { text: summary }) : ""
1160
+ });
622
1161
  return React.createElement(
623
1162
  "div",
624
1163
  { className: "dsh-recall-panel" },
625
- React.createElement("div", { className: "dsh-recall-panel-title" }, "\u6574\u6BB5\u56DE\u9000"),
1164
+ React.createElement("div", { className: "dsh-recall-panel-title" }, t("recall.confirm.title")),
626
1165
  sessionOnly ? (
627
1166
  // session-only:零文件改动即无不可逆操作缺口——安全快照预告随之隐藏
628
1167
  //(不打安全快照),主说明换成模式语义
629
1168
  React.createElement(
630
1169
  "div",
631
1170
  { className: "dsh-recall-panel-note" },
632
- "\u9879\u76EE\u6587\u4EF6\u4FDD\u6301\u5F53\u524D\u72B6\u6001\uFF0C\u4E0D\u4F1A\u88AB\u56DE\u9000\u6216\u5220\u9664\uFF1B\u5BF9\u8BDD\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\u3002"
1171
+ t("recall.confirm.sessionOnly")
633
1172
  )
634
1173
  ) : React.createElement(
635
1174
  "div",
636
1175
  { className: "dsh-recall-panel-note" },
637
- "\u5C06\u9879\u76EE\u6062\u590D\u5230" + (recall.time ? " " + clockText2(recall.time) + " " : " ") + "\u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002\u5171 " + total + " \u4E2A\u6587\u4EF6\u5C06\u53D8\u66F4" + (summaryText(counts) ? "\uFF08" + summaryText(counts) + "\uFF09" : "") + "\u3002\u6B64\u64CD\u4F5C\u4F1A\u8986\u76D6\u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1B\u56DE\u9000\u524D\u4F1A\u81EA\u52A8\u4FDD\u5B58\u4E00\u4EFD\u5F53\u524D\u72B6\u6001\u7684\u5B89\u5168\u5FEB\u7167\uFF08\u4E0D\u542B\u5728\u4E0B\u65B9\u6E05\u5355\u5185\uFF09\u3002"
1176
+ rollbackLead + filesLead
638
1177
  ),
639
1178
  sessionOnly ? null : React.createElement(
640
1179
  "div",
641
1180
  { className: "dsh-recall-panel-note" },
642
- canRevertChat ? "\u5BF9\u8BDD\u5C06\u4E00\u5E76\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\uFF1A\u8BE5\u6D88\u606F\u53CA\u4E4B\u540E\u7684\u5168\u90E8\u5BF9\u8BDD\u4F1A\u4ECE\u5F53\u524D\u89C6\u56FE\u79FB\u9664\uFF0C\u539F\u4F1A\u8BDD\u5F52\u6863\u4FDD\u5B58\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002" : "\u8BE5\u6D88\u606F\u662F\u672C\u4F1A\u8BDD\u4E2D\u7B2C\u4E00\u6761\u7528\u6237\u6D88\u606F\uFF0C\u65E0\u6CD5\u56DE\u9000\u5BF9\u8BDD\uFF1B\u786E\u8BA4\u540E\u4EC5\u56DE\u9000\u9879\u76EE\u6587\u4EF6\u3002"
1181
+ t(canRevertChat ? "recall.confirm.chatBoth" : "recall.confirm.chatFirst")
643
1182
  ),
644
1183
  sessionOnly && changes.length > 0 ? (
645
1184
  // 文件清单保留展示但降级为参考语义:清单照常算(Host preview 链路
@@ -647,7 +1186,7 @@
647
1186
  React.createElement(
648
1187
  "div",
649
1188
  { className: "dsh-recall-panel-note", key: "ref-note" },
650
- "\u4EE5\u4E0B\u5DEE\u5F02\u4EC5\u4F5C\u53C2\u8003\uFF0C\u6240\u9009\u6A21\u5F0F\u4E0D\u4F1A\u6539\u52A8\u6587\u4EF6\u3002"
1189
+ t("recall.confirm.refNote")
651
1190
  )
652
1191
  ) : null,
653
1192
  changes.length > 0 ? React.createElement("div", { className: "dsh-recall-list" }, ...rows) : null,
@@ -656,26 +1195,26 @@
656
1195
  // 默认 both 与现状一致,面板每次打开都复位到默认起点
657
1196
  React.createElement(
658
1197
  "div",
659
- { className: "dsh-recall-scope", role: "radiogroup", "aria-label": "\u64A4\u56DE\u8303\u56F4" },
1198
+ { className: "dsh-recall-scope", role: "radiogroup", "aria-label": t("recall.scope.aria") },
660
1199
  React.createElement(
661
1200
  "label",
662
1201
  { className: "dsh-recall-scope-item" },
663
1202
  React.createElement("input", { type: "radio", name: "dsh-recall-scope", checked: scope === "both", onChange: () => onScopeChange("both") }),
664
- React.createElement("span", { className: "dsh-recall-scope-label" }, "\u56DE\u9000\u6587\u4EF6\u4E0E\u5BF9\u8BDD")
1203
+ React.createElement("span", { className: "dsh-recall-scope-label" }, t("recall.scope.both"))
665
1204
  ),
666
1205
  React.createElement(
667
1206
  "label",
668
1207
  { className: "dsh-recall-scope-item" },
669
1208
  React.createElement("input", { type: "radio", name: "dsh-recall-scope", checked: scope === "session-only", onChange: () => onScopeChange("session-only") }),
670
- React.createElement("span", { className: "dsh-recall-scope-label" }, "\u4EC5\u64A4\u56DE\u5BF9\u8BDD")
1209
+ React.createElement("span", { className: "dsh-recall-scope-label" }, t("recall.scope.sessionOnly"))
671
1210
  )
672
1211
  )
673
1212
  ) : null,
674
1213
  React.createElement(
675
1214
  "div",
676
1215
  { className: "dsh-recall-panel-actions" },
677
- React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u53D6\u6D88"),
678
- React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-danger", onClick: executeRecall }, sessionOnly ? "\u786E\u8BA4\u64A4\u56DE\u5BF9\u8BDD" : "\u786E\u8BA4\u56DE\u9000")
1216
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, t("common.cancel")),
1217
+ React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-danger", onClick: executeRecall }, t(sessionOnly ? "recall.confirm.submit.sessionOnly" : "recall.confirm.submit.both"))
679
1218
  )
680
1219
  );
681
1220
  }
@@ -683,26 +1222,26 @@
683
1222
  return React.createElement(
684
1223
  "div",
685
1224
  { className: "dsh-recall-panel" },
686
- React.createElement("div", { className: "dsh-recall-panel-title" }, scope === "session-only" ? "\u6B63\u5728\u64A4\u56DE\u5BF9\u8BDD\u2026" : "\u6B63\u5728\u56DE\u9000\u2026")
1225
+ React.createElement("div", { className: "dsh-recall-panel-title" }, t(scope === "session-only" ? "recall.executing.sessionOnly" : "recall.executing.both"))
687
1226
  );
688
1227
  }
689
1228
  if (recall.stage === "done") {
690
1229
  return React.createElement(
691
1230
  "div",
692
1231
  { className: "dsh-recall-panel" },
693
- React.createElement("div", { className: "dsh-recall-panel-title" }, "\u56DE\u9000\u5B8C\u6210"),
1232
+ React.createElement("div", { className: "dsh-recall-panel-title" }, t("recall.done.title")),
694
1233
  React.createElement(
695
1234
  "div",
696
1235
  { className: "dsh-recall-panel-note" },
697
1236
  scope === "session-only" ? (
698
1237
  // session-only 文案矩阵:文件侧零改动是确定事实,失败时也只描述对话侧
699
- recall.chatReverted ? "\u5BF9\u8BDD\u5DF2\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\uFF0C\u9879\u76EE\u6587\u4EF6\u4FDD\u6301\u5F53\u524D\u72B6\u6001\u3002\u65B0\u4F1A\u8BDD\u5DF2\u6253\u5F00\uFF0C\u539F\u4F1A\u8BDD\u5DF2\u5F52\u6863\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002" : "\u5BF9\u8BDD\u56DE\u9000\u5931\u8D25\uFF1A" + (recall.chatError || "\u672A\u77E5\u539F\u56E0") + "\u3002\u9879\u76EE\u6587\u4EF6\u672A\u505A\u4EFB\u4F55\u6539\u52A8\u3002"
700
- ) : recall.chatReverted ? "\u9879\u76EE\u6587\u4EF6\u4E0E\u5BF9\u8BDD\u5DF2\u56DE\u9000\u5230\u8BE5\u6D88\u606F\u4E4B\u524D\u3002\u65B0\u4F1A\u8BDD\u5DF2\u6253\u5F00\uFF0C\u539F\u4F1A\u8BDD\u5DF2\u5F52\u6863\uFF08\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF09\u3002" : "\u9879\u76EE\u5DF2\u6062\u590D\u5230\u53D1\u9001\u8BE5\u6D88\u606F\u65F6\u7684\u72B6\u6001\u3002" + (recall.chatError ? " \u5BF9\u8BDD\u56DE\u9000\u5931\u8D25\uFF1A" + recall.chatError : "")
1238
+ recall.chatReverted ? t("recall.done.sessionOnly.ok") : t("recall.done.sessionOnly.fail", { error: recall.chatError || t("common.unknownReason") })
1239
+ ) : recall.chatReverted ? t("recall.done.both.ok") : t("recall.done.both.partial") + (recall.chatError ? t("recall.done.both.failChat", { error: recall.chatError }) : "")
701
1240
  ),
702
1241
  React.createElement(
703
1242
  "div",
704
1243
  { className: "dsh-recall-panel-actions" },
705
- React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, "\u5173\u95ED")
1244
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: closePanel }, t("common.close"))
706
1245
  )
707
1246
  );
708
1247
  }
@@ -809,8 +1348,8 @@
809
1348
  } catch (error) {
810
1349
  }
811
1350
  if (Date.now() >= deadline) {
812
- console.warn("[dsh-recall-plugin] \u6B8B\u7559\u6392\u961F\u6D88\u606F\u672A\u80FD\u81EA\u52A8\u6E05\u7406\uFF08\u4F1A\u8BDD\u9762\u672A\u5C31\u7EEA\uFF09\uFF1A", itemIds);
813
- showThrottledToast("\u64A4\u56DE\u524D\u7684\u4E00\u6761\u6392\u961F\u6D88\u606F\u672A\u88AB\u81EA\u52A8\u6E05\u7406\uFF0C\u53EF\u70B9\u51FB\u8BE5\u5361\u7247\u53F3\u4E0A\u89D2\u7684\u5220\u9664\u6309\u94AE\u624B\u52A8\u79FB\u9664");
1351
+ recallNodeLog.warn("\u6B8B\u7559\u6392\u961F\u6D88\u606F\u672A\u80FD\u81EA\u52A8\u6E05\u7406\uFF08\u4F1A\u8BDD\u9762\u672A\u5C31\u7EEA\uFF09\uFF1A", itemIds);
1352
+ showThrottledToast(t("toast.staleQueue"));
814
1353
  return;
815
1354
  }
816
1355
  setTimeout(attempt, 250);
@@ -849,18 +1388,21 @@
849
1388
  if (!alive) return;
850
1389
  if (res && res.has) {
851
1390
  if (recent && Array.isArray(res.skipped) && res.skipped.length) {
852
- const names = res.skipped.slice(0, 5).join("\u3001") + (res.skipped.length > 5 ? " \u7B49 " + res.skipped.length + " \u9879" : "");
853
- showThrottledToast("\u5FEB\u7167\u5DF2\u8DF3\u8FC7\u672A\u7EB3\u5165\u7684\u8DEF\u5F84\uFF1A" + names + "\uFF08\u64A4\u56DE\u4E0D\u4F1A\u6062\u590D\u6216\u5220\u9664\u8FD9\u4E9B\u8DEF\u5F84\uFF09");
1391
+ const names = res.skipped.slice(0, 5).join(t("common.listSep")) + (res.skipped.length > 5 ? t("toast.skippedMore", { n: res.skipped.length }) : "");
1392
+ showThrottledToast(t("toast.skipped", { names }));
854
1393
  }
855
1394
  setHasSnapshot(true);
856
1395
  return;
857
1396
  }
858
1397
  if (res && res.failed) {
859
- if (recent) showThrottledToast("\u5FEB\u7167\u5931\u8D25\uFF1A" + String(res.error || "\u672A\u77E5\u539F\u56E0").slice(0, 140));
1398
+ if (recent) showThrottledToast(t("toast.snapshotFailed", { error: String(res.error || t("common.unknownReason")).slice(0, 140) }));
860
1399
  return;
861
1400
  }
862
- if (res && res.notice) {
863
- if (recent) showThrottledToast(String(res.notice).slice(0, 140));
1401
+ if (res && (res.artifactSeg || res.notice)) {
1402
+ if (recent) {
1403
+ const text2 = res.artifactSeg ? t("notice.buildRoot", { seg: res.artifactSeg }) : String(res.notice).slice(0, 140);
1404
+ showThrottledToast(text2);
1405
+ }
864
1406
  return;
865
1407
  }
866
1408
  if (recent && attempts < MAX_ATTEMPTS) timer = setTimeout(schedule, RETRY_MS);
@@ -897,7 +1439,7 @@
897
1439
  setRecall({ stage: "loading" });
898
1440
  api("preview", { messageId, sessionId }).then((res) => {
899
1441
  if (!res || !res.ok) {
900
- setRecall({ stage: "error", message: messageFor(res, "\u65E0\u6CD5\u83B7\u53D6\u5FEB\u7167") });
1442
+ setRecall({ stage: "error", message: messageFor(res, t("fallback.preview")) });
901
1443
  return;
902
1444
  }
903
1445
  setRecall({
@@ -917,6 +1459,7 @@
917
1459
  if (recall.stage !== "confirm") return;
918
1460
  const changes = recall.changes || [];
919
1461
  const previewCut = typeof recall.cutSeq === "number" ? recall.cutSeq : null;
1462
+ const notifyBase = { sessionId: String(sessionId || ""), messageId, scope };
920
1463
  const previewTotal = typeof recall.total === "number" ? recall.total : changes.length;
921
1464
  const attachmentFiles = pluginConfig.refillDraft && attachmentRefs.length > 0 && sessionId ? preloadAttachmentFiles(sessionId, attachmentRefs) : null;
922
1465
  setRecall({ stage: "executing", changes });
@@ -926,7 +1469,7 @@
926
1469
  setRecall({ stage: "loading" });
927
1470
  api("preview", { messageId, sessionId }).then((res2) => {
928
1471
  if (!res2 || !res2.ok) {
929
- setRecall({ stage: "error", message: messageFor(res2, "\u65E0\u6CD5\u83B7\u53D6\u5FEB\u7167") });
1472
+ setRecall({ stage: "error", message: messageFor(res2, t("fallback.preview")) });
930
1473
  return;
931
1474
  }
932
1475
  setRecall({
@@ -943,12 +1486,20 @@
943
1486
  });
944
1487
  return;
945
1488
  }
946
- setRecall({ stage: "error", message: messageFor(res, "\u56DE\u9000\u5931\u8D25") });
1489
+ api("notify", buildRecallNotify(notifyBase, {
1490
+ outcome: "execute-rejected",
1491
+ cutSeq: previewCut,
1492
+ code: res && typeof res.code === "string" ? res.code : void 0,
1493
+ error: messageFor(res, "")
1494
+ })).catch(() => {
1495
+ });
1496
+ setRecall({ stage: "error", message: messageFor(res, t("fallback.rollback")) });
947
1497
  return;
948
1498
  }
949
1499
  const cutSeq = typeof res.cutSeq === "number" ? res.cutSeq : previewCut;
950
1500
  let chatReverted = false;
951
1501
  let chatError = "";
1502
+ let archiveRequested = false;
952
1503
  let fillTarget = sessionId;
953
1504
  if (cutSeq !== null && sessionsSvc && typeof sessionsSvc.fork === "function") {
954
1505
  try {
@@ -962,25 +1513,43 @@
962
1513
  api("lineage-record", { childId, parentId: sessionId }).catch(() => {
963
1514
  });
964
1515
  if (pluginConfig.archiveOriginal && workspacesSvc && typeof workspacesSvc.archiveSession === "function") {
1516
+ archiveRequested = true;
965
1517
  workspacesSvc.archiveSession(sessionId, { stopActivity: true }).catch((error) => {
966
- console.warn("[dsh-recall-plugin] archive original session failed:", error);
1518
+ recallNodeLog.warn("archive original session failed:", error);
967
1519
  });
968
1520
  }
969
1521
  } else {
970
- chatError = "\u672A\u8FD4\u56DE\u65B0\u4F1A\u8BDD";
1522
+ chatError = t("recall.chat.noChild");
1523
+ api("notify", buildRecallNotify(notifyBase, { outcome: "fork-failed", cutSeq, error: chatError })).catch(() => {
1524
+ });
971
1525
  }
972
1526
  } catch (error) {
973
1527
  chatError = String(error);
1528
+ api("notify", buildRecallNotify(notifyBase, { outcome: "fork-failed", cutSeq, error: chatError })).catch(() => {
1529
+ });
974
1530
  }
975
1531
  }
976
1532
  if (pluginConfig.refillDraft) {
977
1533
  fillDraft(fillTarget, text, attachmentFiles);
978
- if (fileBlocks.length > 0) showThrottledToast("\u88AB\u64A4\u56DE\u6D88\u606F\u91CC\u7684\u6587\u4EF6\u9644\u4EF6\u65E0\u6CD5\u81EA\u52A8\u56DE\u586B\uFF08\u5B98\u65B9\u63A5\u53E3\u53EA\u652F\u6301\u56FE\u7247\u56DE\u8BFB\uFF09\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u6587\u4EF6");
1534
+ if (fileBlocks.length > 0) showThrottledToast(t("toast.fileAttachRefill"));
979
1535
  }
980
1536
  setHasSnapshot(false);
981
1537
  setRecall({ stage: "done", count: typeof res.count === "number" ? res.count : changes.length, chatReverted, chatError });
1538
+ if (chatError === "") {
1539
+ api("notify", buildRecallNotify(notifyBase, {
1540
+ outcome: "complete",
1541
+ cutSeq,
1542
+ childSessionId: chatReverted ? String(fillTarget || "") || null : null,
1543
+ count: typeof res.count === "number" ? res.count : changes.length,
1544
+ chatReverted,
1545
+ archiveRequested
1546
+ })).catch(() => {
1547
+ });
1548
+ }
982
1549
  }).catch((error) => {
983
1550
  setRecall({ stage: "error", message: String(error) });
1551
+ api("notify", buildRecallNotify(notifyBase, { outcome: "execute-threw", cutSeq: previewCut, error: String(error) })).catch(() => {
1552
+ });
984
1553
  });
985
1554
  };
986
1555
  const closePanel = () => {
@@ -997,7 +1566,7 @@
997
1566
  ));
998
1567
  }
999
1568
  for (let i = 0; i < fileBlocks.length; i++) {
1000
- const info = fileCardInfo(fileBlocks[i]);
1569
+ const info = fileCardInfo(fileBlocks[i], t);
1001
1570
  if (!info) continue;
1002
1571
  bubbleChildren.push(React.createElement(
1003
1572
  "div",
@@ -1021,8 +1590,8 @@
1021
1590
  key: "copy",
1022
1591
  type: "button",
1023
1592
  className: "dsh-recall-action",
1024
- "aria-label": copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236",
1025
- title: copied ? "\u5DF2\u590D\u5236" : "\u590D\u5236",
1593
+ "aria-label": copied ? t("action.copied") : t("action.copy"),
1594
+ title: copied ? t("action.copied") : t("action.copy"),
1026
1595
  onClick: onCopy
1027
1596
  }, copied ? React.createElement(CheckIcon, {}) : React.createElement(CopyIcon, {})));
1028
1597
  if (hasSnapshot) {
@@ -1030,8 +1599,8 @@
1030
1599
  key: "recall",
1031
1600
  type: "button",
1032
1601
  className: "dsh-recall-action",
1033
- "aria-label": "\u64A4\u56DE",
1034
- title: "\u6574\u6BB5\u56DE\u9000\uFF1A\u6587\u4EF6\u4E0E\u5BF9\u8BDD\u4E00\u5E76\u56DE\u5230\u8BE5\u6D88\u606F\u4E4B\u524D",
1602
+ "aria-label": t("action.recall"),
1603
+ title: t("action.recall.title"),
1035
1604
  onClick: openPreview
1036
1605
  }, React.createElement(UndoIcon, {})));
1037
1606
  }
@@ -1048,8 +1617,8 @@
1048
1617
 
1049
1618
  // src/client/config-card.ts
1050
1619
  function buildConfigForm(React, util, SectionToggle) {
1051
- const { api, bytesToMb: bytesToMb2 } = util;
1052
- function ConfigForm() {
1620
+ const { api, bytesToMb: bytesToMb2, t, setLocalePref } = util;
1621
+ function ConfigForm(props) {
1053
1622
  const [baseline, setBaseline] = React.useState(null);
1054
1623
  const [draft, setDraft] = React.useState(null);
1055
1624
  const [envLocks, setEnvLocks] = React.useState({});
@@ -1071,15 +1640,18 @@
1071
1640
  refillDraft: v.refillDraft !== false,
1072
1641
  snapshotEnabled: v.snapshotEnabled !== false,
1073
1642
  archiveOriginal: v.archiveOriginal !== false,
1074
- retentionDays: String(v.retentionDays == null ? "" : v.retentionDays)
1643
+ retentionDays: String(v.retentionDays == null ? "" : v.retentionDays),
1644
+ locale: typeof v.locale === "string" && v.locale ? v.locale : "auto"
1075
1645
  };
1646
+ setLocalePref(next.locale);
1647
+ if (props.onLocaleApplied) props.onLocaleApplied();
1076
1648
  setDraft(next);
1077
1649
  setBaseline(next);
1078
1650
  setEnvLocks(res.envLocks || {});
1079
1651
  setOverridden(res.overridden || {});
1080
1652
  setWritable(res.writable !== false);
1081
1653
  } else {
1082
- setState({ busy: false, message: res && (res.message || res.error) || "\u65E0\u6CD5\u8BFB\u53D6\u914D\u7F6E", error: true });
1654
+ setState({ busy: false, message: res && (res.message || res.error) || t("config.msg.loadFailed"), error: true });
1083
1655
  }
1084
1656
  }).catch((e) => setState({ busy: false, message: String(e), error: true }));
1085
1657
  }
@@ -1092,18 +1664,19 @@
1092
1664
  function save() {
1093
1665
  if (state.busy || !draft || !baseline) return;
1094
1666
  const patch = {};
1095
- for (const key of ["gcSnaps", "gcHours", "maxFileBytes", "maxSnapshotsPerWorkspace", "baseExcludes", "refillDraft", "snapshotEnabled", "archiveOriginal", "retentionDays"]) {
1096
- if (draft[key] !== baseline[key]) patch[key] = draft[key];
1667
+ for (const key of ["gcSnaps", "gcHours", "maxFileBytes", "maxSnapshotsPerWorkspace", "baseExcludes", "refillDraft", "snapshotEnabled", "archiveOriginal", "retentionDays", "locale"]) {
1668
+ const value = draft[key];
1669
+ if (value !== void 0 && value !== baseline[key]) patch[key] = value;
1097
1670
  }
1098
1671
  if (!Object.keys(patch).length) {
1099
- setState({ busy: false, message: "\u6CA1\u6709\u4FEE\u6539", error: false });
1672
+ setState({ busy: false, message: t("config.msg.noChange"), error: false });
1100
1673
  return;
1101
1674
  }
1102
1675
  const clean = {};
1103
1676
  if (patch.gcSnaps !== void 0) {
1104
1677
  const n = parseInt(String(patch.gcSnaps), 10);
1105
1678
  if (!Number.isFinite(n) || n < 1) {
1106
- setState({ busy: false, message: "\u5FEB\u7167\u6761\u6570\u9608\u503C\u5FC5\u987B\u662F >= 1 \u7684\u6574\u6570", error: true });
1679
+ setState({ busy: false, message: t("config.err.gcSnaps"), error: true });
1107
1680
  return;
1108
1681
  }
1109
1682
  clean.gcSnaps = n;
@@ -1111,7 +1684,7 @@
1111
1684
  if (patch.gcHours !== void 0) {
1112
1685
  const n = parseInt(String(patch.gcHours), 10);
1113
1686
  if (!Number.isFinite(n) || n < 1) {
1114
- setState({ busy: false, message: "gc \u5C0F\u65F6\u9608\u503C\u5FC5\u987B\u662F >= 1 \u7684\u6574\u6570", error: true });
1687
+ setState({ busy: false, message: t("config.err.gcHours"), error: true });
1115
1688
  return;
1116
1689
  }
1117
1690
  clean.gcHours = n;
@@ -1119,7 +1692,7 @@
1119
1692
  if (patch.maxFileBytes !== void 0) {
1120
1693
  const mb = Number(patch.maxFileBytes);
1121
1694
  if (!Number.isFinite(mb) || mb < 0.01) {
1122
- setState({ busy: false, message: "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650\u81F3\u5C11 0.01 MB", error: true });
1695
+ setState({ busy: false, message: t("config.err.maxFileBytes"), error: true });
1123
1696
  return;
1124
1697
  }
1125
1698
  clean.maxFileBytes = Math.round(mb * 1048576);
@@ -1127,7 +1700,7 @@
1127
1700
  if (patch.maxSnapshotsPerWorkspace !== void 0) {
1128
1701
  const n = parseInt(String(patch.maxSnapshotsPerWorkspace), 10);
1129
1702
  if (!Number.isFinite(n) || n < 0) {
1130
- setState({ busy: false, message: "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650\u5FC5\u987B\u662F >= 0 \u7684\u6574\u6570\uFF080 \u8868\u793A\u4E0D\u9650\u5236\uFF09", error: true });
1703
+ setState({ busy: false, message: t("config.err.maxSnapshots"), error: true });
1131
1704
  return;
1132
1705
  }
1133
1706
  clean.maxSnapshotsPerWorkspace = n;
@@ -1138,21 +1711,30 @@
1138
1711
  if (patch.retentionDays !== void 0) {
1139
1712
  const n = parseInt(String(patch.retentionDays), 10);
1140
1713
  if (!Number.isFinite(n) || n < 0) {
1141
- setState({ busy: false, message: "\u4FDD\u7559\u5929\u6570\u5FC5\u987B\u662F >= 0 \u7684\u6574\u6570\uFF080 \u8868\u793A\u4E0D\u542F\u7528\uFF09", error: true });
1714
+ setState({ busy: false, message: t("config.err.retentionDays"), error: true });
1142
1715
  return;
1143
1716
  }
1144
1717
  clean.retentionDays = n;
1145
1718
  }
1719
+ if (patch.locale !== void 0) {
1720
+ const v = String(patch.locale);
1721
+ if (v !== "auto" && v !== "zh" && v !== "en") {
1722
+ setState({ busy: false, message: t("config.err.locale"), error: true });
1723
+ return;
1724
+ }
1725
+ clean.locale = v;
1726
+ }
1146
1727
  if (patch.baseExcludes !== void 0) {
1147
1728
  clean.baseExcludes = String(patch.baseExcludes).split("\n").map((l) => l.trim()).filter(Boolean);
1148
1729
  }
1149
- setState({ busy: true, message: "\u4FDD\u5B58\u4E2D\u2026", error: false });
1730
+ setState({ busy: true, message: t("config.msg.saving"), error: false });
1150
1731
  api("config-set", { patch: clean }).then((res) => {
1151
1732
  if (res && res.ok) {
1152
- setState({ busy: false, message: "\u5DF2\u4FDD\u5B58\u5E76\u5373\u65F6\u751F\u6548", error: false });
1733
+ if (clean.locale !== void 0) setLocalePref(clean.locale);
1734
+ setState({ busy: false, message: t("config.msg.saved"), error: false });
1153
1735
  load();
1154
1736
  } else {
1155
- setState({ busy: false, message: res && (res.message || res.error) || "\u4FDD\u5B58\u5931\u8D25", error: true });
1737
+ setState({ busy: false, message: res && (res.message || res.error) || t("config.msg.saveFailed"), error: true });
1156
1738
  }
1157
1739
  }).catch((e) => setState({ busy: false, message: String(e), error: true }));
1158
1740
  }
@@ -1184,10 +1766,10 @@
1184
1766
  step: opts && opts.step,
1185
1767
  onChange: (e) => edit(key, e.target.value)
1186
1768
  }),
1187
- opts && opts.suffix ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, opts.suffix) : null,
1188
- changed && !locked ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-modified" }, "\u5DF2\u4FEE\u6539") : null,
1189
- overridden && overridden[key] !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null,
1190
- locked ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-locked" }, "\u73AF\u5883\u53D8\u91CF\u9501\u5B9A") : null
1769
+ opts && opts.suffixKey ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, t(opts.suffixKey)) : null,
1770
+ changed && !locked ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-modified" }, t("config.tag.modified")) : null,
1771
+ overridden && overridden[key] !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, t("config.tag.overridden")) : null,
1772
+ locked ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-locked" }, t("config.tag.locked")) : null
1191
1773
  ),
1192
1774
  React.createElement("div", { className: "dsh-recall-cfg-hint" }, hint)
1193
1775
  );
@@ -1216,26 +1798,55 @@
1216
1798
  disabled: !writable,
1217
1799
  onClick: () => edit(key, !on)
1218
1800
  }, React.createElement("span", { className: "dsh-recall-cfg-switch-thumb" })),
1219
- changed ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-modified" }, "\u5DF2\u4FEE\u6539") : null,
1220
- overridden && overridden[key] !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
1801
+ changed ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-modified" }, t("config.tag.modified")) : null,
1802
+ overridden && overridden[key] !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, t("config.tag.overridden")) : null
1221
1803
  ),
1222
1804
  React.createElement("div", { className: "dsh-recall-cfg-hint" }, hint)
1223
1805
  );
1224
1806
  }
1807
+ function localeRow() {
1808
+ const changed = Boolean(draft && baseline && draft.locale !== baseline.locale);
1809
+ return React.createElement(
1810
+ "div",
1811
+ { className: "dsh-recall-cfg-row", key: "locale" },
1812
+ React.createElement("span", { className: "dsh-recall-cfg-label", id: "dsh-recall-cfg-label-locale" }, t("config.locale.label")),
1813
+ React.createElement(
1814
+ "div",
1815
+ { className: "dsh-recall-cfg-line" },
1816
+ React.createElement(
1817
+ "select",
1818
+ {
1819
+ id: "dsh-recall-cfg-locale",
1820
+ "aria-labelledby": "dsh-recall-cfg-label-locale",
1821
+ className: "dsh-recall-cfg-select",
1822
+ value: draft ? draft.locale : "auto",
1823
+ disabled: !writable,
1824
+ onChange: (e) => edit("locale", e.target.value)
1825
+ },
1826
+ React.createElement("option", { value: "auto" }, t("config.locale.auto")),
1827
+ React.createElement("option", { value: "zh" }, "\u4E2D\u6587"),
1828
+ React.createElement("option", { value: "en" }, "English")
1829
+ ),
1830
+ changed ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-modified" }, t("config.tag.modified")) : null,
1831
+ overridden && overridden.locale !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, t("config.tag.overridden")) : null
1832
+ ),
1833
+ React.createElement("div", { className: "dsh-recall-cfg-hint" }, t("config.locale.hint"))
1834
+ );
1835
+ }
1225
1836
  function resetDefaults() {
1226
1837
  if (state.busy || !writable) return;
1227
- setState({ busy: true, message: "\u6062\u590D\u9ED8\u8BA4\u4E2D\u2026", error: false });
1838
+ setState({ busy: true, message: t("config.msg.resetting"), error: false });
1228
1839
  api("config-reset", {}).then((res) => {
1229
1840
  if (res && res.ok) {
1230
1841
  load();
1231
- setState({ busy: false, message: "\u5DF2\u6062\u590D\u9ED8\u8BA4\u503C", error: false });
1842
+ setState({ busy: false, message: t("config.msg.resetDone"), error: false });
1232
1843
  } else {
1233
- setState({ busy: false, message: res && (res.message || res.error) || "\u6062\u590D\u9ED8\u8BA4\u5931\u8D25", error: true });
1844
+ setState({ busy: false, message: res && (res.message || res.error) || t("config.msg.resetFailed"), error: true });
1234
1845
  }
1235
1846
  }).catch((e) => setState({ busy: false, message: String(e), error: true }));
1236
1847
  }
1237
1848
  if (!draft || !baseline) {
1238
- return React.createElement("div", { className: "dsh-recall-ex-note" }, state.message || "\u6B63\u5728\u8BFB\u53D6\u914D\u7F6E\u2026");
1849
+ return React.createElement("div", { className: "dsh-recall-ex-note" }, state.message || t("config.msg.loading"));
1239
1850
  }
1240
1851
  return React.createElement(
1241
1852
  "div",
@@ -1247,52 +1858,56 @@
1247
1858
  React.createElement(
1248
1859
  "div",
1249
1860
  { className: "dsh-recall-cfg-grid" },
1250
- // V5 表单分组:9 字段平铺 → 「快照行为 / 自动治理」两组语义分组小标题,
1251
- // 降低认知负担;「高级:基础排除表」沿用 SectionToggle 折叠,不重复加标题。
1252
- React.createElement("div", { className: "dsh-recall-cfg-group" }, "\u5FEB\u7167\u884C\u4E3A"),
1253
- boolRow("snapshotEnabled", "\u542F\u7528\u5FEB\u7167", "\u5173\u95ED\u540E\u4E0D\u518D\u65B0\u5EFA\u5FEB\u7167\uFF1B\u5DF2\u6709\u5FEB\u7167\u4ECD\u53EF\u64A4\u56DE"),
1254
- boolRow("refillDraft", "\u56DE\u586B\u8F93\u5165\u6846", "\u64A4\u56DE\u540E\u628A\u6D88\u606F\u6587\u672C\u56DE\u586B\u8F93\u5165\u6846\uFF0C\u4FBF\u4E8E\u6539\u5B8C\u91CD\u53D1"),
1255
- boolRow("archiveOriginal", "\u5F52\u6863\u539F\u4F1A\u8BDD", "\u539F\u4F1A\u8BDD\u5F52\u6863\u9690\u85CF\uFF0C\u53EF\u4ECE\u5F52\u6863\u627E\u56DE\uFF1B\u5173\u95ED\u5219\u7559\u5728\u5217\u8868\u4FBF\u4E8E\u5BF9\u7167"),
1256
- React.createElement("div", { className: "dsh-recall-cfg-group" }, "\u81EA\u52A8\u6CBB\u7406"),
1257
- // 单位后缀统一挂输入框右侧(与状态标签同基线),不再只藏在说明文字里
1258
- numRow("gcSnaps", "gc \u89E6\u53D1\u6761\u6570", "\u6BCF\u79EF\u7D2F\u591A\u5C11\u6761\u5FEB\u7167\u89E6\u53D1\u4E00\u6B21 gc", { suffix: "\u6761", min: 1, step: 1 }),
1259
- numRow("gcHours", "gc \u89E6\u53D1\u5C0F\u65F6", "\u8DDD\u4E0A\u6B21 gc \u8D85\u8FC7\u591A\u5C11\u5C0F\u65F6\u89E6\u53D1\uFF0C\u4E0E\u6761\u6570\u5148\u5230\u5148\u89E6\u53D1", { suffix: "\u5C0F\u65F6", min: 1, step: 1 }),
1260
- numRow("maxFileBytes", "\u6587\u4EF6\u5927\u5C0F\u4E0A\u9650", "\u8D85\u8FC7\u8BE5\u5927\u5C0F\u7684\u6587\u4EF6\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0", { suffix: "MB", min: 0.01, step: 0.5 }),
1261
- numRow("maxSnapshotsPerWorkspace", "\u5FEB\u7167\u603B\u91CF\u4E0A\u9650", "\u6BCF\u5DE5\u4F5C\u533A\u4FDD\u7559\u7684\u6700\u5927\u5FEB\u7167\u6570\uFF0C\u8D85\u9650\u5220\u9664\u6700\u65E7\u7684\uFF1B0 \u8868\u793A\u4E0D\u9650\u5236", { suffix: "\u6761", min: 0, step: 1 }),
1262
- numRow("retentionDays", "\u5FEB\u7167\u4FDD\u7559\u5929\u6570", "\u8D85\u671F\u5FEB\u7167\u81EA\u52A8\u5220\u9664\u6700\u65E7\u7684\uFF1B0 \u8868\u793A\u4E0D\u542F\u7528", { suffix: "\u5929", min: 0, step: 1 }),
1861
+ // V5 表单分组:9 字段平铺 → 「快照行为 / 自动治理 / 界面」三组语义分组
1862
+ // 小标题,降低认知负担;「高级:基础排除表」沿用 SectionToggle 折叠,
1863
+ // 不重复加标题。
1864
+ React.createElement("div", { className: "dsh-recall-cfg-group" }, t("config.group.snapshot")),
1865
+ boolRow("snapshotEnabled", t("config.snapshotEnabled.label"), t("config.snapshotEnabled.hint")),
1866
+ boolRow("refillDraft", t("config.refillDraft.label"), t("config.refillDraft.hint")),
1867
+ boolRow("archiveOriginal", t("config.archiveOriginal.label"), t("config.archiveOriginal.hint")),
1868
+ React.createElement("div", { className: "dsh-recall-cfg-group" }, t("config.group.auto")),
1869
+ // 单位后缀统一挂输入框右侧(与状态标签同基线),不再只藏在说明文字里;
1870
+ // 传词表键而非字面量——单位词也是语言事实(条/小时 vs snapshots/hours)
1871
+ numRow("gcSnaps", t("config.gcSnaps.label"), t("config.gcSnaps.hint"), { suffixKey: "config.gcSnaps.suffix", min: 1, step: 1 }),
1872
+ numRow("gcHours", t("config.gcHours.label"), t("config.gcHours.hint"), { suffixKey: "config.gcHours.suffix", min: 1, step: 1 }),
1873
+ numRow("maxFileBytes", t("config.maxFileBytes.label"), t("config.maxFileBytes.hint"), { suffixKey: "config.maxFileBytes.suffix", min: 0.01, step: 0.5 }),
1874
+ numRow("maxSnapshotsPerWorkspace", t("config.maxSnapshotsPerWorkspace.label"), t("config.maxSnapshotsPerWorkspace.hint"), { suffixKey: "config.maxSnapshotsPerWorkspace.suffix", min: 0, step: 1 }),
1875
+ numRow("retentionDays", t("config.retentionDays.label"), t("config.retentionDays.hint"), { suffixKey: "config.retentionDays.suffix", min: 0, step: 1 }),
1876
+ React.createElement("div", { className: "dsh-recall-cfg-group" }, t("config.group.ui")),
1877
+ localeRow(),
1263
1878
  // 操作区在「基础排除表」折叠头之前:按钮服务整个表单(含折叠区之外的字段),
1264
1879
  // 排在折叠头之后会被误读为折叠区内容、折叠时像漏收起(用户实测反馈);
1265
1880
  // 因此也不把按钮藏进折叠分支——否则折叠基础排除表后将无法保存。
1266
1881
  React.createElement(
1267
1882
  "div",
1268
1883
  { className: "dsh-recall-panel-actions" },
1269
- state.message ? React.createElement("span", { role: "status", "aria-live": "polite", className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, (state.error ? "\u9519\u8BEF\uFF1A" : "") + state.message) : null,
1270
- React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy || !writable, onClick: () => setDraft(baseline ? Object.assign({}, baseline) : null) }, "\u653E\u5F03\u4FEE\u6539"),
1884
+ state.message ? React.createElement("span", { role: "status", "aria-live": "polite", className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, (state.error ? t("common.errorPrefix") : "") + state.message) : null,
1885
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy || !writable, onClick: () => setDraft(baseline ? Object.assign({}, baseline) : null) }, t("common.discard")),
1271
1886
  React.createElement("button", {
1272
1887
  type: "button",
1273
1888
  className: "dsh-recall-btn",
1274
1889
  disabled: state.busy || !writable,
1275
- title: "\u628A\u6240\u6709\u5B57\u6BB5\u6062\u590D\u5230\u63D2\u4EF6\u51FA\u5382\u9ED8\u8BA4\u503C",
1890
+ title: t("config.reset.title"),
1276
1891
  onClick: resetDefaults
1277
- }, "\u6062\u590D\u9ED8\u8BA4"),
1892
+ }, t("config.reset")),
1278
1893
  // 「保存」是表单唯一主动作,升主色实心按钮;放弃修改/恢复默认维持次级
1279
1894
  // 灰底(同排按钮只有一个视觉焦点,配色令牌经官方主题产物核验,见 css.ts)
1280
- React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-primary", disabled: state.busy || !writable, onClick: save }, "\u4FDD\u5B58"),
1281
- !writable ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u53EA\u8BFB\u8BBE\u7F6E\u6E90") : null
1895
+ React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-primary", disabled: state.busy || !writable, onClick: save }, t("common.save")),
1896
+ !writable ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, t("config.tag.readonly")) : null
1282
1897
  ),
1283
1898
  // divider:本条是折叠分区列表的首项,上方分界线把「字段表单」与「折叠
1284
1899
  // 分区」分开(否则保存按钮行紧贴折叠头,读起来像同一组字段)
1285
- React.createElement(SectionToggle, { title: "\u9AD8\u7EA7\uFF1A\u57FA\u7840\u6392\u9664\u8868", open: showAdvanced, onToggle: () => setShowAdvanced((v) => !v), divider: true }),
1900
+ React.createElement(SectionToggle, { title: t("config.advanced.title"), open: showAdvanced, onToggle: () => setShowAdvanced((v) => !v), divider: true }),
1286
1901
  showAdvanced ? React.createElement(
1287
1902
  "div",
1288
1903
  { className: "dsh-recall-cfg-row", key: "baseExcludes" },
1289
1904
  // V4:标签上提(与 numRow 同法);cfg-line 只剩 tags,textarea/hint 通栏
1290
- React.createElement("span", { className: "dsh-recall-cfg-label", id: "dsh-recall-cfg-label-baseExcludes" }, "\u57FA\u7840\u6392\u9664\u8868"),
1905
+ React.createElement("span", { className: "dsh-recall-cfg-label", id: "dsh-recall-cfg-label-baseExcludes" }, t("config.baseExcludes.label")),
1291
1906
  React.createElement(
1292
1907
  "div",
1293
1908
  { className: "dsh-recall-cfg-line" },
1294
- draft.baseExcludes !== baseline.baseExcludes ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-modified" }, "\u5DF2\u4FEE\u6539") : null,
1295
- overridden && overridden.baseExcludes !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, "\u5DF2\u8986\u76D6") : null
1909
+ draft.baseExcludes !== baseline.baseExcludes ? React.createElement("span", { className: "dsh-recall-cfg-tag dsh-recall-cfg-tag-modified" }, t("config.tag.modified")) : null,
1910
+ overridden && overridden.baseExcludes !== void 0 ? React.createElement("span", { className: "dsh-recall-cfg-tag" }, t("config.tag.overridden")) : null
1296
1911
  ),
1297
1912
  // textarea/hint 加 cfg-span 通栏:折叠区在共享 grid 内展开时,内容若只占
1298
1913
  // 第二列,左侧标签列会成为竖直死区(实测 textarea 被挤窄);通栏后与
@@ -1307,7 +1922,7 @@
1307
1922
  disabled: !writable,
1308
1923
  onChange: (e) => edit("baseExcludes", e.target.value)
1309
1924
  }),
1310
- React.createElement("div", { className: "dsh-recall-cfg-hint dsh-recall-cfg-span" }, "\u5404\u5DE5\u4F5C\u533A\u5171\u4EAB\u7684\u5185\u7F6E\u89C4\u5219\uFF1Bgitignore \u8BED\u6CD5\uFF0C\u6BCF\u884C\u4E00\u6761\uFF1B\u4F18\u5148\u7EA7\u4F4E\u4E8E\u300C\u6392\u9664\u914D\u7F6E\u300D\u7684 exclude.txt")
1925
+ React.createElement("div", { className: "dsh-recall-cfg-hint dsh-recall-cfg-span" }, t("config.baseExcludes.hint"))
1311
1926
  ) : null
1312
1927
  )
1313
1928
  );
@@ -1318,7 +1933,7 @@
1318
1933
  // src/client/exclude-card.ts
1319
1934
  var EXCLUDE_SUGGESTIONS = ["dist/", "build/", "out/", "coverage/", "*.log", ".env"];
1320
1935
  function buildExcludeCards(React, util) {
1321
- const { api } = util;
1936
+ const { api, t } = util;
1322
1937
  function ExcludeCard(props) {
1323
1938
  const file = props.file;
1324
1939
  const [draft, setDraft] = React.useState(file.content || "");
@@ -1331,20 +1946,20 @@
1331
1946
  setDraft((d) => (d && !d.endsWith("\n") ? d + "\n" : d) + pattern + "\n");
1332
1947
  }
1333
1948
  function addQuick() {
1334
- const t = quick.trim();
1335
- if (!t) return;
1336
- appendPattern(t);
1949
+ const pattern = quick.trim();
1950
+ if (!pattern) return;
1951
+ appendPattern(pattern);
1337
1952
  setQuick("");
1338
1953
  }
1339
1954
  function save() {
1340
1955
  if (state.busy || !dirty) return;
1341
- setState({ busy: true, message: "\u4FDD\u5B58\u4E2D\u2026", error: false });
1956
+ setState({ busy: true, message: t("exclude.msg.saving"), error: false });
1342
1957
  api("exclude-set", { path: file.path, content: draft }).then((res) => {
1343
1958
  if (res && res.ok) {
1344
1959
  setBaseline(draft);
1345
- setState({ busy: false, message: "\u5DF2\u4FDD\u5B58\uFF0C\u4E0B\u4E00\u6B21\u5FEB\u7167 / \u9884\u89C8 / \u56DE\u9000\u65F6\u751F\u6548", error: false });
1960
+ setState({ busy: false, message: t("exclude.msg.saved"), error: false });
1346
1961
  } else {
1347
- setState({ busy: false, message: res && (res.message || res.error) || "\u4FDD\u5B58\u5931\u8D25", error: true });
1962
+ setState({ busy: false, message: res && (res.message || res.error) || t("exclude.msg.saveFailed"), error: true });
1348
1963
  }
1349
1964
  }).catch((error) => {
1350
1965
  setState({ busy: false, message: String(error), error: true });
@@ -1367,13 +1982,13 @@
1367
1982
  React.createElement(
1368
1983
  "div",
1369
1984
  { className: "dsh-recall-ex-note" },
1370
- file.home ? "\u6B64\u914D\u7F6E\u5168\u5C40\u5171\u4EAB\uFF0C\u5BF9\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5FEB\u7167\u751F\u6548\u3002" : "home \u76EE\u5F55\u4E0D\u53EF\u5199\u65F6\u6B64\u5DE5\u4F5C\u533A\u964D\u7EA7\u5B58\u50A8\uFF0C\u6392\u9664\u914D\u7F6E\u72EC\u7ACB\u751F\u6548\u3002"
1985
+ file.home ? t("exclude.note.home") : t("exclude.note.fallback")
1371
1986
  ),
1372
- React.createElement("div", { className: "dsh-recall-ex-path" }, "\u5B58\u50A8\u4F4D\u7F6E\uFF1A" + file.path),
1373
- React.createElement("div", { className: "dsh-recall-ex-note" }, "gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF0C\u652F\u6301 # \u6CE8\u91CA\uFF1B\u547D\u4E2D\u9879\u4E0D\u8FDB\u5FEB\u7167\u3001\u4E0D\u88AB\u56DE\u9000\u89E6\u78B0\u3002"),
1987
+ React.createElement("div", { className: "dsh-recall-ex-path" }, t("exclude.path", { path: file.path })),
1988
+ React.createElement("div", { className: "dsh-recall-ex-note" }, t("exclude.syntax")),
1374
1989
  React.createElement("textarea", {
1375
1990
  className: "dsh-recall-ex-area",
1376
- "aria-label": "\u5FEB\u7167\u6392\u9664\u6A21\u5F0F\u5217\u8868\uFF08gitignore \u8BED\u6CD5\uFF0C\u4E00\u884C\u4E00\u6761\uFF09",
1991
+ "aria-label": t("exclude.area.aria"),
1377
1992
  value: draft,
1378
1993
  spellCheck: false,
1379
1994
  onChange: (e) => setDraft(e.target.value)
@@ -1384,8 +1999,8 @@
1384
1999
  React.createElement("input", {
1385
2000
  className: "dsh-recall-ex-input",
1386
2001
  value: quick,
1387
- placeholder: "\u8F93\u5165\u8DEF\u5F84\u6216\u6A21\u5F0F\uFF0C\u56DE\u8F66\u5FEB\u901F\u6DFB\u52A0",
1388
- "aria-label": "\u5FEB\u901F\u6DFB\u52A0\u6392\u9664\u6A21\u5F0F",
2002
+ placeholder: t("exclude.quick.placeholder"),
2003
+ "aria-label": t("exclude.quick.aria"),
1389
2004
  onChange: (e) => setQuick(e.target.value),
1390
2005
  onKeyDown: (e) => {
1391
2006
  if (e.key === "Enter") {
@@ -1394,23 +2009,23 @@
1394
2009
  }
1395
2010
  }
1396
2011
  }),
1397
- React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: addQuick }, "\u6DFB\u52A0"),
2012
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: addQuick }, t("exclude.add")),
1398
2013
  ...suggestions.map((s) => React.createElement("button", {
1399
2014
  key: "chip-" + s,
1400
2015
  type: "button",
1401
2016
  className: "dsh-recall-ex-chip",
1402
- title: "\u70B9\u51FB\u8FFD\u52A0 " + s,
2017
+ title: t("exclude.chip.title", { pattern: s }),
1403
2018
  onClick: () => appendPattern(s)
1404
2019
  }, s))
1405
2020
  ),
1406
2021
  React.createElement(
1407
2022
  "div",
1408
2023
  { className: "dsh-recall-panel-actions" },
1409
- state.message ? React.createElement("span", { role: "status", "aria-live": "polite", className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, (state.error ? "\u9519\u8BEF\uFF1A" : "") + state.message) : null,
1410
- React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: !dirty || state.busy, onClick: discard }, "\u653E\u5F03\u4FEE\u6539"),
2024
+ state.message ? React.createElement("span", { role: "status", "aria-live": "polite", className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, (state.error ? t("common.errorPrefix") : "") + state.message) : null,
2025
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: !dirty || state.busy, onClick: discard }, t("common.discard")),
1411
2026
  // 保存升主色实心(与配置表单同一约定:每卡唯一主动作,对齐官方插件卡
1412
2027
  // footer 的 discard 幽灵 + save 实心组合)
1413
- React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-primary", disabled: !dirty || state.busy, onClick: save }, "\u4FDD\u5B58")
2028
+ React.createElement("button", { type: "button", className: "dsh-recall-btn dsh-recall-btn-primary", disabled: !dirty || state.busy, onClick: save }, t("common.save"))
1414
2029
  )
1415
2030
  );
1416
2031
  }
@@ -1425,10 +2040,10 @@
1425
2040
  return;
1426
2041
  }
1427
2042
  if (res && res.unsupported) {
1428
- setError("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u5FEB\u7167\u529F\u80FD\uFF0C\u6392\u9664\u914D\u7F6E\u4E0D\u53EF\u7528\u3002");
2043
+ setError(t("exclude.unsupported"));
1429
2044
  return;
1430
2045
  }
1431
- setError(res && (res.message || res.error) || "\u65E0\u6CD5\u8BFB\u53D6\u6392\u9664\u914D\u7F6E");
2046
+ setError(res && (res.message || res.error) || t("exclude.loadFailed"));
1432
2047
  }).catch((e) => setError(String(e)));
1433
2048
  }
1434
2049
  React.useEffect(() => {
@@ -1442,15 +2057,15 @@
1442
2057
  React.createElement(
1443
2058
  "div",
1444
2059
  { className: "dsh-recall-panel-actions" },
1445
- React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: load }, "\u91CD\u8BD5")
2060
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", onClick: load }, t("common.retry"))
1446
2061
  )
1447
2062
  );
1448
2063
  }
1449
2064
  if (files === null) {
1450
- return React.createElement("div", { className: "dsh-recall-ex-note" }, "\u6B63\u5728\u52A0\u8F7D\u6392\u9664\u914D\u7F6E\u2026");
2065
+ return React.createElement("div", { className: "dsh-recall-ex-note" }, t("exclude.loading"));
1451
2066
  }
1452
2067
  if (!files.length) {
1453
- return React.createElement("div", { className: "dsh-recall-ex-note" }, "\u5C1A\u672A\u521B\u5EFA\u4EFB\u4F55\u5FEB\u7167\u5B58\u50A8\uFF1A\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u53EF\u7F16\u8F91\u7684\u6392\u9664\u914D\u7F6E\u3002");
2068
+ return React.createElement("div", { className: "dsh-recall-ex-note" }, t("exclude.empty"));
1454
2069
  }
1455
2070
  return React.createElement(
1456
2071
  "div",
@@ -1502,7 +2117,7 @@
1502
2117
  return result;
1503
2118
  }
1504
2119
  function buildSnapshotManager(React, util, sessionsSvc, workspacesSvc, uiWorkspaceSvc) {
1505
- const { api, clockText: clockText2, sizeText: sizeText2, buildTree: buildTree2 } = util;
2120
+ const { api, clockText: clockText2, sizeText: sizeText2, buildTree: buildTree2, t } = util;
1506
2121
  function DeleteButton(props) {
1507
2122
  return React.createElement("button", {
1508
2123
  type: "button",
@@ -1637,16 +2252,16 @@
1637
2252
  api("status", { op: "clear" }).catch(() => {
1638
2253
  });
1639
2254
  }
1640
- function run(op, extra, doneText) {
2255
+ function run(op, extra, doneKey) {
1641
2256
  if (state.busy) return;
1642
- setState({ busy: true, message: "\u6267\u884C\u4E2D\u2026", error: false });
2257
+ setState({ busy: true, message: t("manage.busy"), error: false });
1643
2258
  api("manage", Object.assign({ op }, extra || {})).then((res) => {
1644
2259
  if (res && res.ok) {
1645
2260
  const deleted = res.deleted;
1646
- setState({ busy: false, message: typeof deleted === "number" ? "\u5DF2\u5220\u9664 " + deleted + " \u6761\u5FEB\u7167" : doneText, error: false });
2261
+ setState({ busy: false, message: typeof deleted === "number" ? t("manage.deletedCount", { deleted }) : t(doneKey), error: false });
1647
2262
  refresh();
1648
2263
  } else {
1649
- setState({ busy: false, message: res && (res.message || res.error) || "\u64CD\u4F5C\u5931\u8D25", error: true });
2264
+ setState({ busy: false, message: res && (res.message || res.error) || t("common.opFailed"), error: true });
1650
2265
  }
1651
2266
  }).catch((e) => setState({ busy: false, message: String(e), error: true }));
1652
2267
  }
@@ -1657,17 +2272,17 @@
1657
2272
  "div",
1658
2273
  { className: "dsh-recall-tree-confirm" },
1659
2274
  props.text,
1660
- React.createElement("button", { type: "button", className: "dsh-recall-ex-chip dsh-recall-ex-chip-danger", onClick: props.onConfirm }, "\u786E\u8BA4"),
1661
- React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: props.onCancel }, "\u53D6\u6D88")
2275
+ React.createElement("button", { type: "button", className: "dsh-recall-ex-chip dsh-recall-ex-chip-danger", onClick: props.onConfirm }, t("common.confirm")),
2276
+ React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: props.onCancel }, t("common.cancel"))
1662
2277
  );
1663
2278
  }
1664
2279
  function renderDeleteAllConfirm() {
1665
2280
  if (!confirming || confirming.kind !== "all") return null;
1666
2281
  return React.createElement(ConfirmRow, {
1667
- text: "\u786E\u8BA4\u5220\u9664\u6240\u6709\u5DE5\u4F5C\u533A\u7684\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002",
2282
+ text: t("manage.confirm.all"),
1668
2283
  onConfirm: () => {
1669
2284
  setConfirming(null);
1670
- run("deleteAll", {}, "\u5DF2\u6E05\u7A7A\u5168\u90E8\u5FEB\u7167");
2285
+ run("deleteAll", {}, "manage.done.deleteAll");
1671
2286
  },
1672
2287
  onCancel: () => setConfirming(null)
1673
2288
  });
@@ -1684,7 +2299,7 @@
1684
2299
  const filteredItems = q ? (items || []).filter(
1685
2300
  (it) => (it.workspace || "").toLowerCase().indexOf(q) >= 0 || (it.sessionTitle || "").toLowerCase().indexOf(q) >= 0 || (it.messageText || "").toLowerCase().indexOf(q) >= 0 || String(it.id || "").toLowerCase().indexOf(q) >= 0
1686
2301
  ) : items;
1687
- const tree = buildTree2(filteredItems);
2302
+ const tree = buildTree2(filteredItems, t);
1688
2303
  const allSessionIds = Array.from(new Set((items || []).map((it) => it.sessionId).filter(Boolean)));
1689
2304
  const versionMap = groupByLineage(allSessionIds, lineage);
1690
2305
  let listById = null;
@@ -1703,17 +2318,17 @@
1703
2318
  } catch (e) {
1704
2319
  archivedIds = null;
1705
2320
  }
1706
- function confirmDelete(kind, key, extra, text) {
1707
- setConfirming({ kind, key, extra, text });
2321
+ function confirmDelete(kind, key, extra, textKey) {
2322
+ setConfirming({ kind, key, extra, text: textKey });
1708
2323
  }
1709
- function renderConfirm(kind, key, extra, text) {
2324
+ function renderConfirm(kind, key, extra, textKey) {
1710
2325
  if (!confirming || confirming.kind !== kind || confirming.key !== key) return null;
1711
2326
  return React.createElement(ConfirmRow, {
1712
- text,
2327
+ text: t(textKey),
1713
2328
  onConfirm: () => {
1714
2329
  const c = confirming;
1715
2330
  setConfirming(null);
1716
- run("delete", c.extra, "\u5DF2\u5220\u9664");
2331
+ run("delete", c.extra, "manage.done.delete");
1717
2332
  },
1718
2333
  onCancel: () => setConfirming(null)
1719
2334
  });
@@ -1737,18 +2352,18 @@
1737
2352
  // 删除紧贴本条消息摘要(而非行尾):按行扫读时动作落在「看的那一行」,
1738
2353
  // 不必横跳到右侧同列按钮再回读行号
1739
2354
  React.createElement(DeleteButton, {
1740
- title: "\u5220\u9664\u8BE5\u5FEB\u7167\uFF08tag \u4E0E\u7D22\u5F15\u6761\u76EE\uFF09",
1741
- onClick: () => confirmDelete("snapshot", key, { messageId: it.id, root: it.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
2355
+ title: t("tree.delete.snapshot.title"),
2356
+ onClick: () => confirmDelete("snapshot", key, { messageId: it.id, root: it.root || null }, "tree.delete.snapshot.confirm")
1742
2357
  })
1743
2358
  )
1744
2359
  ),
1745
- renderConfirm("snapshot", key, { messageId: it.id, root: it.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
2360
+ renderConfirm("snapshot", key, { messageId: it.id, root: it.root || null }, "tree.delete.snapshot.confirm")
1746
2361
  );
1747
2362
  }
1748
2363
  function renderSession(s) {
1749
2364
  const key = "session-" + (s.root || "") + "-" + s.sessionId;
1750
2365
  const open = expanded.has(key);
1751
- const label = s.title || (titlesPending && s.sessionId ? "\u2026" : "\uFF08\u5DF2\u5220\u9664\u4F1A\u8BDD\uFF09");
2366
+ const label = s.title || (titlesPending && s.sessionId ? "\u2026" : t("tree.deletedSession"));
1752
2367
  const version = s.sessionId ? versionMap.get(String(s.sessionId)) : null;
1753
2368
  const switchable = Boolean(s.sessionId && listById && listById[s.sessionId] && !(archivedIds && archivedIds.has(String(s.sessionId))));
1754
2369
  return React.createElement(
@@ -1766,7 +2381,7 @@
1766
2381
  type: "button",
1767
2382
  className: "dsh-recall-tree-toggle",
1768
2383
  "aria-expanded": open,
1769
- "aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + "\uFF1A" + label,
2384
+ "aria-label": t(open ? "tree.collapse" : "tree.expand", { label }),
1770
2385
  onClick: (e) => {
1771
2386
  e.stopPropagation();
1772
2387
  toggle(key);
@@ -1776,11 +2391,11 @@
1776
2391
  "span",
1777
2392
  { className: "dsh-recall-tree-label", title: s.sessionId || "" },
1778
2393
  React.createElement("span", { className: "dsh-recall-tree-title" }, label),
1779
- version ? React.createElement("span", { className: "dsh-recall-tree-meta", title: "\u7248\u672C\u5BB6\u65CF\uFF1A" + version.family.join(" \u2192 ") }, "v" + version.index + "/" + version.family.length) : null,
1780
- React.createElement("span", { className: "dsh-recall-tree-meta" }, s.items.length + " \u6761"),
2394
+ version ? React.createElement("span", { className: "dsh-recall-tree-meta", title: t("tree.family.title", { chain: version.family.join(" \u2192 ") }) }, "v" + version.index + "/" + version.family.length) : null,
2395
+ React.createElement("span", { className: "dsh-recall-tree-meta" }, t("tree.snapCount", { n: s.items.length })),
1781
2396
  s.sessionId ? React.createElement(DeleteButton, {
1782
- title: "\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167",
1783
- onClick: () => confirmDelete("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
2397
+ title: t("tree.delete.session.title"),
2398
+ onClick: () => confirmDelete("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "tree.delete.session.confirm")
1784
2399
  }) : null
1785
2400
  ),
1786
2401
  // 「切换」留在行尾单占右侧:它是导航动作(离开当前视图),与行内
@@ -1788,7 +2403,7 @@
1788
2403
  switchable ? React.createElement("button", {
1789
2404
  type: "button",
1790
2405
  className: "dsh-recall-ex-chip",
1791
- title: "\u5207\u6362\u5230\u8BE5\u7248\u672C\u4F1A\u8BDD",
2406
+ title: t("tree.switch.title"),
1792
2407
  onClick: (ev) => {
1793
2408
  ev.stopPropagation();
1794
2409
  try {
@@ -1797,10 +2412,10 @@
1797
2412
  } catch (e) {
1798
2413
  }
1799
2414
  }
1800
- }, "\u5207\u6362") : null
2415
+ }, t("tree.switch")) : null
1801
2416
  ),
1802
2417
  open ? React.createElement("div", { className: "dsh-recall-tree-children" }, ...s.items.map(renderLeaf)) : null,
1803
- s.sessionId ? renderConfirm("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "\u786E\u8BA4\u5220\u9664\u8BE5\u4F1A\u8BDD\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002") : null
2418
+ s.sessionId ? renderConfirm("session", key, { scope: "session", sessionId: s.sessionId, root: s.root || null }, "tree.delete.session.confirm") : null
1804
2419
  );
1805
2420
  }
1806
2421
  function renderWorkspace(ws) {
@@ -1820,7 +2435,7 @@
1820
2435
  type: "button",
1821
2436
  className: "dsh-recall-tree-toggle",
1822
2437
  "aria-expanded": open,
1823
- "aria-label": (open ? "\u6536\u8D77" : "\u5C55\u5F00") + "\uFF1A" + ws.name,
2438
+ "aria-label": t(open ? "tree.collapse" : "tree.expand", { label: ws.name }),
1824
2439
  onClick: (e) => {
1825
2440
  e.stopPropagation();
1826
2441
  toggle(key);
@@ -1830,20 +2445,26 @@
1830
2445
  "span",
1831
2446
  { className: "dsh-recall-tree-label", title: ws.root || "" },
1832
2447
  React.createElement("span", { className: "dsh-recall-tree-name" }, ws.name),
1833
- React.createElement("span", { className: "dsh-recall-tree-meta" }, sessionCount + " \u4F1A\u8BDD / " + snapCount + " \u5FEB\u7167"),
2448
+ React.createElement("span", { className: "dsh-recall-tree-meta" }, t("tree.wsCount", { n: sessionCount, m: snapCount })),
1834
2449
  ws.root ? React.createElement(DeleteButton, {
1835
- title: "\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167",
1836
- onClick: () => confirmDelete("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002")
2450
+ title: t("tree.delete.workspace.title"),
2451
+ onClick: () => confirmDelete("workspace", key, { scope: "workspace", root: ws.root }, "tree.delete.workspace.confirm")
1837
2452
  }) : null
1838
2453
  )
1839
2454
  ),
1840
2455
  open ? React.createElement("div", { className: "dsh-recall-tree-children" }, ...ws.sessions.map(renderSession)) : null,
1841
- ws.root ? renderConfirm("workspace", key, { scope: "workspace", root: ws.root }, "\u786E\u8BA4\u5220\u9664\u8BE5\u5DE5\u4F5C\u533A\u5168\u90E8\u5FEB\u7167\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002") : null
2456
+ ws.root ? renderConfirm("workspace", key, { scope: "workspace", root: ws.root }, "tree.delete.workspace.confirm") : null
1842
2457
  );
1843
2458
  }
1844
2459
  const treeNodes = tree.map(renderWorkspace);
1845
2460
  const loaded = items ? items.length : null;
1846
- const countText = loaded === null ? "\u5171 \u2026 \u6761\u5FEB\u7167" : "\u5171 " + total + " \u6761\u5FEB\u7167" + (limit < total ? "\uFF08\u5F53\u524D\u663E\u793A\u6700\u65B0 " + loaded + " \u6761\uFF09" : "");
2461
+ const countText = loaded === null ? t("manage.countLoading") : t("manage.countLoaded", { total }) + (limit < total ? t("manage.countShown", { shown: loaded }) : "");
2462
+ function errorLine(e, key) {
2463
+ const kindKey = e.kind ? "errorKind." + e.kind : "";
2464
+ const localized = kindKey !== "" && hasTranslation(kindKey);
2465
+ const base = localized ? t(kindKey) + (typeof e.count === "number" && e.count > 1 ? t("manage.errors.dup", { n: e.count }) : "") : String(e.hint || e.message || "");
2466
+ return React.createElement("div", { className: "dsh-recall-ex-note", key, title: e.message || "" }, clockText2(e.time) + " " + base);
2467
+ }
1847
2468
  function loadMore() {
1848
2469
  const next = Math.min(Math.max(total, limit), 2e3);
1849
2470
  if (next <= limit) return;
@@ -1859,7 +2480,7 @@
1859
2480
  React.createElement(
1860
2481
  "div",
1861
2482
  { className: "dsh-recall-ex-note" },
1862
- usage === null ? countText + "\u3002" : countText + "\uFF0C\u5168\u90E8\u5DE5\u4F5C\u533A\u5FEB\u7167\u5B58\u50A8\u5360\u7528 " + sizeText2(usage) + "\u3002"
2483
+ usage === null ? countText + t("manage.countEnd") : countText + t("manage.usageSuffix", { size: sizeText2(usage) })
1863
2484
  ),
1864
2485
  // V6 健康行徽章化:git 状态用彩色 pill(成功/失败,官方状态行配对),
1865
2486
  // 从普通 note 提升为卡片顶部横幅(渲染在搜索框与树之前);存储计数
@@ -1869,9 +2490,9 @@
1869
2490
  { className: "dsh-recall-ex-note", key: "health" },
1870
2491
  React.createElement("span", {
1871
2492
  className: "dsh-recall-health-pill " + (health.gitAvailable ? "dsh-recall-health-pill-ok" : "dsh-recall-health-pill-bad"),
1872
- title: "\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git"
1873
- }, health.gitAvailable ? "git \u53EF\u7528" : "git \u4E0D\u53EF\u7528\uFF08\u5FEB\u7167\u5F15\u64CE\u4F9D\u8D56 git\uFF09"),
1874
- " \xB7 \u5FEB\u7167\u5B58\u50A8\uFF1Ahome " + health.homeStores + " \u4E2A\u5DE5\u4F5C\u533A" + (health.fallbackStores ? "\uFF0C\u964D\u7EA7 " + health.fallbackStores + " \u4E2A" : "")
2493
+ title: t("manage.health.gitTitle")
2494
+ }, t(health.gitAvailable ? "manage.health.gitOk" : "manage.health.gitBad")),
2495
+ t("manage.health.stores", { n: health.homeStores }) + (health.fallbackStores ? t("manage.health.storesFallback", { n: health.fallbackStores }) : "")
1875
2496
  ) : null,
1876
2497
  // 搜索行:图标绝对定位在框内左侧(pointer-events:none 不挡点击),输入框
1877
2498
  // 靠 padding-left 让出图标位;高度由 CSS 的 .dsh-recall-search 覆写加高 20%
@@ -1898,8 +2519,8 @@
1898
2519
  ),
1899
2520
  React.createElement("input", {
1900
2521
  className: "dsh-recall-ex-input",
1901
- placeholder: "\u641C\u7D22\u5DE5\u4F5C\u533A / \u4F1A\u8BDD\u6807\u9898 / \u6D88\u606F\u5185\u5BB9 / ID",
1902
- "aria-label": "\u641C\u7D22\u5FEB\u7167",
2522
+ placeholder: t("manage.search.placeholder"),
2523
+ "aria-label": t("manage.search.aria"),
1903
2524
  value: query,
1904
2525
  spellCheck: false,
1905
2526
  onChange: (e) => setQuery(e.target.value)
@@ -1913,41 +2534,41 @@
1913
2534
  ...[1, 2, 3, 4, 5].map((n) => React.createElement("div", { key: "sk-" + n, className: "dsh-recall-tree-skeleton-row" }))
1914
2535
  ) : null,
1915
2536
  treeNodes.length > 0 ? React.createElement("div", { className: "dsh-recall-tree" }, ...treeNodes) : null,
1916
- items && items.length === 0 && !q ? React.createElement("div", { className: "dsh-recall-empty", key: "empty" }, "\u5728\u4EFB\u610F\u5DE5\u4F5C\u533A\u53D1\u9001\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5FEB\u7167\u3002") : null,
1917
- q && filteredItems && filteredItems.length === 0 ? React.createElement("div", { className: "dsh-recall-empty", key: "no-match" }, "\u65E0\u5339\u914D\u5FEB\u7167") : null,
2537
+ items && items.length === 0 && !q ? React.createElement("div", { className: "dsh-recall-empty", key: "empty" }, t("manage.empty")) : null,
2538
+ q && filteredItems && filteredItems.length === 0 ? React.createElement("div", { className: "dsh-recall-empty", key: "no-match" }, t("manage.emptyFiltered")) : null,
1918
2539
  renderDeleteAllConfirm(),
1919
2540
  // V6:错误区从卡片最底上移到操作区上方(fail-loud 可见性);标题
1920
2541
  // error 色带条数徽章,时间戳格式维持原样
1921
2542
  errors && errors.length > 0 ? React.createElement(
1922
2543
  "div",
1923
2544
  { key: "errors" },
1924
- React.createElement("div", { className: "dsh-recall-errors-title" }, "\u6700\u8FD1\u9519\u8BEF (" + errors.length + ")"),
1925
- (showAllErrors ? errors : errors.slice(0, 5)).map((e, i) => React.createElement("div", { key: i, className: "dsh-recall-ex-note" }, clockText2(e.time) + " " + e.message)),
2545
+ React.createElement("div", { className: "dsh-recall-errors-title" }, t("manage.errors.title", { n: errors.length })),
2546
+ (showAllErrors ? errors : errors.slice(0, 5)).map(errorLine),
1926
2547
  React.createElement(
1927
2548
  "div",
1928
2549
  { className: "dsh-recall-panel-actions" },
1929
- errors.length > 5 ? React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: () => setShowAllErrors((v) => !v) }, showAllErrors ? "\u6536\u8D77" : "\u5C55\u5F00\u5168\u90E8 (" + errors.length + ")") : null,
1930
- React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: clearErrors }, "\u6E05\u7A7A")
2550
+ errors.length > 5 ? React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: () => setShowAllErrors((v) => !v) }, showAllErrors ? t("manage.errors.collapse") : t("manage.errors.expand", { n: errors.length })) : null,
2551
+ React.createElement("button", { type: "button", className: "dsh-recall-ex-chip", onClick: clearErrors }, t("manage.errors.clear"))
1931
2552
  )
1932
2553
  ) : null,
1933
2554
  React.createElement(
1934
2555
  "div",
1935
2556
  { className: "dsh-recall-panel-actions" },
1936
- state.message ? React.createElement("span", { role: "status", "aria-live": "polite", className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, (state.error ? "\u9519\u8BEF\uFF1A" : "") + state.message) : null,
2557
+ state.message ? React.createElement("span", { role: "status", "aria-live": "polite", className: "dsh-recall-ex-status" + (state.error ? " dsh-recall-ex-status-error" : " dsh-recall-ex-status-success") }, (state.error ? t("common.errorPrefix") : "") + state.message) : null,
1937
2558
  limit < total ? React.createElement("button", {
1938
2559
  type: "button",
1939
2560
  className: "dsh-recall-btn",
1940
2561
  disabled: state.busy,
1941
2562
  onClick: loadMore
1942
- }, "\u52A0\u8F7D\u66F4\u591A") : null,
1943
- React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy, onClick: refresh }, "\u5237\u65B0"),
2563
+ }, t("manage.loadMore")) : null,
2564
+ React.createElement("button", { type: "button", className: "dsh-recall-btn", disabled: state.busy, onClick: () => refresh() }, t("manage.refresh")),
1944
2565
  React.createElement("button", {
1945
2566
  type: "button",
1946
2567
  className: "dsh-recall-btn",
1947
2568
  disabled: state.busy,
1948
- title: "\u7ACB\u5373\u5BF9\u5168\u90E8\u5DE5\u4F5C\u533A\u6267\u884C\u4E00\u6B21 git gc\uFF08\u538B\u7F29\u5BF9\u8C61\u5E93\u91CA\u653E\u7A7A\u95F4\uFF09",
1949
- onClick: () => run("gc", {}, "gc \u5B8C\u6210")
1950
- }, "\u7ACB\u5373 gc"),
2569
+ title: t("manage.gc.title"),
2570
+ onClick: () => run("gc", {}, "manage.done.gc")
2571
+ }, t("manage.gc")),
1951
2572
  // V5:全部删除固定为操作区最后一个按钮(排在立即 gc 之后)——即使
1952
2573
  // 「加载更多」出现/消失也不漂移;danger 与普通按钮间在 panel-actions
1953
2574
  // 统一 gap:8px 之上再加 btn-gap 的 8px 物理间隔,危险按钮与常规按钮
@@ -1956,9 +2577,9 @@
1956
2577
  type: "button",
1957
2578
  className: "dsh-recall-btn dsh-recall-btn-danger dsh-recall-btn-gap",
1958
2579
  disabled: state.busy,
1959
- title: "\u5220\u9664\u5168\u90E8\u5DE5\u4F5C\u533A\u7684\u6240\u6709\u5FEB\u7167\uFF1B\u4F1A\u76F4\u63A5\u6838\u5BF9\u5E76\u5220\u9664 git tag\uFF08\u5373\u4F7F\u5217\u8868\u4E3A\u7A7A\u4E5F\u53EF\u6E05\u7406\u6B8B\u7559\uFF09",
2580
+ title: t("manage.deleteAll.title"),
1960
2581
  onClick: () => setConfirming({ kind: "all" })
1961
- }, "\u5168\u90E8\u5220\u9664")
2582
+ }, t("manage.deleteAll"))
1962
2583
  )
1963
2584
  );
1964
2585
  }
@@ -2000,8 +2621,11 @@
2000
2621
  const { ConfigForm } = buildConfigForm(React, util, SectionToggle);
2001
2622
  const { ExcludeFilesSection } = buildExcludeCards(React, util);
2002
2623
  const { ManageCard } = buildSnapshotManager(React, util, sessionsSvc, workspacesSvc, uiWorkspaceSvc);
2624
+ const { t } = util;
2003
2625
  function RecallSettingsCard() {
2004
2626
  const [sections, setSections] = React.useState({ exclude: false, manage: false });
2627
+ const [, setLocaleTick] = React.useState(0);
2628
+ const notifyLocale = React.useCallback(() => setLocaleTick((n) => n + 1), []);
2005
2629
  function toggle(key) {
2006
2630
  setSections((prev) => Object.assign({}, prev, { [key]: !prev[key] }));
2007
2631
  }
@@ -2011,14 +2635,14 @@
2011
2635
  React.createElement(
2012
2636
  "div",
2013
2637
  { className: "dsh-recall-settings-body" },
2014
- React.createElement(ConfigForm),
2015
- React.createElement(SectionToggle, { title: "\u6392\u9664\u914D\u7F6E\uFF08exclude.txt\uFF09", open: sections.exclude, onToggle: () => toggle("exclude") }),
2638
+ React.createElement(ConfigForm, { onLocaleApplied: notifyLocale }),
2639
+ React.createElement(SectionToggle, { title: t("section.exclude"), open: sections.exclude, onToggle: () => toggle("exclude") }),
2016
2640
  // section-body 只做纵向间距与展开入场动画,不带描边/底色:外层卡片框
2017
2641
  // 去掉后内容区再套一层框等于把「去包裹」加回来(用户实测反馈)。「高级:
2018
2642
  // 基础排除表」不包——它在 cfg-grid 内本就走通栏,且 label 列对齐依赖
2019
2643
  // grid item 身份,包容器会破坏跨行对齐。
2020
2644
  sections.exclude ? React.createElement("div", { className: "dsh-recall-section-body" }, React.createElement(ExcludeFilesSection)) : null,
2021
- React.createElement(SectionToggle, { title: "\u5FEB\u7167\u7BA1\u7406", open: sections.manage, onToggle: () => toggle("manage") }),
2645
+ React.createElement(SectionToggle, { title: t("section.manage"), open: sections.manage, onToggle: () => toggle("manage") }),
2022
2646
  sections.manage ? React.createElement("div", { className: "dsh-recall-section-body" }, React.createElement(ManageCard)) : null
2023
2647
  )
2024
2648
  );
@@ -2066,7 +2690,7 @@
2066
2690
  );
2067
2691
  });
2068
2692
  } catch (error) {
2069
- console.error("[dsh-recall-plugin] slot register failed (" + slotKey + "):", error);
2693
+ appLog.error("slot register failed (" + slotKey + "):", error);
2070
2694
  }
2071
2695
  }
2072
2696
  try {
@@ -2075,7 +2699,7 @@
2075
2699
  RecallSettingsCard
2076
2700
  ));
2077
2701
  } catch (error) {
2078
- console.error("[dsh-recall-plugin] settings card register failed:", error);
2702
+ appLog.error("settings card register failed:", error);
2079
2703
  }
2080
2704
  try {
2081
2705
  slots.inject("plugins.bundle.config", () => slots.register(
@@ -2083,8 +2707,9 @@
2083
2707
  RecallSettingsCard
2084
2708
  ));
2085
2709
  } catch (error) {
2086
- console.error("[dsh-recall-plugin] plugin manager config register failed:", error);
2710
+ appLog.error("plugin manager config register failed:", error);
2087
2711
  }
2712
+ appLog.info("client applied");
2088
2713
  };
2089
2714
  }
2090
2715