Pass 1 should TL Speakers

This commit is contained in:
DazedAnon 2026-07-09 16:20:19 -05:00
parent f25ce9f990
commit 4abb10df15
2 changed files with 69 additions and 12 deletions

View file

@ -412,28 +412,37 @@ def _body_after_dropped_tag(text: str, original_speaker: str) -> str:
return text return text
def _maybe_fix_nameplate(entry, total_tokens: list) -> None: def _maybe_fix_nameplate(entry, total_tokens: list) -> bool:
"""Swap a Japanese first-line nameplate for English on an otherwise-done line.""" """Resolve a Japanese first-line nameplate on an otherwise-done line.
if ESTIMATE or _batch_phase() == "collect":
return 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", "") speaker_src = entry.get("speaker_src", "")
if not wolf_speakers.is_firstline_enabled(speaker_src, SPEAKER_CONFIG): if not wolf_speakers.is_firstline_enabled(speaker_src, SPEAKER_CONFIG):
return return False
txt = entry.get("text") txt = entry.get("text")
if not isinstance(txt, str) or not txt.strip(): if not isinstance(txt, str) or not txt.strip():
return return False
# Only touch finished dialogue (body already English / skip-translated). # Only touch finished dialogue (body already English / skip-translated).
if _text_still_needs_translation(entry): if _text_still_needs_translation(entry):
return return False
split = wolf_speakers.split_source(txt, speaker_src, SPEAKER_CONFIG) split = wolf_speakers.split_source(txt, speaker_src, SPEAKER_CONFIG)
if split is None: if split is None:
return return False
prefix, speaker, body = split prefix, speaker, body = split
if not re.search(LANGREGEX, speaker): if not re.search(LANGREGEX, speaker):
return return False
speaker_en = _resolve_nameplate(speaker, total_tokens) speaker_en = _resolve_nameplate(speaker, total_tokens)
if speaker_en and speaker_en != speaker: if speaker_en and speaker_en != speaker:
entry["text"] = wolf_speakers.restore_source(prefix, speaker_en, body) entry["text"] = wolf_speakers.restore_source(prefix, speaker_en, body)
return True
return False
def parseDocument(data, filename): def parseDocument(data, filename):
@ -457,9 +466,11 @@ def parseDocument(data, filename):
return False return False
return True return True
# Fix Japanese nameplates on lines whose dialogue body is already done, so # Resolve Japanese nameplates on lines whose dialogue body is already done.
# resume / re-wrap runs do not leave ``セルリア\\nPlease hold on...`` behind. # Must run during batch collect too: those lines are skip-translated so they
if IGNORETLTEXT and not ESTIMATE and _batch_phase() != "collect": # 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: for entry in entries:
if _translatable(entry): if _translatable(entry):
continue continue

View file

@ -470,6 +470,52 @@ class TestTranslationWriteback(unittest.TestCase):
# Short-string speaker resolve, then the remaining dialogue batch. # Short-string speaker resolve, then the remaining dialogue batch.
self.assertEqual(captured, ["司祭", ["まだだ"]]) 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_客\nNow, now.")
def test_ignore_tl_text_false_retranslates(self): def test_ignore_tl_text_false_retranslates(self):
doc = { doc = {
"kind": "map", "kind": "map",