Lazy English Reader Skill
整书学习工作流。它把 PDF/EPUB/MOBI/DOCX/TXT 拆成三个独立可选的产物:全本翻译、章节精读、主题归纳,外加 Obsidian vault 集成。三个产物可单独跑也可组合跑。
本 skill 是 agent-agnostic 的。文档里出现的 Read、Write、Bash、Agent、AskUserQuestion 等名称只是能力示例;非 Claude Code 环境映射到等价工具即可。
起点参考:
- 全本翻译流水线脚本:deusyu/translate-book (MIT)
- 章节提取脚本:hijiangtao/book-reader-skill
- Obsidian vault 架构方法论:alchaincyf/obsidian-ai-orange-book
三个产物
| 全本翻译 | 章节精读 | 主题归纳 | |
|---|---|---|---|
| 目的 | 整本中译,备查 | 主动学习与吸收 | 全书读完后的横向抽取 |
| 工具 | Calibre + Pandoc + 并行 worker | extract_book.py + 笔记编排 | 扫描章节笔记 + Obsidian 双链 |
| 产物 | epub / pdf / docx / md | 每章一份 markdown:第一人称叙事 + 嵌入式精译 + 编辑解读 | 方法论、人物、概念等主题笔记 |
| 依赖 | ebook-convert pandoc | 仅 Python 解析库 | 章节精读完成后的章节笔记 |
核心原则:single source of truth — 全本翻译和章节精读必须按同一份切分对齐
这是 skill 最重要的设计原则。 因为全本翻译和章节精读会互相 wikilink(章节精读里的"原文 → 中译"链接、全本翻译入口里的"→ 精读笔记"链接),两条线的章节划分必须 1:1 对齐——同章号、同文件名、同粒度。
权威是 .chapter_map.json,在 Phase 3 生成:
{
"chapters": [
{"num": 0, "filename": "第00章_引言_Foreword_Introduction",
"anchors": []},
{"num": 1, "filename": "第01章_谁建了金字塔_Who_Built_the_Pyramids",
"anchors": ["序言 I", "PREFACE I", "MIKE LEFEVRE"]},
...
]
}
- 章节精读 用 chapter_map 决定写哪些章节笔记,文件名直接取
chapters[i].filename - 全本翻译 的 postprocess 用
--chapter-map参数驱动切分,文件名也用chapters[i].filename - 结果:
00_全本中译/第04章_耕地谋生_Working_the_Land.md和01_章节精读/第04章_耕地谋生_Working_the_Land.md同名只是不同文件夹
反 pattern(不要做):
- ❌ 章节精读按"工种类别"切 29 篇,全本翻译按 EPUB 自带 chapter 切 14 篇——映射不上
- ❌ 全本翻译按 markdown 标题
###切——翻译过程会把 section 标题降级为段落,导致前言被堆成一个巨型文件 - ❌ "两条线粒度不一致没关系"——必然导致 cross-link 错位
为什么 chapter_map 是权威:它在 EPUB 解出的英文纯文本上用 ALL-CAPS 启发式建立,翻译之前就确定。翻译过程对它不可见,所以不会污染。
工作流总览
Phase 0 收集参数(交互)
Phase 1 环境检查 + vault 准备
Phase 2 全本翻译启动(如选)→ 后台跑
Phase 3 章节结构提取结构 → 生成 MOC + 章节地图
Phase 4 章节循环(前台,按用户节奏)
Phase 5 主题归纳(章节精读全部完成后)
Phase 6 全本翻译完成后挂进 vault
Phase 7 最终报告
Agent 能力映射
执行时只需要:读写文件、运行 shell、搜索文件、向用户收集参数、必要时并行或后台处理 chunk。若平台没有并行 worker,全本翻译可以顺序执行,只是更慢;若没有结构化提问工具,就先让用户提供配置,再写入 .lazy-english-reader.json。
{baseDir} 表示这个 skill 的安装目录——Claude Code 下通常是 ~/.claude/skills/lazy-english-reader/,其他 agent 请替换成本地实际路径。详细的能力名 → 平台工具映射见 references/agent_adapter.md。
Phase 0 — 收集参数
一次问完以下参数。在 Claude Code 里可用 AskUserQuestion;其他 agent 可用自然语言追问、表单、配置文件或等价的用户输入机制。
- 书的路径(必填)
- 跑哪些产物:只做全本翻译 / 只做章节精读 / 全本翻译 + 章节精读(推荐,主题归纳会在章节精读完成后自动跑) / 只重跑主题归纳
- 目标语言:默认
zh - Obsidian vault 路径:默认
~/Documents/Obsidian/Vault - 书的笔记目录名:按
<原书主标题>-<作者姓>命名规则推断。规则:- 用原书主标题(去掉副标题如
: Researching, Interviewing, Writing,去掉冠词The/A,但The是书名核心一部分时保留) - 用
-连接作者姓(last name 或中文姓) - 原书是英文 → 用英文(
Moby-Dick-Melville、War-and-Peace-Tolstoy) - 原书是中文 → 用中文(
活着-余华、人类简史-Harari) - 多字标题用下划线或保留空格替成
-(Brothers-Karamazov-Dostoyevsky)
- 用原书主标题(去掉副标题如
- 章节笔记体量(不卡死,给 AI 一个参考量级):
- 单人章节(一篇前言 / 一位采访对象):1500–2500 字中文
- 多人章节(3–6 位口述者):3000–6000 字中文
- 巨型多人章节(10+ 位口述者,如 某口述史经典的13 人):主推 5–6 位主角,其他人在编辑解读里整合,不要硬把每个人拉成同等长度
- 跟原书章节差不多长(用户想要逐句中文复述):选这个时跳过"嵌入式精译",整章直接当翻译做
- 全本翻译并行度:默认 8
把参数写入 <vault>/书库/<book_dir>/.lazy-english-reader.json 作为本次 run 的配置档。兼容旧项目时,如果只发现 .book-workflow.json,先读取旧文件,再在下次保存时迁移到新文件名。
Phase 1 — 环境检查 + vault 准备
python3 --version
which ebook-convert pandoc # 全本翻译才需要
python3 -m pip install -r {baseDir}/requirements.txt
vault 骨架(仅 vault 不存在时建,不要覆盖用户已有内容):
<vault>/
├── CLAUDE.md ← 用 templates/vault_CLAUDE.md(AI context file)
├── index.md ← 用 templates/vault_index.md
├── 书库/
│ └── <book_dir>/
│ ├── CLAUDE.md ← 用 templates/book_CLAUDE.md(AI context file)
│ ├── index.md ← 用 templates/book_index.md
│ ├── 00_全本中译/
│ ├── 01_章节精读/
│ └── 02_主题笔记/
└── 主题/
重要:vault 根不要自动建
日记/、灵感/、项目/之类,除非用户明确要做综合性第二大脑。本 skill 默认 vault 是读书笔记专用。
Phase 2 — 全本翻译启动(后台)
如果用户选了全本翻译:
mkdir -p <vault>/书库/<book_dir>/00_全本中译/
cd <vault>/书库/<book_dir>/00_全本中译/
python3 {baseDir}/scripts/convert.py "<book_path>" --olang "<target_lang>"
之后按全本翻译的 7 步流程(与原 translate-book 一致):discover chunks → build glossary → parallel worker translate → merge meta per batch → verify → translate title → merge_and_build。
全本翻译可以后台跑。Claude Code 可用 Bash run_in_background=true;其他环境用后台 shell、任务队列或独立 worker。用户同时要全本翻译和章节精读时,先启动全本翻译再进 Phase 3——这样翻译在后台跑的同时用户可以读章节精读。
注:78+ chunks 时建议让全本翻译跑独立长任务,不要在主对话里逐个创建 worker —— 会爆 context。可考虑:
- 写一个独立 shell 脚本调用 claude CLI 顺序翻译,后台 nohup 起
- 或让一个 dispatching worker 内部并行
启动同时必须写 HANDOFF.md(防止"翻译跑完没人接")
后台翻译可能跑 30 min – 2 小时,期间主对话可能完全结束、用户关电脑、几小时后才回来。启动翻译的同一刻必须立刻做两件事:
1. 写 <vault>/书库/<book_dir>/HANDOFF.md,内容样板:
# 全本翻译后台任务
- **PID**:`<pid>`(也写到 `/tmp/<book_dir>-translate/translate.pid`)
- **日志**:`/tmp/<book_dir>-translate/translate.log`
- **temp 目录**:`/tmp/<book_dir>-translate/<book_filename>_temp/`
- **预计时间**:30 min – 2 小时
- **完成标记**:当 `<temp_dir>/_DONE` 文件出现时,翻译完成
## 翻译完成后必须运行(Phase 6)
```bash
python3 ~/.claude/skills/lazy-english-reader/scripts/postprocess_book.py \
"<temp_dir>" \
"<vault>/书库/<book_dir>/00_全本中译" \
--title "<中文书名>" \
--author "<作者>"
进度检查(任何时候都能跑)
tail -3 <log_path>
ls <temp_dir>/output_chunk*.md 2>/dev/null | wc -l # 已完成的 chunk 数
test -f <temp_dir>/_DONE && echo "✅ 翻译完成,可以跑 postprocess" || echo "⏳ 还在跑"
**2. 让后台脚本在结束时 `touch <temp_dir>/_DONE`**——`scripts/convert.py` 或 `merge_and_build.py` 的最后一行加上 `Path(temp_dir / "_DONE").touch()`。这是主对话 / 用户检查是否完成的唯一可靠信号(不要用日志最后一行匹配,会被部分写入糊弄)。
**3. 重入检查**:每次用户重新触发 `lazy-english-reader`,主对话第一件事是检查 `HANDOFF.md` 是否存在、`_DONE` 是否出现——出现就直接跳到 Phase 6。
具体术语表 / merge / build 细节见原 [translate-book SKILL.md](https://github.com/deusyu/translate-book/blob/main/SKILL.md)。
## Phase 3 — 提取章节结构
```bash
python3 {baseDir}/scripts/extract_book.py "<book_path>" \
-o "<vault>/书库/<book_dir>/.extracted.json"
读 JSON 输出,做章节分组:
- PDF:优先用 PDF 自带 outline (
fitz.open(...).get_toc())。如果没 outline,再用 metadata + 目录页 fallback - EPUB/MOBI:默认按 EPUB 自带的 chapter 划分。但要做 anthology 检测——见下
- TXT:按章节标记或固定行数
EPUB anthology 检测(口述史 / 短篇集 / 散文集必做)
很多书 EPUB 文件里的 "chapter" 其实是编辑分组,真正的读单元更细。典型例子:
- 某口述史经典:某 EPUB chapter 内含 13 位工人独白,每位工人才是真正的读单元
- 散文集:一个 EPUB chapter 包含多篇散文
- 短篇集:一个 EPUB chapter = 一本子集,下面才是单篇
检测:
# Phase 3 提取完后,对 EPUB / MOBI 检查每个 chapter 的内部结构
for chapter in extracted_chapters:
body = chapter['text']
# 找全大写或 markdown-style 内嵌标题
allcaps_sections = re.findall(r'^[A-Z][A-Z0-9 ,\'\-]{4,}$', body, re.MULTILINE)
bold_sections = re.findall(r'^\*\*[A-Z][^*]+\*\*$', body, re.MULTILINE)
if len(allcaps_sections) + len(bold_sections) >= 3:
# 这个 EPUB chapter 是 anthology,按内部 section 重切
flag_for_resplit(chapter)
处理:检测到 anthology 后,章节笔记单元改成 section(或 worker / 篇)而不是 EPUB chapter。文件命名仍按阅读顺序数字前缀 第NN章_<slug>.md,但"章"现在指的是真正的读单元——可能比 EPUB 自带的 chapter 多很多(某口述史经典 EPUB 有 ~9 个 chapter,重切后有 29 个真正的读单元 / section)。
这件事章节精读和全本翻译都要按同一份结构走 —— 见上面的「核心原则」。
重切后产出 .chapter_map.json,章节精读用它写笔记,全本翻译 postprocess 用它切中译,两条线产出的 markdown 同名。
生成 .chapter_map.json
# Phase 3 末尾
chapter_map = {
"chapters": [
{"num": i, "filename": f"第{i:02d}章_{中文slug}_{english_slug}",
"anchors": [list_of_anchor_strings_for_this_chapter]}
for i, ch in enumerate(real_reading_units)
]
}
Path(book_dir / '.chapter_map.json').write_text(json.dumps(chapter_map, ensure_ascii=False, indent=2))
anchors 是什么:每章起点的「候选字符串列表」,按优先级排序——脚本对每个候选在合并 markdown 里找 H1/H2 标题,第一个找到的就是该章起始行。工人英文名 / 篇章作者英文名是最稳的锚点(翻译会保留)。section 中文标题(如 "前言" "引言")可以作为备选,但常被翻译降级为段落,作可选 anchor 但不要只放这一个。
产出 01_章节精读/00_章节地图.md:
# 章节地图 — <书名>
| # | 章节 | 英文标题 | 页 | 状态 | 链接 |
|---|------|---------|----|------|------|