intl.datetimeformat invalid language tag bcp47 error
Covers Intl.DateTimeFormat RangeError on invalid BCP47 tags: canonicalize, fall back, fix the source. Use it when the constructor throws on the tag. Not for wrong-language output.
Fix Intl.DateTimeFormat invalid language tag BCP47 error
TL;DR
The tag you passed is not valid BCP47, so canonicalize it before constructing the formatter. Intl validates strictly and throws RangeError on anything malformed. Normalize underscores to hyphens, fix subtag order, and fall back to a default when the tag is beyond repair.
The error
RangeError: Invalid language tag: en__US
new Intl.DateTimeFormat(tag) throwsFix it
Step 1: Reproduce with the exact tag
node -e "try{new Intl.DateTimeFormat('en__US')}catch(e){console.log('throws:',e.message)}"Expected: You confirm the tag is the problem.
Step 2: Canonicalize the tag
node -e "const t='en__US'.replace(/_+/g,'-').replace(/-{2,}/g,'-'); console.log(t); console.log(Intl.getCanonicalLocales(t))"Expected: The tag becomes valid BCP47 or the canonicalizer throws honestly.
Step 3: Add a safe fallback
node -e "function safeTag(t,fb='en-US'){try{return Intl.getCanonicalLocales(t)[0]}catch(e){return fb}}; console.log(safeTag('en__US'), safeTag('!!!'))"Expected: Bad tags fall back instead of throwing.
Step 4: Fix the source that emits the bad tags
grep -rn "en__US\|locale" src/config.js | head -5Expected: The source now emits clean tags.
When to use this
- Intl.DateTimeFormat throws RangeError invalid language tag
- Locale tags come from user input or a CMS
When NOT to use this
- The tag is valid but the language is wrong, check the tag value
- The formatter works but output looks off, check the options
Tool and version compatibility
- Intl.getCanonicalLocales, all modern runtimes
- BCP47 tag structure
Variant phrasings
double underscore from a join bug
Classic. Joining [lang, region] with when one part is empty gives en. Trim empties before joining.
tag with the right parts in the wrong order
Order is language, script, region, variants. zh-Hans-CN is valid, zh-CN-Hans is not.
Why it happens
BCP47 has a strict grammar: subtags separated by single hyphens in a fixed order. Double separators, underscores, or misplaced subtags fail validation, and Intl constructors throw RangeError rather than guess.
Edge cases
- Intl.getCanonicalLocales throws on invalid input, wrap it in try/catch
- Grandfathered tags like i-klingon are valid, do not reject them in validation
- Private-use x- subtags are valid, keep them when present
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_VelybfcRLB61q-xlMaicig
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.