Add Yuris Module

This commit is contained in:
DazedAnon 2026-05-14 15:24:04 -05:00
parent 570c704427
commit 97327d806b
4 changed files with 286 additions and 0 deletions

View file

@ -1106,6 +1106,7 @@ class TranslationTab(QWidget):
from modules.kirikiri import handleKirikiri from modules.kirikiri import handleKirikiri
from modules.json import handleJSON from modules.json import handleJSON
from modules.lune import handleLune from modules.lune import handleLune
from modules.yuris import handleYuris
from modules.nscript import handleOnscripter from modules.nscript import handleOnscripter
from modules.wolf import handleWOLF from modules.wolf import handleWOLF
from modules.wolf2 import handleWOLF2 from modules.wolf2 import handleWOLF2
@ -1125,6 +1126,7 @@ class TranslationTab(QWidget):
["Kirikiri", [".ks"], handleKirikiri], ["Kirikiri", [".ks"], handleKirikiri],
["JSON", [".json"], handleJSON], ["JSON", [".json"], handleJSON],
["Lune", [".l"], handleLune], ["Lune", [".l"], handleLune],
["Yuris", [".json"], handleYuris],
["NScript", [".nscript"], handleOnscripter], ["NScript", [".nscript"], handleOnscripter],
["Wolf RPG", [".json"], handleWOLF], ["Wolf RPG", [".json"], handleWOLF],
["Wolf RPG 2", [".txt"], handleWOLF2], ["Wolf RPG 2", [".txt"], handleWOLF2],

View file

