diff --git a/scripts/import_translation.py b/scripts/import_translation.py new file mode 100644 index 0000000..dd5d4c2 --- /dev/null +++ b/scripts/import_translation.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Import a human-made whole-article translation into the fragment store. + +A full translation produced outside the pipeline (e.g. by ChatGPT, pasted +into a file) is decomposed with the same alignment and validation as an +article-mode LLM result (pagerite.translate.align_article): blocks are +stored as proper Data.trans fragments, so later source edits invalidate +and re-translate per chunk instead of letting one monolithic user patch +silently go stale hunk by hunk. + +Run with the Pagerite server STOPPED (the script opens the same kanta +database). Blocks that fail validation stay untranslated — the translator +service picks them up as scoped jobs on the next run. + +Usage: + uv run python scripts/import_translation.py PATH LANG FILE.md [--db DB] + +PATH is the page path without leading slash ("" = front page), LANG the +target language base tag (e.g. fi), FILE.md the translated Markdown. +""" + +import argparse +import asyncio +import sys + +from kanta import Kanta + +from pagerite.data import Data, node_markdown, resolve +from pagerite.i18n import base_tag, primary_lang +from pagerite.translate import TransResult, align_article, store_results + + +async def main() -> None: + p = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + p.add_argument("path", help="page path without leading slash ('' = front page)") + p.add_argument("lang", help="target language base tag (e.g. fi)") + p.add_argument("file", help="Markdown file holding the translation") + p.add_argument( + "--db", + default="localhost/content.kantadb", + help="kanta database (default: localhost/content.kantadb)", + ) + args = p.parse_args() + path = args.path.strip("/") + lang = base_tag(args.lang) + translated = open(args.file).read() + + data = Data() + kanta = Kanta(args.db, data, migrations="pagerite.migrations") + await kanta.open(create=False, log=False) + try: + chain = resolve(data.menu, path) + node = chain[-1] if chain else None + if node is None or node.chunks is None: + sys.exit(f"no such page: {args.path!r}") + if primary_lang(data.menu, path) == lang: + sys.exit(f"{args.path!r} is already in {lang} (its primary language)") + pairs = align_article(node_markdown(data, node) or "", translated) + if pairs is None: + sys.exit( + "rejected: an anchor block (code fence, raw HTML, container fence) " + "is missing or altered — the translation does not preserve the " + "page structure" + ) + if not pairs: + sys.exit("nothing to import: no blocks aligned") + with kanta.transaction(f"translate:{lang}:import", user="import"): + pages = store_results( + data, lang, [TransResult(key=k, text=t) for k, t in pairs] + ) + print(f"imported {len(pairs)} blocks for [{lang}]; pages: {', '.join(pages)}") + print("untranslated blocks stay pending for the translator service") + finally: + await kanta.close() + + +if __name__ == "__main__": + asyncio.run(main())