# 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

```text
RangeError: Invalid language tag: en__US
new Intl.DateTimeFormat(tag) throws
```

## Fix it

### Step 1: Reproduce with the exact tag

```bash
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

```bash
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

```bash
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

```bash
grep -rn "en__US\|locale" src/config.js | head -5
```

Expected: 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
