区域设置 (Locale)

语言、地区和格式设置的组合,用 ja-JP、en-US 等标识符表示。

区域设置 (locale) 是表示语言、地区和格式设置组合的标识符。像 ja-JP (日语、日本)、en-US (英语、美国)、zh-CN (中文、中国) 这样把语言代码与地区代码连起来的形式最具代表性,语言代码来自 ISO 639,地区代码来自 ISO 3166-1。由于同一语言在不同地区的写法规则也会不同 (例如 en-US 和 en-GB 的日期格式与拼写不同),因此不只是语言代码,地区代码也很重要。

不过标识符的结构并不限于 "语言 + 地区" 这 2 个要素。作为 BCP 47 标准化的语言标签,会把语言、书写系统 (script)、地区、变体 (variant) 的子标签按这个顺序排列,还可以在后面附加历法、时刻写法这类细致指定的扩展。书写系统的子标签不可缺少的代表例就是中文,简体字写作 zh-Hans,繁体字写作 zh-Hant (书写系统的代码来自 ISO 15924)。像使用简体字的新加坡、使用繁体字的香港那样,地区与书写系统并不是一对一对应的,所以只用 zh-CN 与 zh-TW 二选一来搭建就会有遗漏。日语也有 ja-Jpan-JP 这种带书写系统的形式,但在书写系统事实上只定于一种的语言里,通常会省略。

区域设置影响文本显示的方方面面:日期格式 (2025/01/15 vs 01/15/2025 vs 15.01.2025)、数字格式 (1,000.50 vs 1.000,50)、货币符号 (¥ vs $ vs €)、排序顺序和文本方向 (LTR/RTL) 等。例如,在德语区域设置 (de-DE) 中,逗号用作小数点,句号用作千位分隔符,因此 "1.000" 表示一千。忽略这些差异会在金额和数量显示中导致严重的 bug。

在 JavaScript 中,Intl 对象提供了区域设置感知的格式化功能。用 Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }) 可以得到 "¥1,000" 这样的日式货币格式。这里所加的日元符号是全角的 ¥ (U+FFE5),与把同样的 JPY 用 en-US 整形时使用的半角 ¥ (U+00A5) 是不同的字符。由于外观相近而不易察觉,但在显示宽度的计算上,以及把全角数作 2 个字符的计数方式里,就会以差异的形式显现出来。若用字节数来管理上限,全角日元符号在 UTF-8 下是 3 字节、半角是 2 字节这一差别也会起作用。此外还准备了 Intl.DateTimeFormat、Intl.Collator、Intl.PluralRules 等,可以实现随区域设置而变的日期显示、字符串排序和复数形式处理。

多语言网站通常通过检测用户浏览器的区域设置 (navigator.language) 来显示相应的语言版本。但由于浏览器的区域设置可能与用户的期望语言不一致,因此应始终提供语言切换的 UI。与区域设置相关的 Web 标准涵盖 HTML 的 lang 属性、hreflang 标签和 Accept-Language 头等。

一个常见的误解是将区域设置与字符编码混淆。区域设置定义的是显示格式的规则,与字符的编码方式 (UTF-8、Shift_JIS 等) 是不同的概念。而且即使切换区域设置,保存着的文本数据本身也不会被改写,改变的是那份数据的显示方法。不过若把加工字符串的处理也算进来,就存在例外。大小写的转换依赖区域设置,给 JavaScript 的 toLocaleUpperCase 传 "istanbul",在默认区域设置下会变成 "ISTANBUL",而指定土耳其语 (tr) 时则会使用带点的大写 İ (U+0130),变成 "İSTANBUL"。这是因为土耳其语和阿塞拜疆语中 i 与 I 的对应关系与其他语言不同,在要比较转换后的字符串、或把它用作检索键的代码里,这会成为有实害的 bug 源。做忽略大小写的比较时,安全的做法是避开依赖区域设置的转换而使用不依赖区域设置的 toLowerCase,或者用 Intl.Collator 的 sensitivity 指定来比较。

在字符计数方面,不同区域设置表达相同信息所需的字符数差异很大。例如把 2025 年 1 月 15 日这同一个日期用 Intl.DateTimeFormat 的长格式来整形,日语 (ja-JP) 是 2025年1月15日 的 10 个字符,英语 (en-US) 是 January 15, 2025 的 16 个字符,德语 (de-DE) 是 15. Januar 2025 的 15 个字符。日语最短,是因为年、月、日分别由 1 个汉字承担,而且不放入作为分隔的空白。不过短只是字符数上的事,因为有全角的部分,显示宽度并不会按字符数的比例那样缩小。另外在日语的文中,为了易读也有在数字前后放入半角空白的写法,那种情况下同一个日期就成了 15 个字符。这正是一个好例子,说明所数的对象是程序的输出还是人写的文面,结果会随之改变。在为多语言网站设计有字符数限制的 UI 元素 (按钮、标签等) 时,需要考虑各区域设置之间字符数的差异。

分享这篇文章