
简介这是一份面向 Go 开发者的 ISO 639-1 语言代码工具包专为国际化i18n和多语言应用场景设计。它汇总了全部 ISO 639-1 语言名称、本地名称与两字符代码并通过内置 init 函数完成初始化支持快速读取访问。开发者安装后可调用 Codes、Names、NativeNames、Languages 等列表直接获取标准语言数据用于语言选择、内容本地化或代码映射示例中仅需一行 fmt.Println 即可输出全部代码上手成本极低。资源包共 10 个文件核心为 5 个 Go 源文件另含 Markdown 文档、CI 配置、License 等整体仅 9KB轻量易集成。代码在 init 中完成数据构建不依赖外部数据文件适合作为工具库集成到已有项目也适合作为学习 Go 包设计、语言数据组织的参考。目前已有 921 人学习下载对于需要在 Go 项目中快速接入语言标准库的开发者这份资源提供了简洁、可靠的实现参考省去自行整理语言列表的繁琐过程也方便对照源码理解 ISO 639-1 的字段结构。 做过多语言项目的同学应该都有过这种体验产品需求写的是“支持多语言”你打开代码库在 i18n 目录下看到en.json、zh.json、de.json已经躺了很久再往数据库里一翻语言字段里还存着English、中文、en-US、Franch这种神仙数据。要把这套东西理顺最终都会落到一个基础标准上ISO 639-1。这套用两位拉丁字母表示语言代码的标准规模不大但几乎所有国际化项目的底层都在用它从 npm 包、Python 语言库、数据库字段到浏览器Accept-Language头和 SEO 的hreflang标签背后都有它的影子。这篇内容我会从标准本身的定位讲起再结合真实项目里的落地、避坑和维护经验展开适合正在做国际化的开发者、产品经理以及所有需要整理语言数据的同学参考。读完你至少能回答三个问题为什么用两位字母、哪些常见语言要记一下、项目里到底该怎么稳定地维护一份语言和代码列表。1. 为什么语言代码这事值得单独立一个标准1.1 人类看“名字”机器要“编号”语言名是一个很不可靠的标识符。同一门语言英文里叫 German德语里叫 Deutsch中文里叫德语同一个 Chinese 在英语里既可能指“中文”在某些语境下还可能被理解成中文的某一种变体。如果你的程序靠name字段判断语言第一你会依赖翻译表才能显示正确第二你根本没法处理同义词和拼写差异。机器需要一个稳定的、无歧义的、可以放进 URL、文件路径和数据库主键里的短字符串。ISO 639-1 的两位小写字母代码就承担了这个角色en就是 Englishde就是 Deutschzh就是中文。这套代码由 ISO 发布和维护在 Unicode、CLDR、主流操作系统和浏览器里被当作底层的语言标识符引用等于全球技术生态默认的“语言身份证号”。人类对语言的称呼会随着翻译而变化但这个编号不会。这就解释了为什么很多语言数据包的核心内容就是一张看起来极其朴素的三列表code、name、nativeName。name是英文名nativeName是语言对自己名字的自称而code才是真正用于系统内部对接的字段。很多同学一开始会好奇为什么已经有英文名了还要 nativeName。很简单语言选择器里你应该显示“中文”而不是“Chinese 中文”两遍同时你又需要英文名来兜底排序和搜索。1.2 语言代码和国家地区代码是两套体系另一个容易混淆的点ISO 639-1 管的是“语言”ISO 3166-1 管的是“国家和地区”两者是正交的。en不默认代表英国也不默认代表美国它代表英语这门语言本身。当你真的要区分英式拼写和美式拼写时需要引入地区子标签写成en-GB、en-US但这个带连字符的完整标签属于 BCP 47 / RFC 5646 的范畴语言部分取自 ISO 639-1地区部分取自 ISO 3166-1。这套组合规则直接决定了数据表怎么设计如果业务只需要“这门内容是什么语言写的”用char(2)存en、zh就够了如果业务需要精确到“美式英语 vs 英式英语”“简体中文 vs 繁体中文”那就不能只依赖 ISO 639-1必须在系统里定义自己的完整 locale 枚举比如用zh-Hans、zh-Hant、pt-BR这类 BCP 47 标签。我见过不少人把语言代码当国家代码用后来产品要区分pt-PT和pt-BR时整个表结构都在返工根子就在于一开始没理解两套标准的边界。ISO 639-1 覆盖多少门语言目前收录的大约是 180 多种主流语言。两位拉丁字母组合理论上只有 676 个所以这套标准注定不可能覆盖全世界所有语言。碰到覆盖之外的方言、少数民族语言或古代语言就得往 ISO 639-2 或 639-3 的三位代码体系里找。对绝大多数商用产品来说180 多种已经足够用了真正的问题是项目里怎么把这 180 多种代码稳定地管理起来而不是东拼西凑一份 Excel。2. 这份列表里到底有什么字段、规律和边界2.1 常见语言代码速查表如果你不想每次都在文档里翻下面这些代码建议直接记住日常项目里出现频率最高。表格里我把name、nativeName和典型使用场景放在一起代码English NameNative Name说明enEnglishEnglish通用英语不分英美zhChinese中文可覆盖简繁细分靠脚本子标签jaJapanese日本語日语koKorean한국어韩语/朝鲜语deGermanDeutsch德语frFrenchFrançais法语esSpanishEspañol西班牙语itItalianItaliano意大利语ptPortuguesePortuguês葡萄牙语一般还要准备 pt-BRruRussianРусский俄语arArabicالعربية阿拉伯语注意 RTL 方向hiHindiहिन्दी印地语trTurkishTürkçe土耳其语nlDutchNederlands荷兰语在这张表里注意两个常见误解en不是“英文名称”的意思它指的是英语zh也不是“中文简体”的缩写它代表中文这个宏观语言简体、繁体都由它兜着要再细分就得写成zh-Hans或zh-Hant。另外很多代码不是英文单词的缩写而来自该语言对自己的称呼de来自 Deutschfr来自 Françaises来自 Españolzh来自中文拼音 Zhongwen。如果你在代码审查里看到有人用ge表示 German 或者cn表示中文那都是把语言代码和英文缩写搞混了这类“很像但不对”的代码是最容易在项目里留下隐患的。2.2 几个容易混乱的边界情况ISO 639-1 不是表面看起来那么整齐划一。挪威语就有两个标准官方书面语分别用nbBokmål和nnNynorsk区分你只存一个 Norwegian 是说不清用户到底要哪一种的葡萄牙语也存在跨地区差异很多产品干脆在基础语言表之外维护pt-BR和pt-PT两个完整 locale。中文的情况更特殊ISO 639-1 只给了zh没有给“简体”和“繁体”单独分配两位代码因为语言代码本身只负责标记语言系统不负责标记书写系统书写系统差异由 Unicode 的脚本子标签Hans、Hant来处理。所以如果你收到一份语言列表里面既有zh又有zh-CN、zh-TW你要清楚它们是不同粒度的东西不能简单混在一个字段里。还有一类边界情况来自历史包袱。一些语言的 ISO 639-1 代码在早期版本里用过别的写法比如希伯来语旧代码是iw意第绪语旧代码是ji印度尼西亚语旧代码是in后来都改成了he、yi、id。你在老系统里看到这些旧代码不能直接当无效值丢掉最好的做法是在清洗层保留一张新旧对照映射表把旧代码统一转换成当前标准。这件事也提醒我任何“语言和代码列表”都不是永久的接入现成库或自维护数据时都要留意标准更新和历史别名的兼容问题。3. 从纯数据到功能语言代码在项目里的几种落地方式3.1 语言选择器与前端展示最直白的落地场景是前端语言切换器。你需要展示一门语言的本地化名称用户是德国人时看到 Deutsch用户是中国人时看到 中文而不是所有人都看到一份英文名称列表。最有效的数据结构就是code name nativeName三件套显示时优先nativeName搜索和排序时用name兜底。npm 上有不少现成包比如名字就叫iso-639-1的那个核心数据就是这张三列表。实际写法大致是这样import ISO6391 from iso-639-1; const allLanguages ISO6391.getLanguages(); // [{ code: en, name: English, nativeName: English }, ...] // 展示语言时优先用 nativeName function displayName(code) { return ISO6391.getNativeName(code) || ISO6391.getName(code); } // 过滤出需要支持的语言子集 const enabledCodes [en, zh, ja, de, fr, es]; const enabledLanguages allLanguages.filter((item) enabledCodes.includes(item.code) );语言选择器里通常还要处理两件事一是排序是按 nativeName 的字母序排还是按你自己定义的展示顺序排二是持久化用户选完之后一般存回localStorage或 cookiekey 就用标准的两位代码比如locale: zh。如果你要用完整 BCP 47 标签比如zh-Hans或en-GB那就不再是 ISO 639-1 包能直接给出来的内容需要额外维护一份 locale 枚举代码里的语言部分仍以 ISO 639-1 为准。3.2 资源文件、URL 和请求头里的语言标识第二个落地场景是资源文件命名和路由。很多项目的 i18n 目录会按代码组织文件比如src/i18n/en.json、src/i18n/zh.json、src/i18n/de.json。这里我强烈建议统一用小写的两位代码作为文件名不要出现en-US.json和en_GB.json混用的情况等真正需要地区变体时再专门引入zh-Hans.json、zh-Hant.json而不是把zh-CN.json和zh-TW.json当成语言文件。后者会把地区信息和语言边界揉在一起给后续维护埋雷。浏览器请求头里的语言信息也是这么处理的。Accept-Language字段常长这样Accept-Language: zh-CN,zh;q0.9,en;q0.8后端解析时先按q值排序然后把每一项按连字符切成语言部分和地区部分语言部分用 ISO 639-1 代码去匹配语言包。如果你自己写解析逻辑注意不要直接用startsWith(zh)去匹配因为zh-CN、zh-HK、zh-Hant都属于中文但你可能会想按地区或脚本分别回退更稳妥的做法是把完整标签逐个降级匹配先试zh-CN再试zh最后试默认语言。3.3 数据库、SEO 和文本识别中的语言代码数据库里保存文章、商品、轮播图等多语言内容时最常见的做法是给每条内容加一个lang字段值存en、zh、ja这类两位代码。如果一张表里同一篇内容有多个语言版本通常用(entity_id, lang)建联合唯一索引这里的lang字段建议用char(2)省空间且约束直观当业务需要完整 locale 时再单独用varchar(10)存 BCP 47 标签。不要在同一张表里有的行存en、有的行存en-US一个字段只有一个粒度。SEO 场景里hreflang标签的语言部分同样取自 ISO 639-1常见写法有hreflangen和hreflangzh-CN两种前者只声明语言后者声明语言加地区。具体用哪种要看你的内容是否存在地区性差异而不是简单地把所有中文内容都写成zh-CN。此外很多云厂商的文本语言识别接口返回结果就是标准代码比如返回zh、en、ja拿到后直接匹配语言包非常顺滑这也说明在同一生态里坚持使用同一套标准能省掉大量转换和映射代码。4. 我在实际项目里踩过的语言代码相关的坑4.1 zh 不是简体中文en 也不是英式或美式这是我最想强调的一个坑。很多团队在没有完整 locale 概念时直接拿语言代码当成了“地区版式语言”于是产生了两种错误一是把zh写进“简体中文”的翻译文件后来又发现繁体用户也得支持只好把所有文件改名成zh-Hans、zh-Hant二是给海外用户默认en但英国用户反馈拼写不对美国用户也反馈词条偏好问题最后才意识到en只是英语en-GB、en-US才对应具体的变体。语言代码是“语言维度”的标识地区差异和书写体系差异需要额外维度不能把这些问题压给两位代码去表达。这里给一个最小建议如果你的产品只区分语言不区分地区变体那en、zh完全够用如果产品未来一定会区分地区变体尽早把语言层和 locale 层拆开。比如前端语言选择器里展示的是“英文”“中文”“日语”系统内部完整标签是en-US、zh-Hans、ja业务数据存完整标签而 ISO 639-1 代码只作为 fallback 和识别的基础。这种分层设计能避免很多晚期返工。4.2 大小写、连字符和传统标签带来的兼容包袱ISO 639-1 标准本身对大小写没有强制规定但生态里形成了约定语言代码小写地区代码大写脚本子标签首字母大写。所以你会看到zh-Hans、en-US、sr-Latn这样的写法。反过来说如果你在程序里用不区分大小写的比较去处理语言代码倒也不会出大事但在日志、URL、文件系统里大小写不一致会让你后续查问题非常痛苦。最干净的做法是入库前统一 normalize语言部分转小写地区部分转大写脚本部分按规范首字母大写。传统标签的问题更隐蔽。老一代系统里非常流行zh-CN、zh-TW、zh-HK这是“语言地区”的思路用来间接表示简繁而更新的 BCP 47 规范更推荐zh-Hans、zh-Hant这是“语言脚本”的思路。两种写法都合法但混用会导致同一个目标语言被拆成一堆 key。我建议新旧系统对接时准备一个统一层把收到的任意标签先映射到自己定义的规范标签再往下游分发。这个映射不必面面俱到覆盖实际流量里的主要组合就够了。4.3 脏数据清洗从自由文本到标准代码现实世界的存量数据不会天然整洁。我见过语言字段里同时存在English、english、en、en-US、EN、英语六种写法如果不清洗报表、搜索筛选、推荐系统全都会被污染。清洗思路并不复杂先把值转成小写然后依次匹配标准代码、BCP 47 标签的 language 部分、英文名、nativeName最后落到一张人工维护的别名映射表。下面是一张按常见程度排列的清洗对照适合放进数据管道的 lookup 阶段原始值处理后代码说明English, english, eng, en-US, en_GBen语言部分相同统一为 en中文, chinese, zh-CN, zh_CN, zh-Hanszh业务只分语言时归入 zh繁体中文, Chinese (Traditional), zh-TW, zh-Hantzh-Hant需要区分脚本时使用Deutsch, german, de-DEde德国地区的德语français, francais, french, fr-FRfr注意去掉不常见拼写干扰清洗之后还要做一件事把无法识别的值单独报出来而不是静默丢弃。语言代码是低频变更的数据样本量不大完全可以靠人工 review 把别名表完善到足够覆盖。这个环节做好之后后面所有依赖语言标签的功能都会变得非常稳。5. 自己维护语言代码列表哪些该引包哪些必须自己做5.1 项目规模决定你的方案如果项目只支持几种语言完全没必要引一个大而全的包直接在项目里维护一份 JSON 就够了[ { code: en, name: English, nativeName: English, rtl: false }, { code: zh, name: Chinese, nativeName: 中文, rtl: false }, { code: ja, name: Japanese, nativeName: 日本語, rtl: false }, { code: ar, name: Arabic, nativeName: العربية, rtl: true } ]字段越少越容易维护把rtl也加进来是因为多数业务最终都要处理阿拉伯语、希伯来语的排版方向与其在业务代码里写[ar,he,fa].includes(code)不如一开始就放到基础数据里。如果你的产品面向全球几十上百个语言市场再考虑引入社区维护的现成数据或包但引入前要检查三件事数据是否包含 nativeName更新频率如何是否允许你自定义排序和过滤。语言的英文名和本地化名本身就是需要维护的翻译资产完全依赖一个第三方包往往不够尤其是当你想在语言选择器里对 100 多种语言按中文名排序时nativeName不一定能覆盖你关心的全部。5.2 在基础代码表上扩展地区与 RTL 信息自维护不等于自己从零开始。ISO 639-1 的官方注册表和 Unicode CLDR 都提供了权威基础数据你可以基于它们生成自己项目的语言表再补三列所属地区变体列表、RTL 标识、是否建议默认脚本。比如zh这一行可以补variants: [zh-Hans, zh-Hant]pt补variants: [pt-PT, pt-BR]。这样语言选择器可以同时做到按宏观语言去重、按变体展开后台配置也能用同一份数据做下拉选项和校验。维护这份表时要记住一个原则ISO 639-1 代码是“已发布的公开事实”你的项目里不能因为某个语言用户量小就修改它的代码但你可以用enabled字段来决定哪些语言是可选语言。语言代码是公共契约是否支持才是产品决定。如果代码和产品配置混在同一个表里很容易出现有人为了“隐藏阿拉伯语”顺手把ar那行的代码改掉的乌龙这种事在代码审查里看起来都觉得离谱但实际确实发生过。5.3 对标准的版本变化保持敏感ISO 639-1 的更新节奏不快但确实存在历史别名和废弃代码比如前面提到的iw、ji、in。接入任何语言数据源时我会先验证三件事代码是否遵循当前标准是否把历史别名挡在输出层系统内部是否使用统一的枚举而非直接散落字符串。如果项目里只有一两处用到语言代码可能无所谓当代码被埋进多个模块后再做统一就非常痛苦了。我自己的做法是在代码库里放一个language-codes.ts内容只有标准代码的常量对象和一个类型业务代码全部引用这个常量不允许直接写字符串字面量。这样当标准更新或产品需要调整支持范围时只改这一个文件编译期就能发现所有引用点。语言和代码列表看似是个不起眼的小数据结构但它会被语言选择器、路由、数据库、SEO、权限配置同时引用越早收口成统一数据源后续的成本越低。本文还有配套的精品资源点击获取