本文做者:张卓html
原创声明:本文为阅文前端团队 YFE 成员出品,请尊重原创,转载请联系公众号 ( id: yuewen_YFE ) 获取受权,并注明做者、出处和连接。前端
Webnovel(起点海外项目)在今年开始了国际化的脚步,在刚刚上线的版本当中加入了对印尼、马来西亚和菲律宾语言及内容的支持。在作国际化的过程当中,咱们遇到了很多问题,这篇文章就重点分享一下这些问题以及它们的解决方案。react
在开始以前,咱们先明确两个概念: 国际化和本地化。国际化(i18n) 是一个设计和准备应用程序的过程,使其能用于不一样的语言。 而 本地化(l10n) 是一个把国际化的应用针对部分区域翻译成特定语言的过程。这篇文章的标题是“国际化”实践,因此重点讲的也是如何准备应用程序让其可以进行本地化。git
将一个网站进行多语言化看似是一件简单的事情,在大多数状况下的确是的,只是将一个字符串映射到另外一个字符串的过程,可是起点海外做为一款有追求的产品,咱们固然不会采用这么简单的方式。要把国际化这件事情作好,那就会遇到许多问题,例如单复数、富文本等。下面的部分会介绍一些常见的多语言问题以及一些通用的解决方案:github
在咱们中文当中,没有单复数的概念,“一小时”和“两小时”中的“小时”是同样的,但在其它许多语言当中,不一样数量的形式,有着不一样的规则,英文中有单数和复数两种规则,如“1 hour” 和 ”2 hours”,而有的语言可能有更多。在一些语言中,基数和序数的规则可能也是不一样的,如英文的“1st”,“2nd”,“3rd”,“4th”。web
Unicode 标准已经将世界上绝大部分语言的单复数规则进行了归类,总结下来最多只有 6 种规则,分别是:浏览器
如英文中,基数只有 one (1 hour)和 other (0 hours,2.5 hours)两种规则,序数则有 one(1st,11st…),two(2nd),few(3rd),和 other(4th)四种规则。bash
规则是有了,咱们如何在实际的应用中使用呢?一种比较通用的方式是使用 ICU MessageFormat。ICU MessageFormat 是一种语法格式,经过 {key}
的形式来定义变量;经过一些关键词来帮助咱们更方便的处理不一样语言中的一些复杂状况,例如使用 {key, plural, matches}
来处理单复数规则:前端框架
You have {itemCount, plural,
=0 {no items}
one {1 item}
other {{itemCount} items}
}.
复制代码
ICU MessageFormat 不只能够方便单复数状况的使用,一样能够用于日期、性别以及其它复杂状况,而且已经拥有了很是普遍的使用,不只绝大部分 JavaScript 的多语言库使用了它,在其余语言例如 Java 和 PHP 中也一样内置了这套规则。框架
日期、数字以及货币 不一样语言、国家和地区在表示日期和数字时也会有一些差别,如美国习惯“月/日/年”的形式来表示日期,而一样使用英语的英国却更习惯“日/月/年”。而货币就更不用说了,符号首先不一样,如人民币和日元的 “¥” 以及欧元的 “€”,而且它们放置的位置可能也不相同,日元习惯将货币符号放在数字前面,而欧元偏偏相反。
做为开发者,咱们几乎不可能去一一了解这些差别,好在 ECMAScript Internationalization API 提供了4个方法帮助咱们解决上面的问题:
Intl.Collator
Intl.DateTimeFormat
Intl.NumberFormat
Intl.PluralRules
Intl.Collator
并不经常使用,主要是用于语言敏感字符串比较的;Intl.DateTimeFormat
能够帮助咱们根据不一样地区语言格式化时间和日期;Intl.NumberFormat
则用来格式化数字和货币;而Intl.PluralRules
用于判断单复数,能够告诉咱们指定数量在某种语言下的分类(如 “one”,“other”)。其中前三个 API 已经相对稳定,浏览器也有较好的支持,DateTimeFormat
和 NumberFormat
也有 polyfill 来让咱们有更普遍的使用范围,如 Node 和 React Native 环境中;PluralRules
还处在草案阶段,浏览器支持性也比较差,不建议直接使用。
另外,咱们看到浏览器尤为是 Chrome 对国际化的支持正在逐渐加大,除了上面已经进入标准的 4 个 API 外,Chrome 分别在 71 和 72 版本支持了 Intl.RelativeTimeFormat
和 Intl.ListFormat
两个 API,其中 Intl.RelativeTimeFormat
用来格式化相对时间,相似 Moment.js 中的功能,例如:
const rtf = new Intl.RelativeTimeFormat('en');
rtf.format(3.14, 'second');
// → 'in 3.14 seconds'
rtf.format(-15, 'minute');
// → '15 minutes ago'
复制代码
而 Intl.ListFormat
用来格式化列表,例如:
const lf = new Intl.ListFormat('zh');
lf.format(['永锋', '新宇']);
// → '永锋和新宇'
复制代码
这些 API 都远比上面展现的例子强大,具体的用法能够参考 MDN 和 Google Developers 官网,也相信从此在 Web 上进行国际化会愈来愈容易。
咱们知道,不管是中文仍是其它语言,一个字/词语在不一样场景下可能会有不一样的含义,“About” 若是看成一个页面的标题,表达的多是“关于/简介”的含义,但放在一句话中就多是“大约”的意思了。
对于这个问题,咱们能够经过提供给译者更多的信息来解决这个问题。可经过文字描述帮助译者来了解语境,发送截图等方式来确保译者可以准确的翻译。
谁来翻译,看起来彷佛不是一个问题,但它决定着咱们整个翻译的流程,咱们须要在进行多语言时尽早肯定。通常来讲,多是由专业翻译或者用户/志愿者来翻译,这两种方式各有优劣:
除了上面提到的多语言问题,咱们在国际化的过程当中可能还要面临多方合做、分国际/地区运营等其它类型的诸多问题,这里篇幅有限,就不一一讨论了。
在作多语言的 Web 应用时,一种比较通用的方式是:将不一样语言的字符放在不一样的 JSON 或其它形式的文件当中,而后获取用户倾向的语言,加载对应语言的字符文件,而后在应用中展现便可。
在这种方式下,须要解决的最大问题是一些比较复杂的状况,例如上文提到的单复数。在上文种咱们也提到了能够经过 ICU MessageFormat 来解决这个问题,具体的作法是将 ICU MessageFormat 解析成 AST 而后转化为函数,在应用中传递对应参数到对应函数便可。
上面的方式还有一些细节值得讨论,篇幅缘由这里就不讲了,接下来咱们看一下相对比较成熟的基于主流框架的 i18n 解决方案。
React Intl 是雅虎开源的基于 React 的国际化解决方案。遵循 BCP 47 和 Unicode CLDR 标准,支持 ICU Message Format,并支持日期、时间和数字等的国际化。
React Intl 经过组件的形式实现多语言:
<FormattedMessage
id="welcome"
defaultMessage={`Hello {name}, you have {unreadCount, number} {unreadCount, plural,
one {message}
other {messages}
}`}
values={{name: <b>{name}</b>, unreadCount}}
/>
复制代码
更具体的使用方式能够参考它的 Github:github.com/yahoo/react…
Angular 应该是目前主流前端框架中惟一自带 i18n 解决方案的框架,它一样遵循 BCP 47 和 Unicode CLDR 标准,支持 ICU Message Format。
与通常的 i18n 方案不一样,Angular 不须要提早准备一份 JSON 或其它形式的多语言映射表,只需使用 i18n 属性来标记须要进行多语言的文本便可,例如:
<h1 i18n>Hello, webnovel</h1>
复制代码
经过执行 ng xi18n
命令,Angular 会自动提取全部含义 i18n
属性的字符,并生成一份 xlf 文件(xlf 是一种基于XML的交换格式,旨在标准化本地化过程当中在工具之间传递可本地化数据的方式),咱们能够直接将 xlf 文件发送给译者,译者经过一些专门的软件进行翻译而后将这份文件返回给咱们。最后,咱们经过预编译或者即时编译的方式将多语言内容注入到应用当中便可完成所有工做。
Angular 的方案很是完善,对咱们可能不太注意的一些地方也作了支持,如咱们想对 img 标签的 title 属性进行多语言的话, 只需再加上 i18n-title 的属性便可,例如: <img [src]="logo" i18n-title title="Webnovel logo" />
。
对比了上述的几种方案,咱们认为目前 Angular 的方案是最为理想。它相对完善;没有很强的入侵性;得益于它能自动提取所需的字符串并生成 ID,使用它的便利程度也优于其余框架;它的多语言支持预编译和即时编译,在性能和灵活度上都有了保证。
但比较遗憾的是 Webnovel 目前没有使用 Angular,咱们的移动站点和 App 分别基于 React 和 React Native 构建,因为已有的基于 react 的多语言库 react-intl 不能很好的知足咱们的须要,咱们决定本身构建 i18n 的基础库,它须要作到:
在有了基本目标之后,咱们开发了新的多语言库 react-i18n:
核心库,经过 React 的 Context API,提供 withI18n 的高阶组件以及 Message 组件来帮助应用进行多语言。其中 withI18n 将 i18n 信息传递给组件的 props,而 Message 组件相似于 React Intl 中的 FormattedMessage 组件,经过传递对应字符串的 id 和参数来直接渲染出字符。
命令行工具,主要提供预编译字符模版和一些辅助功能,如
HELLO_WORLD_6f5902ac237024bdd0c176cb93063dc4
的 ID,既保证了在使用编辑器是可以经过自动补全方便输入,也保证了惟一性。react-i18n 库已经知足了咱们的基本要求,而且已经在 Webnovel 的移动站点和 App 中运行了一段时间。因为时间仓促, react-i18n 库还不够健全,咱们暂时还不能将其开源。接下来,咱们会将这个库进行完善,尽早回馈给开源社区。
遵循已有标准对于国际化来讲很是重要。咱们在写一个应用时,几乎没法避免与第三方合做,例如支付。在合做的过程当中,咱们若是使用相同的标准,那沟通、调试的成本将大大下降。关于国际化的标准很是多,有些也比较复杂,须要咱们耐心认真的阅读。值得注意的是,标准并非一直不变的,例如上文中提到的 BCP 47 标准当中的地区标示,旧版中印尼的代码是 in
,而新版中改成了 id
,通常咱们应该遵循更新的标准。
对于国际化这样已经存在很是多年的问题,必定是有成熟方案能够借鉴的,在开始以前,去尽量多的了解已有方案,会让咱们少走许多弯路。
本文重点讲述了基于 Web 技术的一些国际化方案以及在 Webnovel 中的实际应用。国际化一直以来都是一个很是困难的问题,因此更须要咱们长远的去思考;国际化也不只仅是多语言,从排版布局到文化差别,都是咱们须要考虑的,Webnovel 的国际化才刚刚开始。