el-schneider/statamic-magic-translator

Translate Statamic content

Maintainers

Package info

github.com/el-schneider/statamic-magic-translator

Type:statamic-addon

pkg:composer/el-schneider/statamic-magic-translator

Fund package maintenance!

Buy Me A Coffee

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.1 2026-04-06 07:41 UTC

This package is auto-updated.

Last update: 2026-04-06 07:49:33 UTC


README

Magic Translator

Statamic Magic Translator

Translate Statamic entry content across multi-site localizations using LLMs or DeepL — with full support for Bard, Replicator, Grid, and deeply nested content structures.

Features

  • LLM & DeepL support: Translate via any Prism-supported provider (OpenAI, Anthropic, Gemini, Mistral, Ollama, …) or DeepL's dedicated translation API
  • Deep content awareness: Recursively walks Bard fields, Replicators, Grids, and Tables — translates text while preserving structure, marks, and custom extensions
  • Async processing: Control-panel translations run as queued jobs with retry and backoff; the CLI can run either synchronously or via dispatched jobs
  • Sidebar UI: Auto-injected fieldtype with a translation dialog — pick target locales, track progress per locale, retry failures inline
  • Bulk actions: Translate multiple entries at once from collection listings
  • Artisan command: statamic:magic-translator:translate for scripted/automated bulk translation with dry-run, filtering, and sync/async modes
  • Staleness detection: Badges in the Sites panel show which localizations are up-to-date, outdated, or missing
  • Customizable prompts: Blade views for system/user prompts, with per-language overrides
  • Statamic v5 + v6

Installation

composer require el-schneider/statamic-magic-translator

Publish the configuration:

php artisan vendor:publish --tag=statamic-magic-translator-config

CP Usage

Translating a single entry

  1. Open an entry in the control panel
  2. Click Translate in the sidebar
  3. Select target locales and options
  4. Click Translate selected

Each locale shows its own progress indicator. Failed translations display the error inline with a retry button.

Bulk translation

  1. Select entries in a collection listing
  2. Choose Translate from the actions menu
  3. Pick target locales and options in the dialog

Translation dialog options

Option Description
Source locale Defaults to origin entry. Can be changed to translate from any existing localization.
Generate slugs Auto-generate slugs from the translated title.
Overwrite existing When disabled (default), locales with existing translations are unchecked to prevent accidental overwrites.

Staleness badges

The Sites panel in the sidebar shows translation status per locale:

  • 2h ago — translated, up-to-date
  • ⚠️ outdated — origin has been updated since last translation
  • — localization exists but was never machine-translated

CLI Usage

The addon also ships with an artisan command for bulk and automated translation:

php please statamic:magic-translator:translate [options]

Requires at least one filter: --to, --collection, --entry, or --blueprint.

Common examples

Preview what would be translated for a collection (safe, no changes):

php please statamic:magic-translator:translate --collection=pages --to=de --dry-run

Translate all missing pages entries into German and French asynchronously:

php please statamic:magic-translator:translate --collection=pages --to=de --to=fr --dispatch-jobs -n

Re-translate stale entries for CI/cron:

php please statamic:magic-translator:translate --collection=pages --include-stale --dispatch-jobs -n

Translate one specific entry to every site its collection supports:

php please statamic:magic-translator:translate --entry=abc-123

Options

Option Description
--to=* Target site handle (repeatable). Default: all sites each entry supports minus source site.
--from= Source site handle. Default: entry origin site.
--collection=* Filter by collection handle (repeatable).
--entry=* Filter by entry ID (repeatable).
--blueprint=* Filter by blueprint handle (repeatable).
--include-stale Also re-translate entries where source was updated after target last_translated_at.
--overwrite Re-translate everything regardless of existing state.
--generate-slug Slugify translated title.
--dispatch-jobs Dispatch queued jobs instead of running synchronously.
--dry-run Print the plan without executing.
-n, --no-interaction Skip confirmation prompt (required in CI/non-interactive environments).

Exit codes

Code Meaning
0 Success, dry run, empty plan, or user-declined confirmation.
1 Partial failure (some translations failed).
2 Command-level error (invalid options/handles, unsafe non-interactive run without -n).

Configuration

1. Exclude blueprints (optional)

By default, the addon auto-injects its fieldtype into entry blueprints. Use exclude_blueprints to opt out specific blueprints or whole collections:

// config/statamic/magic-translator.php

'exclude_blueprints' => [
    'pages.redirect', // exact blueprint
    'blog.*',         // all blueprints in a collection
],

2. Choose a translation service

Prism (LLMs)

Set your provider and model:

MAGIC_TRANSLATOR_SERVICE=prism
MAGIC_TRANSLATOR_PROVIDER=openai
MAGIC_TRANSLATOR_MODEL=gpt-5-mini
OPENAI_API_KEY=sk-...

