Ein mehrsprachiges Portfolio mit Symfony entwickeln: Die Markdown-Editor-Herausforderung

Feb 01, 2026 8 Min. Lesezeit 114 Aufrufe
Ein mehrsprachiges Portfolio mit Symfony entwickeln: Die Markdown-Editor-Herausforderung

Als ich mein persönliches Portfolio-Website aufbauen wollte, hatte ich eine nicht verhandelbare Anforderung: Es musste Arabisch, Englisch und Deutsch gleichermaßen gut unterstützen. Nicht als Nachgedanke, nicht mit einem Workaround — sondern ordentlich, mit vollständiger Rechts-nach-links-Unterstützung (RTL) für Arabisch und links-nach-rechts (LTR) für die anderen. Was ich nicht erwartet hatte, war, wie sehr diese eine Anforderung ein scheinbar kleines Teil des Puzzles verkomplizieren würde: den Inhaltseditor des Blogs.

Das Projekt

Das Portfolio — sym-port — ist eine Symfony 7-Anwendung, die als vollständige persönliche Website mit Blog, Projektpräsentation, Empfehlungen, mehrsprachigen Übersetzungen und einem Kontaktformular entwickelt wurde. Der Tech-Stack ist bewusst modern:

  • Symfony 7.3 mit Asset Mapper (kein Webpack)
  • Doctrine ORM 3 mit MySQL
  • Symfony UX Turbo für schnelle Navigation
  • Tailwind CSS über das SymfonyCasts-Bundle
  • VichUploaderBundle für Datei-Uploads
  • Eigenes datenbankbasiertes Übersetzungssystem für UI-Texte

Der Blog war immer als zentrales Feature geplant. Ich schreibe über Webentwicklung auf Arabisch und Englisch, und ich wollte auch deutschsprachige Leser erreichen. Jeder Blogbeitrag hat drei separate Inhaltsfelder — eines pro Sprache — die als Markdown gespeichert und beim Anzeigen in HTML gerendert werden.

Das Editor-Problem

Für einen Blog, der Markdown speichert, gibt es zwei Optionen: Rohes Markdown in einem einfachen Textarea schreiben oder einen Rich-WYSIWYG-Editor verwenden, der die Ausgabe vorab anzeigt. Besonders für arabische Inhalte ist das Schreiben in einem einfachen Textarea unpraktisch. Arabischer Text fließt von rechts nach links, und die meisten Textareas sind standardmäßig LTR ausgerichtet — der Cursor beginnt auf der falschen Seite, der Text bricht falsch um, und das Lesen des Geschriebenen wird mühsam.

Ich brauchte einen WYSIWYG-Markdown-Editor, der:

  1. Arabischen (RTL) Text korrekt darstellt — rechtsbündig, korrekte Cursorpositionierung
  2. Englischen und deutschen (LTR) Text in derselben Editor-Sitzung verarbeitet
  3. Eine Split-Ansicht bietet: Markdown-Quelle auf einer Seite, gerendertes Vorschau auf der anderen
  4. Mit Symfonys Formularsystem zusammenarbeitet
  5. Alle Assets lokal bereitstellt — keine CDN-Abhängigkeiten im Produktionsbetrieb

Die letzte Anforderung ist wichtiger als sie klingt. Die Produktionszuverlässigkeit sollte nicht davon abhängen, ob uicdn.toast.com oder fonts.googleapis.com erreichbar sind. Ich wollte alles selbst hosten.

Was ich ausprobiert habe

CKEditor und TinyMCE

Beide sind ausgereifte, weit verbreitete Editoren. Beide haben eine gewisse RTL-Unterstützung. Aber keiner ist wirklich ein Markdown-Editor — sie produzieren direkt HTML. Die Konvertierung zwischen HTML und Markdown führt zu Mehrdeutigkeiten und Formatierungsverlusten. Und ihre Symfony-Bundles fügen erhebliche Komplexität hinzu.

SimpleMDE / EasyMDE

Reine Markdown-Editoren, sauber und leichtgewichtig. Aber die RTL-Unterstützung ist praktisch nicht vorhanden. Das Vorschaufenster wendet keine RTL-Richtung auf arabischen Text an, das Textarea erkennt keine Textrichtung, und es gibt keine Möglichkeit, dies ohne eine Fork des Projekts zu konfigurieren.

Toast UI Editor (standalone)

Toast UI Editor erfüllte die meisten Anforderungen: Es ist ein echter Markdown-Editor (speichert und liest das .md-Format), hat einen WYSIWYG-Modus, unterstützt Split-Vorschau und wird aktiv gewartet. Der Haken: Die bestehenden Symfony-Integrationen, die ich gefunden habe, luden Assets alle aus einem CDN und keine handhabte RTL-Korrekt von Haus aus.

Die Split-View-Trennlinie — die visuelle Trennlinie zwischen dem Markdown-Eingabefeld und dem Vorschaufenster — war im RTL-Modus vollständig unsichtbar. Das stellte sich als CSS-Bug heraus: Eine Regel, die left: auto; right: 0 auf das Splitter-Element setzte und es an den äußersten Rand des Containers schob statt in die Mitte. Kleiner Bug, bricht die Split-Ansicht komplett.

