养成游戏汉化(游戏汉化日记 一)
下面是好好范文网小编收集整理的养成游戏汉化(游戏汉化日记 一),仅供参考,欢迎大家阅读!
导言
很久没写技术文章了,一来感觉自己这技术没什么值得分享的,二来最近确实有点忙得焦头烂额。
这不刚好兴致来了,记录和整理一下最近汉化遇到的技术难题,梳理一下思路,分享一下经验。
最近汉化的几个游戏呢,都是开源的文字游戏。
作者都是野路子出身,没有技术背景,所以代码习惯很差。
逻辑和数据耦合是常态,有时甚至拿变量名或文件名来输入输出和判断,没有设计本地化配置文件,有版本更新需求,这是大背景。
既然是开源,而且没有本地化配置文件,那理所当然的,汉化过程就是暴力替换源代码,然后重新编译。
当然其实也没那么暴力,大致来说分为五个主要阶段:
提取源代码中需要翻译的文本;
将文本分类存储,并发送到在线协作平台;
汉化组在平台上做翻译;
定期下载翻译文本,然后替换源代码,最后打包;
测试游戏Bug。
其中除了3以外,都多少涉及了一些技术问题,我一条条展开讲讲。
提取文本
最麻烦的第一步,首先得熟悉项目使用的语言,然后还要熟悉这个项目的结构,不然没法解析。
就拿我最近在汉化的《Era***》来说,这游戏使用了一种叫做era basic的古老小众的脚本语言,我谷歌百度找遍全网都没有一篇系统全面的文档,只有一些残卷散落在互联网的各个角落。
并且,这游戏使用的era basic居然还包含了新特性EM+EE。大概就是我连C#4.0的文档都没找到,人已经用上C#10.0了这样,真的很无助。
什么,你说era basic有一个开源解释器。但那解释器也不支持EM+EE,注释比鱼香肉丝的鱼还少而且全是日文,啃这玩意儿不如我从零造轮子。
离题了,总之当熟悉了语言和项目结构后,就可以试着提取一下文本了。
根据语言和项目熟悉程度以及作者的代码习惯,多多少少会有漏提取的内容,这就需要之后不断测试、不断改进提取工具了。也是一场麻烦的持久战。
词典分类
分类问题是一个设计问题。
可能有人要问,文本提取出来不用管那么多,直接就存成单独一个超大的词典,不就能猛猛上工了吗?NO!
下面我介绍两种分类方式,并说明为什么这样分类。
本人脑子比较拙,暂时也只想到这两种分类方式,有新想法欢迎在评论区与我交流,拜托了!
拆分文本与变量名
背景
众所周知,游戏是状态很多的一类程序,经常有字符串内嵌变量拼接的需求,这是其一。
汉化组的翻译大多不懂代码,分不清文本内嵌的各种代码符号,这是其二。
变量名会在多个地方出现,一旦翻译不同,就会发生惨烈的Bug,哪怕在平台上标记术语也不能避免,这是其三。
方案A
基于这三个痛点,文本与变量名分离的方式应运而生。
在提取文本阶段,就将识别到的变量名扔到单独的变量名词典。
而内插变量名的文本将被截断成多个短文本,避免变量名呈现到译者眼前。
那么,代价是什么呢?
代价就是译者拿到的文本都切得碎碎的,经常搞不清语境,容易闹乌龙;
并且还无法自由调整文本语序,让译文看起来十分机翻。
这可不行。
方案B
方案B在方案A的基础上做了一点优化。
现在长文本不再被切碎,而是包括内插变量名,这样译者就能搞明白上下文,也能调整语序了。
但是译者乱改变量名怎么办呢,这个倒不用操心。
在替换阶段,先使用变量名词典替换掉长文本词典内插的变量名,得到二次修改的长文本词典;
再使用二次修改的长文本词典去替换源代码,如此便能保证变量名统一。
有没有缺点呢,其实还是有的。
多了一个变量名提取-翻译-替换的工作流,每一步都增加了出错的风险。提取和替换一定会有漏的。
变量名一定要翻译吗?大多数情况下其实不翻也可以,但有的游戏变量名会直接拿来输出,那能怎么办嘛!
还有一个小众的需求,玩家可能希望他在原版的存档也能被汉化版继承,或者反过来。
前面也说了,作者都是野路子,什么东西都往存档里塞。
像什么“你今年24岁,是学生,身穿XX衣服”这种动态改变的人物描述(有时间流逝机制)都能以字符串形式存到存档里,导致检查汉化的时候莫名发现人物描述居然没变化,仔细看存档才发觉问题的严重性。
所有玩家见过的NPC的描述都以字符串形式存档,存档真的,我哭死。
为了存档兼容,汉化版只能搞一个新的映射或者扩展容器。比如原先的Cloth类只有Name成员,汉化组可以加一个NameCN成员,然后把原文的{cloth.Name}通通翻译成{cloth.NameCN}。
按文件拆分
众所周知,代码习惯再烂的游戏程序员也不会一个main函数写到尾。
按文件拆分有什么好处呢?
一是上下文语境更明确,译者能完整地了解一段文本从什么地方开始什么地方结束,到底是用在什么地方。
这方面比上一种方法更理想;
二是可以更精准的替换,只要在提取阶段标记代码行数,就极少会出现错误替换漏替换的情况。
即使出现,也很容易定位到问题并修正。
乍一看是一套完美的解决方案啊!然而和上一种方式对比,还是能找到两个缺点。
一是无法保证变量名统一,这一点前面提过。这很容易导致游戏出现Bug,而且也不是很好排查;
二是需要翻译的文本膨胀了。可能有点抽象,但对于野路子开发者来说刚刚好。
比如我前段时间在翻译的《莉莉丝的**》里,经常就出现一段文本到处复制粘贴,有的文本甚至被粘贴了二十几遍,于是译者就要在不同的文件里翻译二十几遍。
如果采用上一种方法,只分离长文本和变量名,那么重复的长文本会被合并,译者只需要翻译一次。
不妨说这其实也是为了更好的上下文语境做出的牺牲,一想到作者维护的时候一定也在痛苦扭曲抓狂尖叫,多少有点心理平衡呢。
替换代码
词典分类的方法其实直接影响了替换的方法。
如果只拆分文本和变量,就需要对目录下所有满足条件的文本做读取、修改然后保存。
性能上会有压力吗?其实没什么压力,要有性能压力也是在原作上。
根据原作的容量,替换通常在1~15秒之间就会结束。
如果按文件拆分,效率当然要高得多。但这个流程其实并不常跑,也没必要在电脑上抠这几秒的。
按文件拆分还有个优势,前面也提到过,由于替换的范围缩小了,就很难出现错漏替换。
按文件拆分还可以再给每个条目添加一个行范围标记,把范围缩得更小,虽然会拖慢执行速度但显然利远大于弊。
不过就算范围缩小到行,还是会面临一个很极端的情况。
比方说我有"Chin"->"下巴";"ina"->"伊娜";"Ch"->"策划"。
而我的原文是"China",正确翻译是"策划伊娜"。
如果按一般流程处理,把字典按原文长度排序,下巴>伊娜>策划,然后再替换,就会得到"下巴a"。
当然,如果范围缩小到行,错误替换概率基本可以忽略不计。
而如果是只拆分文本和变量,然后全局替换,就相当容易出现这样的状况。
行标记的问题主要是代码写起来稍微复杂一些,因为一个翻译条目很可能是跨行的。
但能直接String.replace为什么还要逐行改,这一切值得吗?
更别说原作更新后,行数也会跟着变化呢。
测试游戏
最后还要跑游戏,排查错漏提取、错漏翻译、错漏替换。
汉化真是比娘化麻烦太多了。
技术出身的同志一般习惯让反馈者上Github提issue,但反馈者绝大多数没有技术背景,也没有那个精力。
你汉化者是用爱发电,反馈者难道就不是吗?
所以最后还是会开DC频道或者QQ群啦……
然后还有一点不起眼但影响很大的,就是有没有高频率更新汉化。
如果有每日或每周打包新版本的话,反馈和修正会更加准确;
译者因为有了所见即所得的工作成果,工作也会更加积极。
为了达到这个目的,可能需要构建自动打包工作流,可以参考Github提供的解决方案:
了解 GitHub Actions - GitHub Enterprise Cloud Docs
版本更新
虽然没遇到什么难点,但还是踩了一个小坑,不太具备参考价值,姑且简单讲讲。
因为翻译是在平台上进行的,为了保留平台上的操作记录和批注,必须要维持旧条目的键值一致。
版本更新不仅会新增一些条目,也会删去一些条目。
为了保留上下文语境,新增加的条目很可能插在旧条目之间,而不是在末尾追加。
然后一开始我脑子没拎清,版本更新的时候直接取了条目总数作为追加条目的初始键值,忽略了删除条目导致条目数小于上版本最终键值的情况。
所以正确做法应该是将键值按数字大小排序,取到最大的那个再+1。
行数变动导致行标记变化也是一件麻烦事,这里就略过不表了。
谢幕
#オリジナル うたた寝 - みこフライ的插画 - pixiv