Language Catalog
What is in the catalog, where it came from, and what to do with the odd rows.
348 languages, 237 aliases, seeded from the legacy console database and curated from there.
Browsing it
curl "https://languages.service.custom.mt/v1/languages?q=portuguese"
curl "https://languages.service.custom.mt/v1/languages?kind=regional&limit=50"
curl "https://languages.service.custom.mt/v1/languages/pt-BR"
q matches the name, the canonical code and every alias, so Brazil, pt-br and por-br all find the same row.
What a language row carries
| Field | Meaning |
|---|---|
code |
canonical BCP-47 code — store this one |
name |
English display name |
kind |
base (no qualifier) or regional |
base_language_code |
for a regional variant, the language it qualifies |
script, region |
parsed out of the code, for filtering |
is_active |
false for rows kept for traceability but not usable |
also_known_as |
names folded in when duplicate console rows merged |
aliases |
every inbound spelling that resolves here |
legacy_language_ids |
the cabinet_language.id values this row came from |
legacy_language_ids is the audit trail back to the console. More than one id means the seed merged duplicates.
Merges
Five pairs of console rows described the same language under different ids and were folded together:
| Canonical | Kept | Folded in |
|---|---|---|
fa |
Persian (41) | Persian (222) — a straight duplicate |
uk |
Ukrainian (320) | Ukrainian (Ukraine uk) (162) |
zh-CN |
Chinese (Simplified) (217) | Chinese (PRC) (271) |
zh-TW |
Chinese (Traditional) (176) | Chinese (Taiwan) (278) |
nb |
Norwegian (Bokmal) (221) | Norwegian (Norway) (203) |
Both ids stay in legacy_language_ids and the discarded name is kept in also_known_as, so a lookup by either console id still lands correctly.
Inactive rows
Six console rows had neither a two-letter abbreviation nor any region code — no code at all, so nothing could ever resolve to them:
Serbian (Cyrillic-Trados) · Chinese (SmartCat) · Serbian (Cyrillic-XTM) · Serbian (Latin-Trados) · Chinese (Hong Kong-2) · Chin
They are imported with a placeholder x-… code and is_active: false rather than dropped, so the information is not lost and an operator can give them a real code from the admin panel. Filter them out with ?active=true.
Most are integration-specific duplicates of languages that already exist (Serbian (Cyrillic-Trados) is sr-Cyrl). Chin is a real language family that never got a code.
Choosing the canonical code
Where a language had several spellings, the canonical one is chosen by:
- a usable two-letter
abbreviation— those rows are the ISO 639-1 base languages, - otherwise: the console's
is_defaultflag, an explicit human choice, - then fewer subtags — a canonical code should carry no qualifier it does not need. Cebuano is
ceb, notceb-Latn-PH; Serbian Cyrillic issr-Cyrl, notsr-Cyrl-RS. The dropped qualifiers stay as aliases, so precision is preserved on the way in, - then the most textbook BCP-47 spelling,
- then the lowest legacy id — what the console's unordered
.first()returns today, so ties keep current behaviour.
Note this is not how a provider's outbound code is chosen. What a provider's API accepts is a fact about that API, and shortness is irrelevant to it — see Providers and Vendors.
Known gaps worth fixing
The catalog inherited the console's blind spots. These are visible in the admin panel's coverage view and are good first edits:
Baidu and traditional Chinese
Baidu uses its own code set (zh, cht, jp, kor), not BCP-47. With no explicit row, zh-TW falls back to zh — which is what production sends today, and it is wrong: Baidu wants cht. Pinning it takes one edit; see Pin a provider code.
[!warning] DeepL's regional English and Portuguese
DeepL has three explicit rows (zh-CN, zh-TW, pt-BR). en-GB falls back to en, though DeepL accepts and distinguishes EN-GB and EN-US.
Neither is a regression — both match what the console does now. They are simply now visible.
Next
- Aliases and Normalisation — how codes reach these rows
- Add a new language