s2hub / silverstripe-autotranslate
Auto Translate for SilverStripe CMS
Package info
github.com/s2hub/silverstripe-autotranslate
Type:silverstripe-vendormodule
pkg:composer/s2hub/silverstripe-autotranslate
Requires
- php: ^8.2
- ext-zip: *
- deeplcom/deepl-php: ^1.12
- openai-php/client: ^0.10
- s2hub/silverstripe-cms-popup: *
- silverstripe/framework: ^6
- tractorcow/silverstripe-fluent: ^8
Requires (Dev)
README
An extension for silverstripe/fluent to automatically translate content using AI services (ChatGPT, DeepL).
Installation
composer require s2hub/silverstripe-autotranslate
Silverstripe CMS Versions
The current version of this module is for Silverstripe CMS 6.
Setup
1. Add the extension to your classes
# SiteTree already has fluent applied SilverStripe\CMS\Model\SiteTree: extensions: autotranslate: S2Hub\AutoTranslate\Extension\AutoTranslate My\Namespace\Model\Foo: extensions: fluent: TractorCow\Fluent\Extension\FluentExtension autotranslate: S2Hub\AutoTranslate\Extension\AutoTranslate # if you have a manual `translate` config on this class, you must add these fields explicitly: translate: - IsAutoTranslated - LastTranslation
The AutoTranslate extension adds two fields to each locale:
IsAutoTranslated– flag that editors can toggle to mark a translation as manually reviewedLastTranslation– timestamp of the last auto-translation
⚠️ Some extensions from other modules define translated config on a class. If that applies to your class, you must add IsAutoTranslated and LastTranslation to the translate list manually (see Troubleshooting).
2. Choose and configure a translation backend
Two backends are available: ChatGPT (default) and DeepL.
Set the active backend in your config or via environment variable:
S2Hub\AutoTranslate\Translator\TranslatableFactory: backend: DeepL # or ChatGPT (default)
The environment variable FLUENT_TRANS_BACKEND takes precedence over the config value:
FLUENT_TRANS_BACKEND=DeepL
ChatGPT
API Key
CHATGPT_API_KEY=your-api-key
Configuration
S2Hub\AutoTranslate\Translator\ChatGPTTranslator: gpt_model: gpt-4o-mini # %s will be replaced with the target locale name gpt_command: 'You are a professional translator. Translate the following text to %s language. Please keep the json format intact.'
Customising the prompt dynamically
Add an extension to ChatGPTTranslator and implement updateGptCommand:
public function updateGptCommand(&$command, $locale) { $command = 'Translate the following JSON to ' . $locale . '. Preserve the JSON structure.'; }
Finding available GPT models
In ssshell you can list models available for your API key:
$gpt = new S2Hub\AutoTranslate\Translator\ChatGPTTranslator(Environment::getEnv('CHATGPT_API_KEY')); $gpt->getModels();
DeepL
API Key
DEEPL_API_KEY=your-api-key
Enabling DeepL
S2Hub\AutoTranslate\Translator\TranslatableFactory: backend: DeepL
or via environment variable:
FLUENT_TRANS_BACKEND=DeepL
Locale mapping
The module ships with locale mappings for 40+ languages in _config/locales.yml. DeepL uses different language codes than SilverStripe (e.g. en_US → EN-US, de_DE → DE). Override or extend mappings in your project config if needed:
S2Hub\AutoTranslate\Translator\DeepLTranslator: source_locales: de_DE: DE en_US: EN target_locales: de_DE: DE en_US: EN-US en_GB: EN-GB
Glossaries
DeepL glossaries let you enforce consistent terminology (e.g. brand names, product terms). Create glossaries in your DeepL account, then map their IDs to DeepL target language codes in your config:
S2Hub\AutoTranslate\Translator\DeepLTranslator: glossaries: EN-US: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' DE: 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy'
The key must match the DeepL target language code (not the SilverStripe locale). The glossary is applied automatically whenever a translation targets that language.
HTML handling
DeepL receives the SilverStripe field values as a JSON object. If a field value contains HTML markup, the translator automatically enables DeepL's HTML tag handling (tag_handling: html) to preserve markup structure. Plain text values are handled separately, with HTML entities decoded back after translation.
Large HTML content is split into chunks at block-level tag boundaries to stay below DeepL's 75 kB request limit.
Running translations
CLI task
sake tasks:FluentAIAutoTranslate --do_publish=1
Parameters
| Parameter | Shortcut | Required | Description |
|---|---|---|---|
--do_publish |
-p |
yes | Set to 1 to publish translated content. Requires FluentVersionedExtension on versioned objects. |
--force_translation |
no | Re-translate everything, including content already marked as manually edited (IsAutoTranslated=false). |
|
--locale_from |
-l |
no | Source locale (defaults to the site's default locale). |
--locales_to |
-t |
no | Semicolon-separated list of target locales, e.g. --locales_to="en_US;es_ES". Translates to all locales if omitted. |
sake tasks:FluentAIAutoTranslate --help
CMS UI
The AutoTranslate extension adds an Auto Translate button to the CMS actions bar. The button is only shown when editing a record in the default locale – it is hidden for translated locales.
Requires the s2hub/silverstripe-cms-popup module, which provides the modal infrastructure.
Clicking the button opens a modal with four options:
| Option | Default | Description |
|---|---|---|
| Target languages | all non-default locales selected | Select which locales to translate to. |
| Publish after translation | on | Publish the translated content immediately. Requires FluentVersionedExtension and the source record must be published. |
| Only translate new content | on | Skip records where IsAutoTranslated = false (manually edited) or whose LastTranslation is newer than the source. Uncheck to force re-translation of everything. |
| Recursive | off | Also queue all child pages for translation (SiteTree only). |
The modal processes items one by one and displays per-locale feedback (translated, published, skipped, error) for each item as it completes.
Owned objects (e.g. Elemental blocks, Links, related media records) are always translated inline as part of the parent record – they do not appear as separate queue items. The Recursive option only controls whether child pages are added to the queue.
Translation behaviour
- Translation always reads from the default locale.
- A record is skipped if
IsAutoTranslated = false(manual edit detected), unlessforce_translationis set. - A locale is skipped if its
LastTranslationtimestamp is newer than the source record's, meaning it was manually edited after the last auto-translation, unlessforce_translationis set. IsAutoTranslatedis set totrueandLastTranslationis updated after each successful translation.- Publishing only works if the object uses
FluentVersionedExtensioninstead ofFluentExtension.
Troubleshooting
[Emergency] Uncaught RuntimeException: My\Namespace\HomePage does not have IsAutoTranslated as translatable field
Your class defines a manual translate list. Add the required fields:
SilverStripe\CMS\Model\SiteTree: extensions: autotranslate: S2Hub\AutoTranslate\Extension\AutoTranslate translate: - IsAutoTranslated - LastTranslation
DeepL API character limit reached
The task throws a RuntimeException when the DeepL character quota is exhausted. Check your usage in the DeepL account dashboard and upgrade your plan or wait for the quota reset.
Thanks to
This module is based on the fluent-export-import-module by wernerkrauss. Thanks to Nobrainer and Adiwidjaja Teamworks for sponsoring this module ❤️.
Thanks to TractorCow and all contributors for the great fluent module. And thanks to the folks at Silverstripe for their great work.
S2-Hub
This module is published and maintained by S2-Hub. S2-Hub is a non-profit organisation that consists of Silverstripe CMS professionals and agencies around Europe with the goal of setting up a dedicated European professional hub for all things Silverstripe CMS.
See you on next Stripecon!