Localization stage 1: stable IDs, import of tl/ translations, language picker

- every script node gets a permanent id, say nodes a Ren'Py-style tid
- all languages from the game's tl/ are imported at conversion: dialogue blocks
  matched by their source-location comment or by label + original text,
  `translate strings` become a per-language strings table
- source language from config.language or detected from the dialogue
- manifest stores locales/<lang>/<module>.json and strings.json; game.json
  lists sourceLang and available locales with progress
- player: language picker in main menu and settings, fallback to source,
  __(), [var!t] and _preferences.language
- parser: `default` with init priority, and one bad statement no longer drops
  the whole file
- docs/LOCALIZATION.md: design for translations, publishing and mods

Line endings of several converter/player files go back to LF: an earlier
commit accidentally stored them as CRLF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-25 03:39:40 +03:00
1 parent 5d9ddb6376
commit e1d7b29f77
16 files changed
+4280 -3530

No files matched your search

+12 -1
View File
@@ -36,7 +36,7 @@ def test_say_variants():
d = parse('label start:\n "narration"\n e happy "hi" with dissolve\n'
' "Bob" "named"\n centered "c"\n')
body = d.modules["t.rpy"][0]["body"]
assert body[0] == {"op": "say", "what": "narration"}
assert {k: v for k, v in body[0].items() if k != "_ln"} == {"op": "say", "what": "narration"}
assert body[1]["who"] == "e" and body[1]["attrs"] == ["happy"] and body[1]["with"]["t"] == "dissolve"
assert body[2]["whoStr"] == "Bob"
assert body[3]["who"] == "centered"
@@ -80,6 +80,17 @@ def test_full_pipeline(data_dir, tmp_path):
if shutil.which("ffmpeg"):
assert "audio/theme.ogg" in files
# every node has an id, says have tids; tl/english imported into our tables
assert all("id" in n for n in script["labels"][0]["body"])
assert game["sourceLang"] == "ru"
en = game["locales"]["en"]
assert en["renpy"] == "english" and en["lines"] == 3
lines = json.loads(read_bytes(files[en["modules"]["script/script.json"]]["hash"]))
first_say = next(n for n in script["labels"][0]["body"] if n["op"] == "say")
assert lines[first_say["tid"]]["t"] == "Welcome to the {b}test{/b} novel."
strings = json.loads(read_bytes(files[en["strings"]]["hash"]))
assert strings["Эйлин"]["t"] == "Eileen"
def test_nested_labels_and_named_menus_become_anchors():
from app.converter.pipeline import anchors