Home/Languages/Aliases and Normalisation ENУКРРУС API Reference (ReDoc) ↗

Aliases and Normalisation

How any spelling of a code lands on the right language — including the ones that are broken.

Codes reach us from TMS exports, customer APIs, spreadsheets and a decade of accumulated integrations. They do not agree on much. Normalisation is what makes them comparable.

Two forms of a code

The lookup key — lowercase, - separated, whitespace stripped. pt_BR, PT-BR, pt-br and pt - br all become pt-br. Aliases are stored in this form and matched by exact equality, so the hot path is one indexed comparison and never a LIKE scan.

The canonical form — BCP-47 casing: primary subtag lowercase, script Titlecase, region uppercase. pt-BR, zh-Hans, sr-Latn-RS. This is what the API returns and what you should store.

Canonicalisation also repairs subtag order. BCP-47 puts script before region, so sr-RS-Latn and sr-Latn-RS parse to the same tag and both render as sr-Latn-RS.

Homoglyph repair

Before either form is computed, codes go through a homoglyph pass.

This is not hypothetical. A code whose first character is U+0441 CYRILLIC SMALL LETTER ES rather than ASCII c renders identically in every font, and without repair could never match anything a caller sends — the kind of defect that makes a language look simply absent.

The repair maps the Cyrillic letters that are visually identical to ASCII ones — а е о р с х у і ј — and only when the result becomes pure ASCII, so a genuinely Cyrillic-scripted value is left alone rather than mangled. Full-width Latin (en) is folded by NFKC in the same pass.

What aliases cover

Kind Examples
The language's own canonical code pt-BR
Other spellings of the same language es-la, ES-LA for es-419
ISO 639-2/3 and deprecated ISO 639-1 codes deu, ger, zho, iw, in, ji, mo
Whatever a given integration turns up with por-br

GET /v1/languages/{code} returns the full list in aliases, so you can check whether a spelling you receive is already covered.

Uniqueness

An alias belongs to exactly one language, globally. This is enforced by the database, not by convention.

It matters because inbound resolution must be a function: the same code must always mean the same language, on every request and in every process. If a code could claim two languages, the answer would depend on which row happened to be read first — and the same job could resolve differently twice in a row.

So when two languages would both plausibly claim a spelling, the contest is decided once, up front, and recorded — never at request time.

What you do not need an alias for

Case and separator variants. pt_BR already matches the alias pt-br; adding pt_br as a separate row would be redundant.

Checking a code without resolving it

/v1/normalize answers the provider-independent question — which language is this, and what do we call it?

curl "https://languages.service.custom.mt/v1/normalize?language=ZH_HANS"
{ "query": "ZH_HANS", "known": true, "matched_alias": "zh-hans",
  "language": { "code": "zh-CN", "name": "Chinese (Simplified)", "aliases": ["chs", "zh-hans", …] } }

Unlike the other endpoints, an unknown code is not an error here — known: false comes back with 200. That lets a caller classify a whole list in one pass instead of catching 404s. See Normalise incoming codes.

Next