diff --git a/modules/rpgmakermvmz.py b/modules/rpgmakermvmz.py index abfafb1..eae793a 100644 --- a/modules/rpgmakermvmz.py +++ b/modules/rpgmakermvmz.py @@ -588,6 +588,22 @@ def _101_name_source(cmd, is_var: bool) -> str: return "" +def _101_speaker_name(raw_name: str) -> str: + """Return the translatable name inside a code-101 display wrapper. + + Parameter 4 may use ``【Name】`` as its visible name-window styling. Those + brackets belong in the 101 field, not in the ``[Speaker]:`` transport prefix + temporarily attached to the following 401 dialogue. + """ + name = str(raw_name or "").strip() + name = re.sub(r"^(?:[\\]+[cC]\[\d+\]\s*)+", "", name) + bracketed = re.match(r"^【\s*([^】]+?)\s*】", name) + if bracketed: + return bracketed.group(1).strip() + plain = re.match(r"^([^\\]+)", name) + return plain.group(1).strip() if plain else "" + + def _entry_orig(entry) -> dict: """Return _original dict on a database entry, or empty dict if absent.""" orig = entry.get("_original") if isinstance(entry, dict) else None @@ -3136,9 +3152,8 @@ def searchCodes(page, pbar, jobList, filename): # Get Speaker rawName = _101_name_source(codeList[i], isVar) - match = re.search(r"^(?:[\\]+[cC]\[\d+?\])?([^\\]+)", rawName) - if match: - sourceName = match.group(1) + sourceName = _101_speaker_name(rawName) + if sourceName: response = getSpeaker(sourceName) totalTokens[0] += response[1][0] totalTokens[1] += response[1][1] diff --git a/tests/test_mvmz_source_original.py b/tests/test_mvmz_source_original.py index 1e29416..9be8e2b 100644 --- a/tests/test_mvmz_source_original.py +++ b/tests/test_mvmz_source_original.py @@ -112,7 +112,7 @@ def _resolve_case_command(page_list, entry, marked_cases=None): return cmd -def _run_search_codes(page, *, preserve_original=True): +def _run_search_codes(page, *, preserve_original=True, speaker_fn=None): """Full Pass 1 -> mock translate -> Pass 2 cycle.""" captured = [] @@ -121,7 +121,7 @@ def _run_search_codes(page, *, preserve_original=True): return _mock_translate(text, history, batch) def speaker(name): - return _mock_speaker(name) + return speaker_fn(name) if speaker_fn is not None else _mock_speaker(name) orig_t = mvmz.translateAI orig_s = mvmz.getSpeaker @@ -194,6 +194,45 @@ class TestMVMZSourceOriginal(unittest.TestCase): self.assertEqual(c122["_original"], "変数テスト") self.assertIn("EN_TRANSLATED", c122["parameters"][4]) + def test_101_display_brackets_do_not_leak_into_401_dialogue(self): + speakers_seen = [] + + def speaker(name): + speakers_seen.append(name) + return ["Game Description", [0, 0]] + + page = { + "list": [ + { + "code": 101, + "indent": 0, + "parameters": ["", 0, 0, 2, "【[Game Description]】"], + "_original": "【ゲーム説明】", + }, + { + "code": 401, + "indent": 0, + "parameters": ["ここから本編スタートとなります。"], + }, + ] + } + + translated, captured = _run_search_codes(page, speaker_fn=speaker) + cmd101, cmd401 = translated["list"] + + self.assertTrue(speakers_seen) + self.assertEqual(set(speakers_seen), {"ゲーム説明"}) + self.assertEqual(cmd101["parameters"][4], "【Game Description】") + self.assertEqual(cmd401["parameters"][0], "EN_TRANSLATED") + self.assertNotIn("Game Description", cmd401["parameters"][0]) + self.assertTrue( + any( + isinstance(item, str) and item.startswith("[Game Description]: ") + for payload in captured + for item in (payload if isinstance(payload, list) else [payload]) + ) + ) + def test_rerun_uses_original_not_display_text(self): page1, captured1 = _run_search_codes(_load_map_excerpt()) originals_snapshot = json.dumps(