@ -39,6 +39,7 @@ from modules.tyrano import handleTyrano
from modules.kirikiri import handleKirikiri from modules.kirikiri import handleKirikiri
from modules.json import handleJSON from modules.json import handleJSON
from modules.lune import handleLune from modules.lune import handleLune
from modules.yuris import handleYuris
from modules.nscript import handleOnscripter from modules.nscript import handleOnscripter
from modules.wolf import handleWOLF from modules.wolf import handleWOLF
from modules.wolf2 import handleWOLF2 from modules.wolf2 import handleWOLF2
@ -64,6 +65,7 @@ MODULES = [
["Kirikiri", ["ks", "tjs", "ssd", "asd"], handleKirikiri], ["Kirikiri", ["ks", "tjs", "ssd", "asd"], handleKirikiri],
["JSON", ["json"], handleJSON], ["JSON", ["json"], handleJSON],
["Lune", ["json"], handleLune], ["Lune", ["json"], handleLune],
["Yuris", ["json"], handleYuris],
["NScript", ["txt"], handleOnscripter], ["NScript", ["txt"], handleOnscripter],
["Wolf", ["json"], handleWOLF], ["Wolf", ["json"], handleWOLF],
["Wolf", ["txt"], handleWOLF2], ["Wolf", ["txt"], handleWOLF2],

279
modules/yuris.py Normal file
View file

@ -0,0 +1,279 @@
import json
import os
import re
import tempfile
import threading
import time
import traceback
from pathlib import Path
from colorama import Fore
from tqdm import tqdm
import util.dazedwrap as dazedwrap
from util.translation import (
TranslationConfig,
calculateCost,
getPricingConfig,
translateAI as sharedtranslateAI,
)
MODEL = os.getenv("model")
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()
WIDTH = int(os.getenv("width"))
MAXHISTORY = 10
ESTIMATE = ""
TOKENS = [0, 0]
NAMESLIST = []
MISMATCH = []
FILENAME = None
PBAR = None
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
POSITION = 0
LEAVE = False
LANGREGEX = r"[一-龠ぁ-ゔァ-ヴーa---\uFF61-\uFF9F]+"
PRICING_CONFIG = getPricingConfig(MODEL)
BATCHSIZE = PRICING_CONFIG["batchSize"]
TRANSLATION_CONFIG = TranslationConfig(
model=MODEL,
language=LANGUAGE,
prompt=PROMPT,
vocab=VOCAB,
langRegex=LANGREGEX,
batchSize=BATCHSIZE,
maxHistory=MAXHISTORY,
estimateMode=False,
)
def handleYuris(filename, estimate):
global ESTIMATE, FILENAME, TOKENS
ESTIMATE = estimate
FILENAME = filename
try:
start = time.time()
translatedData = openFiles(filename)
if not estimate:
os.makedirs(os.path.dirname(os.path.join("translated", filename)) or "translated", exist_ok=True)
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:
traceback.print_exc()
return "Fail"
totalString = getResultString(["", TOKENS, None], end - start, "TOTAL")
if MISMATCH:
return totalString + Fore.RED + f"\nMismatch Errors: {MISMATCH}" + Fore.RESET
return totalString
def openFiles(filename):
with open("files/" + filename, "r", encoding="UTF-8-sig") as readFile:
data = json.load(readFile)
translatedData = parseYuris(data, filename)
return translatedData
def parseYuris(data, filename):
totalTokens = [0, 0]
global PBAR
if not isinstance(data, list):
return [data, totalTokens, TypeError(f"{filename} must be a JSON array")]
totalLines = sum(
1
for item in data
if isinstance(item, dict) and isinstance(item.get("message"), str) and item["message"].strip()
)
with tqdm(bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE) as pbar:
pbar.desc = filename
PBAR = pbar
try:
result = translateYuris(data, filename)
totalTokens[0] += result[0]
totalTokens[1] += result[1]
except Exception as e:
traceback.print_exc()
return [data, totalTokens, e]
return [data, totalTokens, None]
def translateYuris(data, filename):
global PBAR
tokens = [0, 0]
entries = []
stringList = []
for index, item in enumerate(data):
if not isinstance(item, dict):
continue
speaker = ""
if isinstance(item.get("name"), str) and item["name"].strip():
speakerData = getSpeaker(item["name"].strip())
speaker = speakerData[0]
tokens[0] += speakerData[1][0]
tokens[1] += speakerData[1][1]
item["name"] = speaker
message = item.get("message")
if not isinstance(message, str) or not message.strip():
continue
# if not re.search(LANGREGEX, message):
# continue
cleanMessage = message.replace("\r\n", " ").replace("\n", " ").strip()
hasSpeaker = bool(speaker)
stringList.append(f"[{speaker}]: {cleanMessage}" if hasSpeaker else cleanMessage)
entries.append((index, message, hasSpeaker))
if not stringList:
return tokens
PBAR.total = len(stringList)
PBAR.refresh()
response = translateAI(stringList, "Reply with the English Translation")
tokens[0] += response[1][0]
tokens[1] += response[1][1]
translatedList = response[0]
if len(stringList) != len(translatedList):
with LOCK:
if FILENAME not in MISMATCH:
MISMATCH.append(FILENAME)
return tokens
for (index, originalMessage, hasSpeaker), translatedText in zip(entries, translatedList):
if hasSpeaker:
translatedText = stripSpeakerPrefix(translatedText)
translatedText = translatedText.replace("\r\n", "\n")
translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
translatedText = translatedText.replace("\n", "\r\n")
data[index]["message"] = originalMessage.replace(originalMessage, translatedText)
save_progress_json(data, filename)
if PBAR is not None:
PBAR.update(1)
return tokens
def stripSpeakerPrefix(translated_text):
"""Same behavior as modules/json.translateJSON: strip [Name]: prefix from TL lines."""
match = re.search(r"(^\[.+?\]\s?[|:]\s?)", translated_text)
if match:
translated_text = translated_text.replace(match.group(1), "")
else:
cjk_m = re.match(
r"^[一-龠ぁ-ゔァ-ヴーa---\uFF61-\uFF9F]+\s*[\(:]\s*",
translated_text,
)
if cjk_m:
translated_text = translated_text[cjk_m.end():]
translated_text = re.sub(r"\s*[)]\s*$", "", translated_text)
return translated_text.strip()
def save_progress_json(data, filename):
try:
if ESTIMATE:
return
target = os.path.join("translated", filename)
os.makedirs(os.path.dirname(target) or "translated", exist_ok=True)
tmp_fd, tmp_path = tempfile.mkstemp(
prefix=f"{os.path.basename(filename)}.",
suffix=".tmp",
dir=os.path.dirname(target) or "translated",
)
try:
with os.fdopen(tmp_fd, "w", encoding="utf-8", newline="\n") as tmp_file:
json.dump(data, tmp_file, ensure_ascii=False, indent=4)
os.replace(tmp_path, target)
finally:
if os.path.exists(tmp_path):
try:
os.remove(tmp_path)
except OSError:
pass
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
try:
raise translatedData[2]
except Exception as e:
traceback.print_exc()
errorString = str(e) + Fore.RED
return f"{filename}: {totalTokenstring}{timeString}" + Fore.RED + " \u2717 " + errorString + Fore.RESET
def getSpeaker(speaker):
if not speaker:
return ["", [0, 0]]
for original, translated in NAMESLIST:
if speaker == original:
return [translated, [0, 0]]
response = translateAI(
speaker,
"Reply with the " + LANGUAGE + " translation of the NPC name.",
False,
)
response[0] = response[0].title().replace("'S", "'s").replace("Speaker: ", "")
if re.search(r"([a-zA-Z?])", response[0]) is None:
response = translateAI(
speaker,
"Reply with the " + LANGUAGE + " translation of the NPC name.",
False,
)
response[0] = response[0].title().replace("'S", "'s").replace("Speaker: ", "")
NAMESLIST.append([speaker, response[0]])
return response
def translateAI(text, history, history_ctx=None):
global PBAR, MISMATCH, FILENAME
TRANSLATION_CONFIG.estimateMode = bool(ESTIMATE)
return sharedtranslateAI(
text=text,
history=history,
config=TRANSLATION_CONFIG,
filename=FILENAME,
pbar=PBAR,
lock=LOCK,
mismatchList=MISMATCH,
)

View file

@ -89,6 +89,9 @@ def run_handler(project_root, module_name, filename, estimate_only):
elif "Lune" in module_name: elif "Lune" in module_name:
from modules.lune import handleLune from modules.lune import handleLune
handler = handleLune handler = handleLune
elif "Yuris" in module_name:
from modules.yuris import handleYuris
handler = handleYuris
elif "NScript" in module_name: elif "NScript" in module_name:
from modules.nscript import handleOnscripter from modules.nscript import handleOnscripter
handler = handleOnscripter handler = handleOnscripter