Skip to Content
Author's profile photo Cong Wang

你的项目刚刚启动?是时候考虑Globalization了!

我身边的朋友中追求个性的人很多,但我最佩服的是我表弟小周。他的人生处处都把“酷”作为唯一的行事准则,甚至反映在了高考报考这种人生大事上。

“我要学最酷的专业。”

深知他个性的我明白这事挡不住,只能顺着。于是给他推荐长沙民政职业技术学院的殡仪学院,里面的专业随便说一个出来都是让人目瞪口呆。何止是酷,简直酷到口吐白沫!

他爸妈听了我的建议恨不得打死我,只有小周眼放金光,作势就要把志愿填了且不服从调剂。全家人一致决定剥夺我提出建议的权利,并通过各种威逼利诱、恐吓威胁终于把小周的殡仪梦想扼杀于摇篮之中。

可强权暴政镇压不住一颗红彤彤的向往个性的心,最终小周还是报考了一个极其冷门的专业——北外的豪萨语专业。

“先不说这个专业多冷门,就这个名字,说出来,酷不酷?”

“酷酷酷!”我连声附和。

可后来我才知道这真的是一门极其冷门极其古老的语言,甚至古老到了仍在大量使用象声词的程度。比如鸭子就叫“啊呱呱”(agwagwa),两只鸭子就叫“啊呱呱啊呱呱”。感叹这孩子真有个性之余,我也在内心暗暗替小周祈祷,毕业千万别去了当地的养鸭场工作。实在无法想象他用豪萨语骄傲地给客户介绍“我们公司年产50万只鸭子”会是怎样磅礴的景象。

今天的文章跟鸭子无关,我只想谈一谈语言。

据统计世界上已存在的语言多达5000多种,即便不考虑豪萨语这种冷门语言,被广泛使用的语言也有几十种。把温暖(chǎn pǐn)洒满人间是每一个程序员的梦想,可你又不能指望每一个客户都使用英语,这时便产生了软件Globalization这一概念。

作为SAP九大Product Standards之一的Globalization不单单是指文本的翻译,还包含支持多货币、支持各国相关法律、支持不同国家业务流程等要求。为了满足这一系列的标准,不但需要开发人员有过硬的业务知识,有时还需要花大把精力实现一些枯燥复杂的功能(比如为满足阿拉伯语,需要UI支持从右向左的排布)。但是在产品的设计和开发初期,如果开发人员愿意花费极少的精力去留心一些方面,那么就可以大大降低未来出现Globalization问题的概率。下面我们就列举几个例子(感谢Ray Ding和Vicky Chen同学对文中部分内容的研究)。

爱ta,就给ta足够的空间

在UX设计UI布局的初期,就应该将Globalization纳入考虑,而这时最容易引发的问题就是空间不够。一套布局在英文环境看上去美轮美奂,并不代表在其他语言环境中也有很好的用户体验,有时甚至会影响到可用性。比方说下面这个“添加”按钮,在英文环境下看起来还不错,但是切换到了德语就会让人完全看不懂。

大多数情况下,一个英文单词的长度是短于其他语言相同意义的单词。而我们大多数的产品都是以英语作为第一版语言进行开发的。当用户已经习惯了英语的布局之后,这时再因为引入其他语言而大幅度更改页面布局,将会是非常痛苦的事情。所以,请永远记得给文本足够的空间。

可是多大才算足够呢?幸运的是我们有一个工具叫做Text Space Calculator。它能够根据你提供的英文文本给你推荐一个能够满足90%以上情况的长度。感兴趣的同学可以看一下它的说明文档,也许你会发现它背后的逻辑非常简单粗暴,但是的确能够帮助到我们。同时它还有一个Bridge的版本,你可以在Bridge中搜索“UI text space calculator”然后把它添加进你的应用。

另外一个非常实用的工具就是Pseudo,它的基础就是上面的Text Space Calculator。你可以通过它快速的发现潜在的文本截断问题,同时它还能够帮助你发现UI上的Hardcode以及拼接文本。

拼接文本是把双刃剑!

拼接文本是指在i18n文件中将某些固定文本以参数的形式传入,并拼接在一起的方式。这样做的好处是可以在运行时动态的生成一些文本。类似的代码在我们的产品中屡见不鲜,可当我们开始考虑多语言的时候,突然间一切可能都玩不转了。

也许……你hardcode了标点符号

一天,你的PO告诉你“我们把所有有效的产品都展示给用户吧!这真是个伟大的idea!”于是,你可能就写下了这样的代码:

function getActiveProductsString(activeProducts) {
    // i18nActiveProducts = 'Your active products:'
    var activeProductsString = this.getResourceBundle().getText('i18nActiveProducts');

    activeProducts.forEach(product => activeProductsString += '"' + product + '" ');

    return activeProductsString;
}

一切看起来这么完美,多语言的情况也加以考虑了,然后你在英文环境中测试了一下,并得到了想要的结果:Your active products: “apple” “orange” “banana”。但实际上你忽略了双引号,在不同的语言中双引号看起来有可能是不同的。与之类似的还有括号,句号等。虽然我并不认为这是一个致命的问题,但是从Globalization的角度来讲它确实不是一个完美的解决方案。

"Product"     // English quotaion marks
“产品”        // Chinese quotaion marks
„Produkt“     // German quotaion marks

即便你的客户是冯绍峰,也不要随便把人家的名字倒过来写

