BCP 47 语言标签与 locale 标准
High Contrast
Dark Mode
Light Mode
Sepia
Forest
6 min read1,129 words

BCP 47 语言标签与 locale 标准

核心问题zh-CNzh-TWzh-Hant-HK 这些代码到底怎么读懂?如何在代码中正确使用 locale 标签?


真实场景

你的产品要支持繁体中文,但收到反馈:台湾用户的"繁体中文"和香港用户的"繁体中文"界面文字习惯不同("軟體" vs "軟件","資料" vs "數據")。这时候你需要用精确的 BCP 47 语言标签来区分。


什么是 BCP 47

BCP 47(Best Current Practice 47)是 IETF 发布的语言标签规范,合并了 RFC 5646 和 RFC 4647,定义了语言标签的语法和匹配规则。

BCP 47 语言标签的基本结构:

language[-script][-region][-variant][-extension][-privateuse]
例子:
zh-CN          = 简体中文(中国大陆)
zh-TW          = 繁体中文(台湾)
zh-Hant-HK     = 繁体中文(香港)
en-US          = 英文(美国)
en-GB          = 英文(英国)
ar-SA          = 阿拉伯语(沙特阿拉伯)
pt-BR          = 葡萄牙语(巴西)
es-419         = 西班牙语(拉丁美洲)

标签结构详解

1. 语言子标签(Language)

ISO 639-1 两字母代码或 ISO 639-2/3 三字母代码。

代码 语言
zh 中文
en 英语
ar 阿拉伯语
ja 日语
ko 韩语
fr 法语
de 德语
es 西班牙语
pt 葡萄牙语
ru 俄语
hi 印地语
th 泰语

2. 文字子标签(Script)

ISO 15924 四字母代码(首字母大写)。只在必要时使用,避免冗余。

代码 文字系统
Hans 简体汉字
Hant 繁体汉字
Latn 拉丁字母
Arab 阿拉伯字母
Cyrl 西里尔字母
Deva 天城文(印地语)
zh-Hans    = 简体中文(不指定地区)
zh-Hant    = 繁体中文(不指定地区)
zh-Hans-CN = 简体中文(中国大陆)—— 冗余,通常简写为 zh-CN
zh-Hant-TW = 繁体中文(台湾)—— 通常简写为 zh-TW
zh-Hant-HK = 繁体中文(香港)

3. 地区子标签(Region)

ISO 3166-1 两字母国家代码(大写)或 UN M.49 数字代码。

代码 地区
CN 中国大陆
TW 台湾
HK 香港
US 美国
GB 英国
AU 澳大利亚
CA 加拿大
SA 沙特阿拉伯
BR 巴西
419 拉丁美洲(数字区域代码)

4. 变体子标签(Variant)

用于区分同一语言/文字的特殊变体,较少使用。

sl-rozaj   = 斯洛文尼亚语(Resia 方言)
ca-valencia = 加泰罗尼亚语(瓦伦西亚语)

5. 扩展子标签(Extension)

u- 扩展用于指定 Unicode locale 选项:

zh-CN-u-nu-hanidec   = 中文,使用中文数字(一二三)
en-US-u-ca-buddhist  = 英文,使用佛历
en-US-u-hc-h23       = 英文,24小时制
ar-u-nu-arab         = 阿拉伯语,使用阿拉伯-印度数字(٠١٢٣)

完整语言标签示例

graph TD A["zh-Hant-HK-u-ca-gregory"] --> B["zh\n语言:中文"] A --> C["Hant\n文字:繁体汉字"] A --> D["HK\n地区:香港"] A --> E["u-ca-gregory\n扩展:使用公历"]

语言标签的大小写规范

BCP 47 规范虽然不区分大小写,但推荐约定

语言子标签:小写      zh, en, ar
文字子标签:首字母大写 Hans, Hant, Latn
地区子标签:大写      CN, US, GB
// 推荐写法
const locale = 'zh-CN';   // ✅
const locale2 = 'ZH-cn';  // ❌ 不规范(虽然技术上有效)

在 JavaScript / Node.js 中使用

Intl.Locale API

// 创建 Locale 对象
const locale = new Intl.Locale('zh-Hant-HK');
console.log(locale.language);   // 'zh'
console.log(locale.script);     // 'Hant'
console.log(locale.region);     // 'HK'
console.log(locale.baseName);   // 'zh-Hant-HK'
// 带扩展的 locale
const localeExt = new Intl.Locale('en-US', {
calendar: 'gregory',
numberingSystem: 'latn',
hourCycle: 'h23'
});
console.log(localeExt.calendar);         // 'gregory'
console.log(localeExt.numberingSystem);  // 'latn'
console.log(localeExt.hourCycle);        // 'h23'

