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

  1. Registrieren Sie sich bei DeepL
  2. Wählen Sie zwischen:
    • DeepL API Free: 500.000 Zeichen/Monat kostenlos
    • DeepL API Pro: Ab €5.49/Monat für 1 Million Zeichen
  3. 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

  1. Async Processing: Nutzen Sie Queue API für große Übersetzungen
  2. Field Selection: Übersetzen Sie nur notwendige Felder
  3. Batch Size: Verarbeiten Sie max. 50 Nodes pro Batch
  4. HTML Handling: DeepL kann HTML-Tags beibehalten mit tag_handling Option

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.


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

← Zurück zum Blog