一. 阿/巴/俄/法/简/繁/土/西/英,还敢再多一点么?
说到如此多的语种和如此多的分布地区的玩家,就想起曾经的一个笑话,运营有次出资料片,用的口号是,“全球300多个国家和5000w玩家齐欢庆……”,姑且不论5000w玩家是怎么统计出来的,有一个玩家就写信反馈说,全球目前总共就200多个国家...无独有偶,金山的剑网3的口号是,“8000w玩家期待……
”,剑网3确实美轮美奂让人不能自拔,但人数寥寥,我和其他玩家都不厚道的觉得,在剑侠世界里,有一个玩家,他的角色名叫“八千万”,咳咳,下面进入正题
1 字符串资源不能硬编码在程序中
不应该在代码中出现形如这样的做法
MsgBox("是否确定删除当天聊天记录?");
这个很好理解,因为需要翻译,如果把提示文字写在程序内,不同语言版本就无法通过配置来实现。
所以应该是这样
MsgBox(g_objStrMgr.GetStr("STR_DEL_CHATLOG"));
Cn_Res.ini:
STR_DEL_CHATLOG=是否确定删除当天聊天记录?
En_Res.ini:
STR_DEL_CHATLOG=Are you sure you want to delete the chat log?
P.S. 即使不涉及到多语种的翻译问题,这些放在配置中也利于提高灵活度,也许哪天策划就像换一种表达方式。
2 尽量把字符串资源写在少数几个文件上
不建议出现形如这样的配置
Msgbox.ini:
[BUYITEM_MONEY_CONFIRM]
Type=3
StrRes=请确认是否使用%u两银子购买%s?
多语种带来的情况就是有各种字符串资源需要翻译,可以想见,在交由专业的翻译去处理之前,需要把配置中出现的字符串资源都完整的找到,若是各国文字分散在无数配置中,光是寻找这些文字就是繁杂和易错的事情。
因此我们的处理方法是,只把字符串资源放在少数几个文件上,其他文件若是想使用,都只使用其索引,所以应该是:
MsgBox.ini
[BUYITEM_MONEY_CONFIRM]
Type=3
StrRes=STR_BUY_ITEM_MONEY_CONFIRM
Cn_Res.ini
STR_BUY_ITEM_MONEY_CONFIRM=请确认是否使用%u两银子购买%s?
3 注意翻译以后的语序问题
你赢得了[xxxx]比赛,获得了[xxxx]金币,这样一句话,写在配置里可能是这样的
Cn_Res.ini
STR_RIDE_MATCH_RESULT=你赢得了%s,获得了%d金币。
然后代码中通常是
_snprintf(szResult, sizeof(szResult), g_objStrMgr.GetStr("STR_RIDE_MATCH_RESULT"), szName, nMoney);
然而某天,在英文版本上,这里可能引发了崩溃,于是调查了英文资源:
En_Res.ini
STR_RIDE_MATCH_RESULT=you get %d by win %s.
带入上面的代码,崩溃是可以预料的
不同语言的语序习惯不同,对翻译来说,可能英文版如上的语序是最自然的,而有经验的翻译可能才知道不同格式串的颠倒,尤其涉及到%s和其他类型的,尤其容易导致问题
之后我们为了避免类似这样的问题,做了以下的事情:
a. 提供一个字符串资源文件比对工具,输入为原字符串资源文件,和翻译后的字符串资源文件,输出格式串数量或者顺序不匹配的索引
例如,资源部门把Cn_Res.ini/En_Res.ini拖到工具中,这个工具会告诉他们,STR_RIDE_MATCH_RESULT这个索引配置的格式串,在两个文件中的格式串顺序不一样,这样就能把这个问题检查出来,并告知翻译换一种规则内的译法
b. 程序上,难以调查的原因在于用于组合提示的_snprintf系列没有类型检查,于是我们之后修改成更安全的带有类型检查的实现
std::string strResult = FORMAT(g_objStrMgr.GetStr("STR_RIDE_MATCH_RESULT")) << szName << nMoney;
FORMAT宏隐藏了实现类,这个实现类string_format::CFormatHelper,重载了 operator << ,支持各种基本类型,这样就有了类型检查
在遇到不合适的参数时,可以记出log帮助资源部门定位问题
4. 不应该有和语言相关的不合理假设
举几个实际遇到的问题
a) C/S消息,有时候出于一些原因,会把一些数据包含在字符串里面下发,然后由客户端之间解出
sscanf(szInfoRev, "%s%d", szName, &point);
咋看之下没有问题,但也许英文环境下,szName中包含空格呢?可以想见这样一来就难以得到正确的结果
b) 为了应付%s读取到空格截止的尴尬,有一种方法是,在组合字符串的时候,先把空格转为一种其他少用的符号,比如~,在读取后再转回来
但这个符号本身的选取是尴尬的,要确认好真的在所有语言版本都不会用到,而且如果是ANSI的字符串,还必须考虑双字节编码的文字的第二字节是否在这个范围内
曾经出现过对一个繁体版本的乱码杯具就是对字符串一股脑儿的替换引发:
std::replace(strMsg.begin(), strMsg.end(), '~', ' ');
c) 用中文表达比较简略的文字,也许使用其他国家文字就会翻译的特别长,比如英文就有各种冠词介词,在做UI相关的部分时需要注意考虑这些
5. 一些中文的特性其他语种可能没有的
a) 被称为方块字的汉字通常都是等宽字体,而其他文字是否等宽通常取决于采用的字体
b) 中文以字为单位分行,其他语种通常需要遇到空格才分行
说到如此多的语种和如此多的分布地区的玩家,就想起曾经的一个笑话,运营有次出资料片,用的口号是,“全球300多个国家和5000w玩家齐欢庆……”,姑且不论5000w玩家是怎么统计出来的,有一个玩家就写信反馈说,全球目前总共就200多个国家...无独有偶,金山的剑网3的口号是,“8000w玩家期待……
”,剑网3确实美轮美奂让人不能自拔,但人数寥寥,我和其他玩家都不厚道的觉得,在剑侠世界里,有一个玩家,他的角色名叫“八千万”,咳咳,下面进入正题
1 字符串资源不能硬编码在程序中
不应该在代码中出现形如这样的做法
MsgBox("是否确定删除当天聊天记录?");
这个很好理解,因为需要翻译,如果把提示文字写在程序内,不同语言版本就无法通过配置来实现。
所以应该是这样
MsgBox(g_objStrMgr.GetStr("STR_DEL_CHATLOG"));
Cn_Res.ini:
STR_DEL_CHATLOG=是否确定删除当天聊天记录?
En_Res.ini:
STR_DEL_CHATLOG=Are you sure you want to delete the chat log?
P.S. 即使不涉及到多语种的翻译问题,这些放在配置中也利于提高灵活度,也许哪天策划就像换一种表达方式。
2 尽量把字符串资源写在少数几个文件上
不建议出现形如这样的配置
Msgbox.ini:
[BUYITEM_MONEY_CONFIRM]
Type=3
StrRes=请确认是否使用%u两银子购买%s?
多语种带来的情况就是有各种字符串资源需要翻译,可以想见,在交由专业的翻译去处理之前,需要把配置中出现的字符串资源都完整的找到,若是各国文字分散在无数配置中,光是寻找这些文字就是繁杂和易错的事情。
因此我们的处理方法是,只把字符串资源放在少数几个文件上,其他文件若是想使用,都只使用其索引,所以应该是:
MsgBox.ini
[BUYITEM_MONEY_CONFIRM]
Type=3
StrRes=STR_BUY_ITEM_MONEY_CONFIRM
Cn_Res.ini
STR_BUY_ITEM_MONEY_CONFIRM=请确认是否使用%u两银子购买%s?
3 注意翻译以后的语序问题
你赢得了[xxxx]比赛,获得了[xxxx]金币,这样一句话,写在配置里可能是这样的
Cn_Res.ini
STR_RIDE_MATCH_RESULT=你赢得了%s,获得了%d金币。
然后代码中通常是
_snprintf(szResult, sizeof(szResult), g_objStrMgr.GetStr("STR_RIDE_MATCH_RESULT"), szName, nMoney);
然而某天,在英文版本上,这里可能引发了崩溃,于是调查了英文资源:
En_Res.ini
STR_RIDE_MATCH_RESULT=you get %d by win %s.
带入上面的代码,崩溃是可以预料的
不同语言的语序习惯不同,对翻译来说,可能英文版如上的语序是最自然的,而有经验的翻译可能才知道不同格式串的颠倒,尤其涉及到%s和其他类型的,尤其容易导致问题
之后我们为了避免类似这样的问题,做了以下的事情:
a. 提供一个字符串资源文件比对工具,输入为原字符串资源文件,和翻译后的字符串资源文件,输出格式串数量或者顺序不匹配的索引
例如,资源部门把Cn_Res.ini/En_Res.ini拖到工具中,这个工具会告诉他们,STR_RIDE_MATCH_RESULT这个索引配置的格式串,在两个文件中的格式串顺序不一样,这样就能把这个问题检查出来,并告知翻译换一种规则内的译法
b. 程序上,难以调查的原因在于用于组合提示的_snprintf系列没有类型检查,于是我们之后修改成更安全的带有类型检查的实现
std::string strResult = FORMAT(g_objStrMgr.GetStr("STR_RIDE_MATCH_RESULT")) << szName << nMoney;
FORMAT宏隐藏了实现类,这个实现类string_format::CFormatHelper,重载了 operator << ,支持各种基本类型,这样就有了类型检查
在遇到不合适的参数时,可以记出log帮助资源部门定位问题
4. 不应该有和语言相关的不合理假设
举几个实际遇到的问题
a) C/S消息,有时候出于一些原因,会把一些数据包含在字符串里面下发,然后由客户端之间解出
sscanf(szInfoRev, "%s%d", szName, &point);
咋看之下没有问题,但也许英文环境下,szName中包含空格呢?可以想见这样一来就难以得到正确的结果
b) 为了应付%s读取到空格截止的尴尬,有一种方法是,在组合字符串的时候,先把空格转为一种其他少用的符号,比如~,在读取后再转回来
但这个符号本身的选取是尴尬的,要确认好真的在所有语言版本都不会用到,而且如果是ANSI的字符串,还必须考虑双字节编码的文字的第二字节是否在这个范围内
曾经出现过对一个繁体版本的乱码杯具就是对字符串一股脑儿的替换引发:
std::replace(strMsg.begin(), strMsg.end(), '~', ' ');
c) 用中文表达比较简略的文字,也许使用其他国家文字就会翻译的特别长,比如英文就有各种冠词介词,在做UI相关的部分时需要注意考虑这些
5. 一些中文的特性其他语种可能没有的
a) 被称为方块字的汉字通常都是等宽字体,而其他文字是否等宽通常取决于采用的字体
b) 中文以字为单位分行,其他语种通常需要遇到空格才分行