← 路由设计 · 稳定入口与可变目标 / 国际化路由 · 同一个页面,哪个语言版本 待审核 22 / 23

国际化路由 · 同一个页面,哪个语言版本

多语言站点的路由要回答一个新问题:同一个 /about,该给英文、中文还是日文版?这有两件事要分清。其一,locale 写在哪——大多写进 URL 让它可分享、对 SEO 友好路径前缀/en/about)、或域名en.site.com / site.fr)。其二,当 URL 没带 locale 时(用户直接访问 /about),靠什么一个——这就要读浏览器的 Accept-Language 头去协商

第一个 lab 看策略怎么决定 locale 从哪来、URL 不带时怎么 302 跳到带前缀的规范地址;第二个 lab 拆开 Accept-Language协商算法——它不是一个值而是一串带权重(q)的偏好,还要走 fallback 链(要 zh-TW 没有就退 zh,再退默认)。本站支持 enzhzh-TWfrja,默认 en

1 · 策略:locale 写在 URL 的哪里,不带时怎么办

URL 自带的 locale 优先,且不应再被「猜」覆盖。 如果用户访问 /zh/about,就直接返回中文——哪怕他的 Accept-Language 是英文。Accept-Language 只在 URL 没有携带 locale 时(访问裸 /about)才参与,且结果通常是一次 302 跳到带前缀的规范地址(/about/zh/about),让此后每个语言版本都有自己唯一、可分享、可被搜索引擎收录的 URL。这也呼应 URL 规范化:一个内容一个规范地址。

2 · Accept-Language 协商:一串带权重的偏好 + fallback 链

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

协商算法本身只有十来行:解析 q、降序、精确命中或剥地区 fallback、全不中回默认。

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;                                 // 全不中 → 默认
}

剥地区 fallback 是分寸活。 zh-TW 退到 zh 多半没问题(都是中文),但 zh 默认指简体,给繁体用户可能并不理想——所以认真做的站点会同时支持 zhzh-TW 并精确区分。更别把不同书写系统混为一谈(简繁、pt-BRpt-PT)。协商只是挑默认:务必再给用户一个显式语言切换器,并把选择记下来(cookie / 账户设置),别让人每次都和 Accept-Language 较劲。

3 · 相关页

  • 本系列 · HTTP 重定向——裸 /about/zh/about 用的就是 302;为什么是 302 而非 301(locale 可能随用户而变,不宜永久缓存),看那页的语义表。
  • 本系列 · URL 规范化——「一个内容一个规范地址」在多语言下变成「每个语言版本一个规范地址」,并用 hreflang 互相声明——同一套规范化思路。
  • Intl · 浏览器内建的本地化——路由挑出 locale 之后,日期 / 数字 / 排序怎么按该 locale 格式化,是 Intl 那个系列的事。