系统设计 / 路由设计 · 稳定入口与可变目标 / 国际化路由 待审核 22 / 23

国际化路由

多语言站点的路由要回答一个新问题:同一个 /about,该给英文、中文还是日文版。这有两件事要分清。

其一,locale 写在哪。大多写进 URL 让它可分享、对 SEO 友好,两种常见写法是路径前缀(/en/about)与域名(en.site.comsite.fr)。其二,当 URL 没带 locale 时(用户直接访问 /about),靠什么猜一个——这就要读浏览器的 Accept-Language 头去协商。

本页支持的 locale 是 enzhzh-TWfrja,默认 en

1 · locale 的位置与默认时的处理

图 1-1 · 路径前缀与域名两种策略下 locale 从哪里取,以及 URL 不带 locale 时如何跳到带前缀的规范地址。可切换策略与入口 URL 观察判定过程。

注 · URL 自带的 locale 优先,且不应再被猜测覆盖。用户访问 /zh/about 就直接返回中文,哪怕他的 Accept-Language 是英文。Accept-Language 只在 URL 没有携带 locale 时才参与,且结果通常是一次 302 跳到带前缀的规范地址,让此后每个语言版本都有自己唯一、可分享、可被搜索引擎收录的 URL。用 302 而非 301,是因为判定结果随用户而变,不宜被永久缓存——HTTP 重定向一页有两者的语义对照。

2 · Accept-Language 的协商

Accept-Language 不是单个值,而是浏览器按用户语言设置排好的偏好列表,每项带一个 q 权重(0 到 1,默认 1)。协商就是把它按 q 降序排好,逐个去和本站支持的 locale 匹配:先求精确命中(zh-TWzh-TW),不中就剥掉地区退一步(zh-TW 退到 zh)再试,这就是 fallback 链。第一个匹配上的即结果,全都不中则落到默认值。

图 2-1 · 协商算法逐步执行:解析 q、降序排列、精确命中或剥地区回退。可改 Accept-Language 观察匹配落在哪一项。

协商算法本身只有十来行。

const SUPPORTED = ['en', 'zh', 'zh-TW', 'fr', 'ja'];
const DEFAULT = 'en';

// "zh-TW,zh;q=0.9,en;q=0.5" → 按 q 降序的偏好列表
function parse(header) {
  return header.split(',').map(part => {
    const [tag, q] = part.split(';q=');
    return { tag: tag.trim(), q: q ? +q : 1 };   // q 缺省为 1
  }).sort((a, b) => b.q - a.q);                   // 权重高的优先
}

function negotiate(header) {
  for (const { tag } of parse(header)) {
    if (SUPPORTED.includes(tag)) return tag;      // 精确命中
    const base = tag.split('-')[0];               // zh-TW → zh
    if (base !== tag && SUPPORTED.includes(base)) return base;  // 剥地区 fallback
  }
  return DEFAULT;                                 // 全不中 → 默认
}

这段代码与图 2-1 背后的引擎(core/i18n.ts)在 12 组头部上给出相同结果,包括 zh-Hant-TW,zh;q=0.7 这种三段标签退到 zhzh-TW;q=0.1,en;q=0.9 这种低权重项被排到后面、以及 *pt-BR 全不命中落回默认这几种情形。

建议 · 剥地区 fallback 是分寸活。zh-TW 退到 zh 多半没问题,但 zh 默认指简体,给繁体用户未必理想,所以认真做的站点会同时支持 zhzh-TW 并精确区分。更不要把不同书写系统混为一谈,简繁如此,pt-BRpt-PT 也如此。协商只是挑一个默认值,务必再给用户一个显式的语言切换器,并把选择记进 cookie 或账户设置。

「一个内容一个规范地址」在多语言下变成「每个语言版本一个规范地址」,再用 hreflang 互相声明,与 URL 规范化是同一套思路。路由挑出 locale 之后,日期、数字与排序怎么按该 locale 格式化,是 Intl 系列的事。