语言标签验证

// 验证 locale 是否合法
function isValidLocale(locale) {
try {
new Intl.Locale(locale);
return true;
} catch {
return false;
}
}
isValidLocale('zh-CN');     // true
isValidLocale('zh_CN');     // false(下划线不合法!)
isValidLocale('chinese');   // false

⚠️ 常见错误:很多旧系统使用下划线(zh_CN),这在 BCP 47 中是无效的。JavaScript 的 Intl API 会报错或静默降级。永远使用连字符。


从 HTTP 请求获取用户语言

// Express.js 示例
const acceptLanguage = require('accept-language-parser');
app.use((req, res, next) => {
const languages = acceptLanguage.parse(req.headers['accept-language']);
// Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
// 解析结果:[{code:'zh', region:'CN', quality:1}, ...]
req.locale = languages[0]?.code + (languages[0]?.region ? `-${languages[0].region}` : '');
next();
});

CLDR(Unicode Common Locale Data Repository)

CLDR 是 Unicode 维护的最大的 locale 数据库,包含:

graph LR CLDR["CLDR 数据库\n(Unicode 维护)"] --> IntlAPI["浏览器 Intl API"] CLDR --> ICU["ICU 库\n(Java / C++)"] CLDR --> FormatJS["FormatJS\n(react-intl)"] CLDR --> vueI18n["vue-i18n"] CLDR --> Nodejs["Node.js 完整 ICU 数据"]

Node.js 与 ICU 数据

默认安装的 Node.js 可能只包含小型 ICU(small-icu),不支持所有 locale:

# 检查当前 ICU 数据集
node -e "console.log(process.versions.icu)"
# 输出:73.1
# 检查支持的 locale 数量
node -e "console.log(Intl.DateTimeFormat.supportedLocalesOf(['zh-CN', 'ar-SA']))"
# 安装完整 ICU 数据
npm install full-icu
node --icu-data-dir=node_modules/full-icu index.js
# 或通过环境变量
NODE_ICU_DATA=node_modules/full-icu node index.js

locale 标识符的冲突与对应关系

不同系统对 locale 的命名规范不同:

平台 格式 例子
BCP 47 / IETF language-REGION zh-CN
Java / ICU language_REGION zh_CN
POSIX / Linux language_REGION.encoding zh_CN.UTF-8
Windows LCID 数字 2052
macOS BCP 47 zh-Hans-CN
// 在代码中统一转换为 BCP 47 格式
function normalizeToBCP47(locale) {
// Java/Python 风格 zh_CN → zh-CN
return locale.replace(/_/g, '-').replace(/\..*$/, '');
}
normalizeToBCP47('zh_CN.UTF-8');  // 'zh-CN'
normalizeToBCP47('en_US');         // 'en-US'

主流语言的标准标签速查

语言 推荐标签 备注
简体中文(中国) zh-CN 等同于 zh-Hans-CN
繁体中文(台湾) zh-TW 等同于 zh-Hant-TW
繁体中文(香港) zh-HKzh-Hant-HK 词汇有差异
英语(美国) en-US
英语(英国) en-GB 拼写、日期格式不同
日语 jaja-JP
韩语 koko-KR
阿拉伯语(沙特) ar-SA RTL
阿拉伯语(埃及) ar-EG 口语差异大
法语(法国) fr-FR
法语(加拿大) fr-CA 词汇、日期格式不同
西班牙语(西班牙) es-ES
西班牙语(拉丁美洲) es-419 多国共用
葡萄牙语(巴西) pt-BR 与葡萄牙语差异显著
德语 de-DE
俄语 ru-RU 西里尔字母
印地语 hi-IN
泰语 th-TH

常见问题

Q:zhzh-CN 有什么区别?

A:zh 不指定地区,系统会根据上下文决定显示简体还是繁体。建议总是指定地区以消除歧义,用 zh-CN(简体)或 zh-TW/zh-HK(繁体)。

Q:enen-US 哪个更好?

A:对于大多数 Web 应用,建议使用 en-US 作为默认英语,日期格式(MM/DD/YYYY vs DD/MM/YYYY)会有区别。

Q:一个产品需要同时支持 zh-HKzh-TW 吗?

A:取决于业务规模。初期可以用同一套繁体中文(zh-TW),后续根据用户反馈决定是否拆分。词汇差异主要集中在技术术语和部分口语习惯。


下一节:知道了 locale 标签的语法,下面看看 locale 到底会改变程序的哪些行为——排序、数字、日期、货币,每个领域都有陷阱。

locale 敏感行为与工程影响