From 4f122e13b3bbd558b3897d0e3c8cb94f6c333c7e Mon Sep 17 00:00:00 2001 From: dazedanon Date: Fri, 6 Mar 2026 17:26:48 -0600 Subject: [PATCH] Fix json schema for claude --- modules/rpgmakermvmz.py | 75 +++++++++++++++++++++++++++++++---------- util/translation.py | 14 +++----- 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/modules/rpgmakermvmz.py b/modules/rpgmakermvmz.py index 3e45360..4bc754c 100644 --- a/modules/rpgmakermvmz.py +++ b/modules/rpgmakermvmz.py @@ -2233,23 +2233,23 @@ def searchCodes(page, pbar, jobList, filename): # Map Plugins headerMappings = { - "LL_InfoPopupWIndow": (["messageText"], None), - "QuestSystem": (["DetailNote"], None), - "BalloonInBattle": (["text"], None), - "MNKR_CommonPopupCoreMZ": (["text"], None), - "DestinationWindow": (["destination"], None), - "_TMLogWindowMZ": (["text"], None), - "TorigoyaMZ_NotifyMessage": (["message"], None), - "SoR_GabWindow": (["arg1"], None), - "DarkPlasma_CharacterText": (["text"], None), - "DTextPicture": (["text"], None), - "TextPicture": (["text"], None), - # "TRP_SkitMZ": (["name"], None), - "LogWindow": (["text"], None), - "BattleLogOutput": (["message"], None), - "TorigoyaMZ_NotifyMessage_CommandMessage": (["message"], None), - "NUUN_SaveScreen": (["AnyName"], None), - "build/ARPG_Core": (["Text", "SkillByName"], None), + # "LL_InfoPopupWIndow": (["messageText"], None), + # "QuestSystem": (["DetailNote"], None), + # "BalloonInBattle": (["text"], None), + # "MNKR_CommonPopupCoreMZ": (["text"], None), + # "DestinationWindow": (["destination"], None), + # "_TMLogWindowMZ": (["text"], None), + # "TorigoyaMZ_NotifyMessage": (["message"], None), + # "SoR_GabWindow": (["arg1"], None), + # "DarkPlasma_CharacterText": (["text"], None), + # "DTextPicture": (["text"], None), + # "TextPicture": (["text"], None), + # # "TRP_SkitMZ": (["name"], None), + # "LogWindow": (["text"], None), + # "BattleLogOutput": (["message"], None), + # "TorigoyaMZ_NotifyMessage_CommandMessage": (["message"], None), + # "NUUN_SaveScreen": (["AnyName"], None), + # "build/ARPG_Core": (["Text", "SkillByName"], None), } for key, (argVars, font) in headerMappings.items(): @@ -2320,6 +2320,47 @@ def searchCodes(page, pbar, jobList, filename): params_obj[chosen_key] = translatedText + # VisuMZ_4_ProximityMessages handler + # Text:json value is stored as a JSON-encoded string, e.g. "\"\\\\{\\\\{text\"" + # After Python JSON parsing: "\\{\\{text" (outer quotes + \\{ formatting prefix) + if "VisuMZ_4_ProximityMessages" in headerString and len(codeList[i]["parameters"]) > 3: + params_obj = codeList[i]["parameters"][3] + if isinstance(params_obj, dict) and "Text:json" in params_obj: + rawValue = params_obj["Text:json"] + if isinstance(rawValue, str): + # Strip outer JSON quotes ("\"...\"" wrapper) + innerMatch = re.match(r'^"(.*)"$', rawValue, re.DOTALL) + innerText = innerMatch.group(1) if innerMatch else rawValue + + # Preserve \\{ / \\} RPGMaker font-size codes at start and end + prefixMatch = re.match(r'^((?:\\\\[{}])+)', innerText) + prefix = prefixMatch.group(1) if prefixMatch else "" + remaining = innerText[len(prefix):] + suffixMatch = re.search(r'((?:\\\\[{}])+)$', remaining) + suffix = suffixMatch.group(1) if suffixMatch else "" + jaString = remaining[: len(remaining) - len(suffix)] if suffix else remaining + + # Skip if IGNORETLTEXT is enabled and no Japanese text + skip = IGNORETLTEXT and not re.search(LANGREGEX, jaString) + if not skip and jaString.strip(): + # Pass 1 + if setData: + list357.append(jaString) + # Pass 2 + else: + if len(list357) > 0: + translatedText = list357[0] + list357.pop(0) + + # Remove characters that would break the JSON string encoding + translatedText = translatedText.replace('"', "'") + + # Normalize color/name codes to 4 backslashes (required for Text:json encoding) + translatedText = re.sub(r'\\+([cCnNiIvV]\[\d+\])', r'\\\\\\\\\1', translatedText) + + # Reassemble: restore outer quotes and formatting codes + params_obj["Text:json"] = f'"{prefix}{translatedText}{suffix}"' + if headerString == "LL_GalgeChoiceWindow": ### Message Text First jaString = codeList[i]["parameters"][3]["messageText"] diff --git a/util/translation.py b/util/translation.py index 23a815e..40bb3b9 100644 --- a/util/translation.py +++ b/util/translation.py @@ -726,18 +726,16 @@ def translateText(system, user, history, penalty, formatType, model, numLines=No if formatType == "json" and numLines is not None: schema = createTranslationSchema(numLines) - if _is_claude: - responseFormat = {"type": "text"} # unused for Claude; controlled via extra_body - _claude_output_config = {"format": {"type": "json_schema", "schema": schema}} - elif _is_deepseek: + if _is_deepseek: + # Deepseek: use json_object (no strict schema support) responseFormat = {"type": "json_object"} - _claude_output_config = None else: + # OpenAI, Claude, Gemini: use json_schema with strict enforcement responseFormat = { "type": "json_schema", "json_schema": {"name": "translation_response", "strict": True, "schema": schema} } - _claude_output_config = None + _claude_output_config = None else: responseFormat = {"type": "text"} _claude_output_config = None @@ -784,10 +782,6 @@ def translateText(system, user, history, penalty, formatType, model, numLines=No # frequency_penalty is not supported via the OpenAI compatibility layer for Gemini elif _is_claude: params["temperature"] = 0 - # Claude uses output_config.format instead of response_format - del params["response_format"] - if _claude_output_config: - params["extra_body"] = {"output_config": _claude_output_config} else: # Default to OpenAI behavior if "gpt-5" in model: params["reasoning_effort"] = "minimal"