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:
1 parent
5d9ddb6376
commit
e1d7b29f77
16 files changed
+4280
-3530
No files matched your search
@@ -0,0 +1,28 @@
|
||||
# TODO: Translation updated at 2026-10-25 12:00
|
||||
|
||||
# game/script.rpy:29
|
||||
translate english start_1a2b3c4d:
|
||||
|
||||
# "Добро пожаловать в {b}тестовую{/b} новеллу."
|
||||
"Welcome to the {b}test{/b} novel."
|
||||
|
||||
# game/script.rpy:32
|
||||
translate english start_5e6f7a8b:
|
||||
|
||||
# e "Привет! Как тебя зовут?"
|
||||
e "Hi! What's your name?"
|
||||
|
||||
# matched by label + original text, the location comment is stale
|
||||
translate english walk_deadbeef:
|
||||
|
||||
# e sad "На улице дождь..."
|
||||
e sad "It's raining outside..."
|
||||
|
||||
translate english strings:
|
||||
|
||||
# game/script.rpy:2
|
||||
old "Эйлин"
|
||||
new "Eileen"
|
||||
|
||||
old "Пойдём гулять"
|
||||
new "Let's go for a walk"
|
||||
@@ -59,6 +59,8 @@ def build(path: Path) -> Path:
|
||||
with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||
zf.writestr("SampleGame-1.0-pc/game/archive.rpa", rpa_path.read_bytes())
|
||||
zf.writestr("SampleGame-1.0-pc/game/options.rpy", options)
|
||||
zf.writestr("SampleGame-1.0-pc/game/tl/english/script.rpy",
|
||||
(FIXTURES / "tl" / "english" / "script.rpy").read_bytes())
|
||||
zf.writestr("SampleGame-1.0-pc/game/audio/theme.wav", _wav())
|
||||
zf.writestr("SampleGame-1.0-pc/game/gui/main_menu.png",
|
||||
_png((1280, 720), (30, 30, 50), "Main menu"))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user