#!/usr/bin/env python3 """Importa las traducciones editoriales humanas adjuntas a un issue de carta. Por defecto solo valida y muestra el plan. --apply-local crea borradores en el WordPress Docker local; nunca publica ni toca producción. """ from __future__ import annotations import argparse import hashlib import html import json import re import subprocess import tempfile import zipfile from pathlib import Path from xml.etree import ElementTree as ET ROOT = Path(__file__).resolve().parent HELPER = ROOT / "fea_translate_helper.php" CONTAINER = "wordpress-web" SOURCE_ID = 55465 # Pagola, Carta 738 LANG_BY_NAME = {"2_eng": "en", "3_fr": "fr", "4_it": "it", "5_pt": "pt"} TRANSLATOR_LABELS = ("Translator:", "Traducteur:", "Traduzzione:", "Tradutor:") def docx_lines(path: Path) -> list[str]: ns = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"} with zipfile.ZipFile(path) as zf: root = ET.fromstring(zf.read("word/document.xml")) return [ "".join(t.text or "" for t in p.findall(".//w:t", ns)).strip() for p in root.findall(".//w:p", ns) if "".join(t.text or "" for t in p.findall(".//w:t", ns)).strip() ] def value_after(lines: list[str], label: str) -> str: try: return lines[lines.index(label) + 1] except (ValueError, IndexError) as exc: raise ValueError(f"Falta {label!r}") from exc def parse_doc(path: Path) -> dict: lines = docx_lines(path) title = value_after(lines, "Título:") excerpt = value_after(lines, "Entradilla:") author = value_after(lines, "Autor:") start = lines.index("Cuerpo:") + 1 end = next((i for i in range(start, len(lines)) if any(lines[i].startswith(label) for label in TRANSLATOR_LABELS)), len(lines)) body = lines[start:end] if not body: raise ValueError("Cuerpo vacío") translator = "" if end < len(lines): label = next(label for label in TRANSLATOR_LABELS if lines[end].startswith(label)) translator = lines[end][len(label):].strip() if not translator and end + 1 < len(lines): translator = lines[end + 1] # La fuente española incluye el crédito/origen como último párrafo. body.append('Publicado en: https://www.gruposdejesus.com') content = "\n".join( f"

{line if line.startswith('Publicado en: " for line in body ) return { "title": title, "excerpt": excerpt, "author": author, "translator": translator, "content": content, "source_sha256": hashlib.sha256(path.read_bytes()).hexdigest(), } def run(cmd: list[str], stdin: str | None = None) -> str: result = subprocess.run(cmd, input=stdin, text=True, capture_output=True) if result.returncode: raise RuntimeError(f"rc={result.returncode}: {result.stderr.strip()} {result.stdout.strip()}") return result.stdout.strip() def helper(*args: str, stdin: str | None = None) -> str: return run(["docker", "exec", "-i", CONTAINER, "php", "/tmp/fea_translate_helper.php", *args], stdin) def lang_for(path: Path) -> str: for marker, lang in LANG_BY_NAME.items(): if marker in path.name: return lang raise ValueError(f"No reconozco idioma en {path.name}") def apply_meta(post_id: int, doc: Path, parsed: dict) -> None: payload = json.dumps({"id": post_id, "doc": doc.name, "sha": parsed["source_sha256"], "translator": parsed["translator"]}, ensure_ascii=False) php = r'''$p=json_decode(base64_decode(getenv('PAYLOAD')),true); update_post_meta($p['id'],'traduccion_automatica','0'); update_post_meta($p['id'],'traduccion_modelo','editorial-humana'); update_post_meta($p['id'],'traduccion_fuente_doc',$p['doc']); update_post_meta($p['id'],'traduccion_fuente_sha256',$p['sha']); update_post_meta($p['id'],'traduccion_editor',$p['translator']); echo 'ok';''' import base64 encoded = base64.b64encode(payload.encode()).decode() run(["docker", "exec", "-i", "-e", f"PAYLOAD={encoded}", CONTAINER, "wp", "--allow-root", "eval", php]) def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--dir", type=Path, required=True) ap.add_argument("--apply-local", action="store_true") args = ap.parse_args() docs = sorted(args.dir.glob("*.docx")) if len(docs) != 4: raise SystemExit(f"Se esperaban 4 DOCX, encontrados {len(docs)}") run(["docker", "cp", str(HELPER), f"{CONTAINER}:/tmp/fea_translate_helper.php"]) for doc in docs: lang, parsed = lang_for(doc), parse_doc(doc) existing = helper("exists", str(SOURCE_ID), lang) plan = {"lang": lang, "doc": doc.name, "existing": int(existing or "0"), "title": parsed["title"], "excerpt_chars": len(parsed["excerpt"]), "content_chars": len(parsed["content"]), "translator": parsed["translator"]} print(json.dumps(plan, ensure_ascii=False)) if not args.apply_local: continue if int(existing or "0"): raise RuntimeError(f"{lang} ya existe como #{existing}; no sobrescribo") payload = json.dumps({"title": parsed["title"], "excerpt": parsed["excerpt"], "content": parsed["content"], "model": "editorial-humana"}, ensure_ascii=False) post_id = int(helper("create", str(SOURCE_ID), lang, "draft", stdin=payload)) apply_meta(post_id, doc, parsed) print(json.dumps({"created": post_id, "lang": lang, "status": "draft"})) return 0 if __name__ == "__main__": raise SystemExit(main())