When I set out to build my personal portfolio website, I had one non-negotiable requirement: it had to support Arabic, English, and German equally well. Not as an afterthought, not with a workaround β but properly, with full right-to-left (RTL) support for Arabic and left-to-right (LTR) for the others. What I didn't anticipate was how much this single requirement would complicate one seemingly small piece of the puzzle: the blog's content editor.
The Project
The portfolio, which I call sym-port, is a Symfony 7 application built as a full-featured personal website with a blog, project showcase, testimonials, multilingual translations, and a contact form. The tech stack is deliberately modern:
- Symfony 7.3 with Asset Mapper (no webpack)
- Doctrine ORM 3 with MySQL
- Symfony UX Turbo for fast navigation
- Tailwind CSS via SymfonyCasts bundle
- VichUploaderBundle for file uploads
- Custom database-driven translation system for UI strings
The blog was always going to be a central feature. I write about web development in both Arabic and English, and I wanted to reach German-speaking readers as well. Each blog post has three separate content fields β one per language β stored as Markdown and rendered to HTML on display.
The Editor Problem
For a blog that stores Markdown, you have two options: write raw Markdown in a plain textarea, or use a rich WYSIWYG editor that previews the output. For Arabic content especially, writing in a plain textarea is impractical. Arabic text flows right-to-left, and most textareas default to LTR β the cursor starts on the wrong side, text wraps incorrectly, and reading back what you wrote becomes exhausting.
I needed a WYSIWYG Markdown editor that could:
- Render Arabic (RTL) text correctly β right-aligned, proper cursor placement
- Handle English and German (LTR) text in the same editor session
- Provide a split view: markdown source on one side, rendered preview on the other
- Work with Symfony's form system
- Serve all assets locally β no CDN dependencies in production
That last requirement is more important than it sounds. Production reliability shouldn't depend on uicdn.toast.com or fonts.googleapis.com being reachable. I wanted everything self-hosted.
What I Tried
CKEditor and TinyMCE
Both are mature, widely-used editors. Both have some RTL support. But neither is truly a Markdown editor β they produce HTML directly. Converting between HTML and Markdown introduces ambiguity and formatting loss. And their Symfony bundles add considerable complexity.
SimpleMDE / EasyMDE
Pure Markdown editors, clean and lightweight. But RTL support is essentially nonexistent. The preview panel doesn't apply RTL direction to Arabic text, the textarea doesn't detect text direction, and there's no way to configure this without forking the project.
Toast UI Editor (standalone)
Toast UI Editor checked most boxes: it's a proper Markdown editor (stores and reads .md format), has a WYSIWYG mode, supports split preview, and is actively maintained. The catch: the existing Symfony integrations I found all loaded assets from CDN, and none handled RTL properly out of the box.
The split-view divider line β the visual separator between the Markdown input and the preview panel β was completely invisible in RTL mode. This turned out to be a CSS bug: a rule that set left: auto; right: 0 on the splitter element, pushing it to the far edge of the container instead of the center. Small bug, completely breaks the split view.
There was also a more serious PHP issue: the only existing Symfony bundle for Toast UI Editor had its MarkdownRuntime Twig service commented out in its services.yaml. The |markdown filter in templates would silently fail unless you manually re-registered the service in your own app's services.yaml. I only discovered this after deploying and wondering why blog posts were rendering as raw Markdown text.
The Decision to Build runio/mde
After spending time patching these issues in a forked project, I realized I was maintaining something that would diverge from upstream and become a maintenance burden. The better path was to build a clean, properly structured Symfony bundle that:
- Fixes the RTL splitter bug permanently
- Registers all services correctly β no manual workarounds
- Bundles Toast UI Editor and its fonts locally
- Adds mobile responsiveness (the existing CSS had essentially no mobile support)
- Integrates correctly with Symfony Asset Mapper (not just the older
assets:installapproach) - Supports Symfony UX Turbo out of the box
The result is runio/mde β a Symfony bundle published on Packagist. For the full technical story of how it's built and what problems it solves under the hood, see my follow-up post: Introducing runio/mde: An RTL-Ready Markdown Editor Bundle for Symfony.
How sym-port Uses It
In the blog post form, each language field uses 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',
]);
The RTL flag flips the entire editor β toolbar direction, text alignment, blockquote border side, list padding β via CSS applied to the wrapper element. Code blocks always stay LTR regardless of the surrounding content direction.
Blog posts are stored as Markdown in the database. On display, the |markdown Twig filter (provided by the bundle) converts them to sanitized HTML. The sanitization layer strips unsafe tags and attributes, so editor-generated content is safe to render directly.
Lessons Learned
Test RTL early. If your application needs to support Arabic or Hebrew, don't leave RTL testing for the end. The interactions between CSS direction, editor libraries, and form rendering are subtle and easy to get wrong.
CDN dependencies are a deployment risk. Having critical editor JavaScript hosted on uicdn.toast.com means a CDN outage or a network policy change on your hosting provider can silently break your admin panel. Self-host what matters.
Bundle contributions have compounding value. The time I spent building runio/mde cost more upfront than a quick patch would have. But the result is a reusable, testable, versioned package that I can drop into future projects. The investment pays back.
The portfolio is live at runio.dev. The blog editor now handles Arabic, English, and German content correctly in a single form, with a working split-view preview and no CDN dependencies. That's the baseline I should have had from the start.
Author Amer Malik Mohammed
Full-Stack Developer with 2+ years of experience in object-oriented PHP development (Symfony), JavaScript and MySQL. Specialized in e-commerce solutions (Shopware 5/6), REST API development and process automation in agile teams.
Contact author