mtapi / app /renpy /inject.py
Cnass's picture
Upload 7 files
71edbba verified
Raw History Blame Contribute Delete
2.54 kB
"""Write translations back into .rpy scripts.
A translation replaces exactly the characters between one pair of quotes and
nothing else. Spans are applied from the end of the file backwards so that
every offset still refers to the text it was measured against - rewriting
front-to-back would shift each following span by the length the last one
changed.
The script around the strings is never re-indented, re-wrapped or re-parsed, so
a file whose strings all translate to themselves comes back byte-identical.
"""
from __future__ import annotations
from pathlib import Path
from .text import escape
def apply_to_source(source: str, spans: list[tuple[int, int, str, str]]) -> str:
"""Replace each (start, end, quote, translation) span in `source`."""
ordered = sorted(spans, key=lambda item: item[0], reverse=True)
out = source
last_start = len(source) + 1
for start, end, quote, translated in ordered:
if end > last_start:
# Overlapping spans would corrupt the file. The scanner cannot
# produce them, but a stale units.json against an edited source
# could, so refuse rather than write nonsense.
raise ValueError(f"overlapping span at {start}:{end}")
out = out[:start] + escape(translated, quote) + out[end:]
last_start = start
return out
def apply(project_dir: Path, units: list, translations: dict[str, str]) -> tuple[int, int]:
"""Write every translated unit into the scripts under `project_dir`.
Groups by file so each script is read and written once. Returns
(written_spans, skipped_spans).
"""
by_file: dict[str, list[tuple[int, int, str, str]]] = {}
for unit in units:
translated = translations.get(unit.source)
if translated is None:
continue
for slot in unit.slots:
if not slot.writable:
continue
by_file.setdefault(slot.file, []).append(
(slot.start, slot.end, slot.quote, translated)
)
written = skipped = 0
for name, spans in by_file.items():
path = project_dir / name
try:
source = path.read_text(encoding="utf-8")
except OSError:
skipped += len(spans)
continue
try:
updated = apply_to_source(source, spans)
except ValueError:
skipped += len(spans)
continue
path.write_text(updated, encoding="utf-8")
written += len(spans)
return written, skipped