translate-book

translate-book

热门

使用并行子代理将书籍(PDF/DOCX/EPUB)翻译成任何语言。将输入转换为 Markdown 分块,再转换为翻译后的分块,最后生成 HTML/DOCX/EPUB/PDF。

1207Star
151Fork
更新于 2026/8/6
SKILL.md
只读
名称
translate-book
描述

使用并行子代理将书籍(PDF/DOCX/EPUB)翻译成任何语言。将输入转换为 Markdown 分块,再转换为翻译后的分块,最后生成 HTML/DOCX/EPUB/PDF。

书籍翻译技能

你是一名书籍翻译助手。你通过编排多步骤流水线,将整本书从一种语言翻译成另一种语言。

工作流程

1. 收集参数

从用户消息中确定以下内容:

  • file_path:输入文件(PDF、DOCX 或 EPUB)的路径 — 必填
  • target_lang:目标语言代码(默认:zh)— 例如 zh、en、ja、ko、fr、de、es
  • concurrency:每批并行子代理的数量(默认: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/;只有父目录会改变。

这将创建一个 {filename}_temp/ 目录,包含:

  • input.htmlinput.md — 中间文件
  • chunk0001.mdchunk0002.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. 构建术语表(术语一致性)

一个单独的子代理在全新上下文中翻译每个分块。如果没有共享状态,同一个专有名词可能会在多个翻译中漂移。术语表使每个子代理都能看到其分块中出现的术语的相同规范翻译。

如果 <temp_dir>/glossary.json 已存在,则跳过重建 — 重新运行技能不得覆盖手动编辑的术语表。要强制重建,请删除该文件。

否则:

  1. 采样分块:读取 chunk0001.md、最后一个分块以及 3 个均匀分布的中间分块。如果 chunk_count < 5,则全部采样。

  2. 提取术语:从样本中识别需要在整本书中保持一致翻译的专有名词和重复出现的领域术语 — 通常是人名、地名、组织、技术概念。将每个术语翻译成目标语言。跳过任何翻译者都会以相同方式处理的通用词汇。

  3. 在临时目录中写入 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 文件具有多义重复源,升级将中止并显示消歧消息。

  4. 统计频率,运行:

    python3 {baseDir}/scripts/glossary.py count-frequencies "<temp_dir>"
    

    这会扫描每个 chunk*.md(排除 output_chunk*.md),更新每个术语的 frequency 字段,并原子地写回。

术语表可手动编辑。如果用户在部分运行后编辑了 targetaliasescategory 字段,下一步中的运行状态规划器将仅重新翻译其记录的术语集或术语哈希受影响的那些分块。

3.7. 规划选择性重译

运行:

python3 {baseDir}/scripts/run_state.py plan "<temp_dir>"

如果用户明确要求将术语表编辑应用于 run_state.json 存在之前生成的输出,请添加 --retranslate-untracked;否则保持默认,以便旧临时目录无需大规模重译即可恢复。

捕获标准输出 JSON:

  • translation_chunk_ids — 本次运行中要翻译的分块。
  • record_only_chunk_ids — 需要 run_state.json 记录但不需要翻译的现有有效输出。
  • unchanged_chunk_ids — 已与当前源分块和术语表一致的现有输出。

如果 record_only_chunk_ids 非空,请在启动子代理之前记录它们:

python3 {baseDir}/scripts/run_state.py record "<temp_dir>" chunk0001 chunk0002 ...

使用 translation_chunk_ids 作为第 4 步的工作队列。如果为空,则跳到第 5 步。

4. 使用子代理进行并行翻译

每个分块都有自己的独立子代理(1 个分块 = 1 个子代理 = 1 个全新上下文)。这可以防止上下文累积和输出截断。

分批启动分块以遵守 API 速率限制:

  • 每批:最多 concurrency 个子代理并行(默认:8)
  • 等待当前批次完成后再启动下一批

使用以下任务生成每个子代理。 使用运行时提供的任何子代理/后台代理机制(例如 Agent 工具、sessions_spawn 或等效机制)。

输出文件是 output_ 前缀加上源文件名:chunk0001.mdoutput_chunk0001.md

将文件 <temp_dir>/chunk<NNNN>.md 翻译成 {TARGET_LANGUAGE},并将结果写入 <temp_dir>/output_chunk<NNNN>.md。遵循下面的翻译规则。只输出翻译后的内容 — 不要任何评论。

每个子代理接收:

  • 它负责的单个分块文件
  • 临时目录路径
  • 目标语言
  • 翻译提示(见下文)
  • 每个分块的术语表(见“术语表组装”)
  • 只读的相邻分块摘录(见“邻居上下文组装”)
  • 任何自定义指令

术语表组装 — 在生成子代理之前,运行:

python3 {baseDir}/scripts/glossary.py print-terms-for-chunk "<temp_dir>" "chunk<NNNN>.md"

捕获标准输出。CLI 输出一个 3 列 Markdown 表格(原文 | 别名 | 译文),列出此分块中出现的每个术语(按源或任何别名)或全书最高频术语中的前 N 个。将表格作为 {TERM_TABLE} 注入翻译提示的规则 #13。如果标准输出为空(没有术语表,或没有相关术语),则完全省略此分块提示中的规则 #13 — 不要留下悬空的 {TERM_TABLE} 占位符。

邻居上下文组装 — 在生成子代理之前,运行:

python3 {baseDir}/scripts/chunk_context.py "<temp_dir>" "chunk<NNNN>.md"

捕获标准输出。CLI 输出提示就绪的只读摘录:上一分块的最后约 300 个字符和下一分块的前约 300 个字符(如果这些文件存在)。将此块注入为 {NEIGHBOR_CONTEXT}。如果标准输出为空,则完全省略邻居上下文块。子代理不得翻译相邻摘录或将其复制到输出中;它们仅用于代词、性别和实体解析上下文。

每个子代理的任务

  1. 读取源分块文件(例如 chunk0001.md
  2. 按照下面的翻译规则翻译内容
  3. 将翻译后的内容写入 output_chunk0001.md
  4. 将观察结果写入 output_chunk0001.meta.json,匹配下面的模式。非阻塞 — 如果不确定,请留空字段;不要发明实体。始终发出该文件(即使所有数组为空),因为它的存在 + 内容哈希是主代理跟踪反馈是否已合并的方式。

子代理元数据模式output_chunk<NNNN>.meta.json):

{
  "schema_version": 1,
  "new_entities": [
    {"source": "Taig", "target_proposal": "泰格", "category": "person",
     "evidence": "<≤200 字符的引用>"}
  ],
  "alias_hypotheses": [
    {"variant": "Taig", "may_be_alias_of_source": "Tai",
     "evidence": "<≤200 字符的引用>"}
  ],
  "attribute_hypotheses": [
    {"entity_source": "Tai", "attribute": "gender", "value": "male",
     "confidence": "high", "evidence": "<≤200 字符的引用>"}
  ],
  "used_term_sources": ["Tai", "Manhattan"],
  "conflicts": [
    {"entity_source": "Tai", "field": "target", "injected": "泰",
     "observed_better": "太一", "evidence": "<≤200 字符的引用>"}
  ]
}

不要包含 chunk_id 字段 — 分块身份从文件名派生。将其放入有效负载会创建幻觉漏洞,验证将拒绝该文件。

元数据文件稍后由主代理读取并合并到 glossary.json(参见 merge_meta.py)。子代理应如实填写模式:引用分块中的真实引用,切勿发明实体以“显得有成效”。空元数据是完全有效的输出。

重要:每个子代理只翻译一个分块,并直接将结果写入输出文件。不需要 START/END 标记。

子代理的翻译提示

在每个子代理的指令中包含此翻译提示(将 {TARGET_LANGUAGE} 替换为实际语言名称,例如“中文”):


请翻译markdown文件为 {TARGET_LANGUAGE}.
IMPORTANT REQUIREMENTS:

  1. 严格保持 Markdown 格式不变,包括标题、链接、图片引用等
  2. 仅翻译文字内容,保留所有 Markdown 语法和文件名
  3. 删除空链接、不必要的字符和如: 行末的'\'。页码已由 convert.py 上游处理,不要再删除独立的数字行(可能是年份 1984、章节编号、引用编号等正文内容)。
  4. 保证格式和语义准确翻译内容自然流畅
  5. 只输出翻译后的正文内容,不要有任何说明、提示、注释或对话内容。
  6. 表达清晰简洁,不要使用复杂的句式。请严格按顺序翻译,不要跳过任何内容。
  7. 必须保留所有图片引用,包括:
    • 所有 ![alt](path) 格式的图片引用必须完整保留

    • 图片文件名和路径不要修改(如 media/image-001.png

    • 图片alt文本可以翻译,但必须保留图片引用结构

    • 不要删除、过滤或忽略任何图片相关内容

    • 图片引用示例:![Figure 1: Data Flow](media/image-001.png) -> ![图1:数据流](media/image-001.png)

    • 原始 HTML 标签(如 <img alt="..." /><a title="...">)必须保持合法:翻译 alttitle 等属性值内部文本时,下列字符会破坏 HTML 结构,必须替换为安全形式(仅适用于原始 HTML 标签的属性值内部;普通 Markdown 正文、代码块、URL 不要主动转义):

      字符 在属性值内的危险 替换为
      " 闭合 attr="..." 目标语言合适的弯引号(如中文 )或 &quot;
      ' 闭合 attr='...' 目标语言合适的弯引号(如中文 )或 &#39;
      < 被解析为新标签 &lt;
      > 被解析为标签结束 &gt;
      & 被解析为实体起始(除非已是 &xxx; &amp;

      不要修改 srchref 等结构性属性的值,只翻译可见文本属性(alttitle)。

      • 错误示例:alt="爱丽丝拿着标着"喝我"的瓶子" ← 内层英文 " 把外层 alt 撑断了
      • 正确示例:alt="爱丽丝拿着标着“喝我”的瓶子"alt="爱丽丝拿着标着&quot;喝我&quot;的瓶子"
  8. 智能识别和处理多级标题,按照以下规则添加markdown标记:
    • 主标题(书名、章节名等)使用 # 标记
    • 一级标题(大节标题)使用 ## 标记
    • 二级标题(小节标题)使用 ### 标记
    • 三级标题(子标题)使用 #### 标记
    • 四级及以下标题使用 ##### 标记
  9. 标题识别规则:
    • 独立成行的较短文本(通常少于50字符)
    • 具有总结性或概括性的语句
    • 在文档结构中起到分隔和组织作用的文本
    • 字体大小明显不同或有特殊格式的文本
    • 数字编号开头的章节文本(如 "1.1 概述"、"第三章"等)
  10. 标题层级判断:
    • 根据上下文和内容重要性判断标题层级
    • 章节类标题通常为高层级(# 或 ##)
    • 小节、子节标题依次降级(### #### #####)
    • 保持同一文档内标题层级的一致性
  11. 注意事项:
    • 不要过度添加标题标记,只对真正的标题文本添加
    • 正文段落不要添加标题标记
    • 如果原文已有markdown标题标记,保持其层级结构
  12. {CUSTOM_INSTRUCTIONS if provided}
  13. 术语一致性:以下术语必须严格使用指定译法,不要自行变换。表格中"原文"列或"别名"列任一形式出现在正文中时,都必须翻译为"译文"列对应的形式。

{TERM_TABLE}

邻居上下文(只读,不要翻译,不要写入输出,只用于判断代词、性别、别名和跨 chunk 指代;为空则省略):

{NEIGHBOR_CONTEXT}

markdown文件正文:


4.5. 将子代理元数据合并到术语表(每批之后)

每个子代理在其翻译的分块旁边生成了一个 output_chunk<NNNN>.meta.json。每批完成后,首先在术语表仍用于该批时,将已完成的分块输出记录到 run_state.json 中,然后将观察结果合并到规范术语表中,以便后续批次看到丰富的术语表。

  1. 在修改术语表之前,记录本批成功翻译的分块:

    python3 {baseDir}/scripts/run_state.py record "<temp_dir>" chunk0001 chunk0002 ...
    

    如果失败,请先修复缺失/空的输出或状态错误,然后再继续。

  2. 运行 prepare-merge:

    python3 {baseDir}/scripts/merge_meta.py prepare-merge "<temp_dir>"
    

    捕获标准输出 JSON。它包含四个数组:

    • auto_apply — 没有术语表冲突且所有提出分块一致(目标、类别)的新实体。
    • decisions_needed — 需要主代理判断的项目。每个都有 idkindoptions 数组以及选择所需的数据。种类:
      • 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 — 子代理建议将 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 — 子代理为 entity_source 提出了与规范不同的(目标、类别)。所有不同的提议都会暴露。{entity_source, current_target, current_category, proposed_variants: [{target_proposal, category, evidence, evidence_chunks}, ...]}。选项:keep_current / 每个竞争提议一个 use_variant_N(覆盖目标和类别,将先前的值记录到 notes 中)/ record_in_notes(规范不变;每个提议的变体都记录到 notes 中)。
      • alias_or_new_entityvariant 有多个竞争选项,在 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_0use_variant_1、...、skip
    • consumed_chunk_ids — 本轮扫描的每个元数据文件(无论是否产生发现)。这些哈希在应用时记录在 applied_meta_hashes 中。
    • malformed_meta_chunk_ids — 验证失败的元数据文件。已隔离:不消耗,不使运行崩溃。在批次进度中显示它们。
  3. 如果 consumed_chunk_ids 为空 → 没有扫描任何内容;跳到第 5 步。

  4. 如果 consumed_chunk_ids 非空但 auto_applydecisions_needed 都为空 → 仍然将 {"auto_apply": [], "decisions": [], "consumed_chunk_ids": [...]} 管道到 apply-merge,以便记录哈希。跳过这是错误 — 否则无操作元数据将永远重新扫描。

  5. 否则,解决每个决策

    • 内联阅读其证据引用。

    • 从其 options 数组中选择一个选项。

    • 构建一个 decisions 条目,该条目往返原始决策以及您的选择。该条目必须包含原始 kind 和(对于 conflicting_new_entity_proposalsvariants 数组,以便 apply-merge 可以验证并执行:

      {"id": "d1", "kind": "alias", "variant": "Taig", "candidate_source": "Tai", "choice": "yes_alias"}
      
  6. 将决策 JSON 管道到 apply-merge:

    echo '{"auto_apply": [...], "decisions": [...], "consumed_chunk_ids": [...]}' \
      | python3 {baseDir}/scripts/merge_meta.py apply-merge "<temp_dir>"
    

    在批次进度消息中显示摘要 JSON(auto_applieddecisions_resolvedconsumed_chunkserrors)。

    apply-merge 是事务性的。 如果任何决策格式错误(种类选择错误、缺少字段、引用不存在的实体),整个批次将以非零退出码中止,并在 stderr 中显示详细信息 — 没有术语表变更,没有记录哈希。在非零退出时,修复有问题的决策并重新管道;prepare-merge 将显示相同的提议,因为没有任何内容被消耗。

    输入列表中的决策顺序不重要。 apply-merge 内部先分派创建实体的决策,然后分派附加别名的决策,因此 yes_alias 决策的候选由同一批次中的另一个决策(use_standalone_Nuse_variant_Npromote_to_separate_entity)创建时,无论您传递的顺序如何,都会成功。别名链(例如 Taighi → Taig 其中 Taig → Tai 也是待处理的别名决策)通过别名附加器传递中的定点循环解决 — 您无需手动拓扑排序或排序链式别名。

在先前中断的批次之后的全新运行中,prepare-merge 将拾取任何遗留的元数据文件。不要手动删除它们。

5. 验证完整性和重试

所有批次完成后,使用 Glob 检查每个源分块是否有对应的输出文件。

如果有缺失,重试它们 — 每个缺失的分块作为自己的子代理。每个分块最多尝试 2 次(初始 + 1 次重试)。

还要读取 manifest.json 并验证:

  • 每个分块 id 都有对应的输出文件
  • 没有输出文件为空(0 字节)或空白(仅空白字符)

然后运行元数据合并可观察性快照:

python3 {baseDir}/scripts/merge_meta.py status "<temp_dir>"

还要运行选择性重译状态快照:

python3 {baseDir}/scripts/run_state.py status "<temp_dir>"

在验证报告中显示一行摘要:

已翻译分块:50 • 元数据文件:48 找到 / 47 已消耗 • 格式错误:1(chunk0099 — 参见 stderr)• 缺少元数据的分块:chunk0017、chunk0042

严重性规则(这些都不会使运行失败 — 元数据是非阻塞的):

  • unmerged_meta_files > 0 在第 4.5 步之后 → 错误,突出显示。恢复应该已经捕获了这一点。
  • malformed_meta_files > 0 → 子代理发出了无效元数据;打印 chunk_ids 并注明“手动修复文件并重新运行,如果您希望此分块的反馈被合并”。
  • meta_files_found < translated_chunks → 子代理合规性问题(某些分块根本没有发出元数据)。打印缺失的 chunk_ids。

报告任何重试后仍失败的翻译分块。

6. 翻译书名

从临时目录读取 config.txt 以获取 original_title 字段。

将标题翻译成目标语言。对于中文,用书名号括起来:《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 — 合并的翻译 Markdown
  • book.html — 带浮动目录的网页版本
  • book_doc.html — 电子书版本
  • book.docxbook.epubbook.pdf — 格式转换(需要 Calibre)

8. 报告结果

告诉用户:

  • 输出文件的位置
  • 翻译了多少个分块
  • 翻译后的标题
  • 列出生成的输出文件及其大小
  • 任何格式生成失败