1. 项目缘起:为什么我们需要一份靠谱的语言缩写列表?
在数字化的世界里,处理多语言内容几乎成了开发、运营、产品经理乃至内容创作者的日常。无论是为网站配置国际化(i18n)语言包,还是在数据库中存储用户的语言偏好,又或者是在API接口中定义请求头里的Accept-Language,我们总绕不开一个看似简单却极易出错的东西:语言缩写。
你可能遇到过这样的场景:产品经理说“我们要支持法语”,你兴冲冲地在代码里写上了fr。上线后,法国用户反馈界面显示异常。一查才发现,法国本土除了法语,还有布列塔尼语、阿尔萨斯语等,而瑞士法语区的用户期望的可能是fr-CH。又或者,你从某个开源库的文档里复制了一段语言代码列表,结果发现它把简体中文标成了zh-CN,把繁体中文标成了zh-TW,但在某些国际标准里,它们更精确的表示是zh-Hans和zh-Hant。这些细微的差别,轻则导致界面显示错误,重则可能引发本地化内容推送失败,影响用户体验。
因此,一份准确、全面、且附带使用场景说明的语言缩写列表,绝不是简单的信息罗列,而是一份能帮你避开无数坑的“地图”。它需要告诉你,在什么情况下该用哪种标准,不同标准之间有何差异,以及在实际应用中如何选择。本文的目的,就是为你绘制这样一张地图,并结合我在多个跨国项目中的实战经验,告诉你如何正确地使用它们。
2. 语言缩写的核心标准:ISO 639 与 IETF BCP 47
在深入列表之前,我们必须先理解支撑这些缩写的两大核心标准体系。不同的标准适用于不同的场景,用错了标准,就像拿着地图却看错了图例。
2.1 ISO 639:语言标识的基石
ISO 639是国际标准化组织制定的一套用于表示语言名称的代码标准。它有几个部分,最常用的是:
ISO 639-1 (两位字母代码):这是最广为人知、最常用的形式。它涵盖世界上主要语言,代码简洁。例如:
en- 英语zh- 中文es- 西班牙语fr- 法语de- 德语ja- 日语ko- 韩语ru- 俄语
使用场景:适用于对语言区分粒度要求不高的场景,如简单的网站语言切换、内容的大类分类。它的优点是极其简短,易于记忆和传播。
ISO 639-2 (三位字母代码):当639-1的两位代码不够用时(例如某些没有广泛书面形式的语言),或图书馆、文献领域需要更精细的分类时,会使用三位代码。它又分为“术语型”(T)和“文献型”(B)两种,通常我们使用术语型。例如:
eng- 英语 (术语型)chi/zho- 中文 (这里就体现了B/T的区别,chi是文献型,zho是术语型,在实际技术应用中,强烈建议使用zho以避免歧义)fre/fra- 法语 (fra为术语型)
使用场景:主要用于图书编目、学术研究等专业领域。在现代软件开发中,除非对接特定传统系统,否则较少直接使用。
ISO 639-3 (三位字母代码):旨在覆盖全球所有已知的语言,包括方言、古语等,数量超过7000种。它是639-2的超集。例如:
cmn- 普通话 (官话)yue- 粤语wuu- 吴语
使用场景:语言学研究、非常精细的语言识别和分类。对于大多数应用产品来说,粒度过于细致。
实战经验一:首选 ISO 639-1对于绝大多数商业软件和互联网产品,ISO 639-1的两位代码是首选。它足够通用,被几乎所有操作系统、浏览器、开发库所支持。在数据库设计时,用一个CHAR(2)字段来存储用户语言偏好,既节省空间又高效。记住一个原则:如无特殊必要,不要引入更复杂的代码体系。
2.2 IETF BCP 47:现代应用的事实标准
如果说ISO 639定义了“语言”本身,那么IETF的BCP 47(通常表现为RFC 5646)则定义了如何完整地描述一个“语言区域”。它更像一个构造规则,通过子标签(Subtags)的组合来精确表达。
一个BCP 47语言标签的格式通常为:语言-脚本-地区-变体-扩展-私有。最常用的是“语言-地区”对。
- 语言子标签:通常使用ISO 639-1的两位代码(首选)或639-2/3的三位代码。
- 脚本子标签(可选):使用ISO 15924四位字母代码,用于区分书写系统。这是解决“简体中文”与“繁体中文”问题的关键。
Hans- 简体中文Hant- 繁体中文Cyrl- 西里尔字母Latn- 拉丁字母
- 地区子标签(可选):使用ISO 3166-1的两位字母国家代码或UN M.49的三位数字地区代码。
CN- 中国TW- 中国台湾地区US- 美国GB- 英国001- 全球(数字代码)
组合示例与解析:
zh-CN:中文,中国大陆地区。这是一个常见的“语言-地区”组合。但它隐含了“使用简体中文”的意思,因为中国大陆主要使用简体。然而,从标准角度看,它并未明确指定脚本。zh-Hans:中文,简体脚本。这是表示“简体中文”更精确的方式,它不绑定特定区域,适用于所有使用简体中文的地区(如中国大陆、新加坡、马来西亚华人社区)。zh-Hans-CN:中文,简体脚本,中国大陆地区。这是最完整、最无歧义的表示,明确了语言、书写形式和地理区域。zh-Hant:中文,繁体脚本。表示“繁体中文”,不绑定区域。zh-Hant-TW:中文,繁体脚本,中国台湾地区。常用于台湾地区使用的繁体中文。zh-Hant-HK:中文,繁体脚本,中国香港地区。香港地区使用的繁体中文在词汇、用语上与台湾地区略有不同。en-US:英语,美国地区。美式英语。en-GB:英语,英国地区。英式英语。es-ES:西班牙语,西班牙地区。欧洲西班牙语。es-MX:西班牙语,墨西哥地区。拉丁美洲西班牙语的一种。
实战经验二:理解“语言-地区”与“语言-脚本”的区别这是最容易混淆的地方。zh-CN和zh-Hans看似都指向简体中文,但侧重点不同。zh-CN强调“在中国大陆使用的语言”,其默认脚本是简体,但理论上也可能包含其他脚本(虽然极少)。zh-Hans则纯粹强调“使用简体汉字书写的中文”,与地域无关。在涉及内容本地化(Localization)时,优先使用“语言-地区”(如zh-CN,en-US),因为它包含了地域文化习惯;在涉及纯粹的文字呈现或字体选择时,使用“语言-脚本”(如zh-Hans,zh-Hant)更准确。对于中文,一个稳妥的实践是在系统内部使用zh-Hans和zh-Hant,而在面向用户的界面上显示“简体中文(中国)”、“繁体中文(台湾)”等友好名称。
3. 核心语言缩写列表与使用指南
下面我将结合ISO 639-1和BCP 47,整理一份在软件开发、内容管理中最常遇到的语言标签列表,并附上关键说明。
3.1 全球主要语言(基于ISO 639-1)
| ISO 639-1 代码 | 英语名称 | 本地名称(示例) | 典型BCP 47扩展(语言-地区) | 主要使用地区/说明 |
|---|---|---|---|---|
ar | Arabic | العربية | ar-SA(沙特),ar-EG(埃及) | 阿拉伯语,注意是从右向左书写(RTL)。 |
de | German | Deutsch | de-DE(德国),de-AT(奥地利),de-CH(瑞士) | 德语。瑞士德语(de-CH)在日期、数字格式上有所不同。 |
en | English | English | en-US(美国),en-GB(英国),en-AU(澳大利亚) | 必须区分地区,拼写、日期、货币格式差异大。 |
es | Spanish | Español | es-ES(西班牙),es-MX(墨西哥),es-AR(阿根廷) | 西班牙语变体极多,词汇、发音、甚至语法有差异。 |
fr | French | Français | fr-FR(法国),fr-CA(加拿大),fr-BE(比利时) | 加拿大法语(fr-CA)与法国法语在词汇和用语上区别明显。 |
it | Italian | Italiano | it-IT | 意大利语。 |
ja | Japanese | 日本語 | ja-JP | 日语。通常不需要进一步细分。 |
ko | Korean | 한국어 | ko-KR | 韩语。 |
pt | Portuguese | Português | pt-PT(葡萄牙),pt-BR(巴西) | 欧洲葡萄牙语和巴西葡萄牙语差异巨大,务必区分。 |
ru | Russian | Русский | ru-RU | 俄语。使用西里尔字母。 |
zh | Chinese | 中文 | 见下方详细分解 | 中文情况最复杂,必须处理简繁体。 |
3.2 中文的详细分解(重点与难点)
中文处理是国际化中的重中之重,也是最易出错的部分。下面这个表格清晰地展示了各种组合:
| BCP 47 标签 | 说明 | 常见对应友好名称 | 使用场景建议 |
|---|---|---|---|
zh | 中文(泛指) | 中文 | 避免单独使用,歧义太大。 |
zh-Hans | 中文,简体脚本 | 简体中文 | 推荐。用于表示所有简体中文内容,不绑定地区。 |
zh-Hant | 中文,繁体脚本 | 繁体中文 | 推荐。用于表示所有繁体中文内容,不绑定地区。 |
zh-CN | 中文,中国大陆地区 | 简体中文(中国) | 隐含简体。适用于针对中国大陆市场的完整本地化。 |
zh-SG | 中文,新加坡地区 | 简体中文(新加坡) | 新加坡简体,用词习惯与大陆略有不同。 |
zh-TW | 中文,中国台湾地区 | 繁体中文(台湾) | 台湾繁体。注意政治敏感性,在列表中宜与zh-Hant-TW等同。 |
zh-HK | 中文,中国香港地区 | 繁体中文(香港) | 香港繁体。 |
zh-MO | 中文,中国澳门地区 | 繁体中文(澳门) | 澳门繁体。 |
zh-Hans-CN | 中文,简体脚本,中国大陆 | 简体中文(中国大陆) | 最精确、最无歧义的表示法。 |
zh-Hant-TW | 中文,繁体脚本,台湾地区 | 繁体中文(台湾) | 最精确、最无歧义的表示法。 |
zh-Hant-HK | 中文,繁体脚本,香港地区 | 繁体中文(香港) | 最精确、最无歧义的表示法。 |
实战经验三:中文标签的存储与显示策略
- 内部存储:在数据库、配置文件、代码常量中,强烈建议使用
zh-Hans和zh-Hant。这分离了“书写系统”和“地域文化”,逻辑更清晰。例如,你可以用zh-Hans来标记一份文档的书写形式,而用CN、SG来标记其目标市场。 - 用户界面显示:给用户选择时,不要显示
zh-Hans这样的代码。应该显示本地化的语言名称,如“简体中文”、“繁體中文”。如果需要区分地区,可以显示“简体中文(中国大陆)”、“繁體中文(香港)”。 - HTTP
Accept-Language:浏览器发送的通常是zh-CN,zh;q=0.9,en;q=0.8。你的后端服务应该能正确解析,并将zh-CN映射到你的zh-Hans资源,或者更精确的zh-Hans-CN资源(如果你有)。 - 字体回退:在CSS中,你可以针对不同脚本指定字体:
font-family: “PingFang SC”, “Microsoft YaHei”, sans-serif;对应zh-Hans,而font-family: “PingFang TC”, “Microsoft JhengHei”, sans-serif;对应zh-Hant。
3.3 其他需要特别注意的语言
- 塞尔维亚语:它同时使用西里尔字母和拉丁字母书写。可以使用
sr-Cyrl(塞尔维亚语,西里尔字母)和sr-Latn(塞尔维亚语,拉丁字母)来精确区分。 - 维吾尔语:在中国新疆地区使用,阿拉伯字母书写。标准代码是
ug(ISO 639-1),完整标签可以是ug-Arab-CN。 - 粤语:作为中文的一种主要方言,在ISO 639-3中有独立代码
yue。在YouTube等平台,你可以看到yue作为一个可选语言。但在大多数产品中,它通常被归入zh-Hant或zh-Hans下的变体处理。 - 库尔德语:有
ku(库尔德语泛称)、kmr(北库尔德语,常用)、ckb(中库尔德语)等代码,书写系统也有拉丁和阿拉伯之分。
4. 在实战中应用:从配置到代码
理解了标准和列表,关键在于如何用起来。下面我以几个典型场景为例,说明如何实际操作。
4.1 场景一:网站/应用国际化(i18n)配置
假设你使用流行的i18n库(如 i18next, react-i18n, vue-i18n)。
1. 资源文件命名与组织:一种清晰的结构是按BCP 47标签建立文件夹或文件名。
locales/ ├── en-US/ │ ├── common.json │ └── product.json ├── zh-Hans/ │ ├── common.json │ └── product.json ├── zh-Hant/ │ ├── common.json │ └── product.json └── ja-JP/ ├── common.json └── product.json为什么这样组织?这分离了语言和地区。zh-Hans下的资源可以被zh-CN和zh-SG共享(基础翻译),然后通过地区特定的扩展或覆写来调整用词差异(例如,“软件”在大陆叫“软件”,在新加坡可能更常用“软体”,虽然都是简体)。
2. 初始化i18n库:
// 以 i18next 为例 import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import Backend from 'i18next-http-backend'; // 从后端加载资源 import LanguageDetector from 'i18next-browser-languagedetector'; // 检测浏览器语言 i18n .use(Backend) .use(LanguageDetector) .use(initReactI18next) .init({ fallbackLng: 'en', // 回退语言 supportedLngs: ['en', 'zh-Hans', 'zh-Hant', 'ja'], // 支持的语言列表 nonExplicitSupportedLngs: true, // 重要!允许检测到`zh-CN`时回退到`zh-Hans` backend: { loadPath: '/locales/{{lng}}/{{ns}}.json', // 资源路径 }, detection: { order: ['querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag'], caches: ['cookie'], }, interpolation: { escapeValue: false, }, });关键配置解析:nonExplicitSupportedLngs: true这个选项至关重要。当浏览器语言是zh-CN时,检测器会依次尝试加载zh-CN->zh->en的资源。由于我们支持zh-Hans,i18next在zh-CN失败后,会聪明地尝试去掉地区码,然后匹配到zh,但我们的支持列表里只有zh-Hans和zh-Hant。此时,nonExplicitSupportedLngs会尝试将zh-CN转换为zh,然后寻找最匹配的支持语言。通常,你需要配置loadPath逻辑或使用后端映射,将zh-CN的请求指向zh-Hans资源。
更稳健的后端映射示例(Node.js/Express):
app.get('/locales/:lng/:ns.json', (req, res) => { let { lng, ns } = req.params; // 语言标签映射 const languageMap = { 'zh-CN': 'zh-Hans', 'zh-SG': 'zh-Hans', 'zh-TW': 'zh-Hant', 'zh-HK': 'zh-Hant', 'zh-MO': 'zh-Hant', 'en-US': 'en', 'en-GB': 'en', // ... 其他映射 }; const mappedLng = languageMap[lng] || lng; // 检查 mappedLng 是否在支持列表中 if (supportedLngs.includes(mappedLng)) { const filePath = path.join(__dirname, 'locales', mappedLng, `${ns}.json`); res.sendFile(filePath); } else { res.status(404).send('Language not found'); } });4.2 场景二:数据库存储用户语言偏好
在设计用户表时,如何存储language字段?
方案A(简单,推荐):使用VARCHAR(5)存储BCP 47语言标签。
CREATE TABLE users ( id INT PRIMARY KEY, username VARCHAR(50), language VARCHAR(5) DEFAULT 'en' -- 存储如 'zh-Hans', 'en-US', 'ja' );优点:直接、灵活,可以存储最精确的标签。前端提交什么就存什么。缺点:查询和统计时需要处理(例如,统计所有中文用户需要查询LIKE 'zh%'或更复杂的解析)。
方案B(规范化):分离语言和地区。
CREATE TABLE users ( id INT PRIMARY KEY, username VARCHAR(50), language_code CHAR(2), -- ISO 639-1, 如 'zh', 'en' region_code CHAR(2), -- ISO 3166-1, 如 'CN', 'US', NULLable script_code VARCHAR(4) -- ISO 15924, 如 'Hans', 'Hant', NULLable );优点:结构清晰,便于基于语言、地区、脚本进行多维度的分析和查询。缺点:复杂度高,写入和读取时需要组合/解析。对于大多数应用来说,过度设计。
我的建议:对于90%的应用,方案A足够了。使用短字符串存储完整标签,并在业务逻辑层建立一套清晰的映射和回退规则。例如,当用户选择“中文(简体)”时,前端可以提交zh-Hans;当从浏览器获取到zh-CN时,通过后端映射或配置,将其关联到zh-Hans资源。
4.3 场景三:操作系统与浏览器环境识别
在Node.js后端或浏览器端,你需要获取系统或环境的语言设置。
浏览器端:
// 获取浏览器优先语言(数组) const browserLanguages = navigator.languages; // 例如:['zh-CN', 'zh', 'en-US', 'en'] // 获取单个首选语言 const userLanguage = navigator.language || navigator.userLanguage; // 例如:'zh-CN'注意:navigator.language返回的是单个字符串,而navigator.languages返回一个按优先级排序的数组,后者更可靠。你应该用这个数组作为Accept-Language的模拟,交给你的语言检测逻辑处理。
Node.js 后端:可以从HTTP请求头Accept-Language中解析。
const acceptLanguage = req.headers['accept-language']; // 例如:'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7' // 需要使用类似 `accepts` 或 `locale` 这样的库来解析 const Accept = require('accepts'); const accept = Accept(req); const locale = accept.languages(['en', 'zh-Hans', 'zh-Hant', 'ja']); // 返回匹配的第一个实战经验四:语言检测的优先级策略永远不要完全依赖自动检测。应提供一个明显的语言切换器让用户手动选择。自动检测的逻辑应该是:1) 用户上次手动选择并保存的语言;2) URL参数(如?lang=zh-Hans);3) Cookie或本地存储;4) 浏览器Accept-Language头;5) 应用默认语言(如英语)。用户的明确选择权必须高于系统的猜测。
5. 常见陷阱与最佳实践总结
在多年与多语言打交道的经历中,我踩过不少坑,也总结出一些铁律。
陷阱一:混淆“语言”与“区域设置”en是语言,en-US是区域设置。区域设置除了语言,还包含数字格式(1,234.56 vs 1.234,56)、日期格式(MM/DD/YYYY vs DD/MM/YYYY)、货币符号($ vs €)、排序规则等。如果你的应用只做了文本翻译,但数字、日期格式还是原来的样子,体验会非常割裂。解决方案是使用完整的国际化库,如JavaScript的IntlAPI,它能根据语言标签自动处理这些格式。
陷阱二:硬编码语言列表不要在代码里写死const languages = ['en', 'zh-CN', 'ja']。一旦要新增一种语言,就需要改代码、重新部署。最佳实践是将支持的语言列表作为配置文件(如i18n.config.json)或从后端API动态获取。这样运营人员或产品经理可以在后台管理界面直接添加新语言,前端自动呈现。
陷阱三:翻译资源的键名使用源语言错误示例:{ “Hello”: “你好”, “Goodbye”: “再见” }。当源语言(如英语)的键名需要修改时(“Hello”改成“Hi”),所有其他语言的翻译文件都需要同步修改键名,极易出错。正确做法是使用业务逻辑相关的、语义化的键名。 正确示例:{ “greeting.hello”: “你好”, “greeting.goodbye”: “再见” }。英文文件里是{ “greeting.hello”: “Hello”, “greeting.goodbye”: “Goodbye” }。这样,无论源语言文本如何变化,键名稳定不变。
陷阱四:忽略文本长度变化德语单词通常比英语长,中文短语通常比英文短。UI设计时必须考虑文本扩展(通常预留30%-50%的额外空间)和收缩。使用CSS属性如text-overflow: ellipsis时要小心,确保关键信息不被截断。对于按钮、标签等固定宽度的元素,可以采用最小宽度(min-width)或弹性布局。
陷阱五:缺乏上下文给翻译者把一句“Save”扔给翻译者,他可能翻译成“保存”(动词)或“储蓄”(名词)。必须提供上下文。专业的做法是使用带有描述性的键名,并在翻译管理平台(如Crowdin, Transifex)或JSON文件中添加注释。
{ “button.save”: { “description”: “The label for the button that saves a document”, “defaultMessage”: “Save” } } // 在中文文件中 { “button.save”: “保存” }最佳实践清单:
- 内部统一使用BCP 47标签,优先使用“语言-脚本”(如
zh-Hans)或“语言-地区”(如en-US)形式。 - 建立中央映射表,处理浏览器语言标签到内部标签的转换(如
zh-CN->zh-Hans)。 - 分离“文本翻译”和“区域格式化”,使用
Intl等标准API处理日期、数字、货币。 - 设计弹性的UI,适应文本长度变化。
- 永远提供手动语言切换入口,并持久化用户选择。
- 对翻译资源进行版本控制,并与代码版本关联。
- 在开发早期就引入国际化框架,而不是事后补救。