Resolution Ladder
The exact order /v1/resolve runs in, and what each match value means.
Resolution is two lookups and a ladder. All of it runs against an in-memory snapshot of the catalog — no query per request.
Step 1 — find the provider
The value you sent is normalised and matched against provider codes, then provider aliases. No match is provider_not_found (404).
Step 2 — find the language
The value you sent is normalised (see Aliases and Normalisation) and matched against canonical codes first, then aliases. Canonical codes are registered first and never overwritten, so an alias can never shadow a real language code.
If nothing matches exactly, the code is progressively shortened:
zh-Hant-HK → zh-hant → zh-hk → zh
Both qualifier axes appear because providers disagree about which one matters — DeepL wants zh-Hans, Papago wants zh-CN. A tag with an unknown qualifier degrades to the language it qualifies rather than 404ing: de-AT-1996 resolves to de-AT, reported in matched_alias.
Still nothing? language_not_found (404).
Step 3 — the ladder
Now that both are known, in order:
1. exact — an explicit row for this pair
Someone configured it. Return its code.
{ "provider": "deepl_api", "language": "zh-CN", "code": "zh-Hans", "match": "exact" }
If that row says is_supported: false, stop here: supported: false, code: null. The pair is known not to work and the request should not be attempted.
2. variant — a less specific tag of the same language has a row
No row for zh-Hant-HK, but one for zh-TW? Use it, and name it in via_language.
{ "language": "zh-Hant-HK", "code": "zh-Hant", "match": "variant", "via_language": "zh-TW" }
3. canonical — the language carries no qualifier
A base language needs no mapping: its own code is the answer.
{ "language": "uk", "code": "uk", "match": "canonical" }
This mirrors the console exactly
get_lang_code_by_provider_and_language starts with if language.abbreviation: return language.abbreviation — it returns the two-letter code before it ever looks at the provider. That is why the console only ever stored codes for regional languages, and why a base language with no row is normal rather than a gap.
4. The provider's fallback policy
Only reached for a regional language with nothing configured. See Fallback Policies for how to choose one.
| Policy | Result | match |
|---|---|---|
base_language |
the base language's code — pt-BR → pt |
base_language |
passthrough |
the canonical code unchanged | passthrough |
strict |
supported: false, code: null |
— |
Every provider currently ships with base_language, which is what the console does today (lang_region_list.code[:2]). See Fallback Policies for why strict is not a default.
Reading match as a caller
match |
Trust level | What to do |
|---|---|---|
exact |
Configured deliberately | Use it. |
variant |
Configured for a near tag | Use it. Worth pinning if it recurs. |
canonical |
Nothing to decide | Use it. |
base_language |
Derived — you lose the region | Use it, but if regional fidelity matters to you, check the pair. |
passthrough |
Derived — untested against this API | Use it, and watch for provider errors. |
Most callers can ignore match entirely and just send code. It is there for the ones that cannot.
Worked example
GET /v1/resolve?provider=deepl_api&language=zh_hant_hk
deepl_apimatches a provider code.zh_hant_hknormalises tozh-hant-hk. No canonical code matches; the chain trieszh-hant— that is an alias ofzh-TW. Language =zh-TW,matched_alias: "zh-hant".- DeepL has an explicit row for
zh-TW→zh-Hant.
{ "query": "zh_hant_hk", "provider": "deepl_api", "language": "zh-TW",
"code": "zh-Hant", "supported": true, "match": "exact",
"matched_alias": "zh-hant", "via_language": null }
Try it yourself
The admin panel's Tools page has a live tester that runs this exact code path, so you can check a pair without writing a request.
Next
- Fallback Policies — what each policy is for
- Pin a provider code — turning a derived answer into an
exactone