在GitHub中随便翻一翻,就能看到很多诸如firstName + ” ” + lastName这样的代码。作为中国人的我们知道这样的写法是十分片面的,但是这样的问题经常会被我们忽视掉。类似的问题还有日期,永远不要让你的代码中出现这样的东西:month + “/” + day + “/” + year。

小心使用大小写转换

在很多的应用场景下我们会使用到大小写转换,比如字符串对比、字符串排序等,偶尔也会用在拼接文本中。而不恰当的使用大小写转换则会引起潜在的Globalization问题。下面我们通过一个例子加以说明。

假设我们现在有一个客户维护页面,用户可以创建或更改客户的基本信息,比如姓名,电话号码,邮箱等。页面上会对每个字段的格式加以校验,当校验不通过时则会对弹出类似这样的警告:“The field email does not match the format.”

我们当然可以给每一个字段都维护这样一条极其类似的警告,但这时“聪明”的我们发现在i18n文件中我们已经维护了各个字段的标签,所以我们是不是可以只维护一条警告,然后将这些标签通过参数传进去呢?想一想还真是有点小激动呢,于是我们说干就干。

# I18n File

#XFLD: The label for the filed name:
i18nNameLabel = Name
#XFLD: The label for the filed phone:
i18nNameLabel = Phone
#XFLD: The label for the filed email:
i18nNameLabel = Email
......
#XMSG: The error message that a field does not match the format. {0}: field name
i18nWrongFormatErrorMsg = The field {0} does not match the format.
function getErrorMsg(label) {
    return this.getResourceBundle().getText('i18nWrongFormatErrorMsg', [label.toLowerCase()]);
}

作为严谨的工程师,我们当然考虑到了把Label传入Error Message的时候要转换成小写,这样才符合英语的语法!可是却不知道这样却为日后引入Globalization埋下了隐患。并不是所有的语言都与英语有着相同的大小写规范,比方说在德语中任何名词在任何情况下都要保持首字母的大写,上面这段代码在德语环境下无疑成了画蛇添足。

语言的复杂性,远远不止如此

还是从一个案例开始,假设PO叫我们实现一个购物车,当用户之前添加的某种属性的某种商品被下架了之后,提醒用户商品不可用。

有了上面的种种教训,我们终于学会了要把文本中的标点以及顺序信息统统写入i18n文件,也不能随意更换大小写。所以我们想出了这样的提示信息:

#XMSG: The message that product with specified property is not available. {0}: property, (1): product name
i18nProductNotAvailableMsg = Sorry, the {0} {1} is not available.

这下总没问题了吧?翻译人员可以根据不同的语言调整参数{0}和{1}的位置,我们也不会主动修改参数的大小写。在英文环境中测试一下,“Sorry, the blue pen is not available.”,完美。

但是后面我们还是再次栽在了Globalization上面,因为不是所有的语言都像英语这么简单。比方说这种简单粗暴的拼接在德语中是完全行不通的,感兴趣的同学可以通过这个wiki了解一下。如果你觉得wiki太难读懂了,那就对比一下下面四句话,相信你会发现定冠词和形容词居然是一直在变的。

所以永远不要低估语言的复杂性,再回想一下“啊呱呱啊呱呱”,你真的无法想象其他的语言到底是什么样的逻辑。所以在进行文本拼接的时候一定要慎重。

不要过度限制用户的输入

有些时候我们习惯对于用户的输入进行一定的限制,以保证数据的质量和一致性。但是限制要有度,比方说如果对于“名字”这个字段限制为仅接受大小写英文字母以及一些标点符号,那这样的限制在未来就很可能会出现问题。

你真的了解搜索和排序吗?

搜索和排序是两个极其常用的数据处理操作,而且往往都是在一个应用架构的设计初级就被纳入考虑的。所以,如果你能够在架构设计阶段就意识到一些潜在的问题,那将来很有可能你就不会收到那么多Globalization的ticket。

搜索

关于字符串的搜索,我们比较常用的是“equals”和“contains”这两种策略。但其实这两种策略都应该基于不同的语言做出不同的反馈。举一个例子,德语中的“ö”同时可以表现为“oe”,所以如果用户想要搜索德国球星厄齐尔的时候,无论用户的输入是“Özil”还是“Oezil”,我们的搜索都应该命中到数据库中的同一条记录。幸运的是大多数数据库都支持这样的行为,只不过我们要记得去配置。

排序

“Apple”应当被排在“Banana”之前,因为“A”比“B”靠前。那如果我要将“Apple”、“Banana”、“安全”、“崩溃”四个文本在数据库中排序呢?你觉得正确的顺序是什么?我认为这个跟用户的使用语言是相关的。对于一个英文用户你把“安全”放在“Apple”和“Banana”中间是显然不合理的,但是对于某些中文用户他们又会觉得从拼音的角度就应该把“安全”放在那里啊。所以在数据库设计初期,应当尽量考虑到对于多语言排序的支持。

好的注释是i18n文件的灵魂

使用注释是一个良好的编程习惯,尤其是在i8n文件当中。请永远记得,当一个翻译人员在翻译你的i18n文件的时候,阅读注释是他获取文本含义的唯一方式。一个不好的注释往往会对翻译人员造成困扰。比如如果没有注释,“Order”这个词可能有很多种翻译:

  • 订单
  • 顺序
  • 命令

所以,在维护每一个i18n条目的时候,都请认真仔细地去写一条注释,这样会帮你避免未来的许多麻烦。在Product Standards中对于i18n的注释有如下建议。

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.