# Translator

[English](README.md) | 简体中文

[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE)
[![npm](https://img.shields.io/npm/v/@orbit-labs-ai/translator?logo=npm&label=npm)](https://www.npmjs.com/package/@orbit-labs-ai/translator)
[![Node.js](https://img.shields.io/badge/Node.js-%3E%3D22.19-339933?logo=node.js&logoColor=white)](https://nodejs.org/)
[![Integrations](https://img.shields.io/badge/integrations-Pi%20%7C%20Claude%20Code-8A2BE2)](#环境要求)

**用中文写提示词，向模型发送英文；需要时，再把回答翻译成中文阅读。**

Translator 是面向 **Pi** 编程 agent 和 **Claude Code** 的翻译层。提交含中文正文的提示词时，它会通过一个独立、隔离的请求把提示词译成英文，再提交英文版本。开启回答翻译后，它还会把每个最终回答译成中文，显示在原回答之后。模型会收到译后的提示词；原始提示词和中文回答译文仅供显示，不进入模型上下文。

它是 [Orbit Tools](../../README.zh-CN.md) 的一部分。

## 目录

- [功能介绍](#功能介绍)
- [环境要求](#环境要求)
- [快速开始](#快速开始)
- [使用方法](#使用方法)
- [翻译机制](#翻译机制)
- [配置](#配置)
- [数据与隐私](#数据与隐私)
- [安装详解](#安装详解)
- [命令行工具与其他宿主](#命令行工具与其他宿主)
- [故障排查](#故障排查)
- [已知限制](#已知限制)
- [卸载](#卸载)
- [开发与测试](#开发与测试)
- [延伸阅读](#延伸阅读)

## 功能介绍

例如，输入下面第一行（要求 agent 检查 `src/app.ts` 中的空指针问题，并且不要改动测试）时，模型收到的内容大致如第二行：

```text
你输入：        请检查 `src/app.ts` 中的空指针问题，不要修改测试。
模型收到：      Please check the null-pointer issue in `src/app.ts`; do not modify the tests.
```

**翻译**

- **中文输入，英文发送（Input）。** 含中文正文的提示词由一个单独的请求翻译，该请求只能看到这条提示词，随后发送的是译文。预检先排除确定的字面量和名称；若不再剩余中文正文（包括纯字面量输入），提示词直接放行，不调用翻译模型，也不产生翻译记录。引号内的中文正文仍可翻译。
- **以中文阅读回答（Output）。** 可选：把每个最终回答再译成中文供你阅读。预检跳过没有英文正文需要翻译的回答，包括纯字面量回答；普通的 `OK` 仍会翻译。模型永远看不到这份译文。
- **语言要求（Instruction）。** 可选：在译文后附加要求，让模型用英文推理、用简体中文作答。
- **忠实、最小改动。** 翻译器只修正明显的错别字和语法错误，不改写、不作答、不总结、不扩写。
- **复用。** 再次提交完全相同的提示词时，直接复用之前的译文，不调用模型。

**精准**

- **共享预检遮蔽。** 两个方向都由同一个扫描器在翻译前识别确定的代码、公式、URL、邮箱、文件名（包括未加引号的中文文件名）、路径、类字面量标识符 / 设置 / 提及，以及 Markdown 链接目标。这些区间被遮蔽后逐字节恢复；有歧义的正文仍交给翻译器。
- **结构安全，而非语言审核。** 回复必须完成且非空，每个占位符必须恰好归还一次，受保护的输入代码块保持顺序。每次新执行的输入翻译或每个需要翻译的输出分段只进行一次 completion，不做 AI 审核或修复调用。翻译结果不会再次接受语言或语义质量检查。
- **请求精简。** 占位符很短，代码也不会发给翻译器，翻译请求因此保持低成本。

**安全**

- **失败时保守处理。** 无法翻译的提示词不会被发送：既不发原文，也不发不完整的译文。回答翻译失败时，原回答保持不变。
- **上下文互不混淆。** 原始提示词和回答译文是仅供显示的副本。模型上下文中保留实际提交的提示词和原回答，不包含这些显示记录。
- **随时取消。** Pi：Ctrl+Alt+C；Claude Code：翻译进行中按 4。取消不会发送任何内容，也不会改变任何设置。

**设置**

- **一个命令。** `/translator` 打开原生设置面板，包含 Input、Instruction、Output、Model 和 Thinking；`/translator output on` 等子命令可直接设置每一项。
- **只作用于翻译器的模型与思考档位。** 翻译默认跟随会话模型，并使用自己的思考档位（`medium`）；也可以固定其他模型。两者都不影响主对话。
- **按会话生效，另有默认值。** 设置作用于当前 Pi 分支或 Claude Code 会话；`translator` 命令设置新会话的默认值。
- **统一名称。** 两个 agent 使用相同的名称：Input、Instruction 和 Output。

**显示与记录**

- **原文与译文同屏，各显示一次。** 提示词显示实际发送的英文，回答保持英文；Translator 只补上中文：每条翻译后的提示词配上你的原文，每个回答配上中文译文。Pi 把它们显示在按 Markdown 渲染的卡片中。Claude Code 把它们绘制在对应行下方，在终端和它的桌面应用中都一样（包括它工作期间你发送的提示词），恢复会话时会重新绘制。
- **安静的状态显示。** Claude Code 在输入框下方提示行的末尾显示简短状态；Pi 在编辑器下方显示开关栏。
- **本地记录。** 原文、译文和实际发送的文本以纯文本保存在本机，输入失败时也会留下诊断信息。仅内存模式不在磁盘上保留任何内容。

**宿主**

- **Pi 与 Claude Code。** 两者共用同一套翻译核心、字面量扫描器、输入缓存和名称，各自使用自己的模型。Claude Code 不需要 Pi。
- **agent 运行期间的更新。** Translator 的文件变化时，Claude Code 会自动重新加载它并继续翻译，即使重新加载发生在轮次进行中。正在运行的 Pi 会一直使用已加载的版本，直到执行 `/reload`。
- **其他宿主。** 独立的命令行工具可以在你把文本交给任何 agent 之前完成翻译。

各版本的变化见[更新日志](CHANGELOG.zh-CN.md)。

## 环境要求

- **Node.js 22.19 或更高版本**，并可在 `PATH` 中以 `node` 调用。本工具没有 npm 依赖。
- 至少一个宿主：**Pi 1.0.4 或更高版本**，或 **Claude Code 2.1.292 或更高版本**；安装器会拒绝更旧的版本。0.6.10 已在 Pi 1.1.0 和 Claude Code 2.1.296 上测试。早期桌面界面记录还覆盖 Claude Code 2.1.293 和 2.1.294，但本补丁没有重测这些旧版本。详见[验证记录](docs/testing.md#verification-record-for-0610)（英文）。Claude Code 集成使用 Mods API。
- **macOS 或 Linux。** 已在 macOS 上实际使用；不支持 Windows，安装器会拒绝在 Windows 上运行。不需要 `sudo`。
- agent 中已登录可用的模型。翻译使用 agent 自己的模型，因此走你平时的账号；安装器本身从不调用模型，也不接触凭据。

## 快速开始

1. 从 npm 安装本工具，然后运行它的安装器：

   ```sh
   npm install -g @orbit-labs-ai/translator
   translator install
   ```

   或者克隆仓库，在克隆目录中运行安装器：

   ```sh
   git clone https://github.com/Orbit-Labs-AI/orbit-tools.git
   cd orbit-tools/Hooks/translator
   ./install.sh
   ```

   安装器会查找本机的 Pi 和 Claude Code：只装了一个时直接配置它，两个都装了时询问使用哪个。`translator` 与 `./install.sh` 接受相同的选项，后者也可以在任意目录用完整路径运行；各选项见[安装详解](#安装详解)。请保留已安装的包或克隆下来的目录：agent 从这里加载本工具。

2. 重启 agent（Pi 中也可执行 `/reload`），然后运行 `/translator status` 确认工具已加载。在任一 agent 的终端 UI 中，不带参数的 `/translator` 都会打开其原生设置面板。

3. 用中文输入提示词。输入翻译默认开启。

4. 可按需为当前会话开启另外两项功能：

   ```text
   /translator output on         # 为每个最终回答显示中文译文
   /translator instruction on    # 要求模型用英文推理、用中文作答
   ```

要修改新会话的默认值，使用 [`translator`](#新会话的默认设置)。它安装在 `~/.local/bin` 中，该目录须在 `PATH` 里；安装器不会修改你的 shell 启动文件。

## 使用方法

### 命令与快捷键

**`/translator` 是本工具唯一的斜杠命令。** 不带参数时打开原生设置面板，包含五项：**Input**（输入）、**Instruction**（语言要求）、**Output**（输出）、**Model**（翻译模型）和 **Thinking**（思考档位）。修改设置不调用模型，也从不改变主对话的模型或思考档位。

| 操作 | 命令（Pi 与 Claude Code 通用） | Pi 快捷键 | Claude Code 按钮热键 |
|---|---|---|---|
| 打开设置 | `/translator` | — | — |
| 显示全部设置 / 帮助 | `/translator status` / `/translator help` | — | — |
| **Input**：把提示词译为英文 | `/translator input [on\|off\|toggle\|status]` | Ctrl+Alt+T | 1 |
| **Instruction**：附加“英文推理、中文回答”要求 | `/translator instruction [on\|off\|toggle\|status]` | Ctrl+Alt+L | 2 |
| **Output**：以中文显示回答 | `/translator output [on\|off\|toggle\|status]` | Ctrl+Alt+Y | 3 |
| **Model**（翻译模型） | `/translator model [follow\|MODEL]` | — | — |
| **Thinking**（思考档位） | `/translator thinking [low\|medium\|high\|xhigh\|max]` | — | — |

- 两个 agent 使用同一套名称，统一定义在 `shared/features.mjs` 中：开关显示为 `Input: ON`、`Instruction: OFF`、`Output: OFF`，完整状态行显示为 `Translator: Input ON · Instruction OFF · Output OFF`，通知以 `Translator · Input` 或 `Translator · Output` 开头。Claude Code 的提示行显示简短形式，例如 `Translator · Input + Output`。
- `input`、`instruction`、`output` 不带值时切换对应开关；其 `status` 操作只读。`model`、`thinking` 不带值时只报告设置。无效参数不会改变设置。
- **Pi** 的 `MODEL` 是注册表中的 `provider/model` ID；紧凑面板包含 ON/OFF 标记、同一行内的模型 / 思考档位反馈，以及可搜索的模型选择器。在设置列表中按 `?` 查看当前选项的帮助，再按一次即可回到仅显示控件的视图。**Claude Code 原生后端**接受 `sonnet` 等别名或完整模型 ID；面板提供常用别名及 ID 输入框。Claude 的可选 Pi 后端则使用 `provider/model`。Claude 后端的模型可用性和允许列表在 completion 时检查，不会为了保存设置而发起测试模型调用。
- `model follow` 显式覆盖配置文件中的模型固定值。它跟随 Pi 当前会话模型或 Claude 原生当前会话模型；Claude 的 Pi 后端则跟随 **Pi 配置的默认模型**，而不是 Claude 模型。思考档位独立，默认 `medium`；`follow` 不会采用主对话的思考档位。Pi 同时显示请求档位和实际生效档位，包括按模型能力限制或不支持推理的情况。
- 五项设置均作用于当前范围：Pi 按**对话分支**保存覆盖值，Claude 按 **session ID** 保存。它们优先于文件默认值，不修改共享配置。新会话使用文件默认值；持久化设置在恢复会话时还原，Pi 树导航会恢复活动分支的值。不持久化模式只在内存中保存。修改影响后续翻译，不改变正在进行的请求所持有的设置快照。
- 原有开关栏与面板、命令和快捷键保持同步。Pi fullscreen 模式下可点击输入框下方的 **Input**、**Instruction**、**Output**。按钮栏支持换行，不修改草稿或抢占焦点；regular 模式保留键盘控制。RPC / JSON / print 不创建终端控件或面板；无参数命令改为报告状态和帮助。全局默认值在输入 / agent 边界或重载时刷新；`TRANSLATOR_DISABLE=1` 会把输入显示为锁定关闭。
- 在 Claude Code 中，**1/2/3** 属于输入框上方按钮行。输入框为空时按数字并稍停；或先按 **Ctrl+X，再按 Tab** 聚焦按钮行；也可以用鼠标。不覆盖全局 Ctrl 快捷键或已有绑定。按钮行或设置面板不可用时，使用直接子命令。Claude 按会话串行处理重叠的面板、按钮和命令更新，包括仅内存模式。

### 取消待处理的翻译

取消通过快捷键或按钮完成，**不是斜杠命令，也没有 `cancel` 子命令**。取消不会关闭任何设置：取消输入时既不提交原文，也不提交译文；取消输出时保留已完成的原回答。后续翻译仍按你的设置启用。

- **Pi：** **Ctrl+Alt+C** 取消待处理的**输入和输出**翻译，包括两者同时进行或设置面板获得焦点时。宿主提供操作 signal 时会使用它，但 idle 输入预处理期间的普通 SDK / RPC `abort()` 不是可靠的取消方式。树导航、重载和会话替换会使待处理结果失效。直接调用 SDK `dispose()` 会阻止迟到写入，但请求可能持续到完成或超时。
- **Claude Code：** 仅在翻译待处理期间，输入框上方按钮行及已打开的设置面板才显示按键为 **4** 的 **Cancel translation** 按钮。输入框为空时按 **4**，或依次按 **Ctrl+X、Tab、4** 聚焦并触发按钮行；也可点击按钮。取消会传递至原生 completion 和 worker，包括其可选的 Pi 子进程。完整结果开始发布后，按钮即消失：取消不撤销已完成的翻译或不可逆的宿主记录 / 显示写入。mod 也监听宿主 dispatch signal 和 `turn.abort`，不接管快捷键。在 2.1.294 上的真实宿主检查验证了两种后端待处理输出的原生 `turn.abort` 路径，但不能据此认定物理 Escape 键的行为。证据边界见 [Claude 适配器](claude-code/README.zh-CN.md#取消翻译)。

### 你会看到什么

- **你的提示词。** 英文提示词与你的中文原文一起显示，原文标注 `Translator · Input · original · display only`。Pi 只在译后用户行真正送达后追加原文卡片，不在翻译完成或入队时提前显示。Claude Code 保持原有的英文行下方同步原文展示，并保留仅供显示的 transcript 通知（Ctrl+O）。
- **agent 工作期间发送的提示词。** 译文在 steer / follow-up 队列中等待时，原文保持隐藏，避免中间的工具调用把提前出现的卡片埋掉。Claude Code 中包括 Ctrl+X Ctrl+S（或 Ctrl+Enter）送入当前轮次，以及 Enter 排到下一轮；Pi 中包括 steering 和 follow-up。日志和已开启的输入缓存仍在提交前写入，不随展示一起延后。
- **回答译文。** 中文紧接着显示在英文回答下方，标注 `Translator · Output · display only`：Claude Code 把它绘制在回答行下方，在终端和它的桌面应用中都一样，Pi 把它显示在回答之后的卡片中。两种情况下模型都不会读取译文。
- **Pi 卡片中的 Markdown。** 卡片中的链接、路径、行内代码、代码块和列表，用 Pi 自己的 Markdown 渲染器和主题绘制。按 Ctrl+O 展开卡片，可查看翻译模型和追踪信息。
- **重启之后。** 恢复会话时，Claude Code 会根据本地 journal 中保存的关联重新绘制原文和中文回答。Pi 把两者保存在自己的会话文件中。
- **失败。** 输入翻译失败时，提示词被拦下，并立即显示原因和原文，不等待永远不会出现的用户行。成功送达后的展示失败不能撤销提交；Pi 改为报告 `Prompt sent; original display unavailable`。回答翻译失败时，会显示一条简短警告，回答本身保持不变。

### 调回原始提示词

在 Claude Code 终端中，**Up / Down 会为已知的译后提示词恢复中文原文**，已发送的 transcript 行仍是英文。Translator 通过公开的输入框 API 恢复匹配的历史草稿，不改写 Claude 的历史文件，也不接管方向键。已有记录可从近期共享输入历史和恢复会话的 journal 中找回。普通键入 / 粘贴，以及无关或有歧义的历史条目保持不变。

将恢复后的原文**原样发送，会重放已保存的译文**，应用当前语言要求设置，不再调用翻译模型。即使关闭自动缓存，或记录使用旧缓存契约，只要保存的译文仍保留原文字面量，也可以显式重放。编辑调回的原文后，恢复正常翻译流程。关闭 Input 时仍原样发送原文。若要对比模型的新翻译，请关闭自动缓存，并手动键入 / 粘贴原文，而不是选择其已保存的重放。

Claude 2.1.296 没有历史导航事件，因此仅在终端输入框有草稿且存在已知原文时，每 150 ms 在本地检查一次。该检查不调用模型，也不在无界面 / 桌面模式运行。只能恢复近期、无歧义的匹配，缺失的原文记录无法重建。仅内存模式会让调回所需文本对在插件重载期间保留于会话状态，但不跨进程退出保存。

### 新会话的默认设置

安装器会添加 `translator` 命令，用于修改 `config.json` 中的默认值：

```sh
translator                           # 显示当前默认值
translator input on
translator instruction on
translator output off
translator cache off
# translator <input|instruction|output|cache> [on|off|toggle|status]
```

它只修改指定字段。并发命令在规范化的配置路径上串行读改写，保留符号链接、权限和未知字段。分支 / 会话设置优先。它不翻译文本，也不修改其他 agent 设置；外部写入者也须遵守锁协议，才能避免丢失更新。

## 翻译机制

```text
你的提示词
  |-- 输入翻译关闭 --> 原样发送
  `-- 共享预检扫描器：识别确定的字面量，检查剩余正文
       |-- 没有中文正文 / 纯字面量 --> 原样通过，不调用翻译模型、不写翻译记录
       `-- 中文正文
            |-- 原文精确匹配 + 当前扫描器 / 提示词契约缓存 --> 字面量完整性检查后复用
            `-- 遮蔽已识别字面量 --> 一次隔离 completion
                   --> 回复完成且非空 + 占位符次数 / 代码块顺序安全
                         |-- 失败 --> 拦下提示词（不调用修复）
                         `-- 恢复字面量 --> 记录 + 可选的语言附加要求
                                              |
                                译文发送给模型；原文显示在旁边，
                                仅供显示

模型的最终回答 ---------------------------------------> 保持不变
  |-- 输出翻译关闭 --> 不翻译
  `-- 共享预检扫描器
       |-- 没有英文正文 / 纯字面量 --> 不调用翻译模型、不写翻译记录
       `-- 英文正文 --> 遮蔽字面量 --> 每部分一次 completion + 回复 / 字面量安全
                                       --> 恢复后的译文仅显示并保存在本机
```

**输入翻译失败会拦下提示词**：本工具绝不会退而发送中文原文。确实要发送原文时，先执行 `/translator input off`。**回答翻译失败会保留原回答**：不改写任何内容，也不让模型重新回答。

### 本地语言判断

语言判断**只在翻译之前**进行，在本地完成，早于翻译调用和记录。两个方向都使用同一个预检扫描器，识别确定的字面量区间并检查剩余正文。如果需要翻译，这些区间会在请求前被遮蔽；文件名和类字面量名称不再只是被语言判断忽略，却仍原样暴露给模型。

- **输入**：不再剩余中文正文时原样发送。`Read 报告.md` 不调用翻译模型，单独的 `报告.md` 现在也跳过。引号内的中文正文仍可翻译：`Change the title to “你好”` 会进入翻译路径，模型可以保留精确的引号内字面值。仅有引号并不能把任意正文变成受保护的字面量。
- **输出**：没有英文正文需要翻译时跳过。缩写、驼峰名称、命令行参数、版本号、点文件（如 `.gitignore`）、提及以及 ASCII 链接或路径，不会让其余全为中文的回答被当作英文，因此 `已合并 PR #3，见 docs/config.md` 保持原样，而 `已完成 API checks` 会被翻译。在中文回答中，精确匹配且区分大小写的技术名称及标签 `Markdown`、`Mermaid`、`Downloads`、`Token`、`Tokens` 也会被排除：`已保存 Markdown 文档和 Mermaid 图源`、`已保存到 iCloud Downloads；Token 用量未增加` 不会调用翻译模型。这份小型输出分类列表不会遮蔽这些普通单词，不会一概排除首字母大写的词，也不按中文字符占比判断。其他普通英文单词（包括未识别的名称，如 `Orbit`）仍算正文；追加 `Please review the diagram` 仍会触发翻译。`OK` 或 `Mermaid` 这样的普通回答仍会翻译；它们不是扫描器识别出的纯字面量回答。
- **纯字面量文本在两个方向都跳过**，即使完全没有另一种语言的正文。例子包括 `报告.md`、`./文档/报告.md`、`用户表_v2`、`LABEL=中文` 和 `@张三`。跳过时不调用翻译模型、不写翻译日志，也不附加语言要求。

有歧义的正文仍交给模型，而不是整体当作一个受保护名称：紧贴拉丁字母的中文（`请查看data.csv`、`…/docs然后`），用 `：` 或 `、` 连着名称的中文（`问题：src/app.ts`、`配置、README.md`），以中文结尾且没有文件扩展名的链接或路径（`https://example.com/文档里的第三节`、`D:\备份里面`），后面接着写成句子的提及（`@李四麻烦今天合并一下`），本地部分是中文的邮箱地址，以及只用斜杠或 `#` 连接的写法（`English/中文`、`src/组件/按钮`、`问题#3`）。拿不准时宁可翻译，也不漏翻。不属于可识别语法、但需要精确保留的字符串可放进反引号以获得保护。

`请检查 报告.md` 会被翻译，文件名先被遮蔽再恢复。译稿和恢复后的文本都不会再次经过语言判断；字面量内容不能触发语言拒绝。

### 由哪个模型翻译

没有配置文件或会话固定值时，翻译使用**会话当前选择的模型**，每次请求都会重新读取，因此主模型切换时翻译模型也随之切换。翻译使用翻译器的 `thinkingLevel`（默认 `medium`），而不是会话自身的档位：会话为 `max` 时，只要未修改翻译设置，翻译仍请求 `medium`。

- **Pi** 会把请求档位限制在翻译模型支持的范围内；不支持推理的模型不会收到推理选项。面板在同一行内显示选择 / 解析后的模型，仅在思考档位存在差异时显示“请求值 → 实际值”；选中 Thinking 后按 `?` 可查看原因。`/translator status` 保留完整信息。用 `/translator model provider/model` 固定模型，或将 `provider` 和 `model` 设为文件默认值。固定模型不可用时，不会静默回退到主模型。
- **Claude Code** 默认使用自身的隔离 completion API。用 `/translator model sonnet`（或完整 ID）固定模型，或设置 `claudeModel` 文件默认值。请求的思考档位作为 effort 发送；宿主判断模型支持情况，对不接受 effort 的模型省略该值。也可设置 `claudeBackend: "pi"`，使用已安装的 Pi，并接受 `provider/model` 固定值。
- `/translator thinking high` 只改变**两个翻译方向**的思考档位。即使文件里固定了模型，`/translator model follow` 也会为当前分支 / 会话清除实际使用的翻译固定值，但不改变思考档位。这两个命令均不改变主模型、主模型的思考档位或共享配置文件。

### 轻微纠错，不改写

输入翻译可以对正文中明显的拼写、语法或输入错误做**最小纠正**。只有原文能明确确定缺失的措辞时，才会补全残缺片段；否则保留片段或歧义。

翻译不得改写、润色、扩写或猜测。原意、语气、细节、否定、约束、列表和 Markdown 都会保留。这些规则只作用于被翻译的输入：英文提示词仍原样放行，回答翻译也不受影响。它们是给模型的指令，并不保证任何特定模型的翻译质量。

### 本地保护的内容

共享预检扫描器在输入和输出两个方向都只遮蔽识别为**确定字面量**的语法和名称。它采用保守边界，避免把正文藏进猜测的名称中。所有不确定的部分都交给翻译器，由它决定保留还是翻译；这不是面向任意源文本的完整解析器。

| 本地遮蔽 | 例子与边界 |
|---|---|
| 代码 | 反引号和波浪线围栏（含引用块中的围栏和未闭合围栏）、反引号数量精确匹配的行内代码、`<pre>`、`<code>`、`<script>` 和 `<style>` 块。代码中的中文保持原样 |
| 数学公式 | `$…$`（单行，不包括 `$5 到 $10`）、`$$…$$`、`\(…\)`、`\[…\]` |
| URL 和邮箱 | ASCII URL（`https://…`、`www.…`、`mailto:`）和 ASCII 邮箱地址。`请阅读https://…，然后` 只遮蔽 URL；URL 直接连着中文时（如 `看看https://a.com/docs然后`）整体交给翻译器 |
| 文件名和路径 | 确定的裸文件名和相对路径（`README.md`、`报告.md`、`docs/报告.md`）、显式路径（`/`、`~/`、`./`、`../`、`C:\`、`\\server`、`@src/…`），以及引号内可含空格和中文的路径或文件名。含歧义的末尾中文仍按正文处理；文件扩展名或路径结尾分隔符提供明确边界。紧跟中文的 `/` 只有后面还有一个 `/` 时才算 ASCII 路径开头，因此 `项目/Orbit` 仍按正文处理 |
| 类字面量标识符、设置和提及 | `用户表_v2`、`LABEL=中文` 等确定名称，以及 `@张三` 等短提及；不包括任意混合正文、斜杠连接的短语，或后面接着整句的提及 |
| Markdown 链接 | 链接和图片目标、引用定义和标识符、脚注标识符以及 wikilink；链接文字和标题属于正文 |

**不作整体保护：** 有歧义的名称和路径、JSON、普通 HTML 标签和属性、任意命令行参数值、引号内正文、缩进文本，以及没有定界符的公式。其中可识别的字面量仍可能被遮蔽。翻译器收到保留精确值、翻译正文的指令，但这些指令不保证未遮蔽文本的逐字节完整性。

单次 completion 之后，本地只检查回复和恢复过程的安全性：

- **回复完成且非空。** 不完整、为空或失败的回复会被拒绝。Claude 原生后端还要求回复包含唯一的末尾标记，才会接受。
- **每个占位符恰好归还一次。** 占位符很短（`__PT_` 加 8 位随机十六进制，保证不出现在你的文本中），以减少 token 用量。输入语序可以移动行内字面量；受保护的围栏代码块须保持相对顺序。仅供显示的回答译文可以移动任何字面量。通过这些检查后才恢复原始字节。
- **没有 AI 审核或修复调用。** 每次新执行的输入翻译或每个需要翻译的输出分段只进行一次 completion。回复或占位符失败时，拦下输入或保留原回答，不再进行第二次模型调用。

**翻译后没有语言或语义检查**：输入不会因为仍有中文而被拒绝，输出也不要求含有汉字。这同样适用于恢复的字面量和拼接后的长回答。字面量内容不能导致语言拒绝。通过结构检查的结果仍可能未被翻译、译错或质量不佳；Translator 不保证语义质量。

例如：

```text
请阅读https://claude.dev/blog/getting-started-with-claude-code-mods/，然后告诉我mods的工作原理
```

URL 被遮蔽；翻译器看到的是 `请阅读__PT_…_0__，然后告诉我mods的工作原理`，返回 `Please read __PT_…_0__, then tell me how mods work`。图片原样交给主模型，从不送去翻译；本工具不做 OCR，也不翻译附件内容。

Pi 和共享 worker 使用 `shared/literals.mjs`。Claude 沙箱无法导入插件外文件，因此使用 `claude-code/hooks/literals.js` 中生成的逐字节相同镜像。修改扫描器后运行 `npm run sync:shared`；`npm run check:shared` 只读检查一致性。不要维护两套算法。

### 长回答

翻译请求需要写出的内容越多，耗时越长，而 `timeoutMs` 限制的是每一个请求。因此在 Pi 中，以及使用 Claude Code 的 Pi 后端时，遮蔽字面量后超过 2,000 个字符的回答会分成长度大致相等的几部分翻译，最多同时进行四部分：7,000 个字符的回答，所需时间与 2,000 个字符的回答相当。Pi 要等它的 `turn_end` handler（包括这次翻译）完成后才结束本轮，所以这也缩短了这段等待。

- 切分点位于段落之间；一个块长到一部分容纳不下时，才在块内的行与行之间切分，从不切在被遮蔽的代码内部。超过 2,000 个字符的单独一行保持完整。中文按原顺序拼接，保留原有的空白和缩进。
- 每个需要翻译的部分恰好进行一次 completion，有自己的时限，要求回复完成且非空，并检查占位符的精确恢复。预检发现没有英文正文的部分（包括纯字面量部分）不发送请求，原样保留。各部分及拼接后的回答都不再检查语言，也不进行审核或修复调用。
- 任何一部分失败，其他部分随即停止，警告会指明是哪一部分，例如 `Translation timed out (part 2 of 4)`，回答保持不变。取消会停止所有部分。
- 每一部分都在看不到其他部分的情况下翻译，因此同一个术语在两部分中的译法可能不同。
- Claude Code 原生后端用一个请求翻译整个回答，因此很长的回答在那里仍可能超时；请调大 `timeoutMs` 或改用 Pi 后端。

### 译文复用

- 只有原文**完全相同**且扫描器 / 提示词契约一致时才复用缓存译文。空格、换行和标点都参与匹配，没有模糊匹配。
- 只缓存通过回复及字面量恢复检查的结果；不完整、取消或失败的结果从不缓存。0.6.10 的契约升级会隔离旧的带版本条目，不删除其历史。缓存条目不代表语义质量获得认可。
- Pi 与 Claude Code 共用配置文件旁的缓存。更换主模型、翻译模型、翻译思考档位或后端，不会清空或绕过缓存：相同的有效缓存输入仍可复用。要用新译文比较模型，请关闭缓存。
- 命中缓存后才附加语言要求，不会重复附加。复用的记录标记为 `reused`，不会重复计算用量。
- Pi 不修改编辑器内容或提示词历史，只复用当前活动分支的记录，不读取已放弃的分支。Claude 终端会按[调回原始提示词](#调回原始提示词)恢复已知历史草稿的原文，但不改写历史文件。
- 没有版本标记的旧 Pi 记录仍可导入或从当前分支作为候选。每个非空候选都必须精确保留扫描器在原文中识别出的字面量字节及出现次数；这仅检查原文字面量完整性，不重新检查缓存文本的顺序、语言或语义。修改了字面量的候选会触发重新翻译，通过检查的新结果可以替换它，但不抹去旧日志。要强制重新翻译，请关闭缓存。

## 配置

设置保存在工具目录中的 `config.json`；通过 npm 安装时，保存在 `~/.agents/translator/` 中。安装器从 `config.example.json` 生成该文件，Git 会忽略它。文件不存在时，使用以下内置默认值：

```json
{
  "enabled": true,
  "cacheEnabled": true,
  "appendLanguageInstruction": false,
  "outputEnabled": false,
  "provider": null,
  "model": null,
  "claudeBackend": "native",
  "claudeModel": null,
  "thinkingLevel": "medium",
  "timeoutMs": 30000
}
```

| 字段 | 类型与默认值 | 作用 |
|---|---|---|
| `enabled` | boolean，`true` | 输入翻译。关闭时原文原样发送，不附加语言要求 |
| `cacheEnabled` | boolean，`true` | 自动复用输入译文。关闭不会删除历史，也不阻止显式重放原样调回的 Claude 提示词 |
| `appendLanguageInstruction` | boolean，`false` | 为翻译或复用的输入附加语言要求 |
| `outputEnabled` | boolean，`false` | 把最终回答译成中文供显示 |
| `provider`、`model` | 非空字符串或 `null`，均为 `null` | **Pi 后端**的模型覆盖；须同时设置或同时为 `null` |
| `claudeBackend` | `"native"` 或 `"pi"`，`"native"` | Claude Code 使用自身 completion API 翻译，或改用已安装的 Pi |
| `claudeModel` | 非空字符串或 `null`，`null` | Claude Code 的翻译模型。`null` 跟随会话当前模型，包括 `/model` 切换；填写别名或完整 ID 则固定该模型，并受 Claude 模型允许列表约束 |
| `thinkingLevel` | `"low"`、`"medium"`、`"high"`、`"xhigh"` 或 `"max"`，`"medium"` | 翻译的思考档位，独立于会话自身设置：Claude Code 中为 effort，Pi 中为推理档位 |
| `timeoutMs` | `1` 到 `120000` 的整数，`30000` | 每个翻译请求的时限；Pi 子进程后端包含启动时间。[长回答](#长回答)的每一部分都是一个独立请求 |

设置按以下顺序生效，后者优先：内置默认值、配置文件、分支 / 会话对三个开关**以及翻译模型和思考档位**的覆盖，最后是 `TRANSLATOR_DISABLE=1`（强制关闭输入翻译）。没有设置会话覆盖的字段保留文件默认值；显式的 `model follow` 为该范围清除文件中的模型固定值。配置文件在每次请求时读取，修改后无需重启。代码更新会自动到达 Claude Code，Pi 则在执行 `/reload` 时生效（见[移动或更新仓库副本](#移动或更新仓库副本)）；**注册方式的变化需要重启**。

Pi 的 `provider` 与 `model` 覆盖只改变 Pi 翻译所用的模型。独立命令行工具和 Claude Code 的 Pi 后端没有 Pi 会话模型，因此使用 **Pi 的全局默认模型**或文件覆盖值；Claude 的 Pi 后端还可使用会话的 `provider/model` 固定值。它们把 `thinkingLevel` 作为 `--thinking` 传入。Claude Code 原生后端忽略 Pi 模型默认值，使用实际生效的 `claudeModel`（包括会话固定值或显式 `follow`），也从不读取 Pi 的配置或凭据。使用 Pi 后端时，由 Pi 管理自己的登录。安装器不检查模型登录，安装后请确认 agent 本身能正常工作。

Claude Code 原生翻译不带工具和历史，以 `thinkingLevel` 作为 effort，输出上限 32,000 tokens（与思考共用）。由于 API 不报告回复停止的原因，每个请求都要求一个唯一的结束标记，缺少标记的回复会被拒绝，因此截断的译文永远不会到达模型。标记在显示或缓存前移除。很长的提示词可能翻译失败，请拆分或改用 Pi 后端。不会提交不完整的译文。

### 语言附加要求

该要求固定为：

```text
Use English for intermediate reasoning. Write your final response in Simplified Chinese.
```

它只附加到翻译或复用的输入上；预检跳过的提示词（没有中文正文，包括纯字面量文本），以及关闭翻译时的输入都不附加。记录中 `translated` 只含译文，`submitted` 是实际发送的内容（含该要求）。它不改变思考档位，也不要求模型公开隐藏推理。

### 环境变量与插件选项

| 变量或选项 | 用途 |
|---|---|
| `TRANSLATOR_CONFIG` | 配置文件的绝对路径。输入缓存和 Claude Code 会话记录也随之移动，它们保存在配置文件旁 |
| `TRANSLATOR_DISABLE=1` | 无论会话开关如何都关闭输入翻译；不影响回答翻译 |
| `TRANSLATOR_PI_BYPASS=1` | 仅 Pi，0.6.8 起支持：在读取配置或注册 hook 之前，为本进程跳过整个 Pi 扩展；供网关和自动执行使用 |
| `TRANSLATOR_NO_PERSIST=1` | Claude Code：不写入本工具的历史、会话记录和设置存储，五项会话设置均只保存在内存中。应与 Claude Code 的 `--no-session-persistence` 一起使用 |
| `TRANSLATOR_PI` | Pi 可执行文件路径，仅供命令行工具和 Claude Code 的 Pi 后端使用 |
| `PI_CODING_AGENT_DIR` | Pi 的配置目录；安装器会遵循它 |
| `CLAUDE_CONFIG_DIR` | Claude Code 的配置目录；安装器会遵循它 |
| `HOME` | 安装链接、`translator` 命令、默认 agent 目录以及 npm 安装数据目录的基准位置 |
| Claude Code 插件选项 `bridgePath` | 默认为空，即插件自身目录中的 worker `claude-code/bridge.mjs`，因此每份 mod 副本都运行自己的 worker。自定义安装时可设为 worker 的绝对路径 |

设置 `bridgePath` 的方法（插件 ID 取决于插件的安装方式）：

```sh
printf '%s\n' '{"bridgePath":"/absolute/path/to/translator/claude-code/bridge.mjs"}' |
  claude plugin configure translator@skills-dir --values-stdin
```

### Pi 自动执行

网关或其他无人值守启动器可**仅在其 Pi 子进程环境中**设置 `TRANSLATOR_PI_BYPASS=1`。此时 Translator 不注册输入 / 输出 hook、控制项或渲染器：提示词和回答直接通过，不翻译、不附加语言要求、不调用翻译模型，也不新增翻译记录。翻译配置损坏、翻译模型不可用或已保存的分支开关，都不会因此阻塞或延迟本次执行。Pi 的其他扩展、工具、认证和项目信任保持不变。

```sh
TRANSLATOR_PI_BYPASS=1 pi --mode json "Run the saved task"
```

这是显式的进程级退出翻译机制，不是翻译失败后的回退。如果希望普通 Pi 聊天仍使用翻译，请勿在全局环境中导出它。没有设置精确值 `1` 时保留原有行为，包括 JSON / print / RPC 模式下输入翻译失败时拦下提示词；提示词内容从不触发跳过。Claude Code 和独立翻译 CLI 不受影响。与此机制不同，`TRANSLATOR_DISABLE=1` 只关闭输入翻译。启动器必须设置新变量，并使用支持它的 Translator 版本；只更新一方不足以生效。不会修改共享设置文件或已有会话记录。

## 数据与隐私

**隔离不等于离线。** 每个翻译请求都会发送给你配置的模型服务商，内容是待翻译的文本，其中代码等受保护字面量已被遮蔽。请求中从不包含对话历史、工具结果、隐藏推理或图片。原回答始终保留在主对话中，它的中文译文只用于显示和保存。

本工具会在本机保存**明文记录**：

| 位置 | 内容 |
|---|---|
| 配置文件旁的 `history/input.jsonl` | 成功的译文（原文、英文、规则版本和来源），Pi 与 Claude Code 共用 |
| 配置文件旁的 `claude-sessions/<session-id>.jsonl` | Claude Code 输入与回答的写前记录：译文、实际提交的文本、翻译模型与思考档位、用量、请求 ID，以及每条译文显示在哪一 transcript 行 |
| Pi 自身的会话文件 | 模型不读取的条目：双语记录和分支设置（开关、翻译模型、思考档位） |
| Pi 会话文件旁的 `prompt-translations/` 和 `output-translations/` | 本工具自己的写前日志和结果；从不直接写入 Pi 的会话文件 |
| Claude Code 自身的 transcript | 双语通知，模型从不读取（它们不是系统提示词） |
| Claude Code 的私有插件存储 | 三个开关、翻译模型和思考档位覆盖值，按 session ID 保存 |

新建的日志目录权限为 `0700`，文件为 `0600`，每次追加都会刷入磁盘。默认位置已被 Git 忽略；如果把配置或日志放进其他仓库，请自行添加忽略规则。

不在磁盘上留下任何记录：

- **Pi：** `--no-session` 只在内存中保留记录和缓存。
- **Claude Code：** 同时使用 `--no-session-persistence` **和** `TRANSLATOR_NO_PERSIST=1`；mod 无法读取前一个命令行参数。Claude Code 的翻译请求只通过内存和标准输入在插件与 worker 之间传递，从不生成额外文件。

只有 `started` 而没有结果的记录表示翻译被中断。`translated` 表示结果通过了回复及字面量恢复检查，不证明语言或语义质量，也不代表主模型的整轮回答成功。在 agent 中删除会话不会删除这些记录或缓存，升级和卸载也不会删除；请按上表自行清理。

不让译文进入模型上下文，并不等于把这些文件放进沙箱：如果你让 agent 读取本地日志，日志内容会像其他文件一样进入那次请求。

从旧 Pi 会话导入记录：

```sh
HOOK="$HOME/.agents/hooks/translator"
node "$HOOK/import-history.mjs"                    # 默认读取 ~/.pi/agent/sessions
node "$HOOK/import-history.mjs" "/path/to/sessions"
```

导入是幂等的，不会修改原会话。删除共享的 `history/input.jsonl` 不会移除 Pi 活动分支中的旧记录；要强制重新翻译，请执行 `translator cache off`。

## 安装详解

### 会配置哪些 agent

| 检测结果 | 安装器的行为 |
|---|---|
| 只有 Pi | 配置 Pi，不触碰任何 Claude Code 目录 |
| 只有 Claude Code | 配置 Claude Code；它的默认后端不需要 Pi |
| 两者都有 | 询问：`1` Pi、`2` Claude Code、`3` 两者、`0` 取消 |
| 两者都没有 | 以退出码 `1` 结束，不创建任何配置、链接、备份或目录 |

安装器在 `PATH` 中查找可执行文件，并额外检查 Pi 的 `~/.pi/agent/bin/pi` 以及 Claude Code 的 `~/.local/bin/claude` 或 `~/.claude/local/claude`。它用 `--version` 检查所选 agent 的版本；仅有配置目录不算已安装。版本过旧或无法识别时拒绝安装；未选择的 agent 既不会被安装，也不会被升级。

### 选项

```sh
./install.sh --agent pi                 # 只配置 Pi
./install.sh --agent claude             # 只配置 Claude Code
./install.sh --agent both               # 两者都配置，两者都必须已安装
./install.sh --yes                      # 两者都有时选两者，否则选找到的那个
./install.sh --agent both --dry-run     # 只打印计划，不写入任何内容
./install.sh --help

# 另一个 Claude Code 账号，不改动默认账号
./install.sh --agent claude --claude-dir "$HOME/.claude-work"

# 非默认的 Pi 配置目录
./install.sh --agent pi --pi-dir "/absolute/path/to/pi-agent"
```

没有可交互的终端且两个 agent 都已安装时，必须传入 `--agent` 或 `--yes`；安装器不会擅自选择。`--yes` **不会**跳过冲突或版本检查。

配置目录按以下顺序确定：`--pi-dir` 和 `--claude-dir` 选项，然后是 `PI_CODING_AGENT_DIR` 和 `CLAUDE_CONFIG_DIR`，最后是默认目录。要把配置文件放在别处，请将 `TRANSLATOR_CONFIG` 设为绝对路径，并确保之后的每个 agent 进程也设置了它。

### 安装器会修改什么

1. 写入任何内容之前，先检查 Node、agent 版本、源码是否完整以及每个目标位置。只要有冲突就中止整个安装，不会只配置好一个 agent。
2. 创建指向源码的稳定链接和 `translator` 命令：

   ```text
   ~/.agents/hooks/translator  ->  克隆目录中的 translator 目录
   ~/.local/bin/translator      ->  ~/.agents/hooks/translator/control.mjs
   ```

3. 仅在没有配置时，从 `config.example.json` 创建 `config.json`，权限为 `0600`。已有设置、历史和日志从不被覆盖。
4. **Pi：** 把 `<稳定链接>/pi/index.ts` 加入 Pi `settings.json` 的 `extensions` 列表，保留其他所有字段和扩展。已知的旧 `pi.ts` 注册会被迁移并去重。
5. **Claude Code：** 创建链接 `<claude-dir>/skills/translator -> <稳定链接>/claude-code`。不修改你的 settings hooks，也不替换已有的同名插件。
6. 修改 Pi 设置前，保存一份**逐字节备份**，文件名为 `settings.json.translator.bak.<时间戳>.<随机值>`。文件权限和已有的符号链接都会保留。
7. 重复安装无变化。文件更新锁住规范化目标及 Pi 的逻辑设置路径，最终替换前再核对基线。检测到修改会中止安装。回滚失败时保留原始备份并报告恢复路径，不宣称已完整恢复。

锁只能协调遵守锁协议的写入者，不能约束任意编辑器；最终比较与 rename 不是操作系统级 compare-and-swap。避免并发修改宿主设置，尤其是首次创建或通过未知符号链接别名访问时。争用锁最多等待五秒。崩溃后应检查报错中的 `.lock`，确认没有活动写入者后再移除；安装器不会抢占未知锁。

### 移动或更新仓库副本

agent 通过这些链接加载本工具，因此**安装后不要删除克隆目录**。要移动它，请把 `~/.agents/hooks/translator` 指向新位置，更新所有直接引用旧绝对路径的注册或环境变量，然后重载 Pi 或重启 Claude Code。`config.json`、历史和日志随克隆目录一起移动；保存在自定义外部路径中的数据可以留在原处。安装器拒绝替换链接位置上已存在的实体目录，或指向另一个克隆的链接：请先备份配置和数据，再手动解决。

**更新会到达正在运行的 agent。** agent 直接从克隆目录加载本工具，因此克隆目录中的内容就是正在运行的版本：

- **Claude Code** 会在 mod 的文件变化时自动重新加载它：在当前轮次结束时，或在你执行 `/translator` 时提前进行。翻译会跨越重新加载继续进行：进行中的轮次，其回答仍只翻译一次并完成绘制，设置和尚未显示的原文也会保留。如果 Claude Code 显示 `reload failed, the previous version stays loaded`，说明新文件有错误：之前的版本会继续翻译，文件下次变化时会再次尝试加载。
- **Pi** 会一直使用已加载的版本，直到你执行 `/reload` 或重启它。Pi 只允许命令重新加载扩展，因此无法自行应用更新；它也从不混用两个版本。
- **会切换分支的 checkout 不是稳定的安装。** agent 加载被链接克隆中的内容，因此检出其他分支（例如本仓库中其他工具的分支）会同时改变所有正在运行的 agent。日常使用请安装发布的 npm 包，它在 Git 之外保留自己的副本（见[通过 npm 安装](#通过-npm-安装)）；开发用的 checkout 只在测试时按[开发与测试](#开发与测试)中的方式用 `claude --plugin-dir` 或 `pi -e` 加载。

如果 Claude Code 提示 `The loaded translation mod is out of date`，说明仍加载着 0.4.0 之前的 mod：请重启 Claude Code。

### 通过 npm 安装

`npm install -g @orbit-labs-ai/translator` 会把本工具放进 npm 的全局包目录，并添加 `translator` 命令：`translator install` 运行与 `./install.sh` 相同的安装器，接受相同的选项；其他参数用于修改默认设置，见上文。此时 `~/.agents/hooks/translator` 链接指向 npm 的全局目录。

npm 每次更新都会替换包目录，因此通过 npm 安装时，`config.json`、翻译缓存和 Claude Code 会话记录保存在 `~/.agents/translator/` 中，而不是源码旁边。

- **更新：** 运行 `npm install -g @orbit-labs-ai/translator@latest`，然后在 Pi 中执行 `/reload`。Claude Code 会自行重新加载 mod；如果某个会话仍在运行旧版本，在该会话中执行 `/reload-plugins`。链接仍然有效，无需再次运行安装器。
- **切换 Node.js 安装**（例如使用版本管理器）会改变 npm 的全局目录。请删除 `~/.agents/hooks/translator` 链接，在新的 Node.js 下安装本包，再运行一次 `translator`。
- **不要使用 `npx`：** 它从临时缓存运行包，因此安装器会拒绝在那里运行。

### 从早期版本升级

**从 0.6.0（`prompt-translate`）升级到 0.6.1（`translator`）。** 所有名称都已更改，安装器不会删除旧条目。请执行一次以下步骤，然后重启 Claude Code 并重载 Pi：

1. 删除旧条目：`~/.agents/hooks/prompt-translate` 和 `~/.local/bin/agent-translate` 链接、每个 Claude Code 账号中的 `skills/local-translation` 链接，以及 Pi `settings.json` 中的 `…/prompt-translate/pi/index.ts` 条目。
2. 获取新版本。**克隆**：执行 `git pull`，然后把 `config.json`、`history/` 和 `claude-sessions/` 从 `Hooks/prompt-translate/` 移到 `Hooks/translator/`；Git 不会移动被忽略的文件。**npm**：执行 `npm uninstall -g @orbit-labs-ai/prompt-translate` 和 `npm install -g @orbit-labs-ai/translator`，然后把 `~/.agents/prompt-translate/` 中的内容移到 `~/.agents/translator/`。
3. 重新运行安装器：在克隆目录中运行 `./install.sh`，或运行 `translator install`。
4. 重命名你设置过的环境变量：`AGENT_TRANSLATE_CONFIG`、`AGENT_TRANSLATE_DISABLE`、`AGENT_TRANSLATE_NO_PERSIST` 和 `AGENT_TRANSLATE_PI` 分别改为 `TRANSLATOR_CONFIG`、`TRANSLATOR_DISABLE`、`TRANSLATOR_NO_PERSIST` 和 `TRANSLATOR_PI`。旧名称不再被读取。

命令也已更改：`/translator` 取代 `/translate`、`/translate-language`、`/translate-output`、`/translation-status` 和 `/translate-cancel`，`translator` 命令取代 `agent-translate`（`agent-translate language` 现为 `translator instruction`）。Pi 已保存的分支设置仍然有效。Claude Code 按插件名称保存会话覆盖值，因此以 `local-translation` 名义保存的覆盖值不会迁移，恢复的旧会话将从文件默认值开始。[更新日志](CHANGELOG.zh-CN.md)列出了全部变化。

**更早的版本：**

- Pi 的入口已从旧的顶层 `pi.ts` 移到 **`pi/index.ts`**，所有 Pi 代码现在位于 `pi/` 中，顶层不再保留兼容 shim。安装器会迁移它所管理的 Pi 设置中的旧路径；其他手动注册、项目级注册和外部调用方需要你自行更新。
- Claude Code 的 worker 已从顶层移到 **`claude-code/bridge.mjs`**。默认插件设置无需修改，但请更新任何自定义的非空 `bridgePath`。`cli.mjs`、`control.mjs` 和 `import-history.mjs` 的路径不变。
- Claude Code 现在默认使用 `claudeBackend: "native"`。若要继续通过 Pi 翻译，请在配置中加入 `"claudeBackend": "pi"`；安装器从不覆盖已有的选择。

## 命令行工具与其他宿主

独立命令可以在把文本交给任何 agent 之前先翻译它。**它需要 Pi**：Claude Code 原生后端只能在其插件内部使用。下面的中文示例意为“请检查代码”：

```sh
HOOK="$HOME/.agents/hooks/translator"
printf '%s' '请检查代码。' | node "$HOOK/cli.mjs"
printf '%s' '请检查代码。' | node "$HOOK/cli.mjs" --json
node "$HOOK/cli.mjs" --help
```

纯文本模式下，待提交的文本写入标准输出，双语预览写入标准错误。JSON 模式下，标准输出只有 `{status,text?,error?,usage?}`。成功（包括本地直接放行）时退出码为 `0`；失败或取消时退出码非零，并且**没有可提交的文本**。调用方必须同时检查状态和退出码，不能把输出直接管道给 agent。该命令不附加语言要求，不维护会话历史，也不翻译回答。

要支持其他宿主，请调用 `shared/core.mjs` 中的 `translatePrompt` 和 `translateOutput`，并提供自己的隔离 completion 和存储。输入失败必须阻止提交，回答译文绝不能写入模型上下文。[架构文档](docs/architecture.md)（英文）记录了 Claude Code worker 协议和依赖规则。

## 故障排查

| 现象 | 处理方法 |
|---|---|
| 安装器找不到 agent | 检查 `PATH`、文件权限，以及 `pi --version` / `claude --version`。仅有配置目录不算已安装 |
| 安装器报告冲突 | 检查已有的文件、目录或链接，不要直接覆盖。先备份，再决定保留或迁移哪些内容 |
| Pi 仍引用旧的 `pi.ts` | 用你实际使用的 `--pi-dir` 重新运行安装器，并检查项目级或命令行中的注册 |
| Claude Code 找不到 worker | worker 现在是 `claude-code/bridge.mjs`；检查是否有旧的非空 `bridgePath` |
| 升级后仍出现旧的 `/translate…` 命令、`local-translation` 插件，或 Pi 报告与 `prompt-translate` 有关的错误 | 按[从早期版本升级](#从早期版本升级)删除 0.6.0 的条目，然后重启 Claude Code 并重载 Pi |
| 翻译失败，提示词被拦下 | 检查后端的登录、模型、超时和网络。只有确实想发送原文时才使用 `/translator input off` |
| Claude Code 原生模型不可用 | 固定一个允许的 `claudeModel`，或设置 `claudeBackend: "pi"` 并配置 Pi |
| `Prompt NOT sent: The loaded translation mod is out of date` | 仍加载着 0.4.0 之前的 mod。重启 Claude Code，包括其他使用同一链接的账号 |
| Claude Code 显示 `reload failed, the previous version stays loaded` | 更新后的文件有错误，或更新进行到一半时被读取。之前的版本会继续翻译；文件完整后，Claude Code 会在它们下次变化时重新加载 mod，也可以执行 `/reload-plugins` |
| 克隆目录切换到其他分支后翻译停止工作 | agent 直接从被链接的克隆加载 Translator，因此该分支中的版本（或缺少的文件）立即生效。把它切回去，并改用发布的 npm 包运行 agent，而不是会切换分支的 checkout；见[移动或更新仓库副本](#移动或更新仓库副本) |
| Claude 工作期间发送的提示词下方没有原文 | 更新到 0.6.2 或更高版本；更早的版本只在 Claude 空闲时发送的提示词下方绘制原文 |
| 在 Claude Code 桌面应用中，提示词显示了两遍中文，或者回答、Claude 工作期间发送的提示词下方没有中文 | 更新到 0.6.6 或更高版本，然后在该会话中执行 `/reload-plugins` 或新建一个会话：桌面应用的会话不会自行重新加载 mod。桌面应用根据自己的副本绘制各行，并按自己的方式命名它们；更早的版本只为终端中的行绘制。会话中已有的行会在桌面应用下次显示它们时重新绘制 |
| 翻译变慢或超时 | 检查 `/translator status`，调大文件中的 `timeoutMs`，使用 `/translator thinking low`，或通过 `/translator model MODEL` 固定更快的翻译模型。指明某一部分的警告（`part 2 of 4`）来自[长回答](#长回答)中的一部分。在 Pi 中，0.6.7 之前的版本用一个请求发送整个回答，长回答会超时：请更新，然后执行 `/reload` |
| Claude Code 报告完成标记不完整或缺失 | 回复可能被截断或格式不对。内容没有被提交；请拆分提示词或更换后端 |
| 默认设置似乎不起作用 | 分支 / 会话设置优先；用 `/translator status` 检查。可用 `/translator model follow` 覆盖文件中的模型固定值 |
| 快捷键没有反应 | Pi 依赖终端上报组合键；Claude Code 需要输入框为空或按钮行已聚焦。设置请用 `/translator`；取消通过下述控制项完成，不是斜杠命令 |
| 以中文为主的回答仍被翻译 | 若仅由 `Markdown`、`Mermaid`、`Downloads` 或 `Token`/`Tokens` 触发，请升级至 0.6.9 并重载。其他英文正文或未识别的普通单词名称仍会触发翻译；判断不会仅因中文占多数而跳过。适当时，可将需要原样保留的名称放进反引号 |
| 含中文名称或词语的英文回答被翻译 | 这是预期行为：只要还有英文正文，回答中的中文就不会阻止翻译，翻译器会保留名称和路径 |
| 引用中文的英文提示词被翻译 | 这是预期行为：引号内的中文算作正文。翻译器会保留引号内的字面值；放进反引号可让这条提示词不经翻译 |
| 译文仍有未翻译的正文，或使用了非预期语言 | 翻译后没有语言审核或修复调用。请检查结果，尝试其他翻译模型，并关闭输入缓存以强制重新翻译；适当时，将需要精确保留的字面字符串放进反引号 |
| 需要取消翻译 | Pi：Ctrl+Alt+C。Claude：仅待处理时显示的 **Cancel translation**，输入框为空时按 4，或依次按 Ctrl+X、Tab、4。两者都取消输入 / 输出且不改变设置；Pi idle 输入预处理期间不要依赖普通 abort |
| 文件锁超时 / 回滚不完整 | 停止并发修改设置，检查报错中的锁和保留备份；不要移除活动锁或直接覆盖更新后的设置 |
| Claude Code 为插件显示“Hooks (0)” | 该计数只包含 classic settings hooks。运行 `claude plugin validate` 查看 mod 的事件 |

## 已知限制

- 成功原文必须有送达证据。Pi 的公开 API 没有从输入到队列项的关联 ID：文本和图片采用保守的精确匹配；不同原文产生无法区分的待送达译文、相冲突的跳过输入，或宿主改写图片 / 文本时，可能省略卡片，持久化日志和已开启的缓存仍可查阅。丢弃队列、导航、重载或替换会话后，不会从历史重建待显示卡片；已送达的卡片保留。

- 预检的中文检测依据汉字，因此日文汉字也会被识别。它只在翻译之前运行。回复完成且非空、字面量完整，并不保证原意、翻译完整度或目标语言；保留的字面量内容从不触发语言拒绝。
- 从不翻译隐藏推理、工具调用和未完成的回答。其他模板或插件在本工具之后添加的文本，不在其保证范围内。
- classic `UserPromptSubmit` hook 无法安全地替换原始提示词（它的 `additionalContext` 只能追加文本），因此本工具不提供这种退路，也没有 Codex hook。
- Claude Code 的 safe 和 bare 模式、已禁用的 hooks、未受信任的工作区或组织策略，都可能阻止插件加载。新安装后，请先用 `/translator status` 确认再依赖它。
- 在 Claude Code 中，transcript 里绘制的原文和中文回答在重启后只能从会话 journal 恢复，因此仅内存模式（`TRANSLATOR_NO_PERSIST=1`）只在 Claude Code 退出前显示它们。它们的通知仍在 transcript 视图中。
- 正在运行的 Pi 只有在执行 `/reload` 或重启后才会应用代码更新：Pi 只允许命令重新加载扩展。
- Claude Code 原生后端用一个请求翻译每个回答，因此很长的回答在那里可能超过 `timeoutMs`；Pi 和 Pi 后端会把它分成[几部分](#长回答)翻译。
- 少数 Claude Code 回合在结束时没有常规的模型步骤。这些回答通过去重的 fallback 翻译，其 transcript 通知能否保存取决于宿主；详见 [Claude Code 适配器说明](claude-code/README.zh-CN.md)。

## 卸载

1. **Pi：** 从你所用 Pi 目录下 `settings.json` 的 `extensions` 中删除本工具的 `pi/index.ts` 条目，然后重载或重启 Pi。
2. **Claude Code：** 删除你配置过的每个账号下的 `skills/translator` **符号链接**，然后重启 Claude Code。
3. 不再需要时，删除 `~/.local/bin/translator` 链接。
4. 确认没有其他 agent 仍在使用后，再删除 `~/.agents/hooks/translator` 链接或源码本身。
5. 如果是通过 npm 安装的，用 `npm uninstall -g @orbit-labs-ai/translator` 删除该包。

不要删除整个 agent 配置目录，也不要用旧的设置备份覆盖已经改动过的文件。以上步骤会保留你的配置、翻译历史和日志；请参照[数据与隐私](#数据与隐私)单独删除。

## 开发与测试

不安装、只为单个会话加载本工具：把 `HOOK` 设为 `translator` 目录，然后运行 `pi -e "$HOOK/pi/index.ts"` 或 `claude --plugin-dir "$HOOK/claude-code"`。不要同时通过两个入口加载同一个集成。

```sh
npm run check:shared   # 校验生成的 Claude 扫描器与共享源码一致
npm test                 # Node 测试，以及真实 Pi 与 Claude Code 对本地 HTTP fixture 的测试
npm run test:claude      # 校验 Claude Code 插件并运行它的 mod 测试
npm run test:all         # 以上两项；完整验证需要两个 agent 都已安装
npm run test:coverage    # Node 模块的覆盖率；范围另有说明
bash -n install.sh
```

测试中的所有模型回复都来自本地 fixture，因此测试从不产生付费调用。安装器测试在临时的 home 和克隆目录中运行，覆盖四种已安装 agent 的组合、交互选择、重复运行、含空格的路径、迁移、冲突、格式错误的设置、备份和回滚。真实 agent 测试加载的是**安装器创建的入口**。

缺少 Pi SDK 或 Claude Code 时，相应的集成测试会跳过并注明；**跳过的测试不等于通过**。两个变量便于调试：

- `PI_SDK_PATH`：Pi SDK 的 `dist/index.js` 路径，用于 Pi 不在常规 managed 布局中的情况。
- `KEEP_TRANSLATION_TEST=1`：保留 Claude Code 集成测试的临时目录。

`test/claude-tui.test.mjs` 通过 tmux 驱动真实的 Claude Code 终端界面；未安装 tmux 时会跳过并说明原因。

[测试文档](docs/testing.md#verification-record-for-0610)（英文）记录了 0.6.10 通过的 1,013 项 Node 测试、48 项 Claude 插件测试、测试矩阵和仍存在的证据边界。

## 延伸阅读

- [Claude Code 适配器](claude-code/README.zh-CN.md)：mod 在 Claude Code 中的工作方式。
- [Pi 适配器](pi/README.zh-CN.md)：扩展在 Pi 中的工作方式。
- [架构](docs/architecture.md)（英文）：源码布局、依赖规则和 worker 协议。
- [测试](docs/testing.md)（英文）：测试矩阵、记录结果和边界。
- [更新日志](CHANGELOG.zh-CN.md)：各版本的变化以及升级方法。
- [贡献指南](../../CONTRIBUTING.md)（英文）：如何提出修改；中英文 README 同步维护。

## 许可证

[MIT](../../LICENSE) © 2026 Orbit Labs contributors。
