Localization stages 2-3: drafts, publishing, translation editor
- games.main_version_id, version visibility (draft/published) and kind
(original/translation/mod), version_history; migration 0002
- permissions translations.edit and versions.publish
- "Translate" creates a translation draft; publishing merges the changed
languages into the main version in place (players keep their saves), the
previous manifest goes to the history and can be restored
- translation API: overview with per-state stats, entries with filters,
search and scene context, save, glossary, locale font, PO/CSV export/import
- translation editor page: languages and progress, lines and strings,
filters, autosave, approve/outdated/fuzzy actions, glossary highlighting,
"play from here" with the scene restored
- player: ?lang= and ?at= from the editor, per-locale font
(models.py and permissions.py for this landed earlier in 9ee3e1f)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4e047f3aa0
commit
ac164d1782
20 files changed
+2039
-298
No files matched your search
@@ -0,0 +1,69 @@
|
||||
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_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)
|
||||
Reference in new issue
Block a user