Normalize Chinese Part ordinals and support resumable complete-book translation

This commit is contained in:
Kai Tan
2026-10-08 08:32:05 +08:00
parent 498b5d09ce
commit 644e93b874
12 changed files with 186 additions and 20 deletions
+37
View File
@@ -228,3 +228,40 @@ def test_calibre_resolves_parent_part_destination(tmp_path,mode):
assert part['frag']=='part'
assert part['dest_exists'] is True
assert part['children'][0]['dest_exists'] is True
@pytest.mark.parametrize('mode', ['target_only', 'bilingual'])
def test_chinese_part_number_uses_uniform_ordinal_style(tmp_path, mode):
from src.preprocessing.format_extractor import FormatExtractor
source, work, structure, entries = fixture(tmp_path)
part = next(r for r in structure.resources.values() if r.href == 'Text/part.xhtml')
part.content = '<html><body><h1 id="part">PART <span class="c1">1</span><span class="heading_breakp">BOY GENIUS</span></h1></body></html>'
entry = next(e for e in entries if e.element_id == 'part')
_, text, mapping, _, _ = FormatExtractor().extract(str(BeautifulSoup(part.content, 'html.parser').h1))
entry.original_text = text
entry.placeholders = mapping
entry.translated_text = text.replace('PART ', '第').replace('BOY GENIUS', '天才少年')
# Preserve placeholder indices; insert the ordinal suffix after the number span.
closing = 'φ' + next(k for k, v in mapping.items() if v == '</span>') + 'φ'
entry.translated_text = entry.translated_text.replace(closing, closing + '部', 1)
asyncio.run(RestorationEngine().restore_entries([entry], force=True))
asyncio.run(BackfillEngine().backfill(structure, entries, mode))
soup = BeautifulSoup(part.content, 'html.parser')
translated = soup.find('h1', class_='translation')
assert translated.find('span', class_='c1')['data-translation-ordinal'] == 'true'
assert translated.find('span', class_='heading_breakp').get_text() == '天才少年'
assert not translated.find('span', class_='heading_breakp').has_attr('data-translation-ordinal')
if mode == 'bilingual':
assert not soup.find('h1', id='part').find('span', class_='c1').has_attr('data-translation-ordinal')
def test_complete_book_includes_backmatter_and_non_spine_documents(tmp_path):
_, _, structure, _ = fixture(tmp_path)
notes = next(r for r in structure.resources.values() if r.href == 'Text/notes.xhtml')
structure.resources['supplement'] = notes.model_copy(update={'href': 'Text/footnotes.xhtml'})
standard = FineGrainedExtractor().extract(structure)
complete = FineGrainedExtractor(complete_book=True).extract(structure)
assert not any(e.file_path == 'Text/footnotes.xhtml' for e in standard)
assert any(e.file_path == 'Text/footnotes.xhtml' and 'Note text' in e.original_text for e in complete)
assert FineGrainedExtractor(complete_book=True)._classify_document('index.xhtml') == 'core'
assert FineGrainedExtractor(complete_book=True)._classify_document('nav.xhtml') == 'toc'