Kodokon kodokon.com

国际化:lang、dir、charset 与 Unicode 陷阱

明智地使用 lang、dir 和编码声明,并击败 Unicode 规范化与方向性方面的陷阱。

9 分钟 · 3 题

在 Kodokon 中打开本课

lang 属性远不止是元数据:它驱动屏幕阅读器的语音、连字符断字(hyphens: auto 没有它就无法工作)、CJK 字形的选择(同一个表意文字在日语和中文中绘制方式不同)、<q> 生成的引号以及 CSS 的 :lang() 选择器。它是可继承的:在 <html> 上用一个 BCP 47 标签声明它(enen-GBja),并在每次语言切换处进行局部覆盖。

HTML
<html lang="en">
  <p>She said: <q>hello</q>.</p>
  <p lang="fr">Elle a dit : <q>bonjour</q>.</p>
</html>
q 的引号会跟随语言:“hello”,然后是 « bonjour »。

dir=rtl 会翻转默认对齐方式,以及方向性中性的字符的视觉顺序。对于无法预测的用户内容,dir=auto 会根据第一个具有强方向性的字符来选择方向。<bdi> 元素会把一个片段从双向算法中隔离出来:没有它,一个后面跟着“:12 分”的阿拉伯语用户名,其标点符号会被重新排序成一堆乱码。

HTML
<p dir="auto">مرحبا - inferred direction: RTL</p>
<ul>
  <li><bdi>مستخدم</bdi>: 12 points</li>
  <li><bdi>Alice</bdi>: 8 points</li>
</ul>
bdi 保护布局不受双向用户名的影响。

<meta charset=utf-8> 声明必须出现在文档的前 1024 个字节以内:浏览器会在选定编码之前,在这个窗口范围内运行一次预扫描。如果既没有声明也没有 HTTP 头,默认编码取决于用户的区域设置(在西欧常常是 windows-1252)—— 绝不会是 UTF-8。然而,文件开头的 BOM 会覆盖其他一切,包括 HTTP 头。

JAVASCRIPT
const composed = "\u00e9";
const decomposed = "e\u0301";
console.log(composed === decomposed); // false
const nfc = decomposed.normalize("NFC");
console.log(composed === nfc);        // true
两个视觉上相同的“é”,但在内存中却不同。

macOS 以分解形式(NFD)存储某些文件名:因此,来自一次上传的“é”可能与用键盘输入的那个不同。系统性地把所有用户输入在存储或比较之前规范化为 NFC。最后,.length 计的是 UTF-16 码元(code unit),而不是感知到的字符:一个家庭 emoji 组合了七个以上的码元;用 Intl.Segmenter 来计算真正的字素(grapheme)。

知识检测

确认你已牢记本课的重点内容。

  1. <meta charset=utf-8> 声明必须位于何处?
    • <head> 中的任何位置
    • 文档的前 1024 个字节以内
    • DOCTYPE 声明之前
    • <body> 的前 512 个字节以内
  2. dir=auto 如何确定一个元素的方向?
    • 它检查内容中第一个具有强方向性的字符
    • 它应用在 <html> 元素上声明的方向
    • 它统计 RTL 和 LTR 字符的数量,并保留占多数的一方
  3. 为什么两个都显示为“é”的字符串对于 === 可能不相等?
    • 因为一个用 UTF-8 编码,另一个用 UTF-16 编码
    • 因为 Unicode 的大小写转换会因区域设置而异
    • 因为一个使用预组合字符 U+00E9,另一个使用分解序列 e + U+0301