Skip to main content
Translation uses gettext. The application, the active theme and every installed plugin each contribute a catalog, loaded per request for the resolved language.

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.
Languages themselves come from 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 under public/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

Targets can be 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.

Updates preserve translations

An update backs up the locale directory, extracts the new release’s strings, and merges the previous translations back in, so local edits aren’t lost when the catalogs change.