Agent Wranglers

LOC-23Recommended

Fall back to the closest supported locale

Choose each request's locale from the user's saved choice first, then Accept-Language, then the default. Match against the supported list by removing subtags from the end and following CLDR parent locales.

How to do it

RFC 4647 lookup turns zh-Hant-TW into zh-Hant, then zh, until one matches. CLDR adds parent locales that plain truncation misses, such as es-MX falling back to es-419 before es. Use a matcher from your i18n library, such as @formatjs/intl-localematcher or Babel's negotiate_locale, instead of comparing strings. Treat Accept-Language as a first guess only, because it names a language, not where someone lives. Save an explicit choice from Let people pick their language and let it win.

Examples

JavaScript example

import { match } from '@formatjs/intl-localematcher';
const SUPPORTED = ['en', 'sv', 'pt-PT', 'zh-Hant'];
const DEFAULT_LOCALE = 'en';
export const resolveLocale = (saved, acceptLanguages) =>
  match(saved ? [saved] : acceptLanguages, SUPPORTED, DEFAULT_LOCALE);
resolveLocale(null, ['zh-Hant-TW', 'en']); // "zh-Hant"

Python example

from babel import negotiate_locale

SUPPORTED = ["en", "sv", "pt_PT", "zh_Hant"]
DEFAULT_LOCALE = "en"

def resolve_locale(saved: str | None, accept_languages: list[str]) -> str:
    wanted = [saved] if saved else accept_languages
    return negotiate_locale([tag.replace("-", "_") for tag in wanted], SUPPORTED) or DEFAULT_LOCALE

How to check

A unit test resolves pt-AO, zh-Hant-TW, de-CH and xx against the supported list. Each gets the expected locale, and xx gets the default.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/localization/locales-and-preferences/locale-fallback">Agent Wranglers localization playbook</a>. In code you write, add a comment next to it with the same link.