专栏/大概是我见过的最好的Renpy游戏翻译教程V2.0#2

大概是我见过的最好的Renpy游戏翻译教程V2.0#2

2023年10月20日 09:30--浏览 · --点赞 · --评论
胡适不打牌
粉丝:6314文章:10

        胡适不打牌(转载者)说:

        大家好啊!很高兴又与你见面了。能找到这里,说明你对翻译这个爱好非常感兴趣呀!接下来的内容是承接上篇翻译教程的。内容涉及了较为进阶的代码,函数,大佬的解决问题思路等等如果你还没入门,建议先看看上篇内容

        如果你已经准备就绪了,那我就不耽误你学习了。让我们开始吧!

3.-为什么没法正常运行

        在教了你这么多之后,如果你成功完成了自己的翻译,并且一切都运行正常,没有出现问题,那么恭喜你,你是个学得很快的学生,也恭喜你选了一个很简单的游戏拿来练手。因为一般来说,在试玩刚刚做好的翻译时,总会时不时看见没有翻译的文本,即使你已经把翻译文件里的文本全部翻译好了。接下来,我会解释一些最常遇见的问题。

 

3.1.-bug和错误

        首先,我们要熟悉一下当出了问题的时候Renpy用来警告我们的界面。在理想情况下,游戏在发布时只会有很轻微的bug,而不会有严重的错误,所以译者们第一次见到的报错界面应该就是Renpy无法生成翻译文件时弹出的错误界面。这种错误只有两种原因:要么我们在编辑原本的.rpy文件时犯了一些错误,要么我们使用的Renpy引擎的版本比游戏的开发者使用的版本要老,导致出现了兼容性问题。显然,第二种情况非常好解决,只要花几分钟下载最新版本的Renpy引擎就可以了。

        如果我们怀疑问题出在我们编辑脚本时犯了错误,那就要检查一下游戏的根目录,看看到底发生了什么。如图所示,在游戏的.exe文件旁边,有几个.txt文本文件:

        我们应该先看看errors.txt。其中会列出出现了问题的代码。在这个例子里,只是少了一半引号(script.rpy的第5624行)这样的小问题,但也有可能是使用Tab键制造了缩进,不完整的{ }符号,等等。我们只需要打开对应的脚本,修复问题,保存,再用Renpy生成翻译文件就可以了。

        如果我们尝试打开游戏,就会显示这个界面,其中的信息与errors.txt中的一致。

        有时候,错误并不严重,导致的结果就是游戏可以正常运行,也可以生成翻译文件,但在游戏中途的某个节点会突然弹出错误界面。一般来说,这种错误最常见的原因是{ }符号不完整或者游戏的变量出了问题。这种时候,就会突然弹出这种界面来:

        这里我们可以选择忽略这个选项,使得游戏可以继续进行(有比较高的可能性会出现更多错误,甚至导致游戏崩溃)。我们也可以选择回滚到一个较早的时间点,并在尝试修复问题之前保存游戏。

        此外,我们还可以用BBCode格式复制这些信息(使我们可以在线上论坛中插入)或者用Markdown格式复制(可以附带在Discord消息中),方便我们向他人寻求帮助。然而,这种错误往往都是很轻微的,即使并不是由于我们的翻译而出现的,也很容易修复。要修复这些问题,我们要看错误信息的第一行,那里标注了引发问题的代码的位置。在这个例子中,在“script.rpy”这个翻译文件中(如果你仔细观察的话,可以看到这个文件是位于“game/tl/chinese”文件夹里的,所以我可以断定是翻译文件,而不是原始文件),第21787行,有一个不完整的{ }符号对:{i}命令被用来让文字斜体,但却并没有完整使用,使得游戏出现了错误。

        如果我们现在再去看看游戏的根目录,可以发现新生成了一份“traceback.txt”文件。这个文件里包含着与刚刚的错误界面相同的信息。traceback.txt与errors.txt在每次出现错误时都会重新生成并删除上次错误的信息,使得你打开时总是只看到最后一次报错的信息。不管怎么说,在错误出现后,我们要做的就是定位并修复错误,保存脚本,并重复一次导致错误的操作,看看问题是否已被修复。

        最后,如果我们看到了一条更复杂的报错,或者是指向“renpy”文件夹中的脚本的报错,那有可能是我们不小心删除了一些看起来并不重要的符号(比如说百分号%或者是斜杠\和/),或者是添加了一些符号(比如说在%的前后加了空格),从而违背了Renpy事先编程好的功能。这种错误在使用机器翻译的时候更容易出现(因为机器翻译有时会因为各种各样的原因对特殊符号进行替换),要修复起来更加困难,因为我们必须对Renpy的内部构造有高水平的了解才能知道到底要去哪里才能找到问题的根源。所以要么多点耐心,去论坛里学习知识仔细研究,要么就下载一个干净、没有bug的版本的游戏,重新开始。

 

3.2.-一行文本过多的解决办法

        一般来说,用英语写的句子比大多数语言要更短。这就导致在翻译时,有时候文本会溢出文本框的范围,到屏幕外看不见的地方,或者和其他的界面重叠在一起,使得玩家的游戏体验大打折扣,甚至有时还会影响到其他按钮的使用。这个问题有三种比较简单的方法可以解决。

        对初学者来说,我们总是可以更改译文,使得这句话可以用更少的篇幅表达。如果实在难以做到,我们就可以利用Renpy赋予我们的便利。尽管Renpy在翻译文件中只给了我们一行用来翻译,但实际上一句译文是可以被分为许多行的,只要遵守Renpy有关引号和缩进的基本原则就好。请看这个例子:

# game/script.rpy:10

translate chinese label_start_XXXXXXXX:

     #mc “Hello. My name is John.”

     mc “你好。”

     mc “我的名字是约翰。”

        尽管文本在源代码中只用了一行,但在翻译版本中却会被分成两行,而不会引发任何问题。实际上,我们可以做很多很多的修改,比如添加函数,修改变量,等等,并且这些修改也只会在使用我们的语言时适用。这是因为,尽管翻译函数只是把一个文本串用另一个语言的文本串进行替换,但实际上这个功能的实现是用我们在翻译文件中写的代码去替换源文件中的对应的一整行代码,所以我们才能用一整块不同的代码去替换原本的一行代码(这行代码恰巧是一个角色说的一句话)。

        第三个方法,有点复杂,就是更改字体的大小和/或者文本框的宽度,使得每行可以显示更多字符。但是,因为这就意味着要更改所谓的“样式”(styles,也就是游戏的文本和菜单的外观),我就把它放在下一段里解释。

 