Any Prism-supported provider works — Anthropic, OpenAI, Gemini, Mistral, Ollama, etc. Just add the provider's API key to your .env and reference it in Prism's config.

DeepL

MAGIC_TRANSLATOR_SERVICE=deepl
DEEPL_API_KEY=your-deepl-key

DeepL-specific options:

'deepl' => [
    'api_key' => env('DEEPL_API_KEY'),
    'formality' => 'default', // 'more', 'less', 'prefer_more', 'prefer_less'
    'overrides' => [
        'de' => ['formality' => 'prefer_more'],
    ],
],

3. Set up a queue worker

Control-panel translations and CLI runs using --dispatch-jobs execute asynchronously. You need a queue driver other than sync and a running worker:

php artisan queue:work

Optionally configure a dedicated queue:

MAGIC_TRANSLATOR_QUEUE_CONNECTION=redis
MAGIC_TRANSLATOR_QUEUE_NAME=translations

Content Structure Support

The addon handles all idiomatic Statamic content patterns:

Fieldtype Handling
Text, Textarea Translated as plain text
Markdown Translated as markdown (formatting preserved)
Bard Body text serialized with inline HTML tags, sets extracted recursively. Custom marks and extensions (e.g., Bard Texstyle) are preserved — the ProseMirror structure is never round-tripped through HTML.
Bard (raw markdown) Starter kit entries storing markdown instead of ProseMirror JSON are detected and translated as markdown.
Replicator Each set's fields are recursively extracted and translated.
Grid Each row's columns are recursively extracted and translated.
Table Each cell is translated as plain text.
Link text property is translated, url is preserved.
Assets, Toggle, Integer, Select, … Skipped (non-text fields are never translated).

Fields marked localizable: false in the blueprint are always skipped. Individual fields can be excluded with translatable: false in the field config.

Deeply nested structures (Bard → set → Replicator → set → Bard → …) work to arbitrary depth.

Customizing Prompts

Translation prompts are Blade views. Publish them to customize:

php artisan vendor:publish --tag=statamic-magic-translator-views

This copies prompt templates to resources/views/vendor/magic-translator/prompts/.

Per-language prompt overrides

Different languages may need different instructions (e.g., formal "Sie" in German, polite form in Japanese):

// config/statamic/magic-translator.php

'prism' => [
    'prompts' => [
        'system' => 'magic-translator::prompts.system',
        'user' => 'magic-translator::prompts.user',
        'overrides' => [
            'de' => ['system' => 'magic-translator::prompts.system-de'],
            'ja' => ['system' => 'magic-translator::prompts.system-ja'],
        ],
    ],
],

Create the override views (e.g., resources/views/vendor/magic-translator/prompts/system-de.blade.php) with language-specific instructions.

Available prompt variables

Variable Example
$sourceLocale en
$targetLocale de
$sourceLocaleName English
$targetLocaleName German
$hasHtmlUnits true (Bard content present)
$hasMarkdownUnits true (Markdown fields present)

Events

Hook into the translation lifecycle:

BeforeEntryTranslation

Fired before extraction. Modify the $units array to exclude or alter translation units.

use ElSchneider\MagicTranslator\Events\BeforeEntryTranslation;

Event::listen(BeforeEntryTranslation::class, function ($event) {
    // $event->entry — the source entry
    // $event->targetSite — target locale handle
    // $event->units — mutable array of TranslationUnit objects
});

AfterEntryTranslation

Fired after translation, before save. Modify $translatedData to post-process the result.

use ElSchneider\MagicTranslator\Events\AfterEntryTranslation;

Event::listen(AfterEntryTranslation::class, function ($event) {
    // $event->entry — the source entry
    // $event->targetSite — target locale handle
    // $event->translatedData — mutable array of translated entry data
});

Custom Translation Service

Implement the TranslationService contract to add your own backend (Google Translate, etc.):

use ElSchneider\MagicTranslator\Contracts\TranslationService;
use ElSchneider\MagicTranslator\Data\TranslationUnit;

class GoogleTranslateService implements TranslationService
{
    public function translate(array $units, string $sourceLocale, string $targetLocale): array
    {
        // $units is an array of TranslationUnit objects
        // Return the same array with translatedText set on each unit
        return array_map(fn (TranslationUnit $unit) => $unit->withTranslation(
            $this->callGoogleApi($unit->text, $sourceLocale, $targetLocale)
        ), $units);
    }
}

Bind it in a service provider:

$this->app->bind(TranslationService::class, GoogleTranslateService::class);

Requirements

  • PHP 8.2+
  • Statamic 5.0+ or 6.0+
  • An async queue driver (database, redis, sqs, …) with a running worker
  • At least one translation provider configured (LLM API key or DeepL API key)

License

MIT