diff --git a/modules/alice.py b/modules/alice.py
deleted file mode 100644
index d8edc71..0000000
--- a/modules/alice.py
+++ /dev/null
@@ -1,610 +0,0 @@
-# Libraries
-import os
-import re
-import util.dazedwrap as dazedwrap
-import threading
-import time
-import traceback
-import tiktoken
-import openai
-from pathlib import Path
-from colorama import Fore
-from dotenv import load_dotenv
-from retry import retry
-from tqdm import tqdm
-
-# Open AI
-load_dotenv()
-if os.getenv("api").replace(" ", "") != "":
- openai.base_url = os.getenv("api")
-openai.organization = os.getenv("org")
-openai.api_key = os.getenv("key")
-
-# 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")
-THREADS = int(os.getenv("threads"))
-LOCK = threading.Lock()
-WIDTH = int(os.getenv("width"))
-LISTWIDTH = int(os.getenv("listWidth"))
-NOTEWIDTH = 70
-MAXHISTORY = 10
-ESTIMATE = ""
-TOKENS = [0, 0]
-NAMESLIST = []
-NAMES = False # Output a list of all the character names found
-BRFLAG = False # If the game uses
instead
-FIXTEXTWRAP = True # Overwrites textwrap
-IGNORETLTEXT = False # Ignores all translated text.
-MISMATCH = [] # Lists files that throw a mismatch error (Length of GPT list response is wrong)
-
-# tqdm Globals
-BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
-POSITION = 0
-LEAVE = False
-
-# 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]+"
-
-# Pricing - Depends on the model https://openai.com/pricing
-# Batch Size - GPT 3.5 Struggles past 15 lines per request. GPT4 struggles past 50 lines per request
-# If you are getting a MISMATCH LENGTH error, lower the batch size.
-if "gpt-3.5" in MODEL:
- INPUTAPICOST = 3.00
- OUTPUTAPICOST = 5.00
- BATCHSIZE = 10
- FREQUENCY_PENALTY = 0.2
-elif "gpt-4" in MODEL:
- INPUTAPICOST = 2.0
- OUTPUTAPICOST = 8.00
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-elif "deepseek" in MODEL:
- INPUTAPICOST = 0.27
- OUTPUTAPICOST = 1.10
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-else:
- INPUTAPICOST = float(os.getenv("input_cost"))
- OUTPUTAPICOST = float(os.getenv("output_cost"))
- BATCHSIZE = int(os.getenv("batchsize"))
- FREQUENCY_PENALTY = float(os.getenv("frequency_penalty"))
-
-
-def handleAlice(filename, estimate):
- global ESTIMATE
- totalTokens = [0, 0]
- ESTIMATE = estimate
-
- if estimate:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- end = time.time()
- tqdm.write(getResultString(translatedData, end - start, filename))
- with LOCK:
- totalTokens[0] += translatedData[1][0]
- totalTokens[1] += translatedData[1][1]
-
- # Print Total
- totalString = getResultString(["", totalTokens, 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
-
- else:
- try:
- with open("translated/" + filename, "w", encoding="utf-8", newline="\n") as outFile:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- end = time.time()
- outFile.writelines(translatedData[0])
- tqdm.write(getResultString(translatedData, end - start, filename))
- with LOCK:
- totalTokens[0] += translatedData[1][0]
- totalTokens[1] += translatedData[1][1]
- except Exception:
- traceback.print_exc()
- return "Fail"
-
- return getResultString(["", totalTokens, None], end - start, "TOTAL")
-
-
-def openFiles(filename):
- with open("files/" + filename, "r", encoding="UTF-8") as f:
- translatedData = parseText(f, filename)
-
- return translatedData
-
-
-def getResultString(translatedData, translationTime, filename):
- # File Print String
- totalTokenstring = (
- Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
- "[Output: "
- + str(translatedData[1][1])
- + "]" "[Cost: ${:,.4f}".format(((translatedData[1][0] / 1000000) * INPUTAPICOST) + ((translatedData[1][1] / 1000000) * OUTPUTAPICOST))
- + "]"
- )
- timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
-
- if translatedData[2] == 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 parseText(data, filename):
- # Get total for progress bar
- linesList = data.readlines()
- totalTokens = [0, 0]
- totalLines = len(linesList)
- global LOCK
-
- with tqdm(bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE) as pbar:
- pbar.desc = filename
- pbar.total = totalLines
- try:
- result = translateLines(linesList, pbar)
- totalTokens[0] += result[1][0]
- totalTokens[1] += result[1][1]
- except Exception as e:
- traceback.print_exc()
- return [linesList, totalTokens, e]
- return [linesList, totalTokens, None]
-
-
-# Grab scenario data from text file
-def translateLines(linesList, pbar):
- currentGroup = []
- batch = []
- textHistory = []
- tokens = [0, 0]
- batchStartIndex = 0
- insertBool = False
- multiLine = False
- i = 0
-
- try:
- while i < len(linesList):
- # Check if Proper Message
- match = re.findall(r"s\[[0-9]+\] = \"(.*)\"", linesList[i])
- if len(match) > 0:
- jaString = match[0]
-
- # Skip Files
- if "/" in jaString:
- i += 1
- continue
-
- ### Translate
- # Remove any textwrap
- jaString = re.sub(r"\\n", " ", jaString)
-
- # Grab Speaker
- speakerMatch = re.findall(r"s\[[0-9]+\] = \"([^/]+)\"", linesList[i - 1])
- if len(speakerMatch) > 0:
- # If there isn't any Japanese in the text just skip
- if re.search(r"[一-龠]+|[ぁ-ゔ]+|[ァ-ヴー]+", jaString) and "_" not in speakerMatch[0]:
- speaker = speakerMatch[0]
- else:
- speaker = ""
- else:
- speaker = ""
-
- # Grab rest of the messages
- currentGroup.append(jaString)
-
- # Check if next line should be merged
- if insertBool is True:
- linesList[i] = re.sub(r"(s\[[0-9]+\]) = \"(.+)\"", r'\1 = ""', linesList[i])
- linesList[i] = linesList[i].replace(";", "")
- start = i
- while len(linesList) > i + 1 and re.search(r"s\[[0-9]+\] = \"\s+(.*)\"", linesList[i + 1]) != None:
- multiLine = True
- i += 1
- match = re.findall(r"s\[[0-9]+\] = \"\s+(.*)\"", linesList[i])
- currentGroup.append(match[0])
- if insertBool is True:
- linesList[i] = re.sub(r"(s\[[0-9]+\]) = \"\s+(.+)\"", r'\1 = ""', linesList[i])
- linesList[i] = linesList[i].replace(";", "")
- i += 1
-
- # Combine Groups and Add Speaker
- finalJAString = " ".join(currentGroup)
- if speaker != "":
- finalJAString = f"{speaker}: {finalJAString}"
- else:
- finalJAString = f"{finalJAString}"
-
- # [Passthrough 1] Pulling From File
- if insertBool is False:
- # Append to List and Clear Values
- batch.append(finalJAString)
-
- # Translate Batch if Full
- if len(batch) == BATCHSIZE or i >= len(linesList) - 1:
- # Translate
- response = translateGPT(batch, textHistory, True)
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- translatedBatch = response[0]
- textHistory = translatedBatch[-10:]
-
- # Set Values
- if len(batch) == len(translatedBatch):
- i = batchStartIndex
- insertBool = True
-
- # Mismatch
- else:
- pbar.write(f"Mismatch: {batchStartIndex} - {i}")
- MISMATCH.append(batch)
- batchStartIndex = i
- batch.clear()
-
- multiLine = False
- currentGroup = []
-
- # [Passthrough 2] Setting Data
- else:
- # Get Text
- translatedText = translatedBatch[0]
-
- # Remove added speaker and quotes
- translatedText = re.sub(r"^.+?:\s", "", translatedText)
-
- # Textwrap
- translatedText = translatedText.replace('"', '\\"')
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
-
- # Set Data
- if multiLine:
- textList = translatedText.split("\n")
- for t in textList:
- translatedText = translatedText.replace(";", "")
- translatedText = re.sub(
- r"(s\[[0-9]+\]) = \"(.*)\"",
- rf'\1 = "{t}"',
- linesList[start],
- )
- translatedText = translatedText.replace(";", "")
- linesList[start] = translatedText
- pbar.update(1)
- start += 1
- multiLine = False
- translatedText = translatedText.replace(";", "")
- translatedBatch.pop(0)
- else:
- # Remove any textwrap
- translatedText = translatedText.replace("\n", " ")
- translatedText = re.sub(
- r"(s\[[0-9]+\]) = \"(.*)\"",
- rf'\1 = "{translatedText}"',
- linesList[start],
- )
- translatedText = translatedText.replace(";", "")
- linesList[start] = translatedText
- pbar.update(1)
- translatedBatch.pop(0)
-
- # If Batch is empty. Move on.
- if len(translatedBatch) == 0:
- insertBool = False
- batchStartIndex = i
- pbar.update(1)
- batch.clear()
-
- currentGroup = []
- else:
- if insertBool is True:
- pbar.update(1)
- i += 1
-
- return [linesList, tokens]
- except Exception:
- traceback.print_exc()
- return [linesList, tokens]
-
-
-def subVars(jaString):
- jaString = jaString.replace("\u3000", " ")
-
- # Nested
- count = 0
- nestedList = re.findall(r"[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]", jaString)
- nestedList = set(nestedList)
- if len(nestedList) != 0:
- for icon in nestedList:
- jaString = jaString.replace(icon, "{Nested_" + str(count) + "}")
- count += 1
-
- # Icons
- count = 0
- iconList = re.findall(r"[\\]+[iIkKwWaA]+\[[0-9]+\]", jaString)
- iconList = set(iconList)
- if len(iconList) != 0:
- for icon in iconList:
- jaString = jaString.replace(icon, "{Ascii_" + str(count) + "}")
- count += 1
-
- # Colors
- count = 0
- colorList = re.findall(r"[\\]+[cC]\[[0-9]+\]", jaString)
- colorList = set(colorList)
- if len(colorList) != 0:
- for color in colorList:
- jaString = jaString.replace(color, "{Color_" + str(count) + "}")
- count += 1
-
- # Names
- count = 0
- nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
- nameList = set(nameList)
- if len(nameList) != 0:
- for name in nameList:
- jaString = jaString.replace(name, "{Noun_" + str(count) + "}")
- count += 1
-
- # Variables
- count = 0
- varList = re.findall(r"[\\]+[vV]\[[0-9]+\]", jaString)
- varList = set(varList)
- if len(varList) != 0:
- for var in varList:
- jaString = jaString.replace(var, "{Var_" + str(count) + "}")
- count += 1
-
- # Formatting
- count = 0
- formatList = re.findall(r"[\\]+[\w]+\[.+?\]", jaString)
- formatList = set(formatList)
- if len(formatList) != 0:
- for var in formatList:
- jaString = jaString.replace(var, "{FCode_" + str(count) + "}")
- count += 1
-
- # Put all lists in list and return
- allList = [nestedList, iconList, colorList, nameList, varList, formatList]
- return [jaString, allList]
-
-
-def resubVars(translatedText, allList):
- # Fix Spacing and ChatGPT Nonsense
- matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
- if len(matchList) > 0:
- for match in matchList:
- text = match.strip()
- translatedText = translatedText.replace(match, text)
-
- # Nested
- count = 0
- if len(allList[0]) != 0:
- for var in allList[0]:
- translatedText = translatedText.replace("{Nested_" + str(count) + "}", var)
- count += 1
-
- # Icons
- count = 0
- if len(allList[1]) != 0:
- for var in allList[1]:
- translatedText = translatedText.replace("{Ascii_" + str(count) + "}", var)
- count += 1
-
- # Colors
- count = 0
- if len(allList[2]) != 0:
- for var in allList[2]:
- translatedText = translatedText.replace("{Color_" + str(count) + "}", var)
- count += 1
-
- # Names
- count = 0
- if len(allList[3]) != 0:
- for var in allList[3]:
- translatedText = translatedText.replace("{Noun_" + str(count) + "}", var)
- count += 1
-
- # Vars
- count = 0
- if len(allList[4]) != 0:
- for var in allList[4]:
- translatedText = translatedText.replace("{Var_" + str(count) + "}", var)
- count += 1
-
- # Formatting
- count = 0
- if len(allList[5]) != 0:
- for var in allList[5]:
- translatedText = translatedText.replace("{FCode_" + str(count) + "}", var)
- count += 1
-
- return translatedText
-
-
-def batchList(input_list, batch_size):
- if not isinstance(batch_size, int) or batch_size <= 0:
- raise ValueError("batch_size must be a positive integer")
-
- return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
-
-
-def createContext(fullPromptFlag, subbedT):
- characters = "Game Characters:\n\
-林つかさ (Tsukasa Hayashi) - Female\n\
-山田美兎 (Miyato Yamada) - Female\n\
-鈴木赤音 (Akane Suzuki) - Female\n\
-佐藤莉伊南 (Riina Satou) - Female\n\
-佐々木万梨美 (Marimi Sasaki) - Female\n\
-渡辺登樹子 (Tokiko Watanabe) - Female\n\
-桃乃夢 (Yume Momono) - Female\n\
-吉浦美雪 (Miyuki Yoshiura) - Female\n\
-三ツ門まあな (Maana Mitsukado) - Female\n\
-モリー・ボイド (Molly Boyd) - Female\n\
-オルガ・ブヤチッチ (Olga Buyachich) - Female\n\
-アッチャラー ギッティ (Atchara Gitti) - Female\n\
-"
-
- system = (
- PROMPT
- if fullPromptFlag
- else f"Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`"
- )
- user = f"{subbedT}"
- return characters, system, user
-
-
-def translateText(characters, system, user, history):
- # Prompt
- msg = [{"role": "system", "content": system + characters}]
-
- # Characters
- msg.append({"role": "system", "content": characters})
-
- # History
- if isinstance(history, list):
- msg.extend([{"role": "assistant", "content": h} for h in history])
- else:
- msg.append({"role": "assistant", "content": history})
-
- # Content to TL
- msg.append({"role": "user", "content": f"{user}"})
- response = openai.chat.completions.create(
- temperature=0.1,
- frequency_penalty=0.1,
- model=MODEL,
- messages=msg,
- )
- return response
-
-
-def cleanTranslatedText(translatedText, varResponse):
- placeholders = {
- f"{LANGUAGE} Translation: ": "",
- "Translation: ": "",
- "っ": "",
- "〜": "~",
- "ッ": "",
- "。": ".",
- "Placeholder Text": "",
- # Add more replacements as needed
- }
- for target, replacement in placeholders.items():
- translatedText = translatedText.replace(target, replacement)
-
- translatedText = resubVars(translatedText, varResponse[1])
- if "\n" in translatedText:
- return [line for line in translatedText.split("\n") if line]
- else:
- return [line for line in translatedText.split("\\n") if line]
-
-
-def extractTranslation(translatedTextList, is_list):
- pattern = r"[\\]*`?(.*?)[\\]*?`??Line\d+>"
- # If it's a batch (i.e., list), extract with tags; otherwise, return the single item.
- if is_list:
- return [re.findall(pattern, line)[0][1] for line in translatedTextList if re.search(pattern, line)]
- else:
- matchList = re.findall(pattern, translatedTextList)
- return matchList[0][1] if matchList else translatedTextList
-
-
-def countTokens(characters, system, user, history):
- inputTotalTokens = 0
- outputTotalTokens = 0
- enc = tiktoken.encoding_for_model("gpt-4")
-
- # Input
- if isinstance(history, list):
- for line in history:
- inputTotalTokens += len(enc.encode(line))
- else:
- inputTotalTokens += len(enc.encode(history))
- inputTotalTokens += len(enc.encode(system))
- inputTotalTokens += len(enc.encode(characters))
- inputTotalTokens += len(enc.encode(user))
-
- # Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
-
- return [inputTotalTokens, outputTotalTokens]
-
-
-@retry(exceptions=Exception, tries=5, delay=5)
-def translateGPT(text, history, fullPromptFlag):
- totalTokens = [0, 0]
- if isinstance(text, list):
- tList = batchList(text, BATCHSIZE)
- else:
- tList = [text]
-
- for index, tItem in enumerate(tList):
- # Before sending to translation, if we have a list of items, add the formatting
- if isinstance(tItem, list):
- payload = "\n".join([f"`{item}`" for i, item in enumerate(tItem)])
- payload = payload.replace("``", "`Placeholder Text`")
- varResponse = subVars(payload)
- subbedT = varResponse[0]
- else:
- varResponse = subVars(tItem)
- subbedT = varResponse[0]
-
- # Things to Check before starting translation
- if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
- continue
-
- # Create Message
- characters, system, user = createContext(fullPromptFlag, subbedT)
-
- # Calculate Estimate
- if ESTIMATE:
- estimate = countTokens(characters, system, user, history)
- totalTokens[0] += estimate[0]
- totalTokens[1] += estimate[1]
- continue
-
- # Translating
- response = translateText(characters, system, user, history)
- translatedText = response.choices[0].message.content
- totalTokens[0] += response.usage.prompt_tokens
- totalTokens[1] += response.usage.completion_tokens
-
- # Formatting
- translatedTextList = cleanTranslatedText(translatedText, varResponse)
- if isinstance(tItem, list):
- extractedTranslations = extractTranslation(translatedTextList, True)
- tList[index] = extractedTranslations
- if len(tItem) != len(translatedTextList):
- mismatch = True # Just here so breakpoint can be set
- history = extractedTranslations[-10:] # Update history if we have a list
- else:
- # Ensure we're passing a single string to extractTranslation
- extractedTranslations = extractTranslation("\n".join(translatedTextList), False)
- tList[index] = extractedTranslations
-
- # Combine if multilist
- if isinstance(tList[0], list):
- tList = [t for sublist in tList for t in sublist]
-
- # Return
- if format == "json":
- return [tList, totalTokens]
- else:
- return [tList[0], totalTokens]
diff --git a/modules/anim.py b/modules/anim.py
deleted file mode 100644
index cdb0f83..0000000
--- a/modules/anim.py
+++ /dev/null
@@ -1,581 +0,0 @@
-# Libraries
-import json
-import os
-import re
-import util.dazedwrap as dazedwrap
-import threading
-import time
-import traceback
-import tiktoken
-import openai
-from pathlib import Path
-from colorama import Fore
-from dotenv import load_dotenv
-from retry import retry
-from tqdm import tqdm
-
-# Open AI
-load_dotenv()
-if os.getenv("api").replace(" ", "") != "":
- openai.base_url = os.getenv("api")
-openai.organization = os.getenv("org")
-openai.api_key = os.getenv("key")
-
-# 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")
-THREADS = int(os.getenv("threads"))
-LOCK = threading.Lock()
-WIDTH = int(os.getenv("width"))
-LISTWIDTH = int(os.getenv("listWidth"))
-NOTEWIDTH = 70
-MAXHISTORY = 10
-ESTIMATE = ""
-TOKENS = [0, 0]
-NAMESLIST = []
-NAMES = False # Output a list of all the character names found
-BRFLAG = False # If the game uses
instead
-FIXTEXTWRAP = True # Overwrites textwrap
-IGNORETLTEXT = False # Ignores all translated text.
-MISMATCH = [] # Lists files that throw a mismatch error (Length of GPT list response is wrong)
-
-# tqdm Globals
-BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
-POSITION = 0
-LEAVE = False
-
-# 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]+"
-
-# Pricing - Depends on the model https://openai.com/pricing
-# Batch Size - GPT 3.5 Struggles past 15 lines per request. GPT4 struggles past 50 lines per request
-# If you are getting a MISMATCH LENGTH error, lower the batch size.
-if "gpt-3.5" in MODEL:
- INPUTAPICOST = 3.00
- OUTPUTAPICOST = 5.00
- BATCHSIZE = 10
- FREQUENCY_PENALTY = 0.2
-elif "gpt-4" in MODEL:
- INPUTAPICOST = 2.0
- OUTPUTAPICOST = 8.00
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-elif "deepseek" in MODEL:
- INPUTAPICOST = 0.27
- OUTPUTAPICOST = 1.10
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-else:
- INPUTAPICOST = float(os.getenv("input_cost"))
- OUTPUTAPICOST = float(os.getenv("output_cost"))
- BATCHSIZE = int(os.getenv("batchsize"))
- FREQUENCY_PENALTY = float(os.getenv("frequency_penalty"))
-
-
-def handleAnim(filename, estimate):
- global ESTIMATE
- totalTokens = [0, 0]
- ESTIMATE = estimate
-
- if estimate:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- end = time.time()
- tqdm.write(getResultString(translatedData, end - start, filename))
- with LOCK:
- totalTokens[0] += translatedData[1][0]
- totalTokens[1] += translatedData[1][1]
-
- # Print Total
- totalString = getResultString(["", totalTokens, 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
-
- else:
- try:
- with open("translated/" + filename, "w", encoding="utf-8", newline="\n") as outFile:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- end = time.time()
- json.dump(translatedData[0], outFile, ensure_ascii=False, indent=4)
- tqdm.write(getResultString(translatedData, end - start, filename))
- with LOCK:
- totalTokens[0] += translatedData[1][0]
- totalTokens[1] += translatedData[1][1]
- except Exception:
- return "Fail"
-
- return getResultString(["", totalTokens, None], end - start, "TOTAL")
-
-
-def openFiles(filename):
- with open("files/" + filename, "r", encoding="UTF-8-sig") as f:
- data = json.load(f)
-
- # Map Files
- if ".json" in filename:
- translatedData = parseJSON(data, filename)
-
- else:
- raise NameError(filename + " Not Supported")
-
- return translatedData
-
-
-def getResultString(translatedData, translationTime, filename):
- # File Print String
- totalTokenstring = (
- Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
- "[Output: "
- + str(translatedData[1][1])
- + "]" "[Cost: ${:,.4f}".format(((translatedData[1][0] / 1000000) * INPUTAPICOST) + ((translatedData[1][1] / 1000000) * OUTPUTAPICOST))
- + "]"
- )
- timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
-
- if translatedData[2] == 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 parseJSON(data, filename):
- keys = list(data.keys())
- batches = [keys[i : i + BATCHSIZE] for i in range(0, len(keys), BATCHSIZE)]
- totalTokens = [0, 0]
- totalLines = 0
- totalLines = len(batches)
- global LOCK
-
- with tqdm(bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE) as pbar:
- pbar.desc = filename
- pbar.total = totalLines
- try:
- result = translateJSON(batches, data, pbar)
- totalTokens[0] += result[0]
- totalTokens[1] += result[1]
- except Exception as e:
- traceback.print_exc()
- return [data, totalTokens, e]
- return [data, totalTokens, None]
-
-
-def translateJSON(keys, data, pbar):
- translatedBatch = []
- textHistory = []
- tokens = [0, 0]
-
- for batch in keys:
- # Save Batch
- originalBatch = batch.copy()
-
- # If there isn't any Japanese in the text just skip
- needTL = False
- for i in range(len(batch)):
- t = data[batch[i]]
- if re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", t) or t == "":
- needTL = True
- if needTL is False and IGNORETLTEXT is True:
- pbar.update(1)
- continue
-
- # Remove any textwrap and Furigana
- for i in range(len(batch)):
- if FIXTEXTWRAP == True:
- # Textwrap
- data[originalBatch[i]] = data[originalBatch[i]].replace("@b", " ")
-
- # Furigana
- rcodeMatch = re.findall(r"(@\[(.+?):.+?\])", batch[i])
- if len(rcodeMatch) > 0:
- for match in rcodeMatch:
- batch[i] = batch[i].replace(match[0], match[1])
-
- # Translate
- if needTL is True:
- response = translateGPT(batch, textHistory, True)
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- translatedBatch = response[0]
- else:
- for i in range(len(originalBatch)):
- translatedBatch.append(data[originalBatch[i]])
-
- # Format and Set Text
- if len(batch) == len(translatedBatch):
- for i in range(len(translatedBatch)):
- # Remove added speaker
- translatedText = translatedBatch[i]
- translatedText = re.sub(r"^.+?\s\|\s?", "", translatedText)
-
- # Textwrap
- if "@n" in translatedText:
- match = re.search(r".*@n(.*)", translatedText)
- if match != None:
- tlText = match.group(1)
- tlText = dazedwrap.wrapText(tlText, width=WIDTH)
- tlText = tlText.replace("\n", "@b")
- translatedText = translatedText.replace(match.group(1), tlText)
-
- elif "@b" not in translatedText:
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
- translatedText = translatedText.replace("\n", "@b")
-
- # Set Data
- data[originalBatch[i]] = translatedText
- textHistory = translatedBatch
- translatedBatch.clear()
- # Mismatch, Skip Batch
- else:
- MISMATCH.append(batch)
- pbar.update(1)
- continue
- pbar.update(1)
-
- return tokens
-
-
-def subVars(jaString):
- jaString = jaString.replace("\u3000", " ")
-
- # Nested
- count = 0
- nestedList = re.findall(r"[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]", jaString)
- nestedList = set(nestedList)
- if len(nestedList) != 0:
- for icon in nestedList:
- jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
- count += 1
-
- # Icons
- count = 0
- iconList = re.findall(r"[\\]+[iIkKwWaA]+\[[0-9]+\]", jaString)
- iconList = set(iconList)
- if len(iconList) != 0:
- for icon in iconList:
- jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
- count += 1
-
- # Colors
- count = 0
- colorList = re.findall(r"[\\]+[cC]\[[0-9]+\]", jaString)
- colorList = set(colorList)
- if len(colorList) != 0:
- for color in colorList:
- jaString = jaString.replace(color, "[Color_" + str(count) + "]")
- count += 1
-
- # Names
- count = 0
- nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
- nameList = set(nameList)
- if len(nameList) != 0:
- for name in nameList:
- jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
- count += 1
-
- # Variables
- count = 0
- varList = re.findall(r"[\\]+[vV]\[[0-9]+\]", jaString)
- varList = set(varList)
- if len(varList) != 0:
- for var in varList:
- jaString = jaString.replace(var, "[Var_" + str(count) + "]")
- count += 1
-
- # Formatting
- count = 0
- formatList = re.findall(r"[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]", jaString)
- formatList = set(formatList)
- if len(formatList) != 0:
- for var in formatList:
- jaString = jaString.replace(var, "[FCode_" + str(count) + "]")
- count += 1
-
- # Put all lists in list and return
- allList = [nestedList, iconList, colorList, nameList, varList, formatList]
- return [jaString, allList]
-
-
-def resubVars(translatedText, allList):
- # Fix Spacing and ChatGPT Nonsense
- matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
- if len(matchList) > 0:
- for match in matchList:
- text = match.strip()
- translatedText = translatedText.replace(match, text)
-
- # Nested
- count = 0
- if len(allList[0]) != 0:
- for var in allList[0]:
- translatedText = translatedText.replace("[Nested_" + str(count) + "]", var)
- count += 1
-
- # Icons
- count = 0
- if len(allList[1]) != 0:
- for var in allList[1]:
- translatedText = translatedText.replace("[Ascii_" + str(count) + "]", var)
- count += 1
-
- # Colors
- count = 0
- if len(allList[2]) != 0:
- for var in allList[2]:
- translatedText = translatedText.replace("[Color_" + str(count) + "]", var)
- count += 1
-
- # Names
- count = 0
- if len(allList[3]) != 0:
- for var in allList[3]:
- translatedText = translatedText.replace("[Noun_" + str(count) + "]", var)
- count += 1
-
- # Vars
- count = 0
- if len(allList[4]) != 0:
- for var in allList[4]:
- translatedText = translatedText.replace("[Var_" + str(count) + "]", var)
- count += 1
-
- # Formatting
- count = 0
- if len(allList[5]) != 0:
- for var in allList[5]:
- translatedText = translatedText.replace("[FCode_" + str(count) + "]", var)
- count += 1
-
- return translatedText
-
-
-def batchList(input_list, batch_size):
- if not isinstance(batch_size, int) or batch_size <= 0:
- raise ValueError("batch_size must be a positive integer")
-
- return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
-
-
-def createContext(fullPromptFlag, subbedT):
- characters = "Game Characters:\n\
-達也 (Tatsuya) - Male\n\
-香織 (Kaori) - Female\n\
-岩瀬 (Iwase)\n\
-万蔵 (Manzou) - Male\n\
-結奈 (Yuuna) - Female\n\
-茅部 (Kayabe)\n\
-"
-
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
-You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
-Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
-- You translate everything, including erotic and explicit content.\n\
-- 'Game Characters' - The names, nicknames, and genders of the game characters. Reference this to know the names, nicknames, and gender of characters in the game\n\
-- All text in your response must be in {LANGUAGE} even if it is hard to translate.\n\
-- Never include any notes, explanations, dislaimers, or anything similar in your response.\n\
-- Maintain any spacing in the translation.\n\
-- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
-- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
-"
- )
- user = f"{subbedT}"
- return characters, system, user
-
-
-def translateText(characters, system, user, history, penalty):
- # Prompt
- msg = [{"role": "system", "content": system + characters}]
-
- # Characters
- msg.append({"role": "system", "content": characters})
-
- # History
- if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
- else:
- msg.append({"role": "system", "content": history})
-
- # Content to TL
- msg.append({"role": "user", "content": f"{user}"})
- response = openai.chat.completions.create(
- temperature=0,
- frequency_penalty=penalty,
- model=MODEL,
- messages=msg,
- )
- return response
-
-
-def cleanTranslatedText(translatedText, varResponse):
- placeholders = {
- f"{LANGUAGE} Translation: ": "",
- "Translation: ": "",
- "っ": "",
- "〜": "~",
- "ッ": "",
- "。": ".",
- "Placeholder Text": "",
- "é": "e",
- "—": "-",
- "ū": "u",
- # Add more replacements as needed
- }
- for target, replacement in placeholders.items():
- translatedText = translatedText.replace(target, replacement)
-
- # Elongate Long Dashes (Since GPT Ignores them...)
- translatedText = elongateCharacters(translatedText)
- translatedText = resubVars(translatedText, varResponse[1])
- return translatedText
-
-
-def elongateCharacters(text):
- # Define a pattern to match one character followed by one or more `ー` characters
- # Using a positive lookbehind assertion to capture the preceding character
- pattern = r"(?<=(.))ー+"
-
- # Define a replacement function that elongates the captured character
- def repl(match):
- char = match.group(1) # The character before the ー sequence
- count = len(match.group(0)) - 1 # Number of ー characters
- return char * count # Replace ー sequence with the character repeated
-
- # Use re.sub() to replace the pattern in the text
- return re.sub(pattern, repl, text)
-
-
-def extractTranslation(translatedTextList, is_list):
- pattern = r"`?<[Ll]ine\d+>([\\]*.*?[\\]*?)<\/?[Ll]ine\d+>`?"
- # If it's a batch (i.e., list), extract with tags; otherwise, return the single item.
- if is_list:
- matchList = re.findall(pattern, translatedTextList)
- return matchList
- else:
- matchList = re.findall(pattern, translatedTextList)
- return matchList[0][0] if matchList else translatedTextList
-
-
-def countTokens(characters, system, user, history):
- inputTotalTokens = 0
- outputTotalTokens = 0
- enc = tiktoken.encoding_for_model("gpt-4")
-
- # Input
- if isinstance(history, list):
- for line in history:
- inputTotalTokens += len(enc.encode(line))
- else:
- inputTotalTokens += len(enc.encode(history))
- inputTotalTokens += len(enc.encode(system))
- inputTotalTokens += len(enc.encode(characters))
- inputTotalTokens += len(enc.encode(user))
-
- # Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
-
- return [inputTotalTokens, outputTotalTokens]
-
-
-@retry(exceptions=Exception, tries=5, delay=5)
-def translateGPT(text, history, fullPromptFlag):
- mismatch = False
- totalTokens = [0, 0]
- if isinstance(text, list):
- tList = batchList(text, BATCHSIZE)
- else:
- tList = [text]
-
- for index, tItem in enumerate(tList):
- # Before sending to translation, if we have a list of items, add the formatting
- if isinstance(tItem, list):
- payload = "\n".join([f"`{item}`" for i, item in enumerate(tItem)])
- payload = re.sub(r"(<)(\/Line\d+>)", r"\1>Placeholder Text<\3", payload)
- varResponse = subVars(payload)
- subbedT = varResponse[0]
- else:
- varResponse = subVars(tItem)
- subbedT = varResponse[0]
-
- # Things to Check before starting translation
- if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
- continue
-
- # Create Message
- characters, system, user = createContext(fullPromptFlag, subbedT)
-
- # Calculate Estimate
- if ESTIMATE:
- estimate = countTokens(characters, system, user, history)
- totalTokens[0] += estimate[0]
- totalTokens[1] += estimate[1]
- continue
-
- # Translating
- response = translateText(characters, system, user, history, 0.02)
- translatedText = response.choices[0].message.content
- totalTokens[0] += response.usage.prompt_tokens
- totalTokens[1] += response.usage.completion_tokens
-
- # Formatting
- translatedText = cleanTranslatedText(translatedText, varResponse)
- if isinstance(tItem, list):
- extractedTranslations = extractTranslation(translatedText, True)
- tList[index] = extractedTranslations
- if len(tItem) != len(extractedTranslations):
- # Mismatch. Try Again
- response = translateText(characters, system, user, history, 0.1)
- translatedText = response.choices[0].message.content
- totalTokens[0] += response.usage.prompt_tokens
- totalTokens[1] += response.usage.completion_tokens
-
- # Formatting
- translatedText = cleanTranslatedText(translatedText, varResponse)
- if isinstance(tItem, list):
- extractedTranslations = extractTranslation(translatedText, True)
- tList[index] = extractedTranslations
- if len(tItem) != len(extractedTranslations):
- mismatch = True # Just here for breakpoint
-
- # Create History
- if not mismatch:
- history = extractedTranslations[-10:] # Update history if we have a list
- else:
- history = text[-10:]
- else:
- # Ensure we're passing a single string to extractTranslation
- extractedTranslations = extractTranslation(translatedText, False)
- tList[index] = extractedTranslations
-
- # Combine if multilist
- if isinstance(tList[0], list):
- tList = [t for sublist in tList for t in sublist]
-
- # Return
- if format == "json":
- return [tList, totalTokens]
- else:
- return [tList[0], totalTokens]
diff --git a/modules/atelier.py b/modules/atelier.py
deleted file mode 100644
index 0ead901..0000000
--- a/modules/atelier.py
+++ /dev/null
@@ -1,396 +0,0 @@
-import os
-from pathlib import Path
-import re
-import util.dazedwrap as dazedwrap
-import threading
-import time
-import traceback
-import tiktoken
-from colorama import Fore
-from dotenv import load_dotenv
-import openai
-from retry import retry
-from tqdm import tqdm
-
-# Open AI
-load_dotenv()
-if os.getenv("api").replace(" ", "") != "":
- openai.base_url = os.getenv("api")
-openai.organization = os.getenv("org")
-openai.api_key = os.getenv("key")
-
-# Globals
-MODEL = os.getenv("model")
-TIMEOUT = int(os.getenv("timeout"))
-LANGUAGE = os.getenv("language").capitalize()
-INPUTAPICOST = 0.002 # Depends on the model https://openai.com/pricing
-OUTPUTAPICOST = 0.002
-PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
-VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
-THREADS = int(os.getenv("threads")) # Controls how many threads are working on a single file (May have to drop this)
-LOCK = threading.Lock()
-WIDTH = int(os.getenv("width"))
-LISTWIDTH = int(os.getenv("listWidth"))
-NOTEWIDTH = 40
-MAXHISTORY = 10
-ESTIMATE = ""
-totalTokens = [0, 0]
-NAMESLIST = []
-
-# tqdm Globals
-BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
-POSITION = 0
-LEAVE = False
-
-# Translation Flags
-FIXTEXTWRAP = True
-IGNORETLTEXT = True
-
-
-def handleAtelier(filename, estimate):
- global ESTIMATE, totalTokens
- ESTIMATE = estimate
-
- if estimate:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- end = time.time()
- tqdm.write(getResultString(translatedData, end - start, filename))
- with LOCK:
- totalTokens[0] += translatedData[1][0]
- totalTokens[1] += translatedData[1][1]
-
- return getResultString(["", totalTokens, None], end - start, "TOTAL")
-
- else:
- try:
- with open("translated/" + filename, "w", encoding="utf-8", newline="\n") as outFile:
- start = time.time()
- translatedData = openFiles(filename)
- outFile.writelines(translatedData[0])
-
- # Print Result
- end = time.time()
- tqdm.write(getResultString(translatedData, end - start, filename))
- with LOCK:
- totalTokens[0] += translatedData[1][0]
- totalTokens[1] += translatedData[1][1]
- except Exception:
- return "Fail"
-
- return getResultString(["", totalTokens, None], end - start, "TOTAL")
-
-
-def openFiles(filename):
- with open("files/" + filename, "r", encoding="UTF-8") as f:
- translatedData = parseText(f, filename)
-
- return translatedData
-
-
-def getResultString(translatedData, translationTime, filename):
- # File Print String
- totalTokenstring = (
- Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
- "[Output: "
- + str(translatedData[1][1])
- + "]" "[Cost: ${:,.4f}".format(((translatedData[1][0] / 1000000) * INPUTAPICOST) + ((translatedData[1][1] / 1000000) * OUTPUTAPICOST))
- + "]"
- )
- timeString = Fore.BLUE + "[" + str(round(translationTime, 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:
- errorString = str(e) + Fore.RED
- return filename + ": " + totalTokenstring + timeString + Fore.RED + " \u2717 " + errorString + Fore.RESET
-
-
-def parseText(data, filename):
- totalLines = 0
- global LOCK
-
- # Get total for progress bar
- linesList = data.readlines()
- totalLines = len(linesList)
-
- with tqdm(bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE) as pbar:
- pbar.desc = filename
- pbar.total = totalLines
- try:
- response = translateText(linesList, pbar)
- except Exception as e:
- traceback.print_exc()
- return [linesList, 0, e]
- return [response[0], response[1], None]
-
-
-def translateText(data, pbar):
- textHistory = []
- maxHistory = MAXHISTORY
- totalTokens = [0, 0]
- syncIndex = 0
-
- for i in range(len(data)):
- if syncIndex > i:
- i = syncIndex
-
- match = re.findall(r"◆.+◆(.+)", data[i])
- if len(match) > 0:
- jaString = match[0]
-
- ### Translate
- # Remove any textwrap
- finalJAString = re.sub(r"\\n", " ", jaString)
-
- # Translate
- response = translateGPT(
- finalJAString,
- "Previous Text for Context: " + " ".join(textHistory),
- True,
- )
- totalTokens[0] += response[1][0]
- totalTokens[1] += response[1][1]
- translatedText = response[0]
-
- # TextHistory is what we use to give GPT Context, so thats appended here.
- textHistory.append('"' + translatedText + '"')
-
- # Keep textHistory list at length maxHistory
- if len(textHistory) > maxHistory:
- textHistory.pop(0)
-
- # Textwrap
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
- translatedText = translatedText.replace("\n", "\\n")
-
- # Write
- data[i] = data[i].replace(match[0], translatedText)
-
- syncIndex = i + 1
- pbar.update()
- return [data, totalTokens]
-
-
-def subVars(jaString):
- jaString = jaString.replace("\u3000", " ")
-
- # Nested
- count = 0
- nestedList = re.findall(r"[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]", jaString)
- nestedList = set(nestedList)
- if len(nestedList) != 0:
- for icon in nestedList:
- jaString = jaString.replace(icon, "{Nested_" + str(count) + "}")
- count += 1
-
- # Icons
- count = 0
- iconList = re.findall(r"[\\]+[iIkKwWaA]+\[[0-9]+\]", jaString)
- iconList = set(iconList)
- if len(iconList) != 0:
- for icon in iconList:
- jaString = jaString.replace(icon, "{Ascii_" + str(count) + "}")
- count += 1
-
- # Colors
- count = 0
- colorList = re.findall(r"[\\]+[cC]\[[0-9]+\]", jaString)
- colorList = set(colorList)
- if len(colorList) != 0:
- for color in colorList:
- jaString = jaString.replace(color, "{Color_" + str(count) + "}")
- count += 1
-
- # Names
- count = 0
- nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
- nameList = set(nameList)
- if len(nameList) != 0:
- for name in nameList:
- jaString = jaString.replace(name, "{N_" + str(count) + "}")
- count += 1
-
- # Variables
- count = 0
- varList = re.findall(r"[\\]+[vV]\[[0-9]+\]", jaString)
- varList = set(varList)
- if len(varList) != 0:
- for var in varList:
- jaString = jaString.replace(var, "{Var_" + str(count) + "}")
- count += 1
-
- # Formatting
- count = 0
- if "笑えるよね." in jaString:
- print("t")
- formatList = re.findall(r"[\\]+[\w]+\[.+?\]", jaString)
- formatList = set(formatList)
- if len(formatList) != 0:
- for var in formatList:
- jaString = jaString.replace(var, "{FCode_" + str(count) + "}")
- count += 1
-
- # Put all lists in list and return
- allList = [nestedList, iconList, colorList, nameList, varList, formatList]
- return [jaString, allList]
-
-
-def resubVars(translatedText, allList):
- # Fix Spacing and ChatGPT Nonsense
- matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
- if len(matchList) > 0:
- for match in matchList:
- text = match.strip()
- translatedText = translatedText.replace(match, text)
-
- # Nested
- count = 0
- if len(allList[0]) != 0:
- for var in allList[0]:
- translatedText = translatedText.replace("{Nested_" + str(count) + "}", var)
- count += 1
-
- # Icons
- count = 0
- if len(allList[1]) != 0:
- for var in allList[1]:
- translatedText = translatedText.replace("{Ascii_" + str(count) + "}", var)
- count += 1
-
- # Colors
- count = 0
- if len(allList[2]) != 0:
- for var in allList[2]:
- translatedText = translatedText.replace("{Color_" + str(count) + "}", var)
- count += 1
-
- # Names
- count = 0
- if len(allList[3]) != 0:
- for var in allList[3]:
- translatedText = translatedText.replace("{N_" + str(count) + "}", var)
- count += 1
-
- # Vars
- count = 0
- if len(allList[4]) != 0:
- for var in allList[4]:
- translatedText = translatedText.replace("{Var_" + str(count) + "}", var)
- count += 1
-
- # Formatting
- count = 0
- if len(allList[5]) != 0:
- for var in allList[5]:
- translatedText = translatedText.replace("{FCode_" + str(count) + "}", var)
- count += 1
-
- # Remove Color Variables Spaces
- # if '\\c' in translatedText:
- # translatedText = re.sub(r'\s*(\\+c\[[1-9]+\])\s*', r' \1', translatedText)
- # translatedText = re.sub(r'\s*(\\+c\[0+\])', r'\1', translatedText)
- return translatedText
-
-
-@retry(exceptions=Exception, tries=5, delay=5)
-def translateGPT(t, history, fullPromptFlag):
- # Sub Vars
- varResponse = subVars(t)
- subbedT = varResponse[0]
-
- # If there isn't any Japanese in the text just skip
- if not re.search(r"[一-龠]+|[ぁ-ゔ]+|[ァ-ヴ]+|[\uFF00-\uFFEF]", subbedT):
- return (t, [0, 0])
-
- # If ESTIMATE is True just count this as an execution and return.
- if ESTIMATE:
- enc = tiktoken.encoding_for_model("gpt-4")
- historyRaw = ""
- if isinstance(history, list):
- for line in history:
- historyRaw += line
- else:
- historyRaw = history
-
- inputTotalTokens = len(enc.encode(historyRaw)) + len(enc.encode(PROMPT))
- outputTotalTokens = len(enc.encode(t)) * 2 # Estimating 2x the size of the original text
- totalTokens = [inputTotalTokens, outputTotalTokens]
- return (t, totalTokens)
-
- # Characters
- context = "Game Characters:\
- Character: Surname:久高 Name:有史 == Surname:Kudaka Name:Yuushi - Gender: Male\
- Character: Surname:葛城 Name:碧璃 == Surname:Katsuragi Name:Midori - Gender: Female\
- Character: Surname:葛城 Name:依理子 == Surname:Katsuragi Name:Yoriko - Gender: Female\
- Character: Surname:桐乃木 Name:奏 == Surname:Kirinogi Name:Kanade - Gender: Female\
- Character: Surname:葛城 Name:光男 == Surname:Katsuragi Name:Mitsuo - Gender: Male\
- Character: Surname:尾木 Name:優真 == Surname:Ogi Name:Yuuma - Gender: Male"
-
- # Prompt
- if fullPromptFlag:
- system = PROMPT
- user = "Line to Translate = " + subbedT
- else:
- system = "Output ONLY the " + LANGUAGE + " translation in the following format: `Translation: <" + LANGUAGE.upper() + "_TRANSLATION>`"
- user = "Line to Translate = " + subbedT
-
- # Create Message List
- msg = []
- msg.append({"role": "system", "content": system})
- msg.append({"role": "user", "content": context})
- if isinstance(history, list):
- for line in history:
- msg.append({"role": "user", "content": line})
- else:
- msg.append({"role": "user", "content": history})
- msg.append({"role": "user", "content": user})
-
- response = openai.ChatCompletion.create(
- temperature=0,
- frequency_penalty=0.2,
- presence_penalty=0.2,
- model=MODEL,
- messages=msg,
- request_timeout=TIMEOUT,
- )
-
- # Save Translated Text
- translatedText = response.choices[0].message.content
- totalTokens = [response.usage.prompt_tokens, response.usage.completion_tokens]
-
- # Resub Vars
- translatedText = resubVars(translatedText, varResponse[1])
-
- # Remove Placeholder Text
- translatedText = translatedText.replace(LANGUAGE + " Translation: ", "")
- translatedText = translatedText.replace("Translation: ", "")
- translatedText = translatedText.replace("Line to Translate = ", "")
- translatedText = translatedText.replace("Translation = ", "")
- translatedText = translatedText.replace("Translate = ", "")
- translatedText = translatedText.replace(LANGUAGE + " Translation:", "")
- translatedText = translatedText.replace("Translation:", "")
- translatedText = translatedText.replace("Line to Translate =", "")
- translatedText = translatedText.replace("Translation =", "")
- translatedText = translatedText.replace("Translate =", "")
- translatedText = translatedText.replace("っ", "")
- translatedText = translatedText.replace("ッ", "")
- translatedText = translatedText.replace("ぁ", "")
- translatedText = translatedText.replace("。", ".")
- translatedText = translatedText.replace("、", ",")
- translatedText = translatedText.replace("?", "?")
- translatedText = translatedText.replace("!", "!")
-
- # Return Translation
- if len(translatedText) > 15 * len(t) or "I'm sorry, but I'm unable to assist with that translation" in translatedText:
- raise Exception
- else:
- return [translatedText, totalTokens]
diff --git a/modules/csv.py b/modules/csv.py
index 0dc8e30..b794059 100644
--- a/modules/csv.py
+++ b/modules/csv.py
@@ -439,7 +439,6 @@ def translateCSV(data, pbar, writer, filename, translatedList, format):
return totalTokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -453,15 +452,11 @@ def getSpeaker(speaker):
if speaker == NAMESLIST[i][0]:
return [NAMESLIST[i][1], [0, 0]]
- # If there isn't any Japanese in the text just skip
- if not re.search(LANGREGEX, speaker):
- return [speaker, [0, 0]]
-
# Translate and Store Speaker
response = translateGPT(
f"{speaker}",
"Reply with the " + LANGUAGE + " translation of the NPC name.",
- True,
+ False,
)
response[0] = response[0].title()
response[0] = response[0].replace("'S", "'s")
@@ -490,11 +485,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -504,9 +556,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -520,9 +571,10 @@ def translateText(system, user, history, penalty, format, model=MODEL):
# History
if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
+ msg.append({"role": "system", "content": "Translation History:"})
+ msg.extend([{"role": "assistant", "content": h} for h in history])
else:
- msg.append({"role": "system", "content": history})
+ msg.append({"role": "assistant", "content": history})
# Response Format
if format == "json":
@@ -557,8 +609,11 @@ def cleanTranslatedText(translatedText):
"】": "]",
"【": "[",
"é": "e",
- "ō": "o",
+ "this guy": "this bastard",
+ "This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
@@ -620,7 +675,7 @@ def countTokens(system, user, history):
inputTotalTokens += len(enc.encode(user))
# Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
+ outputTotalTokens += round(len(enc.encode(user)) * 2.5)
return [inputTotalTokens, outputTotalTokens]
@@ -684,7 +739,8 @@ def translateGPT(text, history, fullPromptFlag):
# AI Refused, Try Again
if not translatedText:
- response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format)
+ response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format, model="gpt-4o")
+ translatedText = response.choices[0].message.content
# Report Tokens
totalTokens[0] += response.usage.prompt_tokens
diff --git a/modules/eushully.py b/modules/eushully.py
deleted file mode 100644
index cb850d3..0000000
--- a/modules/eushully.py
+++ /dev/null
@@ -1,747 +0,0 @@
-# Libraries
-import os
-import re
-import util.dazedwrap as dazedwrap
-import threading
-import time
-import traceback
-import tiktoken
-import openai
-from pathlib import Path
-from colorama import Fore
-from dotenv import load_dotenv
-from retry import retry
-from tqdm import tqdm
-
-# Open AI
-load_dotenv()
-if os.getenv("api").replace(" ", "") != "":
- openai.base_url = os.getenv("api")
-openai.organization = os.getenv("org")
-openai.api_key = os.getenv("key")
-
-# 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")
-THREADS = int(os.getenv("threads"))
-LOCK = threading.Lock()
-WIDTH = int(os.getenv("width"))
-LISTWIDTH = int(os.getenv("listWidth"))
-NOTEWIDTH = 70
-MAXHISTORY = 10
-ESTIMATE = ""
-TOKENS = [0, 0]
-NAMESLIST = []
-NAMES = False # Output a list of all the character names found
-BRFLAG = False # If the game uses
instead
-FIXTEXTWRAP = True # Overwrites textwrap
-IGNORETLTEXT = False # Ignores all translated text.
-MISMATCH = [] # Lists files that throw a mismatch error (Length of GPT list response is wrong)
-
-# tqdm Globals
-BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
-POSITION = 0
-LEAVE = False
-PBAR = None
-
-# 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]+"
-
-# Pricing - Depends on the model https://openai.com/pricing
-# Batch Size - GPT 3.5 Struggles past 15 lines per request. GPT4 struggles past 50 lines per request
-# If you are getting a MISMATCH LENGTH error, lower the batch size.
-if "gpt-3.5" in MODEL:
- INPUTAPICOST = 3.00
- OUTPUTAPICOST = 5.00
- BATCHSIZE = 10
- FREQUENCY_PENALTY = 0.2
-elif "gpt-4" in MODEL:
- INPUTAPICOST = 2.0
- OUTPUTAPICOST = 8.00
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-elif "deepseek" in MODEL:
- INPUTAPICOST = 0.27
- OUTPUTAPICOST = 1.10
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-else:
- INPUTAPICOST = float(os.getenv("input_cost"))
- OUTPUTAPICOST = float(os.getenv("output_cost"))
- BATCHSIZE = int(os.getenv("batchsize"))
- FREQUENCY_PENALTY = float(os.getenv("frequency_penalty"))
-
-
-def handleEushully(filename, estimate):
- global ESTIMATE
- ESTIMATE = estimate
-
- if ESTIMATE:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- 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
-
- else:
- try:
- with open("translated/" + filename, "w", encoding="utf-8", errors="ignore") as outFile:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- end = time.time()
- outFile.writelines(translatedData[0])
- 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"
-
- return getResultString(["", TOKENS, None], end - start, "TOTAL")
-
-
-def getResultString(translatedData, translationTime, filename):
- # File Print String
- totalTokenstring = (
- Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
- "[Output: "
- + str(translatedData[1][1])
- + "]" "[Cost: ${:,.4f}".format(((translatedData[1][0] / 1000000) * INPUTAPICOST) + ((translatedData[1][1] / 1000000) * OUTPUTAPICOST))
- + "]"
- )
- timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
-
- if translatedData[2] == 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 openFiles(filename):
- with open("files/" + filename, "r", encoding="utf-8") as readFile:
- translatedData = parseRegex(readFile, filename)
-
- # Delete lines marked for deletion
- finalData = []
- for line in translatedData[0]:
- if line != "\\d\n":
- finalData.append(line)
- translatedData[0] = finalData
-
- return translatedData
-
-
-def parseRegex(readFile, filename):
- totalTokens = [0, 0]
-
- # Read File into data
- data = readFile.readlines()
-
- # Create Progress Bar
- with tqdm(bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE) as pbar:
- pbar.desc = filename
-
- try:
- result = translateEushully(data, pbar, 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 translateEushully(data, pbar, filename, translatedList):
- stringList = []
- currentGroup = []
- tokens = [0, 0]
- speaker = ""
- voice = False
- global LOCK, ESTIMATE, PBAR
- i = 0
-
- while i < len(data):
- voice = False
- # Speaker
- if "mov (global-int 46e2)" in data[i]:
- # Get Speaker
- speaker = re.search(r"mov \(global-int 46e2\)\s(.+)", data[i]).group(1)
- response = getSpeaker(speaker)
- speaker = response[0]
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- i += 1
-
- # Show Text
- if any(x in data[i] for x in ["show-text"]):
- # Lines
- regex = r'(.*?)"(.*)"'
- match = re.search(regex, data[i])
- # Grab Strings
- if match != None and match.group(2) != "":
- originalString = match.group(2)
- jaString = match.group(2)
- currentGroup = [jaString]
- while "end-text-line" in data[i + 1] and any(x in data[i + 2] for x in ["show-text"]):
- match = re.search(regex, data[i + 2])
- if match != None:
- currentGroup.append(match.group(2))
- if translatedList == []:
- del data[i]
- del data[i]
- jaString = " ".join(currentGroup)
-
- # Pass 1
- if translatedList == []:
- # Add String
- if speaker:
- stringList.append(f"[{speaker}]: {jaString.strip()}")
- else:
- stringList.append(jaString.strip())
-
- # Pass 2
- else:
- # Get Text
- if translatedList:
- # Grab and Pop
- translatedText = translatedList[0]
- translatedList.pop(0)
-
- # Set to None if empty list
- if len(translatedList) <= 0:
- translatedList = None
-
- # Replace Quotes
- translatedText = translatedText.replace('"', "'")
-
- # Remove speaker
- if speaker != "":
- translatedText = re.sub(r"^\[?(.+?)\]?\s?[|:]\s?", "", translatedText)
-
- # Textwrap
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
- translatedTextList = translatedText.split("\n")
-
- # Set Data
- if len(translatedTextList) > 1:
- for j in range(len(translatedTextList)):
- if any(x in data[i] for x in ["show-text", "set-string", "concat"]):
- del data[i]
- data.insert(i, f'{match.group(1)}"{translatedTextList[j]}"\n')
- i += 1
- if "end-text-line" not in data[i]:
- data.insert(i, "end-text-line 0\n")
- i += 1
- else:
- data[i] = f'{match.group(1)}"{translatedTextList[0]}"\n'
- speaker = ""
- i += 1
-
- # Nothing relevant. Skip Line.
- else:
- i += 1
-
- # Set String
- elif "set-string" in data[i]:
- # Lines
- regex = r'(.*?)"(.*)"'
- match = re.search(regex, data[i])
- # Grab Strings
- if match != None and match.group(2) != "":
- originalString = match.group(2)
- jaString = match.group(2)
- currentGroup = [jaString]
-
- # Remove Textwrap
- jaString = jaString.replace("\\n", " ")
-
- # Pass 1
- if translatedList == []:
- # Add String
- stringList.append(jaString.strip())
-
- # Pass 2
- else:
- # Get Text
- if translatedList:
- # Grab and Pop
- translatedText = translatedList[0]
- translatedList.pop(0)
-
- # Set to None if empty list
- if len(translatedList) <= 0:
- translatedList = None
-
- # Replace Quotes
- translatedText = translatedText.replace('"', "'")
-
- # Textwrap
- translatedText = dazedwrap.wrapText(translatedText, width=LISTWIDTH)
- translatedText = translatedText.replace("\n", "\\n")
-
- # Set Data
- data[i] = data[i].replace(originalString, translatedText)
- speaker = ""
- i += 1
-
- # Nothing relevant. Skip Line.
- else:
- i += 1
- else:
- i += 1
-
- # EOF
- if len(stringList) > 0:
- # Set Progress
- pbar.total = len(stringList)
- pbar.refresh()
-
- # Translate
- PBAR = pbar
- response = translateGPT(stringList, "", True)
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- translatedList = response[0]
-
- # Set Strings
- if len(stringList) == len(translatedList):
- translateEushully(data, pbar, filename, translatedList)
-
- # Mismatch
- else:
- with LOCK:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
- return tokens
-
-
-# Save some money and enter the character before translation
-def getSpeaker(speaker):
- match speaker:
- case "1":
- return ["Klaus", [0, 0]]
- case "2":
- return ["Helmina", [0, 0]]
- case "3":
- return ["Juliana", [0, 0]]
- case "4":
- return ["Reginia", [0, 0]]
- case "5":
- return ["Luciel", [0, 0]]
- case "6":
- return ["Mavislaine", [0, 0]]
- case "7":
- return ["Cerouge", [0, 0]]
- case "8":
- return ["Maize", [0, 0]]
- case "9":
- return ["Elvire", [0, 0]]
- case "a":
- return ["Beatrice", [0, 0]]
- case "295":
- return ["Orc", [0, 0]]
- case "232":
- return ["Archangel", [0, 0]]
- case "238":
- return ["False Juliana", [0, 0]]
- case "239":
- return ["False Regina", [0, 0]]
- case "23a":
- return ["False Luciel", [0, 0]]
- case "23d":
- return ["False Mavislaine", [0, 0]]
- case "cb":
- return ["Olga Niza Kite", [0, 0]]
- case "c9":
- return ["Demon Beast Lupus", [0, 0]]
- case "ca":
- return ["Evelinael", [0, 0]]
- case "10":
- return ["Eukleia", [0, 0]]
- case "15":
- return ["Lily", [0, 0]]
- case "16":
- return ["Kupuko", [0, 0]]
- case "b":
- return ["Ramiel", [0, 0]]
- case "c":
- return ["Henriette", [0, 0]]
- case "d":
- return ["Camilla", [0, 0]]
- case "cc":
- return ["Gogonaua", [0, 0]]
- case "65":
- return ["Demon Lord Reyvalois", [0, 0]]
- case "d0":
- return ["Demon Ranwald", [0, 0]]
- case "205":
- return ["Vanqueor", [0, 0]]
- case "66":
- return ["Angel Martina", [0, 0]]
- case "21f":
- return ["Hiten Demon", [0, 0]]
- case "d2":
- return ["Lena Eli", [0, 0]]
- case _:
- return ["Unknown", [0, 0]]
-
-
-def subVars(jaString):
- jaString = jaString.replace("\u3000", " ")
-
- # Nested
- count = 0
- nestedList = re.findall(r"[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]", jaString)
- nestedList = set(nestedList)
- if len(nestedList) != 0:
- for icon in nestedList:
- jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
- count += 1
-
- # Icons
- count = 0
- iconList = re.findall(r"[\\]+[iIkKwWaA]+\[[0-9]+\]", jaString)
- iconList = set(iconList)
- if len(iconList) != 0:
- for icon in iconList:
- jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
- count += 1
-
- # Colors
- count = 0
- colorList = re.findall(r"[\\]+[cC]\[[0-9]+\]", jaString)
- colorList = set(colorList)
- if len(colorList) != 0:
- for color in colorList:
- jaString = jaString.replace(color, "[Color_" + str(count) + "]")
- count += 1
-
- # Names
- count = 0
- nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
- nameList = set(nameList)
- if len(nameList) != 0:
- for name in nameList:
- jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
- count += 1
-
- # Variables
- count = 0
- varList = re.findall(r"[\\]+[vV]\[[0-9]+\]", jaString)
- varList = set(varList)
- if len(varList) != 0:
- for var in varList:
- jaString = jaString.replace(var, "[Var_" + str(count) + "]")
- count += 1
-
- # Formatting
- count = 0
- formatList = re.findall(r"[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]", jaString)
- formatList = set(formatList)
- if len(formatList) != 0:
- for var in formatList:
- jaString = jaString.replace(var, "[FCode_" + str(count) + "]")
- count += 1
-
- # Put all lists in list and return
- allList = [nestedList, iconList, colorList, nameList, varList, formatList]
- return [jaString, allList]
-
-
-def resubVars(translatedText, allList):
- # Fix Spacing and ChatGPT Nonsense
- matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
- if len(matchList) > 0:
- for match in matchList:
- text = match.strip()
- translatedText = translatedText.replace(match, text)
-
- # Nested
- count = 0
- if len(allList[0]) != 0:
- for var in allList[0]:
- translatedText = translatedText.replace("[Nested_" + str(count) + "]", var)
- count += 1
-
- # Icons
- count = 0
- if len(allList[1]) != 0:
- for var in allList[1]:
- translatedText = translatedText.replace("[Ascii_" + str(count) + "]", var)
- count += 1
-
- # Colors
- count = 0
- if len(allList[2]) != 0:
- for var in allList[2]:
- translatedText = translatedText.replace("[Color_" + str(count) + "]", var)
- count += 1
-
- # Names
- count = 0
- if len(allList[3]) != 0:
- for var in allList[3]:
- translatedText = translatedText.replace("[Noun_" + str(count) + "]", var)
- count += 1
-
- # Vars
- count = 0
- if len(allList[4]) != 0:
- for var in allList[4]:
- translatedText = translatedText.replace("[Var_" + str(count) + "]", var)
- count += 1
-
- # Formatting
- count = 0
- if len(allList[5]) != 0:
- for var in allList[5]:
- translatedText = translatedText.replace("[FCode_" + str(count) + "]", var)
- count += 1
-
- return translatedText
-
-
-def batchList(input_list, batch_size):
- if not isinstance(batch_size, int) or batch_size <= 0:
- raise ValueError("batch_size must be a positive integer")
-
- return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
-
-
-def createContext(fullPromptFlag, subbedT):
- characters = "Game Characters:\n\
-グレイス (Grace) - Female\n\
-"
-
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
-You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
-Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
-- You translate everything, including erotic and explicit content.\n\
-- 'Game Characters' - The names, nicknames, and genders of the game characters. Reference this to know the names, nicknames, and gender of characters in the game\n\
-- All text in your response must be in {LANGUAGE} even if it is hard to translate.\n\
-- Never include any notes, explanations, dislaimers, or anything similar in your response.\n\
-- Maintain any spacing in the translation.\n\
-- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
-- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
-"
- )
- user = f"{subbedT}"
- return characters, system, user
-
-
-def translateText(characters, system, user, history, penalty):
- # Prompt
- msg = [{"role": "system", "content": system + characters}]
-
- # Characters
- msg.append({"role": "system", "content": characters})
-
- # History
- if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
- else:
- msg.append({"role": "system", "content": history})
-
- # Content to TL
- msg.append({"role": "user", "content": f"{user}"})
- response = openai.chat.completions.create(
- temperature=0,
- frequency_penalty=penalty,
- model=MODEL,
- messages=msg,
- )
- return response
-
-
-def cleanTranslatedText(translatedText, varResponse):
- placeholders = {
- f"{LANGUAGE} Translation: ": "",
- "Translation: ": "",
- "っ": "",
- "〜": "~",
- "ッ": "",
- "。": ".",
- "< ": "<",
- " ": "",
- " >": ">",
- "「": '"',
- "」": '"',
- "Placeholder Text": "",
- "- chan": "-chan",
- "- kun": "-kun",
- "- san": "-san",
- # Add more replacements as needed
- }
- for target, replacement in placeholders.items():
- translatedText = translatedText.replace(target, replacement)
-
- # Elongate Long Dashes (Since GPT Ignores them...)
- translatedText = elongateCharacters(translatedText)
- translatedText = resubVars(translatedText, varResponse[1])
- return translatedText
-
-
-def elongateCharacters(text):
- # Define a pattern to match one character followed by one or more `ー` characters
- # Using a positive lookbehind assertion to capture the preceding character
- pattern = r"(?<=(.))ー+"
-
- # Define a replacement function that elongates the captured character
- def repl(match):
- char = match.group(1) # The character before the ー sequence
- count = len(match.group(0)) - 1 # Number of ー characters
- return char * count # Replace ー sequence with the character repeated
-
- # Use re.sub() to replace the pattern in the text
- return re.sub(pattern, repl, text)
-
-
-def extractTranslation(translatedTextList, is_list):
- pattern = r"`?<[Ll]ine\d+>([\\]*.*?[\\]*?)<\/?[Ll]ine\d+>`?"
- # If it's a batch (i.e., list), extract with tags; otherwise, return the single item.
- if is_list:
- matchList = re.findall(pattern, translatedTextList)
- return matchList
- else:
- matchList = re.findall(pattern, translatedTextList)
- return matchList[0][0] if matchList else translatedTextList
-
-
-def countTokens(characters, system, user, history):
- inputTotalTokens = 0
- outputTotalTokens = 0
- enc = tiktoken.encoding_for_model("gpt-4")
-
- # Input
- if isinstance(history, list):
- for line in history:
- inputTotalTokens += len(enc.encode(line))
- else:
- inputTotalTokens += len(enc.encode(history))
- inputTotalTokens += len(enc.encode(system))
- inputTotalTokens += len(enc.encode(characters))
- inputTotalTokens += len(enc.encode(user))
-
- # Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
-
- return [inputTotalTokens, outputTotalTokens]
-
-
-@retry(exceptions=Exception, tries=5, delay=5)
-def translateGPT(text, history, fullPromptFlag):
- global PBAR
-
- mismatch = False
- totalTokens = [0, 0]
- if isinstance(text, list):
- tList = batchList(text, BATCHSIZE)
- else:
- tList = [text]
-
- for index, tItem in enumerate(tList):
- # Before sending to translation, if we have a list of items, add the formatting
- if isinstance(tItem, list):
- payload = "\n".join([f"`{item}`" for i, item in enumerate(tItem)])
- payload = re.sub(r"(<)(\/Line\d+>)", r"\1>Placeholder Text<\3", payload)
- varResponse = subVars(payload)
- subbedT = varResponse[0]
- else:
- varResponse = subVars(tItem)
- subbedT = varResponse[0]
-
- # Things to Check before starting translation
- if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
- if PBAR is not None:
- PBAR.update(len(tItem))
- continue
-
- # Create Message
- characters, system, user = createContext(fullPromptFlag, subbedT)
-
- # Calculate Estimate
- if ESTIMATE:
- estimate = countTokens(characters, system, user, history)
- totalTokens[0] += estimate[0]
- totalTokens[1] += estimate[1]
- continue
-
- # Translating
- response = translateText(characters, system, user, history, 0.02)
- translatedText = response.choices[0].message.content
- totalTokens[0] += response.usage.prompt_tokens
- totalTokens[1] += response.usage.completion_tokens
-
- # Formatting
- translatedText = cleanTranslatedText(translatedText, varResponse)
- if isinstance(tItem, list):
- extractedTranslations = extractTranslation(translatedText, True)
- tList[index] = extractedTranslations
- if len(tItem) != len(extractedTranslations):
- # Mismatch. Try Again
- response = translateText(characters, system, user, history, 0.2)
- translatedText = response.choices[0].message.content
- totalTokens[0] += response.usage.prompt_tokens
- totalTokens[1] += response.usage.completion_tokens
-
- # Formatting
- translatedText = cleanTranslatedText(translatedText, varResponse)
- if isinstance(tItem, list):
- extractedTranslations = extractTranslation(translatedText, True)
- tList[index] = extractedTranslations
- if len(tItem) != len(extractedTranslations):
- mismatch = True # Just here for breakpoint
-
- # Create History
- with LOCK:
- if PBAR is not None:
- PBAR.update(len(tItem))
- if not mismatch:
- history = extractedTranslations[-10:] # Update history if we have a list
- else:
- history = text[-10:]
- else:
- # Ensure we're passing a single string to extractTranslation
- extractedTranslations = extractTranslation(translatedText, False)
- tList[index] = extractedTranslations
-
- # Combine if multilist
- if isinstance(tList[0], list):
- tList = [t for sublist in tList for t in sublist]
-
- # Return
- if format == "json":
- return [tList, totalTokens]
- else:
- return [tList[0], totalTokens]
diff --git a/modules/images.py b/modules/images.py
index ba2a104..acdefa2 100644
--- a/modules/images.py
+++ b/modules/images.py
@@ -389,7 +389,6 @@ def translateImages(imageList):
return [translatedList, totalTokens, None]
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -407,7 +406,7 @@ def getSpeaker(speaker):
response = translateGPT(
f"{speaker}",
"Reply with the " + LANGUAGE + " translation of the NPC name.",
- True,
+ False,
)
response[0] = response[0].title()
response[0] = response[0].replace("'S", "'s")
@@ -429,40 +428,6 @@ def getSpeaker(speaker):
return [speaker, [0, 0]]
-def subVars(jaString):
- jaString = jaString.replace("\u3000", " ")
-
- # Formatting
- count = 0
- codeList = re.findall(r"[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]", jaString)
- codeList = set(codeList)
- if len(codeList) != 0:
- for var in codeList:
- jaString = jaString.replace(var, "[FCode_" + str(count) + "]")
- count += 1
-
- # Put all lists in list and return
- return [jaString, codeList]
-
-
-def resubVars(translatedText, codeList):
- # Fix Spacing and ChatGPT Nonsense
- matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
- if len(matchList) > 0:
- for match in matchList:
- text = match.strip()
- translatedText = translatedText.replace(match, text)
-
- # Formatting
- count = 0
- if len(codeList) != 0:
- for var in codeList:
- translatedText = translatedText.replace("[FCode_" + str(count) + "]", var)
- count += 1
-
- return translatedText
-
-
def batchList(input_list, batch_size):
if not isinstance(batch_size, int) or batch_size <= 0:
raise ValueError("batch_size must be a positive integer")
@@ -470,56 +435,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
-def createContext(fullPromptFlag, subbedT, format):
- characters = "Game Characters:\n\
-ロラン (Roland) - Male\n\
-リュカ (Ryuka) - Male\n\
-レックス (Rex) - Male\n\
-タバサ (Tabasa) - Female\n\
-アルス (Ars) - Male\n\
-アマカラ (Amakara) - Male\n\
-エリー (Eri) - Female\n\
-リオ (Rio) - Female\n\
-サマル (Samal) - Male\n\
-ムーン (Moon) - Female\n\
-アリーナ (Arina) - Female\n\
-クリフト (Cliff) - Male\n\
-マーニャ (Manya) - Female\n\
-ミネア (Minea) - Female\n\
-デボラ (Debora) - Female\n\
-ビアンカ (Bianca) - Female\n\
-フローラ (Flora) - Female\n\
-バーバラ (Barbara) - Female\n\
-ミレーユ (Mireyu) - Female\n\
-アイラ (Aira) - Female\n\
-フォズ (Foz) - Female\n\
-マリベル (Maribel) - Female\n\
-ククール (Kukool) - Male\n\
-ゲルダ (Gerda) - Female\n\
-ゼシカ (Jessica) - Female\n\
-ヤンガス (Yangus) - Male\n\
-ラヴィエル (Raviel) - Female\n\
-セティア (Setia) - Female\n\
-ダイ (Dai) - Male\n\
-ヒュンケル (Hyunckel) - Male\n\
-ポップ (Pop) - Male\n\
-マァム (Maam) - Female\n\
-レオナ (Leona) - Female\n\
-アステア (Astea) - Female\n\
-イヨ (Iyo) - Female\n\
-ジャガン (Jagan) - Male\n\
-ヤオ (Yao) - Female\n\
-デイジィ (Daisy) - Female\n\
-バイシュン (Baishun) - Male\n\
-ブライ (Buraimu) - Male\n\
-ハッサン (Hassan) - Male\n\
-アロマ (Aroma) - Female\n\
-"
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
+def createContext(fullPromptFlag, subbedT, format):
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -529,28 +506,25 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
user = subbedT
- return characters, system, user
+ return system, user
-def translateText(characters, system, user, history, penalty, format):
+def translateText(system, user, history, penalty, format, model=MODEL):
# Prompt
- msg = [{"role": "system", "content": system + characters}]
-
- # Characters
- msg.append({"role": "system", "content": characters})
+ msg = [{"role": "system", "content": system}]
# History
if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
+ msg.append({"role": "system", "content": "Translation History:"})
+ msg.extend([{"role": "assistant", "content": h} for h in history])
else:
- msg.append({"role": "system", "content": history})
+ msg.append({"role": "assistant", "content": history})
# Response Format
if format == "json":
@@ -563,14 +537,14 @@ def translateText(characters, system, user, history, penalty, format):
response = openai.chat.completions.create(
temperature=0,
frequency_penalty=penalty,
- model=MODEL,
+ model=model,
response_format=responseFormat,
messages=msg,
)
return response
-def cleanTranslatedText(translatedText, varResponse):
+def cleanTranslatedText(translatedText):
placeholders = {
f"{LANGUAGE} Translation: ": "",
"Translation: ": "",
@@ -581,15 +555,26 @@ def cleanTranslatedText(translatedText, varResponse):
"「": '\\"',
"」": '\\"',
"- ": "-",
+ "—": "―",
+ "】": "]",
+ "【": "[",
+ "é": "e",
+ "this guy": "this bastard",
+ "This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
translatedText = translatedText.replace(target, replacement)
+ # Remove Repeating Characters
+ pattern = re.compile(r"(.)\s*\1(?:\s*\1){" + str(20 - 1) + r",}")
+ translatedText = pattern.sub(lambda match: match.group(0).replace(" ", "")[:20], translatedText)
+
# Elongate Long Dashes (Since GPT Ignores them...)
translatedText = elongateCharacters(translatedText)
- translatedText = resubVars(translatedText, varResponse[1])
return translatedText
@@ -610,6 +595,8 @@ def elongateCharacters(text):
def extractTranslation(translatedTextList, is_list):
try:
+ translatedTextList = re.sub(r'\\"+\"([^,\n}])', r'\\"\1', translatedTextList)
+ translatedTextList = re.sub(r"(? instead
-FIXTEXTWRAP = True # Overwrites textwrap
-IGNORETLTEXT = False # Ignores all translated text.
-MISMATCH = [] # Lists files that throw a mismatch error (Length of GPT list response is wrong)
-
-# tqdm Globals
-BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
-POSITION = 0
-LEAVE = False
-
-# 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]+"
-
-# Pricing - Depends on the model https://openai.com/pricing
-# Batch Size - GPT 3.5 Struggles past 15 lines per request. GPT4 struggles past 50 lines per request
-# If you are getting a MISMATCH LENGTH error, lower the batch size.
-if "gpt-3.5" in MODEL:
- INPUTAPICOST = 3.00
- OUTPUTAPICOST = 5.00
- BATCHSIZE = 10
- FREQUENCY_PENALTY = 0.2
-elif "gpt-4" in MODEL:
- INPUTAPICOST = 2.0
- OUTPUTAPICOST = 8.00
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-elif "deepseek" in MODEL:
- INPUTAPICOST = 0.27
- OUTPUTAPICOST = 1.10
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-else:
- INPUTAPICOST = float(os.getenv("input_cost"))
- OUTPUTAPICOST = float(os.getenv("output_cost"))
- BATCHSIZE = int(os.getenv("batchsize"))
- FREQUENCY_PENALTY = float(os.getenv("frequency_penalty"))
-
-
-def handleIris(filename, estimate):
- global ESTIMATE
- ESTIMATE = estimate
-
- if ESTIMATE:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- 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
-
- else:
- try:
- with open("translated/" + filename, "w", encoding="cp932", errors="ignore") as outFile:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- end = time.time()
- outFile.writelines(translatedData[0])
- 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"
-
- return getResultString(["", TOKENS, None], end - start, "TOTAL")
-
-
-def getResultString(translatedData, translationTime, filename):
- # File Print String
- totalTokenstring = (
- Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
- "[Output: "
- + str(translatedData[1][1])
- + "]" "[Cost: ${:,.4f}".format(((translatedData[1][0] / 1000000) * INPUTAPICOST) + ((translatedData[1][1] / 1000000) * OUTPUTAPICOST))
- + "]"
- )
- timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
-
- if translatedData[2] == 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 openFiles(filename):
- with open("files/" + filename, "r", encoding="shift_jis") as readFile:
- translatedData = parseIris(readFile, filename)
-
- # Delete lines marked for deletion
- finalData = []
- for line in translatedData[0]:
- if line != "\\d\n":
- finalData.append(line)
- translatedData[0] = finalData
-
- return translatedData
-
-
-def parseIris(readFile, filename):
- totalTokens = [0, 0]
-
- # Read File into data
- data = readFile.readlines()
-
- # Create Progress Bar
- with tqdm(bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE) as pbar:
- pbar.desc = filename
-
- try:
- result = translateIris(data, pbar, 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 translateIris(data, pbar, filename, translatedList):
- stringList = []
- currentGroup = []
- tokens = [0, 0]
- speaker = ""
- voice = False
- global LOCK, ESTIMATE
- i = 0
-
- while i < len(data):
- voice = False
- speaker = ""
- if "#MSGVOICE" in data[i]:
- i += 1
- voice = True
- voiceVar = data[i]
- if "#MSG," in data[i] or "#MSG\n" in data[i] or voice == True:
- i += 1
- # Speaker
- if re.search(r'^ ?([^#\/."、。*!!()\(\)\[\] \n]+)\n', data[i]) and len(data[i]) < 30:
- match = re.search(r"(.*)", data[i])
- if match != None:
- speaker = match.group(1)
- if speaker[0] == "\u3000":
- speaker = speaker[1:]
- response = getSpeaker(speaker, pbar, filename)
- speaker = response[0]
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- if translatedList != []:
- speaker = speaker.replace(" ", "\u3000")
- data[i] = f"\u3000{speaker}\n"
- else:
- speaker = ""
- i += 1
-
- # Lines
- match = re.search(r"(.*)", data[i])
- if match != None and match.group(1) != "":
- # Pass 1
- if translatedList == []:
- # Grab Consecutive Strings
- jaString = data[i]
- if data[i] != "\n":
- if data[i][0] == "\u3000":
- jaString = data[i][1:]
- currentGroup.append(jaString)
- i += 1
- while data[i] != "\n":
- jaString = data[i]
- if data[i] != "\n":
- jaString = data[i][1:]
- currentGroup.append(jaString)
- i += 1
-
- # Join up 401 groups for better translation.
- if len(currentGroup) > 0:
- jaString = "".join(currentGroup)
- currentGroup = []
-
- # Remove any textwrap
- jaString = jaString.replace("\n", " ")
-
- # Temporarily convert spaces (For Textwrap Later)
- jaString = jaString.replace("\u3000", " ")
-
- # Add Speaker (If there is one)
- if speaker != "":
- jaString = f"{speaker}: {jaString}"
-
- # Add String
- stringList.append(jaString.strip())
-
- # Pass 2
- else:
- # Insert Strings
- while data[i] != "\n":
- data.pop(i)
-
- # Get Text
- if translatedList:
- translatedText = translatedList[0]
- translatedList.pop(0)
- if len(translatedList) <= 0:
- translatedList = None
-
- # Remove added speaker
- translatedText = re.sub(r"^.+?:\s", "", translatedText)
-
- # Textwrap
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
- translatedText = translatedText.replace("\n", "\n\u3000")
-
- # Replace Whitespace and Commas
- translatedText = translatedText.replace(", ", "、")
- translatedText = translatedText.replace(",\u3000", "、")
- translatedText = translatedText.replace(",", "、")
- translatedText = translatedText.replace(" ", "\u3000")
-
- # Set Data
- # Game crashes on more than 3 lines. Will need to create a new MSG for long translations
- if translatedText.count("\n") > 2:
- # Split List
- translatedTextList = splitNewlines(translatedText)
-
- # MSG Voice
- count = 0
- for text in translatedTextList:
- if count != 0:
- if voice == True:
- # MSG for each item in the list
- data.insert(i, "#MSGVOICE,\n")
- i += 1
- data.insert(i, f"{voiceVar}")
- i += 1
- else:
- data.insert(i, "#MSG,\n")
- i += 1
- if speaker:
- data[i] = f"\u3000{speaker}\n"
- i += 1
- if text[0] == "\u3000":
- data.insert(i, f"{text}\n")
- else:
- data.insert(i, f"\u3000{text}\n")
- i += 1
- count += 1
- if data[i] != "\n":
- data.insert(i, "\n")
- data[i] = f"\n{data[i]}"
- else:
- data.insert(i, f"\u3000{translatedText}\n")
- i += 1
- if data[i] != "\n":
- data[i] = f"\n{data[i]}"
-
- elif "#SELECT" in data[i] and translatedList == []:
- Iris = r"(.+?) +\d$"
- i += 1
- match = re.search(Iris, data[i])
- if match:
- choiceList = []
- choiceList.append(match.group(1))
- i += 1
- match = re.search(Iris, data[i])
- while match:
- choiceList.append(match.group(1))
- i += 1
- match = re.search(Iris, data[i])
-
- # Translate
- question = stringList[len(stringList) - 1]
- response = translateGPT(
- choiceList,
- f"Previous text for context: {question}\n\nThis will be a dialogue option",
- True,
- pbar,
- filename,
- )
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- choiceListTL = response[0]
-
- # Set Data
- i = i - len(choiceListTL)
- for j in range(len(choiceListTL)):
- # Replace Whitespace and Commas
- choiceListTL[j] = choiceListTL[j].replace(", ", "、")
- choiceListTL[j] = choiceListTL[j].replace(",\u3000", "、")
- choiceListTL[j] = choiceListTL[j].replace(",", "、")
- choiceListTL[j] = choiceListTL[j].replace(" ", "\u3000")
- data[i] = data[i].replace(choiceList[j], choiceListTL[j])
- i += 1
-
- # Nothing relevant. Skip Line.
- else:
- i += 1
- else:
- i += 1
-
- # EOF
- if len(stringList) > 0:
- # Set Progress
- pbar.total = len(stringList)
- pbar.refresh()
-
- # Translate
- response = translateGPT(stringList, "", True, pbar, filename)
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- translatedList = response[0]
-
- # Set Strings
- if len(stringList) == len(translatedList):
- translateIris(data, pbar, filename, translatedList)
-
- # Mismatch
- else:
- with LOCK:
- if filename not in MISMATCH:
- MISMATCH.append(filename)
- return tokens
-
-
-def splitNewlines(text):
- parts = []
- newline_count = 0 # Counts the number of newline characters encountered
- start_index = 0 # Start index of the current string part
-
- for i, char in enumerate(text):
- if char == "\n":
- newline_count += 1
- if newline_count == 3:
- # Append the string part from start_index to current index (inclusive)
- parts.append(text[start_index : i + 1])
- # Reset newline count and update start_index for the next string part
- newline_count = 0
- start_index = i + 1
-
- # Edge case: if the text does not end with a newline, we still need to append the last part
- if start_index < len(text):
- parts.append(text[start_index:])
-
- return parts
-
-
-# Save some money and enter the character before translation
-def getSpeaker(speaker, pbar, filename):
- match speaker:
- case "ファイン":
- return ["Fine", [0, 0]]
- case "":
- return ["", [0, 0]]
- case _:
- # Store Speaker
- if speaker not in str(NAMESLIST):
- response = translateGPT(
- speaker,
- "Reply with only the " + LANGUAGE + " translation of the NPC name.",
- False,
- pbar,
- filename,
- )
- response[0] = response[0].replace("'S", "'s")
- speakerList = [speaker, response[0]]
- NAMESLIST.append(speakerList)
- return response
-
- # Find Speaker
- else:
- for i in range(len(NAMESLIST)):
- if speaker == NAMESLIST[i][0]:
- return [NAMESLIST[i][1], [0, 0]]
-
- return [speaker, [0, 0]]
-
-
-def subVars(jaString):
- jaString = jaString.replace("\u3000", " ")
-
- # Nested
- count = 0
- nestedList = re.findall(r"[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]", jaString)
- nestedList = set(nestedList)
- if len(nestedList) != 0:
- for icon in nestedList:
- jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
- count += 1
-
- # Icons
- count = 0
- iconList = re.findall(r"[\\]+[iIkKwWaA]+\[[0-9]+\]", jaString)
- iconList = set(iconList)
- if len(iconList) != 0:
- for icon in iconList:
- jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
- count += 1
-
- # Colors
- count = 0
- colorList = re.findall(r"[\\]+[cC]\[[0-9]+\]", jaString)
- colorList = set(colorList)
- if len(colorList) != 0:
- for color in colorList:
- jaString = jaString.replace(color, "[Color_" + str(count) + "]")
- count += 1
-
- # Names
- count = 0
- nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
- nameList = set(nameList)
- if len(nameList) != 0:
- for name in nameList:
- jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
- count += 1
-
- # Variables
- count = 0
- varList = re.findall(r"[\\]+[vV]\[[0-9]+\]", jaString)
- varList = set(varList)
- if len(varList) != 0:
- for var in varList:
- jaString = jaString.replace(var, "[Var_" + str(count) + "]")
- count += 1
-
- # Formatting
- count = 0
- formatList = re.findall(r"[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]", jaString)
- formatList = set(formatList)
- if len(formatList) != 0:
- for var in formatList:
- jaString = jaString.replace(var, "[FCode_" + str(count) + "]")
- count += 1
-
- # Put all lists in list and return
- allList = [nestedList, iconList, colorList, nameList, varList, formatList]
- return [jaString, allList]
-
-
-def resubVars(translatedText, allList):
- # Fix Spacing and ChatGPT Nonsense
- matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
- if len(matchList) > 0:
- for match in matchList:
- text = match.strip()
- translatedText = translatedText.replace(match, text)
-
- # Nested
- count = 0
- if len(allList[0]) != 0:
- for var in allList[0]:
- translatedText = translatedText.replace("[Nested_" + str(count) + "]", var)
- count += 1
-
- # Icons
- count = 0
- if len(allList[1]) != 0:
- for var in allList[1]:
- translatedText = translatedText.replace("[Ascii_" + str(count) + "]", var)
- count += 1
-
- # Colors
- count = 0
- if len(allList[2]) != 0:
- for var in allList[2]:
- translatedText = translatedText.replace("[Color_" + str(count) + "]", var)
- count += 1
-
- # Names
- count = 0
- if len(allList[3]) != 0:
- for var in allList[3]:
- translatedText = translatedText.replace("[Noun_" + str(count) + "]", var)
- count += 1
-
- # Vars
- count = 0
- if len(allList[4]) != 0:
- for var in allList[4]:
- translatedText = translatedText.replace("[Var_" + str(count) + "]", var)
- count += 1
-
- # Formatting
- count = 0
- if len(allList[5]) != 0:
- for var in allList[5]:
- translatedText = translatedText.replace("[FCode_" + str(count) + "]", var)
- count += 1
-
- return translatedText
-
-
-def batchList(input_list, batch_size):
- if not isinstance(batch_size, int) or batch_size <= 0:
- raise ValueError("batch_size must be a positive integer")
-
- return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
-
-
-def createContext(fullPromptFlag, subbedT):
- characters = "Game Characters:\n\
-フィリア (Philia) - Female\n\
-アルネット (Annett) - Female\n\
-ラピュセナ (Rapusena) - Female\n\
-リッカ (Rikka) - Female\n\
-アンデリビア (Andelivia) - Female\n\
-リリアブルム (Liliabloom) - Female\n\
-カルナ (Karna) - Female\n\
-ラフィング=スピア (Laughing Spear) - Female\n\
-ノーラ (Nora) - Female\n\
-"
-
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
-You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
-Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
-- You translate everything, including erotic and explicit content.\n\
-- 'Game Characters' - The names, nicknames, and genders of the game characters. Reference this to know the names, nicknames, and gender of characters in the game\n\
-- All text in your response must be in {LANGUAGE} even if it is hard to translate.\n\
-- Never include any notes, explanations, dislaimers, or anything similar in your response.\n\
-- Maintain any spacing in the translation.\n\
-- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
-- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
-"
- )
- user = f"{subbedT}"
- return characters, system, user
-
-
-def translateText(characters, system, user, history):
- # Prompt
- msg = [{"role": "system", "content": system + characters}]
-
- # Characters
- msg.append({"role": "system", "content": characters})
-
- # History
- if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
- else:
- msg.append({"role": "system", "content": history})
-
- # Content to TL
- msg.append({"role": "user", "content": f"{user}"})
- response = openai.chat.completions.create(
- temperature=0.1,
- frequency_penalty=0.1,
- model=MODEL,
- messages=msg,
- )
- return response
-
-
-def cleanTranslatedText(translatedText, varResponse):
- placeholders = {
- f"{LANGUAGE} Translation: ": "",
- "Translation: ": "",
- "っ": "",
- "〜": "~",
- "ッ": "",
- "。": ".",
- "Placeholder Text": "",
- # Add more replacements as needed
- }
- for target, replacement in placeholders.items():
- translatedText = translatedText.replace(target, replacement)
-
- # Elongate Long Dashes (Since GPT Ignores them...)
- translatedText = elongateCharacters(translatedText)
- translatedText = resubVars(translatedText, varResponse[1])
- return translatedText
-
-
-def elongateCharacters(text):
- # Define a pattern to match one character followed by one or more `ー` characters
- # Using a positive lookbehind assertion to capture the preceding character
- pattern = r"(?<=(.))ー+"
-
- # Define a replacement function that elongates the captured character
- def repl(match):
- char = match.group(1) # The character before the ー sequence
- count = len(match.group(0)) - 1 # Number of ー characters
- return char * count # Replace ー sequence with the character repeated
-
- # Use re.sub() to replace the pattern in the text
- return re.sub(pattern, repl, text)
-
-
-def extractTranslation(translatedTextList, is_list):
- pattern = r"`?<[Ll]ine\d+>([\\]*.*?[\\]*?)<\/?[Ll]ine\d+>`?"
- # If it's a batch (i.e., list), extract with tags; otherwise, return the single item.
- if is_list:
- matchList = re.findall(pattern, translatedTextList)
- return matchList
- else:
- matchList = re.findall(pattern, translatedTextList)
- return matchList[0][0] if matchList else translatedTextList
-
-
-def countTokens(characters, system, user, history):
- inputTotalTokens = 0
- outputTotalTokens = 0
- enc = tiktoken.encoding_for_model("gpt-4")
-
- # Input
- if isinstance(history, list):
- for line in history:
- inputTotalTokens += len(enc.encode(line))
- else:
- inputTotalTokens += len(enc.encode(history))
- inputTotalTokens += len(enc.encode(system))
- inputTotalTokens += len(enc.encode(characters))
- inputTotalTokens += len(enc.encode(user))
-
- # Output
- outputTotalTokens += round(len(enc.encode(user)) * 2)
-
- return [inputTotalTokens, outputTotalTokens]
-
-
-@retry(exceptions=Exception, tries=5, delay=5)
-def translateGPT(text, history, fullPromptFlag, pbar, filename):
- mismatch = False
- totalTokens = [0, 0]
- if isinstance(text, list):
- tList = batchList(text, BATCHSIZE)
- else:
- tList = [text]
-
- for index, tItem in enumerate(tList):
- # Before sending to translation, if we have a list of items, add the formatting
- if isinstance(tItem, list):
- payload = "\n".join([f"`{item}`" for i, item in enumerate(tItem)])
- payload = re.sub(r"(<)(\/Line\d+>)", r"\1>Placeholder Text<\3", payload)
- varResponse = subVars(payload)
- subbedT = varResponse[0]
- else:
- varResponse = subVars(tItem)
- subbedT = varResponse[0]
-
- # Things to Check before starting translation
- if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
- continue
-
- # Create Message
- characters, system, user = createContext(fullPromptFlag, subbedT)
-
- # Calculate Estimate
- if ESTIMATE:
- estimate = countTokens(characters, system, user, history)
- totalTokens[0] += estimate[0]
- totalTokens[1] += estimate[1]
- continue
-
- # Translating
- response = translateText(characters, system, user, history)
- translatedText = response.choices[0].message.content
- totalTokens[0] += response.usage.prompt_tokens
- totalTokens[1] += response.usage.completion_tokens
-
- # Formatting
- translatedText = cleanTranslatedText(translatedText, varResponse)
- if isinstance(tItem, list):
- extractedTranslations = extractTranslation(translatedText, True)
- if len(tItem) != len(extractedTranslations):
- # Mismatch. Try Again
- response = translateText(characters, system, user, history)
- translatedText = response.choices[0].message.content
- totalTokens[0] += response.usage.prompt_tokens
- totalTokens[1] += response.usage.completion_tokens
-
- # Formatting
- translatedText = cleanTranslatedText(translatedText, varResponse)
- if isinstance(tItem, list):
- extractedTranslations = extractTranslation(translatedText, True)
- if len(tItem) == len(extractedTranslations):
- tList[index] = extractedTranslations
- else:
- MISMATCH.append(filename)
- else:
- tList[index] = extractedTranslations
-
- # Create History
- history = tList[index] # Update history if we have a list
- pbar.update(len(tList[index]))
-
- else:
- # Ensure we're passing a single string to extractTranslation
- extractedTranslations = extractTranslation(translatedText, False)
- tList[index] = extractedTranslations
-
- # Combine if multilist
- if isinstance(tList[0], list):
- tList = [t for sublist in tList for t in sublist]
-
- # Return
- if format == "json":
- return [tList, totalTokens]
- else:
- return [tList[0], totalTokens]
diff --git a/modules/javascript.py b/modules/javascript.py
deleted file mode 100644
index 40aef6a..0000000
--- a/modules/javascript.py
+++ /dev/null
@@ -1,537 +0,0 @@
-# Libraries
-import os
-import re
-import util.dazedwrap as dazedwrap
-import threading
-import time
-import traceback
-import tiktoken
-import openai
-from pathlib import Path
-from colorama import Fore
-from dotenv import load_dotenv
-from retry import retry
-from tqdm import tqdm
-
-# Open AI
-load_dotenv()
-if os.getenv("api").replace(" ", "") != "":
- openai.base_url = os.getenv("api")
-openai.organization = os.getenv("org")
-openai.api_key = os.getenv("key")
-
-# 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")
-THREADS = int(os.getenv("threads"))
-LOCK = threading.Lock()
-WIDTH = int(os.getenv("width"))
-LISTWIDTH = int(os.getenv("listWidth"))
-NOTEWIDTH = 70
-MAXHISTORY = 10
-ESTIMATE = ""
-TOKENS = [0, 0]
-NAMESLIST = []
-NAMES = False # Output a list of all the character names found
-BRFLAG = False # If the game uses
instead
-FIXTEXTWRAP = True # Overwrites textwrap
-IGNORETLTEXT = False # Ignores all translated text.
-MISMATCH = [] # Lists files that throw a mismatch error (Length of GPT list response is wrong)
-
-# tqdm Globals
-BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
-POSITION = 0
-LEAVE = False
-
-# 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]+"
-
-# Pricing - Depends on the model https://openai.com/pricing
-# Batch Size - GPT 3.5 Struggles past 15 lines per request. GPT4 struggles past 50 lines per request
-# If you are getting a MISMATCH LENGTH error, lower the batch size.
-if "gpt-3.5" in MODEL:
- INPUTAPICOST = 3.00
- OUTPUTAPICOST = 5.00
- BATCHSIZE = 10
- FREQUENCY_PENALTY = 0.2
-elif "gpt-4" in MODEL:
- INPUTAPICOST = 2.0
- OUTPUTAPICOST = 8.00
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-elif "deepseek" in MODEL:
- INPUTAPICOST = 0.27
- OUTPUTAPICOST = 1.10
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-else:
- INPUTAPICOST = float(os.getenv("input_cost"))
- OUTPUTAPICOST = float(os.getenv("output_cost"))
- BATCHSIZE = int(os.getenv("batchsize"))
- FREQUENCY_PENALTY = float(os.getenv("frequency_penalty"))
-
-
-def handleJavascript(filename, estimate):
- global ESTIMATE
- ESTIMATE = estimate
-
- if ESTIMATE:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- 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
-
- else:
- try:
- with open("translated/" + filename, "w", encoding="utf8", errors="ignore") as outFile:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- end = time.time()
- outFile.writelines(translatedData[0])
- 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"
-
- return getResultString(["", TOKENS, None], end - start, "TOTAL")
-
-
-def getResultString(translatedData, translationTime, filename):
- # File Print String
- totalTokenstring = (
- Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
- "[Output: "
- + str(translatedData[1][1])
- + "]" "[Cost: ${:,.4f}".format(((translatedData[1][0] / 1000000) * INPUTAPICOST) + ((translatedData[1][1] / 1000000) * OUTPUTAPICOST))
- + "]"
- )
- timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
-
- if translatedData[2] == 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 openFiles(filename):
- with open("files/" + filename, "r", encoding="utf-8") as readFile:
- translatedData = parseJS(readFile, filename)
-
- return translatedData
-
-
-def parseJS(readFile, filename):
- totalTokens = [0, 0]
- data = readFile.readlines()
-
- with tqdm(bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE) as pbar:
- pbar.desc = filename
-
- try:
- result = translateJS(data, pbar)
- totalTokens[0] += result[0]
- totalTokens[1] += result[1]
- except Exception as e:
- traceback.print_exc()
- return [data, totalTokens, e]
- return [data, totalTokens, None]
-
-
-def translateJS(data, pbar):
- tokens = [0, 0]
- i = 0
-
- # Regex & Plugin Name
- regex = r'ObjectiveContent[\\]+":[\\]+"[\\]+"(.*?)[\\]+"'
-
- # Find Plugin
- while i < len(data):
- # Run Search
- stringList = re.findall(regex, data[i])
- if len(stringList) != 0:
- pbar.total = len(stringList)
- pbar.refresh()
- modifiedStringList = stringList.copy()
-
- # Remove Wordwrap [Optional]
- for j in range(len(modifiedStringList)):
- modifiedStringList[j] = modifiedStringList[j].replace(r"\\\\\\\\n", r" ")
-
- # Translate
- response = translateGPT(modifiedStringList, f"Reply with the {LANGUAGE} translation", True, pbar)
- translatedList = response[0]
- tokens[0] = response[1][0]
- tokens[0] = response[1][1]
-
- # Validate Length & Replace Each Match
- if len(translatedList) == len(modifiedStringList):
- for j in range(len(translatedList)):
- # Add escape for '
- translatedList[j] = re.sub(r"[^\\](')", "\\'", translatedList[j])
-
- # Wordwrap [Optional]
- translatedList[j] = dazedwrap.wrapText(translatedList[j], LISTWIDTH)
- translatedList[j] = translatedList[j].replace("\n", r"\\\\\\\\n")
-
- # Set
- data[i] = data[i].replace(stringList[j], translatedList[j])
- # Mismatch
- else:
- pbar.write("Mismatch Error")
- i += 1
-
- return tokens
-
-
-def subVars(jaString):
- jaString = jaString.replace("\u3000", " ")
-
- # Nested
- count = 0
- nestedList = re.findall(r"[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]", jaString)
- nestedList = set(nestedList)
- if len(nestedList) != 0:
- for icon in nestedList:
- jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
- count += 1
-
- # Icons
- count = 0
- iconList = re.findall(r"[\\]+[iIkKwWaA]+\[[0-9]+\]", jaString)
- iconList = set(iconList)
- if len(iconList) != 0:
- for icon in iconList:
- jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
- count += 1
-
- # Colors
- count = 0
- colorList = re.findall(r"[\\]+[cC]\[[0-9]+\]", jaString)
- colorList = set(colorList)
- if len(colorList) != 0:
- for color in colorList:
- jaString = jaString.replace(color, "[Color_" + str(count) + "]")
- count += 1
-
- # Names
- count = 0
- nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
- nameList = set(nameList)
- if len(nameList) != 0:
- for name in nameList:
- jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
- count += 1
-
- # Variables
- count = 0
- varList = re.findall(r"[\\]+[vV]\[[0-9]+\]", jaString)
- varList = set(varList)
- if len(varList) != 0:
- for var in varList:
- jaString = jaString.replace(var, "[Var_" + str(count) + "]")
- count += 1
-
- # Formatting
- count = 0
- formatList = re.findall(r"[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]", jaString)
- formatList = set(formatList)
- if len(formatList) != 0:
- for var in formatList:
- jaString = jaString.replace(var, "[FCode_" + str(count) + "]")
- count += 1
-
- # Put all lists in list and return
- allList = [nestedList, iconList, colorList, nameList, varList, formatList]
- return [jaString, allList]
-
-
-def resubVars(translatedText, allList):
- # Fix Spacing and ChatGPT Nonsense
- matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
- if len(matchList) > 0:
- for match in matchList:
- text = match.strip()
- translatedText = translatedText.replace(match, text)
-
- # Nested
- count = 0
- if len(allList[0]) != 0:
- for var in allList[0]:
- translatedText = translatedText.replace("[Nested_" + str(count) + "]", var)
- count += 1
-
- # Icons
- count = 0
- if len(allList[1]) != 0:
- for var in allList[1]:
- translatedText = translatedText.replace("[Ascii_" + str(count) + "]", var)
- count += 1
-
- # Colors
- count = 0
- if len(allList[2]) != 0:
- for var in allList[2]:
- translatedText = translatedText.replace("[Color_" + str(count) + "]", var)
- count += 1
-
- # Names
- count = 0
- if len(allList[3]) != 0:
- for var in allList[3]:
- translatedText = translatedText.replace("[Noun_" + str(count) + "]", var)
- count += 1
-
- # Vars
- count = 0
- if len(allList[4]) != 0:
- for var in allList[4]:
- translatedText = translatedText.replace("[Var_" + str(count) + "]", var)
- count += 1
-
- # Formatting
- count = 0
- if len(allList[5]) != 0:
- for var in allList[5]:
- translatedText = translatedText.replace("[FCode_" + str(count) + "]", var)
- count += 1
-
- return translatedText
-
-
-def batchList(input_list, batch_size):
- if not isinstance(batch_size, int) or batch_size <= 0:
- raise ValueError("batch_size must be a positive integer")
-
- return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
-
-
-def createContext(fullPromptFlag, subbedT):
- characters = "Game Characters:\n\
-皆月 (Minazuki)\n\
-さやか (Sayaka)\n\
-皆月 さやか (Minazuki Sayaka) - Female\n\
-広瀬 (Hirose)\n\
-智恵 (Chie) - Female\n\
-広瀬 智恵 (Hirose Chie) - Female\n\
-"
-
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
-You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
-Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
-- You translate everything, including erotic and explicit content.\n\
-- 'Game Characters' - The names, nicknames, and genders of the game characters. Reference this to know the names, nicknames, and gender of characters in the game\n\
-- All text in your response must be in {LANGUAGE} even if it is hard to translate.\n\
-- Never include any notes, explanations, dislaimers, or anything similar in your response.\n\
-- Maintain any spacing in the translation.\n\
-- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
-- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
-"
- )
- user = f"{subbedT}"
- return characters, system, user
-
-
-def translateText(characters, system, user, history):
- # Prompt
- msg = [{"role": "system", "content": system + characters}]
-
- # Characters
- msg.append({"role": "system", "content": characters})
-
- # History
- if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
- else:
- msg.append({"role": "system", "content": history})
-
- # Content to TL
- msg.append({"role": "user", "content": f"{user}"})
- response = openai.chat.completions.create(
- temperature=0.1,
- frequency_penalty=0.1,
- model=MODEL,
- messages=msg,
- )
- return response
-
-
-def cleanTranslatedText(translatedText, varResponse):
- placeholders = {
- f"{LANGUAGE} Translation: ": "",
- "Translation: ": "",
- "っ": "",
- "〜": "~",
- "ッ": "",
- "。": ".",
- "Placeholder Text": "",
- # Add more replacements as needed
- }
- for target, replacement in placeholders.items():
- translatedText = translatedText.replace(target, replacement)
-
- # Elongate Long Dashes (Since GPT Ignores them...)
- translatedText = elongateCharacters(translatedText)
- translatedText = resubVars(translatedText, varResponse[1])
- return translatedText
-
-
-def elongateCharacters(text):
- # Define a pattern to match one character followed by one or more `ー` characters
- # Using a positive lookbehind assertion to capture the preceding character
- pattern = r"(?<=(.))ー+"
-
- # Define a replacement function that elongates the captured character
- def repl(match):
- char = match.group(1) # The character before the ー sequence
- count = len(match.group(0)) - 1 # Number of ー characters
- return char * count # Replace ー sequence with the character repeated
-
- # Use re.sub() to replace the pattern in the text
- return re.sub(pattern, repl, text)
-
-
-def extractTranslation(translatedTextList, is_list):
- pattern = r"`?<[Ll]ine\d+>([\\]*.*?[\\]*?)<\/?[Ll]ine\d+>`?"
- # If it's a batch (i.e., list), extract with tags; otherwise, return the single item.
- if is_list:
- matchList = re.findall(pattern, translatedTextList)
- return matchList
- else:
- matchList = re.findall(pattern, translatedTextList)
- return matchList[0][0] if matchList else translatedTextList
-
-
-def countTokens(characters, system, user, history):
- inputTotalTokens = 0
- outputTotalTokens = 0
- enc = tiktoken.encoding_for_model("gpt-4")
-
- # Input
- if isinstance(history, list):
- for line in history:
- inputTotalTokens += len(enc.encode(line))
- else:
- inputTotalTokens += len(enc.encode(history))
- inputTotalTokens += len(enc.encode(system))
- inputTotalTokens += len(enc.encode(characters))
- inputTotalTokens += len(enc.encode(user))
-
- # Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
-
- return [inputTotalTokens, outputTotalTokens]
-
-
-@retry(exceptions=Exception, tries=5, delay=5)
-def translateGPT(text, history, fullPromptFlag, pbar):
- mismatch = False
- totalTokens = [0, 0]
- if isinstance(text, list):
- tList = batchList(text, BATCHSIZE)
- else:
- tList = [text]
-
- for index, tItem in enumerate(tList):
- # Before sending to translation, if we have a list of items, add the formatting
- if isinstance(tItem, list):
- payload = "\n".join([f"`{item}`" for i, item in enumerate(tItem)])
- payload = re.sub(r"(<)(\/Line\d+>)", r"\1>Placeholder Text<\3", payload)
- varResponse = subVars(payload)
- subbedT = varResponse[0]
- else:
- varResponse = subVars(tItem)
- subbedT = varResponse[0]
-
- # Things to Check before starting translation
- if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
- continue
-
- # Create Message
- characters, system, user = createContext(fullPromptFlag, subbedT)
-
- # Calculate Estimate
- if ESTIMATE:
- estimate = countTokens(characters, system, user, history)
- totalTokens[0] += estimate[0]
- totalTokens[1] += estimate[1]
- continue
-
- # Translating
- response = translateText(characters, system, user, history)
- translatedText = response.choices[0].message.content
- totalTokens[0] += response.usage.prompt_tokens
- totalTokens[1] += response.usage.completion_tokens
-
- # Formatting
- translatedText = cleanTranslatedText(translatedText, varResponse)
- if isinstance(tItem, list):
- extractedTranslations = extractTranslation(translatedText, True)
- tList[index] = extractedTranslations
- if len(tItem) != len(extractedTranslations):
- # Mismatch. Try Again
- response = translateText(characters, system, user, history)
- translatedText = response.choices[0].message.content
- totalTokens[0] += response.usage.prompt_tokens
- totalTokens[1] += response.usage.completion_tokens
-
- # Formatting
- translatedText = cleanTranslatedText(translatedText, varResponse)
- if isinstance(tItem, list):
- extractedTranslations = extractTranslation(translatedText, True)
- if len(tItem) == len(extractedTranslations):
- tList[index] = extractedTranslations
- else:
- mismatch = True # Just here for breakpoint
-
- # Create History
- history = tList[index] # Update history if we have a list
- pbar.update(len(tList[index]))
-
- else:
- # Ensure we're passing a single string to extractTranslation
- extractedTranslations = extractTranslation(translatedText, False)
- tList[index] = extractedTranslations
-
- # Combine if multilist
- if isinstance(tList[0], list):
- tList = [t for sublist in tList for t in sublist]
-
- # Return
- if format == "json":
- return [tList, totalTokens]
- else:
- return [tList[0], totalTokens]
diff --git a/modules/json.py b/modules/json.py
index f6477d9..476a109 100644
--- a/modules/json.py
+++ b/modules/json.py
@@ -296,7 +296,6 @@ def translateJSON(data, translatedList):
translateJSON(data, [stringListTL])
return tokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -343,11 +342,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -357,9 +413,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
diff --git a/modules/kansen.py b/modules/kansen.py
deleted file mode 100644
index 17ac3e8..0000000
--- a/modules/kansen.py
+++ /dev/null
@@ -1,713 +0,0 @@
-# Libraries
-import os
-import re
-import util.dazedwrap as dazedwrap
-import threading
-import time
-import traceback
-import tiktoken
-import openai
-from pathlib import Path
-from colorama import Fore
-from dotenv import load_dotenv
-from retry import retry
-from tqdm import tqdm
-
-# Open AI
-load_dotenv()
-if os.getenv("api").replace(" ", "") != "":
- openai.base_url = os.getenv("api")
-openai.organization = os.getenv("org")
-openai.api_key = os.getenv("key")
-
-# 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")
-THREADS = int(os.getenv("threads"))
-LOCK = threading.Lock()
-WIDTH = int(os.getenv("width"))
-LISTWIDTH = int(os.getenv("listWidth"))
-NOTEWIDTH = 70
-MAXHISTORY = 10
-ESTIMATE = ""
-TOKENS = [0, 0]
-NAMESLIST = []
-NAMES = False # Output a list of all the character names found
-BRFLAG = False # If the game uses
instead
-FIXTEXTWRAP = False # Overwrites textwrap
-IGNORETLTEXT = False # Ignores all translated text.
-MISMATCH = [] # Lists files that throw a mismatch error (Length of GPT list response is wrong)
-
-# tqdm Globals
-BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
-POSITION = 0
-LEAVE = False
-
-# 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]+"
-
-# Pricing - Depends on the model https://openai.com/pricing
-# Batch Size - GPT 3.5 Struggles past 15 lines per request. GPT4 struggles past 50 lines per request
-# If you are getting a MISMATCH LENGTH error, lower the batch size.
-if "gpt-3.5" in MODEL:
- INPUTAPICOST = 3.00
- OUTPUTAPICOST = 5.00
- BATCHSIZE = 10
- FREQUENCY_PENALTY = 0.2
-elif "gpt-4" in MODEL:
- INPUTAPICOST = 2.0
- OUTPUTAPICOST = 8.00
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-elif "deepseek" in MODEL:
- INPUTAPICOST = 0.27
- OUTPUTAPICOST = 1.10
- BATCHSIZE = 30
- FREQUENCY_PENALTY = 0.05
-else:
- INPUTAPICOST = float(os.getenv("input_cost"))
- OUTPUTAPICOST = float(os.getenv("output_cost"))
- BATCHSIZE = int(os.getenv("batchsize"))
- FREQUENCY_PENALTY = float(os.getenv("frequency_penalty"))
-
-
-def handleKansen(filename, estimate):
- global ESTIMATE
- ESTIMATE = estimate
-
- if ESTIMATE:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- 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
-
- else:
- try:
- with open("translated/" + filename, "w", encoding="shift_jis", errors="ignore") as outFile:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- end = time.time()
- outFile.writelines(translatedData[0])
- 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"
-
- return getResultString(["", TOKENS, None], end - start, "TOTAL")
-
-
-def getResultString(translatedData, translationTime, filename):
- # File Print String
- totalTokenstring = (
- Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
- "[Output: "
- + str(translatedData[1][1])
- + "]" "[Cost: ${:,.4f}".format(((translatedData[1][0] / 1000000) * INPUTAPICOST) + ((translatedData[1][1] / 1000000) * OUTPUTAPICOST))
- + "]"
- )
- timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
-
- if translatedData[2] == 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 openFiles(filename):
- with open("files/" + filename, "r", encoding="cp932") as readFile:
- translatedData = parseTyrano(readFile, filename)
-
- # Delete lines marked for deletion
- finalData = []
- for line in translatedData[0]:
- if line != "\\d\n":
- finalData.append(line)
- translatedData[0] = finalData
-
- return translatedData
-
-
-def parseTyrano(readFile, filename):
- totalTokens = [0, 0]
- totalLines = 0
-
- # Get total for progress bar
- data = readFile.readlines()
- totalLines = len(data)
-
- with tqdm(bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE) as pbar:
- pbar.desc = filename
- pbar.total = totalLines
-
- try:
- result = translateTyrano(data, pbar, totalLines)
- totalTokens[0] += result[0]
- totalTokens[1] += result[1]
- except Exception as e:
- traceback.print_exc()
- return [data, totalTokens, e]
- return [data, totalTokens, None]
-
-
-def translateTyrano(data, pbar, totalLines):
- textHistory = []
- batch = []
- currentGroup = []
- maxHistory = MAXHISTORY
- tokens = [0, 0]
- speaker = ""
- insertBool = False
- global LOCK, ESTIMATE
- i = 0
- batchStartIndex = 0
-
- while i < len(data):
- # Speaker
- if "[ns]" in data[i]:
- matchList = re.findall(r"\[ns\](.+?)\[", data[i])
- if len(matchList) != 0:
- response = getSpeaker(matchList[0])
- speaker = response[0]
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- data[i] = "[ns]" + speaker + "[nse]\n"
- else:
- speaker = ""
-
- # Choices
- elif "[sel" in data[i]:
- matchList = re.findall(r'\[sel.+text="(.+?)".+', data[i])
- if len(matchList) != 0:
- originalText = matchList[0]
- if len(textHistory) > 0:
- response = translateGPT(
- matchList[0],
- "Keep your translation as brief as possible. Previous text for context: "
- + textHistory[len(textHistory) - 1]
- + "\n\nReply in the style of a dialogue option.",
- False,
- )
- else:
- response = translateGPT(
- matchList[0],
- "\n\nReply in the style of a dialogue option.",
- False,
- )
- translatedText = response[0]
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
-
- # Remove characters that may break scripts
- charList = [".", '"', "\\n"]
- for char in charList:
- translatedText = translatedText.replace(char, "")
-
- # Escape all '
- translatedText = translatedText.replace("\\", "")
- # translatedText = translatedText.replace("'", "\\\'")
-
- # Set Data
- translatedText = data[i].replace(originalText, translatedText)
- data[i] = translatedText
-
- # Lines
- matchList = re.findall(r"(.+?)\[[rpcms_sel]+\]$", data[i])
- if len(matchList) > 0:
- if "hisout" in matchList[0]:
- i += 1
- continue
- currentGroup.append(matchList[0])
- if len(data) > i + 1:
- while "[r]" in data[i + 1]:
- if insertBool is True:
- data[i] = r"\d\n"
- pbar.update(1)
- i += 1
- matchList = re.findall(r"(.+?)\[r\]", data[i])
- if len(matchList) > 0:
- currentGroup.append(matchList[0])
- while "[pcms]" in data[i + 1]:
- if insertBool is True:
- data[i] = r"\d\n"
- pbar.update(1)
- i += 1
- matchList = re.findall(r"(.+?)\[pcms\]", data[i])
- if len(matchList) > 0:
- currentGroup.append(matchList[0])
- while "[pcms_sel]" in data[i + 1]:
- if insertBool is True:
- data[i] = r"\d\n"
- pbar.update(1)
- i += 1
- matchList = re.findall(r"(.+?)\[pcms_sel\]", data[i])
- if len(matchList) > 0:
- currentGroup.append(matchList[0])
- # Join up 401 groups for better translation.
- if len(currentGroup) > 0:
- finalJAString = " ".join(currentGroup)
- oldjaString = finalJAString
-
- # Remove any textwrap
- if FIXTEXTWRAP == True:
- finalJAString = finalJAString.replace("[r]", " ")
-
- # Remove Extra Stuff bad for translation.
- finalJAString = finalJAString.replace("゙", "")
- finalJAString = finalJAString.replace("・", ".")
- finalJAString = finalJAString.replace("‶", "")
- finalJAString = finalJAString.replace("”", "")
- finalJAString = finalJAString.replace("―", "-")
- finalJAString = finalJAString.replace("…", "...")
- finalJAString = re.sub(r"(\.{3}\.+)", "...", finalJAString)
- finalJAString = finalJAString.replace(" ", " ")
-
- # Furigana Removal
- matchList = re.findall(r"(\[ruby\stext=.+text=\"(.+)\"\])", finalJAString)
- if len(matchList) > 0:
- finalJAString = finalJAString.replace(matchList[0][0], matchList[0][1])
-
- # Add Speaker (If there is one)
- if speaker != "":
- finalJAString = f"{speaker}: {finalJAString}"
-
- # [Passthrough 1] Pulling From File
- if insertBool is False:
- # Append to List and Clear Values
- batch.append(finalJAString)
- speaker = ""
-
- # Translate Batch if Full
- if len(batch) == BATCHSIZE:
- # Translate
- response = translateGPT(batch, textHistory, True)
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- translatedBatch = response[0]
- textHistory = translatedBatch[-10:]
-
- # Set Values
- if len(batch) == len(translatedBatch):
- i = batchStartIndex
- insertBool = True
-
- # Mismatch
- else:
- pbar.write(f"Mismatch: {batchStartIndex} - {i}")
- MISMATCH.append(batch)
- batchStartIndex = i
- batch.clear()
-
- i += 1
- if insertBool is True:
- pbar.update(1)
- currentGroup = []
-
- # [Passthrough 2] Setting Data
- else:
- # Get Text
- translatedText = translatedBatch[0]
- translatedText = translatedText.replace('\\"', '"')
- translatedText = translatedText.replace("[", "(")
- translatedText = translatedText.replace("]", ")")
-
- # Remove added speaker
- translatedText = re.sub(r"^.+?:\s", "", translatedText)
-
- # Textwrap
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
- textList = translatedText.split("\n")
-
- # Set Text
- data[i] = r"\d\n"
- for line in textList:
- # Wordwrap Text
- if "[r]" not in line:
- line = dazedwrap.wrapText(line, width=WIDTH)
- line = line.replace("\n", "[r]")
-
- # Set
- data.insert(i, line.strip() + "[r]\n")
- i += 1
- data[i - 1] = data[i - 1].replace("[r]", "[pcms]")
- translatedBatch.pop(0)
- speaker = ""
- currentGroup = []
-
- # If Batch is empty. Move on.
- if len(translatedBatch) == 0:
- insertBool = False
- batchStartIndex = i
- batch.clear()
-
- # Nothing relevant. Skip Line.
- else:
- i += 1
- if insertBool is True:
- pbar.update(1)
-
- # Translate Batch if not empty and EOF
- if len(batch) != 0 and i >= len(data):
- # Translate
- response = translateGPT(batch, textHistory, True)
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- translatedBatch = response[0]
- textHistory = translatedBatch[-10:]
-
- # Set Values
- if len(batch) == len(translatedBatch):
- i = batchStartIndex
- insertBool = True
-
- # Mismatch
- else:
- pbar.write(f"Mismatch: {batchStartIndex} - {i}")
- MISMATCH.append(batch)
- batchStartIndex = i
- batch.clear()
-
- currentGroup = []
- return tokens
-
-
-# Save some money and enter the character before translation
-def getSpeaker(speaker):
- match speaker:
- case "央":
- return ["Akira", [0, 0]]
- case "累":
- return ["Rui", [0, 0]]
- case "梨里":
- return ["Riri", [0, 0]]
- case "純":
- return ["Jun", [0, 0]]
- case "美鈴":
- return ["Misuzu", [0, 0]]
- case "須田":
- return ["Suda", [0, 0]]
- case "高橋":
- return ["Takahashi", [0, 0]]
- case "勇二":
- return ["Yuuji", [0, 0]]
- case _:
- return translateGPT(
- speaker,
- "Reply with only the " + LANGUAGE + " translation of the NPC name.",
- False,
- )
-
-
-def subVars(jaString):
- jaString = jaString.replace("\u3000", " ")
-
- # Nested
- count = 0
- nestedList = re.findall(r"[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]", jaString)
- nestedList = set(nestedList)
- if len(nestedList) != 0:
- for icon in nestedList:
- jaString = jaString.replace(icon, "{Nested_" + str(count) + "}")
- count += 1
-
- # Icons
- count = 0
- iconList = re.findall(r"[\\]+[iIkKwWaA]+\[[0-9]+\]", jaString)
- iconList = set(iconList)
- if len(iconList) != 0:
- for icon in iconList:
- jaString = jaString.replace(icon, "{Ascii_" + str(count) + "}")
- count += 1
-
- # Colors
- count = 0
- colorList = re.findall(r"[\\]+[cC]\[[0-9]+\]", jaString)
- colorList = set(colorList)
- if len(colorList) != 0:
- for color in colorList:
- jaString = jaString.replace(color, "{Color_" + str(count) + "}")
- count += 1
-
- # Names
- count = 0
- nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
- nameList = set(nameList)
- if len(nameList) != 0:
- for name in nameList:
- jaString = jaString.replace(name, "{Noun_" + str(count) + "}")
- count += 1
-
- # Variables
- count = 0
- varList = re.findall(r"[\\]+[vV]\[[0-9]+\]", jaString)
- varList = set(varList)
- if len(varList) != 0:
- for var in varList:
- jaString = jaString.replace(var, "{Var_" + str(count) + "}")
- count += 1
-
- # Formatting
- count = 0
- formatList = re.findall(r"[\\]+[\w]+\[.+?\]", jaString)
- formatList = set(formatList)
- if len(formatList) != 0:
- for var in formatList:
- jaString = jaString.replace(var, "{FCode_" + str(count) + "}")
- count += 1
-
- # Put all lists in list and return
- allList = [nestedList, iconList, colorList, nameList, varList, formatList]
- return [jaString, allList]
-
-
-def resubVars(translatedText, allList):
- # Fix Spacing and ChatGPT Nonsense
- matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
- if len(matchList) > 0:
- for match in matchList:
- text = match.strip()
- translatedText = translatedText.replace(match, text)
-
- # Nested
- count = 0
- if len(allList[0]) != 0:
- for var in allList[0]:
- translatedText = translatedText.replace("{Nested_" + str(count) + "}", var)
- count += 1
-
- # Icons
- count = 0
- if len(allList[1]) != 0:
- for var in allList[1]:
- translatedText = translatedText.replace("{Ascii_" + str(count) + "}", var)
- count += 1
-
- # Colors
- count = 0
- if len(allList[2]) != 0:
- for var in allList[2]:
- translatedText = translatedText.replace("{Color_" + str(count) + "}", var)
- count += 1
-
- # Names
- count = 0
- if len(allList[3]) != 0:
- for var in allList[3]:
- translatedText = translatedText.replace("{Noun_" + str(count) + "}", var)
- count += 1
-
- # Vars
- count = 0
- if len(allList[4]) != 0:
- for var in allList[4]:
- translatedText = translatedText.replace("{Var_" + str(count) + "}", var)
- count += 1
-
- # Formatting
- count = 0
- if len(allList[5]) != 0:
- for var in allList[5]:
- translatedText = translatedText.replace("{FCode_" + str(count) + "}", var)
- count += 1
-
- return translatedText
-
-
-def batchList(input_list, batch_size):
- if not isinstance(batch_size, int) or batch_size <= 0:
- raise ValueError("batch_size must be a positive integer")
-
- return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
-
-
-def createContext(fullPromptFlag, subbedT):
- characters = "Game Characters:\n\
-渋江 央 (Shibue Akira) - Male\n\
-蘆名 累 (Ashina Rui) - Female\n\
-清原 梨里 (Kiyohara Riri) - Female\n\
-五十嵐 純 (Igarashi Jun) - Female\n\
-子野日 美鈴 (Nenohi Misuzu) - Female\n\
-須田 (Suda) - Male\n\
-高橋 (Takahashi) - Female\n\
-勇二 (Yuuji) - Male\n\
-"
-
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
-You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
-You are going to be translating text from a videogame.\n\
-I will give you lines of text, and you must translate each line to the best of your ability.\n\
-{VOCAB}\n\
-Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\
-"
- )
- user = f"{subbedT}"
- return characters, system, user
-
-
-def translateText(characters, system, user, history):
- # Prompt
- msg = [{"role": "system", "content": system + characters}]
-
- # Characters
- msg.append({"role": "system", "content": characters})
-
- # History
- if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
- else:
- msg.append({"role": "system", "content": history})
-
- # Content to TL
- msg.append({"role": "user", "content": f"{user}"})
- response = openai.chat.completions.create(
- temperature=0.1,
- frequency_penalty=0.1,
- presence_penalty=0.1,
- model=MODEL,
- messages=msg,
- )
- return response
-
-
-def cleanTranslatedText(translatedText, varResponse):
- placeholders = {
- f"{LANGUAGE} Translation: ": "",
- "Translation: ": "",
- "っ": "",
- "〜": "~",
- "ッ": "",
- "。": ".",
- "Placeholder Text": "",
- # Add more replacements as needed
- }
- for target, replacement in placeholders.items():
- translatedText = translatedText.replace(target, replacement)
-
- translatedText = resubVars(translatedText, varResponse[1])
- return [line for line in translatedText.replace("\\n", "\n").split("\n") if line]
-
-
-def extractTranslation(translatedTextList, is_list):
- pattern = r"`?([\\]*.*?[\\]*?)<\/?Line\d+>`?"
- # If it's a batch (i.e., list), extract with tags; otherwise, return the single item.
- if is_list:
- return [re.findall(pattern, line)[0][1] for line in translatedTextList if re.search(pattern, line)]
- else:
- matchList = re.findall(pattern, translatedTextList)
- return matchList[0][1] if matchList else translatedTextList
-
-
-def countTokens(characters, system, user, history):
- inputTotalTokens = 0
- outputTotalTokens = 0
- enc = tiktoken.encoding_for_model("gpt-4")
-
- # Input
- if isinstance(history, list):
- for line in history:
- inputTotalTokens += len(enc.encode(line))
- else:
- inputTotalTokens += len(enc.encode(history))
- inputTotalTokens += len(enc.encode(system))
- inputTotalTokens += len(enc.encode(characters))
- inputTotalTokens += len(enc.encode(user))
-
- # Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
-
- return [inputTotalTokens, outputTotalTokens]
-
-
-@retry(exceptions=Exception, tries=5, delay=5)
-def translateGPT(text, history, fullPromptFlag):
- totalTokens = [0, 0]
- if isinstance(text, list):
- tList = batchList(text, BATCHSIZE)
- else:
- tList = [text]
-
- for index, tItem in enumerate(tList):
- # Before sending to translation, if we have a list of items, add the formatting
- if isinstance(tItem, list):
- payload = "\n".join([f"`{item}`" for i, item in enumerate(tItem)])
- payload = payload.replace("``", "`Placeholder Text`")
- varResponse = subVars(payload)
- subbedT = varResponse[0]
- else:
- varResponse = subVars(tItem)
- subbedT = varResponse[0]
-
- # Things to Check before starting translation
- if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
- continue
-
- # Create Message
- characters, system, user = createContext(fullPromptFlag, subbedT)
-
- # Calculate Estimate
- if ESTIMATE:
- estimate = countTokens(characters, system, user, history)
- totalTokens[0] += estimate[0]
- totalTokens[1] += estimate[1]
- continue
-
- # Translating
- response = translateText(characters, system, user, history)
- translatedText = response.choices[0].message.content
- totalTokens[0] += response.usage.prompt_tokens
- totalTokens[1] += response.usage.completion_tokens
-
- # Formatting
- translatedTextList = cleanTranslatedText(translatedText, varResponse)
- if isinstance(tItem, list):
- extractedTranslations = extractTranslation(translatedTextList, True)
- tList[index] = extractedTranslations
- if len(tItem) != len(translatedTextList):
- mismatch = True # Just here so breakpoint can be set
- history = extractedTranslations[-10:] # Update history if we have a list
- else:
- # Ensure we're passing a single string to extractTranslation
- extractedTranslations = extractTranslation("\n".join(translatedTextList), False)
- tList[index] = extractedTranslations
-
- # Combine if multilist
- if isinstance(tList[0], list):
- tList = [t for sublist in tList for t in sublist]
-
- # Return
- if format == "json":
- return [tList, totalTokens]
- else:
- return [tList[0], totalTokens]
diff --git a/modules/kirikiri.py b/modules/kirikiri.py
index 5ad74a5..015a417 100644
--- a/modules/kirikiri.py
+++ b/modules/kirikiri.py
@@ -341,7 +341,6 @@ def translateKiriKiri(data, pbar, filename, jobList):
return tokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -359,7 +358,7 @@ def getSpeaker(speaker):
response = translateGPT(
f"{speaker}",
"Reply with the " + LANGUAGE + " translation of the NPC name.",
- True,
+ False,
)
response[0] = response[0].title()
response[0] = response[0].replace("'S", "'s")
@@ -388,11 +387,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -402,9 +458,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -418,9 +473,10 @@ def translateText(system, user, history, penalty, format, model=MODEL):
# History
if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
+ msg.append({"role": "system", "content": "Translation History:"})
+ msg.extend([{"role": "assistant", "content": h} for h in history])
else:
- msg.append({"role": "system", "content": history})
+ msg.append({"role": "assistant", "content": history})
# Response Format
if format == "json":
@@ -455,8 +511,11 @@ def cleanTranslatedText(translatedText):
"】": "]",
"【": "[",
"é": "e",
- "ō": "o",
+ "this guy": "this bastard",
+ "This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
@@ -518,7 +577,7 @@ def countTokens(system, user, history):
inputTotalTokens += len(enc.encode(user))
# Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
+ outputTotalTokens += round(len(enc.encode(user)) * 2.5)
return [inputTotalTokens, outputTotalTokens]
@@ -582,7 +641,8 @@ def translateGPT(text, history, fullPromptFlag):
# AI Refused, Try Again
if not translatedText:
- response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format)
+ response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format, model="gpt-4o")
+ translatedText = response.choices[0].message.content
# Report Tokens
totalTokens[0] += response.usage.prompt_tokens
diff --git a/modules/lune.py b/modules/lune.py
index be4e3fc..279fb0c 100644
--- a/modules/lune.py
+++ b/modules/lune.py
@@ -300,7 +300,6 @@ def translateJSON(data, pbar):
currentGroup = []
return tokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -318,7 +317,7 @@ def getSpeaker(speaker):
response = translateGPT(
f"{speaker}",
"Reply with the " + LANGUAGE + " translation of the NPC name.",
- True,
+ False,
)
response[0] = response[0].title()
response[0] = response[0].replace("'S", "'s")
@@ -340,40 +339,6 @@ def getSpeaker(speaker):
return [speaker, [0, 0]]
-def subVars(jaString):
- jaString = jaString.replace("\u3000", " ")
-
- # Formatting
- count = 0
- codeList = re.findall(r"[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]", jaString)
- codeList = set(codeList)
- if len(codeList) != 0:
- for var in codeList:
- jaString = jaString.replace(var, "[FCode_" + str(count) + "]")
- count += 1
-
- # Put all lists in list and return
- return [jaString, codeList]
-
-
-def resubVars(translatedText, codeList):
- # Fix Spacing and ChatGPT Nonsense
- matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
- if len(matchList) > 0:
- for match in matchList:
- text = match.strip()
- translatedText = translatedText.replace(match, text)
-
- # Formatting
- count = 0
- if len(codeList) != 0:
- for var in codeList:
- translatedText = translatedText.replace("[FCode_" + str(count) + "]", var)
- count += 1
-
- return translatedText
-
-
def batchList(input_list, batch_size):
if not isinstance(batch_size, int) or batch_size <= 0:
raise ValueError("batch_size must be a positive integer")
@@ -381,11 +346,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -395,9 +417,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -411,9 +432,10 @@ def translateText(system, user, history, penalty, format, model=MODEL):
# History
if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
+ msg.append({"role": "system", "content": "Translation History:"})
+ msg.extend([{"role": "assistant", "content": h} for h in history])
else:
- msg.append({"role": "system", "content": history})
+ msg.append({"role": "assistant", "content": history})
# Response Format
if format == "json":
@@ -433,7 +455,7 @@ def translateText(system, user, history, penalty, format, model=MODEL):
return response
-def cleanTranslatedText(translatedText, varResponse):
+def cleanTranslatedText(translatedText):
placeholders = {
f"{LANGUAGE} Translation: ": "",
"Translation: ": "",
@@ -444,15 +466,26 @@ def cleanTranslatedText(translatedText, varResponse):
"「": '\\"',
"」": '\\"',
"- ": "-",
+ "—": "―",
+ "】": "]",
+ "【": "[",
+ "é": "e",
+ "this guy": "this bastard",
+ "This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
translatedText = translatedText.replace(target, replacement)
+ # Remove Repeating Characters
+ pattern = re.compile(r"(.)\s*\1(?:\s*\1){" + str(20 - 1) + r",}")
+ translatedText = pattern.sub(lambda match: match.group(0).replace(" ", "")[:20], translatedText)
+
# Elongate Long Dashes (Since GPT Ignores them...)
translatedText = elongateCharacters(translatedText)
- translatedText = resubVars(translatedText, varResponse[1])
return translatedText
@@ -473,6 +506,8 @@ def elongateCharacters(text):
def extractTranslation(translatedTextList, is_list):
try:
+ translatedTextList = re.sub(r'\\"+\"([^,\n}])', r'\\"\1', translatedTextList)
+ translatedTextList = re.sub(r"(?`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -420,9 +476,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -436,9 +491,10 @@ def translateText(system, user, history, penalty, format, model=MODEL):
# History
if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
+ msg.append({"role": "system", "content": "Translation History:"})
+ msg.extend([{"role": "assistant", "content": h} for h in history])
else:
- msg.append({"role": "system", "content": history})
+ msg.append({"role": "assistant", "content": history})
# Response Format
if format == "json":
@@ -473,8 +529,11 @@ def cleanTranslatedText(translatedText):
"】": "]",
"【": "[",
"é": "e",
- "ō": "o",
+ "this guy": "this bastard",
+ "This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
@@ -536,7 +595,7 @@ def countTokens(system, user, history):
inputTotalTokens += len(enc.encode(user))
# Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
+ outputTotalTokens += round(len(enc.encode(user)) * 2.5)
return [inputTotalTokens, outputTotalTokens]
@@ -600,7 +659,8 @@ def translateGPT(text, history, fullPromptFlag):
# AI Refused, Try Again
if not translatedText:
- response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format)
+ response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format, model="gpt-4o")
+ translatedText = response.choices[0].message.content
# Report Tokens
totalTokens[0] += response.usage.prompt_tokens
diff --git a/modules/regex.py b/modules/regex.py
index 9350432..24b6413 100644
--- a/modules/regex.py
+++ b/modules/regex.py
@@ -356,7 +356,6 @@ def translateRegex(data, translatedList):
translateRegex(data, [stringListTL, choiceListTL])
return tokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -403,11 +402,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -417,9 +473,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -474,6 +529,8 @@ def cleanTranslatedText(translatedText):
"this guy": "this bastard",
"This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
@@ -535,7 +592,7 @@ def countTokens(system, user, history):
inputTotalTokens += len(enc.encode(user))
# Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
+ outputTotalTokens += round(len(enc.encode(user)) * 2.5)
return [inputTotalTokens, outputTotalTokens]
@@ -599,7 +656,8 @@ def translateGPT(text, history, fullPromptFlag):
# AI Refused, Try Again
if not translatedText:
- response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format)
+ response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format, model="gpt-4o")
+ translatedText = response.choices[0].message.content
# Report Tokens
totalTokens[0] += response.usage.prompt_tokens
diff --git a/modules/renpy.py b/modules/renpy.py
index 1b51812..5cd3e7c 100644
--- a/modules/renpy.py
+++ b/modules/renpy.py
@@ -280,7 +280,6 @@ def translateRenpy(data, translatedList):
MISMATCH.append(FILENAME)
return tokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -298,7 +297,7 @@ def getSpeaker(speaker):
response = translateGPT(
f"{speaker}",
"Reply with the " + LANGUAGE + " translation of the NPC name.",
- True,
+ False,
)
response[0] = response[0].title()
response[0] = response[0].replace("'S", "'s")
@@ -327,11 +326,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -341,9 +397,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -357,9 +412,10 @@ def translateText(system, user, history, penalty, format, model=MODEL):
# History
if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
+ msg.append({"role": "system", "content": "Translation History:"})
+ msg.extend([{"role": "assistant", "content": h} for h in history])
else:
- msg.append({"role": "system", "content": history})
+ msg.append({"role": "assistant", "content": history})
# Response Format
if format == "json":
@@ -394,8 +450,11 @@ def cleanTranslatedText(translatedText):
"】": "]",
"【": "[",
"é": "e",
- "ō": "o",
+ "this guy": "this bastard",
+ "This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
@@ -457,7 +516,7 @@ def countTokens(system, user, history):
inputTotalTokens += len(enc.encode(user))
# Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
+ outputTotalTokens += round(len(enc.encode(user)) * 2.5)
return [inputTotalTokens, outputTotalTokens]
@@ -521,7 +580,8 @@ def translateGPT(text, history, fullPromptFlag):
# AI Refused, Try Again
if not translatedText:
- response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format)
+ response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format, model="gpt-4o")
+ translatedText = response.choices[0].message.content
# Report Tokens
totalTokens[0] += response.usage.prompt_tokens
diff --git a/modules/rpgmakerace.py b/modules/rpgmakerace.py
index 9639c44..ba858d0 100644
--- a/modules/rpgmakerace.py
+++ b/modules/rpgmakerace.py
@@ -2413,7 +2413,6 @@ def searchSystem(data, pbar):
return totalTokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -2460,11 +2459,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -2474,9 +2530,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
diff --git a/modules/rpgmakermvmz.py b/modules/rpgmakermvmz.py
index adc7efe..faf09f5 100644
--- a/modules/rpgmakermvmz.py
+++ b/modules/rpgmakermvmz.py
@@ -2433,7 +2433,6 @@ def searchSystem(data, pbar):
return totalTokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
diff --git a/modules/rpgmakerplugin.py b/modules/rpgmakerplugin.py
index 75b2314..c9e502c 100644
--- a/modules/rpgmakerplugin.py
+++ b/modules/rpgmakerplugin.py
@@ -586,7 +586,6 @@ def translatePlugin(data, pbar, filename, translatedList):
translatePlugin(data, pbar, filename, [questListTL, customTL])
return tokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -604,7 +603,7 @@ def getSpeaker(speaker):
response = translateGPT(
f"{speaker}",
"Reply with the " + LANGUAGE + " translation of the NPC name.",
- True,
+ False,
)
response[0] = response[0].title()
response[0] = response[0].replace("'S", "'s")
@@ -633,11 +632,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -647,9 +703,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -663,9 +718,10 @@ def translateText(system, user, history, penalty, format, model=MODEL):
# History
if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
+ msg.append({"role": "system", "content": "Translation History:"})
+ msg.extend([{"role": "assistant", "content": h} for h in history])
else:
- msg.append({"role": "system", "content": history})
+ msg.append({"role": "assistant", "content": history})
# Response Format
if format == "json":
@@ -700,8 +756,11 @@ def cleanTranslatedText(translatedText):
"】": "]",
"【": "[",
"é": "e",
- "ō": "o",
+ "this guy": "this bastard",
+ "This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
@@ -763,7 +822,7 @@ def countTokens(system, user, history):
inputTotalTokens += len(enc.encode(user))
# Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
+ outputTotalTokens += round(len(enc.encode(user)) * 2.5)
return [inputTotalTokens, outputTotalTokens]
@@ -827,7 +886,8 @@ def translateGPT(text, history, fullPromptFlag):
# AI Refused, Try Again
if not translatedText:
- response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format)
+ response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format, model="gpt-4o")
+ translatedText = response.choices[0].message.content
# Report Tokens
totalTokens[0] += response.usage.prompt_tokens
diff --git a/modules/sakuranbo.py b/modules/sakuranbo.py
deleted file mode 100644
index f256e75..0000000
--- a/modules/sakuranbo.py
+++ /dev/null
@@ -1,600 +0,0 @@
-import os
-import re
-import util.dazedwrap as dazedwrap
-import threading
-import time
-import traceback
-from pathlib import Path
-
-import openai
-import tiktoken
-from colorama import Fore
-from dotenv import load_dotenv
-from retry import retry
-from tqdm import tqdm
-
-# Open AI
-load_dotenv()
-if os.getenv("api").replace(" ", "") != "":
- openai.base_url = os.getenv("api")
-openai.organization = os.getenv("org")
-openai.api_key = os.getenv("key")
-
-# Globals
-MODEL = os.getenv("model")
-TIMEOUT = int(os.getenv("timeout"))
-LANGUAGE = os.getenv("language").capitalize()
-INPUTAPICOST = 0.002 # Depends on the model https://openai.com/pricing
-OUTPUTAPICOST = 0.002
-PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
-THREADS = int(os.getenv("threads")) # Controls how many threads are working on a single file (May have to drop this)
-LOCK = threading.Lock()
-WIDTH = int(os.getenv("width"))
-LISTWIDTH = int(os.getenv("listWidth"))
-NOTEWIDTH = 40
-MAXHISTORY = 10
-ESTIMATE = ""
-totalTokens = [0, 0]
-NAMESLIST = []
-
-# tqdm Globals
-BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
-POSITION = 0
-LEAVE = False
-
-# Flags
-NAMES = False # Output a list of all the character names found
-BRFLAG = False # If the game uses
instead
-FIXTEXTWRAP = True
-IGNORETLTEXT = False
-
-
-def handleSakuranbo(filename, estimate):
- global ESTIMATE
- totalTokens = [0, 0]
- ESTIMATE = estimate
-
- if estimate:
- start = time.time()
- translatedData = openFiles(filename)
-
- # Print Result
- end = time.time()
- tqdm.write(getResultString(translatedData, end - start, filename))
- if NAMES is True:
- tqdm.write(str(NAMESLIST))
- with LOCK:
- totalTokens[0] += translatedData[1][0]
- totalTokens[1] += translatedData[1][1]
-
- return getResultString(["", totalTokens, None], end - start, "TOTAL")
-
- else:
- try:
- with open("translated/" + filename, "w", encoding="utf-16") as outFile:
- start = time.time()
- translatedData = openFiles(filename)
- outFile.writelines(translatedData[0])
-
- # Print Result
- end = time.time()
- tqdm.write(getResultString(translatedData, end - start, filename))
- with LOCK:
- totalTokens[0] += translatedData[1][0]
- totalTokens[1] += translatedData[1][1]
- except Exception:
- traceback.print_exc()
- return "Fail"
-
- return getResultString(["", totalTokens, None], end - start, "TOTAL")
-
-
-def getResultString(translatedData, translationTime, filename):
- # File Print String
- totalTokenstring = (
- Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
- "[Output: "
- + str(translatedData[1][1])
- + "]" "[Cost: ${:,.4f}".format(((translatedData[1][0] / 1000000) * INPUTAPICOST) + ((translatedData[1][1] / 1000000) * OUTPUTAPICOST))
- + "]"
- )
- timeString = Fore.BLUE + "[" + str(round(translationTime, 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:
- errorString = str(e) + Fore.RED
- return filename + ": " + totalTokenstring + timeString + Fore.RED + " \u2717 " + errorString + Fore.RESET
-
-
-def openFiles(filename):
- with open("files/" + filename, "r", encoding="utf-16") as readFile:
- translatedData = parseTyrano(readFile, filename)
-
- # Delete lines marked for deletion
- finalData = []
- for line in translatedData[0]:
- if line != "\\d\n":
- finalData.append(line)
- translatedData[0] = finalData
-
- return translatedData
-
-
-def parseTyrano(readFile, filename):
- totalTokens = [0, 0]
- totalLines = 0
-
- # Get total for progress bar
- data = readFile.readlines()
- totalLines = len(data)
-
- with tqdm(bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE) as pbar:
- pbar.desc = filename
- pbar.total = totalLines
-
- try:
- response = translateTyrano(data, pbar)
- totalTokens[0] = response[0]
- totalTokens[1] = response[1]
- except Exception as e:
- traceback.print_exc()
- return [data, totalTokens, e]
- return [data, totalTokens, None]
-
-
-def translateTyrano(data, pbar):
- textHistory = []
- maxHistory = MAXHISTORY
- tokens = [0, 0]
- currentGroup = []
- syncIndex = 0
- speaker = ""
- delFlag = False
- global LOCK, ESTIMATE
-
- for i in range(len(data)):
- currentGroup = []
- matchList = []
-
- if syncIndex > i:
- i = syncIndex
-
- if "[▼]" in data[i]:
- data[i] = data[i].replace("[▼]".strip(), "[page]\n")
-
- # If there isn't any Japanese in the text just skip
- if IGNORETLTEXT is True:
- if not re.search(r"[一-龠]+|[ぁ-ゔ]+|[ァ-ヴー]+", data[i]):
- # Keep textHistory list at length maxHistory
- textHistory.append('"' + data[i] + '"')
- if len(textHistory) > maxHistory:
- textHistory.pop(0)
- currentGroup = []
- continue
-
- # Speaker
- matchList = re.findall(r"^\[(.+)\sstorage=.+\]", data[i])
- if len(matchList) == 0:
- matchList = re.findall(r"^\[([^/].+)\]$", data[i])
- if len(matchList) > 0:
- if "主人公" in matchList[0]:
- speaker = "Protagonist"
- elif "思考" in matchList[0]:
- speaker = "Protagonist Inner Thoughts"
- elif "地の文" in matchList[0]:
- speaker = "Narrator"
- elif "マコ" in matchList[0]:
- speaker = "Mako"
- elif "少年" in matchList[0]:
- speaker = "Boy"
- elif "友達" in matchList[0]:
- speaker = "Friend"
- elif "少女" in matchList[0]:
- speaker = "Girl"
- else:
- response = translateGPT(
- matchList[0],
- "Reply with only the " + LANGUAGE + " translation of the NPC name",
- True,
- )
- speaker = response[0]
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- # data[i] = '#' + speaker + '\n'
-
- # Choices
- elif "glink" in data[i]:
- matchList = re.findall(r"\[glink.+text=\"(.+?)\".+", data[i])
- if len(matchList) != 0:
- if len(textHistory) > 0:
- response = translateGPT(
- matchList[0],
- "Past Translated Text: " + textHistory[len(textHistory) - 1] + "\n\nReply in the style of a dialogue option.",
- True,
- )
- else:
- response = translateGPT(matchList[0], "", False)
- translatedText = response[0]
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
-
- # Remove characters that may break scripts
- charList = [".", '"', "\\n"]
- for char in charList:
- translatedText = translatedText.replace(char, "")
-
- # Escape all '
- translatedText = translatedText.replace("\\", "")
- translatedText = translatedText.replace("'", "\\'")
-
- # Set Data
- translatedText = data[i].replace(matchList[0], translatedText.replace(" ", "\u00a0"))
- data[i] = translatedText
-
- # Grab Lines
- matchList = re.findall(r"^([^\n;@*\{\[].+[^;'{}\[]$)", data[i])
- if len(matchList) > 0 and (
- re.search(r"^\[(.+)\sstorage=.+\],", data[i - 1]) or re.search(r"^\[(.+)\]$", data[i - 1]) or re.search(r"^《(.+)》", data[i - 1])
- ):
- currentGroup.append(matchList[0])
- if len(data) > i + 1:
- matchList = re.findall(r"^([^\n;@*\{\[].+[^;'{}\[]$)", data[i + 1])
- while len(matchList) > 0:
- delFlag = True
- data[i] = "\d\n" # \d Marks line for deletion
- i += 1
- matchList = re.findall(r"^([^\n;@*\{\[].+[^;'{}\[]$)", data[i])
- if len(matchList) > 0:
- currentGroup.append(matchList[0])
-
- # Join up 401 groups for better translation.
- if len(currentGroup) > 0:
- finalJAString = " ".join(currentGroup)
-
- # Remove any textwrap
- if FIXTEXTWRAP is True:
- finalJAString = finalJAString.replace("_", " ")
-
- # Check Speaker
- if speaker == "":
- response = translateGPT(finalJAString, textHistory, True)
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- translatedText = response[0]
- textHistory.append('"' + translatedText + '"')
- else:
- response = translateGPT(speaker + ": " + finalJAString, textHistory, True)
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- translatedText = response[0]
- textHistory.append('"' + translatedText + '"')
-
- # Remove added speaker
- translatedText = re.sub(r"^.+:\s?", "", translatedText)
-
- # Set Data
- translatedText = translatedText.replace("ッ", "")
- translatedText = translatedText.replace("っ", "")
- translatedText = translatedText.replace("ー", "")
- translatedText = translatedText.replace('"', "")
- translatedText = translatedText.replace("[", "")
- translatedText = translatedText.replace("]", "")
-
- # Wordwrap Text
- if "_" not in translatedText:
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
- translatedText = translatedText.replace("\n", "_")
-
- # Set
- if delFlag is True:
- data.insert(i, translatedText.strip() + "\n")
- delFlag = False
- else:
- data[i] = translatedText.strip() + "\n"
-
- # Keep textHistory list at length maxHistory
- if len(textHistory) > maxHistory:
- textHistory.pop(0)
- currentGroup = []
- speaker = ""
-
- pbar.update(1)
- if len(data) > i + 1:
- syncIndex = i + 1
- else:
- break
-
- # Grab Lines
- matchList = re.findall(r"(^\[.+\sstorage=.+\](.+)\[/.+\])", data[i])
- if len(matchList) > 0:
- originalLine = matchList[0][0]
- originalText = matchList[0][1]
- currentGroup.append(matchList[0][1])
- if len(data) > i + 1:
- matchList = re.findall(r"^([^\n;@*\{\[].+[^;'{}\[]$)", data[i + 1])
- while len(matchList) > 0:
- delFlag = True
- data[i] = "\d\n" # \d Marks line for deletion
- i += 1
- matchList = re.findall(r"^([^\n;@*\{\[].+[^;'{}\[]$)", data[i])
- if len(matchList) > 0:
- currentGroup.append(matchList[0])
-
- # Join up 401 groups for better translation.
- if len(currentGroup) > 0:
- finalJAString = " ".join(currentGroup)
-
- # Remove any textwrap
- if FIXTEXTWRAP is True:
- finalJAString = finalJAString.replace("_", " ")
-
- # Check Speaker
- if speaker == "":
- response = translateGPT(finalJAString, textHistory, True)
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- translatedText = response[0]
- textHistory.append('"' + translatedText + '"')
- else:
- response = translateGPT(speaker + ": " + finalJAString, textHistory, True)
- tokens[0] += response[1][0]
- tokens[1] += response[1][1]
- translatedText = response[0]
- textHistory.append('"' + translatedText + '"')
-
- # Remove added speaker
- translatedText = re.sub(r"^.+:\s?", "", translatedText)
-
- # Set Data
- translatedText = translatedText.replace("ッ", "")
- translatedText = translatedText.replace("っ", "")
- translatedText = translatedText.replace("ー", "")
- translatedText = translatedText.replace('"', "")
- translatedText = translatedText.replace("[", "")
- translatedText = translatedText.replace("]", "")
-
- # Wordwrap Text
- if "_" not in translatedText:
- translatedText = dazedwrap.wrapText(translatedText, width=WIDTH)
- translatedText = translatedText.replace("\n", "_")
- translatedText = originalLine.replace(originalText, translatedText)
-
- # Set
- if delFlag is True:
- data.insert(i, translatedText.strip() + "\n")
- delFlag = False
- else:
- data[i] = translatedText.strip() + "\n"
-
- # Keep textHistory list at length maxHistory
- if len(textHistory) > maxHistory:
- textHistory.pop(0)
- currentGroup = []
- speaker = ""
-
- pbar.update(1)
- if len(data) > i + 1:
- syncIndex = i + 1
- else:
- break
-
- return tokens
-
-
-def subVars(jaString):
- jaString = jaString.replace("\u3000", " ")
-
- # Nested
- count = 0
- nestedList = re.findall(r"[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]", jaString)
- nestedList = set(nestedList)
- if len(nestedList) != 0:
- for icon in nestedList:
- jaString = jaString.replace(icon, "{Nested_" + str(count) + "}")
- count += 1
-
- # Icons
- count = 0
- iconList = re.findall(r"[\\]+[iIkKwWaA]+\[[0-9]+\]", jaString)
- iconList = set(iconList)
- if len(iconList) != 0:
- for icon in iconList:
- jaString = jaString.replace(icon, "{Ascii_" + str(count) + "}")
- count += 1
-
- # Colors
- count = 0
- colorList = re.findall(r"[\\]+[cC]\[[0-9]+\]", jaString)
- colorList = set(colorList)
- if len(colorList) != 0:
- for color in colorList:
- jaString = jaString.replace(color, "{Color_" + str(count) + "}")
- count += 1
-
- # Names
- count = 0
- nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
- nameList = set(nameList)
- if len(nameList) != 0:
- for name in nameList:
- jaString = jaString.replace(name, "{N_" + str(count) + "}")
- count += 1
-
- # Variables
- count = 0
- varList = re.findall(r"[\\]+[vV]\[[0-9]+\]", jaString)
- varList = set(varList)
- if len(varList) != 0:
- for var in varList:
- jaString = jaString.replace(var, "{Var_" + str(count) + "}")
- count += 1
-
- # Formatting
- count = 0
- if "笑えるよね." in jaString:
- print("t")
- formatList = re.findall(r"[\\]+[\w]+\[.+?\]", jaString)
- formatList = set(formatList)
- if len(formatList) != 0:
- for var in formatList:
- jaString = jaString.replace(var, "{FCode_" + str(count) + "}")
- count += 1
-
- # Put all lists in list and return
- allList = [nestedList, iconList, colorList, nameList, varList, formatList]
- return [jaString, allList]
-
-
-def resubVars(translatedText, allList):
- # Fix Spacing and ChatGPT Nonsense
- matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
- if len(matchList) > 0:
- for match in matchList:
- text = match.strip()
- translatedText = translatedText.replace(match, text)
-
- # Nested
- count = 0
- if len(allList[0]) != 0:
- for var in allList[0]:
- translatedText = translatedText.replace("{Nested_" + str(count) + "}", var)
- count += 1
-
- # Icons
- count = 0
- if len(allList[1]) != 0:
- for var in allList[1]:
- translatedText = translatedText.replace("{Ascii_" + str(count) + "}", var)
- count += 1
-
- # Colors
- count = 0
- if len(allList[2]) != 0:
- for var in allList[2]:
- translatedText = translatedText.replace("{Color_" + str(count) + "}", var)
- count += 1
-
- # Names
- count = 0
- if len(allList[3]) != 0:
- for var in allList[3]:
- translatedText = translatedText.replace("{N_" + str(count) + "}", var)
- count += 1
-
- # Vars
- count = 0
- if len(allList[4]) != 0:
- for var in allList[4]:
- translatedText = translatedText.replace("{Var_" + str(count) + "}", var)
- count += 1
-
- # Formatting
- count = 0
- if len(allList[5]) != 0:
- for var in allList[5]:
- translatedText = translatedText.replace("{FCode_" + str(count) + "}", var)
- count += 1
-
- # Remove Color Variables Spaces
- # if '\\c' in translatedText:
- # translatedText = re.sub(r'\s*(\\+c\[[1-9]+\])\s*', r' \1', translatedText)
- # translatedText = re.sub(r'\s*(\\+c\[0+\])', r'\1', translatedText)
- return translatedText
-
-
-@retry(exceptions=Exception, tries=5, delay=5)
-def translateGPT(t, history, fullPromptFlag):
- # Sub Vars
- varResponse = subVars(t)
- subbedT = varResponse[0]
-
- # If there isn't any Japanese in the text just skip
- if not re.search(r"[一-龠]+|[ぁ-ゔ]+|[ァ-ヴ]+|[\uFF00-\uFFEF]", subbedT):
- return (t, [0, 0])
-
- # If ESTIMATE is True just count this as an execution and return.
- if ESTIMATE:
- enc = tiktoken.encoding_for_model("gpt-4")
- historyRaw = ""
- if isinstance(history, list):
- for line in history:
- historyRaw += line
- else:
- historyRaw = history
-
- inputTotalTokens = len(enc.encode(historyRaw)) + len(enc.encode(PROMPT))
- outputTotalTokens = len(enc.encode(t)) * 2 # Estimating 2x the size of the original text
- totalTokens = [inputTotalTokens, outputTotalTokens]
- return (t, totalTokens)
-
- # Characters
- context = "Game Characters:\
- Character: マコ == Mako - Gender: Female\
- Character: 主人公 == Protagonist - Gender: Male"
-
- # Prompt
- if fullPromptFlag:
- system = PROMPT
- user = "Line to Translate = " + subbedT
- else:
- system = "Output ONLY the " + LANGUAGE + " translation in the following format: `Translation: <" + LANGUAGE.upper() + "_TRANSLATION>`"
- user = "Line to Translate = " + subbedT
-
- # Create Message List
- msg = []
- msg.append({"role": "system", "content": system})
- msg.append({"role": "user", "content": context})
- if isinstance(history, list):
- for line in history:
- msg.append({"role": "user", "content": line})
- else:
- msg.append({"role": "user", "content": history})
- msg.append({"role": "user", "content": user})
-
- response = openai.ChatCompletion.create(
- temperature=0,
- frequency_penalty=0.2,
- presence_penalty=0.2,
- model=MODEL,
- messages=msg,
- request_timeout=TIMEOUT,
- )
-
- # Save Translated Text
- translatedText = response.choices[0].message.content
- totalTokens = [response.usage.prompt_tokens, response.usage.completion_tokens]
-
- # Resub Vars
- translatedText = resubVars(translatedText, varResponse[1])
-
- # Remove Placeholder Text
- translatedText = translatedText.replace(LANGUAGE + " Translation: ", "")
- translatedText = translatedText.replace("Translation: ", "")
- translatedText = translatedText.replace("Line to Translate = ", "")
- translatedText = translatedText.replace("Translation = ", "")
- translatedText = translatedText.replace("Translate = ", "")
- translatedText = translatedText.replace(LANGUAGE + " Translation:", "")
- translatedText = translatedText.replace("Translation:", "")
- translatedText = translatedText.replace("Line to Translate =", "")
- translatedText = translatedText.replace("Translation =", "")
- translatedText = translatedText.replace("Translate =", "")
- translatedText = translatedText.replace("っ", "")
- translatedText = translatedText.replace("ッ", "")
- translatedText = translatedText.replace("ぁ", "")
- translatedText = translatedText.replace("。", ".")
- translatedText = translatedText.replace("、", ",")
- translatedText = translatedText.replace("?", "?")
- translatedText = translatedText.replace("!", "!")
-
- # Return Translation
- if len(translatedText) > 15 * len(t) or "I'm sorry, but I'm unable to assist with that translation" in translatedText:
- raise Exception
- else:
- return [translatedText, totalTokens]
diff --git a/modules/text.py b/modules/text.py
index 0d00965..a207691 100644
--- a/modules/text.py
+++ b/modules/text.py
@@ -269,7 +269,6 @@ def translateTxt(data, translatedList):
translateTxt(data, [stringListTL, choiceListTL])
return tokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -287,7 +286,7 @@ def getSpeaker(speaker):
response = translateGPT(
f"{speaker}",
"Reply with the " + LANGUAGE + " translation of the NPC name.",
- True,
+ False,
)
response[0] = response[0].title()
response[0] = response[0].replace("'S", "'s")
@@ -316,11 +315,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -330,9 +386,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -346,9 +401,10 @@ def translateText(system, user, history, penalty, format, model=MODEL):
# History
if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
+ msg.append({"role": "system", "content": "Translation History:"})
+ msg.extend([{"role": "assistant", "content": h} for h in history])
else:
- msg.append({"role": "system", "content": history})
+ msg.append({"role": "assistant", "content": history})
# Response Format
if format == "json":
@@ -383,8 +439,11 @@ def cleanTranslatedText(translatedText):
"】": "]",
"【": "[",
"é": "e",
- "ō": "o",
+ "this guy": "this bastard",
+ "This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
@@ -446,7 +505,7 @@ def countTokens(system, user, history):
inputTotalTokens += len(enc.encode(user))
# Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
+ outputTotalTokens += round(len(enc.encode(user)) * 2.5)
return [inputTotalTokens, outputTotalTokens]
@@ -510,7 +569,8 @@ def translateGPT(text, history, fullPromptFlag):
# AI Refused, Try Again
if not translatedText:
- response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format)
+ response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format, model="gpt-4o")
+ translatedText = response.choices[0].message.content
# Report Tokens
totalTokens[0] += response.usage.prompt_tokens
diff --git a/modules/tyrano.py b/modules/tyrano.py
index 81873fa..7875626 100644
--- a/modules/tyrano.py
+++ b/modules/tyrano.py
@@ -356,7 +356,6 @@ def translateTyrano(data, translatedList):
translateTyrano(data, [stringListTL, choiceListTL])
return tokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -403,11 +402,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -417,9 +473,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -433,7 +488,7 @@ def translateText(system, user, history, penalty, format, model=MODEL):
# History
if isinstance(history, list):
- msg.append({"role": "assistant", "content": "Translation History:"})
+ msg.append({"role": "system", "content": "Translation History:"})
msg.extend([{"role": "assistant", "content": h} for h in history])
else:
msg.append({"role": "assistant", "content": history})
@@ -474,6 +529,8 @@ def cleanTranslatedText(translatedText):
"this guy": "this bastard",
"This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
@@ -535,7 +592,7 @@ def countTokens(system, user, history):
inputTotalTokens += len(enc.encode(user))
# Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
+ outputTotalTokens += round(len(enc.encode(user)) * 2.5)
return [inputTotalTokens, outputTotalTokens]
@@ -599,7 +656,8 @@ def translateGPT(text, history, fullPromptFlag):
# AI Refused, Try Again
if not translatedText:
- response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format)
+ response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format, model="gpt-4o")
+ translatedText = response.choices[0].message.content
# Report Tokens
totalTokens[0] += response.usage.prompt_tokens
diff --git a/modules/unity.py b/modules/unity.py
index 6c1607b..9b37274 100644
--- a/modules/unity.py
+++ b/modules/unity.py
@@ -269,7 +269,6 @@ def translateUnity(data, pbar, filename, translatedList):
MISMATCH.append(filename)
return tokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -316,11 +315,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -330,9 +386,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -387,6 +442,8 @@ def cleanTranslatedText(translatedText):
"this guy": "this bastard",
"This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
@@ -448,7 +505,7 @@ def countTokens(system, user, history):
inputTotalTokens += len(enc.encode(user))
# Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
+ outputTotalTokens += round(len(enc.encode(user)) * 2.5)
return [inputTotalTokens, outputTotalTokens]
@@ -512,7 +569,8 @@ def translateGPT(text, history, fullPromptFlag):
# AI Refused, Try Again
if not translatedText:
- response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format)
+ response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format, model="gpt-4o")
+ translatedText = response.choices[0].message.content
# Report Tokens
totalTokens[0] += response.usage.prompt_tokens
diff --git a/modules/wolf.py b/modules/wolf.py
index b313e1e..27ee20f 100644
--- a/modules/wolf.py
+++ b/modules/wolf.py
@@ -2563,7 +2563,6 @@ def searchDB(events, pbar, jobList, filename):
return totalTokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -2581,7 +2580,7 @@ def getSpeaker(speaker):
response = translateGPT(
f"{speaker}",
"Reply with the " + LANGUAGE + " translation of the NPC name.",
- True,
+ False,
)
response[0] = response[0].title()
response[0] = response[0].replace("'S", "'s")
@@ -2610,11 +2609,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -2624,9 +2680,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -2640,9 +2695,10 @@ def translateText(system, user, history, penalty, format, model=MODEL):
# History
if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
+ msg.append({"role": "system", "content": "Translation History:"})
+ msg.extend([{"role": "assistant", "content": h} for h in history])
else:
- msg.append({"role": "system", "content": history})
+ msg.append({"role": "assistant", "content": history})
# Response Format
if format == "json":
@@ -2663,36 +2719,36 @@ def translateText(system, user, history, penalty, format, model=MODEL):
def cleanTranslatedText(translatedText):
- if translatedText:
- placeholders = {
- f"{LANGUAGE} Translation: ": "",
- "Translation: ": "",
- "っ": "",
- "〜": "~",
- "ッ": "",
- "。": ".",
- "「": '\\"',
- "」": '\\"',
- "- ": "-",
- "—": "―",
- "】": "]",
- "【": "[",
- "é": "e",
- "ō": "o",
- "Placeholder Text": "",
- # Add more replacements as needed
- }
- for target, replacement in placeholders.items():
- translatedText = translatedText.replace(target, replacement)
+ placeholders = {
+ f"{LANGUAGE} Translation: ": "",
+ "Translation: ": "",
+ "っ": "",
+ "〜": "~",
+ "ッ": "",
+ "。": ".",
+ "「": '\\"',
+ "」": '\\"',
+ "- ": "-",
+ "—": "―",
+ "】": "]",
+ "【": "[",
+ "é": "e",
+ "this guy": "this bastard",
+ "This guy": "This bastard",
+ "Placeholder Text": "",
+ "```json": "",
+ "```": "",
+ # Add more replacements as needed
+ }
+ for target, replacement in placeholders.items():
+ translatedText = translatedText.replace(target, replacement)
- # Remove Repeating Characters
- pattern = re.compile(r"(.)\s*\1(?:\s*\1){" + str(20 - 1) + r",}")
- translatedText = pattern.sub(lambda match: match.group(0).replace(" ", "")[:20], translatedText)
+ # Remove Repeating Characters
+ pattern = re.compile(r"(.)\s*\1(?:\s*\1){" + str(20 - 1) + r",}")
+ translatedText = pattern.sub(lambda match: match.group(0).replace(" ", "")[:20], translatedText)
- # Elongate Long Dashes (Since GPT Ignores them...)
- translatedText = elongateCharacters(translatedText)
- else:
- print(translatedText)
+ # Elongate Long Dashes (Since GPT Ignores them...)
+ translatedText = elongateCharacters(translatedText)
return translatedText
@@ -2743,7 +2799,7 @@ def countTokens(system, user, history):
inputTotalTokens += len(enc.encode(user))
# Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
+ outputTotalTokens += round(len(enc.encode(user)) * 2.5)
return [inputTotalTokens, outputTotalTokens]
@@ -2800,14 +2856,15 @@ def translateGPT(text, history, fullPromptFlag):
continue
# Translating
- response = translateText(system, user, history, 0.05, format, model="gpt-4o")
+ response = translateText(system, user, history, 0.05, format)
# Set Tokens
translatedText = response.choices[0].message.content
# AI Refused, Try Again
if not translatedText:
- response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format)
+ response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format, model="gpt-4o")
+ translatedText = response.choices[0].message.content
# Report Tokens
totalTokens[0] += response.usage.prompt_tokens
diff --git a/modules/wolf2.py b/modules/wolf2.py
index cd046ee..2d66eb7 100644
--- a/modules/wolf2.py
+++ b/modules/wolf2.py
@@ -310,7 +310,6 @@ def translateWOLF(data, translatedList, pbar, filename):
MISMATCH.append(filename)
return tokens
-
# Save some money and enter the character before translation
def getSpeaker(speaker):
match speaker:
@@ -328,7 +327,7 @@ def getSpeaker(speaker):
response = translateGPT(
f"{speaker}",
"Reply with the " + LANGUAGE + " translation of the NPC name.",
- True,
+ False,
)
response[0] = response[0].title()
response[0] = response[0].replace("'S", "'s")
@@ -357,11 +356,68 @@ def batchList(input_list, batch_size):
return [input_list[i : i + batch_size] for i in range(0, len(input_list), batch_size)]
+def parseVocabWithCategories(vocabText):
+ """Parse vocabulary text and extract terms with their categories."""
+ pairs = []
+ seen = set()
+ currentCategory = None
+
+ for line in vocabText.splitlines():
+ line = line.strip()
+ if not line or line.startswith('```'):
+ continue
+
+ # Check if this is a category header
+ if line.startswith('#'):
+ currentCategory = line
+ continue
+
+ # Parse vocabulary term
+ m = re.match(r'^(.+?)(?:\s?[\(–])', line) # term is everything before space + '(' or '–'
+ if m:
+ term = m.group(1)
+ if term not in seen:
+ pairs.append((term, line, currentCategory))
+ seen.add(term)
+
+ return pairs
+
+
+def buildMatchedVocabText(vocabPairs, subbedT):
+ """Build formatted vocabulary text with matched terms organized by category."""
+ matchedCategories = {}
+
+ # Use word boundaries for Japanese if appropriate, or allow substring as before.
+ for term, line, category in vocabPairs:
+ # "term in subbedT" could be false positive; can use regex but Japanese doesn't always have spaces.
+ if term in subbedT:
+ if category not in matchedCategories:
+ matchedCategories[category] = []
+ matchedCategories[category].append(line)
+
+ # Format matched vocabulary with categories
+ if matchedCategories:
+ formattedLines = ["Here are some vocabulary and terms so that you know the proper spelling and translation.\n"]
+ for category, lines in matchedCategories.items():
+ if category: # Only add category header if it exists
+ formattedLines.append(category)
+ formattedLines.extend(lines)
+ formattedLines.append("") # Add blank line between categories
+ matchedVocabText = f"```\n{chr(10).join(formattedLines).rstrip()}\n```"
+ else:
+ matchedVocabText = ""
+
+ return matchedVocabText
+
+
def createContext(fullPromptFlag, subbedT, format):
- system = (
- PROMPT + VOCAB
- if fullPromptFlag
- else f"\
+ vocabPairs = parseVocabWithCategories(VOCAB)
+ matchedVocabText = buildMatchedVocabText(vocabPairs, subbedT)
+
+ if fullPromptFlag:
+ system = PROMPT + matchedVocabText
+ else:
+ system = f"\
You are an expert Eroge Game translator who translates Japanese text to {LANGUAGE}.\n\
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\n\
- You translate everything, including erotic and explicit content.\n\
@@ -371,9 +427,8 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
- Maintain any spacing in the translation.\n\
- Maintain any code text in brackets if given. (e.g `[Color_0]`, `[Ascii_0]`, `[FCode_1`], etc)\n\
- `...` can be a part of the dialogue. Translate it as it is.\n\
-{VOCAB}\n\
+{matchedVocabText}\n\
"
- )
if format == "json":
user = f"```json\n{subbedT}\n```"
else:
@@ -387,9 +442,10 @@ def translateText(system, user, history, penalty, format, model=MODEL):
# History
if isinstance(history, list):
- msg.extend([{"role": "system", "content": h} for h in history])
+ msg.append({"role": "system", "content": "Translation History:"})
+ msg.extend([{"role": "assistant", "content": h} for h in history])
else:
- msg.append({"role": "system", "content": history})
+ msg.append({"role": "assistant", "content": history})
# Response Format
if format == "json":
@@ -424,8 +480,11 @@ def cleanTranslatedText(translatedText):
"】": "]",
"【": "[",
"é": "e",
- "ō": "o",
+ "this guy": "this bastard",
+ "This guy": "This bastard",
"Placeholder Text": "",
+ "```json": "",
+ "```": "",
# Add more replacements as needed
}
for target, replacement in placeholders.items():
@@ -487,7 +546,7 @@ def countTokens(system, user, history):
inputTotalTokens += len(enc.encode(user))
# Output
- outputTotalTokens += round(len(enc.encode(user)) * 3)
+ outputTotalTokens += round(len(enc.encode(user)) * 2.5)
return [inputTotalTokens, outputTotalTokens]
@@ -551,7 +610,8 @@ def translateGPT(text, history, fullPromptFlag):
# AI Refused, Try Again
if not translatedText:
- response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format)
+ response = translateText(f"{system}\n You translate ALL content.", user, history, 0.1, format, model="gpt-4o")
+ translatedText = response.choices[0].message.content
# Report Tokens
totalTokens[0] += response.usage.prompt_tokens