locale.canonicalize
v0.1.0 latestCanonicalize a BCP 47 locale tag to a stable string representation.
Canonicalize a BCP 47 locale tag to a stable string representation.
Signature
function canonicalize(tag: string, opt?: CanonicalizeOptions): string | null
Type Definitions:
CanonicalizeOptions— interface
Problem
Locale tags from user input, HTTP headers, and third-party APIs arrive in inconsistent formats: mixed separators (zh_TW vs zh-TW), inconsistent casing (ZH-tw), extraneous whitespace, and duplicate separators. Using them directly as cache keys, log dimensions, or comparison operands causes silent fragmentation.
How It Works
Splits the tag by recognized separators, applies BCP 47 recommended casing (lowercase language, titlecase script, uppercase region), reassembles with the chosen separator, and trims whitespace. All transformations are deterministic and locale-independent — no Intl dependency.
Boundaries
- Does not validate that subtags are registered in the IANA Language Subtag Registry.
- Does not perform CLDR alias replacement (e.g. iw → he).
- Does not add likely subtags (use Intl.Locale for that).
- Returns null for empty/whitespace input. Throws TypeError for non-string input.
Replaces
Common boilerplate this function replaces:
tag.replace(/_/g, '-').toLowerCase().split('-').map((part, i) => i === 0 ? part : part.length === 4 ? part[0].toUpperCase() + part.slice(1) : part.toUpperCase()).join('-').trim()
Examples
locale.canonicalize("EN_us"); // "en-US"
locale.canonicalize("zh_hant_tw"); // "zh-Hant-TW"
locale.canonicalize(" "); // null
locale.canonicalize("sr-latn-rs"); // "sr-Latn-RS"
locale.canonicalize("en--US"); // "en-US"
locale.canonicalize("en_US", { separator: "_" }); // "en_US"
Standards
Caveats
- normalizeCase: false does not guarantee any specific casing; only separator normalization and cleanup are applied.
- Characters outside the acceptSeparators set are treated as part of the subtag.
- canon is an alias for canonicalize — both reference the same function.
FAQ
How to normalize a BCP 47 locale tag in JavaScript?
Use locale.canonicalize() to normalize mixed separators, casing, and whitespace into a stable canonical form compliant with RFC 5646.
How to convert zh_TW to zh-TW in JavaScript?
locale.canonicalize('zh_TW') returns 'zh-TW'. It handles underscore-to-hyphen conversion, case normalization, and whitespace cleanup.
How to convert en_us to en-US?
locale.canonicalize('en_us') returns 'en-US'. Subtag casing follows BCP 47: lowercase language, uppercase region.
How to normalize zh-hant-tw to zh-Hant-TW?
locale.canonicalize('zh-hant-tw') returns 'zh-Hant-TW'. Script subtags are titlecased, region subtags are uppercased, per BCP 47 conventions.
How to make locale strings consistent for use as cache keys?
Pass locale strings through locale.canonicalize() before using them as cache keys. This ensures 'zh_TW', 'zh-tw', and 'ZH-TW' all map to the same canonical key 'zh-TW'.
How to handle inconsistent language codes from different APIs?
Different APIs return locale tags in varying formats (underscores, mixed case, extra whitespace). locale.canonicalize() normalizes all variants to a single stable form.