案例 CLDR locale 继承:en-GB 的父级是 en-001,不是 en

2026-10-20 00:07:16

CLDR locale 继承:en-GB 的父级是 en-001,不是 en

CLDR 的 locale 不是一组扁平的字符串 key。tag 之间有继承链,而这条链不是简单地把最后一段子标签删掉。cldr-core 里的 parentLocales.json 只有 5.8 KB,却包含 200 多条显式父级映射,覆盖了常见语言的实际行为。搞错这一层,症状通常很具体:词条加错位置、日期格式改了不生效、繁体永远看不到新增内容。

数据里的实际映射

以下取自 cldr-json 的 cldr-core/supplemental/parentLocales.json(CLDR 48):

  • en-GB → en-001。en-AU、en-NZ、en-IN、en-ZA、en-SG、en-HK、en-IE 等 80 多个地区同样指向 en-001。
  • en-150 → en-001;en-AT、en-DE、en-NL、en-SE、en-CH、en-PL 等欧洲英语指向 en-150。
  • es-MX、es-AR、es-CO、es-US 等 → es-419(拉美西语);es-ES 才是 → es。
  • zh-Hant → und;zh-Hant-MO → zh-Hant-HK。繁体不继承 zh,因为 zh 默认是 Hans。
  • pt-AO、pt-MZ、pt-CV 等 → pt-PT;而 pt 默认是巴西。
  • nb、nn → no;no-NO → no。
  • ht(海地克里奥尔语)→ fr-HT。
  • az-Arab、sr-Latn、pa-Arab、uz-Cyrl、yue-Hans、zh-Hant 等 → und,脚本不匹配,直接回 root。规则写在 _localeRules.parentLocale.nonlikelyScript = root。

parentLocales.collations 区块还有一套独立父级:yue → zh-Hant,yue-CN、yue-Hans → zh-Hans,sr-Cyrl-ME → sr-ME。排序用的父级和普通数据不是同一条链。

aliases.json(137 KB)管的是另一件事:弃用 ID。iw→he、in→id、ji→yi、mo→ro、sh→sr-Latn,时区 Asia/Calcutta→Asia/Kolkata。老数据、老 URL 里的 tag 要先过 alias,再谈继承。

截断式 fallback 跳过了中间层

自己写 tag.split('-').slice(0, -1) 会得到这样一条路径:

  • en-GB 直接跳到 en,跳过 en-001
  • es-MX 直接跳到 es,跳过 es-419
  • en-AT 直接跳到 en,跳过 en-150 和 en-001

结果就是「我在 en-001 里补的词条,英国用户看不到,但我本地测试(用 en)是好的」。测试环境和线上环境的 locale 不同,问题不会在本地暴露。

按 CLDR 规则算继承链

规则很短:先查显式父级表,命中就用表里的值;没命中才截断最后一段子标签。下面是可直接跑的版本。

// npm i cldr-core
const core = require('cldr-core/supplemental/parentLocales.json');

// 普通数据的父级表在 parentLocales.parentLocale;
// 排序的父级表在 parentLocales.collations,与 parentLocale 平级,不在它下面。
const parentLocale = core.supplemental.parentLocales.parentLocale;
const collations = core.supplemental.parentLocales.collations;

function makeChain(table) {
  return function chain(tag) {
    const out = [tag];
    const seen = new Set([tag]);
    let cur = tag;

    for (;;) {
      let next = table[cur];
      if (!next) {
        const parts = cur.split('-');
        if (parts.length === 1) {
          out.push('root');
          break;
        }
        next = parts.slice(0, -1).join('-');
      }
      out.push(next);
      if (next === 'root' || next === 'und' || seen.has(next)) break;
      seen.add(next);
      cur = next;
    }
    return out;
  };
}

const chain = makeChain(parentLocale);
const collationChain = makeChain(collations);

console.log(chain('en-GB'));
// ['en-GB', 'en-001', 'en', 'root']
console.log(chain('en-AT'));
// ['en-AT', 'en-150', 'en-001', 'en', 'root']
console.log(chain('es-MX'));
// ['es-MX', 'es-419', 'es', 'root']
console.log(chain('zh-Hant-MO'));
// ['zh-Hant-MO', 'zh-Hant-HK', 'zh-Hant', 'und']
console.log(chain('pt-AO'));
// ['pt-AO', 'pt-PT', 'pt', 'root']
console.log(chain('ht'));
// ['ht', 'fr-HT', 'fr', 'root']

console.log(collationChain('yue'));
// ['yue', 'zh-Hant', 'und']

seen 用来防环,und 和 root 都作为终点。zh-Hant 命中 → und 后停止,不会继续落到 zh。

三个直接后果

词条放在哪一层,决定影响面。 放 en-001 覆盖所有英式/非美英语地区(80+);放 en 覆盖全部英语,包括美式;放 en-GB 只有英国。往 en-001 补一条日期格式,美国不受影响,但澳洲、印度、南非会同时变。这类改动要看一眼链,而不是看 key 名像不像。

繁体用户看不到 zh 的补充。 zh 默认 Hans,zh-Hant 的父级是 und。要覆盖繁体,必须写 zh-Hant,或者落到 zh-HK / zh-TW,不能只写 zh 然后期待被继承。

排序和搜索规则跟展示数据不是一条链。 yue 的排序父级是 zh-Hant,不是 zh-Hans。给 zh-Hans 调排序不会传导到粤语,两套 map 要分别查。

验证方法

这些映射不需要背。请求进来时把 resolved 链打出来,或者直接断言行为差异:

const fmt = (loc) =>
  new Intl.DateTimeFormat(loc, { timeZone: 'UTC' })
    .format(new Date(Date.UTC(2026, 9, 20)));

console.log(fmt('en-GB')); // 20/10/2026
console.log(fmt('en'));    // 10/20/2026
console.log(new Intl.DateTimeFormat('iw').resolvedOptions().locale); // he,alias 已生效

真正的差异点用 golden file 固定住:把各 locale 的 resolved 链和几个关键格式化结果写成快照,CLDR 升级时 diff 会直接告诉你哪条链变了。想确认某一条,读 parentLocales.json 里的键,5.8 KB 的文件比任何二手描述都准。

资料

  • parentLocales.json:https://github.com/unicode-cldr/cldr-json/blob/main/cldr-json/cldr-core/supplemental/parentLocales.json
  • aliases.json:https://github.com/unicode-cldr/cldr-json/blob/main/cldr-json/cldr-core/supplemental/aliases.json
  • 规范 UTS #35 的 Locale Inheritance / Lookup:https://www.unicode.org/reports/tr35/
  • CLDR 数据仓库:https://github.com/unicode-org/cldr
  • cldr-json 总入口:https://github.com/unicode-cldr/cldr-json
复制全文 生成海报 CLDR locale i18n 国际化 en-001 parentLocales Node.js

推荐文章

程序员茄子在线接单