Skip to content

feat: add RTL support for Hebrew, Arabic, Farsi, and other languages#75

Open
adamatan wants to merge 6 commits intojfernandez:mainfrom
adamatan:feature/rtl-support
Open

feat: add RTL support for Hebrew, Arabic, Farsi, and other languages#75
adamatan wants to merge 6 commits intojfernandez:mainfrom
adamatan:feature/rtl-support

Conversation

@adamatan
Copy link
Copy Markdown

@adamatan adamatan commented Mar 21, 2026

Motivation

mdserve is designed as an AI coding agent companion. Developers working in Hebrew, Arabic, Farsi, and other RTL languages need their markdown previews to render correctly without manual configuration. This feature makes it work out of the box while preserving zero-config defaults.

Solution

Auto-detect popular RTL languages by counting RTL characters in the document. Documents that contain more than 50% RTL characters will be rendered in RTL mode, except for code blocks which should stay LTR. Two new CLI params - --rtl and --no-rtl - will override the detection if needed.

Implementation

Adds automatic detection and rendering of right-to-left (RTL) documents, with support for Hebrew, Arabic, Farsi, Syriac, Thaana, NKo, and related scripts.

  • Auto-detection: Triggers RTL when ≥50% of alphabetic characters belong to an RTL script (single-pass, zero allocation)
  • CLI flags: --rtl to force RTL, --no-rtl to disable detection
  • HTML/CSS: dir="rtl" on content div, RTL blockquote styling, code blocks forced LTR, logical table alignment (text-align: start)
  • Fonts: Multilingual system font stack (Hebrew + Arabic families, no CDN)
  • Tests: 60 tests covering detection logic, HTTP rendering, fixture files (Hebrew, Arabic, Farsi, English)
  • No new dependencies

Test plan

  • cargo test — 60 tests pass (detection unit tests, axum-test integration tests, fixture file tests)
  • cargo build --release — clean build, zero warnings
  • Manual verification with Hebrew, Arabic, Farsi, and English markdown files
  • Verified --rtl and --no-rtl CLI flags
  • Verified code blocks render LTR in RTL documents
  • Verified English documents are unaffected

🤖 Generated with Claude Code

Automatic detection and rendering of right-to-left (RTL) documents with
support for Hebrew, Arabic, Farsi, Syriac, Thaana, NKo, and related scripts.

Features:
- Auto-detect RTL when ≥50% of alphabetic characters are RTL script
- CLI flags: --rtl (force RTL), --no-rtl (disable detection)
- dir="rtl" attribute on content div
- RTL font stack (Hebrew + Arabic system fonts, no CDN)
- Blockquote border/padding flipped for RTL
- Code blocks forced LTR (only in RTL documents)
- Logical CSS table alignment (text-align: start)

Implementation:
- Single-pass detect_rtl() with no allocation
- RtlMode enum (Copy) with resolve() method
- RenderedFile struct bundles HTML + RTL flag
- Professional fixture files for testing (Hebrew, Arabic, Farsi, English)

Testing: 60 tests (all in src/app.rs #[cfg(test)])

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner

@jfernandez jfernandez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, nice work on the RTL support. I want to merge this but with a simpler approach.

Since mdserve is an agent companion, the AI agent already knows what language it's writing in. So I'd rather keep the --rtl flag and drop all the auto-detection (detect_rtl, is_rtl_char, RtlMode, RenderedFile, --no-rtl, per-file detection, fixtures). That's ~100 lines of plumbing for something the caller already knows. I'll teach the skill to pass --rtl when needed.

So keep: --rtl flag, dir="rtl", RTL fonts, code blocks forced LTR, text-align: start on tables.

Also, for blockquotes: use border-inline-start / padding-inline-start / margin-inline-start instead of the conditional override block. You already used logical properties for tables, so this keeps it consistent and removes another {% if is_rtl %} block.

Happy to merge once that's cleaned up.

@adamatan
Copy link
Copy Markdown
Author

Yeah, that make sense.
If we need autodetection, we can use a skill or script that would determine the direction and choose whether to use --rtl.

Since mdserve is an agent companion, the AI agent already knows what
language it's writing in — auto-detection is unnecessary plumbing.
Drop `detect_rtl`, `is_rtl_char`, `RtlMode`, `RenderedFile`, `--no-rtl`,
per-file detection, and the four language fixture files (~500 lines
removed).

Keep `--rtl` as a simple global bool: `dir="rtl"` on the content div,
RTL font stack, code blocks forced LTR, `text-align: start` on tables.

Switch blockquote styling from physical properties with a conditional
`{% if is_rtl %}` override to logical properties (`border-inline-start`,
`padding-inline-start`, `margin-inline-start`), consistent with the
existing table approach.

Add `tests/fixtures/rtl.md` and five new tests covering `--rtl`
rendering, code block LTR scoping, and fixture integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@adamatan
Copy link
Copy Markdown
Author

@jfernandez done

@jfernandez
Copy link
Copy Markdown
Owner

Thanks. I'll take another look before the end of the week.

JacobAMason and others added 4 commits April 1, 2026 23:27
…z#76)

The file watcher calls handle_markdown_file_change only after the OS
reports a rename, create, or data modification event. Re-checking mtime
inside refresh_file is redundant and fails when the renamed file has the
same mtime as the original (sub-second filesystem granularity).

Use force_refresh_file in the watcher path to unconditionally re-read
the file. The mtime guard remains in refresh_file for the HTTP handler
path where it prevents unnecessary disk reads on every page load.

Also refactor refresh_file to delegate to force_refresh_file to avoid
duplicating the read/parse logic.
)

HTTP handlers now serve pre-rendered HTML from memory without
re-checking the file on disk. The file watcher is the sole mechanism
for keeping content fresh, which aligns with the pre-rendered in-memory
design.
Add rtl parameter to create_test_server_impl instead of maintaining
a separate helper with duplicated setup logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@adamatan adamatan force-pushed the feature/rtl-support branch from f2ed2e2 to d26a5d7 Compare April 2, 2026 03:29
@adamatan
Copy link
Copy Markdown
Author

adamatan commented Apr 8, 2026

Hi - is there anything I should do to promote this one?
If you're OK with the solution, I'll resolve the conflicts.

@jfernandez
Copy link
Copy Markdown
Owner

jfernandez commented Apr 14, 2026

Hey @adamatan, thanks for iterating on this, the RTL implementation looks solid and matches the feedback from the first review.

A few things to clean up before we merge:

  1. Rebase onto current main. The branch includes commits from fix: bypass mtime check when file watcher triggers refresh #76 and refactor: remove redundant mtime check from HTTP handlers #77 which are already merged, making the diff noisy with unrelated changes. A rebase will give us a clean diff of just the RTL work.

  2. Squash into a single commit. Since the history includes the original auto-detection approach, the simplification, and formatting fixes, squashing into one clean commit (e.g. feat: add RTL support via --rtl flag) will keep the history tidy.

  3. Update the PR description. It still references auto-detection, --no-rtl, RtlMode, 60 tests, and per-file detection — none of which exist after the simplification. A quick update to match what's actually shipping would be helpful.

  4. Drop the unrelated cosmetic changes if possible — the html_bodyhtml rename in markdown_to_html and the variable extraction in add_tracked_file. Minor, but keeps the PR focused.

Once that's done we'll do a final review and merge. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants