This commit is contained in:
DazedAnon 2026-07-08 17:42:25 -05:00
parent 42cda41458
commit 6badfcb246
6 changed files with 32 additions and 32 deletions

View file

@ -327,7 +327,7 @@ Open the **Workflow** tab and choose **Wolf RPG (WolfDawn)** from the engine sel
| **0 Project** | Select the game root folder (browse or Enter — detection also runs when you reopen the tab). If `wolf_json/` does not exist yet, the tool automatically unpacks `.wolf` archives when needed and extracts text into the game's `wolf_json/` folder (maps, common events, databases, `Game.dat`, Evtext, and `names.json`). A checklist lists every JSON file in `wolf_json/`; tick the ones you want and click **Import** (or leave Step 0 — checked files auto-import into the tool's `files/` folder, matching the RPG Maker workflow). Extraction snapshots pristine binaries into `wolf_json/originals/` for idempotent inject in Step 6. | | **0 Project** | Select the game root folder (browse or Enter — detection also runs when you reopen the tab). If `wolf_json/` does not exist yet, the tool automatically unpacks `.wolf` archives when needed and extracts text into the game's `wolf_json/` folder (maps, common events, databases, `Game.dat`, Evtext, and `names.json`). A checklist lists every JSON file in `wolf_json/`; tick the ones you want and click **Import** (or leave Step 0 — checked files auto-import into the tool's `files/` folder, matching the RPG Maker workflow). Extraction snapshots pristine binaries into `wolf_json/originals/` for idempotent inject in Step 6. |
| **1 Pre-process** | Optional: **dazedformat** normalises JSON in `wolf_json/` and `files/` (`json.dump`, indent 4) for clean git diffs; **Copy gameupdate/** installs the updater scripts, patch scripts, and `.gitignore` into the game root for git-based patching. Paths auto-fill from Step 0. | | **1 Pre-process** | Optional: **dazedformat** normalises JSON in `wolf_json/` and `files/` (`json.dump`, indent 4) for clean git diffs; **Copy gameupdate/** installs the updater scripts, patch scripts, and `.gitignore` into the game root for git-based patching. Paths auto-fill from Step 0. |
| **2 Glossary** | Build `vocab.txt` before translating: copy the WOLF-tailored prompt into Cursor/Copilot with the extracted `files/` JSON, let it discover character names, speech registers, and lore terms, then paste the result into the in-tab editor and save. Item/skill/enemy value names (`names.json`) are translated in Step 3 and harvested into `vocab.txt` automatically during Phase 0 — do not list them here. | | **2 Glossary** | Build `vocab.txt` before translating: copy the WOLF-tailored prompt into Cursor/Copilot with the extracted `files/` JSON, let it discover character names, speech registers, and lore terms, then paste the result into the in-tab editor and save. Item/skill/enemy value names (`names.json`) are translated in Step 3 and harvested into `vocab.txt` automatically during Phase 0 — do not list them here. |
| **3 Names** | Translate `names.json` (item/skill/enemy/map value names). WolfDawn tags each name with a per-entry **safety** badge (`safe`, `refs`, or `verify`). Phase 0 translates only `safe` and `refs` entries; `verify` names stay Japanese so inject skips them. Review the category breakdown for this game, pick **Translation mode** (Normal or Batch), and run **Translate safe names (Phase 0)**. | | **3 Names** | Translate `names.json` (item/skill/enemy/map value names). WolfDawn tags each name with a per-entry **safety** badge (`safe`, `refs`, or `verify`). Phase 0 translates only `safe` entries; `refs` and `verify` names stay Japanese so inject skips them. Review the category breakdown for this game, pick **Translation mode** (Normal or Batch), and run **Translate safe names (Phase 0)**. |
| **4 Database** | Review the **discovery summary** to see where this game's text lives (standard RPG sheets vs custom dialogue tables). Database sheets are classified as foundation (items, skills, descriptions — translate first) or narrative (custom event/profile sheets — translate after foundation). Use **Translate foundation DB** then **Translate narrative DB**, or tick specific sheets and run **Translate checked sheets only**. Optional: copy the **DB structure prompt** for an AI audit and import the returned JSON into `wolf_json/db_profile.json`. | | **4 Database** | Review the **discovery summary** to see where this game's text lives (standard RPG sheets vs custom dialogue tables). Database sheets are classified as foundation (items, skills, descriptions — translate first) or narrative (custom event/profile sheets — translate after foundation). Use **Translate foundation DB** then **Translate narrative DB**, or tick specific sheets and run **Translate checked sheets only**. Optional: copy the **DB structure prompt** for an AI audit and import the returned JSON into `wolf_json/db_profile.json`. |
| **5 Maps/Events** | Translate map scripts (`.mps`), common events (`CommonEvent.dat`), `Game.dat`, and Evtext. Run after Steps 34 so vocab and database terms are consistent. Configure speaker handling for low-confidence nameplate guesses. Batch mode is recommended for large `CommonEvent.dat` files. | | **5 Maps/Events** | Translate map scripts (`.mps`), common events (`CommonEvent.dat`), `Game.dat`, and Evtext. Run after Steps 34 so vocab and database terms are consistent. Configure speaker handling for low-confidence nameplate guesses. Batch mode is recommended for large `CommonEvent.dat` files. |
| **6 Inject** | Write translations back into the game's `Data/` binaries and refresh git-tracked `wolf_json/`. Uses pristine originals from `wolf_json/originals/`. Quick inject or inject all. | | **6 Inject** | Write translations back into the game's `Data/` binaries and refresh git-tracked `wolf_json/`. Uses pristine originals from `wolf_json/originals/`. Quick inject or inject all. |

View file

@ -8,9 +8,9 @@ Mirrors the RPGMaker WorkflowTab, driven by the vendored WolfDawn ``wolf`` CLI
Step 1 Pre-process - optional dazedformat + gameupdate/ copy before translating Step 1 Pre-process - optional dazedformat + gameupdate/ copy before translating
Step 2 Glossary - build vocab.txt (characters / worldbuilding terms) before Step 2 Glossary - build vocab.txt (characters / worldbuilding terms) before
translating so the AI keeps names and voice consistent translating so the AI keeps names and voice consistent
Step 3 Names - translate names.json (Phase 0). WolfDawn safe/refs entries Step 3 Names - translate names.json (Phase 0). WolfDawn safe entries
are translated per-name; verify names are skipped. Harvests are translated per-name; refs and verify names are skipped.
short name terms into vocab.txt. Harvests short name terms into vocab.txt.
Step 4 Database - discover content layout; translate foundation DB sheets Step 4 Database - discover content layout; translate foundation DB sheets
(items, skills, descriptions) before narrative custom sheets (items, skills, descriptions) before narrative custom sheets
Step 5 Maps/Events - .mps maps, CommonEvent, Game.dat, Evtext; speaker handling Step 5 Maps/Events - .mps maps, CommonEvent, Game.dat, Evtext; speaker handling
@ -24,7 +24,7 @@ Mirrors the RPGMaker WorkflowTab, driven by the vendored WolfDawn ``wolf`` CLI
Step 9 Saves - fix baked strings in existing .sav files (optional) Step 9 Saves - fix baked strings in existing .sav files (optional)
names.json is staged into files/ but is NOT translated in the bulk phases - WolfDawn names.json is staged into files/ but is NOT translated in the bulk phases - WolfDawn
tags each name safe / refs / verify and Phase 0 translates only safe+refs entries tags each name safe / refs / verify and Phase 0 translates only safe entries
(per-name, not per-category), harvests them into vocab.txt, and Step 6 injects the (per-name, not per-category), harvests them into vocab.txt, and Step 6 injects the
result. result.
@ -1732,7 +1732,7 @@ class WolfWorkflowTab(QWidget):
note = self._desc( note = self._desc(
"You don't need to add item / skill / enemy value names here: Phase 0 translates " "You don't need to add item / skill / enemy value names here: Phase 0 translates "
"WolfDawn safe/refs entries from names.json and harvests them into vocab.txt " "WolfDawn safe entries from names.json and harvests them into vocab.txt "
"Focus this glossary on characters and worldbuilding terms." "Focus this glossary on characters and worldbuilding terms."
) )
layout.addWidget(note) layout.addWidget(note)
@ -2305,8 +2305,8 @@ class WolfWorkflowTab(QWidget):
layout.addWidget(_make_section_label("Step 3 · Translate Name Values (names.json)")) layout.addWidget(_make_section_label("Step 3 · Translate Name Values (names.json)"))
layout.addWidget(self._desc( layout.addWidget(self._desc(
"names.json is WolfDawn's project-wide name glossary. Each entry has a safety badge: " "names.json is WolfDawn's project-wide name glossary. Each entry has a safety badge: "
"safe (display-only), refs (referenced by name but rewritten on inject), or verify " "safe (display-only), refs (referenced by name - skipped), or verify "
"(also in indirect literals - skipped). Phase 0 translates every safe and refs entry " "(also in indirect literals - skipped). Phase 0 translates every safe entry "
"individually, regardless of category, and harvests them into vocab.txt." "individually, regardless of category, and harvests them into vocab.txt."
)) ))
@ -2325,8 +2325,8 @@ class WolfWorkflowTab(QWidget):
layout.addWidget(_make_hr()) layout.addWidget(_make_hr())
layout.addWidget(self._subheading("Translate names (Phase 0)")) layout.addWidget(self._subheading("Translate names (Phase 0)"))
layout.addWidget(self._desc( layout.addWidget(self._desc(
"Pick Normal or Batch below, then run Phase 0. Only safe and refs entries are sent to " "Pick Normal or Batch below, then run Phase 0. Only safe entries are sent to "
"the model; verify names stay identical to source so inject skips them." "the model; refs and verify names stay identical to source so inject skips them."
)) ))
self._add_tl_mode_selector(layout) self._add_tl_mode_selector(layout)
tl_btn = self._register(_make_btn("Translate safe names now (Phase 0)", "#00a86b")) tl_btn = self._register(_make_btn("Translate safe names now (Phase 0)", "#00a86b"))
@ -2531,7 +2531,7 @@ class WolfWorkflowTab(QWidget):
layout.addWidget(_make_section_label("Step 6 · Inject Translations")) layout.addWidget(_make_section_label("Step 6 · Inject Translations"))
layout.addWidget(self._desc( layout.addWidget(self._desc(
"Writes the translated text back into the game's Data/ binaries with WolfDawn, " "Writes the translated text back into the game's Data/ binaries with WolfDawn, "
"byte-exact. Translated safe/refs name values from names.json are applied across Data/ " "byte-exact. Translated safe name values from names.json are applied across Data/ "
"(only the entries you translated change). Lines whose inline codes changed are skipped and " "(only the entries you translated change). Lines whose inline codes changed are skipped and "
"reported (unless you allow drift). Full inject and any inject that includes " "reported (unless you allow drift). Full inject and any inject that includes "
f"{NAMES_JSON} reset live Data/ from {WORK_DIR_NAME}/originals/ first. " f"{NAMES_JSON} reset live Data/ from {WORK_DIR_NAME}/originals/ first. "

View file

@ -22,7 +22,7 @@ and Japanese embedded only in WOLF control codes (e.g. ``\\r[...]`` ruby).
names.json safety: WolfDawn tags every value name with a static ``safety`` badge names.json safety: WolfDawn tags every value name with a static ``safety`` badge
(``safe``, ``refs``, or ``verify``). For ``kind == "names"`` documents, only (``safe``, ``refs``, or ``verify``). For ``kind == "names"`` documents, only
entries whose badge is ``safe`` or ``refs`` are translated; ``verify`` names and entries whose badge is ``safe`` are translated; ``refs`` and ``verify`` names and
legacy entries without a badge keep ``text == source``. After translating legacy entries without a badge keep ``text == source``. After translating
names.json, translatable entries are harvested into ``vocab.txt`` (grouped by names.json, translatable entries are harvested into ``vocab.txt`` (grouped by
``note``, with bilingual headers) so later phases keep item / skill / term names ``note``, with bilingual headers) so later phases keep item / skill / term names
@ -110,7 +110,7 @@ _WOLF_CODE_RE = re.compile(
# configurable from the workflow (data/wolf_speakers.json). # configurable from the workflow (data/wolf_speakers.json).
SPEAKER_CONFIG = wolf_speakers.load_config() SPEAKER_CONFIG = wolf_speakers.load_config()
# names.json: translate per-entry safe/refs badges only (see util.wolfdawn.names). # names.json: translate per-entry safe badges only (see util.wolfdawn.names).
# Text wrapping: optional advanced rewrap via dazedwrap (wolfWrap/wolfWidth). Defaults # Text wrapping: optional advanced rewrap via dazedwrap (wolfWrap/wolfWidth). Defaults
# off - the workflow uses WolfDawn relayout after inject instead. # off - the workflow uses WolfDawn relayout after inject instead.
@ -342,13 +342,13 @@ def parseDocument(data, filename):
# Fresh extracts keep ``text == source``, so they still queue. # Fresh extracts keep ``text == source``, so they still queue.
if IGNORETLTEXT and not _text_still_needs_translation(e): if IGNORETLTEXT and not _text_still_needs_translation(e):
return False return False
# names.json: only translate WolfDawn safe/refs entries (per-name badge). # names.json: only translate WolfDawn safe entries (per-name badge).
if is_names and not wolf_names.is_name_translatable(e): if is_names and not wolf_names.is_name_translatable(e):
return False return False
return True return True
# Only translate entries that still need work; names.json also requires a # Only translate entries that still need work; names.json also requires a
# safe/refs badge. Untouched leaves keep ``text == source`` so WolfDawn # safe badge. Untouched leaves keep ``text == source`` so WolfDawn
# treats them as no-ops on inject. # treats them as no-ops on inject.
translatable = [e for e in entries if _translatable(e)] translatable = [e for e in entries if _translatable(e)]

View file

@ -18,11 +18,11 @@ from util.wolfdawn import names as wn # noqa: E402
class TestNameTranslatable(unittest.TestCase): class TestNameTranslatable(unittest.TestCase):
def test_safe_and_refs_are_translatable(self): def test_safe_is_translatable(self):
self.assertTrue(wn.is_name_translatable({"safety": "safe"})) self.assertTrue(wn.is_name_translatable({"safety": "safe"}))
self.assertTrue(wn.is_name_translatable({"safety": "refs"}))
def test_verify_and_missing_are_not_translatable(self): def test_refs_verify_and_missing_are_not_translatable(self):
self.assertFalse(wn.is_name_translatable({"safety": "refs"}))
self.assertFalse(wn.is_name_translatable({"safety": "verify"})) self.assertFalse(wn.is_name_translatable({"safety": "verify"}))
self.assertFalse(wn.is_name_translatable({})) self.assertFalse(wn.is_name_translatable({}))
self.assertFalse(wn.is_name_translatable({"safety": ""})) self.assertFalse(wn.is_name_translatable({"safety": ""}))
@ -67,11 +67,11 @@ class TestCountNameSafety(unittest.TestCase):
self.assertEqual(counts["refs"], 1) self.assertEqual(counts["refs"], 1)
self.assertEqual(counts["verify"], 1) self.assertEqual(counts["verify"], 1)
self.assertEqual(counts["unknown"], 1) self.assertEqual(counts["unknown"], 1)
self.assertEqual(counts["translatable"], 2) self.assertEqual(counts["translatable"], 1)
def test_summary_mentions_translatable_count(self): def test_summary_mentions_translatable_count(self):
summary = wn.format_name_safety_summary(self.DOC) summary = wn.format_name_safety_summary(self.DOC)
self.assertIn("2 of 4", summary) self.assertIn("1 of 4", summary)
self.assertIn("verify", summary) self.assertIn("verify", summary)

View file

@ -173,15 +173,15 @@ class TestTranslationWriteback(unittest.TestCase):
self.assertIsNone(err) self.assertIsNone(err)
self.assertEqual(data["lines"][0]["text"], "EN_ゲームタイトル") self.assertEqual(data["lines"][0]["text"], "EN_ゲームタイトル")
def test_names_translate_only_safe_and_refs(self): def test_names_translate_only_safe(self):
(data, _t, err), captured = _WolfTranslateHarness().run( (data, _t, err), captured = _WolfTranslateHarness().run(
NAMES_DOC, "names.json" NAMES_DOC, "names.json"
) )
self.assertIsNone(err) self.assertIsNone(err)
self.assertEqual(data["names"][0]["text"], "EN_剣") self.assertEqual(data["names"][0]["text"], "EN_剣")
self.assertEqual(data["names"][1]["text"], "EN_") self.assertEqual(data["names"][1]["text"], "")
self.assertEqual(data["names"][2]["text"], "スイッチ状態") self.assertEqual(data["names"][2]["text"], "スイッチ状態")
self.assertEqual(captured, [["", ""]]) self.assertEqual(captured, [[""]])
def test_names_without_safety_badges_translate_nothing(self): def test_names_without_safety_badges_translate_nothing(self):
(data, _t, err), captured = _WolfTranslateHarness().run( (data, _t, err), captured = _WolfTranslateHarness().run(
@ -198,7 +198,7 @@ class TestTranslationWriteback(unittest.TestCase):
self.assertEqual( self.assertEqual(
harness.vocab_writes, harness.vocab_writes,
[ [
("Weapon · 武器", [("", "EN_剣"), ("", "EN_槍")]), ("Weapon · 武器", [("", "EN_剣")]),
], ],
) )
@ -451,7 +451,7 @@ class TestTranslationWriteback(unittest.TestCase):
self.assertEqual(captured, []) self.assertEqual(captured, [])
self.assertEqual( self.assertEqual(
harness.vocab_writes, harness.vocab_writes,
[("Weapon · 武器", [("", "Sword"), ("", "Spear")])], [("Weapon · 武器", [("", "Sword")])],
) )

View file

@ -6,11 +6,11 @@ a ``note`` field (the WOLF database category it came from), and a static
``safety`` badge from WolfDawn's command-stream analysis: ``safety`` badge from WolfDawn's command-stream analysis:
* ``safe`` - display-only; no command references the string by name. * ``safe`` - display-only; no command references the string by name.
* ``refs`` - referenced by name, but WolfDawn rewrites every literal on inject. * ``refs`` - referenced by name (picture codes, switch labels, etc.); skipped.
* ``verify`` - also appears in indirect literals; left untranslated by default. * ``verify`` - also appears in indirect literals; left untranslated by default.
:mod:`modules.wolfdawn` translates only entries whose badge is ``safe`` or :mod:`modules.wolfdawn` translates only entries whose badge is ``safe``.
``refs``. ``verify`` and legacy entries without a badge keep ``text == source`` ``refs``, ``verify``, and legacy entries without a badge keep ``text == source``
so injection is a no-op for them. so injection is a no-op for them.
Phase 0 still harvests translated **short name-like** entries into ``vocab.txt``, Phase 0 still harvests translated **short name-like** entries into ``vocab.txt``,
@ -27,7 +27,7 @@ from typing import Any
SAFETY_SAFE = "safe" SAFETY_SAFE = "safe"
SAFETY_REFS = "refs" SAFETY_REFS = "refs"
SAFETY_VERIFY = "verify" SAFETY_VERIFY = "verify"
_TRANSLATABLE_SAFETY = frozenset({SAFETY_SAFE, SAFETY_REFS}) _TRANSLATABLE_SAFETY = frozenset({SAFETY_SAFE})
# names.json ``note`` values that are content fields, not short display names. # names.json ``note`` values that are content fields, not short display names.
_VOCAB_SKIP_NOTE_RE = re.compile( _VOCAB_SKIP_NOTE_RE = re.compile(
@ -77,7 +77,7 @@ def entry_safety(entry: dict[str, Any]) -> str:
def is_name_translatable(entry: dict[str, Any]) -> bool: def is_name_translatable(entry: dict[str, Any]) -> bool:
"""True when WolfDawn marks *entry* as safe or refs (per-name, not per-category).""" """True when WolfDawn marks *entry* as safe (per-name, not per-category)."""
return entry_safety(entry) in _TRANSLATABLE_SAFETY return entry_safety(entry) in _TRANSLATABLE_SAFETY
@ -127,7 +127,7 @@ def count_name_safety(data: dict[str, Any]) -> dict[str, int]:
counts["verify"] += 1 counts["verify"] += 1
else: else:
counts["unknown"] += 1 counts["unknown"] += 1
counts["translatable"] = counts["safe"] + counts["refs"] counts["translatable"] = counts["safe"]
return counts return counts
@ -151,7 +151,7 @@ def format_name_safety_summary(data: dict[str, Any]) -> str:
badge_text = ", ".join(parts) if parts else "no badges" badge_text = ", ".join(parts) if parts else "no badges"
return ( return (
f"{counts['translatable']} of {counts['total']} name(s) will translate " f"{counts['translatable']} of {counts['total']} name(s) will translate "
f"({badge_text}). Verify names are skipped." f"({badge_text}). Refs and verify names are skipped."
) )