Es gab auch ein ernsteres PHP-Problem: Das einzige vorhandene Symfony-Bundle für Toast UI Editor hatte seinen MarkdownRuntime-Twig-Dienst in seiner services.yaml auskommentiert. Der |markdown-Filter in Templates würde stillschweigend fehlschlagen, es sei denn, man registrierte den Dienst manuell in der eigenen services.yaml der App. Das entdeckte ich erst nach dem Deployment, als ich mich fragte, warum Blogbeiträge als roher Markdown-Text gerendert wurden.

Die Entscheidung, runio/mde zu bauen

Nachdem ich Zeit damit verbracht hatte, diese Probleme in einem geforkten Projekt zu beheben, erkannte ich, dass ich etwas pflegte, das vom Upstream abweichen und zu einer Wartungslast werden würde. Der bessere Weg war, ein sauberes, ordentlich strukturiertes Symfony-Bundle zu bauen, das:

  • Den RTL-Splitter-Bug dauerhaft behebt
  • Alle Dienste korrekt registriert — keine manuellen Workarounds
  • Toast UI Editor und seine Schriften lokal bündelt
  • Mobile Responsivität hinzufügt (das bestehende CSS hatte praktisch keine mobile Unterstützung)
  • Korrekt mit Symfonys Asset Mapper integriert
  • Symfony UX Turbo von Haus aus unterstützt

Das Ergebnis ist runio/mde — ein auf Packagist veröffentlichtes Symfony-Bundle. Für die vollständige technische Geschichte, wie es aufgebaut ist und welche Probleme es unter der Haube löst, siehe meinen Folgebeitrag: Vorstellung von runio/mde: Ein RTL-fähiges Markdown-Editor-Bundle für Symfony.

Wie sym-port es verwendet

Im Blog-Beitrags-Formular verwendet jedes Sprachfeld MarkdownEditorType:

$builder->add('content', MarkdownEditorType::class, [
    'editor_mode'  => 'wysiwyg',
    'rtl_enabled'  => false,
    'language'     => 'en',
    'toolbar'      => 'full',
]);

$builder->add('contentAr', MarkdownEditorType::class, [
    'editor_mode'  => 'wysiwyg',
    'rtl_enabled'  => true,
    'language'     => 'ar',
    'toolbar'      => 'full',
]);

Das RTL-Flag dreht den gesamten Editor um — Toolbar-Richtung, Textausrichtung, Zitatrahmen-Seite, Listen-Einrückung — über CSS, das auf das Wrapper-Element angewendet wird. Code-Blöcke bleiben immer LTR, unabhängig von der umgebenden Inhaltsrichtung.

Blogbeiträge werden als Markdown in der Datenbank gespeichert. Bei der Anzeige wandelt der |markdown-Twig-Filter (bereitgestellt vom Bundle) sie in bereinigtes HTML um. Die Bereinigungsschicht entfernt unsichere Tags und Attribute, sodass vom Editor generierter Inhalt direkt sicher gerendert werden kann.

Gelernte Lektionen

RTL frühzeitig testen. Wenn Ihre Anwendung Arabisch oder Hebräisch unterstützen muss, lassen Sie RTL-Tests nicht für das Ende. Die Wechselwirkungen zwischen CSS-Richtung, Editor-Bibliotheken und Formularrendering sind subtil und leicht falsch zu machen.

CDN-Abhängigkeiten sind ein Deployment-Risiko. Kritisches Editor-JavaScript auf uicdn.toast.com zu hosten bedeutet, dass ein CDN-Ausfall oder eine Netzwerkrichtlinienänderung bei Ihrem Hosting-Anbieter Ihr Admin-Panel stillschweigend brechen kann. Hosten Sie Wichtiges selbst.

Bundle-Beiträge haben kumulativen Wert. Die Zeit, die ich mit dem Aufbau von runio/mde verbracht habe, hat anfangs mehr gekostet als ein schneller Patch. Aber das Ergebnis ist ein wiederverwendbares, testbares, versioniertes Paket, das ich in zukünftigen Projekten einsetzen kann. Die Investition zahlt sich aus.

Das Portfolio ist live unter runio.dev. Der Blog-Editor verarbeitet nun arabische, englische und deutsche Inhalte korrekt in einem einzigen Formular, mit einer funktionierenden Split-View-Vorschau und ohne CDN-Abhängigkeiten. Das ist die Ausgangslage, die ich von Anfang an hätte haben sollen.

Diesen Beitrag teilen

AM

Autor Amer Malik Mohammed

Full-Stack Entwickler mit 2+ Jahren Erfahrung in objektorientierter PHP-Entwicklung (Symfony), JavaScript und MySQL. Spezialisiert auf E-Commerce-Lösungen (Shopware 5/6), REST-API-Entwicklung und Prozessautomatisierung in agilen Teams.

Autor kontaktieren