diff --git a/gui/rpgmaker_tab.py b/gui/rpgmaker_tab.py index 5143657..a3f5773 100644 --- a/gui/rpgmaker_tab.py +++ b/gui/rpgmaker_tab.py @@ -54,6 +54,8 @@ class RPGMakerTab(QWidget): "BRFLAG": False, "FIXTEXTWRAP": True, "IGNORETLTEXT": False, + "TLSYSTEMVARIABLES": False, + "TLSYSTEMSWITCHES": False, # Main Codes (enabled by default) "CODE401": True, # Show Text @@ -274,6 +276,16 @@ class RPGMakerTab(QWidget): ) col1.addLayout(layout) + self.tlsystemvariables_cb, layout = self._create_checkbox_with_description( + "System Variables", "Translate variable names in System.json", "Translates the variables array in System.json. Can break stuff." + ) + col1.addLayout(layout) + + self.tlsystemswitches_cb, layout = self._create_checkbox_with_description( + "System Switches", "Translate switch names in System.json", "Translates the switches array in System.json." + ) + col1.addLayout(layout) + self.join408_cb, layout = self._create_checkbox_with_description( "Merge 408 Lines", "Join multi-line comments together", "Joins CODE408 lines into one string." ) @@ -494,6 +506,8 @@ class RPGMakerTab(QWidget): self.brflag_cb.stateChanged.disconnect() self.fixtextwrap_cb.stateChanged.disconnect() self.ignoretltext_cb.stateChanged.disconnect() + self.tlsystemvariables_cb.stateChanged.disconnect() + self.tlsystemswitches_cb.stateChanged.disconnect() self.join408_cb.stateChanged.disconnect() if self.engine == "ACE": self.speakers408_cb.stateChanged.disconnect() @@ -534,6 +548,8 @@ class RPGMakerTab(QWidget): self.brflag_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) self.fixtextwrap_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) self.ignoretltext_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) + self.tlsystemvariables_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) + self.tlsystemswitches_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) self.join408_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) if self.engine == "ACE": self.speakers408_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False)) @@ -572,6 +588,8 @@ class RPGMakerTab(QWidget): "BRFLAG": self.brflag_cb.isChecked(), "FIXTEXTWRAP": self.fixtextwrap_cb.isChecked(), "IGNORETLTEXT": self.ignoretltext_cb.isChecked(), + "TLSYSTEMVARIABLES": self.tlsystemvariables_cb.isChecked(), + "TLSYSTEMSWITCHES": self.tlsystemswitches_cb.isChecked(), "JOIN408": self.join408_cb.isChecked(), # Main Codes @@ -614,6 +632,8 @@ class RPGMakerTab(QWidget): self.brflag_cb.setChecked(config.get("BRFLAG", False)) self.fixtextwrap_cb.setChecked(config.get("FIXTEXTWRAP", True)) self.ignoretltext_cb.setChecked(config.get("IGNORETLTEXT", False)) + self.tlsystemvariables_cb.setChecked(config.get("TLSYSTEMVARIABLES", False)) + self.tlsystemswitches_cb.setChecked(config.get("TLSYSTEMSWITCHES", False)) self.join408_cb.setChecked(config.get("JOIN408", False)) # Only set SPEAKERS408 for ACE engine diff --git a/modules/json.py b/modules/json.py index 8c513d2..ba06b06 100644 --- a/modules/json.py +++ b/modules/json.py @@ -165,6 +165,9 @@ def parseJSON(data, filename): # RdData format (RdData.json - dgnDatas with dungeon/dialogue data) elif "dgnDatas" in data: result = translateRdData(data, filename) + # GameSetting format (Nupu_GameSetting.json - fzCardDatas, fzDeckDatas, rentalDecks) + elif any(k in data for k in ["fzCardDatas", "fzDeckDatas", "rentalDecks"]): + result = translateGameSetting(data, filename) else: result = translateJSON(data, filename, []) else: @@ -726,6 +729,122 @@ def translateRdData(data, filename): return tokens +def translateGameSetting(data, filename): + """Translate GameSetting JSON format (e.g. Nupu_GameSetting.json). + + Handles card data, deck definitions, and rental decks. + Only translates player-visible text fields. + """ + global LOCK, ESTIMATE, FILENAME, PBAR, MISMATCH + tokens = [0, 0] + + def batchTranslate(stringList, context): + """Translate a list of strings and return translations. Returns [] on mismatch.""" + nonlocal tokens + if not stringList: + return [] + response = translateAI(stringList, context, True) + tokens[0] += response[1][0] + tokens[1] += response[1][1] + if len(stringList) != len(response[0]): + with LOCK: + if FILENAME not in MISMATCH: + MISMATCH.append(FILENAME) + return [] + return response[0] + + # ================================================================ + # PASS 1: Collect all translatable strings + # ================================================================ + + # -- fzCardDatas: card descriptions -- + desc1S, desc1I = [], [] + desc2S, desc2I = [], [] + + if "fzCardDatas" in data: + for i, card in enumerate(data["fzCardDatas"]): + if card.get("desc1") and re.search(LANGREGEX, card["desc1"]): + desc1S.append(card["desc1"].replace("\r\n", " ").replace("\n", " ").strip()) + desc1I.append(i) + if card.get("desc2") and re.search(LANGREGEX, card["desc2"]): + desc2S.append(card["desc2"].replace("\r\n", " ").replace("\n", " ").strip()) + desc2I.append(i) + + # -- fzDeckDatas: deck name and description -- + deckNameS, deckNameI = [], [] + deckDescS, deckDescI = [], [] + + if "fzDeckDatas" in data: + for i, deck in enumerate(data["fzDeckDatas"]): + if deck.get("name") and re.search(LANGREGEX, deck["name"]): + deckNameS.append(deck["name"]) + deckNameI.append(i) + if deck.get("desc") and re.search(LANGREGEX, deck["desc"]): + deckDescS.append(deck["desc"].replace("\r\n", " ").replace("\n", " ").strip()) + deckDescI.append(i) + + # -- rentalDecks: memo label -- + rentalMemoS, rentalMemoI = [], [] + + if "rentalDecks" in data: + for i, rental in enumerate(data["rentalDecks"]): + if rental.get("memo") and re.search(LANGREGEX, rental["memo"]): + rentalMemoS.append(rental["memo"]) + rentalMemoI.append(i) + + # Set progress bar total + totalItems = ( + len(desc1S) + len(desc2S) + + len(deckNameS) + len(deckDescS) + + len(rentalMemoS) + ) + PBAR.total = totalItems + PBAR.refresh() + + # ================================================================ + # PASS 2: Translate each batch and apply results back to data + # ================================================================ + + # -- fzCardDatas -- + if "fzCardDatas" in data: + cards = data["fzCardDatas"] + + for tl, idx in zip(batchTranslate(desc1S, "Card Description"), desc1I): + cards[idx]["desc1"] = dazedwrap.wrapText(tl, WIDTH) + if desc1S: + save_progress_json(data, filename) + + for tl, idx in zip(batchTranslate(desc2S, "Card Description"), desc2I): + cards[idx]["desc2"] = dazedwrap.wrapText(tl, WIDTH) + if desc2S: + save_progress_json(data, filename) + + # -- fzDeckDatas -- + if "fzDeckDatas" in data: + decks = data["fzDeckDatas"] + + for tl, idx in zip(batchTranslate(deckNameS, "Deck Name"), deckNameI): + decks[idx]["name"] = tl + if deckNameS: + save_progress_json(data, filename) + + for tl, idx in zip(batchTranslate(deckDescS, "Deck Description"), deckDescI): + decks[idx]["desc"] = dazedwrap.wrapText(tl, WIDTH) + if deckDescS: + save_progress_json(data, filename) + + # -- rentalDecks -- + if "rentalDecks" in data: + rentals = data["rentalDecks"] + + for tl, idx in zip(batchTranslate(rentalMemoS, "Deck Label"), rentalMemoI): + rentals[idx]["memo"] = tl + if rentalMemoS: + save_progress_json(data, filename) + + return tokens + + def translateJSON(data, filename, translatedList): global LOCK, ESTIMATE, FILENAME, PBAR, MISMATCH if translatedList: diff --git a/modules/rpgmakerace.py b/modules/rpgmakerace.py index c3035c8..55502d5 100644 --- a/modules/rpgmakerace.py +++ b/modules/rpgmakerace.py @@ -91,6 +91,8 @@ FIXTEXTWRAP = True IGNORETLTEXT = False # TLSYSTEMVARIABLES: Translate System Variables. (Optional but sometimes necessary. Can break stuff.) TLSYSTEMVARIABLES = False +# TLSYSTEMSWITCHES: Translate System Switches. (Optional. Translates switch names in System.json.) +TLSYSTEMSWITCHES = False # Join 408 codes into a single string like 401. JOIN408 = False # SPEAKERS408: Process speakers in code 408 the same way as code 401. @@ -1012,6 +1014,7 @@ def parseSystem(data, filename): if isinstance(gt, str) and gt: count += 1 count += len(sysobj.get("variables", []) or []) + count += len(sysobj.get("switches", []) or []) count += len(sysobj.get("weapon_types", []) or []) count += len(sysobj.get("armor_types", []) or []) count += len(sysobj.get("skill_types", []) or []) @@ -3551,6 +3554,29 @@ def searchSystem(data, pbar): if pbar is not None: pbar.refresh() + # Switches (Optional) — batch translate to reduce calls + if TLSYSTEMSWITCHES and "switches" in data and isinstance(data["switches"], list): + switch_indices = [] + switch_values = [] + for idx, val in enumerate(data["switches"]): + if isinstance(val, str) and val.strip(): + switch_indices.append(idx) + switch_values.append(val) + if switch_values: + response = translateAI( + switch_values, + 'Reply with only the ' + LANGUAGE + ' translation of the switch name', + True, + ) + totalTokens[0] += response[1][0] + totalTokens[1] += response[1][1] + tl_list = response[0] + # Assign back translations to corresponding indices + for n, idx in enumerate(switch_indices[: len(tl_list)]): + data["switches"][idx] = tl_list[n].replace('"', '').strip() + if pbar is not None: + pbar.refresh() + # Messages — batch translate to reduce calls (check if exists) if "messages" in data["terms"]: messages = data["terms"]["messages"] diff --git a/modules/rpgmakermvmz.py b/modules/rpgmakermvmz.py index feef24f..39b23bc 100644 --- a/modules/rpgmakermvmz.py +++ b/modules/rpgmakermvmz.py @@ -125,6 +125,8 @@ FIXTEXTWRAP = True IGNORETLTEXT = False # TLSYSTEMVARIABLES: Translate System Variables. (Optional but sometimes necessary. Can break stuff.) TLSYSTEMVARIABLES = False +# TLSYSTEMSWITCHES: Translate System Switches. (Optional. Translates switch names in System.json.) +TLSYSTEMSWITCHES = True # Join 408 codes into a single string like 401. JOIN408 = False @@ -892,7 +894,7 @@ def parseNames(data, filename, context): note_regexes = [ (r"", False), (r"", False), - (r"", False), + (r"<[Hh]int:(.*?)>", False), (r"", False), (r"", True), (r"", False), @@ -1083,6 +1085,7 @@ def parseSystem(data, filename): if isinstance(gt, str) and gt: count += 1 count += len(sysobj.get("variables", []) or []) + count += len(sysobj.get("switches", []) or []) count += len(sysobj.get("weaponTypes", []) or []) count += len(sysobj.get("armorTypes", []) or []) count += len(sysobj.get("skillTypes", []) or []) @@ -1229,7 +1232,7 @@ def searchNames(data, pbar, context, filename): note_regexes = [ (r"", False), (r"", False), - (r"", False), + (r"<[Hh]int:(.*?)>", False), (r"", False), (r"", True), (r"", False), @@ -3887,6 +3890,32 @@ def searchSystem(data, pbar): if pbar is not None: pbar.refresh() + # Switches (Optional) — batch translate to reduce calls + if TLSYSTEMSWITCHES and "switches" in data and isinstance(data["switches"], list): + switch_indices = [] + switch_values = [] + for idx, val in enumerate(data["switches"]): + if isinstance(val, str) and val.strip(): + # Skip if IGNORETLTEXT is enabled and no Japanese text + if IGNORETLTEXT and not re.search(LANGREGEX, val): + continue + switch_indices.append(idx) + switch_values.append(val) + if switch_values: + response = translateAI( + switch_values, + 'Reply with only the ' + LANGUAGE + ' translation of the switch name', + True, + ) + totalTokens[0] += response[1][0] + totalTokens[1] += response[1][1] + tl_list = response[0] + # Assign back translations to corresponding indices + for n, idx in enumerate(switch_indices[: len(tl_list)]): + data["switches"][idx] = tl_list[n].replace('"', '').strip() + if pbar is not None: + pbar.refresh() + # Messages — batch translate to reduce calls messages = data["terms"]["messages"] if messages: