From f10b52ec887318d527af6bbb1fcd0113efc55a47 Mon Sep 17 00:00:00 2001 From: Phoebe <85695-Phoebe@users.noreply.gitgud.io> Date: Mon, 17 Nov 2025 16:38:21 +0000 Subject: [PATCH] Add Aquedi4 Support --- gui/translation_tab.py | 2 + modules/aquedi4.py | 216 +++++++++++++++++++++++++++++++++++++++++ modules/main.py | 2 + 3 files changed, 220 insertions(+) create mode 100644 modules/aquedi4.py diff --git a/gui/translation_tab.py b/gui/translation_tab.py index a047cd1..2e7ecd6 100644 --- a/gui/translation_tab.py +++ b/gui/translation_tab.py @@ -1064,6 +1064,7 @@ class TranslationTab(QWidget): from modules.unity import handleUnity from modules.images import handleImages from modules.rpgmakerplugin import handlePlugin + from modules.aquedi4 import handleAquedi4 self.modules = [ ["RPG Maker MV/MZ", [".json"], handleMVMZ], @@ -1082,6 +1083,7 @@ class TranslationTab(QWidget): ["Unity", [".unity"], handleUnity], ["Images", [".png", ".jpg", ".jpeg"], handleImages], ["RPG Maker Plugin", [".js"], handlePlugin], + ["Aquedi4 Prepared JSON", [".json"], handleAquedi4], ] for module in self.modules: diff --git a/modules/aquedi4.py b/modules/aquedi4.py new file mode 100644 index 0000000..51a7399 --- /dev/null +++ b/modules/aquedi4.py @@ -0,0 +1,216 @@ +# modules/aquedi4.py + +# Libraries +import json +import os +import re +import util.dazedwrap as dazedwrap +import threading +import time +import traceback +import tiktoken +from pathlib import Path +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 +# from modules.json import translateSimpleKeyValueJSON +import tempfile + +# Boilerplate 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() +ESTIMATE = "" +TOKENS = [0, 0] +MISMATCH = [] +FILENAME = None +PBAR = None +BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}" + +# Flag to control line break replacement +REPLACE_LINEBREAKS_WITH_PIPES = True + +LANGREGEX = r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9\uFF61-\uFF9F]+" +TRANSLATION_CONFIG = TranslationConfig( + model=MODEL, + language=LANGUAGE, + prompt=PROMPT, + vocab=VOCAB, + langRegex=LANGREGEX, + batchSize=int(os.getenv("batchsize", 10)), + maxHistory=10, + estimateMode=False +) +# End Boilerplate + +def handleAquedi4(filename, estimate): + """Main handler for Aquedi4 JSON files.""" + global ESTIMATE, TOKENS, FILENAME + ESTIMATE = estimate + FILENAME = filename + + try: + start = time.time() + translatedData = openFiles(filename) + + if not estimate: + # Write final result after translation is complete + with open("translated/" + filename, "w", encoding="utf-8", newline="\n") as outFile: + json.dump(translatedData[0], outFile, ensure_ascii=False, indent=4) + + end = time.time() + tqdm.write(getResultString(translatedData, end - start, filename)) + with LOCK: + TOKENS[0] += translatedData[1][0] + TOKENS[1] += translatedData[1][1] + + except Exception as e: + traceback.print_exc() + return "Fail" + + return getResultString(["", TOKENS, None], end - start, "TOTAL") + + +def openFiles(filename): + """Opens and parses the JSON file.""" + with open("files/" + filename, "r", encoding="UTF-8-sig") as f: + data = json.load(f) + translatedData = parseAquedi4JSON(data, filename) + return translatedData + + +def parseAquedi4JSON(data, filename): + """Parses and translates the simple key-value JSON.""" + totalTokens = [0, 0] + totalLines = 0 + totalLines = len(data) + global LOCK, PBAR + + stringList = [] + keyList = [] + if isinstance(data, dict) and all(isinstance(v, str) for v in data.values()): + for key, value in data.items(): + if re.search(LANGREGEX, key): + stringList.append(key) + keyList.append(key) + + with tqdm(total=len(stringList), bar_format=BAR_FORMAT, position=0, leave=False) as pbar: + pbar.desc = filename + PBAR = pbar + + # from modules.json import translateSimpleKeyValueJSON + + try: + if stringList: + result = translateSimpleKeyValueJSON(data, filename, stringList, keyList) + totalTokens[0] += result[0] + totalTokens[1] += result[1] + else: + pbar.write(f"Warning: {filename} is not in the expected simple key-value format. Skipping.") + except Exception as e: + traceback.print_exc() + return [data, totalTokens, e] + + return [data, totalTokens, None] + + +def translateSimpleKeyValueJSON(data, filename, stringList, keyList): + """ + Translate simple key-value JSON format where keys contain Japanese text + and values are placeholder strings like "TODO". + + Format example: + { + "\\r\\nスタビライザー": "TODO", + "\\r\\nプラグインなし": "TODO" + } + """ + global LOCK, ESTIMATE, FILENAME, PBAR, MISMATCH + tokens = [0, 0] + + preparedStringList = [] + if REPLACE_LINEBREAKS_WITH_PIPES: + for s in stringList: + preparedStringList.append(s.replace("\r\n", "|").replace("\n", "|")) + else: + preparedStringList = stringList + + # Translate all keys if any were found + if preparedStringList: + PBAR.total = len(preparedStringList) + PBAR.refresh() + + response = translateAI(preparedStringList, "Reply with the English Translation", True) + tokens[0] += response[1][0] + tokens[1] += response[1][1] + translatedList = response[0] + + # Check for mismatch + if len(preparedStringList) != len(translatedList): + with LOCK: + if FILENAME not in MISMATCH: + MISMATCH.append(FILENAME) + + # Pass 2: Update the values with translations + for i, original_key in enumerate(keyList): + if i < len(translatedList): + translatedText = translatedList[i] + + if REPLACE_LINEBREAKS_WITH_PIPES: + translatedText = translatedText.replace("|", "\r\n") + # Set the translated text as the value + data[original_key] = translatedText + + # Save progress after each translation + save_progress_json(data, filename) + + return tokens + +def save_progress_json(data, filename): + """Save current JSON translation progress.""" + try: + if ESTIMATE: return + os.makedirs("translated", exist_ok=True) + tmp_path = os.path.join("translated", f"{filename}.tmp") + final_path = os.path.join("translated", filename) + with open(tmp_path, "w", encoding="utf-8", newline="\n") as outFile: + json.dump(data, outFile, ensure_ascii=False, indent=4) + os.replace(tmp_path, final_path) + except Exception: + traceback.print_exc() + + +def getResultString(translatedData, translationTime, filename): + cost = calculateCost(translatedData[1][0], translatedData[1][1], MODEL) + totalTokenstring = ( + Fore.YELLOW + f"[Input: {translatedData[1][0]}]" + f"[Output: {translatedData[1][1]}]" + f"[Cost: ${cost:,.4f}]" + ) + timeString = Fore.BLUE + f"[{round(translationTime, 1)}s]" + + if translatedData[2] is None: + return f"{filename}: {totalTokenstring}{timeString}" + Fore.GREEN + " \u2713 " + Fore.RESET + else: + errorString = str(translatedData[2]) + Fore.RED + return f"{filename}: {totalTokenstring}{timeString}" + Fore.RED + " \u2717 " + errorString + Fore.RESET + +def translateAI(text, history, fullPromptFlag): + """Legacy wrapper for the shared translation utility.""" + global PBAR, MISMATCH, FILENAME + TRANSLATION_CONFIG.estimateMode = bool(ESTIMATE) + return sharedtranslateAI( + text=text, + history=history, + fullPromptFlag=fullPromptFlag, + config=TRANSLATION_CONFIG, + filename=FILENAME, + pbar=PBAR, + lock=LOCK, + mismatchList=MISMATCH + ) \ No newline at end of file diff --git a/modules/main.py b/modules/main.py index 3575c54..0c979e3 100644 --- a/modules/main.py +++ b/modules/main.py @@ -51,6 +51,7 @@ from modules.unity import handleUnity from modules.images import handleImages from modules.rpgmakerplugin import handlePlugin from modules.srpg import handleSRPG +from modules.aquedi4 import handleAquedi4 # For GPT4 rate limit will be hit if you have more than 1 thread. # 1 Thread for each file. Controls how many files are worked on at once. @@ -75,6 +76,7 @@ MODULES = [ ["Unity", ["txt"], handleUnity], ["SRPG Studio", ["json"], handleSRPG], ["Images", [""], handleImages], + ["Aquedi4 Prepared JSON", [".json"], handleAquedi4], ] # Info Message