Documentation

Locale resolution

How the locale option becomes exactly one locale: exact match, alias, one region strip, and an error otherwise, never a fallback to English.

Updated · spec 1.12.0

transform needs exactly one locale, named by the required locale option. Resolution turns the tag you pass into one of the nineteen supported locales, or throws POLYTYPO_UNKNOWN_LOCALE. There is no default locale, and no fallback to English or to any "nearest" language.

The algorithm is specified in the spec and is the same in all five runtimes. It does not use the platform's locale-negotiation library, because those libraries disagree with each other and will return a best guess for a language the tool has never heard of. Applying Finnish rules to a Portuguese text silently is worse than an error.

How it works #

  1. Presence. A missing, null, empty or non-string locale throws.
  2. Canonicalisation. _ becomes -, the language part is lowercased, the region part uppercased. This uses plain ASCII arithmetic, so the host's own locale (a Turkish machine, for example) has no effect. EN_us, en-US and en_US are the same tag.
  3. Shape check. Only two shapes are accepted: two letters (de), or two letters, a hyphen and two letters (de-AT). Three-letter codes (eng), script subtags (sr-Latn), numeric regions (es-419) and und throw.
  4. Resolve. 1. If the tag is a supported locale, use it. 2. If the tag is an alias, use its target. 3. If the tag has a region, drop the region once and try the language alone, first as a locale and then as an alias. 4. Otherwise throw.

The region is dropped once. There is no second retry, a language is never dropped, and an alias never points to another alias.

Supported locales and aliases #

Nineteen locales: cs, nl, en-GB, en-US, fi, fr, fr-CA, de-DE, de-CH, el, it, pl, pt-BR, pt-PT, ru, es, sv, tr, uk. Three aliases: en is en-US, de is de-DE, and pt is pt-PT. See all locales.

Examples #

An alias:

transform(input, { locale: "en" })

Input

"quoted"

Output

“quoted”

A region that is not supported is stripped, then the language is looked up. de-AT becomes de, which is the alias for de-DE:

transform(input, { locale: "de-AT" })

Input

"quoted"

Output

„quoted“

de-CH is its own locale and is not stripped:

transform(input, { locale: "de-CH" })

Input

"quoted"

Output

«quoted»

tr-TR has no entry of its own. The region is stripped and tr matches:

transform(input, { locale: "tr-TR" })

Input

"quoted"

Output

“quoted”

fr-BE resolves to fr; fr-CA is a separate locale with different spacing around quotes:

transform(input, { locale: "fr-BE" })

Input

"quoted"

Output

« quoted »

Case and separator do not matter:

transform(input, { locale: "EN_us" })

Input

"quoted"

Output

“quoted”

Unsupported language. Norwegian is not Swedish or Danish, and nothing guesses:

transform(input, { locale: "nb-NO" })

Input

"quoted"

Result

Throws POLYTYPO_UNKNOWN_LOCALE

Malformed tags are rejected the same way:

transform(input, { locale: "sr-Latn" })

Input

"quoted"

Result

Throws POLYTYPO_UNKNOWN_LOCALE

transform(input, { locale: "es-419" })

Input

"quoted"

Result

Throws POLYTYPO_UNKNOWN_LOCALE

transform(input, { locale: "eng" })

Input

"quoted"

Result

Throws POLYTYPO_UNKNOWN_LOCALE

Edge cases #

  • One code for every failure. A malformed tag and an unknown one both raise POLYTYPO_UNKNOWN_LOCALE. See Error codes.
  • A region silently resolves to its language. sv-FI uses the Swedish locale, and en-AU uses en-US. If a region needs different conventions, it is not distinguished from its language unless it is in the list above.
  • No environment lookups. LANG, navigator.language and Accept-Language are never read. Pass the locale of the content you are typesetting.
  • Resolved once per call. Resolution happens before any rule runs, and nothing is cached between calls.

Normative text: spec/rules/locale-resolution.md.

Already using polytypo? Add our badge

Two lines: a script tag and a span. No per-embed network call. Pick a language and a theme, then copy the code into your footer or credits. Embedding notes cover Content Security Policy. Sites with the badge are listed on Sites using polytypo.

<script async src="https://polytypo.dev/badge.js"></script>
<span data-polytypo-lang="en" data-polytypo-theme="light"></span>

Search runs in your browser. Nothing is sent anywhere.