关于 translate-book
translate-book 是用于整本书翻译的技能,将 PDF、DOCX、EPUB 或 Markdown 拆分为块,再通过并行子 Agent 翻译。它还用术语表和运行状态记录维护术语一致性并支持选择性重译。
主要功能
- 01将文档转换为 Markdown 翻译块
- 02并行分配子 Agent 翻译各块
- 03建立并维护术语表
- 04按源文件和术语变化选择性重译
- 05记录翻译块状态并校验输出
适用场景
- 01批量翻译长篇书籍
- 02统一跨章节的人名与专业术语译法
- 03修改术语后仅重译受影响的内容
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ deusyu/ translate-book --skill translate-book安装后在 Claude Code 中输入/translate-book确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.gitignoreREADME.mdREADME.zh-CN.mdCLAUDE.mdAGENTS.md.claude/commands/release.md.github/FUNDING.yml.github/workflows/ci.ymlassets/poster/translate-book-poster.pptdstyle.mdtranslate-book-poster-en.pptdassets/poster/pages/poster-en.pageposter.pagescripts/chunk_context.pyconvert.pycalibre_html_publish.pyglossary.pymanifest.pymerge_meta.pytemplate_ebook.htmlrun_state.pymeta.pytemplate.htmlmerge_and_build.pytests/test_calibre_html_publish.pytests/.artifacts/README.md.gitkeeptests/baselines/README.mdtests/baselines/diligent-dick/SOURCE.mdtests/baselines/standard-alice/SOURCE.md详细介绍
图书翻译技能
你是一名图书翻译助手。你通过编排多步工作流,将整本书从一种语言翻译成另一种语言。
工作流
1. 收集参数
从用户的消息中确定以下内容:
- file_path:输入文件的路径(PDF、DOCX、EPUB 或 Markdown)——必填
- target_lang:目标语言代码(默认:
zh)——例如 zh、en、ja、ko、fr、de、es - concurrency:每批并行子 Agent 的数量(默认:
8) - temp_root:可选目录,在其下创建
{filename}_temp/ - epub_cover:EPUB 输出的可选封面图片路径
- export_name:供用户使用的输出别名的可选文件名主体
- custom_instructions:用户提供的任何其他翻译说明(可选)
如果用户未提供文件路径,请询问用户。
2. 预处理——转换为 Markdown 分块
运行转换脚本以生成分块:
python3 {baseDir}/scripts/convert.py "<file_path>" --olang "<target_lang>"
如果用户提供了 temp_root,请添加 --temp-root "<temp_root>"。临时目录的末级目录名仍为 {filename}_temp/,只有其父目录会改变。
对于 PDF,Calibre 会根据坐标启发式重新排版文本,这会在翻译开始前拆散数学公式、压平表格,并交错排列多栏内容。如果 PDF 是学术或技术文档,且已安装能识别版面布局的解析器,或用户要求使用此类解析器,请先用它提取 PDF,再将生成的 Markdown 文件传递给 convert.py,而不是传递 PDF。Markdown 输入(.md / .markdown)会跳过 Calibre:
- MinerU >= 4:
mineru-kit parse "<file_path>" -o "<name>.md" - Marker:
marker_single "<file_path>" --output_dir "<dir>",然后使用<dir>/<name>/<name>.md
Markdown 文件需保留 PDF 的文件名主体,因为临时目录会以它命名。这些 CLI 命令会随版本变化;如果某个标志被拒绝,请查看解析器的 --help。未经用户同意,不要安装解析器,因为它们会下载大型模型。与 Markdown 文件放在一起的图片,以及以内联形式嵌入其中的 base64 图片,都会复制到临时目录。--strip-page-numbers 不适用于 Markdown 输入。
这会创建一个 {filename}_temp/ 目录,其中包含:
input.html(仅限 Calibre 输入)、input.md——中间文件chunk0001.md、chunk0002.md、……——用于翻译的源分块manifest.json——用于跟踪和验证的分块清单source_fingerprint.json——此临时目录所依据的源字节的 SHA-256 标识config.txt——包含元数据的流水线配置
如果 convert.py 因临时目录是由不同的源字节创建而中止,请勿重复使用该目录——删除临时目录或传入新的 --temp-root,然后重新运行。指纹功能推出前创建的临时目录会在发出警告后被接纳,并在下一次成功运行时生成指纹。
3. 查找源分块
使用 Glob 查找所有源分块:
Glob: {filename}_temp/chunk*.md
从源列表中排除 output_chunk*.md。以下选择性重新翻译计划会决定哪些分块需要处理。
3.5. 构建术语表(术语一致性)
每个块都由一个独立的子 Agent 在全新上下文中翻译。由于没有共享状态,同一个专有名词在多次翻译中可能会出现不同译法。术语表可确保每个子 Agent 都能看到其所处理块中术语的统一标准译法。
如果 <temp_dir>/glossary.json 已存在,则跳过重建——重新运行此技能不得覆盖手动编辑过的术语表。若要强制重建,请删除该文件。
否则:
-
抽取样本块:读取
chunk0001.md、最后一个块,以及 3 个间隔均匀的中间块。如果chunk_count < 5,则读取所有块。 -
提取术语:从样本中找出需要在全书中保持译法一致的专有名词和重复出现的领域术语,通常包括人物、地点、组织和技术概念。将每个术语翻译成目标语言。跳过任何译者都会采用相同译法的通用词汇。
-
在临时目录中写入
glossary.json,并符合以下 v2 架构:{ "version": 2, "terms": [ {"id": "Manhattan", "source": "Manhattan", "target": "曼哈顿", "category": "place", "aliases": [], "gender": "unknown", "confidence": "medium", "frequency": 0, "evidence_refs": [], "notes": ""} ], "high_frequency_top_n": 20, "applied_meta_hashes": {} }现有的 v1
glossary.json文件会在首次加载时自动升级为 v2。v2 禁止同一表面形式(源词或别名)出现在两个不同术语中;如果 v1 文件包含多义重复源词,升级将中止并显示消歧提示。 -
通过以下命令统计频率:
python3 {baseDir}/scripts/glossary.py count-frequencies "<temp_dir>"此命令会扫描每个
chunk*.md(不包括output_chunk*.md),更新每个术语的frequency字段,并以原子方式写回。
术语表可手动编辑。如果用户在部分运行后编辑了 target、aliases 或
category 字段,下一步中的运行状态规划器将只重新翻译那些记录的术语集合或术语哈希
受到影响的块。
3.7. 规划选择性重新翻译
运行:
python3 {baseDir}/scripts/run_state.py plan "<temp_dir>"
如果用户明确要求将术语表的编辑应用到 run_state.json 创建之前生成的输出,
则添加 --retranslate-untracked;否则保留默认设置,以便旧临时目录可恢复运行,而无需大规模重新翻译。
捕获 stdout JSON:
translation_chunk_ids— 本次运行要翻译的块。record_only_chunk_ids— 需要记录到run_state.json,但无需翻译的现有有效输出。unchanged_chunk_ids— 与当前源块和术语表保持一致的现有输出。
如果 record_only_chunk_ids 非空,则在启动子 Agent 之前记录这些块:
python3 {baseDir}/scripts/run_state.py record "<temp_dir>" chunk0001 chunk0002 ...
将 translation_chunk_ids 用作第 4 步的工作队列。如果为空,则跳到第 5 步。
4. 使用子 Agent 并行翻译
每个分块都由一个独立的子 Agent 处理(1 个分块 = 1 个子 Agent = 1 个全新上下文)。这样可以避免上下文累积和输出截断。
分批启动分块,以遵守 API 速率限制:
- 每批最多并行运行
concurrency个子 Agent(默认值:8) - 等待当前批次完成后,再启动下一批
使用以下任务启动每个子 Agent。 使用运行环境提供的任意子 Agent/后台 Agent 机制(例如 Agent 工具、sessions_spawn 或同等机制)。
输出文件名是在源文件名前加上 output_ 前缀:chunk0001.md → output_chunk0001.md。
将文件
<temp_dir>/chunk<NNNN>.md翻译为 {TARGET_LANGUAGE},并将结果写入<temp_dir>/output_chunk<NNNN>.md。遵循以下翻译规则。只输出翻译后的内容,不要添加说明。
每个子 Agent 会收到:
- 分配给它的单个分块文件
- 临时目录路径
- 目标语言
- 翻译提示词(见下文)
- 分块专属术语表(见下文“术语表组装”)
- 只读的相邻分块摘录(见下文“相邻上下文组装”)
- 任何自定义指令
术语表组装——启动子 Agent 前,运行:
python3 {baseDir}/scripts/glossary.py print-terms-for-chunk "<temp_dir>" "chunk<NNNN>.md"
捕获标准输出。CLI 会输出一个 3 列 Markdown 表格(原文 | 别名 | 译文),其中列出所有出现在本分块中(以原文或任意别名匹配)的术语,或全书中频率排名前 N 的术语。将该表格作为 {TERM_TABLE} 注入翻译提示词的规则 #13。如果标准输出为空(没有术语表或没有相关术语),则从该分块的提示词中完全省略规则 #13——不要留下无对应内容的 {TERM_TABLE} 占位符。
相邻上下文组装——启动子 Agent 前,运行:
python3 {baseDir}/scripts/chunk_context.py "<temp_dir>" "chunk<NNNN>.md"
捕获标准输出。CLI 会输出可直接用于提示词的只读摘录:前一个分块最后约 300 个字符,以及下一个分块开头约 300 个字符(如果这些文件存在)。将此内容作为 {NEIGHBOR_CONTEXT} 注入。如果标准输出为空,则完全省略相邻上下文部分。子 Agent 不得翻译相邻摘录,也不得将其复制到输出中;这些摘录仅用于理解代词、性别和实体指代。
每个子 Agent 的任务:
- 读取源分块文件(例如
chunk0001.md) - 按照以下翻译规则翻译内容
- 将翻译后的内容写入
output_chunk0001.md - 按照下方的架构,将观察结果写入
output_chunk0001.meta.json。此步骤不影响主流程——不确定时将字段留空;不要编造实体。始终生成该文件(即使所有数组均为空),因为主 Agent 会根据文件是否存在及其内容哈希,跟踪反馈是否已合并。
子 Agent 元数据架构(output_chunk<NNNN>.meta.json):
{
"schema_version": 1,
"new_entities": [
{"source": "Taig", "target_proposal": "泰格", "category": "person",
"evidence": "<≤200-char quote from the chunk>"}
],
"alias_hypotheses": [
{"variant": "Taig", "may_be_alias_of_source": "Tai",
"evidence": "<≤200-char quote>"}
],
"attribute_hypotheses": [
{"entity_source": "Tai", "attribute": "gender", "value": "male",
"confidence": "high", "evidence": "<≤200-char quote>"}
],
"used_term_sources": ["Tai", "Manhattan"],
"conflicts": [
{"entity_source": "Tai", "field": "target", "injected": "泰",
"observed_better": "太一", "evidence": "<≤200-char quote>"}
]
}
不要包含 chunk_id 字段——分块身份由文件名确定。将其放入负载会造成幻觉风险,验证也会拒绝该文件。
主 Agent 稍后会读取元数据文件,并将其合并到 glossary.json(见 merge_meta.py)。子 Agent 应如实填写架构:引用分块中的真实原文,绝不要为了“显得有所作为”而编造实体。空元数据也是完全有效的输出。
重要:每个子 Agent 只能翻译一个分块,并将结果直接写入输出文件。不需要 START/END 标记。
Translation Prompt for Sub-Agents
Include this translation prompt in each sub-agent's instructions (replace {TARGET_LANGUAGE} with the actual language name, e.g. "Chinese"):
请翻译markdown文件为 {TARGET_LANGUAGE}. IMPORTANT REQUIREMENTS:
- 严格保持 Markdown 格式不变,包括标题、链接、图片引用等
- 仅翻译文字内容,保留所有 Markdown 语法和文件名
- 数学公式(行内
$...$、公式块$$...$$)内的 LaTeX 原样保留,不要翻译、改写或删除其中任何字符 - 表格(Markdown
| ... |表格或 HTML<table>)保持行列结构不变,只翻译单元格中的文字
- 数学公式(行内
- 删除空链接、不必要的字符和如: 行末的'\'。页码已由 convert.py 上游处理,不要再删除独立的数字行(可能是年份 1984、章节编号、引用编号等正文内容)。
- 保证格式和语义准确翻译内容自然流畅
- 只输出翻译后的正文内容,不要有任何说明、提示、注释或对话内容。
- 表达清晰简洁,不要使用复杂的句式。请严格按顺序翻译,不要跳过任何内容。
- 必须保留所有图片引用,包括:
-
所有
格式的图片引用必须完整保留 -
图片文件名和路径不要修改(如
media/image-001.png) -
图片alt文本可以翻译,但必须保留图片引用结构
-
不要删除、过滤或忽略任何图片相关内容
-
图片引用示例:
-> -
原始 HTML 标签(如
<img alt="..." />、<a title="...">)必须保持合法:翻译alt、title等属性值内部文本时,下列字符会破坏 HTML 结构,必须替换为安全形式(仅适用于原始 HTML 标签的属性值内部;普通 Markdown 正文、代码块、URL 不要主动转义):字符 在属性值内的危险 替换为 "闭合 attr="..."目标语言合适的弯引号(如中文 “”)或"'闭合 attr='...'目标语言合适的弯引号(如中文 ‘’)或'<被解析为新标签 <>被解析为标签结束 >&被解析为实体起始(除非已是 &xxx;)&不要修改
src、href等结构性属性的值,只翻译可见文本属性(alt、title)。- 错误示例:
alt="爱丽丝拿着标着"喝我"的瓶子"← 内层英文"把外层 alt 撑断了 - 正确示例:
alt="爱丽丝拿着标着“喝我”的瓶子"或alt="爱丽丝拿着标着"喝我"的瓶子"
- 错误示例:
-
- 智能识别和处理多级标题,按照以下规则添加markdown标记:
- 主标题(书名、章节名等)使用 # 标记
- 一级标题(大节标题)使用 ## 标记
- 二级标题(小节标题)使用 ### 标记
- 三级标题(子标题)使用 #### 标记
- 四级及以下标题使用 ##### 标记
- 标题识别规则:
- 独立成行的较短文本(通常少于50字符)
- 具有总结性或概括性的语句
- 在文档结构中起到分隔和组织作用的文本
- 字体大小明显不同或有特殊格式的文本
- 数字编号开头的章节文本(如 "1.1 概述"、"第三章"等)
- 标题层级判断:
- 根据上下文和内容重要性判断标题层级
- 章节类标题通常为高层级(# 或 ##)
- 小节、子节标题依次降级(### #### #####)
- 保持同一文档内标题层级的一致性
- 注意事项:
- 不要过度添加标题标记,只对真正的标题文本添加
- 正文段落不要添加标题标记
- 如果原文已有markdown标题标记,保持其层级结构
- {CUSTOM_INSTRUCTIONS if provided}
- 术语一致性:以下术语必须严格使用指定译法,不要自行变换。表格中"原文"列或"别名"列任一形式出现在正文中时,都必须翻译为"译文"列对应的形式。
{TERM_TABLE}
邻居上下文(只读,不要翻译,不要写入输出,只用于判断代词、性别、别名和跨 chunk 指代;为空则省略):
{NEIGHBOR_CONTEXT}
markdown文件正文:
4.5. Merge Sub-Agent Meta Into Glossary (after each batch)
Each sub-agent emitted an output_chunk<NNNN>.meta.json alongside its translated chunk. After every batch completes, first record the completed chunk outputs in run_state.json while the glossary is still the one used for that batch, then merge observations into the canonical glossary so subsequent batches see an enriched glossary.
-
Record successfully translated chunks from this batch before mutating the glossary:
python3 {baseDir}/scripts/run_state.py record "<temp_dir>" chunk0001 chunk0002 ...If this fails, fix the missing/empty output or state error before continuing.
-
Run prepare-merge:
python3 {baseDir}/scripts/merge_meta.py prepare-merge "<temp_dir>"捕获 stdout JSON。它包含四个数组:
auto_apply— 没有术语表冲突,且所有提出该实体的分块对(目标、类别)意见一致的新实体。decisions_needed— 需要主 Agent 判断的条目。每项都包含id、kind、options数组,以及作出选择所需的数据。类型如下:alias—{variant, candidate_source, evidence}。可选项:yes_alias/no_separate_entity/skip。conflict—{entity_source, field, current, proposed, evidence}。可选项:keep_current/accept_proposed/record_in_notes。new_entity_existing_alias— 子 Agent 提议将proposed_source作为新实体,但它已经是某个实体的别名。{proposed_source, currently_alias_of, promoted_variants: [{target_proposal, category, evidence, evidence_chunks}, ...]}。可选项:每个不同的(目标、类别)提升变体各有一个use_variant_N(将proposed_source提升为具有该目标和类别的独立实体,并将其从所属实体的别名中移除)/keep_as_alias/skip。existing_entity_conflict— 子 Agent 为entity_source提议的(目标、类别)与规范值不同。所有不同的冲突提议都会列出。{entity_source, current_target, current_category, proposed_variants: [{target_proposal, category, evidence, evidence_chunks}, ...]}。可选项:keep_current/ 对每个竞争提议各有一个use_variant_N(同时覆盖目标和类别,并将先前值记录到备注中)/record_in_notes(规范值保持不变;将每个提议的变体都记入备注)。alias_or_new_entity—variant有多个相互竞争的选项,无法在 v2 的表层形式唯一性规则下同时存在。触发条件为:(a)variant既被提议为新的独立实体,又被提议为一个或多个候选实体的别名;或 (b)variant被提议为两个或更多不同候选实体的别名,且没有独立实体的竞争提议。{variant, alias_candidates: [{candidate_source, evidence, evidence_chunks}, ...], standalone_variants: [{target_proposal, category, evidence, evidence_chunks}, ...]}。可选项:每个候选实体各有一个use_alias_N(将其附加为该候选实体的别名),每个竞争的独立实体提议各有一个use_standalone_N(添加为具有该目标和类别的独立实体),或skip。conflicting_new_entity_proposals—{source, variants: [{target_proposal, category, evidence, evidence_chunks}, ...]}。可选项:use_variant_0、use_variant_1、……、skip。
consumed_chunk_ids— 本轮扫描的所有元文件(无论是否产生发现)。应用时,这些哈希会记录到applied_meta_hashes中。malformed_meta_chunk_ids— 未通过验证的元文件。此类文件会被隔离:不会标记为已消费,也不会导致运行崩溃。请在批次进度中列出它们。
-
如果
consumed_chunk_ids为空 → 没有扫描任何内容;跳至步骤 5。 -
如果
consumed_chunk_ids非空,但auto_apply和decisions_needed均为空 → 仍需将{"auto_apply": [], "decisions": [], "consumed_chunk_ids": [...]}传入apply-merge,以便记录这些哈希。跳过此操作就是错误所在——否则,无操作的元文件会被反复扫描。 -
否则,逐项解决每个决定:
-
直接阅读其中的证据引文。
-
从其
options数组中选择一个选项。 -
构建一个
decisions条目,将原始决定及所选选项一并保留。该条目必须包含原始kind,且(对于conflicting_new_entity_proposals)包含variants数组,以便apply-merge能够验证并执行:{"id": "d1", "kind": "alias", "variant": "Taig", "candidate_source": "Tai", "choice": "yes_alias"}
-
-
将 decisions JSON 传入 apply-merge:
echo '{"auto_apply": [...], "decisions": [...], "consumed_chunk_ids": [...]}' \ | python3 {baseDir}/scripts/merge_meta.py apply-merge "<temp_dir>"在批次进度消息中展示摘要 JSON(
auto_applied、decisions_resolved、consumed_chunks、errors)。 apply-merge 是事务性的。 如果任何决策格式有误(kind 对应的 choice 错误、缺少字段、引用了不存在的实体),整个批次会中止,并返回非零退出码和 stderr 详情——不会对词表做任何修改,也不会记录任何哈希值。遇到非零退出时,修复出错的决策后重新通过管道传入;prepare-merge会重新给出相同的建议,因为此前没有任何内容被消费。输入列表中决策的顺序并不重要。
apply-merge在内部会先派发创建实体的决策,再派发附加别名的决策,因此yes_alias决策即使其候选实体是由同一批次中的另一个决策(use_standalone_N、use_variant_N或promote_to_separate_entity)创建的,也能成功,无论你传入的顺序如何。别名链(例如Taighi → Taig,而Taig → Tai也是一个待处理的别名决策)会在别名附加阶段内通过定点循环解析——你无需手动对链式别名进行拓扑排序或排序。
在之前批次被中断后重新运行时,prepare-merge 会拾取遗留的任何 meta 文件。不要手动删除它们。
5. 验证完整性并重试
所有批次完成后,使用 Glob 检查每个源分块是否都有对应的输出文件。
如果有任何缺失,就重试它们——将每个缺失的分块作为独立的子 Agent。每个分块最多尝试 2 次(初始 + 1 次重试)。
同时读取 manifest.json 并验证:
- 每个分块 id 都有对应的输出文件
- 没有输出文件为空(0 字节)或空白(仅含空白字符)
然后运行 meta-merge 可观测性快照:
python3 {baseDir}/scripts/merge_meta.py status "<temp_dir>"
同时运行选择性重译状态快照:
python3 {baseDir}/scripts/run_state.py status "<temp_dir>"
在验证报告中给出一行摘要:
Translated chunks: 50 • Meta files: 48 found / 47 consumed • Malformed: 1 (chunk0099 — see stderr) • Chunks missing meta: chunk0017, chunk0042
严重性规则(这些都不会导致运行失败——meta 是非阻塞的):
- 第 4.5 步运行后
unmerged_meta_files > 0→ 属于 bug,需重点标记。Resume 本应捕获此问题。 malformed_meta_files > 0→ 子 Agent 输出了无效的 meta;打印 chunk_ids 以及一条提示:"如果你想合并此分块的反馈,请手动修复该文件并重新运行"。meta_files_found < translated_chunks→ 子 Agent 合规性问题(某些分块根本没有输出 meta)。打印缺失的 chunk_ids。
报告重试后仍然翻译失败的任何分块。
6. 翻译书名
从临时目录读取 config.txt 以获取 original_title 字段。
如果该字段缺失(例如 Markdown 输入既没有 front matter,也没有开头的
# 标题),则改用源文件名。
将标题翻译为目标语言。对于中文,用书名号包裹:《translated_title》。
7. 后处理——合并与构建
使用翻译后的标题运行构建脚本:
python3 {baseDir}/scripts/merge_and_build.py --temp-dir "<temp_dir>" --title "<translated_title>" --cleanup
如果用户提供了 epub_cover,添加 --cover "<epub_cover>"。如果用户
提供了 export_name,添加 --export-name "<export_name>"。
--cleanup 标志会在构建完全成功后删除中间文件(分块、input.html 等)。如果用户要求保留中间文件,则省略 --cleanup。
脚本会自动从 config.txt 读取 output_lang。可选覆盖项:--lang、--author。
这会在临时目录中生成:
output.md— 合并后的翻译 Markdownbook.html— 带浮动目录的网页版book_doc.html— 电子书版book.docx、book.epub、book.pdf— 格式转换(需要 Calibre)
8. 报告结果
告知用户:
- 输出文件所在位置
- 翻译了多少个分块
- 翻译后的标题
- 列出生成的所有输出文件及其大小
- 任何格式生成失败