Translators
Machine translation of content. A translator is a row in PageBlocks → Translators; the component calls the provider's API and stores the result as a translation of the original.
This is about content, not about interface strings — those live in lexicons and are handled by Localization.
Drivers
| Driver | Provider |
|---|---|
chatgpt | OpenAI |
claude | Anthropic |
gemini | |
grok | xAI |
deepl | DeepL |
Four of them are LLMs and one is a dedicated translation service; they share one interface, so switching is a matter of changing the row.
Configuring one
| Field | What it is |
|---|---|
name | Human-readable name |
driver | One of the keys above |
api_key | The provider's key |
model | Model name, for the LLM drivers |
base_url | Override the endpoint — for a proxy or a self-hosted gateway |
prompt | Instruction template, LLM drivers only |
published_at | An unpublished translator is not used |
An unknown driver is not an error: it is written to the MODX log and the factory returns nothing.
The prompt
The default asks for the translation only, preserving HTML and placeholders. Your own template can use two placeholders:
Translate the following text from [[+from]] to [[+to]].
Reply with the translation only, preserving any HTML markup and placeholders:[[+from]] and [[+to]] are replaced with the language codes in upper case. This is the knob to reach for when a provider keeps "helpfully" adding explanations around the translation, or mangles your {$placeholders}.
It never throws
translate() always returns a string. Every failure path returns the original text and writes the reason to the MODX log:
| Situation | Result |
|---|---|
| Empty or numeric text | Returned unchanged, no request made |
| No API key | Original text, reason logged |
| Transport failed or provider errored | Original text, reason logged |
| Response could not be parsed | Original text, response logged |
That is deliberate — a translation run over hundreds of rows should not abort halfway because one request timed out. It also means a silent failure looks like "nothing was translated". When that happens, read the log before changing the settings.
Numbers are skipped on purpose: sending 2024 to an LLM and getting a paraphrase back is a real risk, and there is nothing to translate anyway.
Token budget
For the LLM drivers the output allowance scales with the input — roughly twenty tokens per word plus a floor for short strings, capped at 8192. Translations are about as long as their source, and the cap is there so a runaway response is not billed.
What gets translated
Translations are stored per language in pb_translations, against the languages configured in pb_languages. The page keeps the original when a translation is missing, so a half-finished translation never blanks the site.
Cost is yours
Every one of these providers bills per request. The component does not batch, cache or deduplicate across runs — translating the same field twice costs twice. Start on a few rows and look at the result before pointing it at a whole table.