diff --git a/CHANGELOG.md b/CHANGELOG.md index e32a0d0..b1f774e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2026-10-10 — Highest Exam edition metadata + +- Preserve OPF metadata XML comments while replacing edition values and their refinements. +- Add a comment-node regression test and document Chinese/bilingual cover and metadata updates for existing complete Highest Exam translations. + ## 2026-10-10 — Protocols AZW3 edition and embedded fonts - Bind graphical chapter headings to navigation using image alt text while preserving source markup. diff --git a/doc/QA_HIGHEST_EXAM_EDITION_2026-10-10.md b/doc/QA_HIGHEST_EXAM_EDITION_2026-10-10.md new file mode 100644 index 0000000..641e99f --- /dev/null +++ b/doc/QA_HIGHEST_EXAM_EDITION_2026-10-10.md @@ -0,0 +1,29 @@ +# The Highest Exam — 封面与 metadata 更新 + +按更新后的 epub-translate skill,复用已有完整 Luna 中文/中英对照译本,只更新封面和书库 metadata。项目译名沿用《至高之考:高考如何塑造中国》,修订号 `luna-r1`;没有确认正式中文出版书名,不标为权威出版译名。 + +原作与作者通过[作者出版清单](https://www.ruixuejia.com/publications.html)核对:Ruixue Jia、Hongbin Li、Claire Cousineau。封面保留原作者英文拼写以及 `WITH CLAIRE COUSINEAU` 的署名关系,metadata 按源 OPF 保留三位 creator。香港大学的[中文书评](https://cccw.hku.hk/publications/op-eds/commentary-on-the-highest-exam-how-the-gaokao-shapes-china/)使用《终极考试——高考如何塑造中国》,这是书评译题,不能据此将本项目译名标为正式出版书名。 + +## 封面与版本 + +使用 Codex 内置 imagegen 编辑原书考试大厅摄影封面;具体模型 ID 未公开,记录 `model: null`。逐字查看生成图,核对“至高之考”“高考如何塑造中国”、英语标题及三位作者署名;两版分别用朱红/深蓝徽标标注“中文版 · AI译本”/“中英对照版 · AI译本”和 `luna-r1`。 + +实际 EPUB 内的封面与180×270缩略图均已查看,未发现拉伸、裁切或错字,中文主副标题及版本徽标可辨。原封面图片原路径、原字节保留。新 metadata 的书库标题含模式及修订号;两版使用独立 UUID,原作 ISBN 留在 source,不用作 AI 译本标识。 + +## 验证结果 + +- 两版均保持完整提取/回填 1,000/1,000 单元,源摘要匹配。 +- 22个目录项有效,零新增结构问题、零新增自动布局问题。 +- 24个非封面 XHTML 文档与对应已有完整译本逐字节一致;没有重译正文。 +- 实际查看两版封面、缩略图和第一章开头。属于封装抽样 QA,没有重新进行全书逐句或逐页审校。原正文语义抽查范围见此前 `doc/QA_HIGHEST_EXAM_2026-10-08.md`。 +- 自动报告的 visual/semantic 状态保留 pending,实际已看截图、输出 SHA 及范围写入单独 review-record。 +- 修复源 metadata 中 XML 注释触发封装异常的问题,注释保留不变;增加回归测试。完整测试:90 passed,2 skipped;实际 EPUB Chromium 封面复验另外完成。 + +## 本地记录 + +`.work/luna-highest-exam/edition/` 中保留 `edition.json`、两模式封面/SHA review、`qa-中文版`/`qa-中英对照版`、对应缩略图和 `review-record.json`。书籍与生成封面不提交仓库。 + +输出位于 `.work/luna-highest-exam/output/`: + +- `The_Highest_Exam_中文版_luna-r1.epub` +- `The_Highest_Exam_中英对照版_luna-r1.epub` diff --git a/src/edition/localize.py b/src/edition/localize.py index b412db7..84c61ce 100644 --- a/src/edition/localize.py +++ b/src/edition/localize.py @@ -100,12 +100,12 @@ def image_type(data): def remove_elements(metadata, elements): ids = {x.get('id') for x in elements if x.get('id')} while True: - related = [x for x in metadata if x.get('refines', '').lstrip('#') in ids] + related = [x for x in metadata if (x.get('refines') or '').lstrip('#') in ids] expanded = ids | {x.get('id') for x in related if x.get('id')} if expanded == ids: break ids = expanded for x in list(metadata): - if x in elements or x.get('refines', '').lstrip('#') in ids: + if x in elements or (x.get('refines') or '').lstrip('#') in ids: metadata.remove(x) diff --git a/tests/test_edition.py b/tests/test_edition.py index 8ad8689..defa00f 100644 --- a/tests/test_edition.py +++ b/tests/test_edition.py @@ -182,3 +182,19 @@ def test_new_edition_uuid_keeps_obfuscated_font_readable_and_audited(tmp_path): with zipfile.ZipFile(out) as z,zipfile.ZipFile(damaged,'w') as target: for info in z.infolist(): target.writestr(info,b'broken' if info.filename==active else z.read(info.filename)) assert any('font_binding' in issue for issue in audit_edition(damaged)['issues']) + + +def test_metadata_comments_survive_localization(tmp_path): + source,cfg,image,review,_ = fixture(tmp_path) + commented=tmp_path/'commented.epub' + with zipfile.ZipFile(source) as z,zipfile.ZipFile(commented,'w') as target: + for info in z.infolist(): + raw=z.read(info.filename) + if info.filename=='EPUB/package.opf': + raw=raw.replace(b'',b'') + target.writestr(info,raw) + out=tmp_path/'localized.epub' + localize(commented,out,cfg,'target_only',image,review) + assert audit_edition(out)['issues']==[] + with zipfile.ZipFile(out) as z: + assert b'' in z.read('EPUB/package.opf')