Multilingual Content-Strategy: Drupal 11 mit DeepL für 30+ Sprachen
Multilingual Content-Strategy: Drupal 11 mit DeepL für 30+ Sprachen
Die Verwaltung mehrsprachiger Websites mit 30+ Sprachen stellt besondere Herausforderungen an Content-Management, Translation-Workflows und Performance. In diesem Enterprise-Guide zeige ich, wie Sie mit Drupal 11 und der DeepL API eine skalierbare, kosteneffiziente Multilingual-Lösung aufbauen.
Warum Drupal 11 + DeepL?
Vorteile der Kombination
Drupal 11 Multilingual:
- Native i18n Support seit Core
- Content Translation Module (Core)
- Configuration Translation
- Interface Translation
- Language Detection & Selection
DeepL API:
- 31 Sprachen verfügbar (Stand 2025)
- Formality API (Du/Sie, Tu/Vous)
- Glossary Support für Fachterminologie
- Context-aware Übersetzungen
- EU-Server, DSGVO-konform
Kostenvorteil:
- DeepL: ~€5-20 pro 1 Million Zeichen
- Professionelle Übersetzer: €150-300 pro 1000 Wörter
- Einsparung: 95-98% bei akzeptabler Qualität
Architektur-Übersicht
┌─────────────────────────────────────────┐
│ Content Creation (Source Language) │
└──────────────────┬──────────────────────┘
│
┌─────────▼─────────┐
│ Translation Queue │
└─────────┬─────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌───▼───┐ ┌───▼───┐ ┌───▼───┐
│DeepL │ │Human │ │Hybrid │
│ API │ │Review │ │Workflow│
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
└──────────────┼──────────────┘
│
┌─────────▼─────────┐
│ Published Langs │
└───────────────────┘
Setup: Drupal 11 Multilingual
Schritt 1: Core Module aktivieren
# Language Module
drush en language -y
# Content Translation
drush en content_translation -y
# Configuration Translation
drush en config_translation -y
# Interface Translation
drush en locale -y
# Language Icons (optional)
composer require drupal/languageicons
drush en languageicons -y
Schritt 2: Sprachen hinzufügen
Via Drush (empfohlen für bulk):
# Einzelne Sprache
drush language:add de
# Multiple Sprachen
LANGUAGES=(de fr es it nl pl pt ru ja zh-hans)
for lang in "${LANGUAGES[@]}"; do
drush language:add $lang
done
Via UI:
/admin/config/regional/language/add
Schritt 3: Content Types konfigurieren
// In einem Custom Module: mymodule.install
/**
* Enable translation for node types.
*/
function mymodule_update_9001() {
$entity_types = ['node', 'taxonomy_term', 'block_content'];
$bundles = [
'node' => ['article', 'page', 'product'],
'taxonomy_term' => ['tags', 'categories'],
'block_content' => ['basic'],
];
foreach ($entity_types as $entity_type) {
foreach ($bundles[$entity_type] as $bundle) {
\Drupal::service('content_translation.manager')
->setEnabled($entity_type, $bundle, TRUE);
}
}
}
Via UI:
/admin/config/regional/content-language
DeepL Glossary Integration
Glossare ermöglichen konsistente Übersetzung von Fachbegriffen.
Glossar erstellen
<?php
namespace Drupal\mymodule\Service;
use DeepL\Translator;
use DeepL\GlossaryEntries;
/**
* DeepL Glossary Manager.
*/
class DeepLGlossaryManager {
/**
* The DeepL Translator.
*
* @var \DeepL\Translator
*/
protected $translator;
/**
* Constructor.
*/
public function __construct() {
$api_key = \Drupal::config('deepl_translator.settings')->get('api_key');
$this->translator = new Translator($api_key);
}
/**
* Creates a glossary.
*
* @param string $name
* Glossary name.
* @param string $source_lang
* Source language.
* @param string $target_lang
* Target language.
* @param array $entries
* Key-value pairs of terms.
*
* @return string
* Glossary ID.
*/
public function createGlossary(
string $name,
string $source_lang,
string $target_lang,
array $entries
): string {
$glossary_entries = GlossaryEntries::fromEntries($entries);
$glossary = $this->translator->createGlossary(
$name,
$source_lang,
$target_lang,
$glossary_entries
);
// Speichern für später
\Drupal::state()->set("deepl_glossary_{$source_lang}_{$target_lang}", [
'id' => $glossary->glossaryId,
'name' => $name,
'source_lang' => $source_lang,
'target_lang' => $target_lang,
'entry_count' => $glossary->entryCount,
]);
return $glossary->glossaryId;
}
/**
* Gets glossary for language pair.
*/
public function getGlossaryId(string $source_lang, string $target_lang): ?string {
$glossary = \Drupal::state()->get("deepl_glossary_{$source_lang}_{$target_lang}");
return $glossary['id'] ?? null;
}
/**
* Lists all glossaries.
*/
public function listGlossaries(): array {
$glossaries = $this->translator->listGlossaries();
$result = [];
foreach ($glossaries as $glossary) {
$result[] = [
'id' => $glossary->glossaryId,
'name' => $glossary->name,
'source' => $glossary->sourceLang,
'target' => $glossary->targetLang,
'entries' => $glossary->entryCount,
'created' => $glossary->creationTime,
];
}
return $result;
}
/**
* Deletes a glossary.
*/
public function deleteGlossary(string $glossary_id): void {
$this->translator->deleteGlossary($glossary_id);
}
}
Glossar verwenden
// Beispiel: Technisches Glossar
$technical_terms = [
'API' => 'API',
'Backend' => 'Backend',
'Frontend' => 'Frontend',
'Cache' => 'Cache',
'Database' => 'Datenbank',
'Server' => 'Server',
'User' => 'Benutzer',
'Admin' => 'Administrator',
'Login' => 'Anmeldung',
'Logout' => 'Abmeldung',
];
$glossary_manager = \Drupal::service('mymodule.deepl_glossary');
$glossary_id = $glossary_manager->createGlossary(
'Technical Terms EN-DE',
'EN',
'DE',
$technical_terms
);
// Übersetzen mit Glossar
$translator = \Drupal::service('deepl_translator.client');
$translation = $translator->translateText(
'Please login to access the admin backend.',
'DE',
'EN',
['glossary_id' => $glossary_id]
);
// Ergebnis: "Bitte melden Sie sich an, um auf das Administrator-Backend zuzugreifen."
Translation Workflow
1. Automated Translation Workflow
<?php
namespace Drupal\mymodule\Service;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Queue\QueueFactory;
/**
* Translation Workflow Manager.
*/
class TranslationWorkflow {
/**
* Queue a node for translation.
*/
public function queueTranslation(
ContentEntityInterface $entity,
array $target_languages,
string $workflow = 'auto'
): void {
$queue = \Drupal::queue('deepl_translation_queue');
foreach ($target_languages as $lang) {
$item = [
'entity_type' => $entity->getEntityTypeId(),
'entity_id' => $entity->id(),
'target_language' => $lang,
'workflow' => $workflow,
'timestamp' => time(),
];
$queue->createItem($item);
}
\Drupal::logger('translation_workflow')->info('Queued @entity for translation to @count languages', [
'@entity' => $entity->label(),
'@count' => count($target_languages),
]);
}
/**
* Process translation queue.
*/
public function processQueue(): void {
$queue = \Drupal::queue('deepl_translation_queue');
$queue_worker = \Drupal::service('plugin.manager.queue_worker')
->createInstance('deepl_translation_queue');
while ($item = $queue->claimItem()) {
try {
$queue_worker->processItem($item->data);
$queue->deleteItem($item);
}
catch (\Exception $e) {
\Drupal::logger('translation_workflow')->error('Failed to process queue item: @error', [
'@error' => $e->getMessage(),
]);
// Release item for retry
$queue->releaseItem($item);
}
}
}
}
2. Hybrid Workflow (Auto + Human Review)
/**
* Translation with review workflow.
*/
class HybridTranslationWorkflow {
/**
* Translate with pending review state.
*/
public function translateWithReview(
ContentEntityInterface $entity,
string $target_language
): void {
// 1. Auto-translate with DeepL
$translator = \Drupal::service('deepl_translator.translator');
$translation = $translator->translateEntity($entity, $target_language);
// 2. Set moderation state to "needs_review"
if ($translation->hasField('moderation_state')) {
$translation->set('moderation_state', 'needs_review');
}
// 3. Add metadata for reviewer
$translation->set('field_translation_info', [
'value' => json_encode([
'auto_translated' => true,
'service' => 'DeepL',
'translated_at' => date('c'),
'source_language' => $entity->language()->getId(),
'requires_review' => true,
]),
]);
// 4. Notify reviewer
$this->notifyReviewer($translation, $target_language);
$translation->save();
}
/**
* Notify translator for review.
*/
protected function notifyReviewer($entity, $lang): void {
$reviewer = $this->getReviewerForLanguage($lang);
if ($reviewer) {
$params = [
'entity' => $entity,
'language' => $lang,
'edit_url' => $entity->toUrl('edit-form')->setAbsolute()->toString(),
];
\Drupal::service('plugin.manager.mail')->mail(
'mymodule',
'translation_review',
$reviewer->getEmail(),
$lang,
$params
);
}
}
}
Content Strategy
Übersetzungs-Prioritäten
/**
* Content Translation Priority Manager.
*/
class TranslationPriority {
/**
* Priority levels.
*/
const PRIORITY_CRITICAL = 1; // Homepage, Landing Pages
const PRIORITY_HIGH = 2; // Products, Services
const PRIORITY_MEDIUM = 3; // Blog Posts, News
const PRIORITY_LOW = 4; // Archive, Documentation
/**
* Determine translation priority.
*/
public function getPriority(ContentEntityInterface $entity): int {
// Critical: Promoted content
if ($entity->hasField('promote') && $entity->get('promote')->value) {
return self::PRIORITY_CRITICAL;
}
// High: Product content types
if ($entity->bundle() === 'product') {
return self::PRIORITY_HIGH;
}
// Medium: Recent content
if ($entity->hasField('created')) {
$created = $entity->get('created')->value;
if ($created > (time() - 2592000)) { // 30 days
return self::PRIORITY_MEDIUM;
}
}
return self::PRIORITY_LOW;
}
/**
* Get languages based on priority.
*/
public function getTargetLanguages(int $priority): array {
$all_languages = \Drupal::languageManager()->getLanguages();
switch ($priority) {
case self::PRIORITY_CRITICAL:
// All languages
return array_keys($all_languages);
case self::PRIORITY_HIGH:
// Major European languages
return ['de', 'fr', 'es', 'it', 'nl', 'pl'];
case self::PRIORITY_MEDIUM:
// Top 3 languages
return ['de', 'fr', 'es'];
case self::PRIORITY_LOW:
// On-demand only
return [];
}
return [];
}
}
Formality Strategy
/**
* Formality Manager for different content types.
*/
class FormalityStrategy {
/**
* Determine formality level.
*/
public function getFormality(ContentEntityInterface $entity): string {
// B2B Content: Formal
if ($entity->hasField('field_audience') &&
$entity->get('field_audience')->value === 'b2b') {
return 'more'; // Sie/Vous/Usted
}
// B2C Content: Informal
if ($entity->bundle() === 'blog_post') {
return 'less'; // Du/Tu/Tú
}
// Legal/Compliance: Formal
if ($entity->bundle() === 'legal_document') {
return 'more';
}
// Default: Context-aware
return 'default';
}
}
Performance Optimization
1. Batch Processing
/**
* Batch translation for large content sets.
*/
class BatchTranslation {
/**
* Create batch for mass translation.
*/
public static function createBatch(array $entity_ids, array $target_languages): array {
$operations = [];
// Split into chunks of 10
$chunks = array_chunk($entity_ids, 10);
foreach ($chunks as $chunk) {
foreach ($target_languages as $lang) {
$operations[] = [
[self::class, 'processBatch'],
[$chunk, $lang],
];
}
}
return [
'title' => t('Translating content...'),
'operations' => $operations,
'finished' => [self::class, 'finishBatch'],
'progressive' => true,
];
}
/**
* Process batch operation.
*/
public static function processBatch($entity_ids, $target_lang, &$context) {
$translator = \Drupal::service('deepl_translator.translator');
$storage = \Drupal::entityTypeManager()->getStorage('node');
foreach ($entity_ids as $id) {
$entity = $storage->load($id);
if ($entity && !$entity->hasTranslation($target_lang)) {
try {
$translator->translateEntity($entity, $target_lang);
$context['results']['success'][] = $id;
}
catch (\Exception $e) {
$context['results']['failed'][] = $id;
}
}
}
$context['message'] = t('Translated @count entities', [
'@count' => count($context['results']['success']),
]);
}
}
2. Caching Strategy
/**
* Translation cache manager.
*/
class TranslationCache {
/**
* Cache translation result.
*/
public function cacheTranslation(
string $text,
string $source,
string $target,
string $result
): void {
$cache_key = $this->generateCacheKey($text, $source, $target);
\Drupal::cache('translation')->set(
$cache_key,
$result,
time() + 2592000, // 30 days
['translation', "translation:$source:$target"]
);
}
/**
* Get cached translation.
*/
public function getCachedTranslation(
string $text,
string $source,
string $target
): ?string {
$cache_key = $this->generateCacheKey($text, $source, $target);
$cached = \Drupal::cache('translation')->get($cache_key);
return $cached ? $cached->data : null;
}
/**
* Generate cache key.
*/
protected function generateCacheKey(
string $text,
string $source,
string $target
): string {
return 'translation:' . md5($text . $source . $target);
}
}
SEO für Multilingual Sites
1. Hreflang Tags
<?php
/**
* Implements hook_page_attachments().
*/
function mymodule_page_attachments(array &$attachments) {
$route_match = \Drupal::routeMatch();
if ($route_match->getRouteName() === 'entity.node.canonical') {
$node = $route_match->getParameter('node');
if ($node instanceof \Drupal\node\NodeInterface) {
$languages = \Drupal::languageManager()->getLanguages();
foreach ($languages as $language) {
$lang_code = $language->getId();
if ($node->hasTranslation($lang_code)) {
$translation = $node->getTranslation($lang_code);
$url = $translation->toUrl()->setAbsolute()->toString();
$attachments['#attached']['html_head'][] = [
[
'#tag' => 'link',
'#attributes' => [
'rel' => 'alternate',
'hreflang' => $lang_code,
'href' => $url,
],
],
'hreflang_' . $lang_code,
];
}
}
}
}
}
2. Structured Data per Language
/**
* JSON-LD Structured Data for each language.
*/
function mymodule_preprocess_node(&$variables) {
$node = $variables['node'];
$current_lang = \Drupal::languageManager()->getCurrentLanguage()->getId();
$structured_data = [
'@context' => 'https://schema.org',
'@type' => 'Article',
'headline' => $node->getTranslation($current_lang)->getTitle(),
'inLanguage' => $current_lang,
'url' => $node->toUrl()->setAbsolute()->toString(),
];
$variables['#attached']['html_head'][] = [
[
'#tag' => 'script',
'#attributes' => ['type' => 'application/ld+json'],
'#value' => json_encode($structured_data),
],
'structured_data_article',
];
}
Cost Optimization
Übersetzungs-Budget Manager
/**
* Translation Budget Manager.
*/
class BudgetManager {
/**
* Calculate translation cost.
*/
public function calculateCost(
ContentEntityInterface $entity,
array $target_languages
): array {
$total_chars = 0;
$fields = $this->getTranslatableFields($entity);
foreach ($fields as $field_name) {
$value = $entity->get($field_name)->value;
$total_chars += mb_strlen(strip_tags($value));
}
$chars_per_lang = $total_chars * count($target_languages);
// DeepL Pricing: ~€0.00002 per character
$cost_eur = $chars_per_lang * 0.00002;
return [
'characters' => $total_chars,
'total_characters' => $chars_per_lang,
'languages' => count($target_languages),
'cost_eur' => round($cost_eur, 2),
'cost_per_language' => round($cost_eur / count($target_languages), 2),
];
}
/**
* Check if within budget.
*/
public function isWithinBudget(float $cost): bool {
$monthly_budget = \Drupal::config('mymodule.settings')->get('monthly_budget');
$current_spend = \Drupal::state()->get('translation_spend_' . date('Y-m'), 0);
return ($current_spend + $cost) <= $monthly_budget;
}
}
Quality Assurance
Translation Quality Checker
/**
* Translation Quality Checker.
*/
class QualityChecker {
/**
* Check translation quality.
*/
public function checkQuality(
string $source,
string $translation,
string $source_lang,
string $target_lang
): array {
$issues = [];
// 1. Length check (translation shouldn't be 3x longer/shorter)
$ratio = mb_strlen($translation) / mb_strlen($source);
if ($ratio < 0.3 || $ratio > 3) {
$issues[] = 'Length ratio unusual: ' . round($ratio, 2);
}
// 2. HTML tag consistency
if ($this->hasHtmlTags($source)) {
if (!$this->tagsMatch($source, $translation)) {
$issues[] = 'HTML tags mismatch';
}
}
// 3. Placeholder consistency
$source_placeholders = $this->extractPlaceholders($source);
$trans_placeholders = $this->extractPlaceholders($translation);
if ($source_placeholders !== $trans_placeholders) {
$issues[] = 'Placeholder mismatch';
}
// 4. Character encoding issues
if (mb_detect_encoding($translation, 'UTF-8', true) === false) {
$issues[] = 'Encoding issue detected';
}
return [
'has_issues' => !empty($issues),
'issues' => $issues,
'quality_score' => $this->calculateScore($issues),
];
}
/**
* Calculate quality score (0-100).
*/
protected function calculateScore(array $issues): int {
return max(0, 100 - (count($issues) * 20));
}
}
Monitoring & Analytics
Translation Analytics Dashboard
/**
* Translation Analytics Service.
*/
class TranslationAnalytics {
/**
* Get translation statistics.
*/
public function getStatistics(string $period = 'month'): array {
$start = strtotime("-1 $period");
$query = \Drupal::database()->select('translation_log', 't');
$query->fields('t', ['language', 'characters', 'cost']);
$query->condition('timestamp', $start, '>=');
$results = $query->execute()->fetchAll();
$stats = [];
foreach ($results as $row) {
$lang = $row->language;
if (!isset($stats[$lang])) {
$stats[$lang] = [
'translations' => 0,
'characters' => 0,
'cost' => 0,
];
}
$stats[$lang]['translations']++;
$stats[$lang]['characters'] += $row->characters;
$stats[$lang]['cost'] += $row->cost;
}
return $stats;
}
/**
* Get most translated content types.
*/
public function getTopContentTypes(): array {
$query = \Drupal::database()->select('translation_log', 't');
$query->fields('t', ['entity_type', 'bundle']);
$query->addExpression('COUNT(*)', 'count');
$query->groupBy('entity_type');
$query->groupBy('bundle');
$query->orderBy('count', 'DESC');
$query->range(0, 10);
return $query->execute()->fetchAll();
}
}
Fazit
Die Kombination von Drupal 11 und DeepL ermöglicht Enterprise-Multilingual-Websites mit 30+ Sprachen bei überschaubarem Budget und Wartungsaufwand. Durch intelligente Workflows, Glossare und Quality-Assurance-Prozesse erreichen Sie professionelle Übersetzungsqualität bei Kostenersparnis von 95%+ gegenüber manueller Übersetzung.
Key Takeaways:
- Glossare für konsistente Fachterminologie
- Hybrid-Workflow (Auto + Human Review) für kritischen Content
- Content-Priorisierung nach Business-Value
- Performance-Optimierung durch Caching und Batch-Processing
- Kontinuierliches Monitoring und Quality-Assurance
Planen Sie eine mehrsprachige Drupal 11 Website? Mit über 20 Jahren Erfahrung in komplexen Drupal-Projekten unterstütze ich Sie bei Architektur, Implementation und Optimierung. Kontakt: mail@stevenschulz.net oder 04037420859