3.3.-不支持特殊字符的字体:更改样式

        有时候,我们翻译的游戏使用的字体并不支持一些特殊字符,或者一些在其他语言中经常用到的独特符号(译注:对中文来说,就是每个汉字)。因此,在玩翻译版本的时候,这些不支持的字符就不会显示,或者会被显示为很奇怪的符号。要想在不修改游戏的源代码的条件下解决这个问题,我们可以在游戏内直接按下A键:如果游戏是用比较新版本的Renpy引擎制作的,那么无障碍菜单就会出现,我们可以选择DejaVuSans作为默认字体(译注:这个方法对中文翻译并不适用)。显然,这会改变整个游戏的美术样式;此外,如果我们能解决这个问题的话,那还是干脆把问题解决掉好了,你不觉得吗?

        如果我们恰巧拥有对应的技能,并且有足够的时间的话,我们可以一步到位,直接使用字体编辑软件(比如TypeLight)修改原字体,设计出缺失的字符和符号。这种方法不会过于影响游戏的开发者原本构想的美术风格,而且我们只要把新的字体文件放到game文件夹里替换已有的字体即可。但这种方法显然没有普适性,最合理的做法还是开始学习一下Renpy中的“样式(style)”到底是什么东西,以及怎么更改它。

        从名字可能也看得出来,在Renpy里,样式就是数个元素组成的阵列(文本颜色,字体大小,文本框的位置和设计,如此等等),用来定义游戏的UI的外观。如同我们在上面看到的一样,我们需要修改其中的一些元素,好让我们的翻译可以被正确显示。我们可以直接在“game/gui.rpy”和“game/options.rpy”等脚本中寻找并修改对应的样式,一般来说定义游戏中文本的显示方式的元素都在这两个文件里。

        这是我在这篇教程的第一版中提到的方法,但我们必须考虑到,如果我们更改了原始文件中的代码,那改动就会被适用到所有语言,也就破坏了开发者对游戏的原本美术构想。此外,如果我们把包含有修改过的源文件的翻译补丁分享给别人的话,即使他们之后决定删除掉翻译补丁,也永远没法变回纯净的原版游戏了。

        所以,如果我们想在我们的翻译被启用时更改游戏中的一些视觉元素,同时不影响游戏的源代码,我们可以使用下面这个翻译函数,写在任意脚本中都可以(最好写在翻译文件夹中的文件里):

translate chinese python:

        这行代码不需要缩进,并且当然,chinese是生成翻译文件时填入的名称。这个函数开启了一个python代码块,在其中我们可以重新定义我们需要为翻译修改的每一个元素,同时还不影响使用其他语言时游戏的原设置。对于字体问题而言,我们可以使用这样的代码:

translate chinese python:

    gui.text_font = "myfont.ttf"

    gui.name_text_font = "myfont.ttf"

    gui.interface_text_font = "myfont.ttf"

    gui.button_text_font = "myfont.ttf"

    gui.choice_button_text_font = "myfont.ttf"

        这里“myfont.ttf”是我们想使用的字体文件。每一行都会影响一种类型的文本(从上到下:对话,角色名字,UI文本,按钮和选项文本),所以我们实际上并不需要全部更改,只用更改我们需要用到的部分就可以了。还有,不要忘了把用到的myfont.ttf文件放进补丁里。

        我们也可以使用这个函数来更改其他的UI元素,比如字体大小或者文本框的位置和宽度,这样就可以解决文本过多,无法正常显示的问题:

translate chinese python:

    gui.text_size = ...

    gui.name_text_size = ...

    gui.interface_text_size = ...

    gui.label_text_size = ...

    gui.notify_text_size = ...

    gui.textbox_height = ...

    gui.dialogue_width = ...

    gui.dialogue_xpos = ...

        还有很多很多。我们只需要把三个点替换为一个数字的值:对于文本大小text_size,我们可以填入一个比原本gui.rpy中分配的值更小的数字,就可以让字变小了;如果增加gui.textbox_height,就能让文本框更高,于是可以容纳更多行;如果增大gui.dialogue_width的数字,就能增加文本框的宽度,使得每一行更长;还有更改分配给gui.dialogue_xpos的数字,可以水平移动文本开始显示的位置(数字越小就会让文本从越靠屏幕左边的地方开始显示)。这些变量使得我们可以调整我们的翻译的文本,从而能够解决大部分游戏中遇到的问题。尽管,就像之前提到的一样,这样做也会改变游戏的外观,从而可能导致新的视觉问题出现,比如文字重叠等。我们需要不断尝试、实验,直到获得满意的结果为止。

        目前为止提到的所有方法都是针对那些影响整个游戏的元素的。但也有可能游戏的开发者定义了一个单独的设置,只用于某个特定的角色,界面或者是游戏的一个部分(比如一段做梦的剧情,在这个剧情里文本的样式不同),所以这些文本不会使用那些更大范围的定义。这就是所谓的“样式”,使用style命令进行定义。这种情况,译者的最大挑战实际上是要找到原脚本中样式被定义的地方,因为我们需要知道样式的名字;然后流程基本就是一样的:使用 translate chinese python: 这个函数,我们可以重新定义那个样式中需要更改的元素。举例来说,如果我们想改变一个名为“dream”的样式中使用的字体和字号,我们可以这么写:

translate chinese python:

    style.dream.font = “myfont.ttf”

    style.dream.size = 22

        再次强调,关键是在于找到原文件中样式的元素被定义的地方,这样才能知道样式的名字和原本的值,以便我们进行修改。

 

3.4.-翻译图片

        有时候游戏的开发者会决定把重要的文本显示在,比如说,一条短信里,或者任何其他的视觉元素(电脑截图,报纸头条,墙上的海报等等,任君想象)。尽管Renpy提供了几种方法可以把这些文本写在脚本里,因此也让翻译变得更加容易,但开发者们往往会觉得直接使用一张包含有文本的图片更加方便。

        如果我们足够幸运的话,当图片被显示的时候,正好也在发生对话,那在我们的翻译中可以把图片里的文本内容直接加到角色说的话里,就好像他们大声读出来了一样:

# game/script.rpy:12

translate chinese label_start_XXXXXXXX:

    # mc “Look. He sent me a message.”

    mc “看。他给我发了条信息。他说(图片里的内容)。”

        同时,如果图片中的内容太多,我们可以使用之前介绍过的方法,使得文本可以被放进文本框里:可以分成好几行,把图片中的内容写进去,需要的话还可以用 斜体 显示,以示区别,让玩家理解。

# game/script.rpy:12

translate chinese label_start_XXXXXXXX:

    # mc “Look. He sent me a message.”

    mc “看。他给我发了条信息。”

    “{i}(在这里翻译图片中的信息。){/i}”

        如果,很遗憾,没有任何与图片相关的对话的话,我可以在源文件里创建一个空的对话。第一步是打开源文件,找到Renpy显示图片的那行代码(我们可以根据邻近的文本串进行定位),然后,使用相同的缩进,在下面新加一行空的串(只写一对空的引号)。之后我们只要重新生成翻译文件,把翻译过的图片里的文本填进空串里。当然,这样一来我们就得把修改后的源文件放进补丁了,或者,使用后面会提到的label替换的方法。

        然而,有时候,这种方法就是行不通,或者看起来效果很不好。这样一来,唯一的办法就只有修改原图片(或者,更准确地说,是创建一个新的图片),需要用到Photoshop或者GIMP等软件(如果只是简单修改的话甚至可以用系统自带的画图)。首先,我们要到原文件中找到图片的名字(一般是出现在show或者scene指令的后面)。之后我们要在“game/images”文件夹中找到图片,然后修改,把文本翻译为我们的语言。理想情况下,我们可以去找游戏的开发者,询问能否得到上面没有文本的基底图片,这样会让编辑图片的工作更容易一些。

        在修改结束之后,我们不应该替换掉原图片;而是要用相同的文件名和后缀对图片进行保存,放入翻译文件夹里和原图相同的路径里(在我的例子里,就是“game/tl/chinese”)。比如说,原图的路径是“game/images/ch1/sms00.jpeg”,那么翻译后的图片就要放到“game/tl/chinese/images/ch1/sms00.jpeg”。这样一来,当我们在玩翻译版本的时候,如果图片“sms00.jpeg”需要被显示,Renpy引擎就会先在翻译文件夹的“images”里搜寻,如果在那里找不到,才会显示原本的“game/images”文件夹中的图片。并且,理所应当地,如果我们使用原语言进行游戏的话,就会显示原本的图片。

 

