ADR-289: autotranslate translates through nr-llm
- Status
-
Accepted
- Date
-
2026-11-11
- Authors
-
Netresearch DTT GmbH
Context
A site that wants its page trees translated in the background, only where a
translation is missing, again when the source changes, switched per table and
field, with news and file references along, needs a translation workflow. The
third-party extension thieleundklose/ is that workflow: a
queue with a backend module and a console command, a save hook, and per-table
settings in the site configuration. nr-llm has the parts it lacks: a
translation service that reaches DeepL or a language model with the site's
glossary, cost tracking and budgets. A separate extension that rebuilds the
workflow on nr-llm was considered and not chosen.
autotranslate builds a \Deep in one static helper and offers no
way to replace it. An installation that wanted nr-llm instead patched that
helper to resolve a class named in autotranslate's configuration from the
container. Its only event, After, fires after the
translation, before autotranslate stores the translated columns with plain
SQL.
The second gap is the notice. nr-llm marks the records its own runs write (ADR-239) and labels them in b13/ai-label, but autotranslate writes past both.
Decision
autotranslate keeps the workflow; nr-llm supplies the translator. nr-llm
ships Autotranslate, a \Deep subclass, as a
public service. autotranslate uses it when its setting translator names
the class. That setting is offered upstream as a configuration option
(autotranslate pull request 175), the
mechanism of the patch made official, so that nr-llm depends on no unreleased
autotranslate code: it works with the patch and with a release that carries
the option alike.
The translator overrides what autotranslate calls. translate sends
the texts of one call to
Translation in one batch,
a blank text excepted. DeepL's tag_ becomes nr-llm's tag handling,
so rich text keeps its markup. A target code keeps its region in nr-llm's form
(EN- becomes en-), which nr-llm's DeepL translator needs; a source
code carries none. The nr-llm translator is the setting
autotranslate.; empty leaves the choice to the service, which
uses the language model. get reports no quota, because nr-llm keeps
its own budgets. list finds none, because a DeepL glossary id
means nothing to the language model. The language lists are DeepL's codes,
which autotranslate stores in the site configuration. The parent class is built
with a placeholder key and a server under the reserved .invalid domain, so a
method the class does not override fails without sending a text to DeepL. A
translator that answers fewer texts than it was given fails the call: a missing
answer would move every later text into the wrong field. So does a blank answer,
an answer of markup without visible text for a text that has some, and one cut off at the output limit (truncated, ADR-209):
autotranslate stores what it gets, and a half text in an unattended batch would
stand as the translation.
On the command line the key is read as the worker. autotranslate's batch
command is a console worker. It reads the provider key as
worker., the identity ADR-234 gives
nr-llm's own queue worker; a web request keeps its user. The integration reads
the setting itself and does not use the agent module's scope class, because a
translation component must not depend on the agent module
(ADR-090).
A machine-translated record is marked in the write that stores it. Two
listeners on After add columns to the translated ones: the
marker tx_ on pages and content elements, and, when AI Label
is loaded and enables the table, its tx_. The label follows
the rule of nr-llm's own translations: AI-modified, an AI-generated origin
kept, the human review cleared. Both apply only while translator names
nr-llm's translator, and only when a text column (input, text,
flex) holds text the translation changed from the source; the fields
autotranslate copies unchanged do not count. A failed translation dispatches no
event and stores neither.
Registered only for an active autotranslate. A compiler pass registers the
integration when autotranslate's batch command is a service, and the AI Label
listener when AI Label's API is one, as for Ai.
Consequences
- Background runs, retranslation on change, per-field settings, news and file references stay autotranslate's, with its limits: a batch translates every configured field again, and a change of the source overwrites a hand correction in the changed field.
- Until autotranslate releases the
translatoroption, an installation carries it as a patch. nr-llm does not change when the release arrives.Class - The label is written with plain SQL in autotranslate's write, not through AI Label's API. A write through the API would be a second DataHandler run with its own history entry and save hooks, made before the text it labels is stored. It therefore leaves no history entry, as autotranslate's text write leaves none.
- In the backend a translation routed through nr-llm counts for the editor who
started it. On the command line the worker identity only reads the key; the
usage is recorded for the backend user of the process,
_cli_, as for any other nr-llm call there. - A failed translation leaves the record autotranslate's localization created, holding the copied source text, as a DeepL error does.
- nr-llm's site glossary (ADR-208) does not apply:
translatecarries no page, so the translator cannot tell the site.Text () - A translation that autotranslate makes with DeepL itself is not marked.
Alternatives considered
A new extension on nr-llm. It would own the workflow end to end, at the price of rebuilding a queue, a module, a context menu, a save hook and per-table settings that autotranslate maintains already.
A PSR-14 event upstream that hands over a translator. It would avoid a class name in the configuration. nr-llm would then depend on an event class that no autotranslate release contains yet, and could not be tested or shipped before that release.
Marking through nr-llm's provenance event. After
names an agent run and is dispatched after the write. An autotranslate
translation has no run, and After comes before the write.
Revisit when
- autotranslate passes the page or site to its translator, so the site glossary can apply.
- autotranslate offers an event or an interface for the translator instead of a class name.