DeepL API in Drupal 11 integrieren: Professionelle Übersetzungen automatisieren
DeepL API in Drupal 11 integrieren: Professionelle Übersetzungen automatisieren
Die DeepL API bietet hochwertige maschinelle Übersetzungen, die in vielen Fällen menschlicher Übersetzung sehr nahe kommen. In diesem Tutorial zeige ich Ihnen, wie Sie die DeepL API in Drupal 11 integrieren, um Content-Übersetzungen zu automatisieren.
Warum DeepL statt Google Translate?
DeepL übertrifft Google Translate in mehreren Bereichen:
- Qualität: Natürlichere, kontextbezogene Übersetzungen
- Fachterminologie: Bessere Handhabung von Fachbegriffen
- Formality API: Wahl zwischen formeller und informeller Ansprache
- DSGVO-Konformität: Server in der EU, keine Datennutzung für Training
- Glossar-Support: Eigene Terminologie-Datenbanken
Voraussetzungen
- Drupal 11 Installation
- PHP 8.3 oder höher
- Composer
- DeepL API Key (kostenlos oder Pro)
Schritt 1: DeepL API Key erhalten
- Registrieren Sie sich bei DeepL
- Wählen Sie zwischen:
- DeepL API Free: 500.000 Zeichen/Monat kostenlos
- DeepL API Pro: Ab €5.49/Monat für 1 Million Zeichen
- Kopieren Sie Ihren API Key aus dem Dashboard
Schritt 2: DeepL PHP Library installieren
Installieren Sie die offizielle DeepL PHP Library via Composer:
cd /path/to/drupal
composer require deeplcom/deepl-php
Schritt 3: Custom Drupal Module erstellen
Erstellen Sie ein Custom Module deepl_translator:
mkdir -p web/modules/custom/deepl_translator
deepl_translator.info.yml
name: 'DeepL Translator'
type: module
description: 'Integrates DeepL API for professional translations'
core_version_requirement: ^11
package: 'Translation'
dependencies:
- drupal:content_translation
- drupal:language
deepl_translator.services.yml
services:
deepl_translator.client:
class: Drupal\deepl_translator\DeepLClient
arguments: ['@config.factory', '@logger.factory']
deepl_translator.translator:
class: Drupal\deepl_translator\DeepLTranslatorService
arguments: ['@deepl_translator.client', '@entity_type.manager']
Schritt 4: DeepL Client Service
Erstellen Sie src/DeepLClient.php:
<?php
namespace Drupal\deepl_translator;
use DeepL\Translator;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
/**
* DeepL API Client Service.
*/
class DeepLClient {
/**
* The DeepL Translator instance.
*
* @var \DeepL\Translator
*/
protected $translator;
/**
* The logger.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* Constructs a DeepLClient object.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
LoggerChannelFactoryInterface $logger_factory
) {
$this->logger = $logger_factory->get('deepl_translator');
$api_key = $config_factory->get('deepl_translator.settings')->get('api_key');
if (empty($api_key)) {
$this->logger->error('DeepL API key not configured');
return;
}
try {
$this->translator = new Translator($api_key);
}
catch (\Exception $e) {
$this->logger->error('Failed to initialize DeepL Translator: @message', [
'@message' => $e->getMessage(),
]);
}
}
/**
* Translates text.
*
* @param string $text
* The text to translate.
* @param string $target_language
* Target language code (e.g., 'DE', 'EN-US').
* @param string|null $source_language
* Source language code (optional, auto-detect if null).
* @param array $options
* Additional options (formality, glossary_id, etc.).
*
* @return string|null
* Translated text or NULL on failure.
*/
public function translateText(
string $text,
string $target_language,
?string $source_language = null,
array $options = []
): ?string {
if (!$this->translator) {
return null;
}
try {
$translate_options = [];
// Add formality if specified
if (isset($options['formality'])) {
$translate_options['formality'] = $options['formality'];
}
// Add glossary if specified
if (isset($options['glossary_id'])) {
$translate_options['glossary_id'] = $options['glossary_id'];
}
$result = $this->translator->translateText(
$text,
$source_language,
$target_language,
$translate_options
);
$this->logger->info('Translated @chars characters from @source to @target', [
'@chars' => strlen($text),
'@source' => $source_language ?? 'auto',
'@target' => $target_language,
]);
return $result->text;
}
catch (\Exception $e) {
$this->logger->error('Translation failed: @message', [
'@message' => $e->getMessage(),
]);
return null;
}
}
/**
* Gets usage statistics.
*
* @return array
* Usage information (character count, limit, etc.).
*/
public function getUsage(): array {
if (!$this->translator) {
return [];
}
try {
$usage = $this->translator->getUsage();
return [
'character_count' => $usage->character->count,
'character_limit' => $usage->character->limit,
'percentage_used' => round(($usage->character->count / $usage->character->limit) * 100, 2),
];
}
catch (\Exception $e) {
$this->logger->error('Failed to get usage: @message', [
'@message' => $e->getMessage(),
]);
return [];
}
}
/**
* Gets supported languages.
*
* @param string $type
* 'source' or 'target'.
*
* @return array
* Array of language codes.
*/
public function getSupportedLanguages(string $type = 'target'): array {
if (!$this->translator) {
return [];
}
try {
$languages = $this->translator->getLanguages($type);
$result = [];
foreach ($languages as $language) {
$result[$language->code] = $language->name;
}
return $result;
}
catch (\Exception $e) {
$this->logger->error('Failed to get languages: @message', [
'@message' => $e->getMessage(),
]);
return [];
}
}
}
Schritt 5: Translation Service
Erstellen Sie src/DeepLTranslatorService.php:
<?php
namespace Drupal\deepl_translator;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
/**
* DeepL Translation Service for Drupal Content.
*/
class DeepLTranslatorService {
/**
* The DeepL client.
*
* @var \Drupal\deepl_translator\DeepLClient
*/
protected $client;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a DeepLTranslatorService object.
*/
public function __construct(
DeepLClient $client,
EntityTypeManagerInterface $entity_type_manager
) {
$this->client = $client;
$this->entityTypeManager = $entity_type_manager;
}
/**
* Translates a content entity.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The entity to translate.
* @param string $target_language
* Target language code.
* @param array $fields
* Fields to translate (default: all translatable text fields).
* @param array $options
* Translation options.
*
* @return \Drupal\Core\Entity\ContentEntityInterface|null
* Translated entity or NULL on failure.
*/
public function translateEntity(
ContentEntityInterface $entity,
string $target_language,
array $fields = [],
array $options = []
): ?ContentEntityInterface {
if (!$entity->hasTranslation($target_language)) {
$entity->addTranslation($target_language);
}
$translation = $entity->getTranslation($target_language);
$source_language = $entity->language()->getId();
// Convert Drupal language codes to DeepL codes
$deepl_source = $this->convertToDeepl($source_language);
$deepl_target = $this->convertToDeepl($target_language);
// Get translatable fields if not specified
if (empty($fields)) {
$fields = $this->getTranslatableFields($entity);
}
foreach ($fields as $field_name) {
if (!$entity->hasField($field_name)) {
continue;
}
$field = $entity->get($field_name);
// Handle different field types
if ($field->getFieldDefinition()->getType() === 'text_with_summary') {
$value = $field->value;
$summary = $field->summary;
$translated_value = $this->client->translateText(
$value,
$deepl_target,
$deepl_source,
$options
);
if ($summary) {
$translated_summary = $this->client->translateText(
$summary,
$deepl_target,
$deepl_source,
$options
);
}
$translation->set($field_name, [
'value' => $translated_value,
'summary' => $translated_summary ?? '',
'format' => $field->format,
]);
}
elseif ($field->getFieldDefinition()->getType() === 'string') {
$value = $field->value;
$translated = $this->client->translateText(
$value,
$deepl_target,
$deepl_source,
$options
);
$translation->set($field_name, $translated);
}
}
$translation->save();
return $translation;
}
/**
* Gets translatable fields from entity.
*/
protected function getTranslatableFields(ContentEntityInterface $entity): array {
$fields = [];
foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
if ($definition->isTranslatable() &&
in_array($definition->getType(), ['string', 'text', 'text_long', 'text_with_summary'])) {
$fields[] = $field_name;
}
}
return $fields;
}
/**
* Converts Drupal language code to DeepL format.
*/
protected function convertToDeepl(string $drupal_code): string {
$mapping = [
'en' => 'EN-US',
'de' => 'DE',
'fr' => 'FR',
'es' => 'ES',
'it' => 'IT',
'nl' => 'NL',
'pl' => 'PL',
'pt' => 'PT-PT',
'ru' => 'RU',
'ja' => 'JA',
'zh-hans' => 'ZH',
];
return $mapping[$drupal_code] ?? strtoupper($drupal_code);
}
}
Schritt 6: Configuration Form
Erstellen Sie src/Form/SettingsForm.php:
<?php
namespace Drupal\deepl_translator\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configuration form for DeepL Translator.
*/
class SettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'deepl_translator_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['deepl_translator.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('deepl_translator.settings');
$form['api_key'] = [
'#type' => 'textfield',
'#title' => $this->t('DeepL API Key'),
'#description' => $this->t('Enter your DeepL API authentication key. Get it from <a href="@url" target="_blank">DeepL Pro</a>.', [
'@url' => 'https://www.deepl.com/pro-api',
]),
'#default_value' => $config->get('api_key'),
'#required' => TRUE,
];
$form['default_formality'] = [
'#type' => 'select',
'#title' => $this->t('Default Formality'),
'#options' => [
'default' => $this->t('Default'),
'more' => $this->t('More formal (Sie)'),
'less' => $this->t('Less formal (Du)'),
],
'#default_value' => $config->get('default_formality') ?? 'default',
'#description' => $this->t('Set the default formality level for translations.'),
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('deepl_translator.settings')
->set('api_key', $form_state->getValue('api_key'))
->set('default_formality', $form_state->getValue('default_formality'))
->save();
parent::submitForm($form, $form_state);
}
}
Schritt 7: Routing und Menü
Erstellen Sie deepl_translator.routing.yml:
deepl_translator.settings:
path: '/admin/config/regional/deepl-translator'
defaults:
_form: '\Drupal\deepl_translator\Form\SettingsForm'
_title: 'DeepL Translator Settings'
requirements:
_permission: 'administer site configuration'
deepl_translator.translate_node:
path: '/admin/content/translate-deepl/{node}'
defaults:
_controller: '\Drupal\deepl_translator\Controller\TranslateController::translateNode'
_title: 'Translate with DeepL'
requirements:
_permission: 'translate content'
node: \d+
Schritt 8: Batch Processing für große Inhalte
Für große Content-Mengen erstellen Sie src/Batch/TranslateBatch.php:
<?php
namespace Drupal\deepl_translator\Batch;
use Drupal\node\Entity\Node;
/**
* Batch processing for bulk translations.
*/
class TranslateBatch {
/**
* Batch process callback.
*/
public static function process($nids, $target_language, &$context) {
if (!isset($context['results']['processed'])) {
$context['results']['processed'] = 0;
$context['results']['failed'] = 0;
}
$translator = \Drupal::service('deepl_translator.translator');
foreach ($nids as $nid) {
$node = Node::load($nid);
if (!$node) {
continue;
}
try {
$translator->translateEntity($node, $target_language);
$context['results']['processed']++;
$context['message'] = t('Translated @count nodes', [
'@count' => $context['results']['processed'],
]);
}
catch (\Exception $e) {
$context['results']['failed']++;
\Drupal::logger('deepl_translator')->error('Failed to translate node @nid: @error', [
'@nid' => $nid,
'@error' => $e->getMessage(),
]);
}
}
}
/**
* Batch finish callback.
*/
public static function finish($success, $results, $operations) {
if ($success) {
\Drupal::messenger()->addMessage(t('Translated @count nodes. @failed failed.', [
'@count' => $results['processed'],
'@failed' => $results['failed'],
]));
}
else {
\Drupal::messenger()->addError(t('An error occurred during translation.'));
}
}
}
Verwendung
Via Drush Command
Erstellen Sie src/Commands/DeepLCommands.php:
<?php
namespace Drupal\deepl_translator\Commands;
use Drush\Commands\DrushCommands;
use Drupal\node\Entity\Node;
/**
* Drush commands for DeepL Translator.
*/
class DeepLCommands extends DrushCommands {
/**
* Translates a node.
*
* @command deepl:translate-node
* @param int $nid Node ID
* @param string $lang Target language code
* @usage deepl:translate-node 123 de
*/
public function translateNode($nid, $lang) {
$node = Node::load($nid);
if (!$node) {
$this->logger()->error('Node not found');
return;
}
$translator = \Drupal::service('deepl_translator.translator');
$translation = $translator->translateEntity($node, $lang);
if ($translation) {
$this->logger()->success(dt('Node @nid translated to @lang', [
'@nid' => $nid,
'@lang' => $lang,
]));
}
}
/**
* Shows DeepL API usage.
*
* @command deepl:usage
*/
public function usage() {
$client = \Drupal::service('deepl_translator.client');
$usage = $client->getUsage();
$this->io()->table(
['Metric', 'Value'],
[
['Characters used', number_format($usage['character_count'])],
['Character limit', number_format($usage['character_limit'])],
['Percentage', $usage['percentage_used'] . '%'],
]
);
}
}
Best Practices & Tipps
1. Cost Optimization
// Nur geänderte Felder übersetzen
$changed_fields = array_keys($entity->getChangedFields());
$translator->translateEntity($entity, $target_language, $changed_fields);
2. Caching
// Cache translations to avoid duplicate API calls
$cache_key = "deepl:$text:$source:$target";
$cached = \Drupal::cache()->get($cache_key);
if ($cached) {
return $cached->data;
}
$translation = $client->translateText($text, $target, $source);
\Drupal::cache()->set($cache_key, $translation, time() + 86400);
3. Rate Limiting
// Implement rate limiting for API calls
$usage = $client->getUsage();
if ($usage['percentage_used'] > 90) {
\Drupal::messenger()->addWarning('DeepL quota nearly exhausted');
}
4. Error Handling
try {
$translation = $client->translateText($text, $target, $source);
}
catch (\DeepL\DeepLException $e) {
\Drupal::logger('deepl_translator')->error('DeepL API error: @error', [
'@error' => $e->getMessage(),
]);
// Fallback to untranslated content
return $text;
}
Performance-Tipps
- Async Processing: Nutzen Sie Queue API für große Übersetzungen
- Field Selection: Übersetzen Sie nur notwendige Felder
- Batch Size: Verarbeiten Sie max. 50 Nodes pro Batch
- HTML Handling: DeepL kann HTML-Tags beibehalten mit
tag_handlingOption
Kosten-Kalkulation
DeepL API Free:
- 500.000 Zeichen/Monat kostenlos
- Ca. 200-250 Blog-Posts (2000 Zeichen/Post)
DeepL API Pro:
- €5.49/Monat für 1 Million Zeichen
- Ca. 400-500 Blog-Posts
- €0.0055 pro 1000 Zeichen
Beispiel-Rechnung:
- 100 Artikel à 2000 Zeichen = 200.000 Zeichen
- 3 Sprachen = 600.000 Zeichen
- Kosten: €3.30 (Pro) oder kostenlos (Free)
Fazit
Die Integration der DeepL API in Drupal 11 ermöglicht professionelle, automatisierte Übersetzungen. Die hohe Übersetzungsqualität spart Zeit bei der Content-Lokalisierung und reduziert Kosten für menschliche Übersetzer.
Die Custom Module Entwicklung bietet volle Kontrolle über den Übersetzungsprozess und lässt sich perfekt in bestehende Content-Workflows integrieren.
Weiterführende Links
Benötigen Sie Unterstützung bei der DeepL Integration in Ihr Drupal 11 Projekt? Kontaktieren Sie mich für eine unverbindliche Beratung: mail@stevenschulz.net oder 04037420859