renpy和RPG的翻译环境太差了,教程和工具都相当落后,除了比较出名的T++(内嵌补丁制作,但什么字符串都提取,得删改好久)和MTool(外挂补丁,就是得挂着个翻译器才能运行补丁,但至少不会太多bug)外,其他项目在Github(开源社区)里没什么浏览,工具也落后。
Renpy的翻译可以说很简单,如果使用T++,只需要将游戏导入,翻译,导出到游戏文件夹即可,不过得排查bug,相当折磨。
还有一种更优的方法,那就是使用renpy的SDK进行翻译。但在这之前,你得把游戏拆包,至于是否只拆文本文件(如Scripts.rpa),取决于你是想要制作PC的翻译补丁,还是想进一步把这游戏的安卓安装包也制作出来。
通用翻译步骤
注意: 确保SDK和游戏都在英文路径下
0. 解压rpa文件
使用unrpa。
- 安装:
pip install unrpa
- 解包:
unrpa script.rpy
- 解压rpa文件到游戏根目录。
1. 提取rpy翻译文件
把整个游戏(包括目录)丢SDK目录(保证都是英文路径),打开renpy.exe,在项目里找到对应游戏,点击生成翻译,随便写个语言(我写的是CN),然后点击生成翻译(如果报错就把报错的文件删了),就获得了可用来翻译的文件,让我们点开看看。
以上是几种文本情况,由此得出他们的文件形式
太复杂了?简单来说,就是以下这两种基础结构不断重复而已。
结构一
# 文件路径.rpy:行数位置
translate lauguage 文件_id位置
#无人名或人名简写或"人名" 原始字符串
无人名或人名简写或"人名" 原始字符串
结构二
translate CN strings(还会提取字符串):
# 文件路径.rpy:行数位置
old "旧字符串"
new "新字符串"
2. 提取译文+翻译+注入译文
根据上述结构,写正则表达式或python代码,来提取原文,同时还得写注回代码(就是把翻译后的文本,再写回对应位置),确保注入注回后,原rpy文件和被注回的rpy文件大致一致,之后你就可以开始AI翻译了(由于代码要调试,所以提取、翻译、注入放同一节)。
我采用的方式是提取json,参考代码放评论区,使用的是GalTransl翻译器进行翻译。
3. 替换语言
你将sdk提取的翻译文件翻译完,还不够,你得让Renpy知道,这游戏要使用你的语言,不然就默认为英文。随便找个rpy文件,把define config.default_language = "CN"
(你自己定义的语言名)写入,就是中文了。
4. 替换字体
游戏使用中文了,但打开游戏,文字变成了方块。怎么回事?是因为这些Renpy游戏没有用中文字体,显示不了中文。好解决,先把自己准备的中文字体先丢game目录下,打开这个目录下的GUI.rpy文件,搜索ttf,把里面的字体修改为你的字体,OK,完美……了吗。
5. 错误修复
翻译完后,游戏并非没了bug,但如果游戏能正常启动,就说明问题不大。问题通常是”rpy翻译文本中出现了出现了语法错误”,应该没什么大问题。在游戏因此报错时,玩家只需要选择跳过就行。当然,如果想错误修复,那就用SDK的”使用lint检查脚本”调出错误信息,对其进行修复。
常见的错误:[name]
翻译成[名字]
,{i}你好{/i}
翻译成{你好}
,\"
翻译成"
等导致错误。
6. 打包
如果要让别人玩到,直接压缩整个游戏文件夹就行。如果只是想发布补丁,就只把rpy相关的文件和字体按目录结构放一起压缩。如果你想要打包到安卓玩,那就把rpa文件全部解压到game目录下,使用SDK打包。
可能的问题
1. 上述完成后,启动游戏,结果还是英文,怎么回事?
答:搜索rpyc文件并删除,重新编译——用SDK和游戏自带的exe分别编译rpy文件(二者在启动时都会编译),反正一定是编译问题……非常离谱,反复试吧。
2. 如果打包到安卓安装包出错了怎么办?
答:就直接找这个游戏的安卓版,apk也是一种压缩包,解压,打开,点击里面的assets目录,里面有个x-game目录,去除里面文件的”x-”前缀,就相当于game目录了。这时游戏就能在SDK里运行了。把你PC版游戏翻译好的的tl翻译文件,丢到这里的tl文件夹中,用SDK进行安卓打包,OVER!
3. 如何更新补丁?
答:打开新版本游戏的game文件夹,把tl文件夹丢里面,然后进行第0步和第1步操作,之后你就会发现,tl补丁文件内容下面出现了新增的未翻译英文,对它们进行翻译即可,建议使用github项目里的Renpy-Translator进行补翻……Renpy-Translator主要是性能很差,不可视,不可操控,api差的时候更是一点都翻译不动,所以只建议在补丁更新的时候使用。
示例代码
# parsejson.py
import json
import sys
if len(sys.argv) != 3:
print("Usage: python parsejson.py <input_file> <output_file>")
sys.exit(1)
input_file = sys.argv[1] # 输入文件路径
output_file = sys.argv[2] # 输出JSON文件路径
parsed_entries = [] # 存储解析后的条目
with open(input_file, "r", encoding="utf-8") as lines:
for line_index, line in enumerate(lines):
# 跳过特殊行
if line.startswith("#") or line.startswith("translate") or line.startswith("game"):
continue
line = line.strip()
if not line.startswith("#") and not line.startswith("old"):
continue
line = line.strip("# ")
if line.startswith("game"):
continue
# 解析文本行
if not line.startswith('"'):
# 处理 name "text" 格式
space_pos = line.index(" ")
speaker_name = line[:space_pos]
message_text = line[space_pos + 1:].strip('"')
else:
# 处理 "name" "text" 格式
parts = line.split('"')
if len(parts) == 3:
message_text = parts[1]
speaker_name = None
elif len(parts) == 5:
speaker_name = '"' + parts[1] + '"'
message_text = parts[3]
entry = {
"name": speaker_name,
"message": message_text,
"originMessage": message_text,
"line": line_index + 1
}
parsed_entries.append(entry)
speaker_name = None
# 写入JSON文件
with open(output_file, "w", encoding="utf-8") as f:
json.dump(parsed_entries, f, indent=4, ensure_ascii=False)
# toScript.py
import json
import sys
if len(sys.argv) != 4:
print("Usage: python toScript.py <input_file> <injson_file> <output_file>")
sys.exit(1)
input_file = sys.argv[1] # 原始文本文件
json_file = sys.argv[2] # JSON翻译文件
output_file = sys.argv[3] # 输出文件
def count_leading_spaces(text_line):
"""计算行首空格数"""
space_count = 0
for char in text_line:
if char == " ":
space_count += 1
else:
break
return space_count
# 读取JSON数据
with open(json_file, "r", encoding="utf-8") as f:
translations = json.load(f)
line_mapping = {} # 行号到翻译的映射
for translation in translations:
line_mapping[translation["line"]] = translation
# 处理文本文件
with open(input_file, "r", encoding="utf-8") as input_f:
with open(output_file, "w", encoding="utf-8") as output_f:
for line_num, line in enumerate(input_f):
speaker_name = None
if line_num in line_mapping:
# 获取该行的翻译
current_translation = line_mapping[line_num]
output_text = current_translation["message"]
speaker_name = current_translation["name"]
if speaker_name == "old":
speaker_name = "new"
if speaker_name is not None:
output_text = speaker_name + ' "' + output_text + '"'
else:
output_text = '"' + output_text + '"'
# 保持原有缩进
space_count = count_leading_spaces(line)
spaces = ' ' * space_count
output_f.write(spaces + output_text + "\n")
else:
output_f.write(line)