Features

Markdown

Translation workspace

Translation workspace is the multi-locale editing UI on the collection item form in externa-core 1.2.1: one active locale at a time, toolbar completeness, copy/apply helpers, and optional AI translate. It is not a separate form mode — there is no Translate toggle and no side-by-side Source/Target panes.

Related

Project settings (content locales) · Collection items · Field types · AI configuration · Public CMS API (locale / include_all_translations)

Prerequisites

  1. Configure content locales under Project settings (at least two codes, e.g. en + it).
  2. Mark fields Translatable on the collection schema.

The toolbar locale control appears when the item form has fields and more than one content locale. Completeness scores appear when there is also at least one translatable field.

Toolbar locale

In the item form toolbar (resources/js/pages/collections/items/form.tsx):

  • A locale dropdown lists each content locale (flag + name).
  • When completeness is available, each row shows filled/total next to the locale (data-test="translation-completeness").
  • Scores come from saved form defaults (contentDefaults): a locale slot counts as filled only when the value is non-null and not an empty/whitespace string.
  • Choosing a locale sets the shared content locale so every translatable field switches to that locale (ContentLocaleProvider via defaultLocale).

When the form is editable, the same menu also offers:

Control (UI label)en.json keyEffect
Apply to all localescollections.localized.applyAllCopy the current locale’s values into every other locale
Apply to empty localescollections.localized.applyEmptySame, but only where the target locale slot is empty

Saving still writes the usual EAV locale slots — no separate storage path.

Per-field locale menu

Each translatable field is wrapped by LocalizedField (resources/js/components/collections/localized-field.tsx), rendered from resources/js/components/collections/dynamic-item-fields.tsx. Only the active locale’s control is visible; other locale inputs stay in the DOM (hidden) so they still submit with the form.

Control (UI label)en.json keyWhen
Copy from {locale}collections.localized.copyFromExactly one other locale
Copy from…collections.localized.copyFromMenuTwo or more other locales (opens a submenu)
Apply to all localescollections.localized.applyAllCopy actions enabled (not readonly)
Apply to empty localescollections.localized.applyEmptyCopy actions enabled (not readonly)
Translate with AI from {locale}collections.localized.translateWithAiFromAI allowed and exactly one other locale
Translate with AI from…collections.localized.translateWithAiFromMenuAI allowed and two or more other locales

{locale} is the display name of the source locale (e.g. “English”), not the code. Copy and AI write into the active field locale. Nothing is persisted until you save the item.

Busy / feedback strings from the same block: Translating…, Translated, No source text to translate, AI translation failed, AI is offline.

AI translate

Per-field AI actions show only when the editor has can-use-ai and the field is not readonly. Before translating, the client checks AI status; if offline it shows AI is offline.

Externa posts to POST /ai/translate-fields (resources/js/lib/ai-translate.ts → AiTranslateFieldsController), throttled to 20 requests per minute, up to 50 fields per call (the item form sends one field per request). ContentTranslator is locked to the local provider (#[Provider('local')]); configure LOCAL_AI_* (optional LOCAL_AI_TRANSLATE_MODEL for a faster instruct model). TipTap and markdown fields remount with the translated value. Empty source text stays empty and is not sent.

The request is blocking (no streaming). For local thinking models, set LOCAL_AI_TRANSLATE_MODEL and keep nginx fastcgi_read_timeout high (the Docker image uses 600s) — see AI configuration.

Out of scope

  • A Translate toggle, Source/Target locale pickers, side-by-side locked source + editable target, and Copy all from {locale} — those existed briefly in core and were removed before v1.2.0; they are not in 1.2.0 / 1.2.1.
  • Form-wide “translate every field with AI” from the toolbar locale menu — AI translate is per-field only.
  • Machine translation vendors (DeepL, Google Translate) and Crowdin/Lokalise sync; AI translate uses the configured local Externa AI provider, not cloud providers via AI_DEFAULT_PROVIDER.