Webnovel 国际化实践
栏目: JavaScript · 发布时间: 5年前
内容简介:Webnovel(起点海外项目)在今年开始了国际化的脚步,在刚刚上线的版本当中加入了对印尼、马来西亚和菲律宾语言及内容的支持。在做国际化的过程中,我们遇到了不少问题,这篇文章就重点分享一下这些问题以及它们的解决方案。在开始之前,我们先明确两个概念: 国际化和本地化。将一个网站进行多语言化看似是一件简单的事情,在大多数情况下的确是的,只是将一个字符串映射到另一个字符串的过程,但是起点海外作为一款有追求的产品,我们当然不会采用这么简单的方式。要把国际化这件事情做好,那就会遇到许多问题,例如单复数、富文本等。下
Webnovel(起点海外项目)在今年开始了国际化的脚步,在刚刚上线的版本当中加入了对印尼、马来西亚和菲律宾语言及内容的支持。在做国际化的过程中,我们遇到了不少问题,这篇文章就重点分享一下这些问题以及它们的解决方案。
国际化和本地化
在开始之前,我们先明确两个概念: 国际化和本地化。 国际化(i18n) 是一个设计和准备应用程序的过程,使其能用于不同的语言。 而 本地化(l10n) 是一个把国际化的应用针对部分区域翻译成特定语言的过程。这篇文章的标题是“国际化”实践,所以重点讲的也是如何准备应用程序让其能够进行本地化。
需要解决的问题
将一个网站进行多语言化看似是一件简单的事情,在大多数情况下的确是的,只是将一个字符串映射到另一个字符串的过程,但是起点海外作为一款有追求的产品,我们当然不会采用这么简单的方式。要把国际化这件事情做好,那就会遇到许多问题,例如单复数、富文本等。下面的部分会介绍一些常见的多语言问题以及一些通用的解决方案:
单复数问题
在我们中文当中,没有单复数的概念,“一小时”和“两小时”中的“小时”是一样的,但在其它许多语言当中,不同数量的形式,有着不同的规则,英文中有单数和复数两种规则,如“1 hour” 和 ”2 hours”,而有的语言可能有更多。在一些语言中,基数和序数的规则可能也是不同的,如英文的“1st”,“2nd”,“3rd”,“4th”。
Unicode 标准已经将世界上绝大部分语言的单复数规则进行了归类,总结下来最多只有 6 种规则,分别是:
- zero
- one
- two
- few
- many (如果有一个单独的分类的话,也用于分数)
- other (必须,如果语言只有一个单一形式也使用)
如英文中,基数只有 one (1 hour)和 other (0 hours,2.5 hours)两种规则,序数则有 one(1st,11st…),two(2nd),few(3rd),和 other(4th)四种规则。
规则是有了,我们如何在实际的应用中使用呢?一种比较通用的方式是使用 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” 如果当作一个页面的标题,表达的可能是“关于/简介”的含义,但放在一句话中就可能是“大约”的意思了。
对于这个问题,我们可以通过提供给译者更多的信息来解决这个问题。可通过文字描述帮助译者来了解语境,发送截图等方式来确保译者能够准确的翻译。
谁来翻译
谁来翻译,看起来似乎不是一个问题,但它决定着我们整个翻译的流程,我们需要在进行多语言时尽早确定。一般来说,可能是由专业翻译或者用户/志愿者来翻译,这两种方式各有优劣:
- 由专业翻译进行翻译 。专业翻译基本可以保证较高的质量和效率,主要问题是成本较高。
- 由用户/志愿者进行翻译 。很多开源项目都采用来这种方式。Twitter 也采用了这种方式,专门搭建了一个翻译平台来让用户更好的进行翻译,在短短一年的时间内有超过 40 万的志愿者帮助其进行了翻译,上线了 21 种语言。这种方式成本低,并且由于可能参与的人数众多,通过多人 review 等方式可以保证翻译质量;唯一需要担心的是翻译时间不可控。而我们一般也不需要自己搭建一套平台,可以选择已有的成熟的商业平台如crowdin.com 或者开源的平台如 Mozilla 的 pontoon。
除了上面提到的多语言问题,我们在国际化的过程中可能还要面临多方合作、分国际/地区运营等其它类型的诸多问题,这里篇幅有限,就不一一讨论了。
解决方案
在做多语言的 Web 应用时,一种比较通用的方式是:将不同语言的字符放在不同的 JSON 或其它形式的文件当中,然后获取用户倾向的语言,加载对应语言的字符文件,然后在应用中展示即可。
在这种方式下,需要解决的最大问题是一些比较复杂的情况,例如上文提到的单复数。在上文种我们也提到了可以通过 ICU MessageFormat 来解决这个问题,具体的做法是将 ICU MessageFormat 解析成 AST 然后转化为函数,在应用中传递对应参数到对应函数即可。
上面的方式还有一些细节值得讨论,篇幅原因这里就不讲了,接下来我们看一下相对比较成熟的基于主流框架的 i18n 解决方案。
React Intl
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 的方案
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" />
。
Webnovel 的方案
对比了上述的几种方案,我们认为目前 Angular 的方案是最为理想。它相对完善;没有很强的入侵性;得益于它能自动提取所需的字符串并生成 ID,使用它的便利程度也优于其他框架;它的多语言支持预编译和即时编译,在性能和灵活度上都有了保证。
但比较遗憾的是 Webnovel 目前没有使用 Angular,我们的移动站点和 App 分别基于 React 和 React Native 构建,由于已有的基于 react 的多语言库 react-intl 不能很好的满足我们的需要,我们决定自己构建 i18n 的基础库,它需要做到:
- 能够在常用 JavaScript 环境中使用,包括浏览器、 Node(服务端渲染)和 React Native
- 遵循国际标准,支持 ICU MessageFormat
- 支持字符模版的预编译和即时编译
- 高性能,支持动态加载语言
- 支持语言/字符串的 fallback
- 使用成本低
- 插件化
在有了基本目标以后,我们开发了新的多语言库 react-i18n:
@react-i18n/core
核心库,通过 React 的 Context API,提供 withI18n 的高阶组件以及 Message 组件来帮助应用进行多语言。其中 withI18n 将 i18n 信息传递给组件的 props,而 Message 组件类似于 React Intl 中的 FormattedMessage 组件,通过传递对应字符串的 id 和参数来直接渲染出字符。
@react-i18n/cli
命令行工具,主要提供 预编译字符模版 和一些辅助功能,如
HELLO_WORLD_6f5902ac237024bdd0c176cb93063dc4
react-i18n 库已经满足了我们的基本要求,并且已经在 Webnovel 的移动站点和 App 中运行了一段时间。由于时间仓促, react-i18n 库还不够健全,我们暂时还不能将其开源。接下来,我们会将这个库进行完善,尽早回馈给开源社区。
其他需要注意的问题
遵循已有标准
遵循已有标准对于国际化来说非常重要。我们在写一个应用时,几乎无法避免与第三方合作,例如支付。在合作的过程中,我们如果使用相同的标准,那沟通、调试的成本将大大降低。关于国际化的标准非常多,有些也比较复杂,需要我们耐心认真的阅读。值得注意的是,标准并不是一直不变的,例如上文中提到的 BCP 47 标准当中的地区标示,旧版中印尼的代码是 in
,而新版中改为了 id
,一般我们应该遵循更新的标准。
更早、更多的了解成熟方案
对于国际化这样已经存在非常多年的问题,一定是有成熟方案可以借鉴的,在开始之前,去尽可能多的了解已有方案,会让我们少走许多弯路。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。