# 形态候选一律翻译，不做逐条判断

渠道三（形态候选）从「文件名像面板模块」的文件里按形态挑字面量。原先提示词让模型逐条判断「这条是面板
描述吗？不是就跳过」。

**判断是个永久税**：判为「跳过」的条目不会写进包，下一轮扫描会报出同一批条目，于是每轮 `/localize`
都要为同一批字符串付一次模型往返。实测本机 80 条，永远收不了尾；`node scan.mjs` 也就永远显示
「另有 80 条特征候选待判断」。

## 为什么不靠更好的脚本判断

「只挑描述」需要知道一条字符串会不会被当作 `description` 字段应用。没法用语法判断：

- 源码里 `description:` 出现 705 次，其中 605 次是 MCP 工具的 JSON Schema 参数说明、快捷键动作说明、
  命令行选项说明 —— 都不是面板描述。
- 80 条形态候选里，`description:` 字段命中 **0**、`label:` 字段命中 **0**。它们全是直接塞进渲染调用的
  标题 / 空状态 / 状态标签 / 键位提示。
- 因此「按字段名匹配」既过宽（捞进 605 条无关项）又过窄（漏掉 80 条渲染位字符串）。

## 为什么全翻不亏

`DESCRIPTIONS` 只有两个应用点，都只改 `description` 字段。查不到的条目就是**惰性条目**：不改变任何
渲染结果。所以「多翻 80 条」的代价是一次性的 80 条 token，换来的是每轮少一次模型往返，以及覆盖率
从「永远有 80 条待判断」变成「确定缺口 0」。

## 行标题是唯一的硬例外

`label:` 字段与 `*_LABELS` 映射的值是查找键，永不翻译（硬规则 2）。两个地方都做这件事，而且共用一份判据
（`lib/titles.mjs`）：

1. `lib/plugin-scan.mjs` 在产出形态候选**之前**先过滤 —— 字面量下限从 15 放宽到 6 后，`Always trust`、
   `Never trust` 这类短标题本来会涌进候选。
2. `test/pack-guards.mjs` 事后验一遍。它是确定性兜底，不依赖模型，也不是「生成时的辅助」。

判据是**位置**，不是内容：同一字符串在 A 文件是 `label:` 值、在 B 文件是 `description:` 值，仍算文案。
若真有标题漏进候选，修的是 `lib/plugin-scan.mjs`，不是提示词。

## 顺带修的两个渠道 bug

- **字面量下限 15 → 6。** `THINKING_DESCRIPTIONS = { off: "No reasoning", … }` 只有 12 字符，永远进不了候选。
- **`LITERAL_RE` 分不清开/闭引号。** `if (mode === "none") return "No change indicators…";` 里，短字面量
  `"none"` 的**闭**引号被当成下一段的开引号，后面那条真文案整条消失。下限越小越容易踩。加了
  `(?<![A-Za-z0-9_])` 前导断言后，本机候选从 28 变 30（找回两条）。回归在 `test/shape.mjs`。

## 代价

- 包变大（本机约 +80 条惰性条目），运行时无影响。
- 失去模型的语感过滤：错误信息、日志行若被形态渠道捞到，也会被翻。`looksLikeCopy` 和
  `throw`/`Error(`/`console.` 前置过滤已挡掉大部分，残余是死重量。
- `scan.mjs` 输出的「另有 N 条特征候选待判断」措辞从此过时（该文件冻结，未改）。
