diff --git a/README.md b/README.md
index 8cffbc1..af86efb 100644
--- a/README.md
+++ b/README.md
@@ -263,7 +263,6 @@ Open the DazedMTLTool folder in VSCode and ask Copilot to make changes to the tr
- *"Add a new regex pattern to skip lines that start with //"*
- *"Change the wordwrap logic to break on full-width punctuation"*
-- *"Make this module also handle .yaml files"*
- *"Explain what CODE 356 does in rpgmakermvmz.py"*
Copilot can read the surrounding code and suggest context-aware edits — you just review and accept. This makes it easy to customize the tool for specific games without deep Python knowledge.
@@ -308,7 +307,6 @@ Not every file needs to be tracked. Create a file called `.gitignore` in the gam
!*.csv
!*.ks
!*.tjs
-!*.yaml
!*.rb
!*.rvdata2
# Other useful files
diff --git a/gui/config_tab.py b/gui/config_tab.py
index aa429ba..8587b20 100644
--- a/gui/config_tab.py
+++ b/gui/config_tab.py
@@ -177,27 +177,21 @@ class ConfigTab(QWidget):
nav_layout.addWidget(btn_mvmz)
self.nav_buttons.append(btn_mvmz)
- # RPG Maker Ace button
- btn_ace = self.create_nav_button("🎲", "RPG Maker Ace")
- btn_ace.clicked.connect(lambda: self.switch_page(2))
- nav_layout.addWidget(btn_ace)
- self.nav_buttons.append(btn_ace)
-
# Wolf RPG button
btn_wolf = self.create_nav_button("🐺", "Wolf RPG")
- btn_wolf.clicked.connect(lambda: self.switch_page(3))
+ btn_wolf.clicked.connect(lambda: self.switch_page(2))
nav_layout.addWidget(btn_wolf)
self.nav_buttons.append(btn_wolf)
# CSV button
btn_csv = self.create_nav_button("📄", "CSV")
- btn_csv.clicked.connect(lambda: self.switch_page(4))
+ btn_csv.clicked.connect(lambda: self.switch_page(3))
nav_layout.addWidget(btn_csv)
self.nav_buttons.append(btn_csv)
# SRPG Studio button
btn_srpg = self.create_nav_button("⚔️", "SRPG Studio")
- btn_srpg.clicked.connect(lambda: self.switch_page(5))
+ btn_srpg.clicked.connect(lambda: self.switch_page(4))
nav_layout.addWidget(btn_srpg)
self.nav_buttons.append(btn_srpg)
@@ -215,19 +209,15 @@ class ConfigTab(QWidget):
self.mvmz_tab = RPGMakerTab("MVMZ")
self.content_stack.addWidget(self.mvmz_tab)
- # Page 3: RPG Maker Ace Engine
- self.ace_tab = RPGMakerTab("ACE")
- self.content_stack.addWidget(self.ace_tab)
-
- # Page 4: Wolf RPG Engine
+ # Page 3: Wolf RPG Engine
self.wolf_tab = WolfTab()
self.content_stack.addWidget(self.wolf_tab)
- # Page 5: CSV Settings
+ # Page 4: CSV Settings
self.csv_tab = CSVTab()
self.content_stack.addWidget(self.csv_tab)
- # Page 6: SRPG Studio Engine
+ # Page 5: SRPG Studio Engine
self.srpg_tab = SRPGTab()
self.content_stack.addWidget(self.srpg_tab)
@@ -279,7 +269,6 @@ class ConfigTab(QWidget):
"""Refresh engine config pages from their module files when shown."""
engine_pages = {
1: getattr(self, "mvmz_tab", None),
- 2: getattr(self, "ace_tab", None),
}
page = engine_pages.get(index)
if page is not None and hasattr(page, "refresh_from_module"):
@@ -882,7 +871,6 @@ class ConfigTab(QWidget):
# Reset engine tabs
self.mvmz_tab.reset_to_defaults()
- self.ace_tab.reset_to_defaults()
self.wolf_tab.reset_to_defaults()
self.csv_tab.reset_to_defaults()
diff --git a/gui/engine_config_tab.py b/gui/engine_config_tab.py
index 457c1c1..a3ec84e 100644
--- a/gui/engine_config_tab.py
+++ b/gui/engine_config_tab.py
@@ -5,7 +5,6 @@ based on signals from the Translation tab (engine selection).
Currently supports:
- RPG Maker MV/MZ
-- RPG Maker Ace
- Wolf RPG
Easily extensible: add new engine widget + mapping entry.
@@ -31,7 +30,6 @@ class EngineConfigTab(QWidget):
# Engine widgets
self.mv_widget = RPGMakerTab("MVMZ")
- self.ace_widget = RPGMakerTab("ACE")
self.wolf_widget = WolfTab()
# Placeholder when no engine selected
@@ -42,7 +40,6 @@ class EngineConfigTab(QWidget):
# Add to stack
self.widget_map = {
"mvmz": self.mv_widget,
- "ace": self.ace_widget,
"wolf": self.wolf_widget,
}
self.stack.addWidget(self.placeholder) # index 0
diff --git a/gui/rpgmaker_tab.py b/gui/rpgmaker_tab.py
index a1f699f..ba92bc5 100644
--- a/gui/rpgmaker_tab.py
+++ b/gui/rpgmaker_tab.py
@@ -40,11 +40,7 @@ def create_section_label(text):
class RPGMakerTab(QWidget):
- """RPG Maker MV/MZ & Ace configuration tab.
-
- This widget now supports both MV/MZ and Ace engines. Pass engine="ACE" to
- target rpgmakerace.py; otherwise it will default to MV/MZ (rpgmakermvmz.py).
- """
+ """RPG Maker MV/MZ configuration tab."""
# Default configuration values for RPG Maker MV/MZ
DEFAULT_CONFIG = {
@@ -187,8 +183,7 @@ class RPGMakerTab(QWidget):
loaded_config = None
if self.config_integration:
- module_filename = "rpgmakermvmz.py" if self.engine != "ACE" else "rpgmakerace.py"
- module_path = Path("modules") / module_filename
+ module_path = Path("modules") / "rpgmakermvmz.py"
loaded_config = self.config_integration.read_current_config(module_path)
# Use loaded module config if available, otherwise use canonical defaults
@@ -237,8 +232,7 @@ class RPGMakerTab(QWidget):
main_layout.setSpacing(12)
# Title
- title = "RPG Maker MV/MZ" if self.engine != "ACE" else "RPG Maker Ace"
- title_label = QLabel(f"{title} Translation Settings")
+ title_label = QLabel("RPG Maker MV/MZ Translation Settings")
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #007acc;")
main_layout.addWidget(title_label)
@@ -297,11 +291,6 @@ class RPGMakerTab(QWidget):
)
col1.addLayout(layout)
- self.speakers408_cb, layout = self._create_checkbox_with_description(
- "408 Speakers", "Detect speakers in comment blocks", "Apply speaker logic to comment blocks."
- )
- col1.addLayout(layout)
-
col1.addStretch()
# ==================== COLUMN 2: DIALOGUE ====================
@@ -516,8 +505,6 @@ class RPGMakerTab(QWidget):
self.tlsystemvariables_cb.stateChanged.disconnect()
self.tlsystemswitches_cb.stateChanged.disconnect()
self.join408_cb.stateChanged.disconnect()
- if self.engine == "ACE":
- self.speakers408_cb.stateChanged.disconnect()
# Main Codes
self.code401_cb.stateChanged.disconnect()
@@ -559,8 +546,6 @@ class RPGMakerTab(QWidget):
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))
# Main Codes
self.code401_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
@@ -626,11 +611,6 @@ class RPGMakerTab(QWidget):
"CODE111": self.code111_cb.isChecked(),
"CODE108": self.code108_cb.isChecked(),
}
-
- # Only include SPEAKERS408 for ACE engine
- if self.engine == "ACE":
- config["SPEAKERS408"] = self.speakers408_cb.isChecked()
-
return config
def set_config(self, config):
@@ -645,10 +625,6 @@ class RPGMakerTab(QWidget):
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
- if self.engine == "ACE":
- self.speakers408_cb.setChecked(config.get("SPEAKERS408", False))
# Main Codes
self.code401_cb.setChecked(config.get("CODE401", True))
@@ -760,11 +736,10 @@ class RPGMakerTab(QWidget):
return is_valid
def apply_to_module(self, show_messages=False):
- """Apply current configuration to the correct RPG Maker module (MV/MZ or Ace)."""
+ """Apply current configuration to the RPG Maker MV/MZ module."""
try:
config = self.get_config()
- # Select module filename based on engine
- module_filename = "rpgmakermvmz.py" if self.engine != "ACE" else "rpgmakerace.py"
+ module_filename = "rpgmakermvmz.py"
module_path = Path(__file__).parent.parent / "modules" / module_filename
if not module_path.exists():
@@ -815,12 +790,12 @@ class RPGMakerTab(QWidget):
self.config_changed.emit()
def load_from_module(self):
- """Load configuration from the actual module file based on engine."""
+ """Load configuration from the RPG Maker MV/MZ module file."""
if not self.config_integration:
return
try:
- module_filename = "rpgmakermvmz.py" if self.engine != "ACE" else "rpgmakerace.py"
+ module_filename = "rpgmakermvmz.py"
module_path = Path("modules") / module_filename
config = self.config_integration.read_current_config(module_path)
if config:
@@ -841,8 +816,7 @@ class RPGMakerTab(QWidget):
return False
try:
- module_filename = "rpgmakermvmz.py" if self.engine != "ACE" else "rpgmakerace.py"
- module_path = Path("modules") / module_filename
+ module_path = Path("modules") / "rpgmakermvmz.py"
config = self.config_integration.read_current_config(module_path)
if not config:
return False
diff --git a/gui/translation_tab.py b/gui/translation_tab.py
index 8916cdd..299b3b5 100644
--- a/gui/translation_tab.py
+++ b/gui/translation_tab.py
@@ -325,11 +325,10 @@ class TranslationWorker(QThread):
threads = int(os.getenv("fileThreads", "1"))
total_cost = "Fail"
- # If we're doing Parse Speakers for RPGMaker MV/MZ or ACE, run handlers in-process so
+ # If we're doing Parse Speakers for RPGMaker MV/MZ, run handlers in-process so
# speaker collection is shared in this process and finalizeSpeakerParse() can run once.
module_name_lower = self.module_info[0].lower() if isinstance(self.module_info[0], str) else ""
is_mvmz = "mv/mz" in module_name_lower
- is_ace = "ace" in module_name_lower
# Change to project directory for module execution
old_cwd = os.getcwd()
@@ -345,16 +344,12 @@ class TranslationWorker(QThread):
pass
try:
- if self.parse_speakers and (is_mvmz or is_ace):
+ if self.parse_speakers and is_mvmz:
# Run handlers sequentially in this worker process so globals are shared
try:
- if is_mvmz:
- from modules.rpgmakermvmz import handleMVMZ as handler, setSpeakerParseMode, finalizeSpeakerParse, resetSpeakerState, TOKENS, calculateCost, MODEL
- elif is_ace:
- from modules.rpgmakerace import handleACE as handler, setSpeakerParseMode, finalizeSpeakerParse, resetSpeakerState, TOKENS, calculateCost, MODEL
+ from modules.rpgmakermvmz import handleMVMZ as handler, setSpeakerParseMode, finalizeSpeakerParse, resetSpeakerState, TOKENS, calculateCost, MODEL
except Exception as e:
- engine_name = "rpgmakermvmz" if is_mvmz else "rpgmakerace"
- self.emit_log(f"❌ Could not import {engine_name} for speaker-parse: {e}")
+ self.emit_log(f"❌ Could not import rpgmakermvmz for speaker-parse: {e}")
self.finished_signal.emit(False, str(e))
return
@@ -1106,7 +1101,6 @@ class TranslationTab(QWidget):
try:
sys.path.append(str(self.project_root))
from modules.rpgmakermvmz import handleMVMZ
- from modules.rpgmakerace import handleACE
from modules.csv import handleCSV
from modules.tyrano import handleTyrano
from modules.kirikiri import handleKirikiri
@@ -1126,7 +1120,6 @@ class TranslationTab(QWidget):
self.modules = [
["RPG Maker MV/MZ", [".json"], handleMVMZ],
- ["RPG Maker Ace", [".yaml"], handleACE],
["CSV", [".csv"], handleCSV],
["Tyrano", [".ks"], handleTyrano],
["Kirikiri", [".ks"], handleKirikiri],
@@ -1161,9 +1154,7 @@ class TranslationTab(QWidget):
def _on_module_changed(self, text: str):
lowered = text.lower()
- if "ace" in lowered:
- self.engine_changed.emit("ace")
- elif "wolf" in lowered and "wolf rpg 2" not in lowered:
+ if "wolf" in lowered and "wolf rpg 2" not in lowered:
self.engine_changed.emit("wolf")
elif "mv/mz" in lowered:
self.engine_changed.emit("mvmz")
@@ -1176,8 +1167,8 @@ class TranslationTab(QWidget):
self.mode_combo.addItem("Translate")
self.mode_combo.addItem("Estimate")
- # Add Parse Speakers for RPG Maker MV/MZ and RPG Maker Ace
- if "mv/mz" in lowered or "ace" in lowered:
+ # Add Parse Speakers for RPG Maker MV/MZ
+ if "mv/mz" in lowered:
self.mode_combo.addItem("Parse Speakers")
# Restore previous selection if it still exists
@@ -1336,7 +1327,7 @@ class TranslationTab(QWidget):
try:
selected_index = self.module_combo.currentIndex()
if 0 <= selected_index < len(self.modules):
- accepted_extensions = self.modules[selected_index][1] # List of extensions like [".json", ".yaml"]
+ accepted_extensions = self.modules[selected_index][1] # List of extensions like [".json"]
except Exception:
pass
diff --git a/modules/main.py b/modules/main.py
index 92a167d..af56461 100644
--- a/modules/main.py
+++ b/modules/main.py
@@ -34,7 +34,6 @@ these values using an .env file, for an example see .env.example"
)
from modules.rpgmakermvmz import handleMVMZ, setSpeakerParseMode as setSpeakerParseMVMZ, finalizeSpeakerParse as finalizeSpeakerParseMVMZ
-from modules.rpgmakerace import handleACE, setSpeakerParseMode as setSpeakerParseACE, finalizeSpeakerParse as finalizeSpeakerParseACE
from modules.csv import handleCSV
from modules.tyrano import handleTyrano
from modules.kirikiri import handleKirikiri
@@ -60,7 +59,6 @@ THREADS = int(os.getenv("fileThreads"))
MODULES = [
["RPGMaker MV/MZ", ["json"], handleMVMZ],
["RPGMaker Plugins", ["js", "rb"], handlePlugin],
- ["RPGMaker ACE", ["yaml"], handleACE],
["CSV (From Translator++)", ["csv"], handleCSV],
["Tyrano", ["ks"], handleTyrano],
["Kirikiri", ["ks", "tjs", "ssd", "asd"], handleKirikiri],
@@ -123,13 +121,12 @@ def main():
files to translate are in the /files folder and that you picked the right game engine."
)
- # If translating RPGMaker MV/MZ (index 0) or RPGMaker ACE (index 2) prompt for speaker parse mode
+ # If translating RPGMaker MV/MZ, prompt for speaker parse mode
speaker_parse = False
- if version in [0, 2] and not estimate:
+ if version == 0 and not estimate:
sub = ""
- engine_name = "RPGMaker MV/MZ" if version == 0 else "RPGMaker ACE"
while sub == "":
- sub = input(f"{engine_name} options:\n\n 1. Standard Translate\n 2. Parse Speakers (collect speaker names only)\n")
+ sub = input("RPGMaker MV/MZ options:\n\n 1. Standard Translate\n 2. Parse Speakers (collect speaker names only)\n")
match sub:
case "1":
speaker_parse = False
@@ -138,10 +135,7 @@ files to translate are in the /files folder and that you picked the right game e
case _:
sub = ""
if speaker_parse:
- if version == 0:
- setSpeakerParseMVMZ(True)
- elif version == 2:
- setSpeakerParseACE(True)
+ setSpeakerParseMVMZ(True)
# Open File (Threads) - recursively walk 'files' and preserve directory structure
# Prepare per-run log file so CLI runs also write to a run-specific history file
@@ -259,10 +253,7 @@ files to translate are in the /files folder and that you picked the right game e
# Finalize speaker parse mode by writing collected speakers to vocab
if speaker_parse:
- if version == 0:
- finalizeSpeakerParseMVMZ()
- elif version == 2:
- finalizeSpeakerParseACE()
+ finalizeSpeakerParseMVMZ()
# Delete Tmp Files
if os.path.isfile("csv.tmp"):
@@ -295,5 +286,5 @@ files to translate are in the /files folder and that you picked the right game e
def deleteFolderFiles(folderPath):
for filename in os.listdir(folderPath):
file_path = os.path.join(folderPath, filename)
- if file_path.endswith((".json", ".yaml", ".ks")):
+ if file_path.endswith((".json", ".ks")):
os.remove(file_path)
\ No newline at end of file
diff --git a/modules/rpgmakerace.py b/modules/rpgmakerace.py
deleted file mode 100644
index d35f163..0000000
--- a/modules/rpgmakerace.py
+++ /dev/null
@@ -1,3851 +0,0 @@
-# Libraries
-import json
-import os
-import re
-import util.dazedwrap as dazedwrap
-import threading
-import time
-import traceback
-import copy
-from pathlib import Path
-import shutil
-from colorama import Fore
-from dotenv import load_dotenv
-from retry import retry
-from tqdm import tqdm
-from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost, getPricingConfig, calculateCost
-from ruamel.yaml import YAML
-
-
-# Globals
-MODEL = os.getenv("model")
-TIMEOUT = int(os.getenv("timeout"))
-LANGUAGE = os.getenv("language").capitalize()
-PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
-VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
-LOCK = threading.Lock()
-THREAD_CTX = threading.local()
-WIDTH = int(os.getenv("width"))
-LISTWIDTH = int(os.getenv("listWidth"))
-NOTEWIDTH = int(os.getenv("noteWidth"))
-MAXHISTORY = 10
-ESTIMATE = ""
-TOKENS = [0, 0]
-MISMATCH = [] # Lists files that throw a mismatch error (Length of GPT list response is wrong)
-PBAR = None
-FILENAME = None
-TIMETOTAL = 0 # Total Time Taken for all translations
-VOCAB_LOCK = threading.Lock()
-PREFLIGHT_COUNT_MODE = False # When True, translateAI wrapper only counts units and never calls API
-
-# Speakers
-NAMESLIST = []
-SPEAKER_PARSE_MODE = False
-_speakerCache = {}
-_speakerCacheLock = threading.Lock()
-SPEAKER_COLLECTED = [] # Original speaker names collected during parse mode (untranslated)
-
-def clearSpeakerCache():
- """Clear the speaker cache between passes to ensure fresh translations"""
- global _speakerCache
- with _speakerCacheLock:
- _speakerCache.clear()
-
-# Regex - Need to change this if you want to translate from/to other languages. Default is Japanese Regex
-LANGREGEX = r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9\uFF61-\uFF9F]+"
-
-# Get pricing configuration based on the model
-PRICING_CONFIG = getPricingConfig(MODEL)
-INPUTAPICOST = PRICING_CONFIG["inputAPICost"]
-OUTPUTAPICOST = PRICING_CONFIG["outputAPICost"]
-BATCHSIZE = PRICING_CONFIG["batchSize"]
-FREQUENCY_PENALTY = PRICING_CONFIG["frequencyPenalty"]
-
-# tqdm Globals
-BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
-POSITION = 0
-
-# Initialize Translation Config
-TRANSLATION_CONFIG = TranslationConfig(
- model=MODEL,
- language=LANGUAGE,
- prompt=PROMPT,
- vocab=VOCAB,
- langRegex=LANGREGEX,
- batchSize=BATCHSIZE,
- maxHistory=MAXHISTORY,
- estimateMode=False # Will be set dynamically based on ESTIMATE
-)
-LEAVE = False
-
-# Config (Default)
-# FIRSTLINESPEAKERS: Guess speaker from first line.
-FIRSTLINESPEAKERS = False
-# FACENAME101: Map face name -> speaker.
-FACENAME101 = False
-# BRFLAG: Newlines ->
.
-BRFLAG = False
-# FIXTEXTWRAP: Rewrap text to WIDTH/NOTEWIDTH.
-FIXTEXTWRAP = True
-# IGNORETLTEXT: Skip Translated Text.
-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.
-SPEAKERS408 = False
-
-# Dialogue / Scroll / Choices (Main Codes)
-CODE101 = True
-CODE401 = True
-CODE405 = True
-CODE102 = True
-
-# Optional
-CODE408 = True
-
-# Variables
-CODE122 = True
-CODE122_VAR_MIN = 0 # Minimum variable ID to translate (inclusive)
-CODE122_VAR_MAX = 2000 # Maximum variable ID to translate (exclusive)
-
-# Plugins / Scripts
-CODE355655 = False
-CODE357 = False
-CODE657 = False
-CODE356 = False
-CODE320 = False
-CODE324 = False
-CODE325 = False
-CODE111 = False
-CODE108 = False
-
-
-def handleACE(filename, estimate):
- global ESTIMATE, TOKENS, FILENAME
- ESTIMATE = estimate
- FILENAME = filename
- # Also record per-thread filename to avoid cross-thread interference
- try:
- THREAD_CTX.filename = filename
- except Exception:
- pass
-
- # Translate
- start = time.time()
- translatedData = openFiles(filename)
-
- # Translate
- # Skip writing output file during speaker-parse mode
- if not estimate and not SPEAKER_PARSE_MODE:
- try:
- with open("translated/" + filename, "w", encoding="utf-8", newline="\n") as outFile:
- yaml = YAML(pure=True)
- yaml.width = 4096
- yaml.default_style = "'"
- yaml.dump(translatedData[0], outFile)
- except Exception:
- traceback.print_exc()
- return "Fail"
-
- # Print File
- end = time.time()
- tqdm.write(getResultString(translatedData, end - start, filename))
- with LOCK:
- TOKENS[0] += translatedData[1][0]
- TOKENS[1] += translatedData[1][1]
-
- # Print Total
- totalString = getResultString(["", TOKENS, None], end - start, "TOTAL")
-
- # Print any errors on maps
- if len(MISMATCH) > 0:
- return totalString + Fore.RED + f"\nMismatch Errors: {MISMATCH}" + Fore.RESET
- else:
- return totalString
-
-
-def openFiles(filename):
- yaml = YAML(pure=True) # Need a yaml instance per thread.
- yaml.width = 4096
- yaml.default_style = "'"
-
- with open("files/" + filename, "r", encoding="UTF-8") as f:
- data = yaml.load(f)
- # Map Files
- if "Map" in filename and "MapInfos" not in filename:
- translatedData = parseMap(data, filename)
-
- # CommonEvents Files
- elif "CommonEvents" in filename:
- translatedData = parseCommonEvents(data, filename)
-
- # Actor File
- elif "Actors" in filename:
- translatedData = parseNames(data, filename, "Actors")
-
- # Armor File
- elif "Armors" in filename:
- translatedData = parseNames(data, filename, "Armors")
-
- # Weapons File
- elif "Weapons" in filename:
- translatedData = parseNames(data, filename, "Weapons")
-
- # Classes File
- elif "Classes" in filename:
- translatedData = parseNames(data, filename, "Classes")
-
- # Enemies File
- elif "Enemies" in filename:
- translatedData = parseNames(data, filename, "Enemies")
-
- # Items File
- elif "Items" in filename:
- translatedData = parseNames(data, filename, "Items")
-
- # MapInfo File
- elif "MapInfos" in filename:
- translatedData = parseNames(data, filename, "MapInfos")
-
- # Skills File
- elif "Skills" in filename:
- translatedData = parseNames(data, filename, "Skills")
-
- # Troops File
- elif "Troops" in filename:
- translatedData = parseTroops(data, filename)
-
- # States File
- elif "States" in filename:
- translatedData = parseSS(data, filename)
-
- # System File
- elif "System" in filename:
- translatedData = parseSystem(data, filename)
-
- # Scenario File
- elif "Scenario" in filename:
- translatedData = parseScenario(data, filename)
-
- else:
- raise NameError(filename + " Not Supported")
-
- return translatedData
-
-
-def getResultString(translatedData, translationTime, filename):
- global TIMETOTAL
- # File Print String
- cost = calculateCost(translatedData[1][0], translatedData[1][1], MODEL)
- totalTokenstring = (
- Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
- "[Output: "
- + str(translatedData[1][1])
- + "]" "[Cost: ${:,.4f}".format(cost)
- + "]"
- )
- if filename != "TOTAL":
- timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
- TIMETOTAL += round(translationTime, 1)
- else:
- timeString = Fore.BLUE + "[" + str(round(TIMETOTAL, 1)) + "s]"
-
- if translatedData[2] is None:
- # Success
- return filename + ": " + totalTokenstring + timeString + Fore.GREEN + " \u2713 " + Fore.RESET
- else:
- # Fail
- try:
- raise translatedData[2]
- except Exception as e:
- traceback.print_exc()
- errorString = str(e) + Fore.RED
- return filename + ": " + totalTokenstring + timeString + Fore.RED + " \u2717 " + errorString + Fore.RESET
-
-
-def saveProgress(data, filename):
- """Atomically write current data to translated/filename to avoid progress loss.
- Skips when running in estimate mode.
- """
- try:
- # Also skip progress saves during speaker-parse mode
- if ESTIMATE or SPEAKER_PARSE_MODE:
- return
- os.makedirs("translated", exist_ok=True)
- # Use a unique temp file name to avoid collisions across threads/processes
- tmp_path = os.path.join(
- "translated",
- f"{filename}.{os.getpid()}.{threading.get_ident()}.tmp",
- )
- final_path = os.path.join("translated", filename)
- yaml = YAML(pure=True)
- yaml.width = 4096
- yaml.default_style = "'"
- with open(tmp_path, "w", encoding="utf-8", newline="\n") as outFile:
- yaml.dump(data, outFile)
- outFile.flush()
- try:
- os.fsync(outFile.fileno())
- except Exception:
- # fsync may not be available on some platforms; ignore best-effort
- pass
-
- # Replace atomically when possible, with retries to mitigate transient locks on Windows
- attempts = 6
- delay = 0.1
- last_err = None
- for attempt in range(attempts):
- try:
- os.replace(tmp_path, final_path)
- last_err = None
- break
- except PermissionError as e:
- last_err = e
- # Try to relax permissions on target if it exists, then back off
- try:
- if os.path.exists(final_path):
- os.chmod(final_path, 0o666)
- except Exception:
- pass
- time.sleep(delay)
- delay = min(1.0, delay * 2)
- except Exception as e:
- last_err = e
- break
- if last_err is not None:
- # Fallback: try move via shutil (not guaranteed atomic), then raise on failure
- try:
- shutil.move(tmp_path, final_path)
- except Exception:
- # Ensure tmp is cleaned up if move failed
- try:
- if os.path.exists(tmp_path):
- os.remove(tmp_path)
- except Exception:
- pass
- raise last_err
- except Exception:
- # Best-effort; don't crash the translation if saving fails
- traceback.print_exc()
-
-
-def checkSave(data, filename, tokens):
- """Save progress only if the given tokens reflect an actual translation.
- tokens should be a [input_tokens, output_tokens] pair returned by a search/translate call.
- """
- try:
- # Never save progress to translated/ during speaker-parse mode
- if SPEAKER_PARSE_MODE:
- return
- if not tokens:
- return
- if (isinstance(tokens, (list, tuple)) and len(tokens) >= 2 and (tokens[0] or tokens[1])):
- saveProgress(data, filename)
- except Exception:
- # Don't let saving issues affect the translation flow
- traceback.print_exc()
-
-
-def update_vocab_section(category: str, pairs: list[tuple[str, str]]):
- """Update or insert a section in vocab.txt for the given category with provided pairs.
- Only writes when there's an actual translation (dst is non-empty and differs from src after normalization).
- - category: e.g., "Items", "Weapons", etc. Section header will be "# {category}".
- - pairs: list of (source, translated) strings. Duplicates by source are deduped (last wins).
- The existing section is replaced entirely; other sections are preserved.
- """
- try:
- vocab_path = Path("vocab.txt")
-
- # Helper: normalized comparison to detect no-op translations
- def _norm(s: str) -> str:
- if s is None:
- return ""
- # Collapse whitespace and case-fold; leave punctuation to avoid over-matching
- return re.sub(r"\s+", " ", str(s)).strip().casefold()
-
- # Filter and deduplicate by source term (last mapping wins)
- dedup: dict[str, str] = {}
- for src, dst in pairs:
- if not src:
- continue
- # Skip when no destination or no actual change
- if dst is None or _norm(dst) == "" or _norm(dst) == _norm(src):
- continue
- dedup[src] = dst
-
- # If nothing to add after filtering, skip touching the file
- if not dedup:
- return
-
- # Guard the read-modify-write with a dedicated lock to avoid races
- with VOCAB_LOCK:
- existing = vocab_path.read_text(encoding="utf-8") if vocab_path.exists() else ""
-
- lines = [f"{src} ({dst})" for src, dst in dedup.items()]
- # Always terminate a section with a blank line to separate from next header
- new_block = f"# {category}\n" + "\n".join(lines)
- if not new_block.endswith("\n\n"):
- if not new_block.endswith("\n"):
- new_block += "\n"
- new_block += "\n"
-
- # Regex to find the specific section starting at the header for this category
- # and ending right before the next header (any number of '#') or EOF.
- # - Handles headers like '#Category', '# Category', '## Category', etc.
- # - Uses non-greedy matching for the body to avoid spanning multiple sections.
- pattern = re.compile(
- rf"^[\t ]*#+\s*{re.escape(category)}\s*$\r?\n.*?(?=^[\t ]*#|\Z)",
- re.MULTILINE | re.DOTALL,
- )
- if pattern.search(existing):
- # Replace only the first matching section for this category.
- updated = pattern.sub(lambda m: new_block, existing, count=1)
- else:
- updated = existing
- if updated and not updated.endswith("\n\n"):
- # Ensure a blank line before appending new section if file not empty
- if not updated.endswith("\n"):
- updated += "\n"
- updated += "\n"
- updated += new_block
-
- # Avoid writing if nothing changed
- if updated == existing:
- return
- # Atomic write: write to unique temp and replace with retries on Windows
- tmp_path = vocab_path.with_suffix(vocab_path.suffix + f".{os.getpid()}.{threading.get_ident()}.tmp")
- tmp_path.write_text(updated, encoding="utf-8")
-
- attempts = 6
- delay = 0.1
- last_err = None
- for attempt in range(attempts):
- try:
- os.replace(tmp_path, vocab_path)
- last_err = None
- break
- except PermissionError as e:
- last_err = e
- # Try relaxing permissions then retry
- try:
- if vocab_path.exists():
- os.chmod(vocab_path, 0o666)
- except Exception:
- pass
- time.sleep(delay)
- delay = min(1.0, delay * 2)
- except Exception as e:
- last_err = e
- break
- if last_err is not None:
- try:
- shutil.move(str(tmp_path), str(vocab_path))
- except Exception:
- try:
- if tmp_path.exists():
- tmp_path.unlink(missing_ok=True)
- except Exception:
- pass
- raise last_err
- except Exception:
- traceback.print_exc()
-
-
-def parseMap(data, filename):
- totalTokens = [0, 0]
- events = data["events"]
- global LOCK
-
- # --- Preflight: estimate exact progress total using the same translation batching ---
- class _CountingBar:
- def __init__(self):
- self.n = 0
- def update(self, n=1):
- try:
- self.n += int(n) if n is not None else 1
- except Exception:
- self.n += 1
- def write(self, *args, **kwargs):
- pass
- def refresh(self):
- pass
-
- def _estimate_map_units(d, fname) -> int:
- # Avoid deep copy - just count items directly
- count = 0
- try:
- # Count display name TL (1 unit if present)
- if "Map" in fname and isinstance(d.get("display_name", None), str):
- count += 1
-
- # Notes and pages - count actual translatable items
- evts = d.get("events", {}) or {}
- for evt in evts.values():
- if not evt:
- continue
- note_val = evt.get("note") or ""
- if not isinstance(note_val, str):
- note_val = str(note_val) if note_val is not None else ""
-
- # Count note-based translations
- if "" in note_val:
- name_val = evt.get("name") or ""
- if isinstance(name_val, str) and name_val:
- count += 1
-
- if "", note_val, re.DOTALL)
- count += len(matches)
-
- if ".*", note_val)
- count += len(matches)
- if ".*", note_val)
- count += len(matches)
-
- # Count commands in pages (rough estimate)
- for page in (evt.get("pages", []) or []):
- if page and "list" in page:
- # Count translatable codes
- for cmd in page.get("list", []):
- if cmd and "c" in cmd:
- code = cmd["c"]
- # Count common translatable codes
- if code in [401, 405, 102, 122, 408, 355, 655, 356, 357, 320, 324, 325, 111, 108, 657]:
- count += 1
-
- return count if count > 0 else 1
- except Exception:
- return 1
-
-
- # Translate display_name for Map files
- if "Map" in filename:
- response = translateAI(
- data["display_name"],
- "Reply with only the " + LANGUAGE + " translation of the RPG location name",
- False,
- )
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- data["display_name"] = response[0].replace('"', "")
-
- totalLines = _estimate_map_units(data, filename)
- if not isinstance(totalLines, int) or totalLines <= 0:
- # Fallback to naive count so a bar still renders
- totalLines = 0
- for evt in events.values():
- if evt:
- for page in (evt.get("pages", []) or []):
- try:
- totalLines += len(page.get("list", []))
- except Exception:
- pass
- global PBAR
-
- # Process each page synchronously with progress updates
- with tqdm(total=totalLines, bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE, desc=filename) as pbar:
- PBAR = pbar
- for event in events.values():
- if event is not None and isinstance(event, dict):
- # Normalize note to a safe string
- note_val = event.get("note") or ""
- if not isinstance(note_val, str):
- note_val = str(note_val) if note_val is not None else ""
-
- # This translates ID of events. (May break the game)
- if ".*")
- if tok and isinstance(tok, (list, tuple)):
- totalTokens[0] += tok[0]
- totalTokens[1] += tok[1]
- if ".*")
- if tok and isinstance(tok, (list, tuple)):
- totalTokens[0] += tok[0]
- totalTokens[1] += tok[1]
-
- for page in event["pages"]:
- if page is not None:
- try:
- totalTokensPage = searchCodes(page, pbar, [], filename)
- totalTokens[0] += totalTokensPage[0]
- totalTokens[1] += totalTokensPage[1]
- except Exception as e:
- traceback.print_exc()
- return [data, totalTokens, e]
- finally:
- # Persist progress only if this page produced tokens
- checkSave(data, filename, totalTokensPage)
- return [data, totalTokens, None]
-
-
-def translateNote(event, regex, wordwrap=False):
- # Regex String
- jaString = event.get("note") or ""
- if not isinstance(jaString, str):
- jaString = str(jaString) if jaString is not None else ""
- match = re.findall(regex, jaString, re.DOTALL)
- if match:
- tokens = [0, 0]
- i = 0
- while i < len(match):
- initialJAString = match[i]
- modifiedJAString = initialJAString
- # Remove any textwrap
- if wordwrap:
- modifiedJAString = modifiedJAString.replace("\n", " ")
-
- # Translate
- response = translateAI(
- modifiedJAString,
- "Reply with only the " + LANGUAGE + " translation.",
- False,
- )
- translatedText = response[0]
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
-
- # Textwrap
- if wordwrap:
- translatedText = dazedwrap.wrapText(translatedText, width=NOTEWIDTH)
- translatedText = translatedText.replace('"', "")
-
- jaString = jaString.replace(initialJAString, translatedText)
- event["note"] = jaString
- i += 1
- return tokens
- return [0, 0]
-
-
-# For notes that can't have spaces.
-def translateNoteOmitSpace(event, regex):
- # Regex that only matches text inside LB.
- jaString = event.get("note") or ""
- if not isinstance(jaString, str):
- jaString = str(jaString) if jaString is not None else ""
-
- match = re.findall(regex, jaString, re.DOTALL)
- if match:
- oldJAString = match[0]
- # Remove any textwrap
- jaString = re.sub(r"\n", " ", oldJAString)
-
- # Translate
- response = translateAI(
- jaString,
- "Reply with the " + LANGUAGE + " translation of the location name.",
- False,
- )
- # Defend against unexpected response shapes
- try:
- translatedText = response[0]
- token_info = response[1] if isinstance(response, (list, tuple)) and len(response) > 1 else [0, 0]
- if not (isinstance(token_info, (list, tuple)) and len(token_info) >= 2):
- token_info = [0, 0]
- except Exception:
- translatedText = str(response) if response is not None else ""
- token_info = [0, 0]
-
- translatedText = translatedText.replace('"', "")
- translatedText = translatedText.replace(" ", "_")
- # Safely update the note if it exists and is a string
- current_note = event.get("note")
- if isinstance(current_note, str):
- event["note"] = current_note.replace(oldJAString, translatedText)
- return token_info
- return [0, 0]
-
-
-def parseCommonEvents(data, filename):
- totalTokens = [0, 0]
- global LOCK
-
- # --- Preflight: estimate exact progress total using same batching ---
- class _CountingBar:
- def __init__(self):
- self.n = 0
- def update(self, n=1):
- try:
- self.n += int(n) if n is not None else 1
- except Exception:
- self.n += 1
- def write(self, *args, **kwargs):
- pass
- def refresh(self):
- pass
-
- def _estimate_units(pages, fname) -> int:
- # Avoid deep copy - just count commands directly
- count = 0
- try:
- for page in pages:
- if page is not None and "list" in page:
- for cmd in page.get("list", []):
- if cmd and "c" in cmd:
- code = cmd["c"]
- if code in [401, 405, 102, 122, 408, 355, 655, 356, 357, 320, 324, 325, 111, 108, 657]:
- count += 1
- return count if count > 0 else 1
- except Exception:
- return 1
-
- totalLines = _estimate_units(data, filename)
- if not isinstance(totalLines, int) or totalLines <= 0:
- # Fallback to naive command count
- totalLines = 0
- for page in data:
- if page is not None:
- try:
- totalLines += len(page.get("list", []))
- except Exception:
- pass
- global PBAR
-
- with tqdm(total=totalLines, bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE, desc=filename) as pbar:
- PBAR = pbar
- for page in data:
- if page is not None:
- try:
- totalTokensPage = searchCodes(page, pbar, [], filename)
- totalTokens[0] += totalTokensPage[0]
- totalTokens[1] += totalTokensPage[1]
- except Exception as e:
- traceback.print_exc()
- return [data, totalTokens, e]
- finally:
- # Persist progress only if this page produced tokens
- checkSave(data, filename, totalTokensPage)
- return [data, totalTokens, None]
-
-
-def parseTroops(data, filename):
- totalTokens = [0, 0]
- global LOCK
-
- # --- Preflight total using same code paths ---
- class _CountingBar:
- def __init__(self):
- self.n = 0
- def update(self, n=1):
- try:
- self.n += int(n) if n is not None else 1
- except Exception:
- self.n += 1
- def write(self, *args, **kwargs):
- pass
- def refresh(self):
- pass
-
- def _estimate_units(troops, fname) -> int:
- # Avoid deep copy - just count commands directly
- count = 0
- try:
- for troop in troops:
- if troop is None:
- continue
- for page in (troop.get("pages", []) or []):
- if page is not None and "list" in page:
- for cmd in page.get("list", []):
- if cmd and "c" in cmd:
- code = cmd["c"]
- if code in [401, 405, 102, 122, 408, 355, 655, 356, 357, 320, 324, 325, 111, 108, 657]:
- count += 1
- return count if count > 0 else 1
- except Exception:
- return 1
-
- totalLines = _estimate_units(data, filename)
- if not isinstance(totalLines, int) or totalLines <= 0:
- totalLines = 0
- for troop in data:
- if troop is not None:
- for page in troop.get("pages", []) or []:
- try:
- totalLines += len(page.get("list", []))
- except Exception:
- pass
- global PBAR
-
- with tqdm(total=totalLines, bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE, desc=filename) as pbar:
- PBAR = pbar
- for troop in data:
- if troop is not None:
- for page in troop["pages"]:
- if page is not None:
- try:
- totalTokensPage = searchCodes(page, pbar, [], filename)
- totalTokens[0] += totalTokensPage[0]
- totalTokens[1] += totalTokensPage[1]
- except Exception as e:
- traceback.print_exc()
- return [data, totalTokens, e]
- finally:
- # Persist progress only if this page produced tokens
- checkSave(data, filename, totalTokensPage)
- return [data, totalTokens, None]
-
-
-def parseNames(data, filename, context):
- totalTokens = [0, 0]
-
- # --- Preflight: custom estimator that mirrors searchNames increments (incl. notes/messages) ---
- class _CountingBar:
- def __init__(self):
- self.n = 0
- def update(self, n=1):
- try:
- self.n += int(n) if n is not None else 1
- except Exception:
- self.n += 1
- def write(self, *args, **kwargs):
- pass
- def refresh(self):
- pass
-
- def _estimate_names_units(entries, ctx, fname) -> int:
- # Avoid deep copy - just count fields directly
- count = 0
- try:
- note_regexes = [
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", True),
- (r"", False),
- (r"\n(.*)\n", False),
- (r"", False),
- (r"WATs:(.+?)>", False),
- (r"ADTs?:(.+?)>", False),
- (r"", False),
- (r"", False),
- (r"]+)", True),
- (r"]+)", True),
- (r"]+)", True),
- (r"", True),
- (r"", True),
- (r"", False),
- (r"<拡張説明:(.+?)>", False),
- (r"\n(.+?)\n<", False),
- (r"text:(.+)>", False),
- ]
-
- for entry in entries:
- if not entry:
- continue
- nm = entry.get("name") or ""
- ds = entry.get("description") or ""
- nn = entry.get("nickname") or ""
- pf = entry.get("profile") or ""
- if ctx == "Actors":
- if nm: count += 1
- if nn: count += 1
- if pf: count += 1
- elif ctx in ["Armors", "Weapons", "Items"]:
- if nm: count += 1
- if ds: count += 1
- elif ctx == "Skills":
- if nm: count += 1
- if ds: count += 1
- for k in range(1,5):
- if entry.get(f"message{k}"): count += 1
- elif ctx in ["Enemies", "Classes", "MapInfos"]:
- if nm: count += 1
-
- # Notes counting
- note = entry.get("note") or ""
- if isinstance(note, str) and note:
- for regex, _ww in note_regexes:
- try:
- matches = re.findall(regex, note, re.DOTALL)
- except Exception:
- matches = []
- if regex.startswith(r" 0 else 1
- except Exception:
- return 1
-
- total_units = _estimate_names_units(data, context, filename)
- if not isinstance(total_units, int) or total_units <= 0:
- # Reasonable fallback: count visible fields/messages (no notes)
- total_units = 0
- for entry in data:
- if not entry:
- continue
- if entry.get("name"): total_units += 1
- if context in ["Armors", "Weapons", "Items", "Skills"] and entry.get("description"): total_units += 1
- if context == "Actors":
- if entry.get("nickname"): total_units += 1
- if entry.get("profile"): total_units += 1
- if context == "Skills":
- for k in range(1,5):
- if entry.get(f"message{k}"): total_units += 1
- global PBAR
-
- with tqdm(total=total_units, bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE, desc=filename) as pbar:
- PBAR = pbar
- try:
- # Thread the filename through so progress saves write to the right file
- result = searchNames(data, pbar, context, filename)
- totalTokens[0] += result[0]
- totalTokens[1] += result[1]
- except Exception as e:
- traceback.print_exc()
- return [data, totalTokens, e]
- finally:
- # Persist progress only if this names pass produced tokens
- checkSave(data, filename, totalTokens)
- return [data, totalTokens, None]
-
-
-def parseSS(data, filename):
- totalTokens = [0, 0]
-
- # --- Preflight using searchSS over deep copy ---
- class _CountingBar:
- def __init__(self):
- self.n = 0
- def update(self, n=1):
- try:
- self.n += int(n) if n is not None else 1
- except Exception:
- self.n += 1
- def write(self, *args, **kwargs):
- pass
- def refresh(self):
- pass
-
- def _estimate_units(states, fname) -> int:
- # Avoid deep copy - just count fields directly
- count = 0
- try:
- for st in states:
- if not st:
- continue
- if st.get("name"): count += 1
- if st.get("description"): count += 1
- for n in range(1,5):
- if st.get(f"message{n}"): count += 1
- return count if count > 0 else 1
- except Exception:
- return 1
-
- total_units = _estimate_units(data, filename)
- if not isinstance(total_units, int) or total_units <= 0:
- total_units = 0
- for st in data:
- if not st:
- continue
- if st.get("name"): total_units += 1
- if st.get("description"): total_units += 1
- for n in range(1,5):
- if st.get(f"message{n}"): total_units += 1
- global PBAR
-
- with tqdm(total=total_units, bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE, desc=filename) as pbar:
- PBAR = pbar
- for ss in data:
- if ss is not None:
- try:
- result = searchSS(ss, pbar)
- totalTokens[0] += result[0]
- totalTokens[1] += result[1]
- except Exception as e:
- traceback.print_exc()
- return [data, totalTokens, e]
- finally:
- # Persist progress only if this state produced tokens
- checkSave(data, filename, result)
- return [data, totalTokens, None]
-
-
-def parseSystem(data, filename):
- totalTokens = [0, 0]
-
- # --- Preflight: call searchSystem on deep copy to count increments ---
- class _CountingBar:
- def __init__(self):
- self.n = 0
- def update(self, n=1):
- try:
- self.n += int(n) if n is not None else 1
- except Exception:
- self.n += 1
- def write(self, *args, **kwargs):
- pass
- def refresh(self):
- pass
-
- def _estimate_units(sysobj, fname) -> int:
- # Avoid deep copy - just count fields directly
- count = 0
- try:
- for term in sysobj.get("terms", {}) or {}:
- termList = sysobj["terms"][term]
- if isinstance(termList, list):
- count += len(termList)
- gt = sysobj.get("game_title")
- 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 [])
- count += len(sysobj.get("equip_types", []) or [])
- return count if count > 0 else 1
- except Exception:
- return 1
-
- total_units = _estimate_units(data, filename)
- if not isinstance(total_units, int) or total_units <= 0:
- # Fallback: rough count
- total_units = 0
- for term in data.get("terms", {}) or {}:
- termList = data["terms"][term]
- if isinstance(termList, list):
- total_units += len(termList)
- gt = data.get("game_title")
- if isinstance(gt, str) and gt:
- total_units += 1
- total_units += len(data.get("variables", []) or [])
- total_units += len(data.get("weapon_types", []) or [])
- total_units += len(data.get("armor_types", []) or [])
- total_units += len(data.get("skill_types", []) or [])
- total_units += len(data.get("equip_types", []) or [])
- global PBAR
-
- with tqdm(total=total_units, bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE, desc=filename) as pbar:
- PBAR = pbar
- try:
- result = searchSystem(data, pbar)
- totalTokens[0] += result[0]
- totalTokens[1] += result[1]
- except Exception as e:
- traceback.print_exc()
- return [data, totalTokens, e]
- finally:
- # Persist only if system sections produced tokens
- checkSave(data, filename, result)
- return [data, totalTokens, None]
-
-
-def parseScenario(data, filename):
- totalTokens = [0, 0]
- global LOCK
-
- # --- Preflight: run searchCodes on each page list ---
- class _CountingBar:
- def __init__(self):
- self.n = 0
- def update(self, n=1):
- try:
- self.n += int(n) if n is not None else 1
- except Exception:
- self.n += 1
- def write(self, *args, **kwargs):
- pass
- def refresh(self):
- pass
-
- def _estimate_units(scenario, fname) -> int:
- # Avoid deep copy - just count commands directly
- count = 0
- try:
- for key, lst in scenario.items():
- if lst is not None and "list" in lst:
- for cmd in lst.get("list", []):
- if cmd and "c" in cmd:
- code = cmd["c"]
- if code in [401, 405, 102, 122, 408, 355, 655, 356, 357, 320, 324, 325, 111, 108, 657]:
- count += 1
- return count if count > 0 else 1
- except Exception:
- return 1
-
- totalLines = _estimate_units(data, filename)
- if not isinstance(totalLines, int) or totalLines <= 0:
- totalLines = 0
- for _, lst in data.items():
- try:
- totalLines += len(lst or [])
- except Exception:
- pass
- global PBAR
-
- with tqdm(total=totalLines, bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE, desc=filename) as pbar:
- PBAR = pbar
- for page in data.items():
- if page[1] is not None:
- try:
- totalTokensPage = searchCodes(page[1], pbar, [], filename)
- totalTokens[0] += totalTokensPage[0]
- totalTokens[1] += totalTokensPage[1]
- except Exception as e:
- traceback.print_exc()
- return [data, totalTokens, e]
- finally:
- # Persist progress only if this page produced tokens
- checkSave(data, filename, totalTokensPage)
- return [data, totalTokens, None]
-
-
-def searchNames(data, pbar, context, filename):
- totalTokens = [0, 0]
- nameList = []
- profileList = []
- nicknameList = []
- descriptionList = []
- # For Skills: collect messages across all entries for batch translation
- messagesList = [] # List of tuples: (entry_idx, message_field, message_text, needs_taro)
- # Collect name mappings for vocab per run
- vocab_pairs: list[tuple[str, str]] = []
- vocab_enabled = context in ["Armors", "Weapons", "Items", "MapInfos", "Classes", "Enemies", "Skills"]
- # For batching all note types
- notesBatch = [] # List of (i, regex, match_text, note_type)
- notesBatchMap = [] # List of (i, regex, match_text, note_type, groupidx)
- i = 0 # Counter
- j = 0 # Counter 2
- filling = False
- mismatch = False
- batchFull = False
-
- # Set the context of what we are translating
- if "Actors" in context:
- newContext = "Reply with only the " + LANGUAGE + " translation of the NPC name"
- if "Armors" in context:
- newContext = "Reply with only the " + LANGUAGE + " translation of the RPG equipment name"
- if "Classes" in context:
- newContext = "Reply with only the " + LANGUAGE + " translation of the RPG class name"
- if "MapInfos" in context:
- newContext = "Reply with only the " + LANGUAGE + " translation of the location name"
- if "Enemies" in context:
- newContext = "Reply with only the " + LANGUAGE + " translation of the enemy NPC name"
- if "Weapons" in context:
- newContext = "Reply with only the " + LANGUAGE + " translation of the RPG weapon name"
- if "Items" in context:
- newContext = "Reply with only the " + LANGUAGE + " translation of the RPG item name"
- if "Skills" in context:
- newContext = "Reply with only the " + LANGUAGE + " translation of the RPG skill name"
-
- # Names
- with open("log/translations.txt", "a", encoding="utf-8") as file:
- file.write(f"\n#{context}\n")
-
- # --- Batching pass: collect all note texts for all note types ---
- note_regexes = [
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", False),
- (r"", True),
- (r"", False),
- (r"\n(.*)\n", False),
- (r"", False),
- (r"WATs:(.+?)>", False),
- (r"ADTs?:(.+?)>", False),
- (r"", False),
- (r"", False),
- (r"]+)", True),
- (r"]+)", True),
- (r"]+)", True),
- (r"", True),
- (r"", True),
- (r"", False),
- (r"<拡張説明:(.+?)>", False),
- (r"\n(.+?)\n<", False),
- (r"text:(.+)>", False),
- ]
- # For each entry, collect all note matches
- for idx, entry in enumerate(data):
- if entry is None or "note" not in entry or not entry["note"]:
- continue
- note = entry["note"]
- for regex, wordwrap in note_regexes:
- matches = re.findall(regex, note, re.DOTALL)
- # Special filter for to skip if 'Client' is in the match
- if regex.startswith(r"= len(translatedNotesBatch):
- break
- translated = translatedNotesBatch[note_insert_idx]
- if wordwrap:
- translated = dazedwrap.wrapText(translated, width=NOTEWIDTH)
- translated = translated.replace('"', "")
- # Use a safe literal match for the replacement (no re.escape, just str.replace)
- data[idx]["note"] = data[idx]["note"].replace(match_text, translated, 1)
- note_insert_idx += 1
-
- # --- For Skills: Batch translate all messages ---
- if context in ["Skills"]:
- messages_batch = []
- messages_map = [] # List of (entry_idx, message_field, needs_taro)
-
- for idx, entry in enumerate(data):
- if entry is None:
- continue
- # Collect all message1-4 fields
- for msg_num in range(1, 5):
- msg_field = f"message{msg_num}"
- if msg_field in entry and entry[msg_field]:
- msg_text = entry[msg_field]
- needs_taro = len(msg_text) > 0 and msg_text[0] in ["は", "を", "の", "に", "が"]
- if needs_taro:
- messages_batch.append("Taro" + msg_text)
- else:
- messages_batch.append(msg_text)
- messages_map.append((idx, msg_field, needs_taro))
-
- # Batch translate all messages
- if messages_batch:
- response = translateAI(
- messages_batch,
- "reply with only the gender neutral " + LANGUAGE + " translation of the action log. For messages starting with Taro, always start the sentence with Taro. For example, translate 'Taroを倒した!' as 'Taro was defeated!'",
- False,
- )
- translated_messages = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Apply translations back to data
- for msg_idx, (entry_idx, msg_field, needs_taro) in enumerate(messages_map):
- if msg_idx < len(translated_messages):
- translation = translated_messages[msg_idx]
- if needs_taro:
- translation = translation.replace("Taro", "")
- data[entry_idx][msg_field] = translation
-
- # Update progress for messages
- if pbar is not None:
- pbar.refresh()
-
- # Now continue with the rest of the batching logic for names, descriptions, etc.
- i = 0
- filling = False
- batchFull = False
- mismatch = False
- while i < len(data) or filling == True:
- if i < len(data):
- # Empty Data
- if data[i] is None or data[i]["name"] == "":
- i += 1
- continue
- # Filling up Batch
- filling = True
- if context in "Actors":
- if len(nameList) < BATCHSIZE:
- if data[i]["name"] != "":
- nameList.append(data[i]["name"])
- if "nickname" in data[i] and data[i]["nickname"]:
- nicknameList.append(data[i]["nickname"])
- if "profile" in data[i] and data[i]["profile"]:
- profileList.append(data[i]["profile"].replace("\n", " "))
- i += 1
- else:
- batchFull = True
- if context in ["Armors", "Weapons", "Items"]:
- if len(nameList) < BATCHSIZE:
- nameList.append(data[i]["name"])
- if "description" in data[i] and data[i]["description"] != "":
- description = data[i]["description"]
- description = description.replace("\n", " ")
- descriptionList.append(description)
- i += 1
- else:
- batchFull = True
- if context in ["Skills"]:
- if len(nameList) < BATCHSIZE:
- nameList.append(data[i]["name"])
- if "description" in data[i] and data[i]["description"]:
- descriptionList.append(data[i]["description"].replace("\n", " "))
- i += 1
- else:
- batchFull = True
- if context in ["Enemies", "Classes", "MapInfos"]:
- if len(nameList) < BATCHSIZE:
- nameList.append(data[i]["name"])
- i += 1
- else:
- batchFull = True
-
- # Batch Full
- if batchFull == True or i >= len(data):
- k = j # Original Index
- if context in "Actors":
- # Track tokens for this batch
- batchTokens = [0, 0]
- # Name
- response = translateAI(nameList, newContext)
- translatedNameBatch = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- batchTokens[0] += response[1][0]
- batchTokens[1] += response[1][1]
- if pbar is not None and nameList:
- pbar.refresh()
-
- # Nickname
- if nicknameList:
- response = translateAI(nicknameList, newContext)
- translatedNicknameBatch = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- batchTokens[0] += response[1][0]
- batchTokens[1] += response[1][1]
- if pbar is not None:
- pbar.refresh()
-
- # Profile
- if profileList:
- response = translateAI(profileList, "")
- translatedProfileBatch = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- batchTokens[0] += response[1][0]
- batchTokens[1] += response[1][1]
- if pbar is not None:
- pbar.refresh()
-
- # Set Data
- if len(nameList) == len(translatedNameBatch):
- j = k
- while j < i:
- # Empty Data
- if data[j] is None or data[j]["name"] == "":
- j += 1
- continue
- else:
- # Get Text
- if data[j]["name"] != "":
- with open("log/translations.txt", "a", encoding="utf-8") as file:
- file.write(f'{data[j]["name"]} ({translatedNameBatch[0]})\n')
- # Actors are excluded from vocab updates
- data[j]["name"] = translatedNameBatch[0]
- translatedNameBatch.pop(0)
- if "nickname" in data[j] and data[j]["nickname"]:
- data[j]["nickname"] = translatedNicknameBatch[0]
- translatedNicknameBatch.pop(0)
- if "profile" in data[j] and data[j]["profile"]:
- data[j]["profile"] = dazedwrap.wrapText(translatedProfileBatch[0], LISTWIDTH)
- translatedProfileBatch.pop(0)
-
- # If Batch is empty. Move on.
- if len(translatedNameBatch) == 0:
- nameList.clear()
- profileList.clear()
- nicknameList.clear()
- batchFull = False
- filling = False
- j += 1
- # Persist after applying this batch only if we actually translated something in this batch
- checkSave(data, filename, batchTokens)
- else:
- mismatch = True
-
- if context in ["Armors", "Weapons", "Items", "Skills"]:
- # Track tokens for this batch
- batchTokens = [0, 0]
- # Name
- response = translateAI(nameList, newContext)
- translatedNameBatch = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- batchTokens[0] += response[1][0]
- batchTokens[1] += response[1][1]
- if pbar is not None and nameList:
- pbar.refresh()
-
- # Description
- if descriptionList:
- response = translateAI(
- descriptionList,
- f"Reply with only the {LANGUAGE} translation of the text.",
- True,
- )
- translatedDescriptionBatch = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- batchTokens[0] += response[1][0]
- batchTokens[1] += response[1][1]
- if pbar is not None:
- pbar.refresh()
-
- # Set Data
- if len(nameList) == len(translatedNameBatch):
- j = k
- with open("log/translations.txt", "a", encoding="utf-8") as file:
- while j < i:
- # Empty Data
- if data[j] is None or data[j]["name"] == "":
- j += 1
- continue
- else:
- # Get Text
- file.write(f"{data[j]['name']} ({translatedNameBatch[0]})\n")
- if vocab_enabled:
- try:
- vocab_pairs.append((data[j]['name'], translatedNameBatch[0]))
- except Exception:
- pass
- data[j]["name"] = translatedNameBatch[0]
- translatedNameBatch.pop(0)
- if "description" in data[j] and data[j]["description"] != "":
- translatedDescriptionBatch[0] = dazedwrap.wrapText(translatedDescriptionBatch[0], LISTWIDTH)
- data[j]["description"] = translatedDescriptionBatch[0]
- translatedDescriptionBatch.pop(0)
-
- # If Batch is empty. Move on.
- if len(translatedNameBatch) == 0:
- nameList.clear()
- descriptionList.clear()
- batchFull = False
- filling = False
- j += 1
- # Persist after applying this batch only if we actually translated something in this batch
- checkSave(data, filename, batchTokens)
- else:
- mismatch = True
- if context in ["Enemies", "Classes", "MapInfos"]:
- # Track tokens for this batch
- batchTokens = [0, 0]
- response = translateAI(nameList, newContext)
- translatedNameBatch = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- batchTokens[0] += response[1][0]
- batchTokens[1] += response[1][1]
- if pbar is not None and nameList:
- pbar.refresh()
-
- # Set Data
- if len(nameList) == len(translatedNameBatch):
- j = k
- while j < i:
- # Empty Data
- if data[j] is None or data[j]["name"] == "":
- j += 1
- continue
- else:
- with open("log/translations.txt", "a", encoding="utf-8") as file:
- file.write(f'{data[j]["name"]} ({translatedNameBatch[0]})\n')
- # Get Text
- if vocab_enabled:
- try:
- vocab_pairs.append((data[j]["name"], translatedNameBatch[0]))
- except Exception:
- pass
- data[j]["name"] = translatedNameBatch[0]
- translatedNameBatch.pop(0)
-
- # If Batch is empty. Move on.
- if len(translatedNameBatch) == 0:
- nameList.clear()
- batchFull = False
- filling = False
- j += 1
- # Persist after applying this batch only if we actually translated something in this batch
- checkSave(data, filename, batchTokens)
- else:
- mismatch = True
-
- # Mismatch
- if mismatch == True:
- MISMATCH.append(nameList)
- nameList.clear()
- profileList.clear()
- descriptionList.clear()
- filling = False
- mismatch = False
- batchFull = False
-
- i += 1
-
- # Update vocab section once per context after processing all names
- if vocab_enabled and vocab_pairs:
- update_vocab_section(context, vocab_pairs)
-
- return totalTokens
-
-
-def searchCodes(page, pbar, jobList, filename):
- if len(jobList) > 0:
- list401 = jobList[0]
- list122 = jobList[1]
- list355655 = jobList[2]
- list108 = jobList[3]
- list356 = jobList[4]
- list357 = jobList[5]
- list324 = jobList[6]
- list408 = jobList[7]
- list325 = jobList[8]
- setData = False
- else:
- list401 = []
- list122 = []
- list355655 = []
- list108 = []
- list356 = []
- list357 = []
- list324 = []
- list408 = []
- list325 = []
- setData = True
- textHistory = []
- match = []
- totalTokens = [0, 0]
- translatedText = ""
- speaker = ""
- speakerID = None
- syncIndex = 0
- maxHistory = MAXHISTORY
- VNameValue = None
- global LOCK
- global NAMESLIST
- global MISMATCH
- global PBAR
- with LOCK:
- PBAR = pbar
-
- # Begin Parsing File
- try:
- # Normal Format
- if "list" in page:
- codeList = page["list"]
-
- # Special Format (Scenario)
- else:
- codeList = page
-
- # Iterate through page
- i = 0
- while i < len(codeList):
- with LOCK:
- # syncIndex will keep i in sync when it gets modified
- if syncIndex > i:
- i = syncIndex
- if len(codeList) <= i:
- break
-
- # Declare Varss
- currentGroup = []
- nametag = ""
-
- ## Event Code: 401 Show Text
- if "c" in codeList[i] and codeList[i]["c"] in [401, 405, -1] and (CODE401 or CODE405):
- # Save Code and starting index (j)
- code = codeList[i]["c"]
- j = i
- endtag = ""
- instantLineFlag = False
-
- # Grab String
- if len(codeList[i]["p"]) > 0:
- jaString = codeList[i]["p"][0]
- oldjaString = jaString
- else:
- codeList[i]["c"] = -1
- i += 1
- continue
-
- # # For Retarded Devs
- # retardRegex = r'([\\]+[nN]\[[\\]+V\[\d*?\]\])'
- # match = re.search(retardRegex, jaString)
- # if match:
- # if VNameValue == 1:
- # jaString = re.sub(retardRegex, 'リッカ', jaString)
- # if VNameValue == 2:
- # jaString = re.sub(retardRegex, 'ミミ', jaString)
- # if VNameValue == 3:
- # jaString = re.sub(retardRegex, 'ヒトミ', jaString)
- # if VNameValue == 4:
- # jaString = re.sub(retardRegex, 'Taro', jaString)
- # if VNameValue == 5:
- # jaString = re.sub(retardRegex, '富士見', jaString)
-
- # Speaker Check
- speakerList = []
-
- # Remove any RPGMaker Code at start
- ffMatch = re.search(
- r"^((?:[\\]+[^cCnNiIkKvVrRlL]+\[[\d\w]+\])+)",
- jaString,
- )
- if ffMatch != None:
- jaString = jaString.replace(ffMatch.group(0), "")
- nametag += ffMatch.group(0)
-
- # m and z Codes
- match = re.search(r"(.*?)[\\]+m\[\d+?\][\\]+z\[\d+?\]", jaString)
- if match:
- speakerList.append(match.group(1))
- if "\\c" in speakerList[0]:
- speakerList = re.findall(
- r"^[\\]+[cC]\[[^\]]+\]【(.+?)】[\\]+[cC]\[[^\]]+\](?:[\\]+[A-Za-z]+(?:\[[^\]]*\])?)*[\\]*$",
- speakerList[0],
- )
-
- # Brackets (support multiple names like 【A】【B】)
- # This now properly detects speakers after control codes are removed
- if len(speakerList) == 0:
- # Strip \sp, \l, \r control codes to check for bracketed speakers
- testString = re.sub(r"^(?:[\\]+[splrSPLR]\[[^\]]*\]\s*)+", "", jaString)
-
- # Check if the line contains bracketed names 【name】
- startsWithBracket = re.match(r"^\s*【", testString) is not None
- endsWithBracket = re.search(
- r"(】\s*$|】\s*(?:[\\]+[A-Za-z]+(?:\[(?:[^\[\]]|\[[^\]]*\])*\])+\s*)$)",
- testString,
- ) is not None
-
- if startsWithBracket and endsWithBracket:
- candidates = re.findall(r"【(.*?)】", testString)
- if candidates:
- candidates = [c.strip() for c in candidates]
- if candidates:
- speakerList = candidates
-
- # Colors (strict): require a bracketed name inside color codes to count as a speaker
- # This prevents arbitrary color-wrapped sentences from being misclassified as speakers.
- if len(speakerList) == 0:
- speakerList = re.findall(
- r"^[\\]+[cC]\[[^\]]+\]【(.+?)】[\\]+[cC]\[[^\]]+\](?:[\\]+[A-Za-z]+(?:\[[^\]]*\])?)*[\\]*$",
- jaString,
- )
-
- # Colons
- if len(speakerList) == 0:
- speakerList = re.findall(
- r"(.+):$",
- jaString,
- )
-
- # First Line Speakers
- if len(speakerList) == 0 and FIRSTLINESPEAKERS is True:
- # Test Speaker
- if (
- len(jaString) < 40
- and "c" in codeList[i + 1]
- and codeList[i + 1]["c"] in [401, 405, -1]
- and len(codeList[i + 1]["p"]) > 0
- and len(codeList[i + 1]["p"][0]) > 0
- ):
- nextString = codeList[i + 1]["p"][0].strip()
-
- # Remove any RPGMaker Code at start
- ffMatchNS = re.search(
- r"^((?:[\\]+[^cCnNiIkKvVSsrRlL{}]+?\[[\d\w\W]+?\]?\])+)",
- nextString,
- )
- if ffMatchNS != None:
- nextString = nextString.replace(ffMatchNS.group(1), "")
-
- # Remove other format codes
- formatMatch = re.search(r"(^[\\]+[\W]+?)", nextString)
- if formatMatch != None:
- nextString = nextString.replace(formatMatch.group(1), "")
-
- # If next line starts with dialogue marker, current line is likely a speaker
- if nextString and nextString[0] in [
- "「",
- '"',
- "(",
- "(",
- "*",
- "[",
- ]:
- speakerList = re.findall(r".+", jaString)
-
- # Replace Speaker
- if len(speakerList) != 0 and codeList[i + 1]["c"] in [401, 405, -1]:
- # Single
- if len(speakerList) == 1:
- response = getSpeaker(speakerList[0])
- speaker = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Multiple (Brackets)
- elif len(speakerList) > 1:
- jaStringUpdated = jaString
- for idx, sp in enumerate(speakerList):
- response = getSpeaker(sp)
- tled = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- if not setData:
- pattern = r"【\s*" + re.escape(sp) + r"\s*】"
- jaStringUpdated = re.sub(pattern, lambda m: f"【{tled}】", jaStringUpdated)
- # Back-compat: set 'speaker' to the first translated name
- if idx == 0:
- speaker = tled
-
- # Set Data
- if not setData and len(speakerList) > 1:
- codeList[i]["p"][0] = nametag + jaStringUpdated
- elif not setData and len(speakerList) == 1:
- codeList[i]["p"][0] = nametag + jaString.replace(speakerList[0], speaker)
- nametag = ""
-
- # Iterate to next string
- i += 1
- j = i
- while codeList[i]["c"] in [-1]:
- i += 1
- j = i
- jaString = codeList[i]["p"][0]
-
- # Check if there is text to translate
- if not re.search(r"\w+", jaString):
- i += 1
- continue
-
- # Validate Japanese Text
- if not re.search(LANGREGEX, jaString) and IGNORETLTEXT:
- i += 1
- continue
-
- # Using this to keep track of 401's in a row.
- currentGroup.append(jaString)
-
- # Join Up 401's into single string
- if len(codeList) > i + 1:
- while codeList[i + 1]["c"] in [401, 405, -1] and len(codeList[i]["p"]) > 0 and len(codeList[i + 1]["p"]) > 0 and not re.match(r"^(\s*[\\]+[aAbBdDeEfFgGhHjJlLmMoOpPqQrRsStTuUwWxXyYzZ]+\[[\w\d\[\]\\]+\])", codeList[i+1]["p"][0]):
- if not setData:
- codeList[i]["p"] = []
- codeList[i]["c"] = -1
- i += 1
- j = i
-
- jaString = codeList[i]["p"][0]
- if jaString.strip():
- currentGroup.append(jaString)
-
- # Make sure not the end of the list.
- if len(codeList) <= i + 1:
- break
-
- # Format String
- if len(currentGroup) > 0:
- finalJAString = "\n".join(currentGroup)
- oldjaString = finalJAString
-
- # Set Back
- if not setData:
- codeList[i]["p"] = [finalJAString]
-
- ### \\n
- regex = r"([\\]+[kKnN][wWcCrRrEe]?[\[<](?:[\\]*\w\[\d+\])?(.*?)(?:[\\]*\w\[\d+\])?[>])"
- match = re.search(regex, finalJAString)
-
- # Set Name
- if match:
- nametag = match.group(1)
- speaker = match.group(2)
-
- # Translate Speaker
- response = getSpeaker(speaker)
- tledSpeaker = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Set Nametag and Remove from Final String
- finalJAString = finalJAString.replace(nametag, "")
- nametag = nametag.replace(speaker, tledSpeaker)
- speaker = tledSpeaker
-
- # Remove Extra Stuff bad for translation.
- finalJAString = finalJAString.replace("゙", "")
- finalJAString = finalJAString.replace("…", "...")
- finalJAString = finalJAString.replace("。", ".")
- finalJAString = re.sub(r"(\.{3}\.+)", "...", finalJAString)
- finalJAString = finalJAString.replace(" ", "")
- finalJAString = finalJAString.replace("「", '"')
- finalJAString = finalJAString.replace("」", '"')
- finalJAString = finalJAString.replace("\\,", ',')
-
- ### Remove format codes
- # Furigana: \r or \rb [base,reading] -> keep reading/base per pattern
- finalJAString = re.sub(r"[\\]+[rR][bB]?\[(.*?),.*?\]", r"\1", finalJAString)
-
- # Curly-brace furigana: {base|reading} -> keep base
- finalJAString = re.sub(r"\{([^|{}]+)\|[^|{}]+?\}", r"\1", finalJAString)
-
- # Remove any RPGMaker Code at start
- ffMatch = re.search(
- r"^((?:[\\]+[^cCnNiIkKvVSsrRlL{}]+?\[[\d\w\W]+?\]?\])+)",
- finalJAString,
- )
- if ffMatch != None:
- finalJAString = finalJAString.replace(ffMatch.group(1), "")
- nametag = ffMatch.group(1) + nametag
-
- # Remove _ABL Codes
- ffMatch = re.search(r"^(_ABL).*", finalJAString)
- if ffMatch != None:
- finalJAString = finalJAString.replace(ffMatch.group(1), "")
- nametag += ffMatch.group(1)
-
- # Center Lines (We Nuke These)
- if "\\CL" in finalJAString or "\\ac" in finalJAString or "\\#" in finalJAString:
- finalJAString = finalJAString.replace("\\CL", "")
- finalJAString = finalJAString.replace("\\ac", "")
- finalJAString = finalJAString.replace("\\#", "")
-
- # Handle Formatting Codes
- if "\\>" in finalJAString:
- instantLineFlag = True
- finalJAString = finalJAString.replace("\\>", "")
-
- # Check if Empty
- if finalJAString == "":
- if nametag and match:
- codeList[j]["p"][0] = codeList[j]["p"][0].replace(match.group(2), tledSpeaker)
- i += 1
- continue
-
- # Pass 1 (Grabbing Data)
- if setData:
- # Remove Textwrap
- if FIXTEXTWRAP:
- finalJAString = finalJAString.replace("\n", " ")
- if "\\px[200]" in finalJAString:
- finalJAString = finalJAString.replace("\\px[200]", "")
-
- # Append
- if finalJAString != "":
- if speaker == "" and finalJAString != "":
- list401.append(finalJAString)
- elif finalJAString != "":
- list401.append(f"[{speaker}]: {finalJAString}")
- else:
- list401.append(speaker)
- speaker = ""
- match = []
- nametag = ""
- currentGroup = []
- syncIndex = i + 1
-
- # Keep textHistory list at length maxHistory
- textHistory.append('"' + finalJAString + '"')
- if len(textHistory) > maxHistory:
- textHistory.pop(0)
-
- # Pass 2 (Setting Data)
- else:
- # Grab Translated String
- if len(list401) > 0:
- translatedText = list401[0]
-
- # Remove speaker
- match = re.search(r'(^\[.+?\]\s?[|:]\s?)', translatedText)
- if match:
- translatedText = translatedText.replace(match.group(1), "")
-
- # Fix '- '
- translatedText = translatedText.replace("- ", "-")
-
- # Textwrap
- if FIXTEXTWRAP is True:
- finalJAString = re.sub(r"\n", " ", finalJAString)
- finalJAString = finalJAString.replace("
", " ")
-
- if FIXTEXTWRAP is True and "_ABL" in nametag:
- translatedText = dazedwrap.wrapText(translatedText, width=100)
- elif FIXTEXTWRAP is True:
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
-
- # Formatting Code
- if instantLineFlag:
- translatedText = translatedText.replace("\n", "\n\\>")
- translatedText = f"\\>{translatedText}"
-
- # BR Flag
- if BRFLAG is True:
- translatedText = translatedText.replace("\n", "
")
-
- # px
- if "\\px[200]" in nametag:
- translatedText = translatedText.replace("\\px[200]", "")
- translatedText = translatedText.replace("\n", "\n\\px[200]")
-
- # Add Nametag Back In
- translatedText = nametag + translatedText
- nametag = ""
-
- # Endtag
- if endtag != "":
- translatedText = translatedText + endtag
- endtag = ""
-
- # Set Code
- codeList[j]["c"] = code
-
- # Handle 405
- if codeList[j]["c"] == 405:
- # 1. Split translatedText by newlines
- lines = [line for line in translatedText.split('\n') if line.strip() != ""]
-
- # 2. Set the first string to codeList[j]["p"]
- codeList[j]["p"] = [lines[0]]
-
- # 3. Make copies for each additional line and insert them
- for idx, line in enumerate(lines[1:]):
- new_item = copy.deepcopy(codeList[j])
- new_item["p"] = [line]
- codeList.insert(j + idx + 1, new_item)
-
- # 4. Update syncIndex to the last modified/added position
- syncIndex = j + len(lines)
-
- # Handle 401
- else:
- codeList[j]["p"] = [translatedText]
- codeList[j]["c"] = code
- syncIndex = i + 1
-
- # Reset
- speaker = ""
- match = []
- currentGroup = []
- list401.pop(0)
-
- ## Event Code: 122 [Set Variables]
- if "c" in codeList[i] and codeList[i]["c"] == 122 and CODE122 is True:
- # This is going to be the var being translated.
- # Only translate variables within the specified range.
- if codeList[i]["p"][0] not in list(range(CODE122_VAR_MIN, CODE122_VAR_MAX)):
- i += 1
- continue
-
- jaString = codeList[i]["p"][4]
-
- # # For Retarded Devs
- # VNameValue = jaString
- # i += 1
- # continue
-
- # Validate String
- if not isinstance(jaString, str):
- i += 1
- continue
-
- # Definitely don't want to mess with files
- if 'gameV' in jaString or '_' in jaString or '"[' in jaString:
- i += 1
- continue
-
- # # Avoid anything not quoted
- # if '\"' not in jaString:
- # i += 1
- # continue
-
- # Validate Japanese Text
- # if not re.search(LANGREGEX, jaString):
- # i += 1
- # continue
-
- # Set String
- matchedText = None
- if len(re.findall(r"([\'\"\`])", jaString)) >= 2:
- matchedText = re.search(r"[\'\"\`](.*)[\'\"\`]", jaString)
- if matchedText and matchedText.group(1).strip():
- # Remove Textwrap
- finalJAString = matchedText.group(1).replace("\\n", " ")
-
- # Pass 1
- if setData:
- if finalJAString != "":
- list122.append(finalJAString)
-
- # Pass 2
- else:
- if len(list122) > 0:
- # Grab and Replace
- translatedText = list122[0]
- translatedText = jaString.replace(jaString, translatedText)
-
- # Remove characters that may break scripts
- charList = ['"', "\\n"]
- for char in charList:
- translatedText = translatedText.replace(char, "")
-
- # Force 4 Escapes
- translatedText = re.sub(r'(? 0:
- # Grab and Replace
- translatedText = list357[0]
- translatedText = jaString.replace(jaString, translatedText)
-
- # Remove characters that may break scripts
- charList = ['"', "\\n"]
- for char in charList:
- translatedText = translatedText.replace(char, "")
-
- # Textwrap
- # translatedText = dazedwrap.wrapText(translatedText, 80)
- # translatedText = translatedText.replace("\n", "\\n")
- # translatedText = re.sub(r"[\\]+c", r"\\\\c", translatedText)
- translatedText = re.sub(r"[\\]+\*item", r"\\\\*item", translatedText)
-
- # Center Text
- if acExist:
- translatedText = f'\\ac {translatedText.replace('\n', '\n\\ac ')}'
-
- # Check and Set Font
- if "fontSize" in codeList[i]["p"][3]:
- if font:
- codeList[i]["p"][3]["fontSize"] = font
-
- # Set
- codeList[i]["p"][3][argVar] = f"{translatedText}"
- list357.pop(0)
-
- # 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),
- }
-
- for key, (argVar, font) in headerMappings.items():
- if key in headerString:
- translatePlugins(argVar, font)
-
- # AdvExtention plugin support (message event)
- if headerString == "AdvExtentionllk" and len(codeList[i]["p"]) > 3:
- try:
- params_obj = codeList[i]["p"][3]
- except Exception:
- params_obj = None
-
- if isinstance(params_obj, dict):
- # 1) Speaker comes from 'name', fallback to 'altName' if missing/empty
- speaker_name = ""
- if isinstance(params_obj.get("altName", None), str) and params_obj["altName"].strip():
- speaker_name = params_obj["altName"].strip()
- if speaker_name:
- response = getSpeaker(speaker_name)
- params_obj["altName"] = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- speaker = response[0]
- if isinstance(params_obj.get("name", None), str) and params_obj["name"].strip():
- speaker_name = params_obj["name"].strip()
- if speaker_name:
- response = getSpeaker(speaker_name)
- params_obj["name"] = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- speaker = response[0]
- speaker = ""
-
- # 2) Line comes from 'comment' if present, else 'text'
- chosen_key = None
- if isinstance(params_obj.get("comment", None), str) and params_obj["comment"].strip():
- chosen_key = "comment"
- elif isinstance(params_obj.get("text", None), str):
- chosen_key = "text"
-
- if chosen_key is not None:
- jaString = params_obj.get(chosen_key, "")
- if isinstance(jaString, str):
- # Pass 1 (collect data)
- if setData:
- if FIXTEXTWRAP:
- jaString = jaString.replace("\n", " ")
- # Include speaker context like 401 does
- if 'speaker' in locals() and isinstance(speaker, str) and speaker.strip():
- list357.append(f"[{speaker}]: {jaString}")
- else:
- list357.append(jaString)
- # Pass 2 (apply translation)
- else:
- if len(list357) > 0:
- translatedText = list357[0]
- list357.pop(0)
-
- # Remove speaker prefix if present (same pattern used for 401)
- m = re.search(r'(^\[.+?\]\s?[|:]\s?)', translatedText)
- if m:
- translatedText = translatedText.replace(m.group(1), "")
-
- if FIXTEXTWRAP:
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
-
- params_obj[chosen_key] = translatedText
-
- if headerString == "LL_GalgeChoiceWindow":
- ### Message Text First
- jaString = codeList[i]["p"][3]["messageText"]
-
- # Remove any textwrap & TL
- jaString = re.sub(r"\n", " ", jaString)
- response = translateAI(jaString, "")
- translatedText = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Textwrap & Set
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
- codeList[i]["p"][3]["messageText"] = translatedText
-
- ### Choices
- jaString = codeList[i]["p"][3]["choices"]
- matchList = re.findall(r'"label[\\]*":[\\]*"(.*?)[\\]', jaString)
- if matchList != None:
- # Translate
- question = codeList[i]["p"][3]["messageText"]
- response = translateAI(
- matchList,
- f"Previous text for context: {question}\n",
- True,
- )
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- translatedText = jaString
-
- # Replace Strings
- for j in range(len(matchList)):
- translatedText = translatedText.replace(matchList[j], response[0][j].replace('"', ''))
-
- # Set Data
- codeList[i]["p"][3]["choices"] = translatedText
-
- ## Event Code: 657 [Picture Text] [Optional]
- if "c" in codeList[i] and codeList[i]["c"] == 657 and CODE657 is True:
- if "text" in codeList[i]["p"][0]:
- jaString = codeList[i]["p"][0]
- if not isinstance(jaString, str):
- i += 1
- continue
-
- # Definitely don't want to mess with files
- if "_" in jaString:
- i += 1
- continue
-
- # If there isn't any Japanese in the text just skip
- if not re.search(LANGREGEX, jaString):
- i += 1
- continue
-
- # Remove outside text
- startString = re.search(r"^[^一-龠ぁ-ゔァ-ヴー\<\>【】\\]+", jaString)
- jaString = re.sub(r"^[^一-龠ぁ-ゔァ-ヴー\<\>【】\\]+", "", jaString)
- endString = re.search(r"[^一-龠ぁ-ゔァ-ヴー\<\>【】。!?\\]+$", jaString)
- jaString = re.sub(r"[^一-龠ぁ-ゔァ-ヴー\<\>【】。!?\\]+$", "", jaString)
- if startString is None:
- startString = ""
- else:
- startString = startString.group()
- if endString is None:
- endString = ""
- else:
- endString = endString.group()
-
- # Remove any textwrap
- jaString = re.sub(r"\n", " ", jaString)
-
- # Translate
- response = translateAI(jaString, "")
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- translatedText = response[0]
-
- # Remove characters that may break scripts
- charList = [".", '"', "'"]
- for char in charList:
- translatedText = translatedText.replace(char, "")
-
- # Textwrap
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
- translatedText = startString + translatedText + endString
-
- # Set Data
- codeList[i]["p"][0] = translatedText
-
- ## Event Code: 101 [Name] [Optional]
- if "c" in codeList[i] and codeList[i]["c"] == 101 and CODE101 is True:
- isVar = False
-
- # Grab String
- jaString = ""
- if len(codeList[i]["p"]) > 4:
- jaString = codeList[i]["p"][4]
- # Check for Var
- elif len(codeList[i]["p"]) > 0:
- jaString = codeList[i]["p"][0]
- isVar = True
- if not isinstance(jaString, str):
- i += 1
- continue
-
- # Force Speaker using var
- if "memerisu" in jaString.lower():
- speaker = "Memerisu"
- i += 1
- continue
- elif "thina" in jaString.lower():
- speaker = "Tina"
- i += 1
- continue
- elif "\\ap" in jaString:
- speaker = re.search(r"[\\]+AP\[(.*?)\]", jaString).group(1)
- i += 1
- continue
-
- # Get Speaker
- match = re.search(r"^(?:[\\]+[cC]\[\d+?\])?([\w\s]+)", jaString)
- if match:
- jaString = match.group(1)
- response = getSpeaker(jaString)
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- speaker = response[0]
-
- # Validate Speaker is not empty
- if len(speaker) > 0:
- if isVar == False:
- codeList[i]["p"][4] = codeList[i]["p"][4].replace(jaString, speaker)
- i += 1
- continue
- else:
- codeList[i]["p"][0] = codeList[i]["p"][0].replace(jaString, speaker)
- isVar = False
- i += 1
- continue
- else:
- speaker = ""
- elif FACENAME101:
- faceName = codeList[i]["p"][0]
- if faceName == "Actor1_1":
- speaker = "Sakura"
- if faceName == "Actor2_1":
- speaker = "Suzune"
- if faceName == "Actor3_1":
- speaker = "Kaji"
- if faceName == "Actor4_1":
- speaker = "Kirari"
- if faceName == "Actor5_1":
- speaker = "Onsen"
- if faceName == "Actor6_1":
- speaker = "Gufu"
- if faceName == "Actor7_1":
- speaker = "Kahimeru"
- if faceName == "Actor10_1":
- speaker = "Miuma"
- if faceName == "Actor11_1":
- speaker = "Nurari"
- if faceName == "Actor12_1":
- speaker = "Kokotsuzumi"
-
- ## Event Code: 355 or 655 Scripts [Optional]
- if "c" in codeList[i] and (codeList[i]["c"] == 355 or codeList[i]["c"] == 655) and CODE355655 is True:
- jaString = codeList[i]["p"][0]
-
- patterns = {
- # "テキスト-": (r"テキスト-(.+)")
- # "=": (r'=\s?(.*)",'),
- # "var text": (r"var\stext\d+\s=\s\"(.+)\""),
- # "logtxt = ": (r"logtxt\s=\s'(.+)'"
- # ".setNickname": (r'.setNickname\(\\?"(.+?)\\?"\)'
- # "_subject=": (r'_subject=(.+?)_'
- # "text =": (r"text\s*=\s*'(.+[^\\])'"),
- # "const text": (r'(const\stext\s?=\s?"(.+)";?)'),
- # "ex_a_name": (r'ex_a_name\(\d+,"(.+)"\)'),
- # "gameVariables.setValue": (r"\$gameVariables.setValue\(\d+,\s?'(.+)'\)"),
- # "BattleManager._logWindow.push('addText'": (r"BattleManager._logWindow.push\('addText',\s'(.+)'\)"),
- # "BattleManager._logWindow.addText": (r"BattleManager._logWindow.addText\('(.+)'\)"),
- }
-
- for key, (regex) in patterns.items():
- if key in jaString:
- match = re.search(regex, jaString)
- if match:
- # Check if the match contains actual text (not just numbers/special chars)
- if not re.search(r'[a-zA-Z一-龠ぁ-ゔァ-ヴーa-zA-Z0-9\uFF61-\uFF9F]', match.group(1)):
- continue
-
- # Pass 1
- if setData:
- list355655.append(match.group(1))
-
- # Pass 2
- else:
- # Grab and Replace
- translatedText = list355655[0]
- list355655.pop(0)
-
- # Only escape if not already escaped
- matchList = re.findall(r"(.+)'\s*[$+].+?'(.+)", translatedText)
- if matchList:
- for string in matchList[0]:
- escapedMatch = re.sub(r"(? i + 1
- and "c" in codeList[i + 1]
- and codeList[i + 1]["c"] in [408, -1]
- and len(codeList[i + 1]["p"]) > 0
- and len(codeList[i + 1]["p"][0]) > 0
- ):
- nextString = codeList[i + 1]["p"][0].strip()
-
- # Remove any RPGMaker Code at start
- ffMatchNS = re.search(
- r"^((?:[\\]+[^cCnNiIkKvVSsrRlL{}]+?\[[\d\w\W]+?\]?\])+)",
- nextString,
- )
- if ffMatchNS != None:
- nextString = nextString.replace(ffMatchNS.group(1), "")
-
- # Remove other format codes
- formatMatch = re.search(r"(^[\\]+[\W]+?)", nextString)
- if formatMatch != None:
- nextString = nextString.replace(formatMatch.group(1), "")
-
- # If next line starts with dialogue marker, current line is likely a speaker
- if nextString and nextString[0] in [
- "「",
- '"',
- "(",
- "(",
- "*",
- "[",
- ]:
- speakerList = re.findall(r".+", jaString)
-
- # Replace Speaker
- if len(speakerList) != 0 and len(codeList) > i + 1 and codeList[i + 1]["c"] in [408, -1]:
- # Single
- if len(speakerList) == 1:
- response = getSpeaker(speakerList[0])
- speaker = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Multiple (Brackets)
- elif len(speakerList) > 1:
- jaStringUpdated = jaString
- for idx, sp in enumerate(speakerList):
- response = getSpeaker(sp)
- tled = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- if not setData:
- pattern = r"【\s*" + re.escape(sp) + r"\s*】"
- jaStringUpdated = re.sub(pattern, lambda m: f"【{tled}】", jaStringUpdated)
- # Back-compat: set 'speaker' to the first translated name
- if idx == 0:
- speaker = tled
-
- # Set Data
- if not setData and len(speakerList) > 1:
- codeList[i]["p"][0] = nametag + jaStringUpdated
- elif not setData and len(speakerList) == 1:
- codeList[i]["p"][0] = nametag + jaString.replace(speakerList[0], speaker)
- nametag = ""
-
- # Iterate to next string
- i += 1
- j = i
- while codeList[i]["c"] in [-1]:
- i += 1
- j = i
- jaString = codeList[i]["p"][0]
-
- # Using this to keep track of 408's in a row.
- currentGroup.append(jaString)
-
- # Join Up 408's into single string
- if len(codeList) > i + 1 and JOIN408 is True:
- while codeList[i + 1]["c"] in [408] and len(codeList[i]["p"]) > 0 and len(codeList[i + 1]["p"]) > 0 and not re.match(r"^(\s*[\\]+[aAbBdDeEfFgGhHjJlLmMoOpPqQrRsStTuUwWxXyYzZ]+\[[\w\d\[\]\\]+\])", codeList[i+1]["p"][0]):
- if not setData:
- codeList[i]["p"] = []
- codeList[i]["c"] = -1
- i += 1
- j = i
-
- jaString = codeList[i]["p"][0]
- if jaString.strip():
- currentGroup.append(jaString)
-
- # Make sure not the end of the list.
- if len(codeList) <= i + 1:
- break
-
- # Format String
- if len(currentGroup) > 0:
- finalJAString = "\n".join(currentGroup)
-
- # Set Back
- if not setData:
- codeList[j]["p"] = [finalJAString]
-
- # Process speaker name tag if SPEAKERS408 enabled
- if SPEAKERS408:
- ### \\n
- regex = r"([\\]+[kKnN][wWcCrRrEe]?[\[<](?:[\\]*\w\[\d+\])?(.*?)(?:[\\]*\w\[\d+\])?[>])"
- matchSpeaker = re.search(regex, finalJAString)
-
- # Set Name
- if matchSpeaker:
- nametag = matchSpeaker.group(1)
- speakerName = matchSpeaker.group(2)
-
- # Translate Speaker
- response = getSpeaker(speakerName)
- tledSpeaker = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Set Nametag and Remove from Final String
- finalJAString = finalJAString.replace(nametag, "")
- nametag = nametag.replace(speakerName, tledSpeaker)
- speaker = tledSpeaker
-
- # Pass 1
- if setData:
- # Remove Textwrap
- finalJAString = finalJAString.replace("\n", " ")
-
- # Append with or without speaker
- if SPEAKERS408 and speaker != "" and finalJAString != "":
- list408.append(f"[{speaker}]: {finalJAString}")
- else:
- list408.append(finalJAString)
-
- speaker = ""
- currentGroup = []
- syncIndex = i + 1
-
- # Pass 2
- else:
- if len(list408) > 0:
- translatedText = list408[0]
- list408.pop(0)
-
- # Remove speaker if SPEAKERS408 enabled
- if SPEAKERS408:
- matchSpeaker = re.search(r'(^\[.+?\]\s?[|:]\s?)', translatedText)
- if matchSpeaker:
- translatedText = translatedText.replace(matchSpeaker.group(1), "")
-
- # Textwrap
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
-
- # Add Nametag Back In if SPEAKERS408 enabled
- if SPEAKERS408 and nametag:
- translatedText = nametag + translatedText
- nametag = ""
-
- # Set Data
- codeList[j]["p"][0] = translatedText
-
- # Reset
- speaker = ""
- currentGroup = []
- syncIndex = i + 1
-
- ## Event Code: 108 (Script)
- if "c" in codeList[i] and (codeList[i]["c"] == 108) and CODE108 is True:
- jaString = codeList[i]["p"][0]
-
- # If there isn't any Japanese in the text just skip
- if not re.search(LANGREGEX, jaString):
- i += 1
- continue
-
- # Translate
- if "info:" in jaString:
- regex = r"info:([^,]+)"
- elif "ActiveMessage:" in jaString:
- regex = r"?"
- elif "event_text" in jaString:
- regex = r"event_text\s*:\s*(.*)"
- elif "Menu Name" in jaString:
- regex = r"Menu\sName\s*:\s*(.*)>"
- elif "text_indicator" in jaString:
- regex = r"text_indicator\s?:\s?(.+)"
- else:
- i += 1
- continue
-
- # Need to remove outside code and put it back later
- match = re.search(regex, jaString)
- if match:
- # Pass 1
- if setData:
- list108.append(match.group(1))
-
- # # Grab Next
- # j = i
- # while codeList[j + 1]["c"] == 408:
- # j += 1
- # list108[0] = list108[0] + codeList[j]["p"][0].replace(">", "")
- # codeList[j]["p"][0] = ""
- # list108[0] = list108[0].replace("\n", " ")
-
- # Pass 2
- else:
- # Grab and Replace
- translatedText = list108[0]
- list108.pop(0)
-
- # Textwrap
- # if codeList[i + 1]["c"] == 408:
- # translatedText = dazedwrap.wrapText(translatedText, WIDTH)
-
- # Remove characters that may break scripts
- charList = ['"']
- for char in charList:
- translatedText = translatedText.replace(char, "")
- translatedText = translatedText.replace('"', '"')
- translatedText = translatedText.replace(" ", "_")
- translatedText = jaString.replace(match.group(1), translatedText)
-
- # Add >
- if "ActiveMessage" in translatedText and ">" not in translatedText:
- translatedText = translatedText + ">"
-
- # Set Data
- codeList[i]["p"][0] = translatedText
-
- ## Event Code: 356
- if "c" in codeList[i] and codeList[i]["c"] == 356 and CODE356 is True:
- jaString = codeList[i]["p"][0]
- oldjaString = jaString
-
- # Grab Speaker
- if "Tachie showName" in jaString:
- matchList = re.findall(r"Tachie showName (.+)", jaString)
- if len(matchList) > 0:
- # Translate
- response = translateAI(
- matchList[0],
- "Reply with the " + LANGUAGE + " translation of the NPC name.",
- False,
- )
- translatedText = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Set Text
- speaker = translatedText
- speaker = speaker.replace(" ", " ")
- codeList[i]["p"][0] = jaString.replace(matchList[0], speaker)
- i += 1
- continue
-
- # Want to translate this script
- if "D_TEXT " in jaString:
- regex = r"D_TEXT\s*([^\s]+)\s?\d*"
- elif "ShowInfo" in jaString:
- regex = r"ShowInfo\s(.*)"
- elif "PushGab" in jaString:
- regex = r"PushGab\s(.*)"
- elif "addLog" in jaString:
- regex = r"addLog\s(.*)"
- elif "DW_" in jaString:
- regex = r"DW_.*\s\d+\s(.+)"
- elif "CommonPopup" in jaString:
- regex = r"CommonPopup\sadd\stext:(.*?)[\\]+}"
- elif "AddCustomChoice" in jaString:
- regex = r"AddCustomChoice\s\d+\s(.+)\s\d"
- else:
- regex = r""
-
- # Remove any textwrap
- jaString = re.sub(r"\n", "_", jaString)
-
- # Capture Arguments and text
- textMatch = re.search(regex, jaString)
- if textMatch and textMatch.group(0) != "":
- text = textMatch.group(1)
-
- # Capture Speakers
- match = re.search(r"[\\]+ow\[\d+\][\\]+c\[\d+\](.+)", text)
- if match:
- speakerJA = match.group(1)
-
- # Translate
- response = getSpeaker(speakerJA)
- translatedText = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- codeList[i]["p"][0] = jaString.replace(speakerJA, translatedText)
- i += 1
- continue
- else:
- speaker = ""
-
- # Pass 1
- if setData:
- text = text.replace("_", " ")
- list356.append(text)
-
- # Pass 2
- else:
- if len(list356) > 0:
- # Grab
- translatedText = list356[0]
-
- # Remove characters that may break scripts
- charList = [".", '"']
- for char in charList:
- translatedText = translatedText.replace(char, "")
-
- # Cant have spaces?
- translatedText = translatedText.replace(" ", "_")
- translatedText = translatedText.replace("__", "_")
-
- # Put Args Back
- translatedText = jaString.replace(text, translatedText)
-
- # Set Data
- codeList[i]["p"][0] = translatedText
- list356.pop(0)
-
- if "namePop" in jaString:
- # Support both "" and "namePop [num] text" formats
- matchList = re.findall(r"]+)>", jaString)
- if not matchList:
- m = re.search(r"\bnamePop\b\s*(?:-?\d+)?\s*([^\r\n<>]+)", jaString)
- if m:
- matchList = [m.group(1).strip()]
- if len(matchList) > 0:
- # Translate
- text = matchList[0]
- response = translateAI(text, "Reply with the " + LANGUAGE + " Translation")
- translatedText = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Set Data
- updated = jaString.replace(text, translatedText.replace(" ", "_"))
- codeList[i]["p"][0] = updated
-
- if "LL_InfoPopupWIndowMV" in jaString:
- matchList = re.findall(r"LL_InfoPopupWIndowMV\sshowWindow\s(.+?) .+", jaString)
- if len(matchList) > 0:
- text = matchList[0]
-
- # Pass 1: collect into batch
- if setData:
- # store without underscores for cleaner translation later
- list356.append(text.replace("_", " "))
-
- # Pass 2: apply translations from list356
- else:
- if len(list356) > 0:
- translatedText = list356[0]
- list356.pop(0)
-
- # Replace spaces with underscores as original format expects
- translatedText = translatedText.replace(" ", "_")
-
- # Put Args Back
- translatedText = jaString.replace(text, translatedText)
-
- # Set Data
- codeList[i]["p"][0] = translatedText
-
- if "OriginMenuStatus SetParam" in jaString:
- matchList = re.findall(r"OriginMenuStatus\sSetParam\sparam[\d]\s(.*)", jaString)
- if len(matchList) > 0:
- # Translate
- text = matchList[0]
- response = translateAI(text, "Reply with the " + LANGUAGE + " Translation")
- translatedText = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Set Data
- translatedText = translatedText.replace(" ", "_")
- translatedText = jaString.replace(text, translatedText)
- codeList[i]["p"][0] = translatedText
-
- # LL_GalgeChoiceWindowMV Message
- if "LL_GalgeChoiceWindowMV setMessageText" in jaString:
- ### Message Text First
- match = re.search(r"LL_GalgeChoiceWindowMV setMessageText (.+)", jaString)
- if match:
- jaString = match.group(1)
-
- # Remove any textwrap & TL
- jaString = re.sub(r"\n", " ", jaString)
- response = translateAI(jaString, "")
- translatedText = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Textwrap & Replace Whitespace
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
- translatedText = translatedText.replace(" ", "_")
-
- # Replace and Set
- translatedText = match.group(0).replace(match.group(1), translatedText)
- codeList[i]["p"][0] = translatedText
-
- # LL_GalgeChoiceWindowMV Choices
- if "LL_GalgeChoiceWindowMV setChoices":
- match = re.search(r"LL_GalgeChoiceWindowMV setChoices (.+)", jaString)
- if match:
- jaString = match.group(1)
- choiceList = jaString.split(",")
-
- # Translate
- question = translatedText
- response = translateAI(
- choiceList,
- f"Previous text for context: {question}\n",
- True,
- )
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- choiceListTL = response[0]
- translatedText = match.group(0)
-
- # Replace Strings
- for j in range(len(choiceListTL)):
- choiceListTL[j] = choiceListTL[j].replace(" ", "_")
- translatedText = translatedText.replace(choiceList[j], choiceListTL[j])
-
- # Set Data
- codeList[i]["p"][0] = translatedText
-
- ### Event Code: 102 Show Choice
- if "c" in codeList[i] and codeList[i]["c"] == 102 and CODE102 is True:
- choiceList = []
- varList = []
- choiceIndexMap = [] # Track which original indices we're processing
-
- # Process each string in the parameters list
- for choice in range(len(codeList[i]["p"][0])):
- jaString = codeList[i]["p"][0][choice]
- jaString = jaString.replace(" 。", ".")
-
- # Avoid Empty Strings
- if not jaString.strip():
- continue
-
- # If and En Statements
- ifVar = ""
- ifList = re.findall(r"([ei][nf]\(.+?\)\)?\)?)", jaString)
- if len(ifList) != 0:
- for var in ifList:
- jaString = jaString.replace(var, "")
- ifVar += var
-
- # Store the formatting and cleaned string
- varList.append(ifVar)
- choiceList.append(jaString)
- choiceIndexMap.append(choice)
-
- # Translate the list
- if len(choiceList) > 0:
- if len(textHistory) > 0:
- response = translateAI(
- choiceList,
- f"Reply with the English translation of the dialogue choice.\n\nPrevious text for context: {str(textHistory)}\n",
- True,
- )
- else:
- response = translateAI(choiceList, "Reply with the English translation of the dialogue choice.")
-
- translatedTextList = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Check Mismatch and set translations
- if len(translatedTextList) == len(choiceList):
- for idx, translatedText in enumerate(translatedTextList):
- originalIndex = choiceIndexMap[idx]
-
- # Apply formatting
- if translatedText != "":
- translatedText = varList[idx] + translatedText[0].upper() + translatedText[1:]
- else:
- translatedText = varList[idx] + translatedText
-
- # Set the translation back to the original position
- codeList[i]["p"][0][originalIndex] = translatedText
- else:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
-
- ### Event Code: 111 Script
- if "c" in codeList[i] and codeList[i]["c"] == 111 and CODE111 is True:
- for j in range(len(codeList[i]["p"])):
- jaString = codeList[i]["p"][j]
-
- # Check if String
- if not isinstance(jaString, str):
- continue
-
- # Only TL the Game Variable
- if "$gameVariables" not in jaString:
- continue
-
- # This is going to be the var being set. (IMPORTANT)
- if "1045" not in jaString:
- continue
-
- # Need to remove outside code and put it back later
- matchList = re.findall(r"'(.*?)'", jaString)
-
- for match in matchList:
- response = translateAI(match, "")
- translatedText = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Remove characters that may break scripts
- charList = [".", '"', "'", "\\n"]
- for char in charList:
- translatedText = translatedText.replace(char, "")
-
- jaString = jaString.replace(match, translatedText)
-
- # Set Data
- translatedText = jaString
- codeList[i]["p"][j] = translatedText
-
- ### Event Code: 320 Set Variable
- if "c" in codeList[i] and codeList[i]["c"] == 320 and CODE320 is True:
- jaString = codeList[i]["p"][1]
- if not isinstance(jaString, str):
- i += 1
- continue
-
- # Definitely don't want to mess with files
- if "■" in jaString or "_" in jaString:
- i += 1
- continue
-
- # If there isn't any Japanese in the text just skip
- if not re.search(LANGREGEX, jaString):
- i += 1
- continue
-
- # Translate
- response = getSpeaker(jaString)
- translatedText = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Remove characters that may break scripts
- charList = [".", '"', "'", "\\n"]
- for char in charList:
- translatedText = translatedText.replace(char, "")
-
- # Set Data
- codeList[i]["p"][1] = translatedText
-
- ### Event Code: 325
- if "c" in codeList[i] and codeList[i]["c"] == 325 and CODE325 is True:
- # Expect parameters like [index, "text"] where parameters[1] is the string
- if len(codeList[i]["p"]) <= 1:
- i += 1
- continue
-
- jaString = codeList[i]["p"][1]
- if not isinstance(jaString, str):
- i += 1
- continue
-
- # Remove Textwrap
- collectString = jaString.replace("\n", " ")
-
- # Pass 1: collect into batch
- if setData:
- list325.append(collectString)
-
- # Pass 2: apply translations from batch
- else:
- if len(list325) > 0:
- translatedText = list325[0]
- list325.pop(0)
-
- # Textwrap
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
-
- # Set translated value back into parameters[1]
- codeList[i]["p"][1] = "\\}\\}" + translatedText
-
- ### Event Code: 324
- if "c" in codeList[i] and codeList[i]["c"] == 324 and CODE324 is True:
- # Expect parameters like [1, "text"] where index 1 is the string to translate
- if len(codeList[i]["p"]) <= 1:
- i += 1
- continue
-
- jaString = codeList[i]["p"][1]
- if not isinstance(jaString, str):
- i += 1
- continue
-
- # Remove any textwrap for collection
- collectString = jaString.replace("\n", " ")
-
- # Pass 1: collect
- if setData:
- list324.append(collectString)
-
- # Pass 2: apply translations from list324
- else:
- if len(list324) > 0:
- translatedText = list324[0]
- list324.pop(0)
-
- # Clean translation
- for ch in ['"', "\\n"]:
- translatedText = translatedText.replace(ch, "")
-
- # Textwrap to reasonable width
- translatedText = dazedwrap.wrapText(translatedText, width=LISTWIDTH)
-
- # Set translated value back into parameters[1]
- codeList[i]["p"][1] = translatedText
-
- # Iterate
- i += 1
-
- # EOF
- list401TL = []
- list408TL = []
- list324TL = []
- list122TL = []
- list356TL = []
- list357TL = []
- list355655TL = []
- list108TL = []
- list325TL = []
- PBAR = pbar
-
- # 401
- if len(list401) > 0:
- response = translateAI(list401, "")
- list401TL = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- if len(list401TL) != len(list401):
- with LOCK:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
-
- # 122
- if len(list122) > 0:
- response = translateAI(list122, "Keep your translation as brief as possible")
- list122TL = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- if len(list122TL) != len(list122):
- with LOCK:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
-
- # 355/655
- if len(list355655) > 0:
- response = translateAI(list355655, textHistory)
- list355655TL = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- if len(list355655TL) != len(list355655):
- with LOCK:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
-
- # 108
- if len(list108) > 0:
- response = translateAI(list108, "This text is a label. Use title capitalization and keep it brief.")
- list108TL = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- if len(list108TL) != len(list108):
- with LOCK:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
-
- # 356
- if len(list356) > 0:
- response = translateAI(list356, textHistory)
- list356TL = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- if len(list356TL) != len(list356):
- with LOCK:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
-
- # 357
- if len(list357) > 0:
- response = translateAI(list357, textHistory)
- list357TL = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- if len(list357TL) != len(list357):
- with LOCK:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
-
- # 408
- if len(list408) > 0:
- response = translateAI(list408, "")
- list408TL = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- if len(list408TL) != len(list408):
- with LOCK:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
-
- # 324
- if len(list324) > 0:
- # Generic short-text translation for parameter index 1
- response = translateAI(list324, "Reply with only the " + LANGUAGE + " translation of the text.")
- list324TL = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- if len(list324TL) != len(list324):
- with LOCK:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
-
- # 325
- if len(list325) > 0:
- # Use same short-text speaker-style translation as other name fields
- response = translateAI(list325, "Reply with the " + LANGUAGE + " translation of the NPC name.")
- list325TL = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- if len(list325TL) != len(list325):
- with LOCK:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
-
- # Start Pass 2
- if setData:
- clearSpeakerCache() # Clear cache to ensure fresh speaker translations in Pass 2
- searchCodes(
- page,
- pbar,
- [
- list401TL,
- list122TL,
- list355655TL,
- list108TL,
- list356TL,
- list357TL,
- list324TL,
- list408TL,
- list325TL,
- ],
- filename,
- )
-
- # Delete all -1 codes
- codeListFinal = []
- for i in range(len(codeList)):
- if "c" in codeList[i] and codeList[i]["c"] != -1:
- codeListFinal.append(codeList[i])
-
- # Normal Format
- if "list" in page:
- page["list"] = codeListFinal
-
- # Special Format (Scenario)
- else:
- page[:] = codeListFinal
- except IndexError as e:
- traceback.print_exc()
- except Exception as e:
- traceback.print_exc()
-
- return totalTokens
-
-
-def searchSS(state, pbar):
- totalTokens = [0, 0]
-
- # --- Batch collection for basic fields and messages ---
- batch_texts = []
- batch_map = [] # [(field_type, field_name, needs_taro_prefix), ...]
-
- # Name
- if "name" in state and state["name"]:
- batch_texts.append(state["name"])
- batch_map.append(("name", "name", False))
-
- # Description
- if "description" in state and state["description"]:
- batch_texts.append(state["description"])
- batch_map.append(("description", "description", False))
-
- # Messages - collect all with Taro prefix handling
- for msg_field in ["message1", "message2", "message3", "message4"]:
- if msg_field in state and state[msg_field]:
- msg_text = state[msg_field]
- needs_taro = len(msg_text) > 0 and msg_text[0] in ["は", "を", "の", "に", "が"]
- if needs_taro:
- batch_texts.append("Taro" + msg_text)
- else:
- batch_texts.append(msg_text)
- batch_map.append(("message", msg_field, needs_taro))
-
- # --- Batch translate all basic fields ---
- nameResponse = ""
- descriptionResponse = ""
- message1Response = ""
- message2Response = ""
- message3Response = ""
- message4Response = ""
-
- if batch_texts:
- response = translateAI(
- batch_texts,
- "reply with only the gender neutral " + LANGUAGE + " translation. For messages starting with Taro, always start the sentence with Taro. For example, translate 'Taroを倒した!' as 'Taro was defeated!'",
- False,
- )
- translated_batch = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
-
- # Map translations back to their fields
- for idx, (field_type, field_name, needs_taro) in enumerate(batch_map):
- if idx < len(translated_batch):
- translation = translated_batch[idx]
- if field_type == "name":
- nameResponse = [translation, [0, 0]]
- elif field_type == "description":
- descriptionResponse = [translation, [0, 0]]
- elif field_type == "message":
- response_obj = [translation, [0, 0]]
- if field_name == "message1":
- message1Response = response_obj
- elif field_name == "message2":
- message2Response = response_obj
- elif field_name == "message3":
- message3Response = response_obj
- elif field_name == "message4":
- message4Response = response_obj
-
- # --- Batching pass: collect all note texts for all note types ---
- note_regexes = [
- (r"]*)>", False),
- (r"\n(.*)\n", False),
- (r"", False),
- (r"", False),
- (r"(.+)", True),
- ]
- notesBatch = []
- notesBatchMap = []
- if "note" in state and state["note"]:
- note = state["note"]
- for regex, wordwrap in note_regexes:
- matches = re.findall(regex, note, re.DOTALL)
- for m in matches:
- match_text = m if isinstance(m, str) else m[0]
- notesBatch.append(match_text)
- notesBatchMap.append((regex, match_text, wordwrap))
-
- # --- Batch translate all notes ---
- translatedNotesBatch = []
- if notesBatch:
- response = translateAI(notesBatch, f"Reply with only the {LANGUAGE} translation of the note text.")
- translatedNotesBatch = response[0]
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- # Notes don't update progress
-
- # --- Insert translated notes back ---
- note_insert_idx = 0
- if "note" in state and state["note"]:
- for regex, match_text, wordwrap in notesBatchMap:
- if note_insert_idx >= len(translatedNotesBatch):
- break
- translated = translatedNotesBatch[note_insert_idx]
- if wordwrap:
- translated = dazedwrap.wrapText(translated, width=NOTEWIDTH)
- translated = translated.replace('"', "")
- # Replace only the matched text in the note using a literal replacement
- # Avoid re.sub here because replacement strings with backslashes (e.g., \I)
- # are interpreted as escapes and can raise re.PatternError.
- state["note"] = state["note"].replace(match_text, translated, 1)
- note_insert_idx += 1
-
- # Progress accounting for this state: name + description + messages present
- if pbar is not None:
- work_units = 0
- work_units += 1 if nameResponse != "" else 0
- work_units += 1 if descriptionResponse != "" else 0
- work_units += 1 if message1Response != "" else 0
- work_units += 1 if message2Response != "" else 0
- work_units += 1 if message3Response != "" else 0
- work_units += 1 if message4Response != "" else 0
- if work_units:
- pbar.refresh()
-
- # Set Data
- if "name" in state and nameResponse != "":
- state["name"] = nameResponse[0].replace('"', "")
- if "description" in state and descriptionResponse != "":
- # Textwrap
- translatedText = descriptionResponse[0]
- translatedText = dazedwrap.wrapText(translatedText, width=LISTWIDTH)
- state["description"] = translatedText.replace('"', "")
- if "message1" in state and message1Response != "":
- state["message1"] = message1Response[0].replace('"', "").replace("Taro", "")
- if "message2" in state and message2Response != "":
- state["message2"] = message2Response[0].replace('"', "").replace("Taro", "")
- if "message3" in state and message3Response != "":
- state["message3"] = message3Response[0].replace('"', "").replace("Taro", "")
- if "message4" in state and message4Response != "":
- state["message4"] = message4Response[0].replace('"', "").replace("Taro", "")
-
- return totalTokens
-
-
-def searchSystem(data, pbar):
- totalTokens = [0, 0]
- context = "Reply with only the " + LANGUAGE + ' translation of the UI textbox."'
-
- # Title - batch as a single-item list
- response = translateAI(
- [data["game_title"]],
- " Reply with the " + LANGUAGE + " translation of the game title name",
- False,
- )
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- data["game_title"] = response[0][0].strip(".")
- if pbar is not None:
- pbar.refresh()
-
- # Terms - batch translate all term items
- for term in data["terms"]:
- if term != "messages":
- termList = data["terms"][term]
- term_values = []
- term_indices = []
- for i in range(len(termList)):
- if termList[i] is not None:
- term_values.append(termList[i])
- term_indices.append(i)
-
- if term_values:
- response = translateAI(term_values, context)
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- tl_list = response[0]
-
- for n, idx in enumerate(term_indices[: len(tl_list)]):
- termList[idx] = tl_list[n].replace('"', "").strip()
-
- if pbar is not None:
- pbar.refresh()
-
- # Armor Types - batch translate all (check if exists)
- if "armor_types" in data and data["armor_types"]:
- armor_values = [data["armor_types"][i] for i in range(len(data["armor_types"]))]
- if armor_values:
- response = translateAI(
- armor_values,
- "Reply with only the " + LANGUAGE + " translation of the armor type",
- False,
- )
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- tl_list = response[0]
- for i in range(min(len(tl_list), len(data["armor_types"]))):
- data["armor_types"][i] = tl_list[i].replace('"', "").strip()
- if pbar is not None:
- pbar.refresh()
-
- # Skill Types - batch translate all (check if exists)
- if "skill_types" in data and data["skill_types"]:
- skill_values = [data["skill_types"][i] for i in range(len(data["skill_types"]))]
- if skill_values:
- response = translateAI(
- skill_values,
- "Reply with only the " + LANGUAGE + " translation",
- False,
- )
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- tl_list = response[0]
- for i in range(min(len(tl_list), len(data["skill_types"]))):
- data["skill_types"][i] = tl_list[i].replace('"', "").strip()
- if pbar is not None:
- pbar.refresh()
-
- # Equip Types - batch translate all (check if exists)
- if "equip_types" in data and data["equip_types"]:
- equip_values = [data["equip_types"][i] for i in range(len(data["equip_types"]))]
- if equip_values:
- response = translateAI(
- equip_values,
- "Reply with only the " + LANGUAGE + " translation of the equipment type. No disclaimers.",
- False,
- )
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- tl_list = response[0]
- for i in range(min(len(tl_list), len(data["equip_types"]))):
- data["equip_types"][i] = tl_list[i].replace('"', "").strip()
- if pbar is not None:
- pbar.refresh()
-
- # Elements - batch translate all (skip empty)
- element_values = []
- element_indices = []
- for i in range(len(data["elements"])):
- if data["elements"][i]: # Skip empty strings
- element_values.append(data["elements"][i])
- element_indices.append(i)
-
- if element_values:
- response = translateAI(
- element_values,
- "Reply with only the " + LANGUAGE + " translation of the element type",
- False,
- )
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- tl_list = response[0]
- for n, idx in enumerate(element_indices[: len(tl_list)]):
- data["elements"][idx] = tl_list[n].replace('"', "").strip()
- if pbar is not None:
- pbar.refresh()
-
- # Weapon Types - batch translate all (skip empty)
- weapon_key = "weaponTypes" if "weaponTypes" in data else "weapon_types"
- if weapon_key in data and data[weapon_key]:
- weapon_values = []
- weapon_indices = []
- for i in range(len(data[weapon_key])):
- if data[weapon_key][i]: # Skip empty strings
- weapon_values.append(data[weapon_key][i])
- weapon_indices.append(i)
-
- if weapon_values:
- response = translateAI(
- weapon_values,
- "Reply with only the " + LANGUAGE + " translation of the weapon type",
- False,
- )
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- tl_list = response[0]
- for n, idx in enumerate(weapon_indices[: len(tl_list)]):
- data[weapon_key][idx] = tl_list[n].replace('"', "").strip()
- if pbar is not None:
- pbar.refresh()
-
- # Variables (Optional usually) — batch translate to reduce calls
- if TLSYSTEMVARIABLES and "variables" in data and isinstance(data["variables"], list):
- var_indices = []
- var_values = []
- for idx, val in enumerate(data["variables"]):
- if isinstance(val, str) and val.strip():
- var_indices.append(idx)
- var_values.append(val)
- if var_values:
- response = translateAI(
- var_values,
- 'Reply with only the ' + LANGUAGE + ' translation of the title',
- 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(var_indices[: len(tl_list)]):
- data["variables"][idx] = tl_list[n].replace('"', '').strip()
- 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"]
- if messages:
- msg_keys = []
- msg_values = []
- for key, value in messages.items():
- if isinstance(value, str) and value.strip():
- msg_keys.append(key)
- msg_values.append(value)
-
- if msg_values:
- response = translateAI(
- msg_values,
- "Reply with only the "
- + LANGUAGE
- + ' translation of the battle text.\nTranslate "常時ダッシュ" as "Always Dash"\nTranslate "次の%1まで" as Next %1.',
- False,
- )
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- tl_list = response[0]
-
- # Remove characters that may break scripts
- charList = [".", '"', "\\n"]
-
- # Assign back translations to corresponding keys
- for n, key in enumerate(msg_keys[: len(tl_list)]):
- translatedText = tl_list[n]
- for char in charList:
- translatedText = translatedText.replace(char, "")
- messages[key] = translatedText
-
- if pbar is not None:
- pbar.refresh()
-
- return totalTokens
-
-# Save some money and enter the character before translation
-def getSpeaker(speaker: str):
- """Return (and possibly collect) speaker name.
-
- Parse mode (SPEAKER_PARSE_MODE=True):
- - Don't translate immediately. Collect unique originals in SPEAKER_COLLECTED.
- - Return original so caller logic works; token cost is zero.
-
- Normal mode: translate immediately with caching.
- """
- if speaker == "":
- return ["", [0, 0]]
-
- # Preflight count mode: skip translation and caching entirely
- if 'PREFLIGHT_COUNT_MODE' in globals() and PREFLIGHT_COUNT_MODE:
- return [speaker, [0, 0]]
-
- if SPEAKER_PARSE_MODE:
- with _speakerCacheLock:
- if speaker in _speakerCache:
- return [_speakerCache[speaker], [0, 0]]
- if speaker not in SPEAKER_COLLECTED:
- SPEAKER_COLLECTED.append(speaker)
- return [speaker, [0, 0]]
-
- # Normal mode translation path
- with _speakerCacheLock:
- cached = _speakerCache.get(speaker)
- if cached is not None:
- return [cached, [0, 0]]
-
- try:
- THREAD_CTX.in_speaker = True
- except Exception:
- pass
- response = translateAI(
- speaker,
- "Reply with the " + LANGUAGE + " translation of the NPC name.",
- False,
- )
- try:
- THREAD_CTX.in_speaker = False
- except Exception:
- pass
- translated = response[0].title().replace("'S", "'s").replace("Speaker: ", "")
-
- if re.search(r"([a-zA-Z??])", translated) is None:
- try:
- THREAD_CTX.in_speaker = True
- except Exception:
- pass
- response = translateAI(
- speaker,
- "Reply with the " + LANGUAGE + " translation of the NPC name.",
- False,
- )
- try:
- THREAD_CTX.in_speaker = False
- except Exception:
- pass
- translated = response[0].title().replace("'S", "'s")
-
- with _speakerCacheLock:
- if speaker not in _speakerCache:
- _speakerCache[speaker] = translated
- NAMESLIST.append([speaker, translated])
- return [translated, response[1]]
-
-def translateAI(text, history, history_ctx=None):
- """
- Legacy wrapper function for the new shared translation utility.
- This maintains compatibility with existing code while using the new shared implementation.
- """
- global PBAR, MISMATCH, FILENAME
-
- # Update config estimate mode based on global ESTIMATE
- TRANSLATION_CONFIG.estimateMode = bool(ESTIMATE)
-
- # Call the new shared translation function
- # Prefer thread-local filename for logging; fall back to global
- try:
- tl_filename = getattr(THREAD_CTX, "filename", FILENAME)
- except Exception:
- tl_filename = FILENAME
-
- # Speaker-parse mode: bypass all non-speaker translations to save tokens
- if SPEAKER_PARSE_MODE and not getattr(THREAD_CTX, "in_speaker", False):
- # Return original text unmodified with zero tokens
- return [text, [0, 0]]
-
- # Preflight count mode: don't hit API; just simulate progress units
- if 'PREFLIGHT_COUNT_MODE' in globals() and PREFLIGHT_COUNT_MODE:
- try:
- n = len(text) if isinstance(text, list) else 1
- except Exception:
- n = 1
- if PBAR is not None:
- try:
- with LOCK:
- PBAR.update(n)
- except Exception:
- pass
- # Return original payload and zero tokens so totals aren't affected
- return [text, [0, 0]]
-
- return sharedtranslateAI(
- text=text,
- history=history,
- config=TRANSLATION_CONFIG,
- filename=tl_filename,
- pbar=PBAR,
- lock=LOCK,
- mismatchList=MISMATCH
- )
-
-def resetSpeakerState():
- """Clear all speaker-related globals so a fresh run doesn't carry over stale data."""
- global NAMESLIST, SPEAKER_COLLECTED
- NAMESLIST = []
- SPEAKER_COLLECTED = []
- with _speakerCacheLock:
- _speakerCache.clear()
-
-def setSpeakerParseMode(flag: bool):
- """Enable/disable speaker-only parse mode."""
- global SPEAKER_PARSE_MODE
- SPEAKER_PARSE_MODE = bool(flag)
-
-def finalizeSpeakerParse():
- """Batch translate collected speakers and write fresh # Speakers section."""
- if not SPEAKER_PARSE_MODE:
- return
- try:
- # Step 1: batch translate any collected speakers not already translated
- to_translate = []
- with _speakerCacheLock:
- for s in SPEAKER_COLLECTED:
- if s not in _speakerCache and s != "":
- to_translate.append(s)
- if to_translate:
- try:
- THREAD_CTX.in_speaker = True
- except Exception:
- pass
- resp = translateAI(
- to_translate,
- "Reply with the " + LANGUAGE + " translation of the NPC name.",
- True,
- )
- try:
- THREAD_CTX.in_speaker = False
- except Exception:
- pass
- # Record token usage so it appears in the TOTAL string
- try:
- with LOCK:
- TOKENS[0] += resp[1][0]
- TOKENS[1] += resp[1][1]
- except Exception:
- pass
- # Emit a one-time summary line for speaker translation using the same format
- try:
- cost = calculateCost(resp[1][0], resp[1][1], MODEL)
- totalTokenstring = (
- Fore.YELLOW + "[Input: " + str(resp[1][0]) + "]"
- "[Output: "
- + str(resp[1][1])
- + "]" "[Cost: ${:,.4f}".format(cost)
- + "]"
- )
- tqdm.write("Speakers: " + totalTokenstring + Fore.GREEN + " \u2713 " + Fore.RESET)
- except Exception:
- pass
- tl_list = resp[0]
- with _speakerCacheLock:
- for orig, tl in zip(to_translate, tl_list):
- norm = tl.title().replace("'S", "'s").replace("Speaker: ", "")
- if re.search(r"([a-zA-Z??])", norm) is None:
- norm = tl # keep raw if heuristic fails
- if orig not in _speakerCache:
- _speakerCache[orig] = norm
- NAMESLIST.append([orig, norm])
-
- vocab_path = Path("vocab.txt")
- if not vocab_path.exists():
- return
- content = vocab_path.read_text(encoding="utf-8")
-
- seen = set()
- lines = []
- for orig, tl in NAMESLIST:
- if not orig or not tl:
- continue
- if orig in seen:
- continue
- seen.add(orig)
- lines.append(f"{orig} ({tl})")
- if not lines:
- return
- section_block = "# Speakers\n" + "\n".join(lines) + "\n\n"
-
- speakers_pattern = re.compile(r"^[\t ]*#+\s*Speakers\s*$\r?\n.*?(?=^[\t ]*#|\Z)", re.MULTILINE | re.DOTALL)
- content = speakers_pattern.sub("", content)
-
- game_char_header = re.compile(r"^[\t ]*#\s*Game Characters\s*$", re.MULTILINE)
- match_gc = game_char_header.search(content)
- if match_gc:
- subsequent_headers = list(re.finditer(r"^[\t ]*#\s+.*$", content[match_gc.end():], re.MULTILINE))
- if subsequent_headers:
- insert_index = match_gc.end() + subsequent_headers[0].start()
- else:
- insert_index = len(content)
- else:
- insert_index = 0
-
- before = content[:insert_index]
- after = content[insert_index:]
- if not before.endswith("\n\n"):
- if not before.endswith("\n"):
- before += "\n"
- before += "\n"
- new_content = before + section_block + after.lstrip("\n")
-
- tmp_path = vocab_path.with_suffix(vocab_path.suffix + f".{os.getpid()}.{threading.get_ident()}.tmp")
- tmp_path.write_text(new_content, encoding="utf-8")
- try:
- os.replace(tmp_path, vocab_path)
- except Exception:
- try:
- shutil.move(str(tmp_path), str(vocab_path))
- except Exception:
- pass
- except Exception:
- traceback.print_exc()
diff --git a/requirements.txt b/requirements.txt
index 84cd0a6..ad0e17b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,6 @@ colorama>=0.4.6
openai>=2.8.0
python-dotenv>=1.0.0
retry>=0.9.2
-ruamel.yaml>=0.17.32
tiktoken>=0.8.0
tqdm>=4.65.0
jsbeautifier>=1.14.0
diff --git a/util/subprocess_runner.py b/util/subprocess_runner.py
index cb2d575..7a33840 100644
--- a/util/subprocess_runner.py
+++ b/util/subprocess_runner.py
@@ -74,9 +74,6 @@ def run_handler(project_root, module_name, filename, estimate_only):
if "RPG Maker MV/MZ" in module_name:
from modules.rpgmakermvmz import handleMVMZ
handler = handleMVMZ
- elif "RPG Maker Ace" in module_name:
- from modules.rpgmakerace import handleACE
- handler = handleACE
elif "CSV" in module_name:
from modules.csv import handleCSV
handler = handleCSV