/^/
MediumInternational4 min read

ISO 639-1 / BCP-47 Language Code Regex

Validates an ISO 639-1 two-letter language code, optionally followed by a BCP-47 style region subtag such as en-US or zh-CN.

#language-code#iso-639#bcp-47#international#locale

Regex Pattern

^[a-z]{2}(-[A-Z]{2})?$

Pattern Breakdown

Hover over a token to see what it does.

^[a-z]{2}(-[A-Z]{2})?$
TokenMeaning
^Anchors the match to the start of the string
[a-z]{2}Matches exactly two lowercase letters, the ISO 639-1 language subtag
(-[A-Z]{2})?Optionally matches a hyphen followed by a two-letter uppercase region subtag
-Literal hyphen separating the language and region subtags
[A-Z]{2}Matches exactly two uppercase letters, the ISO 3166-1 region subtag
$Anchors the match to the end of the string

Detailed Explanation

What it does

This pattern validates a bare ISO 639-1 language code such as en or fr, or an extended BCP-47 locale tag that adds a region, such as en-US or pt-BR. The language subtag must be lowercase and the region subtag, when present, must be uppercase, matching common convention.

Why it works

The mandatory [a-z]{2} at the start enforces the two-letter lowercase language code required by every valid tag. The entire region portion is wrapped in an optional group (-[A-Z]{2})?, so the pattern matches whether or not a region is supplied, while still requiring that when a hyphen is present it is immediately followed by exactly two uppercase letters.

Common use cases

  • Validating an Accept-Language or locale field in application settings
  • Parsing i18n resource file names that encode a locale, like messages.en-US.json
  • Filtering configuration values before passing them to an Intl or i18n library
  • Normalizing user-selected language preferences in a form

Edge cases

  • A bare language code like en or fr matches without needing a region subtag
  • A full locale tag like en-US or zh-CN matches with the optional region group present
  • Lowercase region codes like en-us are rejected since the region subtag must be uppercase per this pattern
  • Three-letter language codes like eng (ISO 639-2) are rejected since only the two-letter ISO 639-1 form is accepted

Limitations

  • Does not validate against the real list of assigned ISO 639-1 codes or ISO 3166-1 region codes, only the structural shape
  • Does not support full BCP-47 complexity such as script subtags (zh-Hans-CN) or variant subtags
  • Assumes a specific casing convention; some systems use lowercase region codes like en-us instead

Interactive Tester

Edit the pattern or text below — matching runs live in your browser.

en fr en-US

Test Cases

Editable — add your own inputs to see if they pass.

InputExpectedResult
Pass
Pass
Pass
Pass
Pass
Pass
Pass
Pass

Language Variants

Production-ready examples in 12 languages.

const langCodeRegex = /^[a-z]{2}(-[A-Z]{2})?$/;
console.log(langCodeRegex.test('en-US')); // true

Common Mistakes

Making the region subtag case-insensitive by adding the i flag, which then also accepts an incorrectly lowercased language subtag or an uppercase one

Fix: Keep the pattern case-sensitive and normalize casing explicitly with toLowerCase/toUpperCase before validating

Using an underscore separator like en_US instead of a hyphen, which is the POSIX locale convention rather than BCP-47

Fix: Match on a literal hyphen - as required by BCP-47, and convert POSIX-style locale strings before validating

Assuming this pattern supports full BCP-47 tags with script or variant subtags like zh-Hans-CN

Fix: Extend the pattern with additional optional groups, or use a dedicated BCP-47 parsing library for full compliance

Performance Notes

  • The pattern is short, fixed-length, and anchored, so matching is effectively constant time
  • The optional group (-[A-Z]{2})? does not introduce backtracking risk since its contents are fixed-length
  • Precompiling the regex once and reusing it avoids repeated compilation overhead in hot validation paths

Browser Compatibility

EngineSupportedNotes
ChromeYes
FirefoxYes
SafariYes
EdgeYes
Node.jsYes