Format HELL
This commit is contained in:
parent
31c8007055
commit
9fac989f7a
21 changed files with 6392 additions and 4273 deletions
335
modules/alice.py
335
modules/alice.py
|
|
@ -15,52 +15,53 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .01
|
||||
OUTPUTAPICOST = .03
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.01
|
||||
OUTPUTAPICOST = 0.03
|
||||
BATCHSIZE = 1
|
||||
|
||||
|
||||
def handleAlice(filename, estimate):
|
||||
global ESTIMATE
|
||||
totalTokens = [0,0]
|
||||
totalTokens = [0, 0]
|
||||
ESTIMATE = estimate
|
||||
|
||||
if estimate:
|
||||
|
|
@ -75,17 +76,19 @@ def handleAlice(filename, estimate):
|
|||
totalTokens[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', totalTokens, None], end - start, '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
|
||||
return (
|
||||
totalString + Fore.RED + f"\nMismatch Errors: {MISMATCH}" + Fore.RESET
|
||||
)
|
||||
else:
|
||||
return totalString
|
||||
|
||||
|
||||
else:
|
||||
try:
|
||||
with open('translated/' + filename, 'w', encoding='UTF-8') as outFile:
|
||||
with open("translated/" + filename, "w", encoding="UTF-8") as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -98,29 +101,43 @@ def handleAlice(filename, estimate):
|
|||
totalTokens[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", totalTokens, None], end - start, "TOTAL")
|
||||
|
||||
return getResultString(['', totalTokens, None], end - start, 'TOTAL')
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='UTF-8') as f:
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -129,19 +146,30 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
|
||||
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
|
||||
|
||||
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]
|
||||
|
|
@ -151,6 +179,7 @@ def parseText(data, filename):
|
|||
return [linesList, totalTokens, e]
|
||||
return [linesList, totalTokens, None]
|
||||
|
||||
|
||||
# Grab scenario data from text file
|
||||
def translateLines(linesList, pbar):
|
||||
currentGroup = []
|
||||
|
|
@ -165,54 +194,67 @@ def translateLines(linesList, pbar):
|
|||
try:
|
||||
while i < len(linesList):
|
||||
# Check if Proper Message
|
||||
match = re.findall(r's\[[0-9]+\] = \"(.*)\"', linesList[i])
|
||||
match = re.findall(r"s\[[0-9]+\] = \"(.*)\"", linesList[i])
|
||||
if len(match) > 0:
|
||||
jaString = match[0]
|
||||
|
||||
# Skip Files
|
||||
if '/' in jaString:
|
||||
if "/" in jaString:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
### Translate
|
||||
# Remove any textwrap
|
||||
jaString = re.sub(r'\\n', ' ', jaString)
|
||||
jaString = re.sub(r"\\n", " ", jaString)
|
||||
|
||||
# Grab Speaker
|
||||
speakerMatch = re.findall(r's\[[0-9]+\] = \"([^/]+)\"', linesList[i-1])
|
||||
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]:
|
||||
if (
|
||||
re.search(r"[一-龠]+|[ぁ-ゔ]+|[ァ-ヴー]+", jaString)
|
||||
and "_" not in speakerMatch[0]
|
||||
):
|
||||
speaker = speakerMatch[0]
|
||||
else:
|
||||
speaker = ''
|
||||
speaker = ""
|
||||
else:
|
||||
speaker = ''
|
||||
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(';', '')
|
||||
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):
|
||||
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])
|
||||
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(';', '')
|
||||
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}'
|
||||
finalJAString = " ".join(currentGroup)
|
||||
if speaker != "":
|
||||
finalJAString = f"{speaker}: {finalJAString}"
|
||||
else:
|
||||
finalJAString = f'{finalJAString}'
|
||||
finalJAString = f"{finalJAString}"
|
||||
|
||||
# [Passthrough 1] Pulling From File
|
||||
if insertBool is False:
|
||||
|
|
@ -235,7 +277,7 @@ def translateLines(linesList, pbar):
|
|||
|
||||
# Mismatch
|
||||
else:
|
||||
pbar.write(f'Mismatch: {batchStartIndex} - {i}')
|
||||
pbar.write(f"Mismatch: {batchStartIndex} - {i}")
|
||||
MISMATCH.append(batch)
|
||||
batchStartIndex = i
|
||||
batch.clear()
|
||||
|
|
@ -249,33 +291,41 @@ def translateLines(linesList, pbar):
|
|||
translatedText = translatedBatch[0]
|
||||
|
||||
# Remove added speaker and quotes
|
||||
translatedText = re.sub(r'^.+?:\s', '', translatedText)
|
||||
translatedText = re.sub(r"^.+?:\s", "", translatedText)
|
||||
|
||||
# Textwrap
|
||||
translatedText = translatedText.replace('\"', '\\"')
|
||||
translatedText = translatedText.replace('"', '\\"')
|
||||
translatedText = textwrap.fill(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(';', '')
|
||||
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)
|
||||
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(';', '')
|
||||
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)
|
||||
translatedBatch.pop(0)
|
||||
|
||||
# If Batch is empty. Move on.
|
||||
if len(translatedBatch) == 0:
|
||||
|
|
@ -283,7 +333,7 @@ def translateLines(linesList, pbar):
|
|||
batchStartIndex = i
|
||||
pbar.update(1)
|
||||
batch.clear()
|
||||
|
||||
|
||||
currentGroup = []
|
||||
else:
|
||||
if insertBool is True:
|
||||
|
|
@ -295,70 +345,72 @@ def translateLines(linesList, pbar):
|
|||
traceback.print_exc()
|
||||
return [linesList, tokens]
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(icon, "{Nested_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(icon, "{Ascii_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'[\\]+[cC]\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(color, "{Color_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '{Noun_' + str(count) + '}')
|
||||
jaString = jaString.replace(name, "{Noun_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(var, "{Var_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[.+?\]', jaString)
|
||||
formatList = re.findall(r"[\\]+[\w]+\[.+?\]", jaString)
|
||||
formatList = set(formatList)
|
||||
if len(formatList) != 0:
|
||||
for var in formatList:
|
||||
jaString = jaString.replace(var, '{FCode_' + str(count) + '}')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -368,54 +420,58 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('{Nested_' + str(count) + '}', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
林つかさ (Tsukasa Hayashi) - Female\n\
|
||||
山田美兎 (Miyato Yamada) - Female\n\
|
||||
鈴木赤音 (Akane Suzuki) - Female\n\
|
||||
|
|
@ -428,13 +484,17 @@ def createContext(fullPromptFlag, subbedT):
|
|||
モリー・ボイド (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}'
|
||||
"
|
||||
|
||||
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}]
|
||||
|
|
@ -447,9 +507,9 @@ def translateText(characters, system, user, history):
|
|||
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}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0.1,
|
||||
frequency_penalty=0.1,
|
||||
|
|
@ -458,40 +518,47 @@ def translateText(characters, system, user, history):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'Placeholder Text': ''
|
||||
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]
|
||||
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]
|
||||
return [line for line in translatedText.split("\\n") if line]
|
||||
|
||||
|
||||
def extractTranslation(translatedTextList, is_list):
|
||||
pattern = r'<Line(\d+)>[\\]*`?(.*?)[\\]*?`?</?Line\d+>'
|
||||
pattern = r"<Line(\d+)>[\\]*`?(.*?)[\\]*?`?</?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)]
|
||||
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')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -503,15 +570,17 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
totalTokens = [0, 0]
|
||||
|
|
@ -523,8 +592,10 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
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'<Line{i}>`{item}`</Line{i}>' for i, item in enumerate(tItem)])
|
||||
payload = payload.replace('``', '`Placeholder Text`')
|
||||
payload = "\n".join(
|
||||
[f"<Line{i}>`{item}`</Line{i}>" for i, item in enumerate(tItem)]
|
||||
)
|
||||
payload = payload.replace("``", "`Placeholder Text`")
|
||||
varResponse = subVars(payload)
|
||||
subbedT = varResponse[0]
|
||||
else:
|
||||
|
|
@ -532,7 +603,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
continue
|
||||
|
||||
# Create Message
|
||||
|
|
@ -557,11 +628,13 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
extractedTranslations = extractTranslation(translatedTextList, True)
|
||||
tList[index] = extractedTranslations
|
||||
if len(tItem) != len(translatedTextList):
|
||||
mismatch = True # Just here so breakpoint can be set
|
||||
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)
|
||||
extractedTranslations = extractTranslation(
|
||||
"\n".join(translatedTextList), False
|
||||
)
|
||||
tList[index] = extractedTranslations
|
||||
|
||||
finalList = combineList(tList, text)
|
||||
|
|
|
|||
288
modules/anim.py
288
modules/anim.py
|
|
@ -16,52 +16,53 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .01
|
||||
OUTPUTAPICOST = .03
|
||||
BATCHSIZE = 50
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.01
|
||||
OUTPUTAPICOST = 0.03
|
||||
BATCHSIZE = 50
|
||||
|
||||
|
||||
def handleAnim(filename, estimate):
|
||||
global ESTIMATE
|
||||
totalTokens = [0,0]
|
||||
totalTokens = [0, 0]
|
||||
ESTIMATE = estimate
|
||||
|
||||
if estimate:
|
||||
|
|
@ -76,17 +77,19 @@ def handleAnim(filename, estimate):
|
|||
totalTokens[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', totalTokens, None], end - start, '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
|
||||
return (
|
||||
totalString + Fore.RED + f"\nMismatch Errors: {MISMATCH}" + Fore.RESET
|
||||
)
|
||||
else:
|
||||
return totalString
|
||||
|
||||
|
||||
else:
|
||||
try:
|
||||
with open('translated/' + filename, 'w', encoding='UTF-8') as outFile:
|
||||
with open("translated/" + filename, "w", encoding="UTF-8") as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -98,36 +101,50 @@ def handleAnim(filename, estimate):
|
|||
totalTokens[0] += translatedData[1][0]
|
||||
totalTokens[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", totalTokens, None], end - start, "TOTAL")
|
||||
|
||||
return getResultString(['', totalTokens, None], end - start, 'TOTAL')
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='UTF-8-sig') as f:
|
||||
with open("files/" + filename, "r", encoding="UTF-8-sig") as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Map Files
|
||||
if '.json' in filename:
|
||||
if ".json" in filename:
|
||||
translatedData = parseJSON(data, filename)
|
||||
|
||||
else:
|
||||
raise NameError(filename + ' Not Supported')
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -136,20 +153,31 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
|
||||
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)]
|
||||
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
|
||||
|
||||
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]
|
||||
|
|
@ -159,6 +187,7 @@ def parseJSON(data, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateJSON(keys, data, pbar):
|
||||
translatedBatch = []
|
||||
textHistory = []
|
||||
|
|
@ -172,20 +201,20 @@ def translateJSON(keys, data, pbar):
|
|||
needTL = False
|
||||
for i in range(len(batch)):
|
||||
t = data[batch[i]]
|
||||
if re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', t) or t == '':
|
||||
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
|
||||
continue
|
||||
|
||||
# Remove any textwrap and Furigana
|
||||
for i in range(len(batch)):
|
||||
if FIXTEXTWRAP == True:
|
||||
# Textwrap
|
||||
data[originalBatch[i]] = data[originalBatch[i]].replace('@b', ' ')
|
||||
data[originalBatch[i]] = data[originalBatch[i]].replace("@b", " ")
|
||||
|
||||
# Furigana
|
||||
rcodeMatch = re.findall(r'(@\[(.+?):.+?\])', batch[i])
|
||||
rcodeMatch = re.findall(r"(@\[(.+?):.+?\])", batch[i])
|
||||
if len(rcodeMatch) > 0:
|
||||
for match in rcodeMatch:
|
||||
batch[i] = batch[i].replace(match[0], match[1])
|
||||
|
|
@ -203,23 +232,22 @@ def translateJSON(keys, data, pbar):
|
|||
# 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)
|
||||
translatedText = re.sub(r"^.+?\s\|\s?", "", translatedText)
|
||||
|
||||
# Textwrap
|
||||
if '@n' in translatedText:
|
||||
match = re.search(r'.*@n(.*)', translatedText)
|
||||
if "@n" in translatedText:
|
||||
match = re.search(r".*@n(.*)", translatedText)
|
||||
if match != None:
|
||||
tlText = match.group(1)
|
||||
tlText = textwrap.fill(tlText, width=WIDTH)
|
||||
tlText = tlText.replace('\n', '@b')
|
||||
tlText = tlText.replace("\n", "@b")
|
||||
translatedText = translatedText.replace(match.group(1), tlText)
|
||||
|
||||
elif '@b' not in translatedText:
|
||||
|
||||
elif "@b" not in translatedText:
|
||||
translatedText = textwrap.fill(translatedText, width=WIDTH)
|
||||
translatedText = translatedText.replace('\n', '@b')
|
||||
translatedText = translatedText.replace("\n", "@b")
|
||||
|
||||
# Set Data
|
||||
data[originalBatch[i]] = translatedText
|
||||
|
|
@ -232,72 +260,74 @@ def translateJSON(keys, data, pbar):
|
|||
continue
|
||||
pbar.update(1)
|
||||
|
||||
return tokens
|
||||
return tokens
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'[\\]+[cC]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(color, "[Color_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '[Noun_' + str(count) + ']')
|
||||
jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(var, "[Var_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]', jaString)
|
||||
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) + ']')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -307,64 +337,70 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('[Nested_' + str(count) + ']', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
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"\
|
||||
"
|
||||
|
||||
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\
|
||||
|
|
@ -376,9 +412,11 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
|
|||
- `...` can be a part of the dialogue. Translate it as it is.\n\
|
||||
{VOCAB}\n\
|
||||
"
|
||||
user = f'{subbedT}'
|
||||
)
|
||||
user = f"{subbedT}"
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history, penalty):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -391,9 +429,9 @@ def translateText(characters, system, user, history, penalty):
|
|||
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}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0,
|
||||
frequency_penalty=penalty,
|
||||
|
|
@ -402,18 +440,19 @@ def translateText(characters, system, user, history, penalty):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'Placeholder Text': '',
|
||||
'é' : 'e',
|
||||
'—' : '-',
|
||||
'ū' : 'u',
|
||||
f"{LANGUAGE} Translation: ": "",
|
||||
"Translation: ": "",
|
||||
"っ": "",
|
||||
"〜": "~",
|
||||
"ッ": "",
|
||||
"。": ".",
|
||||
"Placeholder Text": "",
|
||||
"é": "e",
|
||||
"—": "-",
|
||||
"ū": "u",
|
||||
# Add more replacements as needed
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
|
|
@ -424,11 +463,12 @@ def cleanTranslatedText(translatedText, varResponse):
|
|||
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'(?<=(.))ー+'
|
||||
|
||||
pattern = r"(?<=(.))ー+"
|
||||
|
||||
# Define a replacement function that elongates the captured character
|
||||
def repl(match):
|
||||
char = match.group(1) # The character before the ー sequence
|
||||
|
|
@ -438,8 +478,9 @@ def elongateCharacters(text):
|
|||
# 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+>`?'
|
||||
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)
|
||||
|
|
@ -448,11 +489,12 @@ def extractTranslation(translatedTextList, is_list):
|
|||
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')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -464,15 +506,17 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
mismatch = False
|
||||
|
|
@ -485,8 +529,12 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
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'`<Line{i}>{item}</Line{i}>`' for i, item in enumerate(tItem)])
|
||||
payload = re.sub(r'(<Line\d+)(><)(\/Line\d+>)', r'\1>Placeholder Text<\3', payload)
|
||||
payload = "\n".join(
|
||||
[f"`<Line{i}>{item}</Line{i}>`" for i, item in enumerate(tItem)]
|
||||
)
|
||||
payload = re.sub(
|
||||
r"(<Line\d+)(><)(\/Line\d+>)", r"\1>Placeholder Text<\3", payload
|
||||
)
|
||||
varResponse = subVars(payload)
|
||||
subbedT = varResponse[0]
|
||||
else:
|
||||
|
|
@ -494,7 +542,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
continue
|
||||
|
||||
# Create Message
|
||||
|
|
@ -531,11 +579,13 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
tList[index] = extractedTranslations
|
||||
if len(tItem) != len(extractedTranslations):
|
||||
mismatch = True # Just here for breakpoint
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
# Create History
|
||||
if not mismatch:
|
||||
history = extractedTranslations[-10:] # Update history if we have a list
|
||||
history = extractedTranslations[
|
||||
-10:
|
||||
] # Update history if we have a list
|
||||
else:
|
||||
history = text[-10:]
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -14,38 +14,41 @@ 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')
|
||||
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 = .002 # Depends on the model https://openai.com/pricing
|
||||
OUTPUTAPICOST = .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)
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 40
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
totalTokens = [0, 0]
|
||||
NAMESLIST = []
|
||||
|
||||
#tqdm Globals
|
||||
BAR_FORMAT='{l_bar}{bar:10}{r_bar}{bar:-10b}'
|
||||
POSITION=0
|
||||
LEAVE=False
|
||||
# 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
|
||||
|
|
@ -61,11 +64,11 @@ def handleAtelier(filename, estimate):
|
|||
totalTokens[0] += translatedData[1][0]
|
||||
totalTokens[1] += translatedData[1][1]
|
||||
|
||||
return getResultString(['', totalTokens, None], end - start, 'TOTAL')
|
||||
return getResultString(["", totalTokens, None], end - start, "TOTAL")
|
||||
|
||||
else:
|
||||
try:
|
||||
with open('translated/' + filename, 'w', encoding='utf-8') as outFile:
|
||||
with open("translated/" + filename, "w", encoding="utf-8") as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
outFile.writelines(translatedData[0])
|
||||
|
|
@ -77,29 +80,43 @@ def handleAtelier(filename, estimate):
|
|||
totalTokens[0] += translatedData[1][0]
|
||||
totalTokens[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", totalTokens, None], end - start, "TOTAL")
|
||||
|
||||
return getResultString(['', totalTokens, None], end - start, 'TOTAL')
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='UTF-8') as f:
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] is None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -107,9 +124,18 @@ def getResultString(translatedData, translationTime, filename):
|
|||
raise translatedData[2]
|
||||
except Exception as e:
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def parseText(data, filename):
|
||||
totalLines = 0
|
||||
global LOCK
|
||||
|
|
@ -117,10 +143,12 @@ def parseText(data, filename):
|
|||
# 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
|
||||
|
||||
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:
|
||||
|
|
@ -128,32 +156,37 @@ def parseText(data, filename):
|
|||
return [linesList, 0, e]
|
||||
return [response[0], response[1], None]
|
||||
|
||||
|
||||
def translateText(data, pbar):
|
||||
textHistory = []
|
||||
maxHistory = MAXHISTORY
|
||||
totalTokens = [0,0]
|
||||
totalTokens = [0, 0]
|
||||
syncIndex = 0
|
||||
|
||||
for i in range(len(data)):
|
||||
if syncIndex > i:
|
||||
i = syncIndex
|
||||
|
||||
match = re.findall(r'◆.+◆(.+)', data[i])
|
||||
match = re.findall(r"◆.+◆(.+)", data[i])
|
||||
if len(match) > 0:
|
||||
jaString = match[0]
|
||||
|
||||
### Translate
|
||||
# Remove any textwrap
|
||||
finalJAString = re.sub(r'\\n', ' ', jaString)
|
||||
|
||||
finalJAString = re.sub(r"\\n", " ", jaString)
|
||||
|
||||
# Translate
|
||||
response = translateGPT(finalJAString, 'Previous Text for Context: ' + ' '.join(textHistory), True)
|
||||
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 + '\"')
|
||||
textHistory.append('"' + translatedText + '"')
|
||||
|
||||
# Keep textHistory list at length maxHistory
|
||||
if len(textHistory) > maxHistory:
|
||||
|
|
@ -161,81 +194,83 @@ def translateText(data, pbar):
|
|||
|
||||
# Textwrap
|
||||
translatedText = textwrap.fill(translatedText, width=WIDTH)
|
||||
translatedText = translatedText.replace('\n', '\\n')
|
||||
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', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(icon, "{Nested_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(icon, "{Ascii_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'[\\]+[cC]\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(color, "{Color_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '{N_' + str(count) + '}')
|
||||
jaString = jaString.replace(name, "{N_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(var, "{Var_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
if '笑えるよね.' in jaString:
|
||||
print('t')
|
||||
formatList = re.findall(r'[\\]+[\w]+\[.+?\]', jaString)
|
||||
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) + '}')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -245,42 +280,42 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('{Nested_' + str(count) + '}', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
translatedText = translatedText.replace("{FCode_" + str(count) + "}", var)
|
||||
count += 1
|
||||
|
||||
# Remove Color Variables Spaces
|
||||
|
|
@ -289,6 +324,7 @@ def resubVars(translatedText, allList):
|
|||
# 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
|
||||
|
|
@ -296,13 +332,13 @@ def translateGPT(t, history, fullPromptFlag):
|
|||
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 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 = ''
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
historyRaw = ""
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
historyRaw += line
|
||||
|
|
@ -310,26 +346,34 @@ def translateGPT(t, history, fullPromptFlag):
|
|||
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
|
||||
outputTotalTokens = (
|
||||
len(enc.encode(t)) * 2
|
||||
) # Estimating 2x the size of the original text
|
||||
totalTokens = [inputTotalTokens, outputTotalTokens]
|
||||
return (t, totalTokens)
|
||||
|
||||
# Characters
|
||||
context = 'Game 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'
|
||||
Character: Surname:尾木 Name:優真 == Surname:Ogi Name:Yuuma - Gender: Male"
|
||||
|
||||
# Prompt
|
||||
if fullPromptFlag:
|
||||
system = PROMPT
|
||||
user = 'Line to Translate = ' + subbedT
|
||||
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
|
||||
system = (
|
||||
"Output ONLY the "
|
||||
+ LANGUAGE
|
||||
+ " translation in the following format: `Translation: <"
|
||||
+ LANGUAGE.upper()
|
||||
+ "_TRANSLATION>`"
|
||||
)
|
||||
user = "Line to Translate = " + subbedT
|
||||
|
||||
# Create Message List
|
||||
msg = []
|
||||
|
|
@ -359,26 +403,29 @@ def translateGPT(t, history, fullPromptFlag):
|
|||
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('!', '!')
|
||||
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:
|
||||
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]
|
||||
return [translatedText, totalTokens]
|
||||
|
|
|
|||
370
modules/csv.py
370
modules/csv.py
|
|
@ -17,63 +17,66 @@ 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')
|
||||
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'))
|
||||
# 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 = int(os.getenv('noteWidth'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = int(os.getenv("noteWidth"))
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
FIXTEXTWRAP = True # Overwrites textwrap
|
||||
IGNORETLTEXT = True # Ignores all translated text.
|
||||
MISMATCH = [] # Lists files that thdata a mismatch error (Length of GPT list response is wrong)
|
||||
IGNORETLTEXT = True # Ignores all translated text.
|
||||
MISMATCH = [] # Lists files that thdata a mismatch error (Length of GPT list response is wrong)
|
||||
BRACKETNAMES = False
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
FREQUENCY_PENALTY = 0.2
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .005
|
||||
OUTPUTAPICOST = .015
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.005
|
||||
OUTPUTAPICOST = 0.015
|
||||
BATCHSIZE = 20
|
||||
FREQUENCY_PENALTY = 0.1
|
||||
|
||||
#tqdm Globals
|
||||
BAR_FORMAT='{l_bar}{bar:10}{r_bar}{bar:-10b}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
PBAR = None
|
||||
|
||||
|
||||
def handleCSV(filename, estimate):
|
||||
global ESTIMATE, TOKENS
|
||||
ESTIMATE = estimate
|
||||
|
||||
if not ESTIMATE:
|
||||
with open('translated/' + filename, 'w+t', newline='', encoding='utf-8-sig') as writeFile:
|
||||
with open(
|
||||
"translated/" + filename, "w+t", newline="", encoding="utf-8-sig"
|
||||
) as writeFile:
|
||||
# Translate
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename, writeFile)
|
||||
|
||||
|
||||
# Print Result
|
||||
end = time.time()
|
||||
tqdm.write(getResultString(translatedData, end - start, filename))
|
||||
|
|
@ -84,7 +87,7 @@ def handleCSV(filename, estimate):
|
|||
# Translate
|
||||
start = time.time()
|
||||
translatedData = openFilesEstimate(filename)
|
||||
|
||||
|
||||
# Print Result
|
||||
end = time.time()
|
||||
tqdm.write(getResultString(translatedData, end - start, filename))
|
||||
|
|
@ -92,41 +95,55 @@ def handleCSV(filename, estimate):
|
|||
TOKENS[0] += translatedData[1][0]
|
||||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', TOKENS, None], end - start, '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
|
||||
return totalString + Fore.RED + f"\nMismatch Errors: {MISMATCH}" + Fore.RESET
|
||||
else:
|
||||
return totalString
|
||||
|
||||
|
||||
def openFiles(filename, writeFile):
|
||||
with open('files/' + filename, 'r', encoding='utf-8-sig') as readFile, writeFile:
|
||||
with open("files/" + filename, "r", encoding="utf-8-sig") as readFile, writeFile:
|
||||
translatedData = parseCSV(readFile, writeFile, filename)
|
||||
|
||||
return translatedData
|
||||
|
||||
|
||||
def openFilesEstimate(filename):
|
||||
with open('files/' + filename, 'r', encoding='utf-8-sig') as readFile:
|
||||
translatedData = parseCSV(readFile, '', filename)
|
||||
with open("files/" + filename, "r", encoding="utf-8-sig") as readFile:
|
||||
translatedData = parseCSV(readFile, "", 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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] is None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
else:
|
||||
# Fail
|
||||
try:
|
||||
|
|
@ -134,38 +151,51 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def parseCSV(readFile, writeFile, filename):
|
||||
totalTokens = [0,0]
|
||||
totalTokens = [0, 0]
|
||||
totalLines = 0
|
||||
global LOCK
|
||||
|
||||
format = ''
|
||||
while format not in ['1', '2', '3']:
|
||||
format = input('\n\nSelect the CSV Format:\n\n1. Translator++\n2. Single\n3. Multiple\n')
|
||||
format = ""
|
||||
while format not in ["1", "2", "3"]:
|
||||
format = input(
|
||||
"\n\nSelect the CSV Format:\n\n1. Translator++\n2. Single\n3. Multiple\n"
|
||||
)
|
||||
match format:
|
||||
case '1':
|
||||
format = '1'
|
||||
case '2':
|
||||
format = '2'
|
||||
case '3':
|
||||
format = '3'
|
||||
case "1":
|
||||
format = "1"
|
||||
case "2":
|
||||
format = "2"
|
||||
case "3":
|
||||
format = "3"
|
||||
|
||||
# Get total for progress bar
|
||||
totalLines = len(readFile.readlines())
|
||||
readFile.seek(0)
|
||||
|
||||
reader = csv.reader(readFile, delimiter=',')
|
||||
reader = csv.reader(readFile, delimiter=",")
|
||||
if not ESTIMATE:
|
||||
writer = csv.writer(writeFile, delimiter=',', quotechar='\"')
|
||||
writer = csv.writer(writeFile, delimiter=",", quotechar='"')
|
||||
else:
|
||||
writer = ''
|
||||
writer = ""
|
||||
|
||||
with tqdm(bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE) as pbar:
|
||||
pbar.desc=filename
|
||||
pbar.total=totalLines
|
||||
with tqdm(
|
||||
bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE
|
||||
) as pbar:
|
||||
pbar.desc = filename
|
||||
pbar.total = totalLines
|
||||
|
||||
# Grab All Rows
|
||||
data = []
|
||||
|
|
@ -180,11 +210,12 @@ def parseCSV(readFile, writeFile, filename):
|
|||
traceback.print_exc()
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateCSV(data, pbar, writer, filename, translatedList, format):
|
||||
global LOCK, ESTIMATE, PBAR
|
||||
PBAR = pbar
|
||||
translatedText = ''
|
||||
totalTokens = [0,0]
|
||||
translatedText = ""
|
||||
totalTokens = [0, 0]
|
||||
i = 0
|
||||
stringList = []
|
||||
|
||||
|
|
@ -193,7 +224,7 @@ def translateCSV(data, pbar, writer, filename, translatedList, format):
|
|||
while i < len(data):
|
||||
match format:
|
||||
# T++ Format: Source Text on column 1. TL Target on Column 2
|
||||
case '1':
|
||||
case "1":
|
||||
# Get String
|
||||
if i != 0:
|
||||
if data[i][1] == "":
|
||||
|
|
@ -202,7 +233,7 @@ def translateCSV(data, pbar, writer, filename, translatedList, format):
|
|||
jaString = data[i][1]
|
||||
|
||||
# Remove Textwrap
|
||||
jaString = jaString.replace('\\n', ' ')
|
||||
jaString = jaString.replace("\\n", " ")
|
||||
|
||||
# Pass 1
|
||||
if not translatedList:
|
||||
|
|
@ -216,16 +247,16 @@ def translateCSV(data, pbar, writer, filename, translatedList, format):
|
|||
|
||||
# Add Wordwrap
|
||||
translatedText = textwrap.fill(translatedText, WIDTH)
|
||||
translatedText = translatedText.replace('\n', '\\n')
|
||||
translatedText = translatedText.replace("\n", "\\n")
|
||||
|
||||
# Set Data
|
||||
data[i][1] = translatedText
|
||||
|
||||
|
||||
# Iterate
|
||||
i += 1
|
||||
|
||||
|
||||
# Target Format
|
||||
case '2':
|
||||
case "2":
|
||||
# Set Values
|
||||
sourceColumn = 0
|
||||
targetColumn = 1
|
||||
|
|
@ -234,7 +265,7 @@ def translateCSV(data, pbar, writer, filename, translatedList, format):
|
|||
jaString = data[i][sourceColumn]
|
||||
|
||||
# Remove Textwrap
|
||||
jaString = jaString.replace('\\n', ' ')
|
||||
jaString = jaString.replace("\\n", " ")
|
||||
|
||||
# Pass 1
|
||||
if not translatedList:
|
||||
|
|
@ -248,22 +279,22 @@ def translateCSV(data, pbar, writer, filename, translatedList, format):
|
|||
|
||||
# Add Wordwrap
|
||||
translatedText = textwrap.fill(translatedText, WIDTH)
|
||||
translatedText = translatedText.replace('\n', '\\n')
|
||||
translatedText = translatedText.replace("\n", "\\n")
|
||||
|
||||
# Set Data
|
||||
data[i][targetColumn] = translatedText
|
||||
|
||||
|
||||
# Iterate
|
||||
i += 1
|
||||
|
||||
# All Format
|
||||
case '3':
|
||||
case "3":
|
||||
# Set columns to translate. Leave empty to translate all.
|
||||
targetColumns = []
|
||||
|
||||
# False - Place translation in source column
|
||||
# True - Place translation in next column
|
||||
targetNextRow = True
|
||||
targetNextRow = True
|
||||
|
||||
for j in range(len(data[i])):
|
||||
if j not in targetColumns:
|
||||
|
|
@ -271,7 +302,7 @@ def translateCSV(data, pbar, writer, filename, translatedList, format):
|
|||
jaString = data[i][j]
|
||||
|
||||
# Remove Textwrap
|
||||
jaString = jaString.replace('\\n', ' ')
|
||||
jaString = jaString.replace("\\n", " ")
|
||||
|
||||
# Pass 1
|
||||
if not translatedList:
|
||||
|
|
@ -285,14 +316,14 @@ def translateCSV(data, pbar, writer, filename, translatedList, format):
|
|||
|
||||
# Add Wordwrap
|
||||
translatedText = textwrap.fill(translatedText, WIDTH)
|
||||
translatedText = translatedText.replace('\n', '\\n')
|
||||
translatedText = translatedText.replace("\n", "\\n")
|
||||
|
||||
# Set Data
|
||||
if targetNextRow:
|
||||
data[i][j + 1] = translatedText
|
||||
else:
|
||||
data[i][j] = translatedText
|
||||
|
||||
|
||||
# Iterate
|
||||
i += 1
|
||||
|
||||
|
|
@ -301,9 +332,9 @@ def translateCSV(data, pbar, writer, filename, translatedList, format):
|
|||
# Set Progress
|
||||
pbar.total = len(stringList)
|
||||
pbar.refresh()
|
||||
|
||||
|
||||
# Translate
|
||||
response = translateGPT(stringList, '', True)
|
||||
response = translateGPT(stringList, "", True)
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
translatedList = response[0]
|
||||
|
|
@ -333,27 +364,35 @@ def translateCSV(data, pbar, writer, filename, translatedList, format):
|
|||
for row in data:
|
||||
writer.writerow(row)
|
||||
return totalTokens
|
||||
|
||||
|
||||
return totalTokens
|
||||
|
||||
|
||||
|
||||
# Save some money and enter the character before translation
|
||||
def getSpeaker(speaker):
|
||||
match speaker:
|
||||
case 'ファイン':
|
||||
return ['Fine', [0,0]]
|
||||
case '':
|
||||
return ['', [0,0]]
|
||||
case "ファイン":
|
||||
return ["Fine", [0, 0]]
|
||||
case "":
|
||||
return ["", [0, 0]]
|
||||
case _:
|
||||
# Store Speaker
|
||||
if speaker not in str(NAMESLIST):
|
||||
response = translateGPT(speaker, 'Reply with the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
response = translateGPT(
|
||||
speaker,
|
||||
"Reply with the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
response[0] = response[0].title()
|
||||
response[0] = response[0].replace("'S", "'s")
|
||||
|
||||
# Retry if name doesn't translate for some reason
|
||||
if re.search(r'([a-zA-Z??])', response[0]) == None:
|
||||
response = translateGPT(speaker, 'Reply with the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
if re.search(r"([a-zA-Z??])", response[0]) == None:
|
||||
response = translateGPT(
|
||||
speaker,
|
||||
"Reply with the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
response[0] = response[0].title()
|
||||
response[0] = response[0].replace("'S", "'s")
|
||||
|
||||
|
|
@ -364,74 +403,76 @@ def getSpeaker(speaker):
|
|||
else:
|
||||
for i in range(len(NAMESLIST)):
|
||||
if speaker == NAMESLIST[i][0]:
|
||||
return [NAMESLIST[i][1],[0,0]]
|
||||
|
||||
return [speaker,[0,0]]
|
||||
return [NAMESLIST[i][1], [0, 0]]
|
||||
|
||||
return [speaker, [0, 0]]
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'([\\]+c\[\d+\][\\]+c|[\\]+c\[\d+\])', jaString)
|
||||
colorList = re.findall(r"([\\]+c\[\d+\][\\]+c|[\\]+c\[\d+\])", jaString)
|
||||
colorList = set(colorList)
|
||||
if len(colorList) != 0:
|
||||
for color in colorList:
|
||||
jaString = jaString.replace(color, '[Color_' + str(count) + ']')
|
||||
jaString = jaString.replace(color, "[Color_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '[Noun_' + str(count) + ']')
|
||||
jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(var, "[Var_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]', jaString)
|
||||
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) + ']')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -441,60 +482,66 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('[Nested_' + str(count) + ']', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
クリスティーナ (Christina) - Female\n\
|
||||
リズ (Liz) - Female\n\
|
||||
'
|
||||
|
||||
system = PROMPT + VOCAB if fullPromptFlag else \
|
||||
f"\
|
||||
"
|
||||
|
||||
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\
|
||||
|
|
@ -506,12 +553,14 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
|
|||
- `...` can be a part of the dialogue. Translate it as it is.\n\
|
||||
{VOCAB}\n\
|
||||
"
|
||||
)
|
||||
if isinstance(subbedT, list):
|
||||
user = f'```json\n{subbedT}```'
|
||||
user = f"```json\n{subbedT}```"
|
||||
else:
|
||||
user = subbedT
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history, penalty, format):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -526,13 +575,13 @@ def translateText(characters, system, user, history, penalty, format):
|
|||
msg.append({"role": "system", "content": history})
|
||||
|
||||
# Response Format
|
||||
if format == 'json':
|
||||
responseFormat = { "type": "json_object" }
|
||||
if format == "json":
|
||||
responseFormat = {"type": "json_object"}
|
||||
else:
|
||||
responseFormat = { "type": "text" }
|
||||
|
||||
responseFormat = {"type": "text"}
|
||||
|
||||
# Content to TL
|
||||
msg.append({"role": "user", "content": f'{user}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0,
|
||||
frequency_penalty=penalty,
|
||||
|
|
@ -542,18 +591,19 @@ def translateText(characters, system, user, history, penalty, format):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'「': '\\"',
|
||||
'」': '\\"',
|
||||
'- ': '-',
|
||||
'Placeholder Text': '',
|
||||
f"{LANGUAGE} Translation: ": "",
|
||||
"Translation: ": "",
|
||||
"っ": "",
|
||||
"〜": "~",
|
||||
"ッ": "",
|
||||
"。": ".",
|
||||
"「": '\\"',
|
||||
"」": '\\"',
|
||||
"- ": "-",
|
||||
"Placeholder Text": "",
|
||||
# Add more replacements as needed
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
|
|
@ -564,11 +614,12 @@ def cleanTranslatedText(translatedText, varResponse):
|
|||
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'(?<=(.))ー+'
|
||||
|
||||
pattern = r"(?<=(.))ー+"
|
||||
|
||||
# Define a replacement function that elongates the captured character
|
||||
def repl(match):
|
||||
char = match.group(1) # The character before the ー sequence
|
||||
|
|
@ -578,6 +629,7 @@ def elongateCharacters(text):
|
|||
# Use re.sub() to replace the pattern in the text
|
||||
return re.sub(pattern, repl, text)
|
||||
|
||||
|
||||
def extractTranslation(translatedTextList, is_list):
|
||||
try:
|
||||
translatedTextList = re.sub(r'\\"+\"([^,\n}])', r'\\"\1', translatedTextList)
|
||||
|
|
@ -590,15 +642,15 @@ def extractTranslation(translatedTextList, is_list):
|
|||
return string_list[0]
|
||||
|
||||
except Exception as e:
|
||||
PBAR.write(f'extractTranslation Error: {e} on String {translatedTextList}')
|
||||
PBAR.write(f"extractTranslation Error: {e} on String {translatedTextList}")
|
||||
return None
|
||||
|
||||
|
||||
def countTokens(characters, system, user, history):
|
||||
inputTotalTokens = 0
|
||||
outputTotalTokens = 0
|
||||
enc = tiktoken.encoding_for_model('gpt-4')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -610,26 +662,28 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
global PBAR
|
||||
|
||||
|
||||
mismatch = False
|
||||
totalTokens = [0, 0]
|
||||
if isinstance(text, list):
|
||||
format = 'json'
|
||||
format = "json"
|
||||
tList = batchList(text, BATCHSIZE)
|
||||
else:
|
||||
format = 'text'
|
||||
format = "text"
|
||||
tList = [text]
|
||||
|
||||
for index, tItem in enumerate(tList):
|
||||
|
|
@ -644,7 +698,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
if PBAR is not None:
|
||||
PBAR.update(len(tItem))
|
||||
continue
|
||||
|
|
@ -669,9 +723,13 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
translatedText = cleanTranslatedText(translatedText, varResponse)
|
||||
if isinstance(tItem, list):
|
||||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
if extractedTranslations == None or len(tItem) != len(extractedTranslations):
|
||||
if extractedTranslations == None or len(tItem) != len(
|
||||
extractedTranslations
|
||||
):
|
||||
# Mismatch. Try Again
|
||||
response = translateText(characters, system, user, history, 0.05, format)
|
||||
response = translateText(
|
||||
characters, system, user, history, 0.05, format
|
||||
)
|
||||
translatedText = response.choices[0].message.content
|
||||
totalTokens[0] += response.usage.prompt_tokens
|
||||
totalTokens[1] += response.usage.completion_tokens
|
||||
|
|
@ -680,13 +738,17 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
translatedText = cleanTranslatedText(translatedText, varResponse)
|
||||
if isinstance(tItem, list):
|
||||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
if extractedTranslations == None or len(tItem) != len(extractedTranslations):
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
if extractedTranslations == None or len(tItem) != len(
|
||||
extractedTranslations
|
||||
):
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
# Set if no mismatch
|
||||
if mismatch == False:
|
||||
tList[index] = extractedTranslations
|
||||
history = extractedTranslations[-10:] # Update history if we have a list
|
||||
history = extractedTranslations[
|
||||
-10:
|
||||
] # Update history if we have a list
|
||||
else:
|
||||
history = text[-10:]
|
||||
mismatch = False
|
||||
|
|
@ -697,7 +759,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
PBAR.update(len(tItem))
|
||||
else:
|
||||
# Ensure we're passing a single string to extractTranslation
|
||||
tList[index] = translatedText
|
||||
tList[index] = translatedText
|
||||
|
||||
finalList = combineList(tList, text)
|
||||
return [finalList, totalTokens]
|
||||
|
|
|
|||
|
|
@ -15,34 +15,34 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
PBAR = None
|
||||
|
|
@ -50,15 +50,16 @@ PBAR = None
|
|||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .005
|
||||
OUTPUTAPICOST = .015
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.005
|
||||
OUTPUTAPICOST = 0.015
|
||||
BATCHSIZE = 40
|
||||
|
||||
|
||||
def handleEushully(filename, estimate):
|
||||
global ESTIMATE
|
||||
ESTIMATE = estimate
|
||||
|
|
@ -75,17 +76,21 @@ def handleEushully(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', TOKENS, None], end - start, '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
|
||||
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:
|
||||
with open(
|
||||
"translated/" + filename, "w", encoding="utf-8", errors="ignore"
|
||||
) as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -98,23 +103,36 @@ def handleEushully(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -123,31 +141,41 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='utf-8') as readFile:
|
||||
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':
|
||||
if line != "\\d\n":
|
||||
finalData.append(line)
|
||||
translatedData[0] = finalData
|
||||
|
||||
|
||||
return translatedData
|
||||
|
||||
|
||||
def parseRegex(readFile, filename):
|
||||
totalTokens = [0,0]
|
||||
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
|
||||
pbar.desc = filename
|
||||
|
||||
try:
|
||||
result = translateEushully(data, pbar, filename, [])
|
||||
|
|
@ -158,11 +186,12 @@ def parseRegex(readFile, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateEushully(data, pbar, filename, translatedList):
|
||||
stringList = []
|
||||
currentGroup = []
|
||||
tokens = [0,0]
|
||||
speaker = ''
|
||||
tokens = [0, 0]
|
||||
speaker = ""
|
||||
voice = False
|
||||
global LOCK, ESTIMATE, PBAR
|
||||
i = 0
|
||||
|
|
@ -170,9 +199,9 @@ def translateEushully(data, pbar, filename, translatedList):
|
|||
while i < len(data):
|
||||
voice = False
|
||||
# Speaker
|
||||
if 'mov (global-int 46e2)' in data[i]:
|
||||
if "mov (global-int 46e2)" in data[i]:
|
||||
# Get Speaker
|
||||
speaker = re.search(r'mov \(global-int 46e2\)\s(.+)', data[i]).group(1)
|
||||
speaker = re.search(r"mov \(global-int 46e2\)\s(.+)", data[i]).group(1)
|
||||
response = getSpeaker(speaker)
|
||||
speaker = response[0]
|
||||
tokens[0] += response[1][0]
|
||||
|
|
@ -180,32 +209,34 @@ def translateEushully(data, pbar, filename, translatedList):
|
|||
i += 1
|
||||
|
||||
# Show Text
|
||||
if any(x in data[i] for x in ['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) != '':
|
||||
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])
|
||||
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)
|
||||
|
||||
del data[i]
|
||||
del data[i]
|
||||
jaString = " ".join(currentGroup)
|
||||
|
||||
# Pass 1
|
||||
if translatedList == []:
|
||||
# Add String
|
||||
if speaker:
|
||||
stringList.append(f'[{speaker}]: {jaString.strip()}')
|
||||
stringList.append(f"[{speaker}]: {jaString.strip()}")
|
||||
else:
|
||||
stringList.append(jaString.strip())
|
||||
|
||||
|
||||
# Pass 2
|
||||
else:
|
||||
# Get Text
|
||||
|
|
@ -222,51 +253,58 @@ def translateEushully(data, pbar, filename, translatedList):
|
|||
translatedText = translatedText.replace('"', "'")
|
||||
|
||||
# Remove speaker
|
||||
if speaker != '':
|
||||
translatedText = re.sub(r'^\[?(.+?)\]?\s?[|:]\s?', '', translatedText)
|
||||
if speaker != "":
|
||||
translatedText = re.sub(
|
||||
r"^\[?(.+?)\]?\s?[|:]\s?", "", translatedText
|
||||
)
|
||||
|
||||
# Textwrap
|
||||
translatedText = textwrap.fill(translatedText, width=WIDTH)
|
||||
translatedTextList = translatedText.split('\n')
|
||||
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')
|
||||
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')
|
||||
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 = ''
|
||||
speaker = ""
|
||||
i += 1
|
||||
|
||||
|
||||
# Nothing relevant. Skip Line.
|
||||
else:
|
||||
i += 1
|
||||
|
||||
# Set String
|
||||
elif 'set-string' in data[i]:
|
||||
elif "set-string" in data[i]:
|
||||
# Lines
|
||||
regex = r'(.*?)"(.*)"'
|
||||
match = re.search(regex, data[i])
|
||||
# Grab Strings
|
||||
if match != None and match.group(2) != '':
|
||||
if match != None and match.group(2) != "":
|
||||
originalString = match.group(2)
|
||||
jaString = match.group(2)
|
||||
currentGroup = [jaString]
|
||||
|
||||
|
||||
# Remove Textwrap
|
||||
jaString = jaString.replace('\\n', ' ')
|
||||
|
||||
jaString = jaString.replace("\\n", " ")
|
||||
|
||||
# Pass 1
|
||||
if translatedList == []:
|
||||
# Add String
|
||||
stringList.append(jaString.strip())
|
||||
|
||||
|
||||
# Pass 2
|
||||
else:
|
||||
# Get Text
|
||||
|
|
@ -284,11 +322,11 @@ def translateEushully(data, pbar, filename, translatedList):
|
|||
|
||||
# Textwrap
|
||||
translatedText = textwrap.fill(translatedText, width=LISTWIDTH)
|
||||
translatedText = translatedText.replace('\n', '\\n')
|
||||
translatedText = translatedText.replace("\n", "\\n")
|
||||
|
||||
# Set Data
|
||||
data[i] = data[i].replace(originalString, translatedText)
|
||||
speaker = ''
|
||||
data[i] = data[i].replace(originalString, translatedText)
|
||||
speaker = ""
|
||||
i += 1
|
||||
|
||||
# Nothing relevant. Skip Line.
|
||||
|
|
@ -302,10 +340,10 @@ def translateEushully(data, pbar, filename, translatedList):
|
|||
# Set Progress
|
||||
pbar.total = len(stringList)
|
||||
pbar.refresh()
|
||||
|
||||
|
||||
# Translate
|
||||
PBAR = pbar
|
||||
response = translateGPT(stringList, '', True)
|
||||
response = translateGPT(stringList, "", True)
|
||||
tokens[0] += response[1][0]
|
||||
tokens[1] += response[1][1]
|
||||
translatedList = response[0]
|
||||
|
|
@ -321,140 +359,143 @@ def translateEushully(data, pbar, filename, translatedList):
|
|||
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 "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]]
|
||||
return ["Unknown", [0, 0]]
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'[\\]+[cC]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(color, "[Color_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '[Noun_' + str(count) + ']')
|
||||
jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(var, "[Var_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]', jaString)
|
||||
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) + ']')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -464,59 +505,65 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('[Nested_' + str(count) + ']', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
グレイス (Grace) - Female\n\
|
||||
'
|
||||
|
||||
system = PROMPT + VOCAB if fullPromptFlag else \
|
||||
f"\
|
||||
"
|
||||
|
||||
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\
|
||||
|
|
@ -528,9 +575,11 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
|
|||
- `...` can be a part of the dialogue. Translate it as it is.\n\
|
||||
{VOCAB}\n\
|
||||
"
|
||||
user = f'{subbedT}'
|
||||
)
|
||||
user = f"{subbedT}"
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history, penalty):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -543,9 +592,9 @@ def translateText(characters, system, user, history, penalty):
|
|||
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}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0,
|
||||
frequency_penalty=penalty,
|
||||
|
|
@ -554,23 +603,24 @@ def translateText(characters, system, user, history, penalty):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'< ': '<',
|
||||
'</ ': '</',
|
||||
' >': '>',
|
||||
'「': '\"',
|
||||
'」': '\"',
|
||||
'Placeholder Text': '',
|
||||
'- chan': '-chan',
|
||||
'- kun': '-kun',
|
||||
'- san': '-san',
|
||||
f"{LANGUAGE} Translation: ": "",
|
||||
"Translation: ": "",
|
||||
"っ": "",
|
||||
"〜": "~",
|
||||
"ッ": "",
|
||||
"。": ".",
|
||||
"< ": "<",
|
||||
"</ ": "</",
|
||||
" >": ">",
|
||||
"「": '"',
|
||||
"」": '"',
|
||||
"Placeholder Text": "",
|
||||
"- chan": "-chan",
|
||||
"- kun": "-kun",
|
||||
"- san": "-san",
|
||||
# Add more replacements as needed
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
|
|
@ -581,11 +631,12 @@ def cleanTranslatedText(translatedText, varResponse):
|
|||
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'(?<=(.))ー+'
|
||||
|
||||
pattern = r"(?<=(.))ー+"
|
||||
|
||||
# Define a replacement function that elongates the captured character
|
||||
def repl(match):
|
||||
char = match.group(1) # The character before the ー sequence
|
||||
|
|
@ -595,8 +646,9 @@ def elongateCharacters(text):
|
|||
# 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+>`?'
|
||||
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)
|
||||
|
|
@ -605,11 +657,12 @@ def extractTranslation(translatedTextList, is_list):
|
|||
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')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -621,19 +674,21 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
global PBAR
|
||||
|
||||
|
||||
mismatch = False
|
||||
totalTokens = [0, 0]
|
||||
if isinstance(text, list):
|
||||
|
|
@ -644,8 +699,12 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
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'`<Line{i}>{item}</Line{i}>`' for i, item in enumerate(tItem)])
|
||||
payload = re.sub(r'(<Line\d+)(><)(\/Line\d+>)', r'\1>Placeholder Text<\3', payload)
|
||||
payload = "\n".join(
|
||||
[f"`<Line{i}>{item}</Line{i}>`" for i, item in enumerate(tItem)]
|
||||
)
|
||||
payload = re.sub(
|
||||
r"(<Line\d+)(><)(\/Line\d+>)", r"\1>Placeholder Text<\3", payload
|
||||
)
|
||||
varResponse = subVars(payload)
|
||||
subbedT = varResponse[0]
|
||||
else:
|
||||
|
|
@ -653,7 +712,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
if PBAR is not None:
|
||||
PBAR.update(len(tItem))
|
||||
continue
|
||||
|
|
@ -692,14 +751,16 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
tList[index] = extractedTranslations
|
||||
if len(tItem) != len(extractedTranslations):
|
||||
mismatch = True # Just here for breakpoint
|
||||
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
|
||||
history = extractedTranslations[
|
||||
-10:
|
||||
] # Update history if we have a list
|
||||
else:
|
||||
history = text[-10:]
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -14,72 +14,77 @@ from dotenv import load_dotenv
|
|||
from retry import retry
|
||||
from tqdm import tqdm
|
||||
|
||||
#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'))
|
||||
# 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()
|
||||
PBAR = None
|
||||
WIDTH = int(os.getenv('width'))
|
||||
LISTWIDTH = int(os.getenv('listWidth'))
|
||||
NOTEWIDTH = int(os.getenv('noteWidth'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = int(os.getenv("noteWidth"))
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
MISMATCH = [] # Lists files that throw a mismatch error (Length of GPT list response is wrong)
|
||||
MISMATCH = [] # Lists files that throw a mismatch error (Length of GPT list response is wrong)
|
||||
|
||||
# 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')
|
||||
if os.getenv("api").replace(" ", "") != "":
|
||||
openai.base_url = os.getenv("api")
|
||||
openai.organization = os.getenv("org")
|
||||
openai.api_key = os.getenv("key")
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
FREQUENCY_PENALTY = 0.2
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .005
|
||||
OUTPUTAPICOST = .015
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.005
|
||||
OUTPUTAPICOST = 0.015
|
||||
BATCHSIZE = 20
|
||||
FREQUENCY_PENALTY = 0.1
|
||||
|
||||
#tqdm Globals
|
||||
BAR_FORMAT='{l_bar}{bar:10}{r_bar}{bar:-10b}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
|
||||
|
||||
def handleImages(folderName, estimate):
|
||||
global ESTIMATE, TOKENS
|
||||
ESTIMATE = estimate
|
||||
start = time.time()
|
||||
|
||||
# Translate Strings
|
||||
translatedData = openFiles(f'files/{folderName}')
|
||||
translatedData = openFiles(f"files/{folderName}")
|
||||
|
||||
# Write Strings to Images
|
||||
if not ESTIMATE:
|
||||
if not os.path.exists(f'translated/{folderName}'):
|
||||
os.mkdir(f'translated/{folderName}')
|
||||
if not os.path.exists(f"translated/{folderName}"):
|
||||
os.mkdir(f"translated/{folderName}")
|
||||
for i in range(len(translatedData[0][0])):
|
||||
try:
|
||||
translatedList = translatedData[0][0]
|
||||
originalList = translatedData[0][1]
|
||||
dimensionsList = translatedData[0][2]
|
||||
image = stringToImage(translatedList[i], dimensionsList[i][0], dimensionsList[i][1])
|
||||
image.save(rf'translated/{folderName}/{translatedList[i]}.png', quality=100)
|
||||
image = stringToImage(
|
||||
translatedList[i], dimensionsList[i][0], dimensionsList[i][1]
|
||||
)
|
||||
image.save(
|
||||
rf"translated/{folderName}/{translatedList[i]}.png", quality=100
|
||||
)
|
||||
except Exception as e:
|
||||
PBAR.write(f'{translatedList[i]}: {str(e)}')
|
||||
#Ignore Error
|
||||
PBAR.write(f"{translatedList[i]}: {str(e)}")
|
||||
# Ignore Error
|
||||
|
||||
# Print File
|
||||
end = time.time()
|
||||
|
|
@ -89,43 +94,67 @@ def handleImages(folderName, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', TOKENS, None], end - start, '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
|
||||
return totalString + Fore.RED + f"\nMismatch Errors: {MISMATCH}" + Fore.RESET
|
||||
else:
|
||||
return totalString
|
||||
|
||||
|
||||
|
||||
def openFiles(folderName):
|
||||
global PBAR
|
||||
|
||||
if os.path.isdir(folderName):
|
||||
imageList = [[],[]]
|
||||
imageList = [[], []]
|
||||
imageList = processImagesDir(folderName, imageList)
|
||||
|
||||
|
||||
# Start Translation
|
||||
with tqdm(bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE, desc=folderName, total=len(imageList[0])) as PBAR:
|
||||
with tqdm(
|
||||
bar_format=BAR_FORMAT,
|
||||
position=POSITION,
|
||||
leave=LEAVE,
|
||||
desc=folderName,
|
||||
total=len(imageList[0]),
|
||||
) as PBAR:
|
||||
translatedData = translateImages(imageList)
|
||||
translatedData = [[translatedData[0], imageList[0], imageList[1]], translatedData[1], translatedData[2]]
|
||||
|
||||
translatedData = [
|
||||
[translatedData[0], imageList[0], imageList[1]],
|
||||
translatedData[1],
|
||||
translatedData[2],
|
||||
]
|
||||
|
||||
return translatedData
|
||||
else:
|
||||
print("The provided directory path does not exist.")
|
||||
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] is None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
else:
|
||||
# Fail
|
||||
try:
|
||||
|
|
@ -133,8 +162,17 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def getFontSize(text, image_width, image_height, font_path):
|
||||
# Start with a high font size and keep reducing it until the text fits within the image bounds
|
||||
|
|
@ -142,50 +180,59 @@ def getFontSize(text, image_width, image_height, font_path):
|
|||
|
||||
while font_size > 0:
|
||||
font = ImageFont.truetype(font_path, font_size)
|
||||
text_bbox = ImageDraw.Draw(Image.new('RGB', (1, 1))).textbbox((0, 0), text, font=font)
|
||||
text_bbox = ImageDraw.Draw(Image.new("RGB", (1, 1))).textbbox(
|
||||
(0, 0), text, font=font
|
||||
)
|
||||
text_width = text_bbox[2] - text_bbox[0]
|
||||
text_height = text_bbox[3] - text_bbox[1] + 5
|
||||
|
||||
|
||||
if text_width <= image_width and text_height <= image_height:
|
||||
return font_size
|
||||
font_size -= 1
|
||||
|
||||
return font_size
|
||||
|
||||
def stringToImage(text, width, height, font_path='fonts/TsunagiGothic.ttf', scale_factor=4):
|
||||
|
||||
def stringToImage(
|
||||
text, width, height, font_path="fonts/TsunagiGothic.ttf", scale_factor=4
|
||||
):
|
||||
# Increase the resolution
|
||||
scaled_width = int(width * scale_factor)
|
||||
scaled_height = int(height * scale_factor)
|
||||
|
||||
|
||||
# Find the appropriate font size for the scaled up image
|
||||
font_size = getFontSize(text, scaled_width, scaled_height, font_path)
|
||||
if font_size == 0:
|
||||
raise ValueError("Text is too long to fit in the supplied dimensions.")
|
||||
|
||||
|
||||
# Create a new image with the scaled width and height and a transparent background
|
||||
image = Image.new('RGBA', (scaled_width, scaled_height), (255, 255, 255, 0))
|
||||
|
||||
image = Image.new("RGBA", (scaled_width, scaled_height), (255, 255, 255, 0))
|
||||
|
||||
# Create a drawing context
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
|
||||
# Load the appropriate font
|
||||
font = ImageFont.truetype(font_path, font_size)
|
||||
|
||||
|
||||
# Calculate the size of the text to center it
|
||||
text_bbox = draw.textbbox((0, 0), text, font=font)
|
||||
text_width = text_bbox[2] - text_bbox[0]
|
||||
text_height = text_bbox[3] - text_bbox[1]
|
||||
x = (scaled_width - text_width) // 2
|
||||
y = (scaled_height - text_height) // 2
|
||||
|
||||
|
||||
# Draw the text on the image
|
||||
draw.text((x, y), text, font=font, fill=(255, 255, 255, 255))
|
||||
|
||||
|
||||
# Resize back to the original dimensions to get a clearer text rendering
|
||||
image = image.resize((width, height), Image.LANCZOS,)
|
||||
|
||||
image = image.resize(
|
||||
(width, height),
|
||||
Image.LANCZOS,
|
||||
)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def getImageDimensions(file_path):
|
||||
try:
|
||||
with Image.open(file_path) as img:
|
||||
|
|
@ -195,10 +242,11 @@ def getImageDimensions(file_path):
|
|||
print(f"Error reading {file_path}: {e}")
|
||||
return None, None
|
||||
|
||||
|
||||
def processImagesDir(directory_path, imageList):
|
||||
for file_name in os.listdir(directory_path):
|
||||
# .png and Japanese
|
||||
if '.png' in file_name and file_name.replace('.png', '') in VOCAB:
|
||||
if ".png" in file_name and file_name.replace(".png", "") in VOCAB:
|
||||
file_path = os.path.join(directory_path, file_name)
|
||||
if os.path.isfile(file_path):
|
||||
# Check if the file is an image
|
||||
|
|
@ -206,7 +254,7 @@ def processImagesDir(directory_path, imageList):
|
|||
width, height = getImageDimensions(file_path)
|
||||
if width is not None and height is not None:
|
||||
placeholders = {
|
||||
'.png': '',
|
||||
".png": "",
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
file_name = file_name.replace(target, replacement)
|
||||
|
|
@ -214,37 +262,49 @@ def processImagesDir(directory_path, imageList):
|
|||
imageList[1].append([width, height])
|
||||
except Exception as e:
|
||||
print(f"Error processing {file_name}: {e}")
|
||||
|
||||
|
||||
return imageList
|
||||
|
||||
|
||||
def translateImages(imageList):
|
||||
totalTokens = [0,0]
|
||||
totalTokens = [0, 0]
|
||||
|
||||
# Translate GPT
|
||||
response = translateGPT(imageList[0], 'Keep the Translation as brief as possible', True)
|
||||
response = translateGPT(
|
||||
imageList[0], "Keep the Translation as brief as possible", True
|
||||
)
|
||||
translatedList = response[0]
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
|
||||
return [translatedList, totalTokens, None]
|
||||
return [translatedList, totalTokens, None]
|
||||
|
||||
|
||||
# Save some money and enter the character before translation
|
||||
def getSpeaker(speaker):
|
||||
match speaker:
|
||||
case 'ファイン':
|
||||
return ['Fine', [0,0]]
|
||||
case '':
|
||||
return ['', [0,0]]
|
||||
case "ファイン":
|
||||
return ["Fine", [0, 0]]
|
||||
case "":
|
||||
return ["", [0, 0]]
|
||||
case _:
|
||||
# Store Speaker
|
||||
if speaker not in str(NAMESLIST):
|
||||
response = translateGPT(speaker, 'Reply with the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
response = translateGPT(
|
||||
speaker,
|
||||
"Reply with the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
response[0] = response[0].title()
|
||||
response[0] = response[0].replace("'S", "'s")
|
||||
|
||||
# Retry if name doesn't translate for some reason
|
||||
if re.search(r'([a-zA-Z??])', response[0]) == None:
|
||||
response = translateGPT(speaker, 'Reply with the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
if re.search(r"([a-zA-Z??])", response[0]) == None:
|
||||
response = translateGPT(
|
||||
speaker,
|
||||
"Reply with the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
response[0] = response[0].title()
|
||||
response[0] = response[0].replace("'S", "'s")
|
||||
|
||||
|
|
@ -255,50 +315,56 @@ def getSpeaker(speaker):
|
|||
else:
|
||||
for i in range(len(NAMESLIST)):
|
||||
if speaker == NAMESLIST[i][0]:
|
||||
return [NAMESLIST[i][1],[0,0]]
|
||||
|
||||
return [speaker,[0,0]]
|
||||
return [NAMESLIST[i][1], [0, 0]]
|
||||
|
||||
return [speaker, [0, 0]]
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
codeList = re.findall(r'[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]', jaString)
|
||||
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) + ']')
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
ロラン (Roland) - Male\n\
|
||||
リュカ (Ryuka) - Male\n\
|
||||
レックス (Rex) - Male\n\
|
||||
|
|
@ -341,10 +407,12 @@ def createContext(fullPromptFlag, subbedT, format):
|
|||
ブライ (Buraimu) - Male\n\
|
||||
ハッサン (Hassan) - Male\n\
|
||||
アロマ (Aroma) - Female\n\
|
||||
'
|
||||
|
||||
system = PROMPT + VOCAB if fullPromptFlag else \
|
||||
f"\
|
||||
"
|
||||
|
||||
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\
|
||||
|
|
@ -356,12 +424,14 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
|
|||
- `...` can be a part of the dialogue. Translate it as it is.\n\
|
||||
{VOCAB}\n\
|
||||
"
|
||||
if format == 'json':
|
||||
user = f'```json\n{subbedT}\n```'
|
||||
)
|
||||
if format == "json":
|
||||
user = f"```json\n{subbedT}\n```"
|
||||
else:
|
||||
user = subbedT
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history, penalty, format):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -376,13 +446,13 @@ def translateText(characters, system, user, history, penalty, format):
|
|||
msg.append({"role": "system", "content": history})
|
||||
|
||||
# Response Format
|
||||
if format == 'json':
|
||||
responseFormat = { "type": "json_object" }
|
||||
if format == "json":
|
||||
responseFormat = {"type": "json_object"}
|
||||
else:
|
||||
responseFormat = { "type": "text" }
|
||||
|
||||
responseFormat = {"type": "text"}
|
||||
|
||||
# Content to TL
|
||||
msg.append({"role": "user", "content": f'{user}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0,
|
||||
frequency_penalty=penalty,
|
||||
|
|
@ -392,18 +462,19 @@ def translateText(characters, system, user, history, penalty, format):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'「': '\\"',
|
||||
'」': '\\"',
|
||||
'- ': '-',
|
||||
'Placeholder Text': '',
|
||||
f"{LANGUAGE} Translation: ": "",
|
||||
"Translation: ": "",
|
||||
"っ": "",
|
||||
"〜": "~",
|
||||
"ッ": "",
|
||||
"。": ".",
|
||||
"「": '\\"',
|
||||
"」": '\\"',
|
||||
"- ": "-",
|
||||
"Placeholder Text": "",
|
||||
# Add more replacements as needed
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
|
|
@ -414,11 +485,12 @@ def cleanTranslatedText(translatedText, varResponse):
|
|||
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'(?<=(.))ー+'
|
||||
|
||||
pattern = r"(?<=(.))ー+"
|
||||
|
||||
# Define a replacement function that elongates the captured character
|
||||
def repl(match):
|
||||
char = match.group(1) # The character before the ー sequence
|
||||
|
|
@ -428,6 +500,7 @@ def elongateCharacters(text):
|
|||
# Use re.sub() to replace the pattern in the text
|
||||
return re.sub(pattern, repl, text)
|
||||
|
||||
|
||||
def extractTranslation(translatedTextList, is_list):
|
||||
try:
|
||||
line_dict = json.loads(translatedTextList)
|
||||
|
|
@ -439,15 +512,15 @@ def extractTranslation(translatedTextList, is_list):
|
|||
return string_list[0]
|
||||
|
||||
except Exception as e:
|
||||
print(f'extractTranslation Error: {e}')
|
||||
print(f"extractTranslation Error: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def countTokens(characters, system, user, history):
|
||||
inputTotalTokens = 0
|
||||
outputTotalTokens = 0
|
||||
enc = tiktoken.encoding_for_model('gpt-4')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -459,26 +532,28 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
global PBAR
|
||||
|
||||
|
||||
mismatch = False
|
||||
totalTokens = [0, 0]
|
||||
if isinstance(text, list):
|
||||
format = 'json'
|
||||
format = "json"
|
||||
tList = batchList(text, BATCHSIZE)
|
||||
else:
|
||||
format = 'text'
|
||||
format = "text"
|
||||
tList = [text]
|
||||
|
||||
for index, tItem in enumerate(tList):
|
||||
|
|
@ -493,7 +568,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
if PBAR is not None:
|
||||
PBAR.update(len(tItem))
|
||||
continue
|
||||
|
|
@ -518,9 +593,13 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
translatedText = cleanTranslatedText(translatedText, varResponse)
|
||||
if isinstance(tItem, list):
|
||||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
if extractedTranslations == None or len(tItem) != len(extractedTranslations):
|
||||
if extractedTranslations == None or len(tItem) != len(
|
||||
extractedTranslations
|
||||
):
|
||||
# Mismatch. Try Again
|
||||
response = translateText(characters, system, user, history, 0.05, format)
|
||||
response = translateText(
|
||||
characters, system, user, history, 0.05, format
|
||||
)
|
||||
translatedText = response.choices[0].message.content
|
||||
totalTokens[0] += response.usage.prompt_tokens
|
||||
totalTokens[1] += response.usage.completion_tokens
|
||||
|
|
@ -529,13 +608,17 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
translatedText = cleanTranslatedText(translatedText, varResponse)
|
||||
if isinstance(tItem, list):
|
||||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
if extractedTranslations == None or len(tItem) != len(extractedTranslations):
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
if extractedTranslations == None or len(tItem) != len(
|
||||
extractedTranslations
|
||||
):
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
# Set if no mismatch
|
||||
if mismatch == False:
|
||||
tList[index] = extractedTranslations
|
||||
history = extractedTranslations[-10:] # Update history if we have a list
|
||||
history = extractedTranslations[
|
||||
-10:
|
||||
] # Update history if we have a list
|
||||
else:
|
||||
history = text[-10:]
|
||||
mismatch = False
|
||||
|
|
@ -546,7 +629,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
PBAR.update(len(tItem))
|
||||
else:
|
||||
# Ensure we're passing a single string to extractTranslation
|
||||
tList[index] = translatedText
|
||||
tList[index] = translatedText
|
||||
|
||||
finalList = combineList(tList, text)
|
||||
return [finalList, totalTokens]
|
||||
|
|
|
|||
|
|
@ -15,49 +15,50 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .005
|
||||
OUTPUTAPICOST = .015
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.005
|
||||
OUTPUTAPICOST = 0.015
|
||||
BATCHSIZE = 40
|
||||
|
||||
|
||||
def handleIris(filename, estimate):
|
||||
global ESTIMATE
|
||||
ESTIMATE = estimate
|
||||
|
|
@ -74,17 +75,21 @@ def handleIris(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', TOKENS, None], end - start, '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
|
||||
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:
|
||||
with open(
|
||||
"translated/" + filename, "w", encoding="cp932", errors="ignore"
|
||||
) as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -97,23 +102,36 @@ def handleIris(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -122,31 +140,41 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='shift_jis') as readFile:
|
||||
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':
|
||||
if line != "\\d\n":
|
||||
finalData.append(line)
|
||||
translatedData[0] = finalData
|
||||
|
||||
|
||||
return translatedData
|
||||
|
||||
|
||||
def parseIris(readFile, filename):
|
||||
totalTokens = [0,0]
|
||||
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
|
||||
pbar.desc = filename
|
||||
|
||||
try:
|
||||
result = translateIris(data, pbar, filename, [])
|
||||
|
|
@ -157,83 +185,87 @@ def parseIris(readFile, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateIris(data, pbar, filename, translatedList):
|
||||
stringList = []
|
||||
currentGroup = []
|
||||
tokens = [0,0]
|
||||
speaker = ''
|
||||
tokens = [0, 0]
|
||||
speaker = ""
|
||||
voice = False
|
||||
global LOCK, ESTIMATE
|
||||
i = 0
|
||||
|
||||
while i < len(data):
|
||||
voice = False
|
||||
speaker = ''
|
||||
if '#MSGVOICE' in data[i]:
|
||||
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:
|
||||
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 (
|
||||
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':
|
||||
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'
|
||||
speaker = speaker.replace(" ", "\u3000")
|
||||
data[i] = f"\u3000{speaker}\n"
|
||||
else:
|
||||
speaker = ''
|
||||
speaker = ""
|
||||
i += 1
|
||||
|
||||
# Lines
|
||||
match = re.search(r'(.*)', data[i])
|
||||
if match != None and match.group(1) != '':
|
||||
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':
|
||||
if data[i] != "\n":
|
||||
if data[i][0] == "\u3000":
|
||||
jaString = data[i][1:]
|
||||
currentGroup.append(jaString)
|
||||
i += 1
|
||||
while data[i] != '\n':
|
||||
while data[i] != "\n":
|
||||
jaString = data[i]
|
||||
if data[i] != '\n':
|
||||
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)
|
||||
jaString = "".join(currentGroup)
|
||||
currentGroup = []
|
||||
|
||||
|
||||
# Remove any textwrap
|
||||
jaString = jaString.replace('\n', ' ')
|
||||
jaString = jaString.replace("\n", " ")
|
||||
|
||||
# Temporarily convert spaces (For Textwrap Later)
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Add Speaker (If there is one)
|
||||
if speaker != '':
|
||||
jaString = f'{speaker}: {jaString}'
|
||||
if speaker != "":
|
||||
jaString = f"{speaker}: {jaString}"
|
||||
|
||||
# Add String
|
||||
stringList.append(jaString.strip())
|
||||
|
||||
|
||||
# Pass 2
|
||||
else:
|
||||
# Insert Strings
|
||||
while data[i] != '\n':
|
||||
while data[i] != "\n":
|
||||
data.pop(i)
|
||||
|
||||
# Get Text
|
||||
|
|
@ -244,21 +276,21 @@ def translateIris(data, pbar, filename, translatedList):
|
|||
translatedList = None
|
||||
|
||||
# Remove added speaker
|
||||
translatedText = re.sub(r'^.+?:\s', '', translatedText)
|
||||
translatedText = re.sub(r"^.+?:\s", "", translatedText)
|
||||
|
||||
# Textwrap
|
||||
translatedText = textwrap.fill(translatedText, width=WIDTH)
|
||||
translatedText = translatedText.replace('\n', '\n\u3000')
|
||||
translatedText = translatedText.replace("\n", "\n\u3000")
|
||||
|
||||
# Replace Whitespace and Commas
|
||||
translatedText = translatedText.replace(', ', '、')
|
||||
translatedText = translatedText.replace(',\u3000', '、')
|
||||
translatedText = translatedText.replace(',', '、')
|
||||
translatedText = translatedText.replace(' ', '\u3000')
|
||||
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:
|
||||
if translatedText.count("\n") > 2:
|
||||
# Split List
|
||||
translatedTextList = splitNewlines(translatedText)
|
||||
|
||||
|
|
@ -267,34 +299,34 @@ def translateIris(data, pbar, filename, translatedList):
|
|||
for text in translatedTextList:
|
||||
if count != 0:
|
||||
if voice == True:
|
||||
#MSG for each item in the list
|
||||
data.insert(i, '#MSGVOICE,\n')
|
||||
# MSG for each item in the list
|
||||
data.insert(i, "#MSGVOICE,\n")
|
||||
i += 1
|
||||
data.insert(i, f'{voiceVar}')
|
||||
data.insert(i, f"{voiceVar}")
|
||||
i += 1
|
||||
else:
|
||||
data.insert(i, '#MSG,\n')
|
||||
data.insert(i, "#MSG,\n")
|
||||
i += 1
|
||||
if speaker:
|
||||
data[i] = f'\u3000{speaker}\n'
|
||||
data[i] = f"\u3000{speaker}\n"
|
||||
i += 1
|
||||
if text[0] == '\u3000':
|
||||
data.insert(i, f'{text}\n')
|
||||
if text[0] == "\u3000":
|
||||
data.insert(i, f"{text}\n")
|
||||
else:
|
||||
data.insert(i, f'\u3000{text}\n')
|
||||
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]}'
|
||||
if data[i] != "\n":
|
||||
data.insert(i, "\n")
|
||||
data[i] = f"\n{data[i]}"
|
||||
else:
|
||||
data.insert(i, f'\u3000{translatedText}\n')
|
||||
data.insert(i, f"\u3000{translatedText}\n")
|
||||
i += 1
|
||||
if data[i] != '\n':
|
||||
data[i] = f'\n{data[i]}'
|
||||
if data[i] != "\n":
|
||||
data[i] = f"\n{data[i]}"
|
||||
|
||||
elif '#SELECT' in data[i] and translatedList == []:
|
||||
Iris = r'(.+?) +\d$'
|
||||
elif "#SELECT" in data[i] and translatedList == []:
|
||||
Iris = r"(.+?) +\d$"
|
||||
i += 1
|
||||
match = re.search(Iris, data[i])
|
||||
if match:
|
||||
|
|
@ -302,14 +334,20 @@ def translateIris(data, pbar, filename, translatedList):
|
|||
choiceList.append(match.group(1))
|
||||
i += 1
|
||||
match = re.search(Iris, data[i])
|
||||
while(match):
|
||||
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)
|
||||
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]
|
||||
|
|
@ -318,10 +356,10 @@ def translateIris(data, pbar, filename, translatedList):
|
|||
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')
|
||||
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
|
||||
|
||||
|
|
@ -336,9 +374,9 @@ def translateIris(data, pbar, filename, translatedList):
|
|||
# Set Progress
|
||||
pbar.total = len(stringList)
|
||||
pbar.refresh()
|
||||
|
||||
|
||||
# Translate
|
||||
response = translateGPT(stringList, '', True, pbar, filename)
|
||||
response = translateGPT(stringList, "", True, pbar, filename)
|
||||
tokens[0] += response[1][0]
|
||||
tokens[1] += response[1][1]
|
||||
translatedList = response[0]
|
||||
|
|
@ -354,20 +392,21 @@ def translateIris(data, pbar, filename, translatedList):
|
|||
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':
|
||||
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])
|
||||
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
|
||||
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):
|
||||
|
|
@ -375,94 +414,103 @@ def splitNewlines(text):
|
|||
|
||||
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 "ファイン":
|
||||
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 = 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]]
|
||||
return [NAMESLIST[i][1], [0, 0]]
|
||||
|
||||
return [speaker, [0, 0]]
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'[\\]+[cC]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(color, "[Color_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '[Noun_' + str(count) + ']')
|
||||
jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(var, "[Var_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]', jaString)
|
||||
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) + ']')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -472,54 +520,58 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('[Nested_' + str(count) + ']', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
フィリア (Philia) - Female\n\
|
||||
アルネット (Annett) - Female\n\
|
||||
ラピュセナ (Rapusena) - Female\n\
|
||||
|
|
@ -529,10 +581,12 @@ def createContext(fullPromptFlag, subbedT):
|
|||
カルナ (Karna) - Female\n\
|
||||
ラフィング=スピア (Laughing Spear) - Female\n\
|
||||
ノーラ (Nora) - Female\n\
|
||||
'
|
||||
|
||||
system = PROMPT + VOCAB if fullPromptFlag else \
|
||||
f"\
|
||||
"
|
||||
|
||||
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\
|
||||
|
|
@ -544,9 +598,11 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
|
|||
- `...` can be a part of the dialogue. Translate it as it is.\n\
|
||||
{VOCAB}\n\
|
||||
"
|
||||
user = f'{subbedT}'
|
||||
)
|
||||
user = f"{subbedT}"
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -559,9 +615,9 @@ def translateText(characters, system, user, history):
|
|||
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}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0.1,
|
||||
frequency_penalty=0.1,
|
||||
|
|
@ -570,15 +626,16 @@ def translateText(characters, system, user, history):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'Placeholder Text': ''
|
||||
f"{LANGUAGE} Translation: ": "",
|
||||
"Translation: ": "",
|
||||
"っ": "",
|
||||
"〜": "~",
|
||||
"ッ": "",
|
||||
"。": ".",
|
||||
"Placeholder Text": "",
|
||||
# Add more replacements as needed
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
|
|
@ -589,11 +646,12 @@ def cleanTranslatedText(translatedText, varResponse):
|
|||
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'(?<=(.))ー+'
|
||||
|
||||
pattern = r"(?<=(.))ー+"
|
||||
|
||||
# Define a replacement function that elongates the captured character
|
||||
def repl(match):
|
||||
char = match.group(1) # The character before the ー sequence
|
||||
|
|
@ -603,8 +661,9 @@ def elongateCharacters(text):
|
|||
# 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+>`?'
|
||||
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)
|
||||
|
|
@ -613,11 +672,12 @@ def extractTranslation(translatedTextList, is_list):
|
|||
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')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -629,15 +689,17 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*2)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 2)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag, pbar, filename):
|
||||
mismatch = False
|
||||
|
|
@ -650,8 +712,12 @@ def translateGPT(text, history, fullPromptFlag, pbar, filename):
|
|||
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'`<Line{i}>{item}</Line{i}>`' for i, item in enumerate(tItem)])
|
||||
payload = re.sub(r'(<Line\d+)(><)(\/Line\d+>)', r'\1>Placeholder Text<\3', payload)
|
||||
payload = "\n".join(
|
||||
[f"`<Line{i}>{item}</Line{i}>`" for i, item in enumerate(tItem)]
|
||||
)
|
||||
payload = re.sub(
|
||||
r"(<Line\d+)(><)(\/Line\d+>)", r"\1>Placeholder Text<\3", payload
|
||||
)
|
||||
varResponse = subVars(payload)
|
||||
subbedT = varResponse[0]
|
||||
else:
|
||||
|
|
@ -659,7 +725,7 @@ def translateGPT(text, history, fullPromptFlag, pbar, filename):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
continue
|
||||
|
||||
# Create Message
|
||||
|
|
|
|||
|
|
@ -15,49 +15,50 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .005
|
||||
OUTPUTAPICOST = .015
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.005
|
||||
OUTPUTAPICOST = 0.015
|
||||
BATCHSIZE = 40
|
||||
|
||||
|
||||
def handleJavascript(filename, estimate):
|
||||
global ESTIMATE
|
||||
ESTIMATE = estimate
|
||||
|
|
@ -74,17 +75,21 @@ def handleJavascript(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', TOKENS, None], end - start, '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
|
||||
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:
|
||||
with open(
|
||||
"translated/" + filename, "w", encoding="utf8", errors="ignore"
|
||||
) as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -97,23 +102,36 @@ def handleJavascript(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -122,21 +140,31 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='utf-8') as readFile:
|
||||
with open("files/" + filename, "r", encoding="utf-8") as readFile:
|
||||
translatedData = parseJS(readFile, filename)
|
||||
|
||||
|
||||
return translatedData
|
||||
|
||||
|
||||
def parseJS(readFile, filename):
|
||||
totalTokens = [0,0]
|
||||
totalTokens = [0, 0]
|
||||
data = readFile.readlines()
|
||||
|
||||
with tqdm(bar_format=BAR_FORMAT, position=POSITION, leave=LEAVE) as pbar:
|
||||
pbar.desc=filename
|
||||
pbar.desc = filename
|
||||
|
||||
try:
|
||||
result = translateJS(data, pbar)
|
||||
|
|
@ -147,8 +175,9 @@ def parseJS(readFile, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateJS(data, pbar):
|
||||
tokens = [0,0]
|
||||
tokens = [0, 0]
|
||||
i = 0
|
||||
|
||||
# Regex & Plugin Name
|
||||
|
|
@ -165,10 +194,14 @@ def translateJS(data, pbar):
|
|||
|
||||
# Remove Wordwrap [Optional]
|
||||
for j in range(len(modifiedStringList)):
|
||||
modifiedStringList[j] = modifiedStringList[j].replace(r'\\\\\\\\n', r' ')
|
||||
modifiedStringList[j] = modifiedStringList[j].replace(
|
||||
r"\\\\\\\\n", r" "
|
||||
)
|
||||
|
||||
# Translate
|
||||
response = translateGPT(modifiedStringList, f'Reply with the {LANGUAGE} translation', True, pbar)
|
||||
response = translateGPT(
|
||||
modifiedStringList, f"Reply with the {LANGUAGE} translation", True, pbar
|
||||
)
|
||||
translatedList = response[0]
|
||||
tokens[0] = response[1][0]
|
||||
tokens[0] = response[1][1]
|
||||
|
|
@ -181,82 +214,83 @@ def translateJS(data, pbar):
|
|||
|
||||
# Wordwrap [Optional]
|
||||
translatedList[j] = textwrap.fill(translatedList[j], LISTWIDTH)
|
||||
translatedList[j] = translatedList[j].replace('\n', r'\\\\\\\\n')
|
||||
translatedList[j] = translatedList[j].replace("\n", r"\\\\\\\\n")
|
||||
|
||||
# Set
|
||||
data[i] = data[i].replace(stringList[j], translatedList[j])
|
||||
# Mismatch
|
||||
else:
|
||||
pbar.write('Mismatch Error')
|
||||
pbar.write("Mismatch Error")
|
||||
i += 1
|
||||
|
||||
|
||||
return tokens
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'[\\]+[cC]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(color, "[Color_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '[Noun_' + str(count) + ']')
|
||||
jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(var, "[Var_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]', jaString)
|
||||
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) + ']')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -266,64 +300,70 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('[Nested_' + str(count) + ']', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
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"\
|
||||
"
|
||||
|
||||
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\
|
||||
|
|
@ -335,9 +375,11 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
|
|||
- `...` can be a part of the dialogue. Translate it as it is.\n\
|
||||
{VOCAB}\n\
|
||||
"
|
||||
user = f'{subbedT}'
|
||||
)
|
||||
user = f"{subbedT}"
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -350,9 +392,9 @@ def translateText(characters, system, user, history):
|
|||
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}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0.1,
|
||||
frequency_penalty=0.1,
|
||||
|
|
@ -361,15 +403,16 @@ def translateText(characters, system, user, history):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'Placeholder Text': ''
|
||||
f"{LANGUAGE} Translation: ": "",
|
||||
"Translation: ": "",
|
||||
"っ": "",
|
||||
"〜": "~",
|
||||
"ッ": "",
|
||||
"。": ".",
|
||||
"Placeholder Text": "",
|
||||
# Add more replacements as needed
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
|
|
@ -380,11 +423,12 @@ def cleanTranslatedText(translatedText, varResponse):
|
|||
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'(?<=(.))ー+'
|
||||
|
||||
pattern = r"(?<=(.))ー+"
|
||||
|
||||
# Define a replacement function that elongates the captured character
|
||||
def repl(match):
|
||||
char = match.group(1) # The character before the ー sequence
|
||||
|
|
@ -394,8 +438,9 @@ def elongateCharacters(text):
|
|||
# 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+>`?'
|
||||
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)
|
||||
|
|
@ -404,11 +449,12 @@ def extractTranslation(translatedTextList, is_list):
|
|||
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')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -420,15 +466,17 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag, pbar):
|
||||
mismatch = False
|
||||
|
|
@ -441,8 +489,12 @@ def translateGPT(text, history, fullPromptFlag, pbar):
|
|||
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'`<Line{i}>{item}</Line{i}>`' for i, item in enumerate(tItem)])
|
||||
payload = re.sub(r'(<Line\d+)(><)(\/Line\d+>)', r'\1>Placeholder Text<\3', payload)
|
||||
payload = "\n".join(
|
||||
[f"`<Line{i}>{item}</Line{i}>`" for i, item in enumerate(tItem)]
|
||||
)
|
||||
payload = re.sub(
|
||||
r"(<Line\d+)(><)(\/Line\d+>)", r"\1>Placeholder Text<\3", payload
|
||||
)
|
||||
varResponse = subVars(payload)
|
||||
subbedT = varResponse[0]
|
||||
else:
|
||||
|
|
@ -450,7 +502,7 @@ def translateGPT(text, history, fullPromptFlag, pbar):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
continue
|
||||
|
||||
# Create Message
|
||||
|
|
@ -488,7 +540,7 @@ def translateGPT(text, history, fullPromptFlag, pbar):
|
|||
if len(tItem) == len(extractedTranslations):
|
||||
tList[index] = extractedTranslations
|
||||
else:
|
||||
mismatch = True # Just here for breakpoint
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
# Create History
|
||||
history = tList[index] # Update history if we have a list
|
||||
|
|
|
|||
316
modules/json.py
316
modules/json.py
|
|
@ -16,49 +16,50 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .01
|
||||
OUTPUTAPICOST = .03
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.01
|
||||
OUTPUTAPICOST = 0.03
|
||||
BATCHSIZE = 50
|
||||
|
||||
|
||||
def handleJSON(filename, estimate):
|
||||
global ESTIMATE, totalTokens
|
||||
ESTIMATE = estimate
|
||||
|
|
@ -74,11 +75,11 @@ def handleJSON(filename, estimate):
|
|||
TOKENS[0] += translatedData[1][0]
|
||||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
return getResultString(['', TOKENS, None], end - start, 'TOTAL')
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
else:
|
||||
try:
|
||||
with open('translated/' + filename, 'w', encoding='UTF-8') as outFile:
|
||||
with open("translated/" + filename, "w", encoding="UTF-8") as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -90,36 +91,50 @@ def handleJSON(filename, estimate):
|
|||
TOKENS[0] += translatedData[1][0]
|
||||
TOKENS[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
return getResultString(['', TOKENS, None], end - start, 'TOTAL')
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='UTF-8-sig') as f:
|
||||
with open("files/" + filename, "r", encoding="UTF-8-sig") as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Map Files
|
||||
if '.json' in filename:
|
||||
if ".json" in filename:
|
||||
translatedData = parseJSON(data, filename)
|
||||
|
||||
else:
|
||||
raise NameError(filename + ' Not Supported')
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -128,18 +143,29 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def parseJSON(data, filename):
|
||||
totalTokens = [0, 0]
|
||||
totalLines = 0
|
||||
totalLines = len(data)
|
||||
global LOCK
|
||||
|
||||
with tqdm(bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE) as pbar:
|
||||
pbar.desc=filename
|
||||
pbar.total=totalLines
|
||||
|
||||
with tqdm(
|
||||
bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE
|
||||
) as pbar:
|
||||
pbar.desc = filename
|
||||
pbar.total = totalLines
|
||||
try:
|
||||
result = translateJSON(data, pbar)
|
||||
totalTokens[0] += result[0]
|
||||
|
|
@ -148,12 +174,13 @@ def parseJSON(data, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateJSON(data, pbar):
|
||||
textHistory = []
|
||||
batch = []
|
||||
maxHistory = MAXHISTORY
|
||||
tokens = [0, 0]
|
||||
speaker = 'None'
|
||||
speaker = "None"
|
||||
insertBool = False
|
||||
i = 0
|
||||
batchStartIndex = 0
|
||||
|
|
@ -161,35 +188,43 @@ def translateJSON(data, pbar):
|
|||
while i < len(data):
|
||||
item = data[i]
|
||||
# Speaker
|
||||
if 'name' in item:
|
||||
if item['name'] not in [None, '-']:
|
||||
response = getSpeaker(item['name'])
|
||||
if "name" in item:
|
||||
if item["name"] not in [None, "-"]:
|
||||
response = getSpeaker(item["name"])
|
||||
speaker = response[0]
|
||||
tokens[0] += response[1][0]
|
||||
tokens[1] += response[1][1]
|
||||
item['name'] = speaker
|
||||
item["name"] = speaker
|
||||
else:
|
||||
speaker = 'None'
|
||||
speaker = "None"
|
||||
pbar.update(1)
|
||||
i += 1
|
||||
|
||||
|
||||
# Text
|
||||
elif 'me' in item:
|
||||
for text in ['text', 'text2', 'help1', 'help2', 'help3', 'like', 'message', 'me']:
|
||||
elif "me" in item:
|
||||
for text in [
|
||||
"text",
|
||||
"text2",
|
||||
"help1",
|
||||
"help2",
|
||||
"help3",
|
||||
"like",
|
||||
"message",
|
||||
"me",
|
||||
]:
|
||||
if text in item:
|
||||
if item[text] != None:
|
||||
jaString = item[text]
|
||||
|
||||
# Remove any textwrap
|
||||
if FIXTEXTWRAP == True:
|
||||
finalJAString = jaString.replace('\n', ' ')
|
||||
finalJAString = jaString.replace("\n", " ")
|
||||
|
||||
# [Passthrough 1] Pulling From File
|
||||
if insertBool is False:
|
||||
# Append to List and Clear Values
|
||||
batch.append(finalJAString)
|
||||
speaker = ''
|
||||
speaker = ""
|
||||
|
||||
# Translate Batch if Full
|
||||
if len(batch) == BATCHSIZE:
|
||||
|
|
@ -207,7 +242,7 @@ def translateJSON(data, pbar):
|
|||
|
||||
# Mismatch
|
||||
else:
|
||||
pbar.write(f'Mismatch: {batchStartIndex} - {i}')
|
||||
pbar.write(f"Mismatch: {batchStartIndex} - {i}")
|
||||
MISMATCH.append(batch)
|
||||
batchStartIndex = i
|
||||
batch.clear()
|
||||
|
|
@ -215,7 +250,7 @@ def translateJSON(data, pbar):
|
|||
if insertBool is False:
|
||||
pbar.update(1)
|
||||
i += 1
|
||||
|
||||
|
||||
currentGroup = []
|
||||
|
||||
# [Passthrough 2] Setting Data
|
||||
|
|
@ -224,15 +259,15 @@ def translateJSON(data, pbar):
|
|||
translatedText = translatedBatch[0]
|
||||
|
||||
# Remove added speaker
|
||||
translatedText = re.sub(r'^.+?:\s', '', translatedText)
|
||||
translatedText = re.sub(r"^.+?:\s", "", translatedText)
|
||||
|
||||
# Textwrap
|
||||
translatedText = textwrap.fill(translatedText, width=WIDTH)
|
||||
|
||||
|
||||
# Set Text
|
||||
item[text] = translatedText
|
||||
translatedBatch.pop(0)
|
||||
speaker = ''
|
||||
speaker = ""
|
||||
currentGroup = []
|
||||
i += 1
|
||||
|
||||
|
|
@ -261,93 +296,99 @@ def translateJSON(data, pbar):
|
|||
|
||||
# Mismatch
|
||||
else:
|
||||
pbar.write(f'Mismatch: {batchStartIndex} - {i}')
|
||||
pbar.write(f"Mismatch: {batchStartIndex} - {i}")
|
||||
MISMATCH.append(batch)
|
||||
batchStartIndex = i
|
||||
batch.clear()
|
||||
|
||||
currentGroup = []
|
||||
return tokens
|
||||
return tokens
|
||||
|
||||
|
||||
# Save some money and enter the character before translation
|
||||
def getSpeaker(speaker):
|
||||
match speaker:
|
||||
case 'セレナ':
|
||||
return ['Serena', [0,0]]
|
||||
case 'レナ':
|
||||
return ['Rena', [0,0]]
|
||||
case 'フィルス':
|
||||
return ['Phils', [0,0]]
|
||||
case 'レイン':
|
||||
return ['Meryl', [0,0]]
|
||||
case "セレナ":
|
||||
return ["Serena", [0, 0]]
|
||||
case "レナ":
|
||||
return ["Rena", [0, 0]]
|
||||
case "フィルス":
|
||||
return ["Phils", [0, 0]]
|
||||
case "レイン":
|
||||
return ["Meryl", [0, 0]]
|
||||
case _:
|
||||
return translateGPT(speaker, 'Reply with only the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
return translateGPT(
|
||||
speaker,
|
||||
"Reply with only the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(icon, "{Nested_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(icon, "{Ascii_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'[\\]+[cC]\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(color, "{Color_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '{Noun_' + str(count) + '}')
|
||||
jaString = jaString.replace(name, "{Noun_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(var, "{Var_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[.+?\]', jaString)
|
||||
formatList = re.findall(r"[\\]+[\w]+\[.+?\]", jaString)
|
||||
formatList = set(formatList)
|
||||
if len(formatList) != 0:
|
||||
for var in formatList:
|
||||
jaString = jaString.replace(var, '{FCode_' + str(count) + '}')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -357,54 +398,58 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('{Nested_' + str(count) + '}', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
ルナリア (Lunaria) - Female\n\
|
||||
ソニア (Sonia) - Female\n\
|
||||
マナ (Mana) - Female\n\
|
||||
|
|
@ -419,10 +464,12 @@ def createContext(fullPromptFlag, subbedT):
|
|||
ツキハ (Tsukiha) - Female\n\
|
||||
フィリカ (Filica) - Female\n\
|
||||
レノ (Renno) - Female\n\
|
||||
'
|
||||
|
||||
system = PROMPT if fullPromptFlag else \
|
||||
f"\
|
||||
"
|
||||
|
||||
system = (
|
||||
PROMPT
|
||||
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\
|
||||
|
|
@ -439,9 +486,11 @@ I will give you lines of text, and you must translate each line to the best of y
|
|||
- Translate 'よかった' as 'thank goodness'\n\
|
||||
Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{LANGUAGE.upper()}_TRANSLATION>`\
|
||||
"
|
||||
user = f'{subbedT}'
|
||||
)
|
||||
user = f"{subbedT}"
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -454,9 +503,9 @@ def translateText(characters, system, user, history):
|
|||
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}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0.1,
|
||||
frequency_penalty=0.1,
|
||||
|
|
@ -466,37 +515,44 @@ def translateText(characters, system, user, history):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'Placeholder Text': ''
|
||||
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.split('\n') if line]
|
||||
return [line for line in translatedText.split("\n") if line]
|
||||
|
||||
|
||||
def extractTranslation(translatedTextList, is_list):
|
||||
pattern = r'`?<Line(\d+)>([\\]*.*?[\\]*?)<\/?Line\d+>`?'
|
||||
pattern = r"`?<Line(\d+)>([\\]*.*?[\\]*?)<\/?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)]
|
||||
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')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -508,15 +564,17 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
totalTokens = [0, 0]
|
||||
|
|
@ -528,8 +586,10 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
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'`<Line{i}>{item}</Line{i}>`' for i, item in enumerate(tItem)])
|
||||
payload = payload.replace('``', '`Placeholder Text`')
|
||||
payload = "\n".join(
|
||||
[f"`<Line{i}>{item}</Line{i}>`" for i, item in enumerate(tItem)]
|
||||
)
|
||||
payload = payload.replace("``", "`Placeholder Text`")
|
||||
varResponse = subVars(payload)
|
||||
subbedT = varResponse[0]
|
||||
else:
|
||||
|
|
@ -537,7 +597,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
continue
|
||||
|
||||
# Create Message
|
||||
|
|
@ -562,12 +622,14 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
extractedTranslations = extractTranslation(translatedTextList, True)
|
||||
tList[index] = extractedTranslations
|
||||
if len(tItem) != len(translatedTextList):
|
||||
mismatch = True # Just here so breakpoint can be set
|
||||
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)
|
||||
extractedTranslations = extractTranslation(
|
||||
"\n".join(translatedTextList), False
|
||||
)
|
||||
tList[index] = extractedTranslations
|
||||
|
||||
finalList = combineList(tList, text)
|
||||
return [finalList, totalTokens]
|
||||
return [finalList, totalTokens]
|
||||
|
|
|
|||
|
|
@ -15,49 +15,50 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .01
|
||||
OUTPUTAPICOST = .03
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.01
|
||||
OUTPUTAPICOST = 0.03
|
||||
BATCHSIZE = 10
|
||||
|
||||
|
||||
def handleKansen(filename, estimate):
|
||||
global ESTIMATE
|
||||
ESTIMATE = estimate
|
||||
|
|
@ -74,17 +75,21 @@ def handleKansen(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', TOKENS, None], end - start, '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
|
||||
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:
|
||||
with open(
|
||||
"translated/" + filename, "w", encoding="shift_jis", errors="ignore"
|
||||
) as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -97,23 +102,36 @@ def handleKansen(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -122,33 +140,45 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='cp932') as readFile:
|
||||
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':
|
||||
if line != "\\d\n":
|
||||
finalData.append(line)
|
||||
translatedData[0] = finalData
|
||||
|
||||
|
||||
return translatedData
|
||||
|
||||
|
||||
def parseTyrano(readFile, filename):
|
||||
totalTokens = [0,0]
|
||||
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
|
||||
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)
|
||||
|
|
@ -159,13 +189,14 @@ def parseTyrano(readFile, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateTyrano(data, pbar, totalLines):
|
||||
textHistory = []
|
||||
batch = []
|
||||
currentGroup = []
|
||||
maxHistory = MAXHISTORY
|
||||
tokens = [0,0]
|
||||
speaker = ''
|
||||
tokens = [0, 0]
|
||||
speaker = ""
|
||||
insertBool = False
|
||||
global LOCK, ESTIMATE
|
||||
i = 0
|
||||
|
|
@ -173,108 +204,118 @@ def translateTyrano(data, pbar, totalLines):
|
|||
|
||||
while i < len(data):
|
||||
# Speaker
|
||||
if '[ns]' in data[i]:
|
||||
matchList = re.findall(r'\[ns\](.+?)\[', data[i])
|
||||
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'
|
||||
data[i] = "[ns]" + speaker + "[nse]\n"
|
||||
else:
|
||||
speaker = ''
|
||||
speaker = ""
|
||||
|
||||
# Choices
|
||||
elif '[sel' in data[i]:
|
||||
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)
|
||||
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)
|
||||
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']
|
||||
charList = [".", '"', "\\n"]
|
||||
for char in charList:
|
||||
translatedText = translatedText.replace(char, '')
|
||||
translatedText = translatedText.replace(char, "")
|
||||
|
||||
# Escape all '
|
||||
translatedText = translatedText.replace('\\', '')
|
||||
translatedText = translatedText.replace("\\", "")
|
||||
# translatedText = translatedText.replace("'", "\\\'")
|
||||
|
||||
# Set Data
|
||||
translatedText = data[i].replace(originalText, translatedText)
|
||||
data[i] = translatedText
|
||||
data[i] = translatedText
|
||||
|
||||
# Lines
|
||||
matchList = re.findall(r'(.+?)\[[rpcms_sel]+\]$', data[i])
|
||||
matchList = re.findall(r"(.+?)\[[rpcms_sel]+\]$", data[i])
|
||||
if len(matchList) > 0:
|
||||
if 'hisout' in 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 len(data) > i + 1:
|
||||
while "[r]" in data[i + 1]:
|
||||
if insertBool is True:
|
||||
data[i] = r'\d\n'
|
||||
data[i] = r"\d\n"
|
||||
pbar.update(1)
|
||||
i += 1
|
||||
matchList = re.findall(r'(.+?)\[r\]', data[i])
|
||||
matchList = re.findall(r"(.+?)\[r\]", data[i])
|
||||
if len(matchList) > 0:
|
||||
currentGroup.append(matchList[0])
|
||||
while '[pcms]' in data[i+1]:
|
||||
while "[pcms]" in data[i + 1]:
|
||||
if insertBool is True:
|
||||
data[i] = r'\d\n'
|
||||
data[i] = r"\d\n"
|
||||
pbar.update(1)
|
||||
i += 1
|
||||
matchList = re.findall(r'(.+?)\[pcms\]', data[i])
|
||||
matchList = re.findall(r"(.+?)\[pcms\]", data[i])
|
||||
if len(matchList) > 0:
|
||||
currentGroup.append(matchList[0])
|
||||
while '[pcms_sel]' in data[i+1]:
|
||||
while "[pcms_sel]" in data[i + 1]:
|
||||
if insertBool is True:
|
||||
data[i] = r'\d\n'
|
||||
data[i] = r"\d\n"
|
||||
pbar.update(1)
|
||||
i += 1
|
||||
matchList = re.findall(r'(.+?)\[pcms_sel\]', data[i])
|
||||
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)
|
||||
finalJAString = " ".join(currentGroup)
|
||||
oldjaString = finalJAString
|
||||
|
||||
# Remove any textwrap
|
||||
if FIXTEXTWRAP == True:
|
||||
finalJAString = finalJAString.replace('[r]', ' ')
|
||||
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(' ', ' ')
|
||||
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)
|
||||
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}'
|
||||
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 = ''
|
||||
speaker = ""
|
||||
|
||||
# Translate Batch if Full
|
||||
if len(batch) == BATCHSIZE:
|
||||
|
|
@ -292,7 +333,7 @@ def translateTyrano(data, pbar, totalLines):
|
|||
|
||||
# Mismatch
|
||||
else:
|
||||
pbar.write(f'Mismatch: {batchStartIndex} - {i}')
|
||||
pbar.write(f"Mismatch: {batchStartIndex} - {i}")
|
||||
MISMATCH.append(batch)
|
||||
batchStartIndex = i
|
||||
batch.clear()
|
||||
|
|
@ -306,31 +347,31 @@ def translateTyrano(data, pbar, totalLines):
|
|||
else:
|
||||
# Get Text
|
||||
translatedText = translatedBatch[0]
|
||||
translatedText = translatedText.replace('\\"', '\"')
|
||||
translatedText = translatedText.replace('[', '(')
|
||||
translatedText = translatedText.replace(']', ')')
|
||||
translatedText = translatedText.replace('\\"', '"')
|
||||
translatedText = translatedText.replace("[", "(")
|
||||
translatedText = translatedText.replace("]", ")")
|
||||
|
||||
# Remove added speaker
|
||||
translatedText = re.sub(r'^.+?:\s', '', translatedText)
|
||||
translatedText = re.sub(r"^.+?:\s", "", translatedText)
|
||||
|
||||
# Textwrap
|
||||
translatedText = textwrap.fill(translatedText, width=WIDTH)
|
||||
textList = translatedText.split('\n')
|
||||
|
||||
textList = translatedText.split("\n")
|
||||
|
||||
# Set Text
|
||||
data[i] = r'\d\n'
|
||||
data[i] = r"\d\n"
|
||||
for line in textList:
|
||||
# Wordwrap Text
|
||||
if '[r]' not in line:
|
||||
if "[r]" not in line:
|
||||
line = textwrap.fill(line, width=WIDTH)
|
||||
line = line.replace('\n', '[r]')
|
||||
|
||||
line = line.replace("\n", "[r]")
|
||||
|
||||
# Set
|
||||
data.insert(i, line.strip() + '[r]\n')
|
||||
i+=1
|
||||
data[i-1] = data[i-1].replace('[r]', '[pcms]')
|
||||
data.insert(i, line.strip() + "[r]\n")
|
||||
i += 1
|
||||
data[i - 1] = data[i - 1].replace("[r]", "[pcms]")
|
||||
translatedBatch.pop(0)
|
||||
speaker = ''
|
||||
speaker = ""
|
||||
currentGroup = []
|
||||
|
||||
# If Batch is empty. Move on.
|
||||
|
|
@ -361,7 +402,7 @@ def translateTyrano(data, pbar, totalLines):
|
|||
|
||||
# Mismatch
|
||||
else:
|
||||
pbar.write(f'Mismatch: {batchStartIndex} - {i}')
|
||||
pbar.write(f"Mismatch: {batchStartIndex} - {i}")
|
||||
MISMATCH.append(batch)
|
||||
batchStartIndex = i
|
||||
batch.clear()
|
||||
|
|
@ -369,92 +410,99 @@ def translateTyrano(data, pbar, totalLines):
|
|||
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 ["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)
|
||||
|
||||
return translateGPT(
|
||||
speaker,
|
||||
"Reply with only the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(icon, "{Nested_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(icon, "{Ascii_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'[\\]+[cC]\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(color, "{Color_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '{Noun_' + str(count) + '}')
|
||||
jaString = jaString.replace(name, "{Noun_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(var, "{Var_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[.+?\]', jaString)
|
||||
formatList = re.findall(r"[\\]+[\w]+\[.+?\]", jaString)
|
||||
formatList = set(formatList)
|
||||
if len(formatList) != 0:
|
||||
for var in formatList:
|
||||
jaString = jaString.replace(var, '{FCode_' + str(count) + '}')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -464,54 +512,58 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('{Nested_' + str(count) + '}', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
渋江 央 (Shibue Akira) - Male\n\
|
||||
蘆名 累 (Ashina Rui) - Female\n\
|
||||
清原 梨里 (Kiyohara Riri) - Female\n\
|
||||
|
|
@ -520,19 +572,23 @@ def createContext(fullPromptFlag, subbedT):
|
|||
須田 (Suda) - Male\n\
|
||||
高橋 (Takahashi) - Female\n\
|
||||
勇二 (Yuuji) - Male\n\
|
||||
'
|
||||
|
||||
system = PROMPT + VOCAB if fullPromptFlag else \
|
||||
f"\
|
||||
"
|
||||
|
||||
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}'
|
||||
)
|
||||
user = f"{subbedT}"
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -545,9 +601,9 @@ def translateText(characters, system, user, history):
|
|||
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}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0.1,
|
||||
frequency_penalty=0.1,
|
||||
|
|
@ -557,37 +613,44 @@ def translateText(characters, system, user, history):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'Placeholder Text': ''
|
||||
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]
|
||||
return [line for line in translatedText.replace("\\n", "\n").split("\n") if line]
|
||||
|
||||
|
||||
def extractTranslation(translatedTextList, is_list):
|
||||
pattern = r'`?<Line(\d+)>([\\]*.*?[\\]*?)<\/?Line\d+>`?'
|
||||
pattern = r"`?<Line(\d+)>([\\]*.*?[\\]*?)<\/?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)]
|
||||
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')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -599,15 +662,17 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
totalTokens = [0, 0]
|
||||
|
|
@ -619,8 +684,10 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
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'`<Line{i}>{item}</Line{i}>`' for i, item in enumerate(tItem)])
|
||||
payload = payload.replace('``', '`Placeholder Text`')
|
||||
payload = "\n".join(
|
||||
[f"`<Line{i}>{item}</Line{i}>`" for i, item in enumerate(tItem)]
|
||||
)
|
||||
payload = payload.replace("``", "`Placeholder Text`")
|
||||
varResponse = subVars(payload)
|
||||
subbedT = varResponse[0]
|
||||
else:
|
||||
|
|
@ -628,7 +695,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
continue
|
||||
|
||||
# Create Message
|
||||
|
|
@ -653,11 +720,13 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
extractedTranslations = extractTranslation(translatedTextList, True)
|
||||
tList[index] = extractedTranslations
|
||||
if len(tItem) != len(translatedTextList):
|
||||
mismatch = True # Just here so breakpoint can be set
|
||||
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)
|
||||
extractedTranslations = extractTranslation(
|
||||
"\n".join(translatedTextList), False
|
||||
)
|
||||
tList[index] = extractedTranslations
|
||||
|
||||
finalList = combineList(tList, text)
|
||||
|
|
|
|||
318
modules/lune.py
318
modules/lune.py
|
|
@ -16,49 +16,50 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .01
|
||||
OUTPUTAPICOST = .03
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.01
|
||||
OUTPUTAPICOST = 0.03
|
||||
BATCHSIZE = 50
|
||||
|
||||
|
||||
def handleLune(filename, estimate):
|
||||
global ESTIMATE, totalTokens
|
||||
ESTIMATE = estimate
|
||||
|
|
@ -74,11 +75,11 @@ def handleLune(filename, estimate):
|
|||
TOKENS[0] += translatedData[1][0]
|
||||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
return getResultString(['', TOKENS, None], end - start, 'TOTAL')
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
else:
|
||||
try:
|
||||
with open('translated/' + filename, 'w', encoding='UTF-8') as outFile:
|
||||
with open("translated/" + filename, "w", encoding="UTF-8") as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -90,36 +91,50 @@ def handleLune(filename, estimate):
|
|||
TOKENS[0] += translatedData[1][0]
|
||||
TOKENS[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
return getResultString(['', TOKENS, None], end - start, 'TOTAL')
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='UTF-8-sig') as f:
|
||||
with open("files/" + filename, "r", encoding="UTF-8-sig") as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Map Files
|
||||
if '.json' in filename:
|
||||
if ".json" in filename:
|
||||
translatedData = parseJSON(data, filename)
|
||||
|
||||
else:
|
||||
raise NameError(filename + ' Not Supported')
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -128,18 +143,29 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def parseJSON(data, filename):
|
||||
totalTokens = [0, 0]
|
||||
totalLines = 0
|
||||
totalLines = len(data)
|
||||
global LOCK
|
||||
|
||||
with tqdm(bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE) as pbar:
|
||||
pbar.desc=filename
|
||||
pbar.total=totalLines
|
||||
|
||||
with tqdm(
|
||||
bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE
|
||||
) as pbar:
|
||||
pbar.desc = filename
|
||||
pbar.total = totalLines
|
||||
try:
|
||||
result = translateJSON(data, pbar)
|
||||
totalTokens[0] += result[0]
|
||||
|
|
@ -148,12 +174,13 @@ def parseJSON(data, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateJSON(data, pbar):
|
||||
textHistory = []
|
||||
batch = []
|
||||
maxHistory = MAXHISTORY
|
||||
tokens = [0, 0]
|
||||
speaker = 'None'
|
||||
speaker = "None"
|
||||
insertBool = False
|
||||
i = 0
|
||||
batchStartIndex = 0
|
||||
|
|
@ -161,32 +188,41 @@ def translateJSON(data, pbar):
|
|||
while i < len(data):
|
||||
item = data[i]
|
||||
# Speaker
|
||||
if 'name' in item:
|
||||
if item['name'] not in [None, '-']:
|
||||
response = getSpeaker(item['name'])
|
||||
if "name" in item:
|
||||
if item["name"] not in [None, "-"]:
|
||||
response = getSpeaker(item["name"])
|
||||
speaker = response[0]
|
||||
tokens[0] += response[1][0]
|
||||
tokens[1] += response[1][1]
|
||||
item['name'] = speaker
|
||||
item["name"] = speaker
|
||||
else:
|
||||
speaker = 'None'
|
||||
speaker = "None"
|
||||
|
||||
# Text
|
||||
if 'message' in item:
|
||||
for text in ['text', 'text2', 'help1', 'help2', 'help3', 'like', 'message', 'me']:
|
||||
if "message" in item:
|
||||
for text in [
|
||||
"text",
|
||||
"text2",
|
||||
"help1",
|
||||
"help2",
|
||||
"help3",
|
||||
"like",
|
||||
"message",
|
||||
"me",
|
||||
]:
|
||||
if text in item:
|
||||
if item[text] != None:
|
||||
jaString = item[text]
|
||||
|
||||
# Remove any textwrap
|
||||
if FIXTEXTWRAP == True:
|
||||
finalJAString = jaString.replace('\n', ' ')
|
||||
finalJAString = jaString.replace("\n", " ")
|
||||
|
||||
# [Passthrough 1] Pulling From File
|
||||
if insertBool is False:
|
||||
# Append to List and Clear Values
|
||||
batch.append(finalJAString)
|
||||
speaker = ''
|
||||
speaker = ""
|
||||
|
||||
# Translate Batch if Full
|
||||
if len(batch) == BATCHSIZE:
|
||||
|
|
@ -204,7 +240,7 @@ def translateJSON(data, pbar):
|
|||
|
||||
# Mismatch
|
||||
else:
|
||||
pbar.write(f'Mismatch: {batchStartIndex} - {i}')
|
||||
pbar.write(f"Mismatch: {batchStartIndex} - {i}")
|
||||
MISMATCH.append(batch)
|
||||
batchStartIndex = i
|
||||
batch.clear()
|
||||
|
|
@ -212,7 +248,7 @@ def translateJSON(data, pbar):
|
|||
if insertBool is False:
|
||||
pbar.update(1)
|
||||
i += 1
|
||||
|
||||
|
||||
currentGroup = []
|
||||
|
||||
# [Passthrough 2] Setting Data
|
||||
|
|
@ -221,15 +257,15 @@ def translateJSON(data, pbar):
|
|||
translatedText = translatedBatch[0]
|
||||
|
||||
# Remove added speaker
|
||||
translatedText = re.sub(r'^.+?:\s', '', translatedText)
|
||||
translatedText = re.sub(r"^.+?:\s", "", translatedText)
|
||||
|
||||
# Textwrap
|
||||
translatedText = textwrap.fill(translatedText, width=WIDTH)
|
||||
|
||||
|
||||
# Set Text
|
||||
item[text] = translatedText
|
||||
translatedBatch.pop(0)
|
||||
speaker = ''
|
||||
speaker = ""
|
||||
currentGroup = []
|
||||
i += 1
|
||||
|
||||
|
|
@ -258,92 +294,99 @@ def translateJSON(data, pbar):
|
|||
|
||||
# Mismatch
|
||||
else:
|
||||
pbar.write(f'Mismatch: {batchStartIndex} - {i}')
|
||||
pbar.write(f"Mismatch: {batchStartIndex} - {i}")
|
||||
MISMATCH.append(batch)
|
||||
batchStartIndex = i
|
||||
batch.clear()
|
||||
|
||||
currentGroup = []
|
||||
return tokens
|
||||
return tokens
|
||||
|
||||
|
||||
# Save some money and enter the character before translation
|
||||
def getSpeaker(speaker):
|
||||
match speaker:
|
||||
case 'セレナ':
|
||||
return ['Serena', [0,0]]
|
||||
case 'レナ':
|
||||
return ['Rena', [0,0]]
|
||||
case 'フィルス':
|
||||
return ['Phils', [0,0]]
|
||||
case 'レイン':
|
||||
return ['Meryl', [0,0]]
|
||||
case "セレナ":
|
||||
return ["Serena", [0, 0]]
|
||||
case "レナ":
|
||||
return ["Rena", [0, 0]]
|
||||
case "フィルス":
|
||||
return ["Phils", [0, 0]]
|
||||
case "レイン":
|
||||
return ["Meryl", [0, 0]]
|
||||
case _:
|
||||
return translateGPT(speaker, 'Reply with only the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
return translateGPT(
|
||||
speaker,
|
||||
"Reply with only the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(icon, "{Nested_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(icon, "{Ascii_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'[\\]+[cC]\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(color, "{Color_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '{Noun_' + str(count) + '}')
|
||||
jaString = jaString.replace(name, "{Noun_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + '}')
|
||||
jaString = jaString.replace(var, "{Var_" + str(count) + "}")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[.+?\]', jaString)
|
||||
formatList = re.findall(r"[\\]+[\w]+\[.+?\]", jaString)
|
||||
formatList = set(formatList)
|
||||
if len(formatList) != 0:
|
||||
for var in formatList:
|
||||
jaString = jaString.replace(var, '{FCode_' + str(count) + '}')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -353,54 +396,58 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('{Nested_' + str(count) + '}', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
林つかさ (Tsukasa Hayashi) - Female\n\
|
||||
山田美兎 (Miyato Yamada) - Female\n\
|
||||
鈴木赤音 (Akane Suzuki) - Female\n\
|
||||
|
|
@ -413,13 +460,17 @@ def createContext(fullPromptFlag, subbedT):
|
|||
モリー・ボイド (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}'
|
||||
"
|
||||
|
||||
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}]
|
||||
|
|
@ -432,9 +483,9 @@ def translateText(characters, system, user, history):
|
|||
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}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0.1,
|
||||
frequency_penalty=0.1,
|
||||
|
|
@ -443,40 +494,47 @@ def translateText(characters, system, user, history):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'Placeholder Text': ''
|
||||
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]
|
||||
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]
|
||||
return [line for line in translatedText.split("\\n") if line]
|
||||
|
||||
|
||||
def extractTranslation(translatedTextList, is_list):
|
||||
pattern = r'<Line(\d+)>[\\]*`?(.*?)[\\]*?`?</?Line\d+>'
|
||||
pattern = r"<Line(\d+)>[\\]*`?(.*?)[\\]*?`?</?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)]
|
||||
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')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -488,15 +546,17 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
totalTokens = [0, 0]
|
||||
|
|
@ -508,8 +568,10 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
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'<Line{i}>`{item}`</Line{i}>' for i, item in enumerate(tItem)])
|
||||
payload = payload.replace('``', '`Placeholder Text`')
|
||||
payload = "\n".join(
|
||||
[f"<Line{i}>`{item}`</Line{i}>" for i, item in enumerate(tItem)]
|
||||
)
|
||||
payload = payload.replace("``", "`Placeholder Text`")
|
||||
varResponse = subVars(payload)
|
||||
subbedT = varResponse[0]
|
||||
else:
|
||||
|
|
@ -517,7 +579,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
continue
|
||||
|
||||
# Create Message
|
||||
|
|
@ -542,11 +604,13 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
extractedTranslations = extractTranslation(translatedTextList, True)
|
||||
tList[index] = extractedTranslations
|
||||
if len(tItem) != len(translatedTextList):
|
||||
mismatch = True # Just here so breakpoint can be set
|
||||
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)
|
||||
extractedTranslations = extractTranslation(
|
||||
"\n".join(translatedTextList), False
|
||||
)
|
||||
tList[index] = extractedTranslations
|
||||
|
||||
finalList = combineList(tList, text)
|
||||
|
|
|
|||
|
|
@ -10,13 +10,27 @@ from dotenv import load_dotenv
|
|||
# upon import, in which case if they are unset the script will crash before we can output these messages.
|
||||
envMissing = False
|
||||
load_dotenv()
|
||||
for env in ['api','key','organization','model','language','timeout','fileThreads','threads','width','listWidth']:
|
||||
if os.getenv(env) is None or str(os.getenv(env))[:1] == '<':
|
||||
tqdm.write(Fore.RED + f'Environment variable {env} is not set!')
|
||||
for env in [
|
||||
"api",
|
||||
"key",
|
||||
"organization",
|
||||
"model",
|
||||
"language",
|
||||
"timeout",
|
||||
"fileThreads",
|
||||
"threads",
|
||||
"width",
|
||||
"listWidth",
|
||||
]:
|
||||
if os.getenv(env) is None or str(os.getenv(env))[:1] == "<":
|
||||
tqdm.write(Fore.RED + f"Environment variable {env} is not set!")
|
||||
envMissing = True
|
||||
if envMissing:
|
||||
tqdm.write(Fore.RED + 'Some of the required environment values may not be set correctly. You can set \
|
||||
these values using an .env file, for an example see .env.example')
|
||||
tqdm.write(
|
||||
Fore.RED
|
||||
+ "Some of the required environment values may not be set correctly. You can set \
|
||||
these values using an .env file, for an example see .env.example"
|
||||
)
|
||||
|
||||
from modules.rpgmakermvmz import handleMVMZ
|
||||
from modules.rpgmakerace import handleACE
|
||||
|
|
@ -40,7 +54,7 @@ from modules.rpgmakerplugin import handlePlugin
|
|||
|
||||
# For GPT4 rate limit will be hit if you have more than 1 thread.
|
||||
# 1 Thread for each file. Controls how many files are worked on at once.
|
||||
THREADS = int(os.getenv('fileThreads'))
|
||||
THREADS = int(os.getenv("fileThreads"))
|
||||
|
||||
# [Display name, file extension, handle function]
|
||||
MODULES = [
|
||||
|
|
@ -66,59 +80,76 @@ MODULES = [
|
|||
]
|
||||
|
||||
# Info Message
|
||||
tqdm.write(Fore.LIGHTYELLOW_EX + "WARNING: Once the translation starts do not close it unless you want to lose your \
|
||||
tqdm.write(
|
||||
Fore.LIGHTYELLOW_EX
|
||||
+ "WARNING: Once the translation starts do not close it unless you want to lose your \
|
||||
translated data. If a file fails or gets stuck, translated lines will remain translated so you don't have \
|
||||
to worry about being charged twice. You can simply copy the file generated in /translations back over to \
|
||||
/files and start the script again. It will skip over any translated text." + Fore.RESET, end='\n\n')
|
||||
/files and start the script again. It will skip over any translated text."
|
||||
+ Fore.RESET,
|
||||
end="\n\n",
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
estimate = ''
|
||||
while estimate == '':
|
||||
estimate = input('Select Translation or Cost Estimation:\n\n 1. Translate\n 2. Estimate\n')
|
||||
estimate = ""
|
||||
while estimate == "":
|
||||
estimate = input(
|
||||
"Select Translation or Cost Estimation:\n\n 1. Translate\n 2. Estimate\n"
|
||||
)
|
||||
match estimate:
|
||||
case '1':
|
||||
case "1":
|
||||
estimate = False
|
||||
case '2':
|
||||
case "2":
|
||||
estimate = True
|
||||
case _:
|
||||
estimate = ''
|
||||
|
||||
version = ''
|
||||
estimate = ""
|
||||
|
||||
version = ""
|
||||
while True:
|
||||
tqdm.write("Select game engine:\n")
|
||||
for position, module in enumerate(MODULES):
|
||||
tqdm.write(f'{str(position + 1).rjust(2)}. {module[0]} (.{module[1]})')
|
||||
tqdm.write(f"{str(position + 1).rjust(2)}. {module[0]} (.{module[1]})")
|
||||
version = input()
|
||||
try:
|
||||
version = int(version) - 1
|
||||
except:
|
||||
continue
|
||||
if version in range(len(MODULES)):
|
||||
break
|
||||
break
|
||||
|
||||
totalCost = Fore.RED + 'Translation module didn\'t return the total cost. Make sure the \
|
||||
files to translate are in the /files folder and that you picked the right game engine.'
|
||||
totalCost = (
|
||||
Fore.RED
|
||||
+ "Translation module didn't return the total cost. Make sure the \
|
||||
files to translate are in the /files folder and that you picked the right game engine."
|
||||
)
|
||||
|
||||
# Open File (Threads)
|
||||
with ThreadPoolExecutor(max_workers=THREADS) as executor:
|
||||
futures = [executor.submit(MODULES[version][2], filename, estimate) \
|
||||
for filename in os.listdir("files") if filename.endswith(MODULES[version][1]) and filename != '.gitkeep']
|
||||
futures = [
|
||||
executor.submit(MODULES[version][2], filename, estimate)
|
||||
for filename in os.listdir("files")
|
||||
if filename.endswith(MODULES[version][1]) and filename != ".gitkeep"
|
||||
]
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
totalCost = future.result()
|
||||
except Exception as e:
|
||||
tracebackLineNo = str(traceback.extract_tb(sys.exc_info()[2])[-1].lineno)
|
||||
tqdm.write(Fore.RED + str(e) + '|' + tracebackLineNo + Fore.RESET)
|
||||
tracebackLineNo = str(
|
||||
traceback.extract_tb(sys.exc_info()[2])[-1].lineno
|
||||
)
|
||||
tqdm.write(Fore.RED + str(e) + "|" + tracebackLineNo + Fore.RESET)
|
||||
|
||||
if totalCost != 'Fail':
|
||||
if totalCost != "Fail":
|
||||
if estimate is False:
|
||||
# This is to encourage people to grab what's in /translated instead
|
||||
deleteFolderFiles('files')
|
||||
deleteFolderFiles("files")
|
||||
|
||||
tqdm.write(str(totalCost))
|
||||
|
||||
|
||||
def deleteFolderFiles(folderPath):
|
||||
for filename in os.listdir(folderPath):
|
||||
file_path = os.path.join(folderPath, filename)
|
||||
if file_path.endswith(('.json', '.yaml', '.ks')):
|
||||
os.remove(file_path)
|
||||
if file_path.endswith((".json", ".yaml", ".ks")):
|
||||
os.remove(file_path)
|
||||
|
|
|
|||
|
|
@ -16,57 +16,58 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
PBAR = None
|
||||
FILENAME = None
|
||||
|
||||
# Full Width
|
||||
ascii_to_wide = dict((i, chr(i + 0xfee0)) for i in range(0x21, 0x7f))
|
||||
ascii_to_wide.update({0x20: u'\u3000', 0x2D: u'\u2212'}) # space and minus
|
||||
wide_to_ascii = dict((i, chr(i - 0xfee0)) for i in range(0xff01, 0xff5f))
|
||||
wide_to_ascii.update({0x3000: u' ', 0x2212: u'-'}) # space and minus
|
||||
ascii_to_wide = dict((i, chr(i + 0xFEE0)) for i in range(0x21, 0x7F))
|
||||
ascii_to_wide.update({0x20: "\u3000", 0x2D: "\u2212"}) # space and minus
|
||||
wide_to_ascii = dict((i, chr(i - 0xFEE0)) for i in range(0xFF01, 0xFF5F))
|
||||
wide_to_ascii.update({0x3000: " ", 0x2212: "-"}) # space and minus
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .005
|
||||
OUTPUTAPICOST = .015
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.005
|
||||
OUTPUTAPICOST = 0.015
|
||||
BATCHSIZE = 40
|
||||
|
||||
|
||||
def handleOnscripter(filename, estimate):
|
||||
global ESTIMATE, FILENAME
|
||||
ESTIMATE = estimate
|
||||
|
|
@ -84,17 +85,21 @@ def handleOnscripter(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', TOKENS, None], end - start, '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
|
||||
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:
|
||||
with open(
|
||||
"translated/" + filename, "w", encoding="cp932", errors="ignore"
|
||||
) as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -107,23 +112,36 @@ def handleOnscripter(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -132,31 +150,41 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='cp932') as readFile:
|
||||
with open("files/" + filename, "r", encoding="cp932") as readFile:
|
||||
translatedData = parseOnscripter(readFile, filename)
|
||||
|
||||
# Delete lines marked for deletion
|
||||
finalData = []
|
||||
for line in translatedData[0]:
|
||||
if line != '\\d\n':
|
||||
if line != "\\d\n":
|
||||
finalData.append(line)
|
||||
translatedData[0] = finalData
|
||||
|
||||
|
||||
return translatedData
|
||||
|
||||
|
||||
def parseOnscripter(readFile, filename):
|
||||
totalTokens = [0,0]
|
||||
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
|
||||
pbar.desc = filename
|
||||
|
||||
try:
|
||||
result = translateOnscripter(data, pbar, filename, [])
|
||||
|
|
@ -167,11 +195,12 @@ def parseOnscripter(readFile, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateOnscripter(data, pbar, filename, translatedList):
|
||||
stringList = []
|
||||
currentGroup = []
|
||||
tokens = [0,0]
|
||||
speaker = ''
|
||||
tokens = [0, 0]
|
||||
speaker = ""
|
||||
voice = False
|
||||
global LOCK, ESTIMATE, PBAR
|
||||
PBAR = pbar
|
||||
|
|
@ -180,38 +209,38 @@ def translateOnscripter(data, pbar, filename, translatedList):
|
|||
# Dialogue
|
||||
while i < len(data):
|
||||
# Lines
|
||||
regex = r'^([\u3000「(【[][^\n]+)'
|
||||
regex = r"^([\u3000「(【[][^\n]+)"
|
||||
match = re.search(regex, data[i])
|
||||
if match != None and match.group(1) != '':
|
||||
if match != None and match.group(1) != "":
|
||||
originalString = match.group(1)
|
||||
# Pass 1
|
||||
if translatedList == []:
|
||||
# Grab Consecutive Strings
|
||||
jaString = match.group(1)
|
||||
while len(data) > i+1 and re.match(regex, data[i+1]):
|
||||
data[i] = ''
|
||||
while len(data) > i + 1 and re.match(regex, data[i + 1]):
|
||||
data[i] = ""
|
||||
i += 1
|
||||
jaString = f'{jaString} {data[i]}'
|
||||
jaString = f"{jaString} {data[i]}"
|
||||
|
||||
# Convert from Wide
|
||||
jaString = jaString.translate(wide_to_ascii)
|
||||
|
||||
|
||||
# Remove any textwrap and \u3000 and \
|
||||
jaString = jaString.replace('\n', '')
|
||||
jaString = jaString.replace('\u3000', '')
|
||||
jaString = jaString.replace('\\', '')
|
||||
jaString = jaString.replace(' >', ')')
|
||||
jaString = jaString.replace('< ', '(')
|
||||
jaString = jaString.replace("\n", "")
|
||||
jaString = jaString.replace("\u3000", "")
|
||||
jaString = jaString.replace("\\", "")
|
||||
jaString = jaString.replace(" >", ")")
|
||||
jaString = jaString.replace("< ", "(")
|
||||
|
||||
# Remove Furigana
|
||||
furiMatch = re.findall(r'({(.+?)\/(.+?)})', jaString)
|
||||
furiMatch = re.findall(r"({(.+?)\/(.+?)})", jaString)
|
||||
if furiMatch:
|
||||
for match in furiMatch:
|
||||
jaString = jaString.replace(match[0], match[2])
|
||||
|
||||
# Add String
|
||||
stringList.append(jaString.strip())
|
||||
|
||||
|
||||
# Pass 2
|
||||
else:
|
||||
# Get Text
|
||||
|
|
@ -226,24 +255,24 @@ def translateOnscripter(data, pbar, filename, translatedList):
|
|||
|
||||
# Textwrap & Other Text
|
||||
translatedText = textwrap.fill(translatedText, width=WIDTH)
|
||||
translatedText = translatedText.replace('\n', '\n\u3000')
|
||||
translatedText = translatedText.replace("\n", "\n\u3000")
|
||||
|
||||
# Split the string into lines
|
||||
lines = translatedText.split('\n')
|
||||
|
||||
lines = translatedText.split("\n")
|
||||
|
||||
# Add a backslash after every 3rd line
|
||||
j = 0
|
||||
while j < len(lines):
|
||||
if j == 4:
|
||||
lines[j-1] = f'{lines[j-1]}\\'
|
||||
lines[j] = f'\n{lines[j]}'
|
||||
lines[j - 1] = f"{lines[j-1]}\\"
|
||||
lines[j] = f"\n{lines[j]}"
|
||||
j += 1
|
||||
|
||||
|
||||
# Join the lines back into a single string
|
||||
translatedText = '\n'.join(lines)
|
||||
translatedText = "\n".join(lines)
|
||||
|
||||
# Remove Double Spaces
|
||||
translatedText = translatedText.replace(' ', ' ')
|
||||
translatedText = translatedText.replace(" ", " ")
|
||||
|
||||
# Convert to Wide
|
||||
translatedText = translatedText.translate(ascii_to_wide)
|
||||
|
|
@ -252,18 +281,20 @@ def translateOnscripter(data, pbar, filename, translatedList):
|
|||
translatedText = fixText(translatedText)
|
||||
|
||||
# Set Data
|
||||
data[i] = data[i].replace(originalString, f'{translatedText}')
|
||||
data[i] = data[i].replace(originalString, f"{translatedText}")
|
||||
i += 1
|
||||
|
||||
# Choices
|
||||
elif 'csel' in data[i] and translatedList != []:
|
||||
elif "csel" in data[i] and translatedList != []:
|
||||
choiceList = []
|
||||
jaString = data[i]
|
||||
|
||||
choiceList = re.findall(r'\"(.*?)\"', jaString)
|
||||
choiceList = re.findall(r"\"(.*?)\"", jaString)
|
||||
if len(choiceList) > 0:
|
||||
# Translate
|
||||
response = translateGPT(choiceList, 'This will be a dialogue option', True)
|
||||
response = translateGPT(
|
||||
choiceList, "This will be a dialogue option", True
|
||||
)
|
||||
translatedTextList = response[0]
|
||||
tokens[0] += response[1][0]
|
||||
tokens[1] += response[1][1]
|
||||
|
|
@ -286,9 +317,9 @@ def translateOnscripter(data, pbar, filename, translatedList):
|
|||
# Set Progress
|
||||
pbar.total = len(stringList)
|
||||
pbar.refresh()
|
||||
|
||||
|
||||
# Translate
|
||||
response = translateGPT(stringList, '', True)
|
||||
response = translateGPT(stringList, "", True)
|
||||
tokens[0] += response[1][0]
|
||||
tokens[1] += response[1][1]
|
||||
translatedList = response[0]
|
||||
|
|
@ -304,54 +335,72 @@ def translateOnscripter(data, pbar, filename, translatedList):
|
|||
MISMATCH.append(filename)
|
||||
return tokens
|
||||
|
||||
|
||||
def fixText(translatedText):
|
||||
# Add Break
|
||||
translatedText = translatedText.replace('\"', '\'')
|
||||
translatedText = f'\u3000{translatedText}\\'
|
||||
translatedText = translatedText.replace('"', "'")
|
||||
translatedText = f"\u3000{translatedText}\\"
|
||||
|
||||
# Unconvert Codes
|
||||
matchList = re.findall(r'([$].+?)[^\w]', translatedText)
|
||||
matchList = re.findall(r"([$].+?)[^\w]", translatedText)
|
||||
if matchList:
|
||||
for match in matchList:
|
||||
translatedText = translatedText.replace(match, match.translate(wide_to_ascii))
|
||||
translatedText = translatedText.replace(
|
||||
match, match.translate(wide_to_ascii)
|
||||
)
|
||||
|
||||
# Unconvert Color Codes
|
||||
matchList = re.findall(r'([#][\w\d]{6})', translatedText)
|
||||
matchList = re.findall(r"([#][\w\d]{6})", translatedText)
|
||||
if matchList:
|
||||
for match in matchList:
|
||||
translatedText = translatedText.replace(match, match.translate(wide_to_ascii))
|
||||
translatedText = translatedText.replace(
|
||||
match, match.translate(wide_to_ascii)
|
||||
)
|
||||
|
||||
# Unconvert Variables
|
||||
matchList = re.findall(r'([%]\w.+?)[^\w_]', translatedText)
|
||||
matchList = re.findall(r"([%]\w.+?)[^\w_]", translatedText)
|
||||
if matchList:
|
||||
for match in matchList:
|
||||
translatedText = translatedText.replace(match, match.translate(wide_to_ascii))
|
||||
translatedText = translatedText.replace(
|
||||
match, match.translate(wide_to_ascii)
|
||||
)
|
||||
|
||||
# Unconvert Backslashes
|
||||
matchList = re.findall(r'\', translatedText)
|
||||
matchList = re.findall(r"\", translatedText)
|
||||
if matchList:
|
||||
for match in matchList:
|
||||
translatedText = translatedText.replace(match, match.translate(wide_to_ascii))
|
||||
translatedText = translatedText.replace(
|
||||
match, match.translate(wide_to_ascii)
|
||||
)
|
||||
|
||||
return translatedText
|
||||
|
||||
|
||||
# Save some money and enter the character before translation
|
||||
def getSpeaker(speaker):
|
||||
match speaker:
|
||||
case 'ファイン':
|
||||
return ['Fine', [0,0]]
|
||||
case '':
|
||||
return ['', [0,0]]
|
||||
case "ファイン":
|
||||
return ["Fine", [0, 0]]
|
||||
case "":
|
||||
return ["", [0, 0]]
|
||||
case _:
|
||||
# Store Speaker
|
||||
if speaker not in str(NAMESLIST):
|
||||
response = translateGPT(speaker, 'Reply with the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
response = translateGPT(
|
||||
speaker,
|
||||
"Reply with the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
response[0] = response[0].title()
|
||||
response[0] = response[0].replace("'S", "'s")
|
||||
|
||||
# Retry if name doesn't translate for some reason
|
||||
if re.search(r'([a-zA-Z??])', response[0]) == None:
|
||||
response = translateGPT(speaker, 'Reply with the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
if re.search(r"([a-zA-Z??])", response[0]) == None:
|
||||
response = translateGPT(
|
||||
speaker,
|
||||
"Reply with the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
response[0] = response[0].title()
|
||||
response[0] = response[0].replace("'S", "'s")
|
||||
|
||||
|
|
@ -362,74 +411,76 @@ def getSpeaker(speaker):
|
|||
else:
|
||||
for i in range(len(NAMESLIST)):
|
||||
if speaker == NAMESLIST[i][0]:
|
||||
return [NAMESLIST[i][1],[0,0]]
|
||||
|
||||
return [speaker,[0,0]]
|
||||
return [NAMESLIST[i][1], [0, 0]]
|
||||
|
||||
return [speaker, [0, 0]]
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'([\\]+c\[\d+\][\\]+c|[\\]+c\[\d+\])', jaString)
|
||||
colorList = re.findall(r"([\\]+c\[\d+\][\\]+c|[\\]+c\[\d+\])", jaString)
|
||||
colorList = set(colorList)
|
||||
if len(colorList) != 0:
|
||||
for color in colorList:
|
||||
jaString = jaString.replace(color, '[Color_' + str(count) + ']')
|
||||
jaString = jaString.replace(color, "[Color_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '[Noun_' + str(count) + ']')
|
||||
jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(var, "[Var_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]', jaString)
|
||||
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) + ']')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -439,54 +490,58 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('[Nested_' + str(count) + ']', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
レナリス (Renalith) - Female\n\
|
||||
スクルー (Sukuru) - Female\n\
|
||||
シスターミサ (Sister Misa) - Female\n\
|
||||
|
|
@ -514,10 +569,12 @@ def createContext(fullPromptFlag, subbedT):
|
|||
エメルーラ (Emerald) - Female\n\
|
||||
フンシス (Funsis) - Male \n\
|
||||
バゼット (Bazzet) - Female\n\
|
||||
'
|
||||
|
||||
system = PROMPT + VOCAB if fullPromptFlag else \
|
||||
f"\
|
||||
"
|
||||
|
||||
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\
|
||||
|
|
@ -529,9 +586,11 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
|
|||
- `...` can be a part of the dialogue. Translate it as it is.\n\
|
||||
{VOCAB}\n\
|
||||
"
|
||||
user = f'```json\n{subbedT}```'
|
||||
)
|
||||
user = f"```json\n{subbedT}```"
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history, penalty):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -544,30 +603,31 @@ def translateText(characters, system, user, history, penalty):
|
|||
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}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0,
|
||||
frequency_penalty=penalty,
|
||||
model=MODEL,
|
||||
response_format={ "type": "json_object" },
|
||||
response_format={"type": "json_object"},
|
||||
messages=msg,
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'「': '\\"',
|
||||
'」': '\\"',
|
||||
'- ': '-',
|
||||
'Placeholder Text': '',
|
||||
f"{LANGUAGE} Translation: ": "",
|
||||
"Translation: ": "",
|
||||
"っ": "",
|
||||
"〜": "~",
|
||||
"ッ": "",
|
||||
"。": ".",
|
||||
"「": '\\"',
|
||||
"」": '\\"',
|
||||
"- ": "-",
|
||||
"Placeholder Text": "",
|
||||
# Add more replacements as needed
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
|
|
@ -578,11 +638,12 @@ def cleanTranslatedText(translatedText, varResponse):
|
|||
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'(?<=(.))ー+'
|
||||
|
||||
pattern = r"(?<=(.))ー+"
|
||||
|
||||
# Define a replacement function that elongates the captured character
|
||||
def repl(match):
|
||||
char = match.group(1) # The character before the ー sequence
|
||||
|
|
@ -592,6 +653,7 @@ def elongateCharacters(text):
|
|||
# Use re.sub() to replace the pattern in the text
|
||||
return re.sub(pattern, repl, text)
|
||||
|
||||
|
||||
def extractTranslation(translatedTextList, is_list):
|
||||
try:
|
||||
line_dict = json.loads(translatedTextList)
|
||||
|
|
@ -603,11 +665,12 @@ def extractTranslation(translatedTextList, is_list):
|
|||
print(e)
|
||||
return translatedTextList
|
||||
|
||||
|
||||
def countTokens(characters, system, user, history):
|
||||
inputTotalTokens = 0
|
||||
outputTotalTokens = 0
|
||||
enc = tiktoken.encoding_for_model('gpt-4')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -619,19 +682,21 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
global PBAR
|
||||
|
||||
|
||||
mismatch = False
|
||||
totalTokens = [0, 0]
|
||||
if isinstance(text, list):
|
||||
|
|
@ -651,7 +716,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
if PBAR is not None:
|
||||
PBAR.update(len(tItem))
|
||||
continue
|
||||
|
|
@ -688,12 +753,14 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
if isinstance(tItem, list):
|
||||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
if len(tItem) != len(extractedTranslations):
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
# Set if no mismatch
|
||||
if mismatch == False:
|
||||
tList[index] = extractedTranslations
|
||||
history = extractedTranslations[-10:] # Update history if we have a list
|
||||
history = extractedTranslations[
|
||||
-10:
|
||||
] # Update history if we have a list
|
||||
else:
|
||||
history = text[-10:]
|
||||
mismatch = False
|
||||
|
|
|
|||
292
modules/regex.py
292
modules/regex.py
|
|
@ -15,49 +15,50 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .005
|
||||
OUTPUTAPICOST = .015
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.005
|
||||
OUTPUTAPICOST = 0.015
|
||||
BATCHSIZE = 40
|
||||
|
||||
|
||||
def handleRegex(filename, estimate):
|
||||
global ESTIMATE
|
||||
ESTIMATE = estimate
|
||||
|
|
@ -74,17 +75,21 @@ def handleRegex(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', TOKENS, None], end - start, '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
|
||||
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:
|
||||
with open(
|
||||
"translated/" + filename, "w", encoding="cp932", errors="ignore"
|
||||
) as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -97,23 +102,36 @@ def handleRegex(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -122,31 +140,41 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='shift_jis') as readFile:
|
||||
with open("files/" + filename, "r", encoding="shift_jis") as readFile:
|
||||
translatedData = parseRegex(readFile, filename)
|
||||
|
||||
# Delete lines marked for deletion
|
||||
finalData = []
|
||||
for line in translatedData[0]:
|
||||
if line != '\\d\n':
|
||||
if line != "\\d\n":
|
||||
finalData.append(line)
|
||||
translatedData[0] = finalData
|
||||
|
||||
|
||||
return translatedData
|
||||
|
||||
|
||||
def parseRegex(readFile, filename):
|
||||
totalTokens = [0,0]
|
||||
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
|
||||
pbar.desc = filename
|
||||
|
||||
try:
|
||||
result = translateRegex(data, pbar, filename, [])
|
||||
|
|
@ -157,36 +185,37 @@ def parseRegex(readFile, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateRegex(data, pbar, filename, translatedList):
|
||||
stringList = []
|
||||
currentGroup = []
|
||||
tokens = [0,0]
|
||||
speaker = ''
|
||||
tokens = [0, 0]
|
||||
speaker = ""
|
||||
voice = False
|
||||
global LOCK, ESTIMATE
|
||||
i = 0
|
||||
|
||||
while i < len(data):
|
||||
voice = False
|
||||
speaker = ''
|
||||
if 'actorID' in data[i]:
|
||||
speaker = ""
|
||||
if "actorID" in data[i]:
|
||||
# Lines
|
||||
match = re.search(r'label[\\]+\":[\\]+\"(.*?)\"', data[i])
|
||||
match = re.search(r"label[\\]+\":[\\]+\"(.*?)\"", data[i])
|
||||
if match == None:
|
||||
match = re.search(r'label[\\]+\":[\\]+\"(.*?)\"', data[i])
|
||||
if match != None and match.group(1) != '':
|
||||
match = re.search(r"label[\\]+\":[\\]+\"(.*?)\"", data[i])
|
||||
if match != None and match.group(1) != "":
|
||||
originalString = match.group(1)
|
||||
# Pass 1
|
||||
if translatedList == []:
|
||||
# Grab Consecutive Strings
|
||||
jaString = match.group(1)
|
||||
|
||||
|
||||
# Remove any textwrap
|
||||
jaString = jaString.replace('\n', ' ')
|
||||
jaString = jaString.replace("\n", " ")
|
||||
|
||||
# Add String
|
||||
stringList.append(jaString.strip())
|
||||
|
||||
|
||||
# Pass 2
|
||||
else:
|
||||
# Get Text
|
||||
|
|
@ -217,9 +246,15 @@ def translateRegex(data, pbar, filename, translatedList):
|
|||
# Set Progress
|
||||
pbar.total = len(stringList)
|
||||
pbar.refresh()
|
||||
|
||||
|
||||
# Translate
|
||||
response = translateGPT(stringList, 'Reply with the English TL of the NPC Name', True, pbar, filename)
|
||||
response = translateGPT(
|
||||
stringList,
|
||||
"Reply with the English TL of the NPC Name",
|
||||
True,
|
||||
pbar,
|
||||
filename,
|
||||
)
|
||||
tokens[0] += response[1][0]
|
||||
tokens[1] += response[1][1]
|
||||
translatedList = response[0]
|
||||
|
|
@ -235,94 +270,103 @@ def translateRegex(data, pbar, filename, translatedList):
|
|||
MISMATCH.append(filename)
|
||||
return tokens
|
||||
|
||||
|
||||
# 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 "ファイン":
|
||||
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 = 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]]
|
||||
return [NAMESLIST[i][1], [0, 0]]
|
||||
|
||||
return [speaker, [0, 0]]
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'[\\]+[cC]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(color, "[Color_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '[Noun_' + str(count) + ']')
|
||||
jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(var, "[Var_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]', jaString)
|
||||
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) + ']')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -332,54 +376,58 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('[Nested_' + str(count) + ']', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
フィリア (Philia) - Female\n\
|
||||
アルネット (Annett) - Female\n\
|
||||
ラピュセナ (Rapusena) - Female\n\
|
||||
|
|
@ -389,10 +437,12 @@ def createContext(fullPromptFlag, subbedT):
|
|||
カルナ (Karna) - Female\n\
|
||||
ラフィング=スピア (Laughing Spear) - Female\n\
|
||||
ノーラ (Nora) - Female\n\
|
||||
'
|
||||
|
||||
system = PROMPT + VOCAB if fullPromptFlag else \
|
||||
f"\
|
||||
"
|
||||
|
||||
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\
|
||||
|
|
@ -404,9 +454,11 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
|
|||
- `...` can be a part of the dialogue. Translate it as it is.\n\
|
||||
{VOCAB}\n\
|
||||
"
|
||||
user = f'{subbedT}'
|
||||
)
|
||||
user = f"{subbedT}"
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -419,9 +471,9 @@ def translateText(characters, system, user, history):
|
|||
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}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0.1,
|
||||
frequency_penalty=0.1,
|
||||
|
|
@ -430,15 +482,16 @@ def translateText(characters, system, user, history):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'Placeholder Text': ''
|
||||
f"{LANGUAGE} Translation: ": "",
|
||||
"Translation: ": "",
|
||||
"っ": "",
|
||||
"〜": "~",
|
||||
"ッ": "",
|
||||
"。": ".",
|
||||
"Placeholder Text": "",
|
||||
# Add more replacements as needed
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
|
|
@ -449,11 +502,12 @@ def cleanTranslatedText(translatedText, varResponse):
|
|||
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'(?<=(.))ー+'
|
||||
|
||||
pattern = r"(?<=(.))ー+"
|
||||
|
||||
# Define a replacement function that elongates the captured character
|
||||
def repl(match):
|
||||
char = match.group(1) # The character before the ー sequence
|
||||
|
|
@ -463,8 +517,9 @@ def elongateCharacters(text):
|
|||
# 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+>`?'
|
||||
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)
|
||||
|
|
@ -473,11 +528,12 @@ def extractTranslation(translatedTextList, is_list):
|
|||
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')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -489,15 +545,17 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*2)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 2)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag, pbar, filename):
|
||||
mismatch = False
|
||||
|
|
@ -510,8 +568,12 @@ def translateGPT(text, history, fullPromptFlag, pbar, filename):
|
|||
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'`<Line{i}>{item}</Line{i}>`' for i, item in enumerate(tItem)])
|
||||
payload = re.sub(r'(<Line\d+)(><)(\/Line\d+>)', r'\1>Placeholder Text<\3', payload)
|
||||
payload = "\n".join(
|
||||
[f"`<Line{i}>{item}</Line{i}>`" for i, item in enumerate(tItem)]
|
||||
)
|
||||
payload = re.sub(
|
||||
r"(<Line\d+)(><)(\/Line\d+>)", r"\1>Placeholder Text<\3", payload
|
||||
)
|
||||
varResponse = subVars(payload)
|
||||
subbedT = varResponse[0]
|
||||
else:
|
||||
|
|
@ -519,7 +581,7 @@ def translateGPT(text, history, fullPromptFlag, pbar, filename):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
continue
|
||||
|
||||
# Create Message
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -16,34 +16,34 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
PBAR = None
|
||||
|
|
@ -51,15 +51,16 @@ PBAR = None
|
|||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .005
|
||||
OUTPUTAPICOST = .015
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.005
|
||||
OUTPUTAPICOST = 0.015
|
||||
BATCHSIZE = 40
|
||||
|
||||
|
||||
def handlePlugin(filename, estimate):
|
||||
global ESTIMATE, PBAR
|
||||
ESTIMATE = estimate
|
||||
|
|
@ -76,17 +77,21 @@ def handlePlugin(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', TOKENS, None], end - start, '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
|
||||
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:
|
||||
with open(
|
||||
"translated/" + filename, "w", encoding="utf_8", errors="ignore"
|
||||
) as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -99,23 +104,36 @@ def handlePlugin(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -124,31 +142,41 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='utf_8') as readFile:
|
||||
with open("files/" + filename, "r", encoding="utf_8") as readFile:
|
||||
translatedData = parsePlugin(readFile, filename)
|
||||
|
||||
# Delete lines marked for deletion
|
||||
finalData = []
|
||||
for line in translatedData[0]:
|
||||
if line != '\\d\n':
|
||||
if line != "\\d\n":
|
||||
finalData.append(line)
|
||||
translatedData[0] = finalData
|
||||
|
||||
|
||||
return translatedData
|
||||
|
||||
|
||||
def parsePlugin(readFile, filename):
|
||||
totalTokens = [0,0]
|
||||
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
|
||||
pbar.desc = filename
|
||||
|
||||
try:
|
||||
result = translatePlugin(data, pbar, filename, [])
|
||||
|
|
@ -159,19 +187,20 @@ def parsePlugin(readFile, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translatePlugin(data, pbar, filename, translatedList):
|
||||
stringList = []
|
||||
currentGroup = []
|
||||
tokens = [0,0]
|
||||
speaker = ''
|
||||
tokens = [0, 0]
|
||||
speaker = ""
|
||||
voice = False
|
||||
global LOCK, ESTIMATE
|
||||
i = 0
|
||||
|
||||
while i < len(data):
|
||||
voice = False
|
||||
speaker = ''
|
||||
newline = r'\n'
|
||||
speaker = ""
|
||||
newline = r"\n"
|
||||
|
||||
"""
|
||||
Plugin List
|
||||
|
|
@ -192,16 +221,16 @@ def translatePlugin(data, pbar, filename, translatedList):
|
|||
for match in matchList:
|
||||
# Save Original String
|
||||
originalString = match
|
||||
|
||||
|
||||
# Remove any textwrap
|
||||
match = match.replace(newline, ' ')
|
||||
match = match.replace(newline, " ")
|
||||
|
||||
# Pass 1
|
||||
if translatedList == []:
|
||||
if translatedList == []:
|
||||
# Add String
|
||||
if match != '\\\\\\\\':
|
||||
if match != "\\\\\\\\":
|
||||
stringList.append(match.strip())
|
||||
|
||||
|
||||
# Pass 2
|
||||
else:
|
||||
# Get Text
|
||||
|
|
@ -216,7 +245,7 @@ def translatePlugin(data, pbar, filename, translatedList):
|
|||
|
||||
# Textwrap
|
||||
translatedText = textwrap.fill(translatedText, width=WIDTH)
|
||||
translatedText = translatedText.replace('\n', newline)
|
||||
translatedText = translatedText.replace("\n", newline)
|
||||
|
||||
# Replace Single Quotes
|
||||
translatedText = translatedText.replace("'", "\\'")
|
||||
|
|
@ -224,7 +253,7 @@ def translatePlugin(data, pbar, filename, translatedList):
|
|||
|
||||
# Set Data
|
||||
data[i] = data[i].replace(originalString, translatedText)
|
||||
# Next Line
|
||||
# Next Line
|
||||
i += 1
|
||||
|
||||
# EOF
|
||||
|
|
@ -233,9 +262,9 @@ def translatePlugin(data, pbar, filename, translatedList):
|
|||
pbar.total = len(stringList)
|
||||
pbar.refresh()
|
||||
PBAR = pbar
|
||||
|
||||
|
||||
# Translate
|
||||
response = translateGPT(stringList, '', True)
|
||||
response = translateGPT(stringList, "", True)
|
||||
tokens[0] += response[1][0]
|
||||
tokens[1] += response[1][1]
|
||||
translatedList = response[0]
|
||||
|
|
@ -251,23 +280,32 @@ def translatePlugin(data, pbar, filename, translatedList):
|
|||
MISMATCH.append(filename)
|
||||
return tokens
|
||||
|
||||
|
||||
# Save some money and enter the character before translation
|
||||
def getSpeaker(speaker):
|
||||
match speaker:
|
||||
case 'ファイン':
|
||||
return ['Fine', [0,0]]
|
||||
case '':
|
||||
return ['', [0,0]]
|
||||
case "ファイン":
|
||||
return ["Fine", [0, 0]]
|
||||
case "":
|
||||
return ["", [0, 0]]
|
||||
case _:
|
||||
# Store Speaker
|
||||
if speaker not in str(NAMESLIST):
|
||||
response = translateGPT(speaker, 'Reply with the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
response = translateGPT(
|
||||
speaker,
|
||||
"Reply with the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
response[0] = response[0].title()
|
||||
response[0] = response[0].replace("'S", "'s")
|
||||
|
||||
# Retry if name doesn't translate for some reason
|
||||
if re.search(r'([a-zA-Z??])', response[0]) == None:
|
||||
response = translateGPT(speaker, 'Reply with the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
if re.search(r"([a-zA-Z??])", response[0]) == None:
|
||||
response = translateGPT(
|
||||
speaker,
|
||||
"Reply with the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
response[0] = response[0].title()
|
||||
response[0] = response[0].replace("'S", "'s")
|
||||
|
||||
|
|
@ -278,74 +316,76 @@ def getSpeaker(speaker):
|
|||
else:
|
||||
for i in range(len(NAMESLIST)):
|
||||
if speaker == NAMESLIST[i][0]:
|
||||
return [NAMESLIST[i][1],[0,0]]
|
||||
|
||||
return [speaker,[0,0]]
|
||||
return [NAMESLIST[i][1], [0, 0]]
|
||||
|
||||
return [speaker, [0, 0]]
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'([\\]+c\[\d+\][\\]+c|[\\]+c\[\d+\])', jaString)
|
||||
colorList = re.findall(r"([\\]+c\[\d+\][\\]+c|[\\]+c\[\d+\])", jaString)
|
||||
colorList = set(colorList)
|
||||
if len(colorList) != 0:
|
||||
for color in colorList:
|
||||
jaString = jaString.replace(color, '[Color_' + str(count) + ']')
|
||||
jaString = jaString.replace(color, "[Color_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '[Noun_' + str(count) + ']')
|
||||
jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(var, "[Var_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]', jaString)
|
||||
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) + ']')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -355,54 +395,58 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('[Nested_' + str(count) + ']', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
シェーア (Shea) - Female\n\
|
||||
ミューテ (Mute) - Female\n\
|
||||
タビノ (Tabino) - Female\n\
|
||||
|
|
@ -411,10 +455,12 @@ def createContext(fullPromptFlag, subbedT):
|
|||
ソフィー (Sophie) - Female\n\
|
||||
ドーラ (Dora) - Female\n\
|
||||
ミューレ (Mule) - Female\n\
|
||||
'
|
||||
|
||||
system = PROMPT + VOCAB if fullPromptFlag else \
|
||||
f"\
|
||||
"
|
||||
|
||||
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\
|
||||
|
|
@ -426,12 +472,14 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
|
|||
- `...` can be a part of the dialogue. Translate it as it is.\n\
|
||||
{VOCAB}\n\
|
||||
"
|
||||
)
|
||||
if isinstance(subbedT, list):
|
||||
user = f'```json\n{subbedT}```'
|
||||
user = f"```json\n{subbedT}```"
|
||||
else:
|
||||
user = subbedT
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history, penalty, format):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -446,13 +494,13 @@ def translateText(characters, system, user, history, penalty, format):
|
|||
msg.append({"role": "system", "content": history})
|
||||
|
||||
# Response Format
|
||||
if format == 'json':
|
||||
responseFormat = { "type": "json_object" }
|
||||
if format == "json":
|
||||
responseFormat = {"type": "json_object"}
|
||||
else:
|
||||
responseFormat = { "type": "text" }
|
||||
|
||||
responseFormat = {"type": "text"}
|
||||
|
||||
# Content to TL
|
||||
msg.append({"role": "user", "content": f'{user}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0,
|
||||
frequency_penalty=penalty,
|
||||
|
|
@ -462,18 +510,19 @@ def translateText(characters, system, user, history, penalty, format):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'「': '\\"',
|
||||
'」': '\\"',
|
||||
'- ': '-',
|
||||
'Placeholder Text': '',
|
||||
f"{LANGUAGE} Translation: ": "",
|
||||
"Translation: ": "",
|
||||
"っ": "",
|
||||
"〜": "~",
|
||||
"ッ": "",
|
||||
"。": ".",
|
||||
"「": '\\"',
|
||||
"」": '\\"',
|
||||
"- ": "-",
|
||||
"Placeholder Text": "",
|
||||
# Add more replacements as needed
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
|
|
@ -484,11 +533,12 @@ def cleanTranslatedText(translatedText, varResponse):
|
|||
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'(?<=(.))ー+'
|
||||
|
||||
pattern = r"(?<=(.))ー+"
|
||||
|
||||
# Define a replacement function that elongates the captured character
|
||||
def repl(match):
|
||||
char = match.group(1) # The character before the ー sequence
|
||||
|
|
@ -498,6 +548,7 @@ def elongateCharacters(text):
|
|||
# Use re.sub() to replace the pattern in the text
|
||||
return re.sub(pattern, repl, text)
|
||||
|
||||
|
||||
def extractTranslation(translatedTextList, is_list):
|
||||
try:
|
||||
translatedTextList = re.sub(r'\\"+\"([^,\n}])', r'\\"\1', translatedTextList)
|
||||
|
|
@ -510,15 +561,15 @@ def extractTranslation(translatedTextList, is_list):
|
|||
return string_list[0]
|
||||
|
||||
except Exception as e:
|
||||
PBAR.write(f'extractTranslation Error: {e} on String {translatedTextList}')
|
||||
PBAR.write(f"extractTranslation Error: {e} on String {translatedTextList}")
|
||||
return None
|
||||
|
||||
|
||||
def countTokens(characters, system, user, history):
|
||||
inputTotalTokens = 0
|
||||
outputTotalTokens = 0
|
||||
enc = tiktoken.encoding_for_model('gpt-4')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -530,26 +581,28 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
global PBAR
|
||||
|
||||
|
||||
mismatch = False
|
||||
totalTokens = [0, 0]
|
||||
if isinstance(text, list):
|
||||
format = 'json'
|
||||
format = "json"
|
||||
tList = batchList(text, BATCHSIZE)
|
||||
else:
|
||||
format = 'text'
|
||||
format = "text"
|
||||
tList = [text]
|
||||
|
||||
for index, tItem in enumerate(tList):
|
||||
|
|
@ -564,7 +617,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
if PBAR is not None:
|
||||
PBAR.update(len(tItem))
|
||||
continue
|
||||
|
|
@ -589,9 +642,13 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
translatedText = cleanTranslatedText(translatedText, varResponse)
|
||||
if isinstance(tItem, list):
|
||||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
if extractedTranslations == None or len(tItem) != len(extractedTranslations):
|
||||
if extractedTranslations == None or len(tItem) != len(
|
||||
extractedTranslations
|
||||
):
|
||||
# Mismatch. Try Again
|
||||
response = translateText(characters, system, user, history, 0.05, format)
|
||||
response = translateText(
|
||||
characters, system, user, history, 0.05, format
|
||||
)
|
||||
translatedText = response.choices[0].message.content
|
||||
totalTokens[0] += response.usage.prompt_tokens
|
||||
totalTokens[1] += response.usage.completion_tokens
|
||||
|
|
@ -600,13 +657,17 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
translatedText = cleanTranslatedText(translatedText, varResponse)
|
||||
if isinstance(tItem, list):
|
||||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
if extractedTranslations == None or len(tItem) != len(extractedTranslations):
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
if extractedTranslations == None or len(tItem) != len(
|
||||
extractedTranslations
|
||||
):
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
# Set if no mismatch
|
||||
if mismatch == False:
|
||||
tList[index] = extractedTranslations
|
||||
history = extractedTranslations[-10:] # Update history if we have a list
|
||||
history = extractedTranslations[
|
||||
-10:
|
||||
] # Update history if we have a list
|
||||
else:
|
||||
history = text[-10:]
|
||||
mismatch = False
|
||||
|
|
@ -617,7 +678,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
PBAR.update(len(tItem))
|
||||
else:
|
||||
# Ensure we're passing a single string to extractTranslation
|
||||
tList[index] = translatedText
|
||||
tList[index] = translatedText
|
||||
|
||||
finalList = combineList(tList, text)
|
||||
return [finalList, totalTokens]
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from tqdm import tqdm
|
|||
# Open AI
|
||||
load_dotenv()
|
||||
if os.getenv("api").replace(" ", "") != "":
|
||||
openai.base_url = os.getenv('api')
|
||||
openai.base_url = os.getenv("api")
|
||||
openai.organization = os.getenv("org")
|
||||
openai.api_key = os.getenv("key")
|
||||
|
||||
|
|
@ -95,8 +95,9 @@ def getResultString(translatedData, translationTime, filename):
|
|||
# File Print String
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: " + str(translatedData[1][1]) + "]"
|
||||
"[Cost: ${:,.4f}".format(
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
|
|
@ -189,17 +190,17 @@ def translateTyrano(data, pbar):
|
|||
if syncIndex > i:
|
||||
i = syncIndex
|
||||
|
||||
if '[▼]' in data[i]:
|
||||
data[i] = data[i].replace('[▼]'.strip(), '[page]\n')
|
||||
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]):
|
||||
if not re.search(r"[一-龠]+|[ぁ-ゔ]+|[ァ-ヴー]+", data[i]):
|
||||
# Keep textHistory list at length maxHistory
|
||||
textHistory.append('\"' + data[i] + '\"')
|
||||
textHistory.append('"' + data[i] + '"')
|
||||
if len(textHistory) > maxHistory:
|
||||
textHistory.pop(0)
|
||||
currentGroup = []
|
||||
currentGroup = []
|
||||
continue
|
||||
|
||||
# Speaker
|
||||
|
|
@ -215,18 +216,16 @@ def translateTyrano(data, pbar):
|
|||
speaker = "Narrator"
|
||||
elif "マコ" in matchList[0]:
|
||||
speaker = "Mako"
|
||||
elif '少年' in matchList[0]:
|
||||
elif "少年" in matchList[0]:
|
||||
speaker = "Boy"
|
||||
elif '友達' in matchList[0]:
|
||||
elif "友達" in matchList[0]:
|
||||
speaker = "Friend"
|
||||
elif '少女' in matchList[0]:
|
||||
elif "少女" in matchList[0]:
|
||||
speaker = "Girl"
|
||||
else:
|
||||
response = translateGPT(
|
||||
matchList[0],
|
||||
"Reply with only the "
|
||||
+ LANGUAGE
|
||||
+ " translation of the NPC name",
|
||||
"Reply with only the " + LANGUAGE + " translation of the NPC name",
|
||||
True,
|
||||
)
|
||||
speaker = response[0]
|
||||
|
|
@ -263,13 +262,17 @@ def translateTyrano(data, pbar):
|
|||
|
||||
# Set Data
|
||||
translatedText = data[i].replace(
|
||||
matchList[0], translatedText.replace(" ", "\u00A0")
|
||||
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])):
|
||||
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])
|
||||
|
|
@ -323,10 +326,10 @@ def translateTyrano(data, pbar):
|
|||
|
||||
# Set
|
||||
if delFlag is True:
|
||||
data.insert(i, translatedText.strip() + '\n')
|
||||
data.insert(i, translatedText.strip() + "\n")
|
||||
delFlag = False
|
||||
else:
|
||||
data[i] = translatedText.strip() + '\n'
|
||||
data[i] = translatedText.strip() + "\n"
|
||||
|
||||
# Keep textHistory list at length maxHistory
|
||||
if len(textHistory) > maxHistory:
|
||||
|
|
@ -399,10 +402,10 @@ def translateTyrano(data, pbar):
|
|||
|
||||
# Set
|
||||
if delFlag is True:
|
||||
data.insert(i, translatedText.strip() + '\n')
|
||||
data.insert(i, translatedText.strip() + "\n")
|
||||
delFlag = False
|
||||
else:
|
||||
data[i] = translatedText.strip() + '\n'
|
||||
data[i] = translatedText.strip() + "\n"
|
||||
|
||||
# Keep textHistory list at length maxHistory
|
||||
if len(textHistory) > maxHistory:
|
||||
|
|
@ -418,6 +421,7 @@ def translateTyrano(data, pbar):
|
|||
|
||||
return tokens
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
|
|
@ -551,7 +555,7 @@ def translateGPT(t, history, fullPromptFlag):
|
|||
|
||||
# If ESTIMATE is True just count this as an execution and return.
|
||||
if ESTIMATE:
|
||||
enc = tiktoken.encoding_for_model('gpt-4')
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
historyRaw = ""
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
|
|||
|
|
@ -15,35 +15,35 @@ 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')
|
||||
if os.getenv("api").replace(" ", "") != "":
|
||||
openai.base_url = os.getenv("api")
|
||||
openai.organization = os.getenv("org")
|
||||
openai.api_key = os.getenv("key")
|
||||
|
||||
#Globals
|
||||
# Globals
|
||||
PBAR = None
|
||||
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'))
|
||||
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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
|
||||
|
|
@ -54,15 +54,16 @@ TEXTWRAPCHOICES = True
|
|||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .005
|
||||
OUTPUTAPICOST = .015
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.005
|
||||
OUTPUTAPICOST = 0.015
|
||||
BATCHSIZE = 40
|
||||
|
||||
|
||||
def handleTyrano(filename, estimate):
|
||||
global ESTIMATE
|
||||
ESTIMATE = estimate
|
||||
|
|
@ -79,17 +80,21 @@ def handleTyrano(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', TOKENS, None], end - start, '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
|
||||
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:
|
||||
with open(
|
||||
"translated/" + filename, "w", encoding="utf8", errors="ignore"
|
||||
) as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -102,23 +107,36 @@ def handleTyrano(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -127,34 +145,46 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='utf8') as readFile:
|
||||
with open("files/" + filename, "r", encoding="utf8") as readFile:
|
||||
translatedData = parseTyrano(readFile, filename)
|
||||
|
||||
# Delete lines marked for deletion
|
||||
finalData = []
|
||||
for line in translatedData[0]:
|
||||
if line != '\\d\n':
|
||||
if line != "\\d\n":
|
||||
finalData.append(line)
|
||||
translatedData[0] = finalData
|
||||
|
||||
|
||||
return translatedData
|
||||
|
||||
|
||||
def parseTyrano(readFile, filename):
|
||||
totalTokens = [0,0]
|
||||
totalTokens = [0, 0]
|
||||
totalLines = 0
|
||||
|
||||
# Get total for progress bar
|
||||
data = readFile.readlines()
|
||||
|
||||
with tqdm(bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE) as pbar:
|
||||
pbar.desc=filename
|
||||
with tqdm(
|
||||
bar_format=BAR_FORMAT, position=POSITION, total=totalLines, leave=LEAVE
|
||||
) as pbar:
|
||||
pbar.desc = filename
|
||||
|
||||
try:
|
||||
result = translateTyrano(data, pbar, filename, False, [[],[]])
|
||||
result = translateTyrano(data, pbar, filename, False, [[], []])
|
||||
totalTokens[0] += result[0]
|
||||
totalTokens[1] += result[1]
|
||||
except Exception as e:
|
||||
|
|
@ -162,58 +192,63 @@ def parseTyrano(readFile, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateTyrano(data, pbar, filename, setData, jobList):
|
||||
textHistory = []
|
||||
lineList = jobList[0]
|
||||
totalTokens = [0,0]
|
||||
speaker = ''
|
||||
totalTokens = [0, 0]
|
||||
speaker = ""
|
||||
global LOCK, ESTIMATE
|
||||
i = 0
|
||||
|
||||
# Set Progress Bar
|
||||
global PBAR
|
||||
global PBAR
|
||||
PBAR = pbar
|
||||
|
||||
while i < len(data):
|
||||
# Choices
|
||||
choiceList = []
|
||||
choiceRegex = r'[sS]tatus.+?\](.+)'
|
||||
if 'tatus' in data[i]:
|
||||
choiceRegex = r"[sS]tatus.+?\](.+)"
|
||||
if "tatus" in data[i]:
|
||||
match = re.search(choiceRegex, data[i])
|
||||
if match != None:
|
||||
jaString = match.group(1)
|
||||
|
||||
# Remove Textwrap
|
||||
if TEXTWRAPCHOICES is True:
|
||||
jaString = jaString.replace('[r]', ' ')
|
||||
data[i] = data[i].replace('[r]', ' ')
|
||||
|
||||
jaString = jaString.replace("[r]", " ")
|
||||
data[i] = data[i].replace("[r]", " ")
|
||||
|
||||
# Add to list
|
||||
choiceList.append(jaString)
|
||||
i += 1
|
||||
|
||||
# Grab them all up for list
|
||||
while(i < len(data) and 'tatus' in data[i]):
|
||||
while i < len(data) and "tatus" in data[i]:
|
||||
match = re.search(choiceRegex, data[i])
|
||||
if match != None:
|
||||
jaString = match.group(1)
|
||||
|
||||
# Remove Textwrap
|
||||
if TEXTWRAPCHOICES is True:
|
||||
jaString = jaString.replace('[r]', ' ')
|
||||
data[i] = data[i].replace('[r]', ' ')
|
||||
jaString = jaString.replace("[r]", " ")
|
||||
data[i] = data[i].replace("[r]", " ")
|
||||
|
||||
# Add to list
|
||||
choiceList.append(jaString)
|
||||
i += 1
|
||||
|
||||
|
||||
# Translate
|
||||
if len(choiceList) != 0:
|
||||
response = translateGPT(choiceList, 'Reply with the {LANGUAGE} translation of the text', True)
|
||||
response = translateGPT(
|
||||
choiceList,
|
||||
"Reply with the {LANGUAGE} translation of the text",
|
||||
True,
|
||||
)
|
||||
choiceListTL = response[0]
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
|
||||
|
||||
# Set Data
|
||||
if len(choiceList) == len(choiceListTL):
|
||||
i = i - len(choiceListTL)
|
||||
|
|
@ -223,75 +258,83 @@ def translateTyrano(data, pbar, filename, setData, jobList):
|
|||
# Textwrap
|
||||
if TEXTWRAPCHOICES is True:
|
||||
translatedText = textwrap.fill(translatedText, WIDTH)
|
||||
translatedText = translatedText.replace('\n', '[r]')
|
||||
translatedText = translatedText.replace("\n", "[r]")
|
||||
data[i] = data[i].replace(choiceList[j], translatedText)
|
||||
i += 1
|
||||
else:
|
||||
with LOCK:
|
||||
if filename not in MISMATCH:
|
||||
MISMATCH.append(filename)
|
||||
|
||||
|
||||
if DIALOGUEFLAG is True:
|
||||
# Speaker
|
||||
if '[@]' in data[i]:
|
||||
if 'FACE' not in data[i]:
|
||||
matchList = re.findall(r'\[(.*?)\].+\[.*\]', data[i])
|
||||
if "[@]" in data[i]:
|
||||
if "FACE" not in data[i]:
|
||||
matchList = re.findall(r"\[(.*?)\].+\[.*\]", data[i])
|
||||
else:
|
||||
matchList = re.findall(r'face=.+?\]\[(.+?)\]', data[i])
|
||||
if len(matchList) != 0 and '=' not in matchList[0] and re.search(r'\[.+\]', matchList[0]) == None:
|
||||
matchList = re.findall(r"face=.+?\]\[(.+?)\]", data[i])
|
||||
if (
|
||||
len(matchList) != 0
|
||||
and "=" not in matchList[0]
|
||||
and re.search(r"\[.+\]", matchList[0]) == None
|
||||
):
|
||||
response = getSpeaker(matchList[0])
|
||||
speaker = response[0]
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
# data[i] = data[i].replace(matchList[0], f'{speaker}')
|
||||
else:
|
||||
speaker = ''
|
||||
|
||||
speaker = ""
|
||||
|
||||
# Lines
|
||||
if 'FACE' not in data[i]:
|
||||
matchList = re.findall(r'\[.+?\](.+)\[.+\]', data[i])
|
||||
if "FACE" not in data[i]:
|
||||
matchList = re.findall(r"\[.+?\](.+)\[.+\]", data[i])
|
||||
else:
|
||||
matchList = re.findall(r'face=.+?\]\[.+?\](.+)\[.+\]', data[i])
|
||||
if len(matchList) > 0 and '=' not in matchList[0]:
|
||||
matchList = re.findall(r"face=.+?\]\[.+?\](.+)\[.+\]", data[i])
|
||||
if len(matchList) > 0 and "=" not in matchList[0]:
|
||||
# No Japanese text
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', matchList[0]):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", matchList[0]):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Remove [r] and [l]
|
||||
oldjaString = matchList[0]
|
||||
jaString = oldjaString
|
||||
jaString = jaString.replace('[r]', ' ')
|
||||
jaString = jaString.replace('[l]', '')
|
||||
jaString = jaString.replace("[r]", " ")
|
||||
jaString = jaString.replace("[l]", "")
|
||||
|
||||
# Join up 401 groups for better translation.
|
||||
finalJAString = jaString
|
||||
|
||||
# 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(' ', ' ')
|
||||
finalJAString = finalJAString.replace('】', ')')
|
||||
finalJAString = finalJAString.replace('【 ', '(')
|
||||
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(" ", " ")
|
||||
finalJAString = finalJAString.replace("】", ")")
|
||||
finalJAString = finalJAString.replace("【 ", "(")
|
||||
|
||||
# Furigana Removal
|
||||
matchList = re.findall(r'(\[ruby\stext=.+text=\"(.+)\"\])', finalJAString)
|
||||
matchList = re.findall(
|
||||
r"(\[ruby\stext=.+text=\"(.+)\"\])", finalJAString
|
||||
)
|
||||
if len(matchList) > 0:
|
||||
finalJAString = finalJAString.replace(matchList[0][0], matchList[0][1])
|
||||
finalJAString = finalJAString.replace(
|
||||
matchList[0][0], matchList[0][1]
|
||||
)
|
||||
|
||||
# Add Speaker (If there is one)
|
||||
if speaker != '':
|
||||
finalJAString = f'{speaker}: {finalJAString}'
|
||||
if speaker != "":
|
||||
finalJAString = f"{speaker}: {finalJAString}"
|
||||
|
||||
# [Passthrough 1] Append To List
|
||||
if setData is False:
|
||||
lineList.append(finalJAString)
|
||||
|
||||
|
||||
# [Passthrough 2] Set Data
|
||||
else:
|
||||
# Grab and Pop
|
||||
|
|
@ -299,22 +342,24 @@ def translateTyrano(data, pbar, filename, setData, jobList):
|
|||
lineList.pop(0)
|
||||
|
||||
# Remove speaker
|
||||
translatedText = re.sub(r'^\[?(.+?)\]?\s?[|:]\s?', '', translatedText)
|
||||
translatedText = re.sub(
|
||||
r"^\[?(.+?)\]?\s?[|:]\s?", "", translatedText
|
||||
)
|
||||
|
||||
# Textwrap
|
||||
translatedText = textwrap.fill(translatedText, WIDTH)
|
||||
translatedText = translatedText.replace('\n', '[r]')
|
||||
translatedText = translatedText.replace("\n", "[r]")
|
||||
|
||||
# Set Data
|
||||
data[i] = data[i].replace(oldjaString, translatedText)
|
||||
|
||||
# Next Line
|
||||
i += 1
|
||||
|
||||
|
||||
# Translate Data
|
||||
lineListTL = []
|
||||
setData = False
|
||||
|
||||
|
||||
# Line List
|
||||
if len(lineList) > 0:
|
||||
pbar.total = len(lineList)
|
||||
|
|
@ -335,17 +380,23 @@ def translateTyrano(data, pbar, filename, setData, jobList):
|
|||
translateTyrano(data, pbar, filename, True, [lineListTL])
|
||||
|
||||
return totalTokens
|
||||
|
||||
|
||||
# Save some money and enter the character before translation
|
||||
def getSpeaker(speaker):
|
||||
match speaker:
|
||||
case 'ファイン':
|
||||
return ['Fine', [0,0]]
|
||||
case '':
|
||||
return ['', [0,0]]
|
||||
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)
|
||||
response = translateGPT(
|
||||
speaker,
|
||||
"Reply with only the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
response[0] = response[0].title()
|
||||
speakerList = [speaker, response[0]]
|
||||
NAMESLIST.append(speakerList)
|
||||
|
|
@ -354,74 +405,76 @@ def getSpeaker(speaker):
|
|||
else:
|
||||
for i in range(len(NAMESLIST)):
|
||||
if speaker == NAMESLIST[i][0]:
|
||||
return [NAMESLIST[i][1],[0,0]]
|
||||
|
||||
return [speaker,[0,0]]
|
||||
return [NAMESLIST[i][1], [0, 0]]
|
||||
|
||||
return [speaker, [0, 0]]
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Nested
|
||||
count = 0
|
||||
nestedList = re.findall(r'[\\]+[\w]+\[[\\]+[\w]+\[[0-9]+\]\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Nested_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Icons
|
||||
count = 0
|
||||
iconList = re.findall(r'[\\]+[iIkKwWaA]+\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(icon, "[Ascii_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Colors
|
||||
count = 0
|
||||
colorList = re.findall(r'[\\]+[cC]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(color, "[Color_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Names
|
||||
count = 0
|
||||
nameList = re.findall(r'[\\]+[nN]\[.+?\]+', jaString)
|
||||
nameList = re.findall(r"[\\]+[nN]\[.+?\]+", jaString)
|
||||
nameList = set(nameList)
|
||||
if len(nameList) != 0:
|
||||
for name in nameList:
|
||||
jaString = jaString.replace(name, '[Noun_' + str(count) + ']')
|
||||
jaString = jaString.replace(name, "[Noun_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Variables
|
||||
count = 0
|
||||
varList = re.findall(r'[\\]+[vV]\[[0-9]+\]', jaString)
|
||||
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) + ']')
|
||||
jaString = jaString.replace(var, "[Var_" + str(count) + "]")
|
||||
count += 1
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
formatList = re.findall(r'[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]', jaString)
|
||||
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) + ']')
|
||||
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)
|
||||
matchList = re.findall(r"\[\s?.+?\s?\]", translatedText)
|
||||
if len(matchList) > 0:
|
||||
for match in matchList:
|
||||
text = match.strip()
|
||||
|
|
@ -431,60 +484,66 @@ def resubVars(translatedText, allList):
|
|||
count = 0
|
||||
if len(allList[0]) != 0:
|
||||
for var in allList[0]:
|
||||
translatedText = translatedText.replace('[Nested_' + str(count) + ']', var)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
眠り姫 (Sleeping Princess) - Female\n\
|
||||
迷子 (Lost Child) - Male\n\
|
||||
'
|
||||
|
||||
system = PROMPT + VOCAB if fullPromptFlag else \
|
||||
f"\
|
||||
"
|
||||
|
||||
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\
|
||||
|
|
@ -496,9 +555,11 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
|
|||
- `...` can be a part of the dialogue. Translate it as it is.\n\
|
||||
{VOCAB}\n\
|
||||
"
|
||||
user = f'{subbedT}'
|
||||
)
|
||||
user = f"{subbedT}"
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history, penalty):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -511,9 +572,9 @@ def translateText(characters, system, user, history, penalty):
|
|||
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}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0,
|
||||
frequency_penalty=penalty,
|
||||
|
|
@ -522,17 +583,18 @@ def translateText(characters, system, user, history, penalty):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'Placeholder Text': '',
|
||||
'[' : '(',
|
||||
']' : ')'
|
||||
f"{LANGUAGE} Translation: ": "",
|
||||
"Translation: ": "",
|
||||
"っ": "",
|
||||
"〜": "~",
|
||||
"ッ": "",
|
||||
"。": ".",
|
||||
"Placeholder Text": "",
|
||||
"[": "(",
|
||||
"]": ")",
|
||||
# Add more replacements as needed
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
|
|
@ -543,11 +605,12 @@ def cleanTranslatedText(translatedText, varResponse):
|
|||
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'(?<=(.))ー+'
|
||||
|
||||
pattern = r"(?<=(.))ー+"
|
||||
|
||||
# Define a replacement function that elongates the captured character
|
||||
def repl(match):
|
||||
char = match.group(1) # The character before the ー sequence
|
||||
|
|
@ -557,8 +620,9 @@ def elongateCharacters(text):
|
|||
# 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+>`?'
|
||||
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)
|
||||
|
|
@ -567,11 +631,12 @@ def extractTranslation(translatedTextList, is_list):
|
|||
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')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -583,15 +648,17 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
global PBAR
|
||||
|
|
@ -605,8 +672,12 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
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'`<Line{i}>{item}</Line{i}>`' for i, item in enumerate(tItem)])
|
||||
payload = re.sub(r'(<Line\d+)(><)(\/Line\d+>)', r'\1>Placeholder Text<\3', payload)
|
||||
payload = "\n".join(
|
||||
[f"`<Line{i}>{item}</Line{i}>`" for i, item in enumerate(tItem)]
|
||||
)
|
||||
payload = re.sub(
|
||||
r"(<Line\d+)(><)(\/Line\d+>)", r"\1>Placeholder Text<\3", payload
|
||||
)
|
||||
varResponse = subVars(payload)
|
||||
subbedT = varResponse[0]
|
||||
else:
|
||||
|
|
@ -614,7 +685,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
continue
|
||||
|
||||
# Create Message
|
||||
|
|
@ -651,11 +722,13 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
tList[index] = extractedTranslations
|
||||
if len(tItem) != len(extractedTranslations):
|
||||
mismatch = True # Just here for breakpoint
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
# Create History
|
||||
if not mismatch:
|
||||
history = extractedTranslations[-10:] # Update history if we have a list
|
||||
history = extractedTranslations[
|
||||
-10:
|
||||
] # Update history if we have a list
|
||||
else:
|
||||
history = text[-10:]
|
||||
PBAR.update(len(tItem))
|
||||
|
|
|
|||
1334
modules/wolf.py
1334
modules/wolf.py
File diff suppressed because it is too large
Load diff
331
modules/wolf2.py
331
modules/wolf2.py
|
|
@ -16,49 +16,50 @@ 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')
|
||||
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'))
|
||||
# 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'))
|
||||
WIDTH = int(os.getenv("width"))
|
||||
LISTWIDTH = int(os.getenv("listWidth"))
|
||||
NOTEWIDTH = 70
|
||||
MAXHISTORY = 10
|
||||
ESTIMATE = ''
|
||||
ESTIMATE = ""
|
||||
TOKENS = [0, 0]
|
||||
NAMESLIST = []
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> instead
|
||||
NAMES = False # Output a list of all the character names found
|
||||
BRFLAG = False # If the game uses <br> 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)
|
||||
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}'
|
||||
# tqdm Globals
|
||||
BAR_FORMAT = "{l_bar}{bar:10}{r_bar}{bar:-10b}"
|
||||
POSITION = 0
|
||||
LEAVE = False
|
||||
|
||||
# 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 = .002
|
||||
OUTPUTAPICOST = .002
|
||||
if "gpt-3.5" in MODEL:
|
||||
INPUTAPICOST = 0.002
|
||||
OUTPUTAPICOST = 0.002
|
||||
BATCHSIZE = 10
|
||||
elif 'gpt-4' in MODEL:
|
||||
INPUTAPICOST = .005
|
||||
OUTPUTAPICOST = .015
|
||||
elif "gpt-4" in MODEL:
|
||||
INPUTAPICOST = 0.005
|
||||
OUTPUTAPICOST = 0.015
|
||||
BATCHSIZE = 40
|
||||
|
||||
|
||||
def handleWOLF2(filename, estimate):
|
||||
global ESTIMATE
|
||||
ESTIMATE = estimate
|
||||
|
|
@ -75,17 +76,21 @@ def handleWOLF2(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
|
||||
# Print Total
|
||||
totalString = getResultString(['', TOKENS, None], end - start, '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
|
||||
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:
|
||||
with open(
|
||||
"translated/" + filename, "w", encoding="shift_jis", errors="ignore"
|
||||
) as outFile:
|
||||
start = time.time()
|
||||
translatedData = openFiles(filename)
|
||||
|
||||
|
|
@ -98,23 +103,36 @@ def handleWOLF2(filename, estimate):
|
|||
TOKENS[1] += translatedData[1][1]
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return 'Fail'
|
||||
return "Fail"
|
||||
|
||||
return getResultString(["", TOKENS, None], end - start, "TOTAL")
|
||||
|
||||
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] * .001 * INPUTAPICOST) +\
|
||||
(translatedData[1][1] * .001 * OUTPUTAPICOST)) + ']'
|
||||
timeString = Fore.BLUE + '[' + str(round(translationTime, 1)) + 's]'
|
||||
totalTokenstring = (
|
||||
Fore.YELLOW + "[Input: " + str(translatedData[1][0]) + "]"
|
||||
"[Output: "
|
||||
+ str(translatedData[1][1])
|
||||
+ "]" "[Cost: ${:,.4f}".format(
|
||||
(translatedData[1][0] * 0.001 * INPUTAPICOST)
|
||||
+ (translatedData[1][1] * 0.001 * OUTPUTAPICOST)
|
||||
)
|
||||
+ "]"
|
||||
)
|
||||
timeString = Fore.BLUE + "[" + str(round(translationTime, 1)) + "s]"
|
||||
|
||||
if translatedData[2] == None:
|
||||
# Success
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.GREEN + u' \u2713 ' + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.GREEN
|
||||
+ " \u2713 "
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
else:
|
||||
# Fail
|
||||
|
|
@ -123,31 +141,41 @@ def getResultString(translatedData, translationTime, filename):
|
|||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
errorString = str(e) + Fore.RED
|
||||
return filename + ': ' + totalTokenstring + timeString + Fore.RED + u' \u2717 ' +\
|
||||
errorString + Fore.RESET
|
||||
return (
|
||||
filename
|
||||
+ ": "
|
||||
+ totalTokenstring
|
||||
+ timeString
|
||||
+ Fore.RED
|
||||
+ " \u2717 "
|
||||
+ errorString
|
||||
+ Fore.RESET
|
||||
)
|
||||
|
||||
|
||||
def openFiles(filename):
|
||||
with open('files/' + filename, 'r', encoding='shift_jis') as readFile:
|
||||
with open("files/" + filename, "r", encoding="shift_jis") as readFile:
|
||||
translatedData = parseWOLF(readFile, filename)
|
||||
|
||||
# Delete lines marked for deletion
|
||||
finalData = []
|
||||
for line in translatedData[0]:
|
||||
if line != '\\d\n':
|
||||
if line != "\\d\n":
|
||||
finalData.append(line)
|
||||
translatedData[0] = finalData
|
||||
|
||||
|
||||
return translatedData
|
||||
|
||||
|
||||
def parseWOLF(readFile, filename):
|
||||
totalTokens = [0,0]
|
||||
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
|
||||
pbar.desc = filename
|
||||
|
||||
try:
|
||||
result = translateWOLF(data, [], pbar, filename)
|
||||
|
|
@ -158,39 +186,40 @@ def parseWOLF(readFile, filename):
|
|||
return [data, totalTokens, e]
|
||||
return [data, totalTokens, None]
|
||||
|
||||
|
||||
def translateWOLF(data, translatedList, pbar, filename):
|
||||
stringList = []
|
||||
currentGroup = []
|
||||
tokens = [0,0]
|
||||
speaker = ''
|
||||
tokens = [0, 0]
|
||||
speaker = ""
|
||||
global LOCK, ESTIMATE, PBAR
|
||||
PBAR = pbar
|
||||
i = 0
|
||||
|
||||
while i < len(data):
|
||||
# Speaker
|
||||
matchList = re.findall(r'(.*):', data[i])
|
||||
matchList = re.findall(r"(.*):", 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] = f'{speaker}:\n'
|
||||
data[i] = f"{speaker}:\n"
|
||||
i += 1
|
||||
else:
|
||||
speaker = ''
|
||||
speaker = ""
|
||||
|
||||
# Options
|
||||
if '//選択肢' in data[i]:
|
||||
if "//選択肢" in data[i]:
|
||||
i += 1
|
||||
choiceList = []
|
||||
initialIndex = i
|
||||
while('//' in data[i] and 'の場合' not in data[i]):
|
||||
choiceList.append(re.search(r'\/\/(.*)', data[i]).group(1))
|
||||
while "//" in data[i] and "の場合" not in data[i]:
|
||||
choiceList.append(re.search(r"\/\/(.*)", data[i]).group(1))
|
||||
i += 1
|
||||
|
||||
|
||||
# Translate
|
||||
response = translateGPT(choiceList, 'This will be a dialogue option', True)
|
||||
response = translateGPT(choiceList, "This will be a dialogue option", True)
|
||||
tokens[0] += response[1][0]
|
||||
tokens[1] += response[1][1]
|
||||
choiceListTL = response[0]
|
||||
|
|
@ -199,9 +228,9 @@ def translateWOLF(data, translatedList, pbar, filename):
|
|||
if len(choiceList) == len(choiceListTL):
|
||||
# Set Data
|
||||
i = initialIndex
|
||||
while('//' in data[i] and 'の場合' not in data[i]):
|
||||
choiceListTL[0] = choiceListTL[0].replace(', ', '、')
|
||||
data[i] = f'//{choiceListTL[0]}\n'
|
||||
while "//" in data[i] and "の場合" not in data[i]:
|
||||
choiceListTL[0] = choiceListTL[0].replace(", ", "、")
|
||||
data[i] = f"//{choiceListTL[0]}\n"
|
||||
choiceListTL.pop(0)
|
||||
i += 1
|
||||
|
||||
|
|
@ -212,36 +241,46 @@ def translateWOLF(data, translatedList, pbar, filename):
|
|||
MISMATCH.append(filename)
|
||||
|
||||
# Lines
|
||||
if r'/' not in data[i] and '@' not in data[i] and data[i] != '\n':
|
||||
if r"/" not in data[i] and "@" not in data[i] and data[i] != "\n":
|
||||
# Pass 1
|
||||
if translatedList == []:
|
||||
# Grab Consecutive Strings
|
||||
currentGroup.append(data[i])
|
||||
i += 1
|
||||
while i < len(data) and r'/' not in data[i] and '@' not in data[i] and data[i] != '\n':
|
||||
while (
|
||||
i < len(data)
|
||||
and r"/" not in data[i]
|
||||
and "@" not in data[i]
|
||||
and data[i] != "\n"
|
||||
):
|
||||
currentGroup.append(data[i])
|
||||
i += 1
|
||||
|
||||
|
||||
# Join up 401 groups for better translation.
|
||||
if len(currentGroup) > 0:
|
||||
jaString = ''.join(currentGroup)
|
||||
jaString = "".join(currentGroup)
|
||||
currentGroup = []
|
||||
|
||||
|
||||
# Remove any textwrap
|
||||
jaString = jaString.replace('\n', ' ')
|
||||
jaString = jaString.replace("\n", " ")
|
||||
|
||||
# Add Speaker (If there is one)
|
||||
if speaker != '':
|
||||
jaString = f'{speaker}: {jaString}'
|
||||
if speaker != "":
|
||||
jaString = f"{speaker}: {jaString}"
|
||||
|
||||
# Add String
|
||||
stringList.append(jaString)
|
||||
i += 1
|
||||
|
||||
|
||||
# Pass 2
|
||||
else:
|
||||
# Insert Strings
|
||||
while i < len(data) and r'/' not in data[i] and '@' not in data[i] and data[i] != '\n':
|
||||
while (
|
||||
i < len(data)
|
||||
and r"/" not in data[i]
|
||||
and "@" not in data[i]
|
||||
and data[i] != "\n"
|
||||
):
|
||||
data.pop(i)
|
||||
|
||||
# Get Text
|
||||
|
|
@ -252,13 +291,13 @@ def translateWOLF(data, translatedList, pbar, filename):
|
|||
translatedList = None
|
||||
|
||||
# Remove added speaker
|
||||
translatedText = re.sub(r'^.+?:\s', '', translatedText)
|
||||
translatedText = re.sub(r"^.+?:\s", "", translatedText)
|
||||
|
||||
# Textwrap
|
||||
translatedText = textwrap.fill(translatedText, width=WIDTH)
|
||||
|
||||
# Set Data
|
||||
data.insert(i, f'{translatedText}\n')
|
||||
data.insert(i, f"{translatedText}\n")
|
||||
i += 1
|
||||
|
||||
# Nothing relevant. Skip Line.
|
||||
|
|
@ -270,9 +309,9 @@ def translateWOLF(data, translatedList, pbar, filename):
|
|||
# Set Progress
|
||||
pbar.total = len(stringList)
|
||||
pbar.refresh()
|
||||
|
||||
|
||||
# Translate
|
||||
response = translateGPT(stringList, '', True)
|
||||
response = translateGPT(stringList, "", True)
|
||||
tokens[0] += response[1][0]
|
||||
tokens[1] += response[1][1]
|
||||
translatedList = response[0]
|
||||
|
|
@ -288,23 +327,32 @@ 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:
|
||||
case 'ファイン':
|
||||
return ['Fine', [0,0]]
|
||||
case '':
|
||||
return ['', [0,0]]
|
||||
case "ファイン":
|
||||
return ["Fine", [0, 0]]
|
||||
case "":
|
||||
return ["", [0, 0]]
|
||||
case _:
|
||||
# Store Speaker
|
||||
if speaker not in str(NAMESLIST):
|
||||
response = translateGPT(speaker, 'Reply with the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
response = translateGPT(
|
||||
speaker,
|
||||
"Reply with the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
response[0] = response[0].title()
|
||||
response[0] = response[0].replace("'S", "'s")
|
||||
|
||||
# Retry if name doesn't translate for some reason
|
||||
if re.search(r'([a-zA-Z??])', response[0]) == None:
|
||||
response = translateGPT(speaker, 'Reply with the '+ LANGUAGE +' translation of the NPC name.', False)
|
||||
if re.search(r"([a-zA-Z??])", response[0]) == None:
|
||||
response = translateGPT(
|
||||
speaker,
|
||||
"Reply with the " + LANGUAGE + " translation of the NPC name.",
|
||||
False,
|
||||
)
|
||||
response[0] = response[0].title()
|
||||
response[0] = response[0].replace("'S", "'s")
|
||||
|
||||
|
|
@ -315,50 +363,56 @@ def getSpeaker(speaker):
|
|||
else:
|
||||
for i in range(len(NAMESLIST)):
|
||||
if speaker == NAMESLIST[i][0]:
|
||||
return [NAMESLIST[i][1],[0,0]]
|
||||
|
||||
return [speaker,[0,0]]
|
||||
return [NAMESLIST[i][1], [0, 0]]
|
||||
|
||||
return [speaker, [0, 0]]
|
||||
|
||||
|
||||
def subVars(jaString):
|
||||
jaString = jaString.replace('\u3000', ' ')
|
||||
jaString = jaString.replace("\u3000", " ")
|
||||
|
||||
# Formatting
|
||||
count = 0
|
||||
codeList = re.findall(r'[\\]+[\w]+\[[a-zA-Z0-9\\\[\]\_,\s-]+\]', jaString)
|
||||
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) + ']')
|
||||
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)
|
||||
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)
|
||||
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)]
|
||||
|
||||
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\
|
||||
characters = "Game Characters:\n\
|
||||
ロラン (Roland) - Male\n\
|
||||
リュカ (Ryuka) - Male\n\
|
||||
レックス (Rex) - Male\n\
|
||||
|
|
@ -403,10 +457,12 @@ def createContext(fullPromptFlag, subbedT):
|
|||
アロマ (Aroma) - Female\n\
|
||||
ピッケ (Pikke) - Female\n\
|
||||
ドラオ (Dorao) - Male\n\
|
||||
'
|
||||
|
||||
system = PROMPT + VOCAB if fullPromptFlag else \
|
||||
f"\
|
||||
"
|
||||
|
||||
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\
|
||||
|
|
@ -418,12 +474,14 @@ Output ONLY the {LANGUAGE} translation in the following format: `Translation: <{
|
|||
- `...` can be a part of the dialogue. Translate it as it is.\n\
|
||||
{VOCAB}\n\
|
||||
"
|
||||
)
|
||||
if isinstance(subbedT, list):
|
||||
user = f'```json\n{subbedT}```'
|
||||
user = f"```json\n{subbedT}```"
|
||||
else:
|
||||
user = subbedT
|
||||
return characters, system, user
|
||||
|
||||
|
||||
def translateText(characters, system, user, history, penalty, format):
|
||||
# Prompt
|
||||
msg = [{"role": "system", "content": system + characters}]
|
||||
|
|
@ -438,13 +496,13 @@ def translateText(characters, system, user, history, penalty, format):
|
|||
msg.append({"role": "system", "content": history})
|
||||
|
||||
# Response Format
|
||||
if format == 'json':
|
||||
responseFormat = { "type": "json_object" }
|
||||
if format == "json":
|
||||
responseFormat = {"type": "json_object"}
|
||||
else:
|
||||
responseFormat = { "type": "text" }
|
||||
|
||||
responseFormat = {"type": "text"}
|
||||
|
||||
# Content to TL
|
||||
msg.append({"role": "user", "content": f'{user}'})
|
||||
msg.append({"role": "user", "content": f"{user}"})
|
||||
response = openai.chat.completions.create(
|
||||
temperature=0,
|
||||
frequency_penalty=penalty,
|
||||
|
|
@ -454,18 +512,19 @@ def translateText(characters, system, user, history, penalty, format):
|
|||
)
|
||||
return response
|
||||
|
||||
|
||||
def cleanTranslatedText(translatedText, varResponse):
|
||||
placeholders = {
|
||||
f'{LANGUAGE} Translation: ': '',
|
||||
'Translation: ': '',
|
||||
'っ': '',
|
||||
'〜': '~',
|
||||
'ッ': '',
|
||||
'。': '.',
|
||||
'「': '\\"',
|
||||
'」': '\\"',
|
||||
'- ': '-',
|
||||
'Placeholder Text': '',
|
||||
f"{LANGUAGE} Translation: ": "",
|
||||
"Translation: ": "",
|
||||
"っ": "",
|
||||
"〜": "~",
|
||||
"ッ": "",
|
||||
"。": ".",
|
||||
"「": '\\"',
|
||||
"」": '\\"',
|
||||
"- ": "-",
|
||||
"Placeholder Text": "",
|
||||
# Add more replacements as needed
|
||||
}
|
||||
for target, replacement in placeholders.items():
|
||||
|
|
@ -476,11 +535,12 @@ def cleanTranslatedText(translatedText, varResponse):
|
|||
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'(?<=(.))ー+'
|
||||
|
||||
pattern = r"(?<=(.))ー+"
|
||||
|
||||
# Define a replacement function that elongates the captured character
|
||||
def repl(match):
|
||||
char = match.group(1) # The character before the ー sequence
|
||||
|
|
@ -490,6 +550,7 @@ def elongateCharacters(text):
|
|||
# Use re.sub() to replace the pattern in the text
|
||||
return re.sub(pattern, repl, text)
|
||||
|
||||
|
||||
def extractTranslation(translatedTextList, is_list):
|
||||
try:
|
||||
translatedTextList = re.sub(r'\\"+\"([^,\n}])', r'\\"\1', translatedTextList)
|
||||
|
|
@ -502,15 +563,15 @@ def extractTranslation(translatedTextList, is_list):
|
|||
return string_list[0]
|
||||
|
||||
except Exception as e:
|
||||
PBAR.write(f'extractTranslation Error: {e} on String {translatedTextList}')
|
||||
PBAR.write(f"extractTranslation Error: {e} on String {translatedTextList}")
|
||||
return None
|
||||
|
||||
|
||||
def countTokens(characters, system, user, history):
|
||||
inputTotalTokens = 0
|
||||
outputTotalTokens = 0
|
||||
enc = tiktoken.encoding_for_model('gpt-4')
|
||||
|
||||
enc = tiktoken.encoding_for_model("gpt-4")
|
||||
|
||||
# Input
|
||||
if isinstance(history, list):
|
||||
for line in history:
|
||||
|
|
@ -522,26 +583,28 @@ def countTokens(characters, system, user, history):
|
|||
inputTotalTokens += len(enc.encode(user))
|
||||
|
||||
# Output
|
||||
outputTotalTokens += round(len(enc.encode(user))*3)
|
||||
outputTotalTokens += round(len(enc.encode(user)) * 3)
|
||||
|
||||
return [inputTotalTokens, outputTotalTokens]
|
||||
|
||||
|
||||
def combineList(tlist, text):
|
||||
if isinstance(text, list):
|
||||
return [t for sublist in tlist for t in sublist]
|
||||
return tlist[0]
|
||||
|
||||
|
||||
@retry(exceptions=Exception, tries=5, delay=5)
|
||||
def translateGPT(text, history, fullPromptFlag):
|
||||
global PBAR
|
||||
|
||||
|
||||
mismatch = False
|
||||
totalTokens = [0, 0]
|
||||
if isinstance(text, list):
|
||||
format = 'json'
|
||||
format = "json"
|
||||
tList = batchList(text, BATCHSIZE)
|
||||
else:
|
||||
format = 'text'
|
||||
format = "text"
|
||||
tList = [text]
|
||||
|
||||
for index, tItem in enumerate(tList):
|
||||
|
|
@ -556,7 +619,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
subbedT = varResponse[0]
|
||||
|
||||
# Things to Check before starting translation
|
||||
if not re.search(r'[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+', subbedT):
|
||||
if not re.search(r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9]+", subbedT):
|
||||
if PBAR is not None:
|
||||
PBAR.update(len(tItem))
|
||||
continue
|
||||
|
|
@ -581,9 +644,13 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
translatedText = cleanTranslatedText(translatedText, varResponse)
|
||||
if isinstance(tItem, list):
|
||||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
if extractedTranslations == None or len(tItem) != len(extractedTranslations):
|
||||
if extractedTranslations == None or len(tItem) != len(
|
||||
extractedTranslations
|
||||
):
|
||||
# Mismatch. Try Again
|
||||
response = translateText(characters, system, user, history, 0.05, format)
|
||||
response = translateText(
|
||||
characters, system, user, history, 0.05, format
|
||||
)
|
||||
translatedText = response.choices[0].message.content
|
||||
totalTokens[0] += response.usage.prompt_tokens
|
||||
totalTokens[1] += response.usage.completion_tokens
|
||||
|
|
@ -592,13 +659,17 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
translatedText = cleanTranslatedText(translatedText, varResponse)
|
||||
if isinstance(tItem, list):
|
||||
extractedTranslations = extractTranslation(translatedText, True)
|
||||
if extractedTranslations == None or len(tItem) != len(extractedTranslations):
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
if extractedTranslations == None or len(tItem) != len(
|
||||
extractedTranslations
|
||||
):
|
||||
mismatch = True # Just here for breakpoint
|
||||
|
||||
# Set if no mismatch
|
||||
if mismatch == False:
|
||||
tList[index] = extractedTranslations
|
||||
history = extractedTranslations[-10:] # Update history if we have a list
|
||||
history = extractedTranslations[
|
||||
-10:
|
||||
] # Update history if we have a list
|
||||
else:
|
||||
history = text[-10:]
|
||||
mismatch = False
|
||||
|
|
@ -609,7 +680,7 @@ def translateGPT(text, history, fullPromptFlag):
|
|||
PBAR.update(len(tItem))
|
||||
else:
|
||||
# Ensure we're passing a single string to extractTranslation
|
||||
tList[index] = translatedText
|
||||
tList[index] = translatedText
|
||||
|
||||
finalList = combineList(tList, text)
|
||||
return [finalList, totalTokens]
|
||||
|
|
|
|||
Loading…
Reference in a new issue