diff --git a/README.md b/README.md index 39c6bd4..6ad321d 100644 --- a/README.md +++ b/README.md @@ -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. | | **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. | -| **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`. | | **5 Maps/Events** | Translate map scripts (`.mps`), common events (`CommonEvent.dat`), `Game.dat`, and Evtext. Run after Steps 3–4 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. | diff --git a/gui/wolf_workflow_tab.py b/gui/wolf_workflow_tab.py index ca1611f..3a0b905 100644 --- a/gui/wolf_workflow_tab.py +++ b/gui/wolf_workflow_tab.py @@ -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 2 Glossary - build vocab.txt (characters / worldbuilding terms) before translating so the AI keeps names and voice consistent - Step 3 Names - translate names.json (Phase 0). WolfDawn safe/refs entries - are translated per-name; verify names are skipped. Harvests - short name terms into vocab.txt. + Step 3 Names - translate names.json (Phase 0). WolfDawn safe entries + are translated per-name; refs and verify names are skipped. + Harvests short name terms into vocab.txt. Step 4 Database - discover content layout; translate foundation DB sheets (items, skills, descriptions) before narrative custom sheets 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) 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 result. @@ -1732,7 +1732,7 @@ class WolfWorkflowTab(QWidget): note = self._desc( "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." ) layout.addWidget(note) @@ -2305,8 +2305,8 @@ class WolfWorkflowTab(QWidget): layout.addWidget(_make_section_label("Step 3 · Translate Name Values (names.json)")) layout.addWidget(self._desc( "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 " - "(also in indirect literals - skipped). Phase 0 translates every safe and refs entry " + "safe (display-only), refs (referenced by name - skipped), or verify " + "(also in indirect literals - skipped). Phase 0 translates every safe entry " "individually, regardless of category, and harvests them into vocab.txt." )) @@ -2325,8 +2325,8 @@ class WolfWorkflowTab(QWidget): layout.addWidget(_make_hr()) layout.addWidget(self._subheading("Translate names (Phase 0)")) layout.addWidget(self._desc( - "Pick Normal or Batch below, then run Phase 0. Only safe and refs entries are sent to " - "the model; verify names stay identical to source so inject skips them." + "Pick Normal or Batch below, then run Phase 0. Only safe entries are sent to " + "the model; refs and verify names stay identical to source so inject skips them." )) self._add_tl_mode_selector(layout) 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(self._desc( "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 " "reported (unless you allow drift). Full inject and any inject that includes " f"{NAMES_JSON} reset live Data/ from {WORK_DIR_NAME}/originals/ first. " diff --git a/modules/wolfdawn.py b/modules/wolfdawn.py index c5cc572..3420bac 100644 --- a/modules/wolfdawn.py +++ b/modules/wolfdawn.py @@ -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 (``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 names.json, translatable entries are harvested into ``vocab.txt`` (grouped by ``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). 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 # 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. if IGNORETLTEXT and not _text_still_needs_translation(e): 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): return False return True # 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. translatable = [e for e in entries if _translatable(e)] diff --git a/tests/test_wolf_names.py b/tests/test_wolf_names.py index 3f915e7..58794b8 100644 --- a/tests/test_wolf_names.py +++ b/tests/test_wolf_names.py @@ -18,11 +18,11 @@ from util.wolfdawn import names as wn # noqa: E402 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": "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({})) self.assertFalse(wn.is_name_translatable({"safety": ""})) @@ -67,11 +67,11 @@ class TestCountNameSafety(unittest.TestCase): self.assertEqual(counts["refs"], 1) self.assertEqual(counts["verify"], 1) self.assertEqual(counts["unknown"], 1) - self.assertEqual(counts["translatable"], 2) + self.assertEqual(counts["translatable"], 1) def test_summary_mentions_translatable_count(self): summary = wn.format_name_safety_summary(self.DOC) - self.assertIn("2 of 4", summary) + self.assertIn("1 of 4", summary) self.assertIn("verify", summary) diff --git a/tests/test_wolfdawn.py b/tests/test_wolfdawn.py index 0334c6f..025995f 100644 --- a/tests/test_wolfdawn.py +++ b/tests/test_wolfdawn.py @@ -173,15 +173,15 @@ class TestTranslationWriteback(unittest.TestCase): self.assertIsNone(err) 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( NAMES_DOC, "names.json" ) self.assertIsNone(err) 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(captured, [["剣", "槍"]]) + self.assertEqual(captured, [["剣"]]) def test_names_without_safety_badges_translate_nothing(self): (data, _t, err), captured = _WolfTranslateHarness().run( @@ -198,7 +198,7 @@ class TestTranslationWriteback(unittest.TestCase): self.assertEqual( harness.vocab_writes, [ - ("Weapon · 武器", [("剣", "EN_剣"), ("槍", "EN_槍")]), + ("Weapon · 武器", [("剣", "EN_剣")]), ], ) @@ -451,7 +451,7 @@ class TestTranslationWriteback(unittest.TestCase): self.assertEqual(captured, []) self.assertEqual( harness.vocab_writes, - [("Weapon · 武器", [("剣", "Sword"), ("槍", "Spear")])], + [("Weapon · 武器", [("剣", "Sword")])], ) diff --git a/util/wolfdawn/names.py b/util/wolfdawn/names.py index f36e397..58ff6f8 100644 --- a/util/wolfdawn/names.py +++ b/util/wolfdawn/names.py @@ -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: * ``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. -:mod:`modules.wolfdawn` translates only entries whose badge is ``safe`` or -``refs``. ``verify`` and legacy entries without a badge keep ``text == source`` +:mod:`modules.wolfdawn` translates only entries whose badge is ``safe``. +``refs``, ``verify``, and legacy entries without a badge keep ``text == source`` so injection is a no-op for them. Phase 0 still harvests translated **short name-like** entries into ``vocab.txt``, @@ -27,7 +27,7 @@ from typing import Any SAFETY_SAFE = "safe" SAFETY_REFS = "refs" 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. _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: - """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 @@ -127,7 +127,7 @@ def count_name_safety(data: dict[str, Any]) -> dict[str, int]: counts["verify"] += 1 else: counts["unknown"] += 1 - counts["translatable"] = counts["safe"] + counts["refs"] + counts["translatable"] = counts["safe"] 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" return ( 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." )