mit ICU Message Format professionell umsetzen
Einfache String-Ersetzungen reichen für echte Mehrsprachigkeit nicht aus. Pluralformen, Geschlechtsanpassungen, Datums- und Zahlenformate nach Locale und kontextabhängige Meldungen brauchen das ICU Message Format — das die Symfony-Translation-Komponente seit Version 5 vollständig unterstützt.
Inhaltsverzeichnis
- 1. Warum einfache Übersetzungen nicht ausreichen
- 2. Symfony Translation einrichten und konfigurieren
- 3. ICU Message Format: Syntax und Grundlagen
- 4. Pluralisierung korrekt umsetzen
- 5. Select-Ausdrücke für Geschlecht und Varianten
- 6. Datum, Uhrzeit und Zahlen lokalisieren
- 7. XLIFF-Kataloge verwalten und extrahieren
- 8. Translation in Twig-Templates einsetzen
- 9. ICU vs. klassisches Symfony-Format im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum einfache Übersetzungen nicht ausreichen
Viele Symfony-Projekte starten mit simplem String-Replacement: Ein Schlüssel wird einem Satz zugeordnet, Variablen werden mit %count% eingebettet. Das funktioniert für Englisch gut — Englisch hat nur zwei Pluralformen, kennt kaum Kasus und kommt mit wenig grammatikalischer Flexion aus. Sobald Deutsch, Russisch, Arabisch oder Polnisch ins Spiel kommen, stößt dieses System an harte Grenzen. Polnisch hat vier Pluralformen, Arabisch sechs, und viele Sprachen passen Artikel und Adjektive ans Geschlecht des Subjekts an. Mit klassischem Symfony Translation und %count%-Variablen sind diese Anforderungen nicht elegant lösbar.
Das ICU Message Format ist der internationale Standard für diese Probleme. Es wurde von der Unicode-Organisation spezifiziert, ist in Android, iOS, Java und allen modernen Frontend-Frameworks verbreitet und steht in PHP über die intl-Extension zur Verfügung. Symfony Translation integriert das ICU Message Format als nativen Formatter — Übersetzungskataloge können beide Formate mischen, ältere Meldungen müssen nicht sofort migriert werden. Der entscheidende Vorteil: Übersetzerinnen und Übersetzer beschreiben die sprachliche Logik direkt in der Übersetzung, nicht in PHP-Code.
2. Symfony Translation einrichten und konfigurieren
Die Symfony-Translation-Komponente ist im Standard-Symfony-Skeleton über das Flex-Recipe bereits enthalten. Für ICU Message Format braucht man zusätzlich die PHP intl-Extension, die in den meisten PHP-Installationen aktiv ist. In config/packages/translation.yaml legt man die Standard-Locale, den Fallback und den Pfad zu den Katalogdateien fest. Der Formatter-Typ wird pro Dateiname durch die Dateiendung gesteuert: Dateien mit der Endung +intl-icu.xlf oder +intl-icu.yaml verwenden automatisch den ICU-Formatter, alle anderen Dateien nutzen den klassischen Symfony-Formatter.
Die empfohlene Verzeichnisstruktur legt Übersetzungen in translations/ im Projektroot ab, unterteilt nach Domain und Locale: messages+intl-icu.de.xlf für die Standard-Domain auf Deutsch, validators+intl-icu.de.xlf für Validierungsmeldungen. Der Befehl bin/console translation:extract de --format=xlf20 --output-format=xliff2 --force durchsucht automatisch alle Twig-Templates und PHP-Klassen nach verwendeten Translations-Schlüsseln und legt fehlende Einträge in den Katalogen an. Das spart erhebliche manuelle Arbeit beim Pflegen großer Übersetzungsdateien.
<?php
// config/packages/translation.yaml
// framework:
// default_locale: de
// translator:
// default_path: '%kernel.project_dir%/translations'
// fallbacks: [en]
// providers: []
// Directory structure for ICU-enabled translations:
// translations/
// messages+intl-icu.de.xlf ← ICU formatter activated by filename suffix
// messages+intl-icu.en.xlf
// validators+intl-icu.de.xlf
// emails+intl-icu.de.xlf
// Extract all translation keys from Twig + PHP automatically:
// bin/console translation:extract de --format=xlf20 --force
// bin/console translation:extract en --format=xlf20 --force
// Check for missing translations:
// bin/console debug:translation de
// bin/console debug:translation de --only-missing
// Verify intl extension is available (required for ICU):
// php -m | grep intl
3. ICU Message Format: Syntax und Grundlagen
Das ICU Message Format erweitert einfache Variablenersetzungen um eine vollständige Ausdruckssprache für sprachliche Varianten. Variablen werden in geschweifte Klammern geschrieben: {name} ersetzt die Variable direkt. Für komplexere Ausdrücke folgt dem Variablennamen ein Komma und der Ausdruckstyp: {count, plural, one {# Artikel} other {# Artikel} }. Das Rautezeichen # steht dabei für den formatierten Wert der aktuellen Variablen. Diese Syntax ist für Übersetzerinnen und Übersetzer lesbar genug, um sie ohne PHP-Kenntnisse zu bearbeiten — ein wichtiger Faktor für professionelle Übersetzungs-Workflows mit externen Agenturen.
Der Symfony-Translator übergibt Parameter als assoziatives Array an die trans()-Methode. Im klassischen Format werden Platzhalter wie %name% ersetzt. Im ICU Message Format übergibt man die Werte direkt ohne Prozentzeichen: $translator->trans('greeting', ['name' => 'Maria'], domain: 'messages'). Die ICU-Engine kümmert sich selbst um Typprüfung, Formatierung und die Auswahl der richtigen Sprachvariante. Das Mischen beider Formate in einem Projekt ist möglich — alte Dateien mit der klassischen Endung nutzen den alten Formatter, neue ICU-Dateien mit dem +intl-icu-Suffix den ICU-Formatter.
4. Pluralisierung korrekt umsetzen
Pluralformen sind das häufigste Problem in der Symfony-Translation. Das klassische Format nutzt eine eigene Pipe-Syntax (One article|{count} articles), die für komplexe Pluralregeln schnell unübersichtlich wird. Das ICU Message Format löst Pluralformen mit dem plural-Ausdruck, der von der intl-Extension die sprachspezifischen Pluralregeln der aktuellen Locale lädt. Für Deutsch gibt es die Schlüssel one (Singular) und other (Plural). Für Russisch kommen few und many dazu. Das ICU Message Format kennt die Regeln für alle Sprachen — das Team muss nur die Texte liefern, nicht die Logik programmieren.
Besonders mächtig ist die Kombination von Pluralisierung mit Variablen in derselben Meldung. Eine typische E-Commerce-Meldung: „Sie haben 3 Artikel im Warenkorb. Der Gesamtpreis beträgt 89,90 €." müsste bei einem Artikel den Singular verwenden und den Preis locale-korrekt formatieren. Im ICU Message Format ist das eine einzige Übersetzungseinheit, die alle Fälle deklarativ abdeckt — ohne if-else-Konstrukte im PHP-Code.
<?php
declare(strict_types=1);
namespace App\Service;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Demonstrates ICU Message Format usage in a Symfony service.
*/
final readonly class OrderSummaryService
{
public function __construct(
private TranslatorInterface $translator,
) {}
/**
* Build a human-readable order summary using ICU pluralization.
*/
public function getSummaryMessage(int $itemCount, float $total, string $locale): string
{
// ICU plural — the translator selects the correct plural rule for the locale
return $this->translator->trans(
id: 'order.summary',
parameters: [
'count' => $itemCount,
'total' => $total,
],
locale: $locale,
);
}
}
// translations/messages+intl-icu.de.xlf entry (simplified):
// <trans-unit id="order.summary">
// <source>order.summary</source>
// <target>{count, plural,
// one {Sie haben # Artikel im Warenkorb. Gesamt: {total, number, ::currency/EUR}.}
// other {Sie haben # Artikel im Warenkorb. Gesamt: {total, number, ::currency/EUR}.}
// }</target>
// </trans-unit>
// English variant in messages+intl-icu.en.xlf:
// {count, plural,
// one {You have # item in your cart. Total: {total, number, ::currency/EUR}.}
// other {You have # items in your cart. Total: {total, number, ::currency/EUR}.}
// }
5. Select-Ausdrücke für Geschlecht und Varianten
Der select-Ausdruck im ICU Message Format wählt basierend auf einem String-Wert zwischen vordefinierten Varianten. Das klassische Anwendungsszenario ist die Anpassung von Anrede und Possessivpronomen nach Geschlecht: {gender, select, female {Ihre Bestellung} male {Seine Bestellung} other {Ihre Bestellung} }. Der other-Zweig ist Pflicht und dient als Fallback für alle nicht explizit genannten Werte. Das ermöglicht auch den Fall „divers" oder unbekanntes Geschlecht ohne separate Übersetzungseinheit.
Verschachtelte select- und plural-Ausdrücke sind im ICU Message Format erlaubt und decken komplexe sprachliche Fälle ab. Ein Beispiel: Auf Russisch muss ein Substantiv nach Numerale nicht nur nach Pluralform, sondern auch nach Geschlecht flektiert werden. Diese Logik liegt vollständig in der Übersetzungsdatei — der PHP-Code bleibt unverändert. Für Teams, die mit externen Übersetzungsagenturen arbeiten, ist das ein wichtiger Vorteil: Die Agenturen arbeiten in ihrer Fachdomäne, ohne dass ein Entwicklereingriff nötig ist.
6. Datum, Uhrzeit und Zahlen lokalisieren
Das ICU Message Format bringt eigene Formatter für Datum, Uhrzeit und Zahlen mit, die aus dem intl-Standard stammen. Ein Datum formatiert man mit {date, date, medium} für ein mittellanges Format (z. B. „9. Mai 2026" auf Deutsch, „May 9, 2026" auf Englisch). short, long und full sind weitere Stufen. Für Zahlen steht {amount, number, ::currency/EUR} bereit, das die Zahl locale-korrekt mit Währungssymbol formatiert: „89,90 €" auf Deutsch, „€89.90" auf Englisch. Diese Formatter verwenden automatisch die aktive Locale des Symfony-Translators.
Für Uhrzeiten gilt {time, time, short} für „14:30" (Deutsch) bzw. „2:30 PM" (Englisch-US). Relative Zeitangaben wie „vor 5 Minuten" oder „in 2 Tagen" kann man kombinieren: Eine Übersetzungseinheit enthält einen plural-Ausdruck für die Zahl und einen select-Ausdruck für Vergangenheit/Zukunft. Das ist ausdrucksstärker als das klassische Symfony-Choiceformat und benötigt kein zusätzliches Twig-Filter. Die Symfony-Translation-Komponente übergibt alle Parameter als native PHP-Werte — \DateTimeInterface-Objekte werden automatisch vom ICU-Formatter korrekt umgewandelt.
7. XLIFF-Kataloge verwalten und extrahieren
XLIFF 2.0 ist das empfohlene Dateiformat für Symfony Translation in produktiven Projekten. Es ist der ISO-Standard für den Austausch von Übersetzungsdaten, wird von professionellen CAT-Tools (Computer-Aided Translation) wie SDL Trados, MemoQ und Memsource unterstützt und enthält Metadaten über den Übersetzungsstatus einzelner Einheiten. Das bedeutet: Wenn das Team mit einer externen Übersetzungsagentur arbeitet, können XLIFF-Dateien direkt übergeben werden, ohne ein proprietäres Format zu benötigen.
Der Extractions-Befehl bin/console translation:extract durchsucht Twig-Templates nach trans-Tags und PHP-Dateien nach $translator->trans()-Aufrufen. Neu gefundene Schlüssel werden in die bestehenden XLIFF-Dateien eingefügt, ohne vorhandene Übersetzungen zu überschreiben. Das --sort-Flag sortiert die Einheiten alphabetisch, was Diff-Lesbarkeit im Git-Repository verbessert. Mit --clean entfernt der Befehl Einträge, die im Code nicht mehr vorkommen — ideal für die regelmäßige Bereinigung alter Übersetzungsschlüssel in aktiv entwickelten Projekten.
<?xml version="1.0" encoding="utf-8"?>
<!--
translations/messages+intl-icu.de.xlf
XLIFF 2.0 with ICU Message Format content
The +intl-icu suffix in the filename activates the ICU formatter automatically.
-->
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0"
srcLang="en" trgLang="de">
<file id="messages+intl-icu.de">
<!-- Simple variable substitution -->
<unit id="greeting.user">
<segment state="translated">
<source>Hello, {name}!</source>
<target>Hallo, {name}!</target>
</segment>
</unit>
<!-- ICU plural with currency formatting -->
<unit id="cart.summary">
<segment state="translated">
<source>{count, plural, one {# item} other {# items} } — Total: {total, number, ::currency/EUR}</source>
<target>{count, plural, one {# Artikel} other {# Artikel} } — Gesamt: {total, number, ::currency/EUR}</target>
</segment>
</unit>
<!-- ICU select for gender-aware salutation -->
<unit id="order.salutation">
<segment state="translated">
<source>Dear {gender, select, female {Ms.} male {Mr.} other {} } {name},</source>
<target>{gender, select, female {Sehr geehrte Frau} male {Sehr geehrter Herr} other {Sehr geehrte/r} } {name},</target>
</segment>
</unit>
<!-- Date and time formatting via ICU -->
<unit id="invoice.date">
<segment state="translated">
<source>Invoice date: {date, date, long}</source>
<target>Rechnungsdatum: {date, date, long}</target>
</segment>
</unit>
</file>
</xliff>
8. Translation in Twig-Templates einsetzen
In Twig-Templates nutzt man den trans-Filter und das {% trans %}-Tag für Symfony-Übersetzungen. Mit dem ICU Message Format übergibt man Parameter als Hash: { { 'cart.summary'|trans({count: items|length, total: cartTotal}) } }. Der Filter erkennt automatisch die aktive Locale und leitet den Aufruf an den ICU-Formatter weiter, wenn der Schlüssel in einer +intl-icu-Datei liegt. Die Angabe einer Domain erfolgt als zweiter Parameter: { { 'form.submit'|trans({}, 'forms') } }.
Für längere Übersetzungsblöcke mit eingebettetem HTML empfiehlt sich das {% trans %}-Tag. Es unterstützt ICU-Variablen und gibt den gesamten Block als Übersetzungseinheit zurück. Wichtig: HTML in Übersetzungsstrings sollte auf ein Minimum beschränkt werden, weil es die Arbeit für Übersetzerinnen und Übersetzer erschwert. Besser ist es, den HTML-Rahmen im Template zu halten und nur den Text zu übersetzen. Der translation:extract-Befehl erkennt beide Schreibweisen und fügt beide korrekt in den XLIFF-Katalog ein — ohne manuellen Eingriff.
9. ICU vs. klassisches Symfony-Format im Vergleich
Der Vergleich zeigt, wo das ICU Message Format gegenüber dem klassischen Symfony-Translation-Format gewinnt und wo der Unterschied gering ist.
| Anforderung | Klassisches Symfony-Format | ICU Message Format | Empfehlung |
|---|---|---|---|
| Einfache Variable | %name% |
{name} |
ICU: sauberer, standardkonform |
| Pluralisierung | Pipe-Syntax, begrenzt auf 2–3 Formen | Alle Pluralklassen der Locale | ICU: unverzichtbar für Slawisch/Arabisch |
| Geschlecht / Select | Nicht nativ unterstützt | {gender, select, …} |
ICU: einzige saubere Lösung |
| Zahlen/Währung | Twig-Filter nötig | {amount, number, ::currency/EUR} |
ICU: in Übersetzung integriert |
| Kompatibilität mit CAT-Tools | XLIFF ist kompatibel | XLIFF + ICU: Industriestandard | ICU: von Agenturen bevorzugt |
Für einfache Projekte mit wenigen Sprachen und ohne komplexe Grammatikanforderungen funktioniert das klassische Symfony-Translation-Format weiterhin gut. Sobald Russisch, Arabisch, Polnisch oder asiatische Sprachen hinzukommen, ist das ICU Message Format die einzige Lösung, die ohne Hacks in PHP-Code auskommt. Der Migrationspfad ist graduell: Neue Kataloge bekommen die +intl-icu-Endung, alte Kataloge laufen parallel weiter.
Mironsoft
Symfony-Entwicklung, i18n-Architektur und Mehrsprachigkeits-Strategie
Symfony-Applikation professionell mehrsprachig machen?
Wir implementieren vollständige i18n-Lösungen mit Symfony Translation und ICU Message Format — von der XLIFF-Katalog-Struktur über Pluralformen und Geschlechtsvarianten bis zur automatischen Extraktion und CAT-Tool-kompatiblen Workflows.
i18n-Architektur
XLIFF-Katalog-Struktur, ICU-Integration und Extractions-Workflow für Symfony-Projekte jeder Größe
Migration
Bestehende Symfony-Translation-Projekte graduell auf ICU Message Format migrieren ohne Breaking Changes
Übersetzungs-Workflow
CAT-Tool-Integration, XLIFF-Export und automatische Synchronisierung mit Übersetzungsagenturen
10. Zusammenfassung
Das ICU Message Format in der Symfony-Translation-Komponente löst die wichtigsten Schwächen klassischer String-Ersetzungssysteme: Pluralformen für alle Sprachen, Geschlechtsanpassungen über select, locale-korrekte Datums-, Uhrzeit- und Zahlenformatierung direkt in der Übersetzungseinheit — ohne PHP-Code-Änderungen. Die Aktivierung erfolgt über das Dateinamensuffix +intl-icu, die Migration von bestehenden Projekten ist graduell möglich. XLIFF 2.0 als Dateiformat stellt Kompatibilität mit professionellen CAT-Tools sicher und ermöglicht reibungslose Zusammenarbeit mit Übersetzungsagenturen.
Der größte Hebel liegt in der Verlagerung von sprachlicher Logik aus dem PHP-Code in die Übersetzungsdatei. Ein Entwickler muss nicht wissen, wie viele Pluralformen Russisch hat — die Übersetzerin weiß es und trägt es direkt in den XLIFF-Katalog ein. Das Symfony-Translation-System verarbeitet diese Information korrekt, ohne dass ein Deployment-Zyklus für sprachliche Änderungen nötig ist. In Projekten mit aktiven Übersetzungsteams ist das ein fundamentaler Effizienzgewinn.
Symfony Translation mit ICU — Das Wichtigste auf einen Blick
ICU aktivieren
Dateiendung +intl-icu.xlf oder +intl-icu.yaml — der ICU-Formatter wird automatisch aktiviert. PHP intl-Extension ist Voraussetzung.
Plural & Select
{count, plural, one {# Artikel} other {# Artikel} } und {gender, select, female {…} male {…} other {…} } decken alle sprachlichen Fälle ab.
Zahlen & Datum
{amount, number, ::currency/EUR} und {date, date, long} formatieren locale-korrekt ohne Twig-Filter oder PHP-Formatter.
Extraktion
bin/console translation:extract de --format=xlf20 --force findet alle Schlüssel automatisch und pflegt XLIFF-Kataloge ohne manuellen Aufwand.
11. FAQ: Symfony Translation & i18n mit ICU Message Format
1Was ist das ICU Message Format in Symfony?
+intl-icu. PHP intl-Extension erforderlich.2Wie aktiviere ich ICU in Symfony?
messages+intl-icu.de.xlf benennen. Symfony erkennt das Suffix automatisch und schaltet den ICU-Formatter ein — keine weitere Konfiguration nötig.3Plural-Formen im ICU Message Format?
4ICU und klassisches Format mischen?
+intl-icu-Suffix. Migration ist graduell möglich — alte Kataloge müssen nicht sofort umgestellt werden.5Schlüssel automatisch extrahieren?
bin/console translation:extract de --format=xlf20 --force — findet alle Schlüssel in Twig und PHP, fügt fehlende Einträge in XLIFF ein, lässt bestehende Übersetzungen unberührt.6Währungsformatierung mit ICU?
{amount, number, ::currency/EUR} in der Übersetzungseinheit. Locale-korrekt: auf Deutsch „89,90 €", auf Englisch „€89.90". Kein Twig-Filter oder PHP-Formatter nötig.7DateTimeInterface direkt übergeben?
DateTimeInterface-Objekte automatisch. {date, date, long} in der ICU-Einheit formatiert locale-korrekt ohne manuelles date() oder Twig-Filter.8XLIFF kompatibel mit Übersetzungstools?
9Select-Ausdruck für Geschlecht?
{gender, select, female {Sehr geehrte Frau} male {Sehr geehrter Herr} other {Sehr geehrte/r} }. Der other-Zweig ist Pflicht und fängt alle unbekannten Werte ab.10Welche PHP-Extension brauche ich?
intl-Extension. Prüfung: php -m | grep intl. In Docker: apt-get install php-intl. In den meisten PHP-Installationen standardmäßig aktiv.