Translate Assistant
Overview
The Translate Assistant powers automatic Markdown translation and rephrasing in the back office. Today, Roadiz ships with a DeepL implementation only. You can swap in another provider by implementing the Translate Assistant contract and wiring your service in the container.
How it works
- The back office submits Markdown text, source language, and target language.
- Roadiz calls a service implementing
TranslateAssistantInterface. - The service returns a
TranslateAssistantOutputwith the translated text and language metadata. - Rephrase is optional and depends on the provider capabilities.
Core contract
Your service must implement RZ\Roadiz\RozierBundle\TranslateAssistant\TranslateAssistantInterface:
<?php
namespace App\TranslateAssistant;
use RZ\Roadiz\RozierBundle\TranslateAssistant\TranslateAssistantInput;
use RZ\Roadiz\RozierBundle\TranslateAssistant\TranslateAssistantInterface;
use RZ\Roadiz\RozierBundle\TranslateAssistant\TranslateAssistantOutput;
final readonly class CustomTranslateAssistant implements TranslateAssistantInterface
{
public function translate(TranslateAssistantInput $translatorDto): TranslateAssistantOutput
{
return new TranslateAssistantOutput(
originalText: $translatorDto->text,
translatedText: '...',
sourceLang: $translatorDto->sourceLang ?? '',
targetLang: $translatorDto->targetLang,
);
}
public function rephrase(TranslateAssistantInput $translatorDto): TranslateAssistantOutput
{
return new TranslateAssistantOutput(
originalText: $translatorDto->text,
translatedText: '...',
sourceLang: $translatorDto->sourceLang ?? '',
targetLang: $translatorDto->targetLang,
);
}
public function supportRephrase(): bool
{
return false;
}
}Input/Output DTOs
TranslateAssistantInputtext(string)targetLang(string)sourceLang(string|null)options(array|null)
TranslateAssistantOutputoriginalText(string)translatedText(string)sourceLang(string)targetLang(string)
Wiring your provider
Bind your service to the interface in your project configuration so the back office uses it:
# config/services.yaml
services:
App\TranslateAssistant\CustomTranslateAssistant:
autowire: true
autoconfigure: true
RZ\Roadiz\RozierBundle\TranslateAssistant\TranslateAssistantInterface:
alias: App\TranslateAssistant\CustomTranslateAssistantIf you remove the DeepL API key in roadiz_rozier.translate_assistant, Roadiz falls back to a null assistant. Your custom service should be registered unconditionally or guarded by your own config.
ChatGPT example (OpenAI)
Example provider using the Chat Completions API with the gpt-4o-mini model and a plain HTTP client:
<?php
namespace App\TranslateAssistant;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use RZ\Roadiz\RozierBundle\TranslateAssistant\TranslateAssistantInput;
use RZ\Roadiz\RozierBundle\TranslateAssistant\TranslateAssistantInterface;
use RZ\Roadiz\RozierBundle\TranslateAssistant\TranslateAssistantOutput;
final readonly class OpenAiTranslateAssistant implements TranslateAssistantInterface
{
public function __construct(
private HttpClientInterface $httpClient,
private string $apiKey,
private string $baseUrl = 'https://api.openai.com/v1',
private string $model = 'gpt-4o-mini',
) {
}
public function translate(TranslateAssistantInput $translatorDto): TranslateAssistantOutput
{
$prompt = sprintf(
'Translate this Markdown to %s. Return Markdown only.\n\n%s',
$translatorDto->targetLang,
$translatorDto->text
);
$translatedText = $this->request($prompt, $translatorDto->sourceLang);
return new TranslateAssistantOutput(
originalText: $translatorDto->text,
translatedText: $translatedText,
sourceLang: $translatorDto->sourceLang ?? '',
targetLang: $translatorDto->targetLang,
);
}
public function rephrase(TranslateAssistantInput $translatorDto): TranslateAssistantOutput
{
$prompt = sprintf(
'Rephrase this Markdown for clarity. Keep the language as %s. Return Markdown only.\n\n%s',
$translatorDto->targetLang,
$translatorDto->text
);
$translatedText = $this->request($prompt, $translatorDto->sourceLang);
return new TranslateAssistantOutput(
originalText: $translatorDto->text,
translatedText: $translatedText,
sourceLang: $translatorDto->sourceLang ?? '',
targetLang: $translatorDto->targetLang,
);
}
public function supportRephrase(): bool
{
return true;
}
private function request(string $prompt, ?string $sourceLang): string
{
$response = $this->httpClient->request('POST', $this->baseUrl.'/chat/completions', [
'headers' => [
'Authorization' => 'Bearer '.$this->apiKey,
],
'json' => [
'model' => $this->model,
'temperature' => 0.2,
'messages' => [
[
'role' => 'system',
'content' => 'You are a translation assistant for Markdown content.',
],
[
'role' => 'user',
'content' => $prompt,
],
],
],
]);
$payload = $response->toArray();
return trim((string) ($payload['choices'][0]['message']['content'] ?? ''));
}
}Service wiring and configuration:
# config/services.yaml
services:
App\TranslateAssistant\OpenAiTranslateAssistant:
arguments:
$apiKey: '%env(OPENAI_API_KEY)%'
$baseUrl: '%env(default:OPENAI_API_BASE_URL:OPENAI_API_BASE_URL)%'
$model: '%env(default:OPENAI_TRANSLATE_MODEL:OPENAI_TRANSLATE_MODEL)%'
RZ\Roadiz\RozierBundle\TranslateAssistant\TranslateAssistantInterface:
alias: App\TranslateAssistant\OpenAiTranslateAssistantOPENAI_API_KEY=your-key
OPENAI_API_BASE_URL=https://api.openai.com/v1
OPENAI_TRANSLATE_MODEL=gpt-4o-miniBe mindful of rate limits and usage costs when enabling this in production.
DeepL note
DeepL remains the only built-in provider at the moment. If you need another service, implement the interface and alias it as shown above.
