快快登录说出你的故事吧~!
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
本帖最后由 INKBIRDDD 于 2025-7-3 19:03 编辑
众所周知,MTS voky translator到目前只能使用DeepL和Google翻译,而且还难免做不到其他AI翻译的信雅达,XML文件导出后因为格式等等问题,会白白耗费大量的AI翻译算力,那有没有一种方法,对这个XML文件进行一个压缩毛巾式的压缩呢?
我抱着这样的疑问屁颠屁颠的去寻找了亲爱的小蓝鲸DeepSeek能不能写,它和我说:
有的,兄弟有的!
说完甩了我一大段代码。
之后我试着用了一下它写出来的代码,虽然有些bug但是小蓝鲸确实做得到,于是就对它进行了一场酣畅淋漓的话疗,让它自己给自己修BUG:
最终,辅助软件出生了,我将它命名为:
GameTranslationAssistant
软件很简单,分为一左一右的两个区域,
左边为模组的.package 中的XML内容
右边为 我叫做SimpleFormat 的简化格式
两边都可以单独【Import】【Export】 文件导入导出
左侧支持.xml .txt .py 右侧仅支持.txt (懒得多搞几个格式了)
*这里举例使用ChingYu大大的functional-lot-traits-challenges举例,可以看到左边的XML各种换行各种空格的格式被改为了右边的压缩格式。模组本来是一个一个的特征Package,我用S4S进行了打包后变成了这么长一大串,也是可以正常压缩的。
有了右边这么一大长串呢,就可以扔给任意AI或者翻译软件进行翻译了。
这里我又拜托了小蓝鲸,这次使用的是让它翻译MOD ,AI翻译完了你就可以直接复制到右边的SimpleFormat里面了。
导出时如果AI让右边的文字内容两旁的 ' 符号 消失(格式中有'是因为为了保持程序的稳定性不得不加的),可以点击【FIX】 自动补充(左边的哈希值AI一般都不会更改格式)。
AI翻译如果如图中一样自己给翻译分组还换行了也不用管,是可以正常导入的。
之后选择【Back to XML】 即可快速替换原文内容,之后保存回XML就可以使用
MTS voky translator的导入译文功能快速替换原文啦~(S4S理论上也行,但是考虑到很多模组有可能实际上是由很多小模组直接用S4S暴力打包的,会需要你手动多次的翻译还难找,我建议是直接用MTS给你导一个包含所有的完整XML,导出翻译package用MTS也更方便。)
Python代码,如果你有Python可以自行复制粘贴
import re
import xml.etree.ElementTree as ET
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import os
from xml.dom import minidom
import html
class GameTranslationAssistant:
def __init__(self, root):
self.root = root
self.root.title("SimsTranslationAssistant")
self.root.geometry("1400x900")
self.root.minsize(1200, 800) # 设置最小尺寸
self.root.configure(bg='#f0f0f0')
# 完整XML数据缓存
self.original_cache = {}
self.xml_declaration = ""
self.non_string_content = ""
self.original_full_content = ""
self.tables = [] # 存储所有Table元素信息
# 创建左右分栏
self.create_panes()
# 创建控制按钮
self.create_controls()
# 当前文件路径
self.current_left_file = None
self.current_right_file = None
# 设置网格权重确保正确展开
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(0, weight=1)
self.root.grid_columnconfigure(1, weight=1)
def create_panes(self):
# 主框架 - 使用pack布局管理器确保正确展开
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 左侧面板 - 格式01 (XML)
left_frame = ttk.LabelFrame(main_frame, text="XML", padding=10)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
left_frame.grid_rowconfigure(1, weight=1)
left_frame.grid_columnconfigure(0, weight=1)
# 左侧按钮行(顶部)
left_btn_frame = ttk.Frame(left_frame)
left_btn_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5))
# 左侧操作按钮
ttk.Button(left_btn_frame, text="Import", command=self.import_xml_left).pack(side=tk.LEFT, padx=2)
ttk.Button(left_btn_frame, text="Export", command=self.save_xml).pack(side=tk.LEFT, padx=2)
ttk.Button(left_btn_frame, text="To SimpleFormat", command=self.convert_to_key_value).pack(side=tk.LEFT, padx=2)
# 左侧跳转行控件(右上角)
left_jump_frame = ttk.Frame(left_btn_frame)
left_jump_frame.pack(side=tk.RIGHT, padx=5)
ttk.Label(left_jump_frame, text="Go to Line:").pack(side=tk.LEFT)
self.left_line_entry = ttk.Entry(left_jump_frame, width=6)
self.left_line_entry.pack(side=tk.LEFT, padx=2)
ttk.Button(left_jump_frame, text="GO", width=3, command=self.jump_to_left_line).pack(side=tk.LEFT)
# 创建左侧文本框架
left_text_frame = ttk.Frame(left_frame)
left_text_frame.grid(row=1, column=0, sticky="nsew", padx=0, pady=0)
left_text_frame.grid_rowconfigure(0, weight=1)
left_text_frame.grid_columnconfigure(0, weight=0) # 行号列不扩展
left_text_frame.grid_columnconfigure(1, weight=1) # 文本列扩展
# 左侧行号 - 禁止滚动
self.left_line_numbers = tk.Text(
left_text_frame, width=6, height=35, wrap=tk.NONE, font=("Consolas", 10),
bg="#f0f0f0", fg="#666666", state=tk.DISABLED, padx=5, pady=5, takefocus=0
)
self.left_line_numbers.grid(row=0, column=0, sticky="ns")
# 禁止行号区域的滚动事件
self.left_line_numbers.bind("<MouseWheel>", lambda e: "break")
# 创建文本容器框架
text_container = ttk.Frame(left_text_frame)
text_container.grid(row=0, column=1, sticky="nsew")
text_container.grid_rowconfigure(0, weight=1)
text_container.grid_columnconfigure(0, weight=1)
text_container.grid_columnconfigure(1, weight=0)
# 左侧文本区域
self.left_text = tk.Text(
text_container, width=65, height=35, wrap=tk.NONE, font=("Consolas", 10),
padx=5, pady=5, undo=True
)
self.left_text.grid(row=0, column=0, sticky="nsew")
# 左侧垂直滚动条
left_v_scroll = ttk.Scrollbar(text_container, orient=tk.VERTICAL)
left_v_scroll.grid(row=0, column=1, sticky="ns")
# 设置滚动回调 - 修复滚动同步问题
left_v_scroll.config(command=self.left_text.yview)
self.left_text.config(yscrollcommand=lambda *args: self.sync_scroll_left(*args))
# 左侧水平滚动条
left_h_scroll = ttk.Scrollbar(left_text_frame, orient=tk.HORIZONTAL, command=self.left_text.xview)
left_h_scroll.grid(row=1, column=0, columnspan=2, sticky="ew")
self.left_text.config(xscrollcommand=left_h_scroll.set)
# 绑定事件
self.left_text.bind("<Configure>", lambda e: self.update_line_numbers(self.left_text, self.left_line_numbers))
self.left_text.bind("<KeyRelease>", lambda e: self.update_line_numbers(self.left_text, self.left_line_numbers))
self.left_text.bind("<MouseWheel>", lambda e: self.update_line_numbers(self.left_text, self.left_line_numbers))
# 右侧面板 - 格式02 (SimpleFormat)
right_frame = ttk.LabelFrame(main_frame, text="SimpleFormat", padding=10)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
right_frame.grid_rowconfigure(1, weight=1)
right_frame.grid_columnconfigure(0, weight=1)
# 右侧按钮行(顶部)
right_btn_frame = ttk.Frame(right_frame)
right_btn_frame.grid(row=0, column=0, sticky="ew", pady=(0, 5))
# 右侧操作按钮
ttk.Button(right_btn_frame, text="Import", command=self.import_key_value).pack(side=tk.LEFT, padx=2)
ttk.Button(right_btn_frame, text="Export", command=self.save_key_value).pack(side=tk.LEFT, padx=2)
ttk.Button(right_btn_frame, text="FIX", command=self.fix_simple_format).pack(side=tk.LEFT, padx=2)
ttk.Button(right_btn_frame, text="Back to XML", command=self.convert_to_xml).pack(side=tk.LEFT, padx=2)
# 右侧跳转行控件(右上角)
right_jump_frame = ttk.Frame(right_btn_frame)
right_jump_frame.pack(side=tk.RIGHT, padx=5)
ttk.Label(right_jump_frame, text="Go to Line:").pack(side=tk.LEFT)
self.right_line_entry = ttk.Entry(right_jump_frame, width=6)
self.right_line_entry.pack(side=tk.LEFT, padx=2)
ttk.Button(right_jump_frame, text="GO", width=3, command=self.jump_to_right_line).pack(side=tk.LEFT)
# 创建右侧文本框架
right_text_frame = ttk.Frame(right_frame)
right_text_frame.grid(row=1, column=0, sticky="nsew", padx=0, pady=0)
right_text_frame.grid_rowconfigure(0, weight=1)
right_text_frame.grid_columnconfigure(0, weight=0)
right_text_frame.grid_columnconfigure(1, weight=1)
# 右侧行号 - 禁止滚动
self.right_line_numbers = tk.Text(
right_text_frame, width=6, height=35, wrap=tk.NONE, font=("Consolas", 10),
bg="#f0f0f0", fg="#666666", state=tk.DISABLED, padx=5, pady=5, takefocus=0
)
self.right_line_numbers.grid(row=0, column=0, sticky="ns")
# 禁止行号区域的滚动事件
self.right_line_numbers.bind("<MouseWheel>", lambda e: "break")
# 创建文本容器框架
text_container_right = ttk.Frame(right_text_frame)
text_container_right.grid(row=0, column=1, sticky="nsew")
text_container_right.grid_rowconfigure(0, weight=1)
text_container_right.grid_columnconfigure(0, weight=1)
text_container_right.grid_columnconfigure(1, weight=0)
# 右侧文本区域
self.right_text = tk.Text(
text_container_right, width=65, height=35, wrap=tk.NONE, font=("Consolas", 10),
padx=5, pady=5, undo=True
)
self.right_text.grid(row=0, column=0, sticky="nsew")
# 右侧垂直滚动条
right_v_scroll = ttk.Scrollbar(text_container_right, orient=tk.VERTICAL)
right_v_scroll.grid(row=0, column=1, sticky="ns")
# 设置滚动回调 - 修复滚动同步问题
right_v_scroll.config(command=self.right_text.yview)
self.right_text.config(yscrollcommand=lambda *args: self.sync_scroll_right(*args))
# 右侧水平滚动条
right_h_scroll = ttk.Scrollbar(right_text_frame, orient=tk.HORIZONTAL, command=self.right_text.xview)
right_h_scroll.grid(row=1, column=0, columnspan=2, sticky="ew")
self.right_text.config(xscrollcommand=right_h_scroll.set)
# 绑定事件
self.right_text.bind("<Configure>", lambda e: self.update_line_numbers(self.right_text, self.right_line_numbers))
self.right_text.bind("<KeyRelease>", lambda e: self.update_line_numbers(self.right_text, self.right_line_numbers))
self.right_text.bind("<MouseWheel>", lambda e: self.update_line_numbers(self.right_text, self.right_line_numbers))
# 左侧滚动同步方法 - 修复滚动同步问题
def sync_scroll_left(self, *args):
"""同步左侧文本区域和行号区域的滚动位置"""
# 更新行号区域的滚动位置
self.left_line_numbers.yview_moveto(args[0])
# 更新垂直滚动条的位置
if len(args) > 1:
self.left_text.yview_moveto(args[0])
# 右侧滚动同步方法 - 修复滚动同步问题
def sync_scroll_right(self, *args):
"""同步右侧文本区域和行号区域的滚动位置"""
# 更新行号区域的滚动位置
self.right_line_numbers.yview_moveto(args[0])
# 更新垂直滚动条的位置
if len(args) > 1:
self.right_text.yview_moveto(args[0])
def escape_xml_text(self, text):
"""转义XML文本中的特殊字符"""
if not text:
return text
text = text.replace('&', '&')
text = text.replace('<', '<')
text = text.replace('>', '>')
text = text.replace('"', '"')
return text
def unescape_xml_text(self, text):
"""反转义XML文本中的特殊字符"""
if not text:
return text
text = text.replace('&', '&')
text = text.replace('<', '<')
text = text.replace('>', '>')
text = text.replace('"', '"')
return text
def jump_to_left_line(self):
"""跳转到左侧文本区域的指定行"""
self.jump_to_line(self.left_text, self.left_line_entry, self.left_line_numbers)
def jump_to_right_line(self):
"""跳转到右侧文本区域的指定行"""
self.jump_to_line(self.right_text, self.right_line_entry, self.right_line_numbers)
def jump_to_line(self, text_widget, entry_widget, line_number_widget):
"""跳转到文本区域的指定行"""
try:
line_num = entry_widget.get().strip()
if not line_num:
return
line_num = int(line_num)
if line_num < 1:
self.status_label.config(text="Line number must be at least 1")
return
lines = text_widget.get("1.0", "end-1c").split('\n')
total_lines = len(lines)
if line_num > total_lines:
self.status_label.config(text=f"Line number exceeds total lines ({total_lines})")
return
text_widget.see(f"{line_num}.0")
text_widget.mark_set(tk.INSERT, f"{line_num}.0")
text_widget.focus_set()
text_widget.tag_remove("highlight", "1.0", "end")
text_widget.tag_add("highlight", f"{line_num}.0", f"{line_num}.end")
text_widget.tag_configure("highlight", background="#39C5BB")
line_number_widget.see(f"{line_num}.0")
text_widget.after(3000, lambda: text_widget.tag_remove("highlight", "1.0", "end"))
self.status_label.config(text=f"Jumped to line {line_num}")
except ValueError:
self.status_label.config(text="Please enter a valid line number")
def update_line_numbers(self, text_widget, line_number_widget):
"""更新行号显示"""
lines = text_widget.get("1.0", "end-1c").split('\n')
line_count = len(lines)
line_number_widget.config(state=tk.NORMAL)
line_number_widget.delete(1.0, tk.END)
for i in range(1, line_count + 1):
line_number_widget.insert(tk.END, f"{i}\n")
line_number_widget.config(state=tk.DISABLED)
# 获取当前滚动位置并同步
first, last = text_widget.yview()
line_number_widget.yview_moveto(first)
def create_controls(self):
# 控制按钮框架
control_frame = ttk.Frame(self.root, padding=10)
control_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
# 状态标签
self.status_label = ttk.Label(control_frame, text="Ready")
self.status_label.pack(side=tk.RIGHT, padx=10)
# 样式配置
style = ttk.Style()
style.configure("Accent.TButton", foreground="#000", background="#4CAF50", font=("Arial", 10, "bold"))
def import_xml_left(self):
"""导入XML到左侧面板"""
file_types = [("XML", "*.xml"), ("TXT", "*.txt"), ("Python", "*.py"), ("ALL", "*.*")]
file_path = filedialog.askopenfilename(filetypes=file_types)
if file_path:
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
self.left_text.delete(1.0, tk.END)
self.left_text.insert(tk.END, content)
self.current_left_file = file_path
self.update_line_numbers(self.left_text, self.left_line_numbers)
filename = os.path.basename(file_path)
self.status_label.config(text=f"Imported: {filename}")
self.cache_original_content(content)
except Exception as e:
messagebox.showerror("Import ERROR", f"Cannot import the file: {str(e)}")
def import_key_value(self):
"""导入SimpleFormat文件到右侧面板"""
file_types = [("TXT", "*.txt"), ("Python", "*.py"), ("ALL", "*.*")]
file_path = filedialog.askopenfilename(filetypes=file_types)
if file_path:
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
self.right_text.delete(1.0, tk.END)
self.right_text.insert(tk.END, content)
self.current_right_file = file_path
self.update_line_numbers(self.right_text, self.right_line_numbers)
filename = os.path.basename(file_path)
self.status_label.config(text=f"Imported: {filename}")
except Exception as e:
messagebox.showerror("Import ERROR", f"Cannot import the file: {str(e)}")
def cache_original_content(self, xml_content):
"""完整缓存原始XML的所有内容"""
self.original_cache = {}
self.xml_declaration = ""
self.non_string_content = ""
self.original_full_content = xml_content
self.tables = [] # 存储所有Table元素信息
try:
if xml_content.strip().startswith("<?xml"):
end_decl = xml_content.find("?>") + 2
self.xml_declaration = xml_content[:end_decl] + "\n"
xml_content = xml_content[end_decl:].strip()
try:
root = ET.fromstring(self.original_full_content)
use_dom = False
except Exception:
try:
dom = minidom.parseString(self.original_full_content)
root = dom.documentElement
use_dom = True
except Exception as e:
print(f"XML parsing error: {str(e)}")
return
if not use_dom:
# 先处理Table元素
for table_elem in root.findall('.//Table'):
instance = table_elem.get('instance', '')
group = table_elem.get('group', '')
self.tables.append({
'instance': instance,
'group': group,
'element': table_elem
})
for string_elem in root.findall('.//String'):
string_id = string_elem.get('id')
if string_id:
source = string_elem.find('Source')
dest = string_elem.find('Dest')
source_text = self.unescape_xml_text(source.text) if source is not None and source.text else ""
dest_text = self.unescape_xml_text(dest.text) if dest is not None and dest.text else ""
self.original_cache[string_id] = {
'source': source_text,
'dest': dest_text,
'element': string_elem
}
else:
# 先处理Table元素
for table_elem in root.getElementsByTagName('Table'):
instance = table_elem.getAttribute('instance')
group = table_elem.getAttribute('group')
self.tables.append({
'instance': instance,
'group': group,
'element': table_elem
})
for string_elem in root.getElementsByTagName('String'):
string_id = string_elem.getAttribute('id')
if string_id:
source = string_elem.getElementsByTagName('Source')
dest = string_elem.getElementsByTagName('Dest')
source_text = source[0].firstChild.data if source and source[0].firstChild else ""
dest_text = dest[0].firstChild.data if dest and dest[0].firstChild else ""
source_text = self.unescape_xml_text(source_text)
dest_text = self.unescape_xml_text(dest_text)
self.original_cache[string_id] = {
'source': source_text,
'dest': dest_text,
'element': string_elem
}
except Exception as e:
print(f"XML caching error: {str(e)}")
def fix_simple_format(self):
"""修复SimpleFormat格式中的引号问题"""
content = self.right_text.get(1.0, tk.END).strip()
if not content:
messagebox.showwarning("FIX Error", "Right side requires content")
return
try:
fixed_lines = []
lines = content.split('\n')
fixed_count = 0
for line in lines:
stripped_line = line.strip()
# 跳过空行和Table行
if not stripped_line or stripped_line.startswith("Table"):
fixed_lines.append(line)
continue
# 检查是否是有效的键值对
if ':' not in stripped_line:
fixed_lines.append(line)
continue
# 分割键和值
parts = stripped_line.split(':', 1)
key_part = parts[0].strip()
value_part = parts[1].strip()
# 修复键部分
if not (key_part.startswith("'") and key_part.endswith("'")):
if key_part.startswith("'") and not key_part.endswith("'"):
key_part = key_part + "'"
elif not key_part.startswith("'") and key_part.endswith("'"):
key_part = "'" + key_part
elif not key_part.startswith("'") and not key_part.endswith("'"):
key_part = "'" + key_part + "'"
fixed_count += 1
# 修复值部分
if not (value_part.startswith("'") and value_part.endswith("'")):
if value_part.startswith("'") and not value_part.endswith("'"):
value_part = value_part + "'"
elif not value_part.startswith("'") and value_part.endswith("'"):
value_part = "'" + value_part
elif not value_part.startswith("'") and not value_part.endswith("'"):
value_part = "'" + value_part + "'"
fixed_count += 1
# 重新组合行
fixed_line = key_part + ':' + value_part
fixed_lines.append(fixed_line)
# 更新文本区域
fixed_content = '\n'.join(fixed_lines)
self.right_text.delete(1.0, tk.END)
self.right_text.insert(tk.END, fixed_content)
self.update_line_numbers(self.right_text, self.right_line_numbers)
if fixed_count > 0:
self.status_label.config(text=f"Fixed {fixed_count} quote issues in SimpleFormat")
else:
self.status_label.config(text="No quote issues found in SimpleFormat")
except Exception as e:
messagebox.showerror("FIX Error", f"Error fixing SimpleFormat: {str(e)}")
def convert_to_key_value(self):
"""将左侧XML格式转换为SimpleFormat格式"""
xml_content = self.left_text.get(1.0, tk.END).strip()
if not xml_content:
messagebox.showwarning("Convert Error", "Left side requires content")
return
try:
try:
root = ET.fromstring(xml_content)
use_dom = False
except Exception:
try:
dom = minidom.parseString(xml_content)
root = dom.documentElement
use_dom = True
except Exception as e:
messagebox.showerror("Parse Error", f"Invalid XML format: {str(e)}")
return
key_value_content = ""
unprocessed_elements = []
# 先处理Table元素
if not use_dom:
for table_elem in root.findall('.//Table'):
instance = table_elem.get('instance', '')
group = table_elem.get('group', '')
key_value_content += f"Table instance='{instance}' group='{group}'\n"
else:
for table_elem in root.getElementsByTagName('Table'):
instance = table_elem.getAttribute('instance')
group = table_elem.getAttribute('group')
key_value_content += f"Table instance='{instance}' group='{group}'\n"
# 处理String元素
if not use_dom:
for string_elem in root.findall('.//String'):
string_id = string_elem.get('id')
dest = string_elem.find('Dest')
if string_id and dest is not None:
dest_text = self.unescape_xml_text(dest.text) if dest.text else ""
# 将换行符替换为XML转义字符
dest_text = dest_text.replace('\n', '
')
key_value_content += f"'{string_id}':'{dest_text}'\n"
else:
unprocessed_elements.append({
'tag': "String",
'text': ET.tostring(string_elem, encoding='unicode').strip(),
'reason': "Missing id or Dest element"
})
else:
for string_elem in root.getElementsByTagName('String'):
string_id = string_elem.getAttribute('id')
dest_nodes = string_elem.getElementsByTagName('Dest')
if string_id and dest_nodes:
dest_text = dest_nodes[0].firstChild.data if dest_nodes[0].firstChild else ""
dest_text = self.unescape_xml_text(dest_text)
# 将换行符替换为XML转义字符
dest_text = dest_text.replace('\n', '
')
key_value_content += f"'{string_id}':'{dest_text}'\n"
else:
unprocessed_elements.append({
'tag': "String",
'text': string_elem.toxml(),
'reason': "Missing id or Dest element"
})
self.right_text.delete(1.0, tk.END)
self.right_text.insert(tk.END, key_value_content)
self.update_line_numbers(self.right_text, self.right_line_numbers)
self.status_label.config(text="Converted to SimpleFormat successfully")
if unprocessed_elements:
self.show_unprocessed_elements(unprocessed_elements, "XML to SimpleFormat")
except Exception as e:
messagebox.showerror("Convert Error", f"Error converting to XML: {str(e)}")
def show_unprocessed_elements(self, elements, conversion_type):
"""显示未处理内容的提示窗口"""
unprocessed_window = tk.Toplevel(self.root)
unprocessed_window.title(f"Unprocessed Elements - {conversion_type}")
unprocessed_window.geometry("800x500")
frame = ttk.Frame(unprocessed_window, padding=10)
frame.pack(fill=tk.BOTH, expand=True)
label = ttk.Label(frame, text=f"Found {len(elements)} elements that were not processed:")
label.pack(pady=(0, 10))
tree_container = ttk.Frame(frame)
tree_container.pack(fill=tk.BOTH, expand=True)
columns = ("#", "Element", "Reason")
tree = ttk.Treeview(tree_container, columns=columns, show="headings")
tree.heading("#", text="#")
tree.heading("Element", text="Element")
tree.heading("Reason", text="Reason")
tree.column("#", width=50, anchor=tk.CENTER)
tree.column("Element", width=500)
tree.column("Reason", width=200)
scrollbar = ttk.Scrollbar(tree_container, orient="vertical", command=tree.yview)
tree.configure(yscrollcommand=scrollbar.set)
tree.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")
tree_container.grid_rowconfigure(0, weight=1)
tree_container.grid_columnconfigure(0, weight=1)
for i, element in enumerate(elements, 1):
element_text = element['text']
if len(element_text) > 100:
element_text = element_text[:97] + "..."
tree.insert("", "end", values=(i, element_text, element['reason']))
close_btn = ttk.Button(frame, text="Close", command=unprocessed_window.destroy)
close_btn.pack(pady=10)
def convert_to_xml(self):
"""将右侧SimpleFormat格式转换为XML格式,保留原始结构"""
key_value_content = self.right_text.get(1.0, tk.END).strip()
if not key_value_content:
messagebox.showwarning("Convert Error", "Right side requires content")
return
try:
xml_content = self.original_full_content
key_value_dict = {}
table_lines = []
# 先解析所有行
lines = key_value_content.split('\n')
for line in lines:
line = line.strip()
if not line:
continue
# 检查是否是Table行
if line.startswith("Table"):
match = re.match(r"Table instance='([^']*)' group='([^']*)'", line)
if match:
instance = match.group(1)
group = match.group(2)
table_lines.append({
'instance': instance,
'group': group
})
continue
# 解析键值对
match = re.match(r"['"]([a-f0-9-]+)['"]\s*:\s*(.*)", line)
if match:
string_id = match.group(1)
new_dest = match.group(2).strip()
# 如果值被引号包围,去掉引号
if new_dest.startswith("'") and new_dest.endswith("'"):
new_dest = new_dest[1:-1]
elif new_dest.startswith('"') and new_dest.endswith('"'):
new_dest = new_dest[1:-1]
# 将XML转义字符恢复为换行符
new_dest = new_dest.replace('
', '\n')
new_dest = self.escape_xml_text(new_dest)
key_value_dict[string_id] = new_dest
# 处理Table元素
for table_info in self.tables:
instance = table_info['instance']
group = table_info['group']
# 查找匹配的Table行
table_found = False
for table_line in table_lines:
if table_line['instance'] == instance and table_line['group'] == group:
table_found = True
break
# 如果没有找到匹配的Table行,保留原始Table
if not table_found:
continue
# 处理Table中的String元素
if isinstance(table_info['element'], ET.Element):
# 处理ET元素
for string_elem in table_info['element'].findall('String'):
string_id = string_elem.get('id')
if string_id in key_value_dict:
dest = string_elem.find('Dest')
if dest is not None:
dest.text = key_value_dict[string_id]
else:
# 处理minidom元素
for string_elem in table_info['element'].getElementsByTagName('String'):
string_id = string_elem.getAttribute('id')
if string_id in key_value_dict:
dest_nodes = string_elem.getElementsByTagName('Dest')
if dest_nodes and dest_nodes[0].firstChild:
dest_nodes[0].firstChild.data = key_value_dict[string_id]
# 处理非Table中的String元素
for string_id, new_dest in key_value_dict.items():
start_tag = f'<String id="{string_id}">'
start_idx = xml_content.find(start_tag)
if start_idx != -1:
end_tag = f'</String>'
end_idx = xml_content.find(end_tag, start_idx)
if end_idx != -1:
string_element = xml_content[start_idx:end_idx + len(end_tag)]
dest_start = string_element.find('<Dest>')
dest_end = string_element.find('</Dest>')
if dest_start != -1 and dest_end != -1:
dest_end += len('</Dest>')
new_string_element = (
string_element[:dest_start] +
f'<Dest>{new_dest}</Dest>' +
string_element[dest_end:]
)
xml_content = (
xml_content[:start_idx] +
new_string_element +
xml_content[end_idx + len(end_tag):]
)
self.left_text.delete(1.0, tk.END)
self.left_text.insert(tk.END, xml_content)
self.update_line_numbers(self.left_text, self.left_line_numbers)
self.status_label.config(text="Restored to XML with original structure")
except Exception as e:
messagebox.showerror("Convert Error", f"Error converting to XML: {str(e)}")
def save_xml(self):
"""保存左侧XML内容到文件"""
content = self.left_text.get(1.0, tk.END).strip()
if not content:
messagebox.showwarning("Save Error", "Left side requires content")
return
file_path = filedialog.asksaveasfilename(
defaultextension=".xml",
filetypes=[("XML", "*.xml"), ("All", "*.*")]
)
if file_path:
try:
with open(file_path, 'w', encoding='utf-8') as file:
file.write(content)
filename = os.path.basename(file_path)
self.status_label.config(text=f"XML file saved: {filename}")
except Exception as e:
messagebox.showerror("Save Error", f"Cannot save file: {str(e)}")
def save_key_value(self):
"""保存右侧SimpleFormat内容到文件"""
content = self.right_text.get(1.0, tk.END).strip()
if not content:
messagebox.showwarning("Save Error", "Right side requires content")
return
file_path = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("TXT", "*.txt"), ("All", "*.*")]
)
if file_path:
try:
with open(file_path, 'w', encoding='utf-8') as file:
file.write(content)
filename = os.path.basename(file_path)
self.status_label.config(text=f"SimpleFormat file saved: {filename}")
except Exception as e:
messagebox.showerror("Save Error", f"Cannot save file: {str(e)}")
if __name__ == "__main__":
root = tk.Tk()
app = GameTranslationAssistant(root)
root.mainloop() 复制代码
说明书(EXE版本里面也有一样的一份)
### 【宇宙免责声明】
本软件使用DeepSeek全程制作,自己几乎一笔没改,所以屎山代码是必然的,如果你需要修改整个文件的代码,那么DeepSeek应该是看得懂的,可以自己用TA修改BUG。
### 【Cosmic Disclaimer】
This software was entirely produced using DeepSeek. I hardly made any changes myself, so it's inevitable that the code is a mess.
If you need to modify the code of the entire file, DeepSeek may be understandable. You can use it for your own modifications BUG.
================================前置软件================================
===============================Prerequisite===============================
你需要下载:【必下】MTS_voky_translator(用于导出模组的XML)
【必下】Python3.6+(用于运行软件)
【选下】Visual Studio Code或者其他文件查看软件(用于快速找出错误的字符)
==============================English=================================
Need USE:【Must】MTS_voky_translator (for exporting the mod's XML)
【Must】Python3.6+ (for running the software)
【Other】Visual Studio Code or other file viewing software (Used to quickly find out the wrong characters)
===============================如何开启=================================
=============================How to Open================================
①
默认显示应该为:
C:\Users\(用户名)>
打开系统自带的命令提示符(CMD)输入cd text_(GameTranslationAssistant所在路径):
C:\Users\(用户名)>cd (文件所在盘):\(文件所在路径)\GameTranslationAssistant
移动到程序文件夹
②
之后输入:
(文件所在盘):\(文件所在路径)\GameTranslationAssistant>python GameTranslationAssistant.py
运行程序。
==============================English=================================
①
The default display should be:
C:\Users\(username)>
Open the Command Prompt (CMD) built-in system and enter the path where GameTranslationAssistant is located:
| C:\Users\(username)>cd (drive where the file is located):\(path where the file is located)\GameTranslationAssistant
to move to the program folder.
②
Then enter:
(drive where the file is located):\(path where the file is located)\GameTranslationAssistant>GameTranslationAssistant.py
to run the program.
===============================使用方式=================================
==============================Use-Pattern================================
本软件分为左右两侧工作区,左侧为MTS_voky_translator支持的XML格式,右边为自动简化而成的格式。
①先在左侧【Import】导入,支持.xml,.txt,.py
②后选择【To SimpleFormat】,转换为方便使用机械翻译的简易格式。
③简易格式支持.txt和.py的导入,可以导出为.txt
④简易格式使用导入更改或者直接手动修改后,选择【Back to XML】成功后左侧<Dest></Dest>之间内容应该显示为右侧修改后的内内容。
⑤确认无误后点击【Export】输出译文即可,若无法显示可能是有特殊字符错误,直接使用浏览器打开.xml会显示错误字符的位置。
==============================English=================================
1. First, import on the left side via 【Import】. It supports file formats of.xml,.txt, and.py.
2. Then select 【To SimpleFormat】 to convert it into a simple format that is convenient for using machine translation.
3. The simple format supports the import of.txt and.py files and can be exported as.txt.
4. After making changes through import or directly manual modification in the simple format, select 【Back to XML】.
After success, the content between <Dest></Dest> on the left side should display the modified content on the right side.
5. After confirmation, click 【Export】 to output the translated text.
If it cannot be displayed, there may be special character errors.
Directly opening the.xml file in a browser will show the location of the error characters.
===============================注意事项=================================
==============================Use-Pattern================================
.xml/.py/.txt三种格式都能使用修改后缀的方式互相转换。
你需要拥有以下格式才能进行转换,大致结构如下,使用MTS_voky_translator导出的译文会自带这个结构,找到并复制即可:
<?xml version="1.0" encoding="utf-8"?>
<STBLXMLResources>
<Content>
<Table instance="模组的16位哈希值," group="00000000">
<String id="句子哈希值">
<Source>Hellow,world.</Source>
<Dest>你好世界。</Dest>
</String>
</Table>
</Content>
</STBLXMLResources>
使用这个结构转换后,右侧应该显示为:
'句子的哈希值':你好世界。
以下这段内容以及所有句子的哈希值(String id="句子的哈希值")会自动存储在左侧软件的缓存中:
<?xml version="1.0" encoding="utf-8"?>
<STBLXMLResources>
<Content>
<Table instance="模组的16位哈希值" group="00000000">
</Table>
</Content>
</STBLXMLResources>
其中
<Table instance="模组的16位哈希值" group="00000000">
会被转化为
Table instance='01175337558a31ae' group='00000000'
在右侧的上端。
请不要修改他们。
==============================English=================================
The three formats of .xml/.py/.txt can all be converted to each other by modifying the suffix. You need to have the following format to perform the conversion.
The general structure is as follows. The translated text exported by MTS_voky_translator will come with this structure. Just find and copy it:
<?xml version="1.0" encoding="utf-8"?>
<STBLXMLResources>
<Content>
<Table instance="The 16-bit hash value of the module" group="00000000">
<String id="Sentence hash value">
<Source>你好世界。</Source>
<Dest>Hellow,world.</Dest>
</String>
</Table>
</Content>
</STBLXMLResources>
After using this structure conversion, the right side should display as:
|'Hash value of the sentence': Hello World
The following content and the hash values of all sentences (String id="Hash value of the sentence") will be automatically stored in the cache of the software on the left.
<?xml version="1.0" encoding="utf-8"?>
<STBLXMLResources>
<Content>
<Table instance="Sentence hash value" group="00000000">
</Table>
</Content>
</STBLXMLResources>
Among them,
|<Table instance="16-bit hash value of the module" group="00000000">
will be transformed into
|Table instance='01175337558a31ae' group='00000000'
at the upper end on the right side.
At the upper right end, please do not modify them.
================================导入失败================================
==============================Fail To Display==============================
当你导入失败时显示:
If when you Import fail to display:
Convert Error to XML: not well-formed (invalid token): line ???, column???
你需要下载一个软件去寻找到XML的那一行文字出错的地方,之后修改它(我用Visual Studio Code)。
You need download a software(I use Visual Studio Code)to find the line of the error line.
本软件自带寻找行的功能,可以酌情使用。
This software comes with a built-in function for finding rows, which can be used as appropriate.
'FIX'功能:你可以使用【'FIX'】按钮自动为SimpleFormat检测并补充句子两侧的''。
'FIX' function: You can use the 【'FIX'】 button to automatically detect and add single quotes on both sides of the sentence for SimpleFormat.
如果右边翻译内容已经有了格式也没有问题但是左边没有导入,可以考虑将改完的文件【Import】导出为.txt之后重新导入一下再转换。
If the translation on the right is already formatted correctly and there are no issues, but the one on the left has not been 【imported】, you may consider exporting the modified file as a.txt, then re-import it and convert it again.
=====================================================================
如果你有特殊字符需要转换,可以按照格式增加.py下面这一部分的内容(上下两个def都需要)
If you have special characters that need to be converted, you can add the following content under the .py part according to the format.(Both of the two "def" statements are required)
def escape_xml_text(self, text):
"""转义XML文本中的特殊字符"""
if not text:
return text
text = text.replace('&', '&')
text = text.replace('<', '<')
text = text.replace('>', '>')
text = text.replace('"', '"')
return text
def unescape_xml_text(self, text):
"""反转义XML文本中的特殊字符"""
if not text:
return text
text = text.replace('&', '&')
text = text.replace('<', '<')
text = text.replace('>', '>')
text = text.replace('"', '"')
return text
===================================================================== 复制代码