Translation Results

The notification or dialog a translation run ends in – and where to find the reason a field kept its source language.

Every run ends in one notification, or in a dialog when a language in a batch run needs attention. The notification is picked by how many texts the strategy actually translated.

A text is one piece of content handed to the strategy: a field value, the text of a block, a single structure or table cell. One field can hold many, so the counts run higher than the number of fields in the form.

What You See

Content translated: every text came back translated. A batch run says Content translated for the selected languages instead.

No field qualifies for translation: no top-level field passes fieldTypes, includeFields, and excludeFields, or every one that does sets translate: false. The message lists the eligible field types, since a narrow fieldTypes is the usual cause.

Nothing to translate: the eligible fields are empty, hold only values a provider would corrupt – a number, a URL, a KirbyTag on its own – or are containers such as blocks whose nested field types are missing from fieldTypes.

of {total} texts kept their source text: {fields}: part of the response was unusable. Those texts stay in the source language, the rest is translated. The notification names the fields whose texts kept their source text, up to three and a count of the rest.

Some texts kept their source text in {languages}: the batch counterpart. Each language is named with its fields. A batch run judges every language on its own, so a language where nothing came back usable is named here rather than diluted into a total across languages.

None of the {total} texts could be translated: the provider answered, but nothing it returned could be applied. Usually a configuration problem rather than one bad field.

of {total} languages were translated and saved: a batch run has a language that needs attention. It opens a dialog rather than a notification, with an entry for each affected language:

  • Not translated: the translation or the save failed for that language, and the entry carries the error message, such as DeepL's exhausted quota. A language with unsaved changes is not translated either – save or discard them, then translate again.
  • Not saved: another user started editing the content during the run, so the translation of this language was not saved.
  • Not started: the run stopped once another user started editing the content, before it reached this language.
  • Saved, but {field} is invalid: a field of that language fails its blueprint validation, such as a translation over a maxlength or an empty required field. Correct the field in that language.
  • was not changed: Kirby refused the title or the slug, such as a slug another page already uses.
  • kept its source text: a text of the field was rejected or failed to translate, with the reason. Once the dialog opens, it lists these for every language.

The languages that were translated stay saved.

is editing this content: a batch run found another user editing the content. Kirby saves no language while they do, so the run stops before anything is sent to the provider.

You are not allowed to edit this content: a batch run saves every language directly, so without the permission to update the content it stops before anything is sent to the provider.

Kirby found no blueprint for this content: Kirby fell back to the default blueprint for the model's template, so nothing was translated or imported. A blueprint filename that differs from the template in case is the usual cause. A blueprint that declares no fields at all is not affected, and with title: true, the title of such a page is still translated.

No importable content found: an import found nothing to copy – no eligible field, no title, no slug. Imports copy rather than translate, so they report no text counts.

The notifications about texts that kept their source text stay on screen until something replaces them.

Keeping the source text never empties a field. Re-running after fixing the cause is safe.

Finding the Reason

Notifications name counts and fields, not reasons. The batch dialog gives the reason per field, and the browser console names each rejected text:

Rejected "intro" (de): placeholder mismatch, expected 0,1, got 0. Keeping source text.

Field key, target language, and the check that rejected it. The language code is part of the line because a batch run repeats every field once per language. A placeholder mismatch adds what the check expected and what it got, on every provider. For the full list of reasons, see Rejection Reasons.

Runs that reach PHP – DeepL or a custom strategy from the Panel, the CLI, and Translator calls from your own code – fire content-translator.translate:warning for the same rejections. Wire it to your logger to catch rejections where nobody is watching a console. Panel AI translation runs in the browser and never reaches the hook.