3.5.-同形同音异义词:对同一个词的不同翻译

        这个问题在之前讨论Renpy使用的两种翻译方式时就已经提到过了。那些使用直接替换法进行翻译的串不具有加密码,也只能对应一个翻译,尽管它们在脚本中可能出现不止一次:实际上,如果我们使用old指令把同一个词翻译两次,那么游戏会直接无法打开,并且在报错信息中会显示同一个词存在多个翻译,使得我们必须删除其中一个。

        但是,正如我在之前的例子中提过的,有些相同的词需要不同的翻译,比如英语中“Right”这个词既可以表示“正确”,又可以表示“右边”。解决的方法就是修改原脚本,使得相同的串变得不再相同,而不影响到使用原语言的游戏。要实现这样的效果,只需要使用#符号。

        我们在文章开头的地方提到过,#符号右边的内容全部会被Renpy视为注释,从而在游戏运行时被忽略掉……除非我们把它放在一个标签里。标签(Tags)就是嵌入在串里的指令,这些指令会被放在{ }括号里,通常用来改变字体的大小和颜色,或者让句子加粗或者斜体,等等。Renpy把这些标签当做串的一部分,并且会执行标签中的操作,但标签中包含的文字却不会显示在屏幕上。所以,如果我们在标签里的文本前加上#符号(这样一来Renpy就知道不必执行这个标签里的指令),我们就可以得到一个与不加标签的串不同的新串(也就会被Renpy引擎当做不同的串给提取出来)。但这两个串显示出来的效果却是一模一样的,因为我们新加入的标签并不要求Renpy进行任何操作。

        所以,我们需要在原文件中找到需要进行不同翻译的串,然后在后面加上一个方便我们之后辨认的标签,比如说可以使用我们已经想好的译文做标签。如此一来,当Renpy在读取和提取串的时候,对它来说“Right”和“Right{#方向}”就不再是相同的了,尽管这两个串在屏幕上都会被显示为“Right”。现在我们只需要重新生成翻译文件(或者手动添加到翻译文件里),然后对加了标签的新串进行翻译就可以了。最后的结果看起来应当是这个样子(注意,这两个串可能并不会挨着出现,甚至可能不会出现在同一个翻译文件里):

translate chinese strings:

    old “Right”

    new “正确”

 

    old “Right{#方向}”

    new “右边”

        请记住,在这种情况下,仅在翻译文件里这么写是没有用的:我们还要修改原文件,在对应的串里添加标签。这样,在玩翻译版本时,Renpy才会搜寻加了标签的串的翻译。

 

 

3.6.-翻译文本变量(Ⅰ):插值

        在之前谈论Renpy引擎提取文本的缺点时,我们已经说过变量中的文本值不会被自动探测并提取。并且,不管我们是否已经努力将这些文本变量包含到我们的翻译文件之中,最大的问题是,大多数时候这些文本变量的翻译就是无法正常显示。

举个例子,在脚本中的某处,定义了如下变量:

default fruit = “apple”

        然后,或许在另一个脚本里,又有一行代码告诉Renpy,要把这个变量里的值进行更换:

$ fruit = “orange”

        有时候我们需要对脚本进行彻底的检查,但现在就先假设我们已经找到了“fruit”变量的初始值,以及所有可能的赋值。并且,不管我们是使用了_( )符号组使得这些文本可以被提取出来也好,还是说我们手动将这些文本变量写入了翻译文件也好,总之,多亏了old和new指令对,这些文本已经被翻译好了,代码长这个样子:

translate chinese strings:

    old “apple”

    new “苹果”

 

    old “orange”

    new “橘子”

        当一个变量目前的值被显示在屏幕上的时候,在代码里实际上它是被嵌入到串里的,这一过程的专业名词叫做“插值”。我们可以轻易分辨出插值的出现,因为在串里,我们可以看到方框里的变量名,就像这样:[变量]。有时候串里只会有一个变量,有时候变量会嵌入在句子的中间。而当变量的类型跟例子里的一样的时候,就会出现翻译问题;也即是,这个变量可能的值是需要被翻译的词语或者句子。

        比如说,在翻译文件里,我们可能会遇见这样的情况:

translate chinese start_XXXXXXXX:

    # mc “I don’t like this [fruit].”

    mc “我不喜欢这个[fruit]。”

        在这个例子里,[fruit]就是被插值的变量:根据我们在游玩过程中做出的不同的选择,这个角色会说出他不喜欢的水果,句子也会随之发生变化。这样编程,使得开发者不用把这一句话重复写很多次。但问题在于,如果我们不管这里的插值,只是按照原文复制到译文里的话,就等于让Renpy显示出[fruit]变量目前的值,而这个值是用原语言进行存储的,所以即使我们已经把这些值全翻译好了,也依然会以原语言进行显示。

        为什么这样子?想一想就会知道,即使在玩翻译版本的时候,Renpy依然在运行原本的代码,也只会在可以翻译的串出现时才会“跳转”到翻译文件去。所有可以更改变量的值的代码都在原脚本里,使用原语言写成。在我们的例子里,在游戏过程中,Renpy收到了把“apple”当做[fruit]变量的默认值进行存储的指令,之后又可能收到了把旧的值替换成新的值“orange”的指令,并且现在这个串要求Renpy显示出[fruit]变量目前存储的值。但是,Renpy本身不可能知道这个值到底是数字,可以翻译的词语还是一些符号的随机组合,所以它只能把存储的值原封不动地显示出来。

        因此,如果我们想让Renpy知道这个值实际上是一个在显示之前需要被翻译的串,我们就必须在变量名后面加上 !t 这个后缀。就像这样:

translate chinese start_XXXXXXXX:

    # mc “I don’t like this [fruit].”

    mc “我不喜欢这个[fruit!t]。”

        现在,在执行这个翻译函数的时候,Renpy就会发现,这个被翻译的串还要求它给串内变量的插值搜寻一个可用的翻译,因为这个值实际上也是一个可翻译的串。最理想的情况是,在源文件里,变量就已经写成了[fruit!t]的形式,因为这并不会影响游戏的正常运行,还会让译者更不容易忽略掉需要在翻译的串里加上的后缀:如果在生成翻译文件时没有勾选生成空字串,那么这个带有后缀的变量就已经在翻译文件里了;如果勾选了生成空字串,那至少可以在上一行里看到正确的变量,我们只需要复制粘贴就好。遗憾的是,大多数游戏开发者并不知道Renpy的这些特性,但是,看得出来,这个问题并不难解决:只要 !t 后缀存在于我们的翻译文件里,Renpy就可以显示出翻译后的值,所以我们不需要对源文件做任何修改。

3.6.1-角色名的插值

        现在,我们来看看插值的一个典型例子:角色的名字。可能是因为这样做会在写对话的时候更加方便,也可能是因为给了玩家自定义角色名字的选项,不管怎么说,有时候,游戏的开发者们会用“角色”变量的定义替换掉角色的全名。

        比如说,在游戏“Deliverance”中,有一些角色没有名字,但却有着通用的称呼,这个称呼是可以翻译的词语。他们是这样被定义的:

        在这个例子里,每次角色“li”说话的时候,我们都能在文本框上面看到他的名字(Lieutenant即中尉,是军衔而不是名字)。我们已经看到过,要是想让词语以我们的语言显示,我们就要把对应的串提取出来,这样才能在翻译文件里以old和new指令对的形式出现。这里的独特之处在于,因为[li]这个变量被定义为“角色”,不管它有没有被插值到串中,我们都不需要在后面添加 !t 的后缀:Renpy会自动地知道它的值是文本,并会为其寻找可用的翻译。

        所以,对这个例子来说,即使在我们的翻译中只写了[li],当使用我们的语言时,屏幕上显示的依然会是翻译后的词语。我们不需要写[li!t]。

        然而,有时候,事情就麻烦了。一个常见的情况是,一个配角一开始被分配了一个通用的称谓,因为此时玩家还不知道他的真实身份,但之后又会得知这个角色的真名。这个角色有可能是这样被定义的:

define p = Character(“[pname]”)

        所以,角色“p”的名字实际上是完全被另一个变量的插值所决定的,而另一个叫做“pname”的变量,可能在脚本的不同的地方有着不同的值:

default pname = _(“Unknown Man”)

$ pname = “Mike”

        这种情况下,我们应该找到“pname”变量的定义,并且提取出它的默认值,使用old和new指令对进行翻译;但是,我们也要提取角色“p”的定义,并在字符串翻译里添加 !t 后缀。就像这样:

translate chinese strings:

    old “[pname]”

    new “[pname!t]”

        这样一来,当“pname”使用通用称谓做默认值的时候(并且我们已经找到并翻译了这个值),叫做“p”的变量在显示角色名字的地方,以及所有有[p]插值出现的对话里都会被翻译。所以我们不需要在翻译中使用[p!t],因为“p”被定义为角色类型的变量,Renpy在需要的时候会自动显示它被翻译后的值;在这个例子里,这个翻译是另外一个变量的译文。但要注意,假如在对话块中的插值变量是[pname]而不是[p],那我们就需要使用[pname!t],因为“pname”是一个普通变量,而不是和“p”一样的角色变量。

        使用插值变量定义角色在Renpy游戏里是一种非常常见的行为,因为这是开发者们让玩家们自定义角色名字的必经之路。一般来说,这个名字变量的定义会在游戏刚开始的地方使用到 renpy.input 函数,使得玩家可以输入自己想要的名字(这个名字显然译者们是无法提前知道的,并且也没有翻译的必要)。但我们要注意这个变量的默认值,因为如果玩家给名字留空,或者角色在起名之前就已经说话了的话,这个默认值就会被显示出来了:如果这个默认值是通用的称谓,我们需要用上面提到的方法进行翻译。

3.6.2-语法的一致性:

        在某些语言里,一些词语根据阴阳性、数量、时态等等会发生语法上的变化……而这些变化,对于我们的翻译的易读性和可理解性来说是非常重要的。并且,导致这些变化的变量的值往往在游戏的原语言里不会有这种作用。

(译注:下面的内容是基于西班牙语进行说明的,对中文译者来说实用性并不高。但其中提到的方法或许可以应用到其他场景中。)

        回到之前讲解插值时使用的例子,变量[fruit],假如除了“apple”和“orange”这两个西班牙语中的阴性词之外,[fruit]还有一个可能的值是阳性词“peach”呢?让我们回想一下[fruit]被插值进的串的样子:

translate Spanish start_XXXXXXXX:

    # mc “I don’t like this [fruit].”

    mc “I don’t like this [fruit!t].”

        在西班牙语中,“this”这个词会根据[fruit]中的词语的词性发生变化。所以,虽然在英语中名词并不存在性别,但在西班牙语里却需要准备阴阳性两份译文。幸运的是,我们在之前已经提过,翻译文件的实质就是用一串代码去替换原本的代码,所以我们大可以不只写一句译文,而是加入更多的代码。因此,我们完全可以利用“fruit”变量来为我们的翻译添加新的代码。为了做到这一点,我们需要对Renpy编程有最基本的了解,但这并不难。

translate Spanish start_XXXXXXXX:

    #mc “I don’t like this [fruite].”

    if fruit == “peach”:

         mc “No me gusta este [fruit!t].”

    else:

         mc “No me gusta esta [fruit!t].”

        首先,考虑到有关缩进的Renpy第一基本原则,我们的函数开头的地方与普通的翻译串相同(也就是从最左端开始四个空格键,因为翻译函数以冒号结束:这是Ren'Py用来知道一个新编码子块开始的符号)。然后我们写一个条件函数:用if命令告诉Ren'Py,如果变量fruit的当前值是 "peach",则必须显示第一个字符串(我们将用另一个缩进级别来写这个字符串);用else命令告诉Ren'Py,如果 "fruit "的当前值是其他值,则必须显示第二个字符串。

        如果我们想确保一切都正常运行,那就必须谨记变量和变量的值在源代码中的写法;还要完全按照上面的例子来写代码,比如双等号,用引号包住变量的值,以及在代码的最后加上冒号,还有翻译串的缩进。如果没有按照正确的语法来写,那么游戏就可能无法启动,或者是在运行到对应的部分时出现错误。

        显然,到底要把哪个值(或者说哪些值)当做if指令后的参考值,我们必须针对具体情况逐个分析。举例来说,如果我们知道”fruit”变量可能的值还有”melon”的话("melon”在西班牙语里也是阳性词),就要把代码写成这样:

translate Spanish start_XXXXXXXX:

     #mc “I don’t like this [fruit].”

     if fruit == “peach” or fruit == “melon”:

           mc “No me gusta este [fruit!t].”

     else:

           mc “No me gusta esta [fruit!t].”

        在这个例子里,多亏了or指令,每当”fruit”的值是阳性名词的时候,就会显示第一种译文(写有”this”的阳性翻译)。如果一个词性的值多于另一种,那合理的做法是让if命令后的值尽可能少。

        在我举的这个例子中使用到了插值变量,使得我们可以轻易地看出保持语法一致性的原因,但显然一句话即使不含有可能有许多不同的值的插值变量,也可能需要不同版本的译文。或许最普遍的情况就出现在那些允许玩家自己选择主角的性别的游戏里。在英语中,这种选择并不会使得对话出现太多变化,但在西班牙语中,却会使写代码这件事成为噩梦。一个简单如“You’re my best friend”的句子,在英语中对男女都适用,但换成西班牙语,却使得我们必须在翻译文件里包含一个条件函数,所以我们必须首先找到储存角色性别的变量以及其可能的值,并将其用在if命令的后面当做条件。这个工作做起来可能非常繁琐,因为我们需要给每句可能受影响的对话都额外写一个条件,还要对这些代码多加注意以避免出现bug,但最终的结果是值得的。

 

3.7.-翻译文本变量(Ⅱ):连接串

        有些变量的文本值是由不同元素相加生成的,比我们在之前的部分见到的变量更加复杂。这些文本串需要我们花费更多的努力才能正确翻译并显示。这些串就被叫做连接串,一般是由下面这种Python函数生成的。

$ score = “Your score is “ + strCalc + “out of” + strToal + “.”

        在这个例子里,变量”score”的值是一个句子,其中插入了两个函数的结果(strCalc与strTotal)。这些函数是在脚本中的其他地方被定义的;同时在别的某处,也会有插入了这个[score]变量的句子,用以在屏幕上显示出玩家的分数来。我们已经知道如何把那个串提取出来,并在翻译的时候使用[score!t],但这还不够。那么,要如何才能正确翻译这种复杂的变量呢?

        首先,我们需要认识到,当我们插入[score!t]这个变量的时候,renpy想要搜寻并显示的翻译是一个纯文本的句子。所以,如果玩家当前的分数是1 out of 10,renpy就会在翻译文件中寻找一个后面完整写有“Your score is 1 out of 10”的old命令。但这种句子并不会出现在源文件里,也就不可能被renpy提取出来。所以问题就是,我们应当在翻译文件里把每一种可能的句子组合都完整地写出来,但这种做法非常没有效率——有时候,由于游戏的设计,可能还是根本无法做到的。

        而解决的办法,就是修改游戏的源代码,把其中的文本部分都用__( )符号串包裹起来。在我们的例子中就是:

$ score = __(“Your score is “) + strCalc + __(“out of”) + strToal + “.”

        __( )符号使得renpy可以把那些“不完整的”文本串提取出来,同时也会让renpy把那个变量的值以那行代码被执行时,游戏所使用的语言来存储。若没有双下划线,变量的值就一定会用源代码的语言来存储。

        但这样就有了一个小问题。我们玩的是翻译版,而renpy也把变量“score”的值用我们的语言存储了。可是,如果我们把语言切换回原语言,renpy就会显示原文,那么当显示[score]变量的值的时候,反而会用我们的语言显示出来。为了避免这一点,我们要修改源代码,将变量的写法改为[score!t]。这将会“撤销”翻译,把已储存的翻译过的值翻译回原本的语言。并且显然地,在我们的翻译文件里,要一直使用[score!t]这个写法。

        此外,如果strCalc和strTotal的值也是文本串,我们也要找到这些函数,并使用__( )符号包住它们可能的值。并且,显然地,我们还要把修改过后含有__( )符号的源文件放进补丁里,或者,使用label替换函数或是init指令(取决于变量是在label中被定义还是在独立的Python语句块中被定义)。如你所见,这有些复杂,但依然是可以做到的。

 

3.8.-实在无计可施的时候:“三把宝剑”

        尽管你已经完全按照上面提到的内容做了,依然有可能见到没能成功翻译的文本。别灰心,即使如此,我们还有一些可以使用的最终手段。先不提最明显的做法,也就是直接把源代码修改为我们的语言(毕竟,如果你愿意这么做的话,干嘛还来看这篇教程呢?),我们至少还有“三把宝剑”可以使用。然而,在使用这些方法之前,至少要知道它们可能会造成的后果以及副作用。请谨慎思考,自行负责。

3.8.1-双下划线:

        我们刚刚已经了解过了第一把宝剑,也就是__( )符号串。当我们找不到含有文本变量的文本串的时候,就可以使用这种最后手段(一般这种情况是出现在复杂的菜单或者是物品栏里,或者是我们没时间/没精力查阅所有源代码并寻找各种变量插值)。我已经提到过,这个方法是一种最后手段,因为如果对应变量的文本值恰好也用来决定游戏的路线或者将要显示的场景的话,就可能会导致一些内部逻辑问题。在这种情况下,为了避免bug和故事出现逻辑问题,就应该把那些代码中的文本值也用__( )符号包裹。

        再次回到变量“fruit”的例子上来,我们可以直接把这个变量可能的值用__( )包住:

default fruit = __(“apple”)

$ fruit = __(“orange”)

        这样一来,每当变量[fruit]被调用的时候(不管是在源文件还是在翻译文件里),显示在屏幕上的词语都会是翻译过的“apple”和“orange”。在我们无法找到变量[fruit],也就没有办法将其翻译为[fruit!t]的时候,这个方法很有用。

        但是,如我们之前所见的那样,第一个副作用就是这些值一旦被储存,哪怕在切换回游戏的原语言之后,也会以我们的语言显示,因为renpy储存了翻译后的值,而源代码中依然是原始的插值[fruit]。请记住,我们正是因为无法找到这个插值才使用双下划线这个办法的,所以这个问题也就无法解决了。

        当这个变量同时也在别处被调用,用以决定接下来要显示的对话或者进行的路线的时候,第二个可能的副作用也就随之出现了。请看下面的代码:

if fruit == “apple”

     mc “I like red apples.”

elif fruit == “orange”

     mc “I like orange juice.”

        如果我们不对这里的代码进行修改,游戏到这里就会出现问题,因为这个变量储存的值是翻译过后的“apple”或“orange”,而不是源代码中所写的“apple”和“orange”。因此,renpy就无法找到对应的值,也就无法判断该显示哪个对话。这可能并不会是一个严重的错误,根据游戏的具体代码不同,这种错误可能根本不会被注意到或者只会导致对话出现一些小差错,但是,不管怎么说,这都背离了游戏作者的原意。为了解决这个问题,我们需要在源代码的这个逻辑表达式中也使用__ ( )符号串:

if fruit == __(“apple”)

mc “I like red apples.”

elif fruit == __(“orange”)

     mc “I like orange juice.”

        理所应当地,如果我们使用正常方法进行翻译的话,这些修改也就完全没有必要了,因为逻辑表达式可以正常使用原语言的值进行判断。但如果我们必须这么做的话,就要把修改过的源文件加入我们的补丁里,除非我们使用后面将要提到的label替换函数。

3.8.2.-“替换”函数:

        第二把宝剑可能是最复杂的了,因为它要求我们写出一个Python语句块,并且要对想要替换的部分极其谨慎,但它也是最强大、最通用的方法。这个函数可以在最后时刻自动把将要显示的东西替换掉,不管它位于源代码的什么地方,也不管其是完整的串还是串的一部分。示例如下:

init python:

     def replace_text(s):

           s = s.replace(‘text 1’, ‘texto 1’)

           s = s.replace(‘text 2’, ‘texto 2’)

           ……

           return s

     config.replace_text = replace_text

        这个函数可以写在任意地方,不管是源文件还是翻译文件都会起效,所以最合理的做法就是写在翻译文件里。这个函数的作用是对指定的字符进行替换,把第一个引号里的内容(在这里我称之为’text 1’,’text 2’等等)用与其配对的第二个引号里的内容替换(也就是’texto 1’,’texto 2’等等)。这个替换是在内容被显示到屏幕上之前的最后一刻进行的,此时renpy已经搜寻过(无论有没有找到)这个串的可用翻译,甚至可能已经执行了{标签}中的指令:只要在将要显示的文本中发现了指定的字符组合,就会将其替换为我们指定的替换文本。对于那些无法被renpy自动提取,我们又无法在源文件中找到(同时也无法在old指令后面完完全全地写出这个串的内容,这可能是由于原串中含有一个我们不知道的标签,使得翻译无法显示)的文本串,可以使用这个方法。这个替换函数只对我们选择的文本起效,而不作用于整个串,所以可以绕过这种限制。

        那,会出现什么问题呢?有比较小的概率出现这种情况:原语言中被替换的特定字符串,可能会出现在已经被翻译好的文本串中,而这种情况也会被替换,使得我们已经翻译好的文本出现错误。这种情况并不常见,但可能发生:想象一下,假设我们在翻译菜单的时候遇到了问题,“OK”这个词始终无法正常翻译,于是我们决定使用这个方法将其替换为“好的”。但之后如果在我们的中文文本串里也出现了“OK”,就也会被替换为“好的”。所以,像是“OK绷”这样的词语,就会被显示为“好的绷”,让人无法理解。因此,我建议在使用这个函数时一定要非常谨慎,不要滥用(比如,只对那些非常长,可以确定不会在翻译后的文本里出现相同内容的文本串使用),并且,为了避免在使用游戏原语言游玩时也出现替换,一定要和接下来的方法组合使用。

3.8.3.-语言检测:

        最后,我们来谈谈第三把宝剑,它既可以用来限制第二把宝剑的副作用,也可以让我们在源代码里做一些虽不提倡,但确实有用的改动。这个方法和我们添加一个函数以解决语法一致性问题时的方法有些类似;主要的区别是这里我们是在对游戏的源文件进行修改,而不是翻译文件。我们要添加一个逻辑表达式,用以判断游戏目前正在使用哪种语言,如果是原语言,那么就让renpy继续执行原本的代码,如果玩家使用了别的语言,那么游戏将会执行另一端代码。

        为了做到这一点,我们就要知道,renpy把选择的语言储存在名为preferences.language的变量中。和其他所有变量一样,我们可以把这个变量用在if条件句里,用以命令renpy在满足条件时,显示某段文本或者执行某段代码。

        把它和我们刚刚介绍的替换函数组合在一起,就可以让字符的替换仅仅发生在启用翻译的情况下,而不会让用原语言游玩的玩家受到影响。修改后的替换函数如下:

init python:

     if preferences.language = “chinese”:

def replace_text(s):

                s = s.replace(‘text 1’, ‘texto 1’)

                s = s.replace(‘text 2’, ‘texto 2’)

                ……

                return s

           config.replace_text = replace_text

        如你所见,我们必须把判断语句放在最开始的部分,使得我们必须要增加后面的代码的缩进。现在,这些代码只会在用我们的语言的时候才会执行了。但是,显然,这无法解决我们刚刚提到的意料之外的替换的问题。

        语言变量也使得我们可以在源代码中加入一些自定义的代码。如果我们在原本的代码前加入了条件语句,renpy就会检测目前正在使用的语言,并且由此判断要执行源代码还是我们新加入的另一种代码(顺带一提,因为只有在启用翻译的时候才会执行替换代码,所以其中可以直接包括用我们的语言写的内容)。举例来说,我们可以使用这个方法来解决之前假设的连接串问题:

if preferences.language = “chinese”:

     $ score = “你的分数是” + strCalc + “/” + strTotal + ”。”

else:

     $ score = “Your score is ” + strCalc + “out of “ + strTotal + “.”

        我建议用这种顺序来写这个函数,因为这样就使得else命令可以在使用原语言,以及任何其他非我们目标语言的时候执行源代码。在这个例子里,我们无需在翻译文件中使用[score!t]这个写法,因为这个变量的值已经用我们的语言储存了。以及,显然,如果要把我们的翻译制作成补丁与他人分享的话,就必须把修改过的源文件也带上。这并不是一个好的解决办法,因为它恰恰表明了我们没有能力用合适的方式来解决问题,但至少它确实有效,并且多一种办法也永远不会是坏事。

 

4.-补丁

        一旦我们完成了翻译,自然可以私下独自享用。但是,如果我们想要把我们的杰作和全世界分享的话,我们就要把我们的翻译制作成仅包含要让翻译起效所需的文件的补丁,以供已经下载了原版游戏的玩家们使用。

4.1.-基础补丁

        想让补丁起效,就必须尊重、仿照游戏原本的文件结构。最简单的办法是,在我们的电脑里创建一个新文件夹,按照游戏原本的名字命名,只是要加上说明,让人知道这是翻译补丁;在这个新文件夹里,我们要新建一个名为“game”的子文件夹,并把修改过的.rpy文件(以及对应的.rpyc文件)放进去,此外还要在“game”里再新建一个名为“tl”的文件夹;这个“tl”文件夹中包含着我们的翻译文件(以及,需要的话,还有图片和字体等等,用以解决3.3节中的问题)。玩家们只需把我们的补丁里的“game”文件夹粘贴到他们的根目录(也就是游戏的.exe文件所在),并在出现警告的时候选择全部替换即可。

        举例来说,这是“Eternum”的中文翻译补丁的“game”文件夹:

        这其实算不上一个非常好的例子,因为我事实上使用了下文将要提到的label替换函数,使得不用对源文件进行任何修改,因此也就只包含一个”tl”文件夹,而没有其他的文件。但假如你决定对源文件进行修改的话,一定要记得把修改过的文件放入”game”文件夹里。显然,另外还有一个”tl”文件夹,其中有一个”chinese”子文件夹,包含着所有的翻译文件。这个子文件夹的内容如下:

        在这里,可以看到所有的翻译文件,以及另一个名为”images”的子文件夹,其中存放着所有翻译过的图片。而这个子文件夹的文件结构也与原本的”game/images”文件夹相同,正如3.4节中所解释的那样。

        就如同之前提到的那样,为了让补丁能正常工作,我们要在我们的补丁的”game”文件夹中加入为了解决一词多译而修改过的所有文件,以及为了更改默认语言或是游戏的字体而修改过的”gui.rpy”或者”screens.rpy”,同时可能还有那些为了翻译难以解决的文本变量或者使用了最后手段而加入了__( )符号串的文件。你还可以把那些为了让renpy能自动提取出对应的串而只加入了_( )符号串的文件也放进去,但这只能帮到其他的翻译人员们,因为玩家们实际上并不需要这些。

        我过去一直使用这种制作补丁的方法;然而,由于技术上的不方便,以及一些玩家(以及开发者)对修改源代码这一行为可能产生的怀疑,从几个月前开始我就换用了另一种方法。这种方法可能更加复杂,我会在下一节中讲解。如果,即使在我刚刚解释之后,你依然决定制作一个传统的补丁,那么我推荐你一定要把被你修改过的.rpy文件所产生的.rpyc文件也放进补丁里。尽管由于你对.rpy文件的改动,这些.rpyc文件也发生了变化,但只要自从你下载原版游戏以来(或者自从你为了生成翻译文件而使用Unren解包以来),没有删除过这些.rpyc文件,那么你的电脑里的这些.rpyc文件就会一直保持着游戏的开发者第一次编译当前版本时生成的AST。同样的,玩家们如果从来没有删除过的话,他们的.rpyc文件也是一样的道理。这样一来,在安装你的补丁之后,玩家们应当依然可以读取他们在没有安装补丁时保存的存档。

        那么,如果你只把被修改过的.rpy文件放进补丁,会发生什么呢?这要视情况而定。如果游戏发布时,所有.rpyc文件都直接放在“game”文件夹里,那被你修改过的.rpy文件就会覆盖掉玩家电脑里的原本文件,并且下次启动游戏时,renpy就会把你的改动也编译到玩家的.rpyc文件里去。这一过程应该不会引发任何问题……除非玩家在某个时刻删除掉了原本的.rpyc文件,但这样一来即使出了问题,也不能怪你了。

        真正的问题是在游戏的文件都被打包进.rpa文件里时才可能出现:如果你只在补丁里放入了.rpy文件,renpy就会给它们创建对应的.rpyc文件,但这些文件使用的AST就和原本的文件不同了。并且,如果你没有把所有.rpy文件都放进补丁,那么游戏就还要从.rpa文件中读取一些使用原本的AST的.rpyc文件。这样一来,renpy就必须同时运行使用原本AST的“内部”.rpyc文件,以及使用新的AST的“外部”.rpyc文件。这种情况下,除了我之前提到过的旧存档问题,由于这些.rpyc文件的AST不同,还很有可能导致翻译对话块的函数出现问题,无法探测到已有的翻译。于是,只有游戏内的选项和菜单会有翻译(因为这些内容使用的是另一种翻译函数),但那些被修改过的文件里的对话就依然以原语言显示,因为AST不匹配,renpy无法定位到这些对话块对应的翻译。

        这个问题非常古怪,但的确可能发生,尤其是在编译游戏时使用的renpy版本低于你生成翻译文件时使用的renpy版本的情况下。但正如我所说的那样,只需把.rpyc文件也加入补丁,就可以避免这一系列的麻烦:即使这些文件的内容因为你的修改也有了变动,但依然会保持着原有的AST结构。

4.2.-进阶补丁

        在上一节中,我们制作的“基础”补丁会强制把玩家电脑中的文件替换为我们提供的,被我们修改过的文件。除了那些旧存档的问题、翻译失效的问题之外,一旦用外部文件替换原文件,就难免可能发生无法预料且难以解决的问题。最糟糕的情况下,甚至可能产生我们自己都不知情的bug;但最常见的情况,是可能会把我们所偏好的视觉效果应用到并不需要这些效果的语言上面。这种情况是有必要考虑的,因为我们也不知道我们的补丁到底会被哪些人使用,也许有的玩家在尝试过我们的翻译之后,决定依然继续使用游戏的原语言。

        在刚刚提到的情况下,以及一些其他可能的情况里,许多玩家会希望可以直接把我们的补丁删除,然后游戏就能回到没有任何修改的原版状态。我们不应该拒绝玩家们的这项要求,而强制要求他们使用一些他们并不需要(以及很可能并不喜欢,并不想要)的视觉效果。此外,如果我们能制作出一个不对原本的游戏文件产生任何影响的补丁,那也能更容易说服游戏的开发者把我们的补丁采纳进官方的版本里,这总归是一件让人骄傲的事,甚至还能给我们带来一些额外的收入(或者,至少,跟在没有开发者的同意的情况下,直接把我们的翻译卖给玩家相比,是一种更加光明磊落的赚钱方式)。

        幸运的是,renpy和Python编程语言都为我们提供了一些方法,使得我们可以把为了让翻译更加完美而进行的所有改动都存放在“game/tl”文件夹里,也就让玩家们可以直接在把那个文件夹删除后,就使游戏恢复原本的状态。然而,请注意,这一方法需要更多的操作以及对renpy游戏代码的一些最基础的理解,但也不会牵扯到复杂的东西。

4.2.1.-zzz.rpy文件以及init指令:

        首先,我们要创建一个新文件,用来存放所有需要对源文件施加的改动。只需在文本编辑器内新建一个空白文档,并按自己的喜好对其进行命名(显然,最好给它起一个容易辨识的名字,并且也必须不能和翻译文件夹里已有的.rpy文件重名),加上.rpy的后缀即可。比如,命名为“zzz.rpy”。然后我们就把它放在“game/tl/...”文件夹里。

        一个比较合理的做法是,首先把唯一一个每个翻译中都要存在的元素放进去:也就是切换语言的选项。所以,我们要复制游戏原本的设置界面的代码,也就是下图中显示的比较长的代码(注意:本文默认游戏使用的是renpy默认的设置界面;如果其使用的是自制的界面,那么显然你应该复制作者使用的自制界面的代码)。

        需要记住,要把整个缩进的代码完全复制,也就是从screen preferences(): 这一行开始,一直到下一行未缩进的代码之前,这行代码通常是style开头(我们不需要复制这一行代码)。在这两行代码之间的代码就是设置界面,也是我们为了覆盖原本的界面而需要全部复制到新文件里的部分。在把这些代码转移到新文件里之后,就可以开始编辑了。

        首先,如果之前没有假如切换语言的选项的话,就要现在假如。图中展示的就是在使用默认的设置界面是需要添加的代码(记住,这段代码的缩进级别要和“Rollback Side”以及“Skip”选项的缩进相同,以及不用忘记,制造缩进时要使用空格键,而不是Tab键):

        然后我们必须告诉Renpy,接下来要使用的是这个修改过的设置界面,而不是在原本的screens.rpy中储存的设置界面。为了做到这一点,就要使用 init 指令,这一指令是用来告诉Renpy哪些函数在启动游戏时(在玩家的屏幕上出现任何画面之前)必须执行,以及执行的顺序。可以给 init 指令分配一个数值(从init -999到init 999),这些数值会决定这些指令被装载进Renpy的内存的顺序。这一载入顺序十分重要,因为如果有两个同名指令的话,Renpy只会存储最后一个被载入内存的;具体来说,就是在存在同名指令是,init数值更大的会覆盖数值小的。如果连init的数值也相同,那么最后留存下来的会是被写在文件名按字母顺序排列排在最后的文件里的指令。

        在通常情况下,原本的设置界面的代码并不会明显带有这个init指令,但实际上它是默认以init 0的顺序被加载(也就是排在负数值之后,正数值之前)。也就是说,如果我们给新文件中的设置界面分配一个大于0的init值,Renpy就会把它排列在原本界面的后面载入内存,从而将原本的界面覆盖,达到在游玩时,每当screen preferences被调用,都会显示出我们修改过的界面的目的。长话短说,只需在screen preferences():的上方写这样一行代码:

init offset = 1

        这一指令告诉Rnepy,之后的代码必须按照我们在此规定的init顺序载入,在这个例子里就是init 1。

        或者,我们也可以将设置界面的第一行代码改写成这样(不过更推荐刚刚的方法):

init 1 screen preferences():

        在这些例子里我使用了init 1,不过你可以使用任意的正数(或者是任意比游戏的开发者给原本的界面分配的值更大的数,有些开发者的确会给设置界面分配一个特定的init值)。一旦完成这一步,那么我们的设置界面就会在所有语言的版本中显示。

        现在,要说一件重要的事:如果我们在加入切换语言的选项之前就生成了翻译文件,那么我们很有可能需要翻译“Language”这个文本串(除非它也出现在了游戏里的某个地方)。我们可以看到在我们的代码里,这个文本串也被 _( )所包裹,所以我们会认为,如果重新生成一次翻译文件的话,它也会被Renpy提取出来,但并不是这样的:在形如”game/tl/...”的语言文件夹中,不会提取出可翻译的文本串。最快(也可能是最好)的解决办法是使用在2.3节中提到的“手动提取”方法,毕竟我们只需要翻译“Language”这一个文本串,而不包括语言的名字(正如我之前提到的,语言的名字应当被保留为原文)。

        所以,在我们的设置界面下方,要写上这些代码,使用translate函数,并且没有缩进(但是,我重申一遍,只有在之前没有翻译过这个文本串时才这样做;否则,就会因为出现重复翻译而导致游戏无法启动):

translate chinese strings:

         old “Language”

         new “语言”

        接下来,我们要在zzz.rpy中添加能让游戏直接以我们的语言启动的函数。回想一下,如果我们想让游戏在任何设备上哪怕第一次启动时都以我们的语言启动,我们需要定义变量config.default_language:

define config.default_language = “chinese”

        “chinese”是举例用的语言,记住换成你自己的语言。如果游戏已经给这个变量分配了其他语言,我们就要把这行代码写在init指令下面,将原来的覆盖。就像这样:

init offset = 1

define config.default_language = “chinese”

        而假如我们想让游戏总是以我们的语言启动(哪怕玩家在游玩时更换成了别的语言),我们可以加入这样一行代码,并且直接忽视刚刚讲解的代码,因为default_language变量会由于Renpy的优先性原则被忽略掉:

define config.language = “chinese”

        一般来说,这个变量绝对不会被定义为其他的语言,但假如我们真的碰上了某个游戏已经有了这个变量,我们还是只需按照已有的方法照做:使用数字更高的init指令将其覆盖。

        如此等等。在这个文件中我们可以不断加入各种为了修改UI的视觉效果而必须加入的函数和变量(也就是在讨论更改字体和样式时解释过的知识),也可以加入任何其他的诸如替换函数等大杀器。尽管它们“隐藏”在我们的翻译文件夹里,Renpy还是可以毫无障碍地找到并执行这些指令。

4.2.2-Label覆盖:

        但是,假如我们为了把同一个词翻译成不同的意思,或者是为了翻译某些文本而使用了双下划线,从而不得不对游戏的某些label进行编辑的时候,该怎么办呢?在我们自己的电脑上的游戏的原本代码被我们自己更改过,让我们能够做出完整的翻译,但要是我们不想覆盖其他玩家的原文件,就必须使用label覆盖,从而让我们的补丁即使在其他的设备上也保持完美。

        要做到这一点,我们就必须把被修改过的label全部、完整地复制粘贴到zzz.rpy中来(或者,如果我们想把对游戏界面代码的更改与这种牵扯到实际文本的代码的更改分割开来的话,就粘贴到专门为了这一目的而创建的新rpy文件中)。然后我们需要重命名粘贴进来的label,最好是添加可以让我们清楚辨别它与原label的前缀或后缀(实际上,只要新的名字不包含英语中没有的特殊符号,并且不与现有的任何label名称重复,就没有问题)。举个例子,假如说我们要修改的是名为“start”的label,我们可以在zzz.rpy中将其重命名为label start_ch。最后,我们需要不加缩进地写出这个函数:

define config.label_overrides = {“start”:”start_ch”}

        务必要把每个部分都写准确,这很重要:每个label的名称都被引号包裹,引号后的label会覆盖引号前的label,并且两个label都必须要用大括号包裹。如果要添加更多的label,就把那些label复制到zzz.rpy中来,并且重命名,之后将其加入到这个函数中来,不同label之间用逗号分隔。就像这样:

        话虽如此,一旦我们命令Renpy把原label使用我们的label覆盖,我们之前对原label中的文本的翻译也会失效。这是因为label的名称也是翻译函数中要用到的识别码的一部分。除此以外,如果我们现在重新生成翻译文件,这个新label也不会被Renpy探测到,因为它位于“game/tl”文件夹的文件里。

        然而,解决办法却比较简单。我们只需打开原label的翻译所在的翻译文件,然后搜索原label的名字,并替换为新label的名字,最终达到所有包含有原label名的识别码全部被替换为包含新label名即可。在之前的例子中我们替换了名为“start”的label,也就是说会存在translate chinese start_XXXXXXXX:这样的识别码,替换后,其应该变为translate chinese start_ch_XXXXXXXX:这样的形式。

        我们需要把这个改动应用于所有受影响的文本。我们需要极其谨慎,只更改label的名称,而不能影响到其他部分。幸运的是,更改label的名称并不会影响到一旁由字母和数字组成的MD5识别码(在这个例子中,就是XXXXXXXX),因为我们并没有在翻译完成后修改原文本的内容,所以在这个小改动之后我们的翻译又能完美运行了。值得一提的是,因为使用old new指令对进行的翻译与文本串所处的label的名称无关,所以不管label的名称如何变动,这些文本串的翻译也不会受到影响。

译注:关于label覆盖函数,我在自己的翻译过程中遇到了作者在这篇教程中没有提到的问题。下面将我自己的解决方法一并附上。

        Label覆盖的实质,是在两个label之间建立一个映射,使得当原label被call或者jump函数调用时,调用的对象从原label自动变为我们自己指定的新label。也就是说,只有在使用了call或者jump函数调用label时,这个覆盖才会生效。然而,在Renpy中还存在着一种情况:Renpy按照文件中代码的顺序,直接执行下一行,如图所示。

        可以看出,在上一个label的结尾并没有使用call和jump函数调用其他label,而名为alexposq的label的开头紧挨着上一个label的结尾。在这种情况下,Renpy会按照顺序执行代码,在上一个label执行完毕后自然可以执行alexposq这个label。然而,由于没有使用call或jump函数,即使我们用修改过的alexposq覆盖了原本的alexposq,也依然会进入原本的alexposq——因为没有出现call或jump,不符合覆盖的条件。要解决这个问题,同时也贯彻不对源代码进行修改的原则,我的解决办法是:把上一个label也给覆盖,并在其最后加入call或jump函数。以这个例子来说,就是在上个label最后新加入一行jump alexposq。

 

 

本教程到此结束。我诚挚希望它可以帮到你。

请随意向我提出你的疑问,建议或者纠错(特别是最后一个)。

再见!


投诉或建议

玻璃钢生产厂家汕头园林玻璃钢雕塑制作艺术商场美陈市场报价广场玻璃钢人物雕塑尺寸定西玻璃钢雕塑企业商场美陈服务建议书玻璃钢浮雕喷泉雕塑厂家汕头定制玻璃钢雕塑云南砂岩雕塑玻璃钢雕塑重庆人物玻璃钢雕塑图片商场玻璃钢公仔雕塑定制湖南室内玻璃钢仿铜雕塑玻璃钢鹿雕塑行情元旦商场中庭美陈吊饰玻璃钢雕塑的成型制造深圳园林景观玻璃钢雕塑加工厂家花朵玻璃钢人物雕塑厂家宁波校园玻璃钢雕塑济南多彩玻璃钢雕塑销售电话江苏周年庆典商场美陈价钱北海玻璃钢雕塑特点大熊猫商场美陈室外玻璃钢商场美陈上海常用商场美陈多少钱广东商场美陈供货商杭州定制玻璃钢雕塑销售电话耐高温玻璃钢花盆报价商场玻璃钢雕塑摆件销售公司天水玻璃钢植物雕塑多少钱玻璃钢小品雕塑生产珠海人物石氏玻璃钢雕塑香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化