From 4abb10df15d5298f3c07fa26c0cbc41df7cd0dfc Mon Sep 17 00:00:00 2001 From: DazedAnon Date: Thu, 9 Jul 2026 16:20:19 -0500 Subject: [PATCH] Pass 1 should TL Speakers --- modules/wolfdawn.py | 35 +++++++++++++++++++++----------- tests/test_wolfdawn.py | 46 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/modules/wolfdawn.py b/modules/wolfdawn.py index 6a449b3..efb2879 100644 --- a/modules/wolfdawn.py +++ b/modules/wolfdawn.py @@ -412,28 +412,37 @@ def _body_after_dropped_tag(text: str, original_speaker: str) -> str: return text -def _maybe_fix_nameplate(entry, total_tokens: list) -> None: - """Swap a Japanese first-line nameplate for English on an otherwise-done line.""" - if ESTIMATE or _batch_phase() == "collect": - return +def _maybe_fix_nameplate(entry, total_tokens: list) -> bool: + """Resolve a Japanese first-line nameplate on an otherwise-done line. + + Returns True when the nameplate was rewritten. During batch collect the + in-memory rewrite is discarded (translated/ is not written), but + ``getSpeaker`` still runs so the English gloss is cached before dialogue + batches are queued - otherwise those live speaker calls only show up in + Pass 2. + """ + if ESTIMATE: + return False speaker_src = entry.get("speaker_src", "") if not wolf_speakers.is_firstline_enabled(speaker_src, SPEAKER_CONFIG): - return + return False txt = entry.get("text") if not isinstance(txt, str) or not txt.strip(): - return + return False # Only touch finished dialogue (body already English / skip-translated). if _text_still_needs_translation(entry): - return + return False split = wolf_speakers.split_source(txt, speaker_src, SPEAKER_CONFIG) if split is None: - return + return False prefix, speaker, body = split if not re.search(LANGREGEX, speaker): - return + return False speaker_en = _resolve_nameplate(speaker, total_tokens) if speaker_en and speaker_en != speaker: entry["text"] = wolf_speakers.restore_source(prefix, speaker_en, body) + return True + return False def parseDocument(data, filename): @@ -457,9 +466,11 @@ def parseDocument(data, filename): return False return True - # Fix Japanese nameplates on lines whose dialogue body is already done, so - # resume / re-wrap runs do not leave ``セルリア\\nPlease hold on...`` behind. - if IGNORETLTEXT and not ESTIMATE and _batch_phase() != "collect": + # Resolve Japanese nameplates on lines whose dialogue body is already done. + # Must run during batch collect too: those lines are skip-translated so they + # never hit the reshape/getSpeaker path, and deferring to consume is what + # made speaker Input/Output spam show up in Pass 2 after the batch returned. + if IGNORETLTEXT and not ESTIMATE: for entry in entries: if _translatable(entry): continue diff --git a/tests/test_wolfdawn.py b/tests/test_wolfdawn.py index c02e5a0..22795cc 100644 --- a/tests/test_wolfdawn.py +++ b/tests/test_wolfdawn.py @@ -470,6 +470,52 @@ class TestTranslationWriteback(unittest.TestCase): # Short-string speaker resolve, then the remaining dialogue batch. self.assertEqual(captured, ["司祭", ["まだだ"]]) + def test_collect_resolves_japanese_nameplate_before_queueing(self): + """Pass 1 must live-translate skip-translated JP nameplates (not defer to Pass 2).""" + doc = { + "kind": "map", + "scenes": [ + { + "event": 1, + "name": "ev", + "lines": [ + { + "cmd": 0, + "str": 0, + "speaker": "客F", + "speaker_src": "literal_line1_lowconf", + "source": "客F\nまぁまぁ。", + "text": "客F\nNow, now.", + }, + { + "cmd": 1, + "str": 0, + "speaker": "UI", + "speaker_src": "ui", + "source": "まだだ", + "text": "まだだ", + }, + ], + } + ], + } + orig_phase = os.environ.get("BATCH_PHASE") + os.environ["BATCH_PHASE"] = "collect" + try: + (data, _t, err), captured = _WolfTranslateHarness().run(doc, "collect_nameplate.mps.json") + finally: + if orig_phase is None: + os.environ.pop("BATCH_PHASE", None) + else: + os.environ["BATCH_PHASE"] = orig_phase + + self.assertIsNone(err) + # Speaker resolved live during collect; dialogue list is also seen (queued). + self.assertEqual(captured[0], "客F") + self.assertEqual(captured[1], ["まだだ"]) + # Collect does not persist translated/, but in-memory text is updated. + self.assertEqual(data["scenes"][0]["lines"][0]["text"], "En_客F\nNow, now.") + def test_ignore_tl_text_false_retranslates(self): doc = { "kind": "map",