前略
软件要分发给使用不同语言和处于不同地区的人员,需要从多个方面处理从而使这些人能以自己文化背景下熟知的方式来使用。这包括了最基本的文本的翻译,也有一些通用习俗和约定的问题(如不同国家和地区的日期时间的表示等)。而本地化(L10N)和国际化(I18N)就是专门来解决这些问题的。
要很好的解决这一问题,往往需要多方面的人员参与,既有软件设计者,有程序开发人员,也需要翻译人员甚至翻译测试人员,而要把这些人组织起来完成工作,需要的不是只面向一两方的API和代码,良好的本地化团队往往需要面面俱到。
传统的解决方案
最知名的解决这些问题的一套系统,是GNU Gettext。这是一套上世纪九十年提出的用以解决*软件的国际化和本地化问题的工具套件。在gettext里,制定了翻译文本的组织方式(po/pot),提供了从程序源码提取字符串并生成翻译模板的工具(xgettext)。在这些之外,gettext和其社区也发展出了面向翻译人员的翻译内容管理系统(Weblate),以及有用于完成翻译工作的专门软件(Poedit)。Gettext也提供DIff/Merge工具来解决翻译内容的渐进式更新的需求。
游戏具有其特殊性
游戏做为最特殊的软件之一,对于本地化和国际化也有着属于它的苛刻要求:
•游戏往往会引入其他类型的需要多语言处理的资源而非只是文本,比如图片,语音,乃至口型文件。
•因为上一条的缘故,导致游戏本地化团队往往会引入更多不同工种的人员,从而增加了人员管理和协调的负担。
传统方案在游戏开发上的搁浅
GNU Gettext天生不适合游戏开发中的多语言问题,它只处理文本资源,原版的gettext甚至只能从源码中收集多语言数据,这显然只适合普通软件的需求,却很难满足游戏开发的需要。
对于游戏而言,只有处于UI界面里的数据,会比较切近Gettext的需求场景,然而在很多成熟引擎下,这些数据也往往位于特定的UI声明文件内(在Unity里这些数据处于scene/prefab文件中)而不是源代码中。至于占游戏内比重更大的其他需要多语言的数据,更是会分散在各种特定格式的数据之中。在Unity下,关卡数据可能存放在prefab中,角色/物品这类的设定性的文档数据可能处于ScriptableObject,甚至这些数据都保存在别的序列化文件内(诸如最常见的csv/xls或者xml/json,再或者自定义的二进制数据文件)。
一点牢骚
诚然,游戏的多语言问题在如今看起来似乎并不算多么麻烦的事情,即使是考虑多语言输入的问题等,在游戏编程精粹系列中,很早就有论证这些问题的文章[1]。而如今面临的问题似乎同前辈们没什么两样。相比游戏开发中的其他领域(尤其是图形学),这个话题好像没什么可谈的。即使我在文章开头的前几段论述了这个问题多么棘手,但貌似现成的解决方案存在着一大堆。而在之前,我在一个游戏开发的群里提到这个话题时,被人指出是在浪费时间,对方建议我应该多研究研究图像学之类的如何让游戏画面看起来漂亮的技术或者去钻研钻研游戏叙事来让游戏内容变得好玩。:-)
但事实真的如此了吗?
从key-value表到I2 Localization
实现一个简单的多语言系统是十分容易。一个最小可用的多语言系统大体需要如下的部分:
•字典表,记录了所有需要多语言处理的字符串,key往往是对应内容的英文,而value则是对应语言的翻译内容。这些内容在文件系统里通常以json/csv/xml这样的形式存在。
•一个形似Localization.Get(key,lang)的函数,用来在Runtime返回指定key和语言所对应的翻译内容
•一套加载机制,用来在对应语言环境下加载对应的多语言文件
•一套通知机制,使得系统能得知语言被切换过。这个系统往往是通过订阅对应的事件来完成通知。
实现这些东西的代码花费不了几个小时就能完成。然而仔细思考就会发现要拿这套系统来用的舒爽,就十分的困难了。
一些待解决的问题:
•字体
•缺乏对其他多媒体资源的支持
•少数语言对文字排版的需求
•翻译人员对接
就字体问题而言,对于欧美不少语言,一个字体文件往往很容易就能解决大部分语言的字体问题,但是一旦遇到中日韩,就会变的十分头疼。所幸很多系统和引擎都提供字体的fallback机制,只要合理利用这套机制,使得字体问题也能轻松得以解决。再或者干脆再弄个字体文件的字典表,替换文本的同时也替换对应的字体。
其他媒体资源得支持问题,在别的本地化系统里出场率远远低于游戏。这是因为游戏里很容易产生那些需要多语言处理得多媒体资源。一个艺术字标题或者一个图片按钮,亦或是人物对话的配音。不过我们照猫画虎,按着如何处理文本,也同样来处理多媒体资源就行。这里比较棘手得问题是,相比文本这种往往一个语言的所有多语言资源都塞到一个文件内就能解决,多媒体资源在文件系统方面就会变得十分复杂。而一旦这些问题和引擎的资源打包系统耦合在一起,要设计出一套好用又节省内存的方案并不简单。
少数语言文字排版需求通常是指阿拉伯文字这种特有的从右往左排版的情况。相关资料十分丰富,我也不再赘述。不过要注意的时如果涉及到多语言混排,基本很难做对。如果需要相关信息,W3C i18n小组下的内容对此会十分有帮助[2]。
和翻译人员对接部分,目前来看,业界主要以通过翻译外包的形式将本地化任务托管给其他公司。这些公司往往要求提供key-value形式的多语言数据文件(如xml/csv/xls)给他们。如果你在第一步的时候就已经决定用这样的方式来组织你的多语言文本,正好可以一劳永逸。但第一步选择了别的方案可能就需要提供导入导出功能。
Unity下有现成的处理好这些问题的框架吗?当然,小节标题后边的部分说的就是它——I2 Localization,Unity资源商店里顶顶有名的国际化插件。
关于它的介绍我不打算多花笔墨,读者可以直接阅读它的文档来一窥究竟。I2 Localization基本上很好的解决了上边提到的问题。而且他还有不少十分有用的特性。
如果你觉得你的问题已经解决了,那么你可以停止阅读赶紧去购买插件/自己实现类似系统了。但是,假如你任然保佑困惑,或者好奇多语言系统还有那些问题和改进的地方,那么我将在下边两个小节内尽力给你一个满意的答案。
Rosetta-关于本地化问题的反思和实现
虽然上一节提到的I2 Localization十分优秀,但是依然有不少问题有待解决:
1.key-value形式的多语言系统天然违反游戏开发的流程和直觉
2.繁杂而未经设计的翻译文本组织文件进一步导致了功能上的缺失和解决这一问题的困难
3.对于渐进式开发和需更新维护的游戏缺乏合适的Merge机制
4.和翻译人员对接的部分过于的粗暴反而埋下多语言问题的祸根
在key-value模式下,你需要为每一个多语言文本分配一个key,这个key往往是一个英文单词或者短语。这对于通常的软件来说十分够用。本来大部分需要多语言处理的文本都是按钮上的文本或者窗体的标题。但是对于游戏来说,这类的数据占比十分低,而更多的多语言文本是游戏内各种物品技能装备等的说明和关卡里的人物对白。这些文字全都篇幅不短。如果你的游戏内有书籍系统,那么更是意味着对应的是一篇短文。这时候为这些数量众多的大段文字去分配一个个不会冲突的key是十分头痛而又麻烦的。可能最后这些key变成了一个个奇怪的缩写标记(比如一个l10e2s2这样的神秘字符串代表着关卡十中第二个事件上的第二段对话)。而开发人员在浏览游戏工程文件时,看着充满神秘字符串的界面,还要去回头查找对应的是什么文本。
你可能会说,既然如此我干脆用需要翻译的文本本身来作为key好了。噔噔噔!恭喜你,想出了几十年前那些*软件黑客们就已经构思并实现了的点子——GNU Gettext。GNU Gettext的一大特点就是用文本本身做为key。整个软件选取一个开发语言,开发时所有的文本均按该语言撰写。然后这些文本数据通过特定的标记(往往是通过Gettext的获取多语言文本的函数),在软件编写完成后,通过特定的程序扫描整个软件的代码,从中将这些被标记的静态文本挨个收集起来。
除此之外,GNU Gettext所定义的用于组织多语言文本的特定文件格式po/pot(po文件用来组织翻译文本,而pot文件则是翻译文本模板文件),也解决了上边的问题二。相比大部分情况下使用某种现成的序列化文本格式来组织多语言文本,po/pot文件考虑了更多多语言工作中需要解决的问题,比如pot文件里支持争对单个文本片段的多种形式的注释:
1).#后紧接着空格符的注释内容,是翻译者添加的注释;
2).#后紧接着.的注释内容,是xgettext从源代码中提取出的注释内容(通过xgettext--add-comments选项);
3).#后紧接着:的注释是待翻译语句在源代码中的位置信息;
4).#后紧接着,的注释是msgfmt程序专用的flag;
5).#后紧接着|的注释是这条待翻译语句之前的相关翻译信息;
而大部分使用通用序列化文本格式的本地化系统,基本不会去考虑这些情况。更何况这些部分还需要各种软件和工具的支持,而繁杂的翻译文本组织方式更是让想支持这些也变得困难,除此之外,对于问题三,大部分使用通用的序列化文本格式的系统也几乎不曾考虑,而po/pot文件很早就开始考虑这些问题并提供支持。GNU Gettext作为一个存在几十年的系统,期间积攒了数量众多的处理这些问题的工具和软件。
问题四要指出的是,目前的外包形式的解决办法,游戏开发商难以较为积极的参与到多语言工作中,其次对于游戏来说往往存在着大量需要多语言处理的多媒体资源,这些资源通常还涉及到美术和配音人员。最后的结果就是要么把这些也一同丢给本地化外包公司,要么就组织两套系统然后花费额外精力来维护两个系统间的周转。其次完全交给外包公司也让游戏本地化工作问题的反馈难以及时更进(实际上大部分游戏根本不会提供多语言测试版或者雇佣多语言测试人员)。这一结果使得游戏开发和多语言工作过分割裂,最后很容易产生各种翻译问题并很难解决(比如被游戏视频UP主神奇陆夫人常常念叨的Artifex公司作品的翻译迫击炮问题)。
我个人认为解决这一问题的办法是游戏开发公司应该把多语言工作的主动性掌握在自己手里,通过自己部署翻译管理系统,将多语言工作涉及到的人员全部聚集在一起。同时游戏应该提供多语言测试工具并提供多语言测试版本,然后让多语言测试人员(可以是公司招聘的人员,也可以是通过发布测试版来让对应语言的玩家充当)将多语言问题通过这个系统提交到翻译内容管理系统上。在这一*下,翻译人员不再是外包给翻译公司,由公司内部分配然后运作在其内部系统上,而是翻译公司外派人员在游戏开发公司的系统上来工作。这样一来无论是监管还是进度追踪抑或是翻译问题的追踪和处理都会变得简单而透明。同时也可以将配音和美术人员也统一在这一框架下。而且如果是大公司的话,这里边的翻译工作人员也可以是公司自己内部的员工。当然,这些构思只是出于一个技术人员在软件开发角度的考量。可能涉及到商业和管理方面的问题并不一定会达成(比如翻译公司拒绝外派人员而坚持使用自己的内部系统)。另外不少独立游戏公司的本地化工作都是通过Mod系统然后让对应语言的玩家社区来完成。如果有这样的工具来提供给玩家社区也是很不错的。
从上边讨论可知,GNU Gettext的解决方法优于key-value方案,手机游戏交易平台并且gettext还有配套的开源的翻译内容管理系统(比如Weblate),这意味着问题四也可以方便的解决。
然而原本的GNU Gettext并不十分符合游戏开发。比如游戏的多语言数据基本上不会存在于源代码中。其次游戏存在众多的需要多语言处理的多媒体资源,而那些配合gettext的TMS往往只支持文本内容的处理,前略里已经完整的讲过这个问题了。如此一来,直接把GNU Gettext搬过来只会水土不服。我们需要一个适用于Unity和游戏开发的使用和GNU Gettext类似理念的全新的本地化框架。
简单来说,我们需要:
1.设计良好的组织多语言数据的文件格式
2.通过简单标记就可以自动化收集多语言资源的工具
3.一个运行时的加载这些多语言数据文件,并在恰当的时机讲资源替换成对应语言的内容的系统
4.对翻译人员提供符合他们工作习惯的工具来完成翻译工作
这一想法最后诞生的结果就是Rosetta。
Rosetta采取了和GNU Gettext相同的组织翻译文本的方式。这意味着我们可以在文本资源上充分利用GNU Gettext现成的工具链。除此之外还处理了多媒体资源的多语言问题。
而在问题二上Rosetta充分利用了C#的attribute和reference这两个语言特性,通过i18nAttribute来标记需要多语言处理的资源,然后通过反射来搜集这些数据并生成对应的多语言模板文件。
对于问题四来说,Rosetta暂时只能依托于GNU Gettext现有的工具来解决。目前我正计划在Rosetta 1.0.0版本的时候,提供一个和Rosetta搭配的适用于游戏开发的翻译内容管理系统:RosettaServer。
只有这些了吗
在游戏本地化问题上,是否还有其他前瞻性的东西,我想很多人和我一样好奇。而思考这些问题则花去了我十分多的时间(实际上这些问题在一年多以前就以各种片段的形式存在于我脑海而直到最近Rosetta的第一个可运行版本完成开发,我才开始着笔写下这篇文章)。有幸于我对小众游戏的关注和经常看到好玩的但没有中文的游戏/软件会动汉化他的念头的缘故。我得以见到很多游戏提供的翻译系统,也接触到了一些处于这个领域十分前沿的问题。
AVG.js和huozi.js
AVG.js是我的一位朋友Icemic在HTML5平台上实现的galgame开发套件。这套工具里最让我印象深刻的是专为游戏内中日韩文字排版而实现的排版引擎huozi.js。
实际上游戏开发对文本显示的要求仅仅处于能看这一阶段。而去思考并实现对应语言的规范排版需求的系统的情况基本不存在。虽然W3C的i18n小组一直在做这方面的工作(huozi.js的开发也有参考他们的中文排版需求),但是游戏行业对这些毫无兴趣。这一方面是因为这个问题本身十分复杂且和多个系统耦合(排版系统和I18N),另一方面游戏内往往并不会出现大篇幅的密集文本。然而随着游戏的发展,我相信以后不少游戏都会遇到类似的需求(如上古卷轴5里的书籍系统)。
Wayward:从语义到语法
大部分情况下,我们汉化一段文本的时候,只是在理解它的语义,然后用另一种语言来表达它。但在一些特殊情况下,翻译工作会涉及到把一门语言的语法翻译到另一门语言上的问题。
最简单的情况是单复数的处理。在中文中单复数很少会导致一个名词发生变化,然而在英语等语言里,单复数往往会导致名词发生变动,而在一些语言里甚至一个两个三个和复数个都对应着不同的形式(在I2 Localization里有处理这个情况,并且支持最多为七的情况)。
然而名词的单复数只是这个问题下最简单的一种情况。在Wayward中,翻译需要处理更为复杂的语法问题,除了名词单复数外,还有代词和介词等。整个游戏翻译模板里有一个很复杂的表格来表述这种转换:
之所以会存在这种情况是因为,Wayward是一个RougeLike类游戏,而游戏内有不少文本来自程序生成。在这时候就自然而然的涉及到语法的问题。翻译工作也不仅仅是翻译语义,而要考虑对应语言的语法。
而可以遇见的未来是,由于过程生成技术在游戏里越来越广泛的使用,游戏文本的过程生成也必然会越来越盛行。以后涉及到语法的本地化工作的情况也会越来越常见。
配音和口型生成
几乎所有的3A游戏都提供配音,而这时候又涉及到3D游戏使用对应语言的配音时人物模型口型的问题。很早以前游戏可能会考虑采取捕捉或者人工调整来生成口型数据,而如今更多的是使用算法通过文本来生成对应的口型数据。这样一来又引入了一种全新的需要多语言处理的数据。
结语
以上提到的诸多问题,很多都处于多个领域交叉的情况,甚至你很难界定某些具体问题属于那个领域。但是你要完成一个游戏的多语言工作,往往必然会遇到他们。
另一方面,从最早的普通软件到如今的3A游戏,随着游戏技术不停的发展,也会引入越来越多的问题。最早我们可能只需要翻译文本,之后引入了图像文件,再后来游戏配音又引入新的问题(配音和口型)。而可以预见的未来是,随着过程生成技术流行,很多传统的对多语言资源的处理也会面临着翻译内容变为翻译规则。而只要游戏技术不停的发展,游戏本地化工作也会出现新的待解决的问题,这是本地化和国际化做为一个同游戏内多个方面耦合的系统的必然结局。