Languages
Multilingual content lives in three tables: the languages themselves, the translations, and the translators that can fill them in.
Interface strings are a separate matter — those are lexicons, and lang() reads them. This page is about content.
A language
A row in PageBlocks → Languages.
| Field | What it is |
|---|---|
key | Short code used everywhere in the code — en, ru, de |
name | Name shown to a human |
culture_key | MODX culture key |
locale | System locale, for date and number formatting |
url_prefix | Prefix in the address. Empty for the default language |
is_default | The fallback language |
translator_id | Which translator fills this language in |
published_at | An unpublished language does not exist for the front end |
One language means no multilingual behaviour at all
Multilingual mode switches on only when there is more than one published language. A single language costs nothing: no prefixes, no resolution, no extra queries.
Prefixes and addresses
The default language has no prefix. That is a deliberate asymmetry: an existing monolingual site keeps every one of its addresses when a second language is added, so nothing has to be redirected.
/about default language
/en/about an additional languageThe address is resolved from the prefix in the URI; when no prefix matches, the default language wins.
Building an address is done in one place, so there is one rule rather than one per caller: the lang_url() helper and the route modifier both go through the same resolver. It is idempotent — an address that already carries a prefix is not prefixed twice. That matters because a caller usually cannot know: some places hand over a bare URI and others an already-prefixed one.
Translations
Stored per language in pb_translations, keyed by the owner — model_type plus model_id — and the context.
The resource fields are stored explicitly: pagetitle, longtitle, description, introtext, content, menutitle, alias, uri. Constructor fields ride along in the row's data, the same way they do on the object itself.
A missing translation shows the original
A field with no translation falls back to the source text rather than rendering empty. A half-finished translation therefore never blanks a page — which also means an untranslated site looks like it is working, so check the coverage rather than the appearance.
Filling them in
By hand in the manager, or automatically — assign a translator to the language and the component will call the provider's API. Drivers, prompts, costs and the failure behaviour are on the Translators page.
Copying between languages
Blocks can be copied from one language to another — see Copying.