تایپوگرافی فارسی و RTL در رابط کاربری: فونت، اعداد، نیمفاصله و Tailwind
نکات عملی برای رابط کاربری فارسی خوانا: میزبانی فونت Vazirmatn، letter-spacing و ارتفاع خط، اعداد فارسی و یکسانسازی ورودیها، ی و ک عربی در جستجو، ترکیب فارسی و انگلیسی، CSS منطقی و تاریخ شمسی بدون خطای hydration.
بیشتر قالبها و کامپوننتهای آماده برای انگلیسی طراحی شدهاند. وقتی فارسی میشوند، متن فشرده و ناخوانا میشود، اعداد نامرتباند، آیکونها برعکساند و جستجو «کیف» را پیدا نمیکند چون کاربر «كيف» عربی تایپ کرده. اینها جزئیات کوچکی هستند که کاربر اسمشان را نمیداند، اما حس «این سایت درست کار نمیکند» را میسازند. در این مقاله قاعدههایی را آوردهام که در همه پروژههای فارسیام، از همین سایت تا فروشگاه و سامانه حسابداری، رعایت میکنم.
فونت را خودتان میزبانی کنید
وابستگی به CDN خارجی برای فونت یعنی هر وقت اینترنت بینالملل کند یا مختل شد، سایت با فونت پیشفرض سیستم و ظاهری بههمریخته باز میشود؛ و در ایران این اتفاق کم نیست. فونت را کنار بقیه فایلهای سایت میزبانی کنید.
نسخه متغیر (Variable) فونت Vazirmatn همه وزنها را در یک فایل woff2 دارد. یعنی بهجای چهار یا پنج فایل، یک فایل دانلود میشود. با font-display: swap متن هیچوقت نامرئی نمیماند و با preload همان فایل در head، فونت زودتر از بقیه منابع دریافت میشود و جابهجایی متن کمتر دیده میشود.
@font-face {
font-family: 'Vazirmatn';
src: url('/fonts/Vazirmatn[wght].woff2') format('woff2-variations');
font-weight: 100 900;
font-display: swap;
}
body {
font-family: 'Vazirmatn', system-ui, sans-serif;
line-height: 1.9; /* متن فارسی به ارتفاع خط بیشتری نیاز دارد */
letter-spacing: normal; /* هرگز منفی */
}letter-spacing منفی، دشمن فارسی
کلاسهایی مثل tracking-tight که در طراحی انگلیسی برای تیترها رایجاند، حروف متصل فارسی را به هم فشرده میکنند یا اتصالها را میشکنند. فاصله حروف را برای متن فارسی عادی نگه دارید و سلسلهمراتب را با وزن و اندازه فونت بسازید.
ارتفاع خط هم مهم است. حروف فارسی دندانهها و نقطههای بالا و پایین زیادی دارند و با ارتفاع خط ۱٫۵ که برای انگلیسی خوب است، خطوط به هم نزدیک و خستهکننده میشوند. برای متن بدنه ۱٫۸ تا ۲ و برای تیترها حدود ۱٫۳ تا ۱٫۴ مناسب است.
نکات کلیدی
- letter-spacing برای فارسی همیشه normal.
- ارتفاع خط متن بدنه بین ۱٫۸ تا ۲.
- اندازه متن بدنه در موبایل حداقل ۱۵ تا ۱۶ پیکسل؛ فارسی در اندازه کوچک زودتر ناخوانا میشود.
اعداد: نمایش فارسی، ورودی یکسان
دو مسئله جدا داریم: نمایش و ورودی. برای نمایش، تاریخ، قیمت و شمارندهها را با ارقام فارسی نشان دهید. در جدولها و فاکتورها از font-variant-numeric: tabular-nums استفاده کنید تا ارقام همعرض شوند و ستونها تراز بمانند.
ورودی مهمتر است. کاربر ایرانی ممکن است شماره موبایل یا مبلغ را با ارقام فارسی، عربی یا انگلیسی وارد کند، و کیبوردهای مختلف ارقام متفاوت تولید میکنند. اگر ورودی را یکسان نکنید، اعتبارسنجی شکست میخورد یا بدتر، داده با ارقام مختلف در دیتابیس ذخیره میشود و جستجو کار نمیکند. همه ورودیهای عددی را پیش از اعتبارسنجی به ارقام انگلیسی تبدیل کنید.
const FA = '۰۱۲۳۴۵۶۷۸۹';
const AR = '٠١٢٣٤٥٦٧٨٩';
/** ارقام فارسی و عربی → انگلیسی؛ برای هر ورودی عددی قبل از اعتبارسنجی. */
export function normalizeDigits(s: string) {
return s.replace(/[۰-۹٠-٩]/g, (d) => String(FA.indexOf(d) >= 0 ? FA.indexOf(d) : AR.indexOf(d)));
}
/** ی و ک عربی → فارسی؛ برای متن ذخیرهشده و عبارت جستجو. */
export function normalizeFa(s: string) {
return s.replace(/ي/g, 'ی').replace(/ى/g, 'ی').replace(/ك/g, 'ک').replace(/+/g, '').trim();
}
/** نمایش: 1250000 → ۱٬۲۵۰٬۰۰۰ */
export const faNumber = (n: number | bigint) =>
n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '٬').replace(/\d/g, (d) => FA[+d]);ی و ک عربی و نیمفاصله در جستجو
یکی از رایجترین دلایل «جستجو چیزی پیدا نکرد» در سایتهای فارسی، تفاوت ی و ک عربی و فارسی است. متنی که از Word یا سیستم قدیمی کپی شده، یا کاربری که کیبورد عربی دارد، حروفی تولید میکند که به چشم یکساناند ولی برای کامپیوتر متفاوت. هم متن ذخیرهشده و هم عبارت جستجو را با یک تابع یکسانسازی کنید (مثل normalizeFa در بالا).
نیمفاصله (ZWNJ) مسئله مشابهی است: «میکنم» با نیمفاصله، با فاصله و بدون فاصله سه رشته متفاوتاند. در جستجو، بهتر است نیمفاصله و فاصله را یکسان در نظر بگیرید. و در متنهایی که خودتان تولید میکنید، نیمفاصله را درست به کار ببرید؛ هم خوانایی بهتر است و هم ظاهر حرفهایتر.
ترکیب فارسی و انگلیسی
کد، ایمیل، دامنه، شماره نسخه و شماره تلفن باید در جهت چپبهراست نمایش داده شوند، وگرنه نقطه، خط تیره و پرانتز جابهجا میشوند و «smbz.ir» به «ir.smbz» شبیه میشود. کافی است این بخشها را در یک المان با dir="ltr" (یا تگ bdi) بگذارید.
آیکونهای جهتدار هم باید قرینه شوند: در رابط راستبهچپ، «ادامه» به سمت چپ اشاره میکند و «بازگشت» به راست. آیکونهایی که جهت ندارند (جستجو، سبد خرید، ساعت) نباید قرینه شوند.
<p>برای پشتیبانی به <bdi dir="ltr">support@example.com</bdi> ایمیل بزنید.</p>
<p>شماره تماس: <span dir="ltr">0912 000 0000</span></p>
<code dir="ltr">npm run build</code>CSS منطقی بهجای چپ و راست
اگر در CSS از margin-left و padding-right استفاده کنید، با هر تغییر جهت باید همه را برعکس کنید. ویژگیهای منطقی (logical properties) مثل margin-inline-start و padding-inline-end بر اساس جهت متن کار میکنند: «ابتدا» در فارسی راست است و در انگلیسی چپ.
در Tailwind، کلاسهای ms-* و me-* (بهجای ml-* و mr-*)، ps-* و pe-*، و start-* و end-* همین کار را میکنند. برای مواردی که واقعاً باید در RTL رفتار متفاوت داشته باشند، پیشوند rtl: در دسترس است. با این روش، اگر روزی نسخه انگلیسی سایت لازم شد، بیشتر رابط بدون تغییر کار میکند.
تاریخ شمسی بدون خطای hydration
در سایتهایی که سمت سرور رندر میشوند (مثل Next.js یا همین سایت)، تبدیل تاریخ با Intl و تقویم persian ممکن است در سرور و مرورگر نتیجه کمی متفاوت بدهد؛ نسخه ICU سرور و مرورگر یکی نیست و نتیجه، خطای hydration و پرش متن است. در این سایت برای تاریخ شمسی و ارقام فارسی از یک تابع خالص و قطعی استفاده کردهام که در هر دو طرف دقیقاً یک خروجی دارد. اگر از Intl استفاده میکنید، دستکم تاریخ را فقط در یک طرف (سرور) قالببندی کنید و رشته نهایی را به کلاینت بدهید.
جمعبندی
رابط فارسی خوب از ترجمه رابط انگلیسی به دست نمیآید؛ باید از ابتدا با قواعد خط فارسی ساخته شود. فونت محلی و متغیر، فاصله حروف عادی و ارتفاع خط بیشتر، یکسانسازی ارقام و ی و ک در ورودی و جستجو، جداسازی جهت متنهای انگلیسی و CSS منطقی، بیشترین اثر را با کمترین هزینه دارند. اگر فروشگاه یا وباپلیکیشنی میخواهید که از روز اول درست فارسی ساخته شود، صفحه طراحی فروشگاه اینترنتی و وباپلیکیشن را ببینید.