Reading a locale code
Web standards spell locales as BCP 47
language tags: a language subtag (zh), an optional four-letter script
(Hant), an optional region, either two letters or three digits
(TW, 419), separated by hyphens. Case carries no meaning,
but the canonical form writes the language in lowercase, the script in title case and the region in uppercase, which is what
Intl.getCanonicalLocales() returns above. It also replaces deprecated codes, so
iw comes back as he.
The "likely subtags" row is what CLDR assumes when parts are missing: plain pt maximizes
to pt-Latn-BR, and zh-TW to
zh-Hant-TW. That is why browsers format pt the Brazilian
way unless you say pt-PT.
Hyphens on the web, underscores in Laravel
Laravel does not validate the locale
string: App::setLocale() accepts anything without a slash, and the translator then loads
lang/{locale}/*.php and lang/{locale}.json. The
underscore spelling (pt_BR) is the convention because other parts of the stack match on it:
Carbon's locale files are named pt_BR.php and zh_Hant_TW.php,
and the plural table behind trans_choice() lists exact strings such as
pt_BR. A locale missing from that table, hyphenated or not, falls back to the first
form for every number, which the tool points out above.
Laravel also does not fall back from a regional locale to its language: with pt_BR active,
a key missing from lang/pt_BR goes straight to your
fallback locale,
not to lang/pt. Your HTML is the other way round: the
lang attribute and hreflang values need the hyphenated tag, which is why Laravel's starter
kit layouts print str_replace('_', '-', app()->getLocale()).
A worked example: Brazilian Portuguese
Say a ticket asks for "pt-br support". Typing it here gives the canonical tag pt-BR
and the Laravel name pt_BR. So the translations go in
lang/pt_BR/ (or lang/pt_BR.json), your middleware calls
App::setLocale('pt_BR') when the URL segment is pt-br,
and the layout prints lang="pt-BR". The plural row shows three CLDR categories, where
one covers 0 and 1, unlike European Portuguese, and the formatting rows show the comma
decimal separator your number and currency output should use. Share the result with
#pt-BR on the URL.
Direction and plurals
Direction comes from the script, not the language alone: Arabic, Hebrew, Persian and Urdu are right to left, but
uz-Arab is right to left while plain uz is not. When the
browser supports Intl.Locale text info the tool asks it directly; otherwise it checks the
likely script against a list of RTL scripts. Set it on the page with dir="rtl" next to
lang; see the RTL glossary entry
and RTL in Laravel and Tailwind for the CSS side.
The plural categories are the CLDR set your browser uses, with sample numbers for each. They tell you how many forms a translator has to write, which can differ from what Laravel's pipe syntax resolves; the pluralization tester checks any number, and the language guides cover each language's Laravel setup in detail. Building language versions of a site? The hreflang generator validates the codes for search engines.