Language Codes
ISO 639-1 two-letter codes with Chinese and English names, for the HTML lang attribute, multilingual APIs and content negotiation.
How It Works
ISO 639 assigns codes to natural languages: two-letter (zh, en, ja) and three-letter (zho, eng, jpn). They feed the HTML lang attribute, the Content-Language header and language switchers, being more universal than spelled names.
A region is often appended to form a language tag (BCP 47): zh-CN, en-US, pt-BR — language then regional variant. The same language can differ sharply in spelling, vocabulary and date formats across regions, so i18n must distinguish them.
The pragmatic minimum: set lang on the root element, mark longer passages in a different language with their own lang, and keep URL segments to the two-letter ISO 639-1 code, appending a region only when a real regional difference matters. Those three steps cover most accessibility and SEO needs at very low cost. What genuinely needs extra investment is managing multilingual content itself — translations, plural rules, date and number formats — which is an i18n engineering problem, not a language-code one, and has no code-level fix.
One common mix-up: ISO 639 covers languages, not scripts. Languages like he may be written in Latin or Hebrew script, and the difference between zh-Hant and zh-Hans is script, not language. For typography and font selection, the script subtag matters more than the language code. Everyday web work rarely needs it, but multilingual typesetting and assistive pronunciation make the script layer visible.
When to Use This Table
This table is for tagging content with a language: HTML lang, the HTTP Content-Language header, search filters, screen-reader voice selection and translation routing all key off language codes. A wrong tag means search engines misread the page, assistive tech picks the wrong voice, and hreflang relations break.
When not to consult it: you are organising translations of UI strings. Language codes answer "what language is this content"; i18n file layout, plural rules and date formats belong to i18n engineering — do not conflate them.
Rule of thumb: wherever a machine branches on the answer, write the code (lang, Accept-Language, hreflang); wherever a human reads, write the language name or an Intl.DisplayNames result. Mixing the two is a common source of bugs.
Mind that two-letter and three-letter codes are not simply short and long forms: ISO 639-1 zh is "Chinese" as a whole, 639-2/T zho is the bibliographic name, and 639-3 zho is a macrolanguage (refinable to cmn, yue). If a database uses it as a key, state which edition you picked — mixing editions causes silent match failures.
Boundaries and Common Pitfalls
A lang attribute on the wrong element inherits the wrong language
lang is inheritable. On a page mixing languages (Chinese body text with an embedded English example), setting lang="zh" only on <html> makes assistive tech read the English passage with a Chinese voice. Mark the differing fragment with lang="en" — and dir for writing direction when needed. Search engines also use it to judge per-passage rather than whole-page language.
hreflang must be reciprocal and use canonical URLs
If you declare en-US pointing at /en/x, the /en/x side must point back at zh-CN or search engines ignore the pair. Three details commonly break it: (1) the URL carries query params or a missing trailing slash and no longer matches the canonical; (2) x-default points at nothing real; (3) relative and absolute URLs are mixed.
Accept-Language q values express preference, not quality
q=0.9 expresses preference, not "this language is 90 percent good". q=0 is an explicit exclusion, not an omission. Browsers send a long list including low-q minor languages, so a server should match on the top high-q values rather than treating the whole list as a filter — otherwise a 30-language request resolves to a translation you never prepared.
Mixing language and region variants causes route mismatches
zh and zh-CN both denote Chinese but are different values: the former is a macrolanguage, the latter specifically Mainland Simplified. Likewise en covers all English regions, while en-US / en-GB / en-AU differ in spelling, dates and number formats. If the URL uses /en/ but the content declares en-US, the link points at a route that does not exist. Keep the URL language segment to the two-letter ISO 639-1 code and put region elsewhere.
How This Relates to Neighbours
For character-level encoding schemes (how UTF-8, GBK or Shift-JIS turn characters into bytes), use the Character Encodings page; this one is about language tags (BCP 47 / ISO 639).
Country codes (ISO 3166) and currency codes (ISO 4217) are out of scope here and have their own in-site pages.
To format dates and numbers per language, feed the code from this page into Intl.DateTimeFormat / Intl.NumberFormat; this page does not implement formatting.
If the goal is search-engine attribution of a multilingual site, hreflang relations matter more than the lang attribute, and they must be reciprocal.
Practical Snippet
Key points: (1) lang is inheritable, so any mixed-language fragment needs its own attribute or assistive tech will read it in the page-wide voice; (2) hreflang pairs must be reciprocal and byte-identical to the canonical (trailing slash included), with x-default pointing at a page that actually exists; (3) Accept-Language q expresses preference, not quality, and q=0 is an exclusion — parse the top few high-q tags and normalise to two-letter codes, or a long list will resolve to a translation you never wrote.
<html lang="zh-CN">
<head>
<link rel="alternate" hreflang="zh-CN" href="https://oltool.net/reference/language-codes.html">
<link rel="alternate" hreflang="en" href="https://oltool.net/en/reference/language-codes.html">
<link rel="alternate" hreflang="x-default" href="https://oltool.net/reference/language-codes.html">
</head>
<body>
<p>中文正文,浏览器会选中文语音朗读。</p>
<!-- 混排片段单独标注,否则会被整页 lang 覆盖 -->
<p>
参考 ISO 639-1,
<span lang="en">the two-letter code list</span>
共收录 184 种语言。
</p>
</body>
</html>
<!-- 解析 Accept-Language:只取前几个高 q 值,不要拿全表当过滤器 -->
function negotiate(header?: string | null): string {
const raw = (header ?? '').split(',').map(part => {
const [tag, ...params] = part.trim().split(';')
const q = params.find(p => p.trim().startsWith('q='))
return { tag: tag.trim(), q: q ? Number(q.split('=')[1]) : 1 }
})
return raw
.filter(x => x.q > 0 && x.tag !== '*') // q=0 是显式排除
.sort((a, b) => b.q - a.q)
.slice(0, 3) // 只匹配前 3 个,其余可能没有翻译
.map(x => x.tag.split('-')[0]) // 归一到 ISO 639-1 两字母
.join(',') || 'en'
}Practical Notes
- ISO 639-1 and 639-2/3 differ in granularity639-1 uses two letters (zh, en) while 639-3 reaches dialect level (cmn for Mandarin, yue for Cantonese). In Chinese contexts zh and zh-Hans/zh-Hant are not the same layer either: one is a language, the other adds a script.
- BCP 47 tag order is conventional, not arbitraryzh-Hans-CN is language-script-region. Writing only zh leaves font selection and collation to runtime guessing, which differs across systems; address Simplified Chinese users with an explicit zh-Hans.
- Do not just take the first Accept-Language valueIt carries q-weights and may list languages the user cannot read. Sort by weight, intersect with available translations, and fall back to the default only when nothing matches, instead of blindly taking the first entry.
Data Sources
- ISO 639-1 / 639-2 / 639-3
- IETF BCP 47 (RFC 5646)
- IANA Language Subtag Registry
Codes, names and definitions above come from public specifications; we claim no originality over the specification data itself. The arrangement, Chinese annotations and practical notes on this page are written by us.
| ISO 639-1 | Language | Language (EN) |
|---|---|---|
| zh | 中文 | Chinese |
| en | 英语 | English |
| es | 西班牙语 | Spanish |
| fr | 法语 | French |
| de | 德语 | German |
| ja | 日语 | Japanese |
| ko | 韩语 | Korean |
| ru | 俄语 | Russian |
| pt | 葡萄牙语 | Portuguese |
| it | 意大利语 | Italian |
| ar | 阿拉伯语 | Arabic |
| hi | 印地语 | Hindi |
| nl | 荷兰语 | Dutch |
| sv | 瑞典语 | Swedish |
| pl | 波兰语 | Polish |
| tr | 土耳其语 | Turkish |
| vi | 越南语 | Vietnamese |
| th | 泰语 | Thai |
Frequently Asked Questions
What is the difference between ISO 639-1 and ISO 639-2?
639-1 uses two-letter codes (zh, en) covering the ~180+ most common languages, common in the HTML lang attribute and APIs. 639-2 uses three-letter codes (zho, eng), covers far more languages including endangered ones, used in libraries and cataloguing.
How is the Accept-Language request header used?
The client sends Accept-Language: zh-CN,zh;q=0.9,en;q=0.8, and the server picks the closest language, with q weights as priorities. Multilingual sites read it for content negotiation (with cookie/URL to let users override).
Are zh-CN and zh-TW ISO 639 codes?
No. zh is the ISO 639-1 language code; CN/TW are ISO 3166 region codes. "zh-CN"/"zh-TW" are language-region combinations distinguishing simplified/traditional and regional habits, but the region part comes from the country-code standard, not the language standard.
How do I express a dialect or regional variant (en-US, pt-BR)?
Use the "language-region" format: language from ISO 639-1, region from ISO 3166-1 Alpha-2. en-US = US English, pt-BR = Brazilian Portuguese. The region part is optional and used for date/currency/phrasing localization.