PrimLagicPrimLagic

Building Persian websites: RTL, ZWNJ, digits and the Jalali calendar

By Ali Abdollahi · · 3 min read

Building a Persian site is not just "translating the text". A few technical details, if ignored, break the looks, search and even data storage. These are issues I hit in my own projects (like ClassBan and this site).

1. Direction and language of the page

Put both language and direction on the html tag:

<html lang="fa" dir="rtl">

lang matters to search engines, browsers and assistive tools; dir flips the layout. For English fragments inside the page (an email address, a code line) use dir="ltr" on that element so punctuation does not jump around.

2. Logical CSS instead of left and right

Use margin-inline-start instead of margin-left and text-align: start instead of text-align: left, so both languages work with one stylesheet. On bilingual sites this removes the need to duplicate CSS per direction.

3. ZWNJ (the half-space)

The half-space is the Unicode character U+200C; it is what makes «می‌شود» and «کتاب‌ها» join correctly. Type it directly or write &zwnj; in HTML. Search note: some users search with it and some without — normalize both forms in your site's internal search.

4. Arabic yeh/kaf vs Persian yeh/kaf

The Arabic shapes ي and ك have different code points from Persian ی and ک. If a user types the Arabic shape, search and name matching break. Normalize on save and on search:

const norm = (s) => s.replace(/ي/g, 'ی').replace(/ك/g, 'ک');

5. Persian and English digits

Users may type «۰۹۱۲…» or "0912…". If you store a phone number or national ID, convert digits to one form (usually English) before validation, then display whichever shape your audience prefers:

const toEn = (s) => s.replace(/[۰-۹]/g, (d) => '۰۱۲۳۴۵۶۷۸۹'.indexOf(d));

For mobile numbers, stripping non-digits and re-adding a leading zero on 10-digit input prevents SMS delivery errors (the school SMS example).

6. Jalali dates

Store dates in the database in a standard form (ISO / Gregorian) and convert to Jalali only for display. Use a proven library like jalaali-js — a one-day error around leap years is deeply annoying. Jalali output can differ across environments (e.g. Intl); always test against a few known dates.

7. Fonts

Use a font with full Persian support (like Vazirmatn). For speed: only the weights you need, woff2 format and font-display: swap. Keep fallbacks in font-family (Tahoma, sans-serif) so text stays readable before the font loads.

8. Numbers and symbols in text

Inside Persian text, decimal points, percent signs and parentheses can jump around because of bidirectional text. If a number or English fragment lands in the wrong place, wrap it in <bdi> or <span dir="ltr">.

Checklist

lang="fa" dir="rtl"; logical CSS; ZWNJ; normalize yeh/kaf; convert digits; store standard dates; a light font; and test on mobile.

RTLPersianWeb