First map tled
This commit is contained in:
parent
2db8ea9fb6
commit
6bff8ff86a
1 changed files with 527 additions and 0 deletions
527
modules/srpg.py
527
modules/srpg.py
|
|
@ -96,6 +96,11 @@ GENERIC_FILES = [
|
|||
# "classes",
|
||||
]
|
||||
|
||||
# List of file patterns that use parseMap
|
||||
MAP_FILES = [
|
||||
"map",
|
||||
]
|
||||
|
||||
|
||||
def update_vocab_section(category: str, pairs: list[tuple[str, str]]):
|
||||
"""Update or insert a section in vocab.txt for the given category with provided pairs.
|
||||
|
|
@ -263,6 +268,9 @@ def openFiles(filename):
|
|||
# Check if filename matches recollection pattern
|
||||
if "recollection" in filename.lower():
|
||||
translatedData = parseRecollection(data, filename)
|
||||
# Check if filename matches map pattern
|
||||
elif any(pattern in filename.lower() for pattern in MAP_FILES):
|
||||
translatedData = parseMap(data, filename)
|
||||
# Check if filename matches any pattern in GENERIC_FILES
|
||||
elif any(pattern in filename.lower() for pattern in GENERIC_FILES):
|
||||
translatedData = parseGeneric(data, filename)
|
||||
|
|
@ -776,6 +784,525 @@ def translateRecollection(data, filename, translatedDataList=None):
|
|||
return totalTokens
|
||||
|
||||
|
||||
def parseMap(data, filename):
|
||||
"""
|
||||
Parser for SRPG Studio map.json files.
|
||||
Structure: Single object with:
|
||||
- id, name, desc, mapName
|
||||
- victoryConds (array of strings)
|
||||
- defeatConds (array of strings)
|
||||
- EnemyUnits (array of objects with id, name, desc, events structure similar to recollection)
|
||||
|
||||
Args:
|
||||
data: Parsed JSON data (single map object)
|
||||
filename: Name of the file being parsed
|
||||
|
||||
Returns:
|
||||
Tuple of (data, token counts, error)
|
||||
"""
|
||||
global PBAR
|
||||
|
||||
totalTokens = [0, 0]
|
||||
|
||||
try:
|
||||
# Count work units (all translatable fields)
|
||||
total_units = 0
|
||||
|
||||
# Count top-level fields: desc, mapName (name is an identifier and should not be translated)
|
||||
for field in ["desc", "mapName"]:
|
||||
if field in data and data[field]:
|
||||
total_units += 1
|
||||
|
||||
# Count victoryConds array items
|
||||
if "victoryConds" in data and isinstance(data["victoryConds"], list):
|
||||
for cond in data["victoryConds"]:
|
||||
if cond:
|
||||
total_units += 1
|
||||
|
||||
# Count defeatConds array items
|
||||
if "defeatConds" in data and isinstance(data["defeatConds"], list):
|
||||
for cond in data["defeatConds"]:
|
||||
if cond:
|
||||
total_units += 1
|
||||
|
||||
# Count units with events (EnemyUnits, EvEnemyUnits)
|
||||
for unitsKey in ["EnemyUnits", "EvEnemyUnits"]:
|
||||
if unitsKey in data and isinstance(data[unitsKey], list):
|
||||
for unit in data[unitsKey]:
|
||||
if not unit:
|
||||
continue
|
||||
|
||||
# Count unit name and desc
|
||||
for field in ["name", "desc"]:
|
||||
if field in unit and unit[field]:
|
||||
total_units += 1
|
||||
|
||||
# Count events->pages->commands->data and speaker
|
||||
if "events" in unit and isinstance(unit["events"], list):
|
||||
for event in unit["events"]:
|
||||
if not event or "pages" not in event:
|
||||
continue
|
||||
|
||||
if not isinstance(event["pages"], list):
|
||||
continue
|
||||
|
||||
for page in event["pages"]:
|
||||
if not page or "commands" not in page:
|
||||
continue
|
||||
|
||||
if not isinstance(page["commands"], list):
|
||||
continue
|
||||
|
||||
for command in page["commands"]:
|
||||
if not command:
|
||||
continue
|
||||
|
||||
# Count data array items
|
||||
if "data" in command and isinstance(command["data"], list):
|
||||
for text in command["data"]:
|
||||
if text:
|
||||
total_units += 1
|
||||
|
||||
# Count speaker field
|
||||
if "speaker" in command and command["speaker"]:
|
||||
total_units += 1
|
||||
|
||||
# Count events (placeEvents, autoEvents, openingEvents, communicationEvents)
|
||||
for eventsKey in ["placeEvents", "autoEvents", "openingEvents", "communicationEvents"]:
|
||||
if eventsKey in data and isinstance(data[eventsKey], list):
|
||||
for event in data[eventsKey]:
|
||||
if not event or "pages" not in event:
|
||||
continue
|
||||
|
||||
if not isinstance(event["pages"], list):
|
||||
continue
|
||||
|
||||
for page in event["pages"]:
|
||||
if not page or "commands" not in page:
|
||||
continue
|
||||
|
||||
if not isinstance(page["commands"], list):
|
||||
continue
|
||||
|
||||
for command in page["commands"]:
|
||||
if not command:
|
||||
continue
|
||||
|
||||
# Count data array items
|
||||
if "data" in command and isinstance(command["data"], list):
|
||||
for text in command["data"]:
|
||||
if text:
|
||||
total_units += 1
|
||||
|
||||
# Count speaker field
|
||||
if "speaker" in command and command["speaker"]:
|
||||
total_units += 1
|
||||
|
||||
# Setup progress bar
|
||||
with LOCK:
|
||||
PBAR = tqdm(
|
||||
desc=filename,
|
||||
total=total_units,
|
||||
bar_format=BAR_FORMAT,
|
||||
position=POSITION,
|
||||
leave=LEAVE,
|
||||
)
|
||||
|
||||
# Translate the data using two-pass approach
|
||||
result = translateMap(data, filename)
|
||||
totalTokens[0] += result[0]
|
||||
totalTokens[1] += result[1]
|
||||
|
||||
# Close progress bar
|
||||
with LOCK:
|
||||
if PBAR:
|
||||
PBAR.close()
|
||||
PBAR = None
|
||||
|
||||
return (data, totalTokens, None)
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
if PBAR:
|
||||
with LOCK:
|
||||
PBAR.close()
|
||||
PBAR = None
|
||||
return (data, totalTokens, e)
|
||||
|
||||
|
||||
def translateMap(data, filename, translatedDataList=None):
|
||||
"""
|
||||
Translates map.json data structure.
|
||||
Uses two-pass approach via recursion:
|
||||
- Pass 1 (translatedDataList=None): Collect strings and batch translate
|
||||
- Pass 2 (translatedDataList set): Apply translations back to the data
|
||||
|
||||
Args:
|
||||
data: Single map object with name, desc, mapName, victoryConds, defeatConds, and EnemyUnits
|
||||
filename: Name of the file being translated
|
||||
translatedDataList: List containing translation lists
|
||||
- Pass 1: Empty lists to collect originals
|
||||
- Pass 2: Filled lists with translations
|
||||
|
||||
Returns:
|
||||
Tuple of [input tokens, output tokens]
|
||||
"""
|
||||
global PBAR
|
||||
|
||||
totalTokens = [0, 0]
|
||||
|
||||
# Initialize or extract lists
|
||||
if translatedDataList is None:
|
||||
# PASS 1: Create empty lists to collect strings
|
||||
descList = []
|
||||
mapNameList = []
|
||||
victoryCondsList = []
|
||||
defeatCondsList = []
|
||||
unitNameList = []
|
||||
unitDescList = []
|
||||
dataList = []
|
||||
speakerList = []
|
||||
originalSpeakerList = [] # For vocab update
|
||||
else:
|
||||
# PASS 2: Use provided translated lists
|
||||
descList = translatedDataList[0]
|
||||
mapNameList = translatedDataList[1]
|
||||
victoryCondsList = translatedDataList[2]
|
||||
defeatCondsList = translatedDataList[3]
|
||||
unitNameList = translatedDataList[4]
|
||||
unitDescList = translatedDataList[5]
|
||||
dataList = translatedDataList[6]
|
||||
speakerList = translatedDataList[7]
|
||||
|
||||
# Note: name field is not translated as it's an identifier (e.g., "ch3_瘴気の森")
|
||||
|
||||
# Handle desc field
|
||||
if "desc" in data and data["desc"]:
|
||||
# PASS 1: Collect original
|
||||
if translatedDataList is None:
|
||||
descList.append(data["desc"])
|
||||
# PASS 2: Apply translation
|
||||
else:
|
||||
if descList:
|
||||
data["desc"] = descList[0]
|
||||
descList.pop(0)
|
||||
|
||||
# Handle mapName field
|
||||
if "mapName" in data and data["mapName"]:
|
||||
# PASS 1: Collect original
|
||||
if translatedDataList is None:
|
||||
mapNameList.append(data["mapName"])
|
||||
# PASS 2: Apply translation
|
||||
else:
|
||||
if mapNameList:
|
||||
data["mapName"] = mapNameList[0]
|
||||
mapNameList.pop(0)
|
||||
|
||||
# Handle victoryConds array
|
||||
if "victoryConds" in data and isinstance(data["victoryConds"], list):
|
||||
for i, cond in enumerate(data["victoryConds"]):
|
||||
if cond:
|
||||
# PASS 1: Collect original
|
||||
if translatedDataList is None:
|
||||
victoryCondsList.append(cond)
|
||||
# PASS 2: Apply translation
|
||||
else:
|
||||
if victoryCondsList:
|
||||
data["victoryConds"][i] = victoryCondsList[0]
|
||||
victoryCondsList.pop(0)
|
||||
|
||||
# Handle defeatConds array
|
||||
if "defeatConds" in data and isinstance(data["defeatConds"], list):
|
||||
for i, cond in enumerate(data["defeatConds"]):
|
||||
if cond:
|
||||
# PASS 1: Collect original
|
||||
if translatedDataList is None:
|
||||
defeatCondsList.append(cond)
|
||||
# PASS 2: Apply translation
|
||||
else:
|
||||
if defeatCondsList:
|
||||
data["defeatConds"][i] = defeatCondsList[0]
|
||||
defeatCondsList.pop(0)
|
||||
|
||||
# Process all unit arrays (EnemyUnits, EvEnemyUnits)
|
||||
for unitsKey in ["EnemyUnits", "EvEnemyUnits"]:
|
||||
if unitsKey in data and isinstance(data[unitsKey], list):
|
||||
for unit in data[unitsKey]:
|
||||
if not unit:
|
||||
continue
|
||||
|
||||
# Handle unit name
|
||||
if "name" in unit and unit["name"]:
|
||||
if translatedDataList is None:
|
||||
unitNameList.append(unit["name"])
|
||||
else:
|
||||
if unitNameList:
|
||||
unit["name"] = unitNameList[0]
|
||||
unitNameList.pop(0)
|
||||
|
||||
# Handle unit desc
|
||||
if "desc" in unit and unit["desc"]:
|
||||
if translatedDataList is None:
|
||||
unitDescList.append(unit["desc"])
|
||||
else:
|
||||
if unitDescList:
|
||||
unit["desc"] = unitDescList[0]
|
||||
unitDescList.pop(0)
|
||||
|
||||
# Process events within units
|
||||
if "events" in unit and isinstance(unit["events"], list):
|
||||
for event in unit["events"]:
|
||||
if not event or "pages" not in event:
|
||||
continue
|
||||
|
||||
if not isinstance(event["pages"], list):
|
||||
continue
|
||||
|
||||
for page in event["pages"]:
|
||||
if not page or "commands" not in page:
|
||||
continue
|
||||
|
||||
if not isinstance(page["commands"], list):
|
||||
continue
|
||||
|
||||
for command in page["commands"]:
|
||||
if not command:
|
||||
continue
|
||||
|
||||
speaker = command.get("speaker", "")
|
||||
|
||||
# Handle data array
|
||||
if "data" in command and isinstance(command["data"], list):
|
||||
for i, text in enumerate(command["data"]):
|
||||
if text:
|
||||
if translatedDataList is None:
|
||||
text = text.replace("\n", " ")
|
||||
if speaker:
|
||||
dataList.append(f"[{speaker}]: {text}")
|
||||
else:
|
||||
dataList.append(text)
|
||||
else:
|
||||
if dataList:
|
||||
translated = dataList[0]
|
||||
if speaker:
|
||||
match = re.search(r'(^\[.+?\]\s?[|:]\s?)', translated)
|
||||
if match:
|
||||
translated = translated.replace(match.group(1), "")
|
||||
translated = dazedwrap.wrapText(translated, width=WIDTH)
|
||||
command["data"][i] = translated
|
||||
dataList.pop(0)
|
||||
|
||||
# Handle speaker field
|
||||
if "speaker" in command and command["speaker"]:
|
||||
if translatedDataList is None:
|
||||
originalSpeakerList.append(command["speaker"])
|
||||
speakerList.append(command["speaker"])
|
||||
else:
|
||||
if speakerList:
|
||||
command["speaker"] = speakerList[0]
|
||||
speakerList.pop(0)
|
||||
|
||||
# Process all event arrays (placeEvents, autoEvents, openingEvents, communicationEvents)
|
||||
for eventsKey in ["placeEvents", "autoEvents", "openingEvents", "communicationEvents"]:
|
||||
if eventsKey in data and isinstance(data[eventsKey], list):
|
||||
for event in data[eventsKey]:
|
||||
if not event or "pages" not in event:
|
||||
continue
|
||||
|
||||
if not isinstance(event["pages"], list):
|
||||
continue
|
||||
|
||||
for page in event["pages"]:
|
||||
if not page or "commands" not in page:
|
||||
continue
|
||||
|
||||
if not isinstance(page["commands"], list):
|
||||
continue
|
||||
|
||||
for command in page["commands"]:
|
||||
if not command:
|
||||
continue
|
||||
|
||||
speaker = command.get("speaker", "")
|
||||
|
||||
# Handle data array
|
||||
if "data" in command and isinstance(command["data"], list):
|
||||
for i, text in enumerate(command["data"]):
|
||||
if text:
|
||||
if translatedDataList is None:
|
||||
text = text.replace("\n", " ")
|
||||
if speaker:
|
||||
dataList.append(f"[{speaker}]: {text}")
|
||||
else:
|
||||
dataList.append(text)
|
||||
else:
|
||||
if dataList:
|
||||
translated = dataList[0]
|
||||
if speaker:
|
||||
match = re.search(r'(^\[.+?\]\s?[|:]\s?)', translated)
|
||||
if match:
|
||||
translated = translated.replace(match.group(1), "")
|
||||
translated = dazedwrap.wrapText(translated, width=WIDTH)
|
||||
command["data"][i] = translated
|
||||
dataList.pop(0)
|
||||
|
||||
# Handle speaker field
|
||||
if "speaker" in command and command["speaker"]:
|
||||
if translatedDataList is None:
|
||||
originalSpeakerList.append(command["speaker"])
|
||||
speakerList.append(command["speaker"])
|
||||
else:
|
||||
if speakerList:
|
||||
command["speaker"] = speakerList[0]
|
||||
speakerList.pop(0)
|
||||
|
||||
# If this was Pass 1, do the translation and recurse for Pass 2
|
||||
if translatedDataList is None:
|
||||
# Store original counts for mismatch checking
|
||||
originalDescCount = len(descList)
|
||||
originalMapNameCount = len(mapNameList)
|
||||
originalVictoryCondsCount = len(victoryCondsList)
|
||||
originalDefeatCondsCount = len(defeatCondsList)
|
||||
originalUnitNameCount = len(unitNameList)
|
||||
originalUnitDescCount = len(unitDescList)
|
||||
originalDataCount = len(dataList)
|
||||
originalSpeakerCount = len(speakerList)
|
||||
|
||||
# Batch translate map descriptions
|
||||
if descList:
|
||||
response = translateAI(
|
||||
descList,
|
||||
"Reply with only the " + LANGUAGE + " translation of the map description.",
|
||||
True
|
||||
)
|
||||
descList = response[0]
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
|
||||
# Batch translate map display names
|
||||
if mapNameList:
|
||||
response = translateAI(
|
||||
mapNameList,
|
||||
"Reply with only the " + LANGUAGE + " translation of the map display name.",
|
||||
True
|
||||
)
|
||||
mapNameList = response[0]
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
|
||||
# Batch translate victory conditions
|
||||
if victoryCondsList:
|
||||
response = translateAI(
|
||||
victoryCondsList,
|
||||
"Reply with only the " + LANGUAGE + " translation of the victory condition.",
|
||||
True
|
||||
)
|
||||
victoryCondsList = response[0]
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
|
||||
# Batch translate defeat conditions
|
||||
if defeatCondsList:
|
||||
response = translateAI(
|
||||
defeatCondsList,
|
||||
"Reply with only the " + LANGUAGE + " translation of the defeat condition.",
|
||||
True
|
||||
)
|
||||
defeatCondsList = response[0]
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
|
||||
# Batch translate unit names
|
||||
if unitNameList:
|
||||
response = translateAI(
|
||||
unitNameList,
|
||||
"Reply with only the " + LANGUAGE + " translation of the enemy unit name.",
|
||||
True
|
||||
)
|
||||
unitNameList = response[0]
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
|
||||
# Batch translate unit descriptions
|
||||
if unitDescList:
|
||||
response = translateAI(
|
||||
unitDescList,
|
||||
"Reply with only the " + LANGUAGE + " translation of the enemy unit description.",
|
||||
True
|
||||
)
|
||||
unitDescList = response[0]
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
|
||||
# Batch translate dialogue data
|
||||
if dataList:
|
||||
response = translateAI(
|
||||
dataList,
|
||||
"Reply with only the " + LANGUAGE + " translation of the dialogue text.",
|
||||
True
|
||||
)
|
||||
dataList = response[0]
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
|
||||
# Batch translate speakers
|
||||
if speakerList:
|
||||
response = translateAI(
|
||||
speakerList,
|
||||
"Reply with only the " + LANGUAGE + " translation of the speaker name.",
|
||||
True
|
||||
)
|
||||
speakerList = response[0]
|
||||
totalTokens[0] += response[1][0]
|
||||
totalTokens[1] += response[1][1]
|
||||
|
||||
# Check for mismatch errors
|
||||
if len(descList) != originalDescCount:
|
||||
with LOCK:
|
||||
if filename not in MISMATCH:
|
||||
MISMATCH.append(filename)
|
||||
|
||||
if len(mapNameList) != originalMapNameCount:
|
||||
with LOCK:
|
||||
if filename not in MISMATCH:
|
||||
MISMATCH.append(filename)
|
||||
|
||||
if len(victoryCondsList) != originalVictoryCondsCount:
|
||||
with LOCK:
|
||||
if filename not in MISMATCH:
|
||||
MISMATCH.append(filename)
|
||||
|
||||
if len(defeatCondsList) != originalDefeatCondsCount:
|
||||
with LOCK:
|
||||
if filename not in MISMATCH:
|
||||
MISMATCH.append(filename)
|
||||
|
||||
if len(unitNameList) != originalUnitNameCount:
|
||||
with LOCK:
|
||||
if filename not in MISMATCH:
|
||||
MISMATCH.append(filename)
|
||||
|
||||
if len(unitDescList) != originalUnitDescCount:
|
||||
with LOCK:
|
||||
if filename not in MISMATCH:
|
||||
MISMATCH.append(filename)
|
||||
|
||||
if len(dataList) != originalDataCount:
|
||||
with LOCK:
|
||||
if filename not in MISMATCH:
|
||||
MISMATCH.append(filename)
|
||||
|
||||
if len(speakerList) != originalSpeakerCount:
|
||||
with LOCK:
|
||||
if filename not in MISMATCH:
|
||||
MISMATCH.append(filename)
|
||||
|
||||
# PASS 2: Recursively call to apply translations
|
||||
translateMap(data, filename, [descList, mapNameList, victoryCondsList, defeatCondsList, unitNameList, unitDescList, dataList, speakerList])
|
||||
|
||||
return totalTokens
|
||||
|
||||
|
||||
def getResultString(translatedData, translationTime, filename):
|
||||
"""
|
||||
Formats the translation result string with token counts, cost, and time.
|
||||
|
|
|
|||
Loading…
Reference in a new issue