---
title: "ADR-213: Approval preview lines are in the acting user's language"
manual: "LLM Foundation"
version: "0.39"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-213@0.39"
source: "Adr/Adr213ApprovalPreviewLinesAreInTheActingUsersLanguage.rst"
rendered: "2026-10-07T21:08:05+00:00"
---

# ADR-213: Approval preview lines are in the acting user's language {#adr-213}

-   *Status:* Accepted
-   *Date:* 2026-10-06
-   *Authors:* Netresearch DTT GmbH

## Context {#context}

A run that needs a human approval suspends, and every tool that implements
`ToolPreviewInterface` describes the pending call in lines of text
([ADR-136](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-136@0.39)). The lines are produced at the moment of suspension,
persisted in the suspended state, shown on the approval card after a per-viewer
authorisation, and produced a second time when the run resumes, to compare them
with what the approver was shown ([ADR-184](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-184@0.39)).

All of them are English literals. For a German editor that makes the card a mix:
the buttons and the labels around it are German, the lines in the middle read
`New page under page [2] "Open":`, `navigation title:` and
`first among the subpages`. The editorial guidelines for the assistant
(rules 14 to 18) ask for approval texts in the editor's language, in editor
vocabulary, in a fixed order, without internal field names.

Two constraints follow from the design above and decide where the language
can come from:

-   The lines are compared byte for byte at resume. Their language must be the
    same at suspend and at resume, on the synchronous path and in the queue
    worker, or every approval of a localised tool bounces as stale.
-   The run owner and the person who approves can be different people
    ([ADR-130](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-130@0.39), [ADR-133](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-133@0.39)). One set of lines is
    persisted per pending call.

## Decision {#decision}

**The language of a preview line is the language of the run's acting backend
user,** the `lang` column of the user the run executes as, read from the
`ToolExecutionContext` ([ADR-083](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-083@0.39)). Never the ambient
`$GLOBALS['LANG']`, which belongs to whoever runs the call, and never the
viewer's language.

-   A user without a `lang` value gets English, which is what TYPO3 itself
    does for such a user. A language without a catalogue of its own gets the
    English source text.
-   **When the viewer's language differs from the acting user's,** the card
    shows the lines in the acting user's language, complete and in one language.
    It does not mix, and it does not retranslate. In the usual case, an editor
    who starts the run in the chat and approves it there, the two are the same
    person.
-   **When the acting user changes their language between suspend and resume,**
    the re-computed lines differ from the persisted ones and the approval bounces
    once, with the current lines shown again ([ADR-184](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-184@0.39)). That is
    the existing staleness path doing its job; no special case.

**The texts live in the extension's catalogue**, `locallang.xlf` and
`de.locallang.xlf`, under `approvalPreview.*`. A closed enum,
`ApprovalPreviewLabel`, names every entry, and
`ApprovalPreviewTranslator` resolves one for a given user. Tools do not
build a sentence from English words: they pick labels and pass the values.
Quotation marks, the word for "empty" and every sentence are catalogue entries,
so a language decides its own.

**A unit test walks the enum** and fails when a label has no English or no
German text, when the two texts take different placeholders, when the German
text is the English one, when a text names an internal field or tool, or when
the catalogue holds an `approvalPreview.*` entry that no label names.

**The order of the lines follows rule 16 of the guidelines:** what changes,
where, the current state, the new state, the consequences. The last line is
**"Technical details"**: UIDs and table names, for support (rules 10 and 26).
It is not an optional extra: it keeps the approval bound to the exact records
the call names, because the lines above it name pages by title and two pages
can share one ([ADR-184](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-184@0.39)).

**What stays English.** A refusal line is the string the tool's `execute()`
hands the model as well; it is shared on purpose, and a refusal at preview time
tells the approver the call would fail, not what it would do. The tool results
that go back to the model stay English too; they are not approval text.

**Scope of this decision.** It is applied to `create_page_draft`,
`move_page` and `delete_record`. The other tools that implement
`ToolPreviewInterface` keep their English lines until they are converted
to the same mechanism; each of them needs its own list of labels and its own
expected text, and the guidelines' rules 19 to 21 (consequences) need to be
checked against what each tool actually reads.

**No new reads.** The consequence lines show what the tools already read for
their plan: translations, subpages, the number of records stored on a page,
the number of references, recoverability. A line the tool cannot know is not
shown. In particular `delete_record` does not say anything about redirects:
it does not look at them, and whether core creates one on a delete is not
something this decision verifies.

## Considered alternatives {#considered-alternatives}

**English only** (the status quo). Rejected: it contradicts rule 17 and mixes
languages on every German card.

**The viewer's language at render time.** The right answer to the second
constraint, and the larger change: the persisted state would have to hold
label identifiers and values instead of text, the comparator would compare
those, and both card renderers would translate at display time. The loop,
the state encoding and the chat panels all change. Not rejected as a goal;
rejected as part of this step, because it is a format change of persisted
state and the acting user is the viewer in the common case. If approvals by
someone other than the run owner turn out to be common, this is the next step.

**The ambient backend language** (`$GLOBALS['LANG']`). Rejected: it is the
language of the process that happens to run the call, and it differs between
the request that suspends and the worker that resumes ([ADR-083](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-083@0.39)).

## Consequences {#consequences}

● A German editor reads a German approval card for these three tools: the
heading names the editorial action, the lines carry no field name, and the
consequences of a delete are listed line by line.

● The English text is the catalogue's source text, so an installation in any
other language sees the same English lines as before in structure, and a
translation file for that language works without code.

◐ The lines of these three tools changed in wording and order. A caller that
matched on the old English strings, rather than showing the lines, has to
change; none in this repository did except the tools' own tests.

◐ A viewer whose language differs from the run owner's reads the owner's
language (see above).

✕ The other tools' previews are still English, so a card for one of them still
mixes languages until it is converted.

✕ A refusal shown in a preview is still English.
