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>
122 lines
6.2 KiB
Python
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"]
|