#!/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())