Resolving the language
LocaleMiddleware picks one, in this order:
1
URL prefix
A path such as
/de-DE/pricing, when that language is enabled. A prefix for a disabled language redirects to the unprefixed path.2
The signed-in user's preference
Their account language.
3
The locale cookie
Set by a language switcher.
4
Accept-Language
The browser’s preference.
5
The default language
From the language configuration.
locale/locale.json, which lists each code, its English label, whether it’s enabled, and its writing direction. Administrators enable languages and pick the default under Settings → Languages.
Domains
Plugins normally set
X-Domain: messages, so their strings merge into the default domain and a plain __() resolves. Themes keep their own domain, which is why theme templates call d__('theme', …).
Translation functions
Registered globally, and as Twig functions and filters:
Contexts matter more than they look: they let one English word be translated differently as a button and as a label.
Browser catalogs
Strings used from JavaScript are compiled into per-language files underpublic/locale, with a content hash in the name. Templates reference the right one through a Twig function, and the catalog is regenerated when translations change or the cache is cleared.
Commands
core, a package name, or an arbitrary path, which is what lets a theme be translated from its own repository. These three commands work even without a database configured.