from pathlib import Path from typing import List, Dict, Optional from bs4 import BeautifulSoup, NavigableString from src.common.data_model import BookStructure, ManifestEntry from src.common.utils import setup_logger logger = setup_logger("backfill_engine") class BackfillEngine: """ Applies translations back to the BookStructure. Now simplified to only inject 'translated_html' or fallback to text. No format restoration logic here. """ def __init__(self): # No more Restorer or LLM Client pass async def backfill(self, structure: BookStructure, manifest_entries: List[ManifestEntry], mode: str = "bilingual") -> BookStructure: """ Modifies the BookStructure in-place with translations. Args: structure: The BookStructure (from book_structure.json). manifest_entries: List of translations. mode: 'bilingual' or 'target_only'. """ logger.info(f"Backfilling with mode: {mode}") # Index manifest manifest_map: Dict[str, Dict[str, ManifestEntry]] = {} for entry in manifest_entries: if not entry.translated_text: continue if entry.file_path not in manifest_map: manifest_map[entry.file_path] = {} manifest_map[entry.file_path][entry.element_id] = entry # Iterate resources for item_id, resource in structure.resources.items(): if resource.media_type != "application/xhtml+xml" or resource.href not in manifest_map: continue file_entries = manifest_map[resource.href] if not file_entries: continue logger.debug(f"Processing {resource.href} with {len(file_entries)} translations") soup = BeautifulSoup(resource.content, 'html.parser') modified = False for element_id, entry in file_entries.items(): element = soup.find(id=element_id) if not element: logger.warning(f"Element {element_id} not found in {resource.href}") continue # Determine content to inject # Prefer translated_html (rich text), fallback to translated_text (plain text) html_content = entry.translated_html plain_text = entry.translated_text # Create translated tag new_tag = soup.new_tag(element.name) if html_content: # Parse HTML fragment # Wrap in div to handle multiple top-level nodes inner_soup = BeautifulSoup(f"