聊聊什么是 i18n?JavaScript 中的 i18n 基本概念

为什么需要 i18n?

英语是世界上使用最广泛的语言,但只有七分之一的人会说英语。它是 3.79 亿人的第一(母语)语言,但有 9.17 亿人说中文普通话,4.6 亿人说西班牙语,3.41 亿人说印地语。

在互联网呈指数级增长的新兴市场,存在大量非英语用户。如果你的网站可以在全球范围内进行翻译,那么你的潜在目标市场可能会增加 700%!

i18n 全称 Internationalization,也就是国际化的意思,因为单词太长,所以中间的 18 个字母被缩写为 18,再加上开头和结尾的字母,就组成了 i18n。

JavaScript i18n API 可以帮助我们对网站进行多语言翻译,让它们可以轻松适应使用不同语言用户的需求。

在本文中,我将介绍 i18n API 提供的各种方法,以及如何在实际项目中实现 i18n 来覆盖更广泛、更国际化的用户。

i18n 其实很难

从上面的描述中,i18n 看起来很容易,但是当你尝试去做的时候,又发现它并不简单。

基于拉丁语的语言可能表面上相似。比如,请求姓名、电子邮件和日期的表单翻译如下:

英语 name email date
西班牙语 nombre email fecha
法语 nom e-mail date
德语 name email datum

Gettext 是一种在类 Unix 计算机操作系统上实现国际化和本地化程序的系统,它已经存在了几十年,而且这个库可以用在大多数编程语言中,在 nodejs 也可用。

在最简单的场景下,我们可以使用某种形式的标记来实现它。比如下面这段 HTML 模板:

<label for="name">{{ NAME }}</label>

当用户将英语设置为主要语言时,NAME 会被动态替换。

但是只是最简单的案例,实际情况中会有很多问题:

  1. 同一种语言可以有不同的变体。在西班牙使用的西班牙语与在南美洲使用的西班牙语不同。
  2. 一种语言中的单词在其他语言中可能会更长。例如,“电子邮件”在俄语中翻译为“электронное письмо”。
  3. 文本并不总是从左到右。有些语言是从右向左书写的,例如阿拉伯语、希伯来语、库尔德语和意第绪语。也有一些语言是可以从上到下书写的,比如中文、韩文、日文。

更糟糕的问题

除了上面提到的问题,还有更糟糕的情况。

当我们需要显示日期、时间、数字、货币或单位时,会出现进一步的复杂问题。

在英文中显示其日,通常是 12/03/24 这种格式。但是在其他语言中:

  • 使用 MDY 格式的美国居民会使用 3 December 2024。
  • 使用 DMY 格式的欧洲、南美和亚洲居民会使用 12 March 2024
  • 加拿大、中国、日本和匈牙利居民会使用 2012 年 3 月 24 日,他们选择了实用得多的 YMD 格式。

英文中的数字 1,000,在其他语言中:

  • 美国、英国、加拿大、中国和日本会以千为单位,表示为一千。
  • 西班牙、法国、德国和俄罗斯会表示为一个零点,其中数字的小数部分用逗号分隔。

JavaScript Intl API

其实很多人不知道,在 JavaScript 中存在 Intl 对象。

在大多数现代浏览器和运行时中都实现了 ECMAScript 国际化 API,并且兼容性还不错。甚至在 IE11 中也有很多比较有用的方法。

对于较旧的浏览器,还有一个 polyfill 可以用。

Intl API 有点不寻常。它为日期、时间、数字和列表分别提供了几个构造函数,它们会接收一个语言环境和一个包含配置参数的可选对象。

比如,指定美国英语的 DateTime 对象:

const dateFormatter = new Intl.DateTimeFormat('en-US'); 

这个对象可以被多次调用,传递一个 Date 实例,或者是一个 ES6 Temporal,如果它被支持的话。

format 是最常用也是最实用的方法。它的用法如下:

const valentinesDay = dateFormatter.format(new Date('2022-02-14')); 
// "2/14/2022" 
const starwarsDay = dateFormatter.format(new Date('2022-05-04')); 
// "5/4/2022" 

你也可以这么用:

const starwarsDay = new Intl
  .DateTimeFormat('en-US')
  .format(new Date('2022-05-04')); 

除了 format() 方法之外,某些对象还支持这些:

  • formatToParts():返回一个包含格式化字符串的对象数组,例如 { type: ‘weekday’, value: ‘Monday’ }
  • resolvedOptions(): 返回一个新对象,其属性反映所使用的语言环境和格式选项,例如 dateFormatter.resolvedOptions().locale。

定义语言环境

所有 Intl 对象都需要一个语言环境参数。它是一个字符串,它可以表示以下含义:

  • 语言子标签
  • 脚本子标签(可选)
  • 地区(或国家)子标签(可选)
  • 一个或多个变体子标签(可选)
  • 一个或多个 BCP 47 扩展序列(可选)
  • 私人使用的扩展序列(可选)

通常来说,只需要指定语言和地区就足够了。例如,”en-US”、”fr-FR”等。

除了使用字符串外,Intl.locale 对象还可用于构造语言环境,例如具有 12 小时时间格式的美国英语:

const us = new Intl.Locale('en', {
  region: 'US', hourCycle: 'h12', calendar: 'gregory'
});

这也可以在另一个 Intl 构造函数中使用。

new Intl
  .DateTimeFormat(us, { timeStyle: 'medium' })   
  .format( new Date('2022-05-04T13:00:00') ); 
// "1:00:00 PM" 

如果没定义区域设置,则使用设备的当前语言和区域设置。

new Intl
  .DateTimeFormat()
  .format( new Date('2022-05-04') ); 

I18n 的 API 其实比较多,在这里就不再多赘述,更加详细的内容可以参考 MDN 文档。