Files
pgames-manager/backend/tests/test_translation.py
T
mrleo1nidandClaude Sonnet 5.5 cc71050362
CI / frontend (push) Failing after 14s
CI / backend (push) Failing after 44s
feat: _() strings and screen texts become translatable strings; search looks in all states; stutter hint
show text _("...") and literal screen texts are listed in Names and menu; typing in search switches the filter from To do to All; the translator is told how stutters are spelled.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 21:09:55 +03:00

122 lines
6.2 KiB
Python

import json
import pytest
from app import translation as tr
from app.config import get_settings
from app.converter.pipeline import convert_archive
from tests.make_sample import build
@pytest.fixture
def manifest(tmp_path, monkeypatch):
monkeypatch.setenv("DATA_DIR", str(tmp_path / "data"))
get_settings.cache_clear()
archive = build(tmp_path / "sample.zip")
yield convert_archive(archive, "sample.zip", tmp_path / "work", lambda _: None).manifest
get_settings.cache_clear()
def test_index_has_context(manifest):
idx = tr.text_index(manifest)
first = idx.lines[0]
assert first.label == "start" and first.scene == "images/bg room.webp"
assert idx.lines[1].who == "Эйлин" and idx.lines[1].prev == first.source
assert idx.strings["Пойдём гулять"] == "menu" and idx.strings["Эйлин"] == "name"
def test_edit_new_language_and_outdated(manifest):
idx = tr.text_index(manifest)
tid = idx.lines[0].tid
m = tr.apply_changes(manifest, "de", lines={tid: {"t": "Willkommen", "state": "approved"}},
strings={"Эйлин": {"t": "Eileen"}}, meta={"glossary": [{"src": "Эйлин", "t": "Eileen"}]})
lines, strings, meta = tr.locale_tables(m, "de")
assert lines[tid]["t"] == "Willkommen" and lines[tid]["state"] == "translated" # no approve right
assert strings["Эйлин"]["t"] == "Eileen" and meta["glossary"][0]["t"] == "Eileen"
game = tr.file_json(m, "game.json")
assert game["locales"]["de"]["renpy"] == "german" and game["locales"]["de"]["lines"] == 1
# the original line changed in a mod: the translation is kept but marked outdated
info = idx.by_tid[tid]
changed = tr.LineInfo(**{**info.__dict__, "source": "другой текст"})
assert tr.line_state(lines[tid], changed) == "outdated"
# removing an entry
m2 = tr.apply_changes(m, "de", lines={tid: None})
assert tid not in tr.locale_tables(m2, "de")[0]
with pytest.raises(ValueError):
tr.apply_changes(manifest, "ru", lines={tid: {"t": "x"}}) # source language
def test_merge_languages_keeps_target_untouched(manifest):
tid = tr.text_index(manifest).lines[0].tid
fork = tr.apply_changes(manifest, "de", lines={tid: {"t": "Hallo"}})
assert tr.changed_languages(fork, manifest) == ["de"]
merged = tr.merge_languages(manifest, fork, ["de"])
assert tr.locale_tables(merged, "de")[0][tid]["t"] == "Hallo"
assert tr.locale_tables(merged, "en")[0] == tr.locale_tables(manifest, "en")[0]
assert "de" not in (tr.file_json(manifest, "game.json").get("locales") or {})
def test_three_way_publication_keeps_independent_lines(manifest):
a, b = [line.tid for line in tr.text_index(manifest).lines[:2]]
left = tr.apply_changes(manifest, "de", lines={a: {"t": "One"}})
right = tr.apply_changes(manifest, "de", lines={b: {"t": "Two"}})
merged, conflicts = tr.merge_translation(manifest, left, right)
assert not conflicts
lines = tr.locale_tables(merged, "de")[0]
assert lines[a]["t"] == "One" and lines[b]["t"] == "Two"
conflict = tr.apply_changes(manifest, "de", lines={a: {"t": "Other"}})
_, conflicts = tr.merge_translation(manifest, left, conflict)
assert conflicts and any(a in c["path"] for c in conflicts)
def test_po_and_csv_roundtrip(manifest):
tid = tr.text_index(manifest).lines[0].tid
m = tr.apply_changes(manifest, "de", lines={tid: {"t": "Zeile 1\nZeile \"2\""}},
strings={"Эйлин": {"t": "Eileen"}})
lines, strings = tr.parse_po(tr.export_po(m, "de", "Test"))
assert lines[tid]["t"] == "Zeile 1\nZeile \"2\"" and strings["Эйлин"]["t"] == "Eileen"
lines, strings = tr.parse_csv(tr.export_csv(m, "de"))
assert lines[tid]["t"] == "Zeile 1\nZeile \"2\"" and strings["Эйлин"]["t"] == "Eileen"
st = tr.stats(m, "de")
assert st["translated"] == 1 and st["stringsTranslated"] == 1
assert json.dumps(st)
def test_entries_page_locates_a_line_in_the_filtered_list(manifest):
idx = tr.text_index(manifest)
first, second = idx.lines[0], idx.lines[1]
page = tr.entries_page(manifest, "ru", state="untranslated", limit=1, locate=second.tid)
assert page["located"] == 1 and page["total"] == len(idx.lines) and len(page["items"]) == 1
m = tr.apply_changes(manifest, "ru", lines={first.tid: {"t": "Привет", "state": "machine"}})
# a translated line leaves the "untranslated" list, so the next one moves up
assert tr.entries_page(m, "ru", state="untranslated", locate=second.tid)["located"] == 0
assert tr.entries_page(m, "ru", state="untranslated", locate=first.tid)["located"] is None
# ...but stays in "todo" because machine translations still await a review
assert tr.entries_page(m, "ru", state="todo", locate=first.tid)["located"] == 0
assert tr.entries_page(m, "ru", locate="")["located"] is None
assert tr.entries_page(m, "ru", kind="strings", locate="Эйлин")["located"] is not None
def _mark(text):
return {"k": "call", "f": {"k": "name", "id": "_"}, "a": [{"k": "const", "v": text}]}
def test_marked_strings_and_screen_texts_become_translatable_strings():
body = [
{"op": "show", "img": {"textExpr": _mark("Do you want a short tutorial?")}},
{"op": "python", "code": [{"k": "assign", "value": _mark("Hi, [name]!")}]},
{"op": "show", "img": {"textExpr": _mark("")}}, # empty: nothing to translate
{"op": "show", "img": {"textExpr": _mark("42")}}, # no letters: not text
{"op": "show", "img": {"textExpr": {"k": "call", "f": {"k": "name", "id": "other"},
"a": [{"k": "const", "v": "Not marked"}]}}},
]
screens = {"menu": {"body": [
{"w": "text", "args": [{"k": "const", "v": "Preferences"}], "props": {}, "body": []},
{"w": "textbutton", "args": [{"k": "const", "v": "Quit"}], "props": {}, "body": []},
{"w": "text", "args": [{"k": "name", "id": "title"}], "props": {}, "body": []}, # variable
{"w": "add", "args": [{"k": "const", "v": "bg.png"}], "props": {}, "body": []}, # not text
]}}
assert tr.marked_strings([{"name": "start", "body": body}]) == ["Do you want a short tutorial?", "Hi, [name]!"]
assert tr.marked_strings(screens) == ["Preferences", "Quit"]