قالب‌بندی عدد و تاریخ

تقویم شمسی، جداکنندهٔ عدد و مقدار مختصر — کدام تابع برای کدام کار، و کدام تله‌ها واقعی‌اند

اصل

این فارسی‌ترین کاری است که سیستم طراحی انجام می‌دهد و تا امروز صفحه نداشت. دو قاعده کل ماجراست:

  1. رقم را تبدیل نکنید. در متن صفحه همیشه رقم لاتین (0–9) است و ارقام فارسی از ویژگی ss01 فونت می‌آیند، فقط برای محتوای فارسی (lang="fa"؛ از 4.0). هیچ تابع سیستم طراحی رقم فارسی یا عربی تولید نمی‌کند، پس کسی که عددی را از صفحه کپی می‌کند رقم لاتین می‌گیرد و Excel و ماشین‌حساب و جستجو آن را عدد می‌شناسند. صفحهٔ عربی و انگلیسی رقم لاتین دارند، و شناسه‌ها (نام کاربری، IP، URL، کد) داخل متن فارسی با LatinDigits رقم لاتین می‌مانند. جزئیات و اثباتش در فارسی‌محور بودن.
  2. قالب را تبدیل کنید. جداکنندهٔ هزار، مختصرسازی و تاریخ شمسی کارِ کد است، چون آن‌ها شکل عدد نیستند، معنای آن‌اند.

نمونه بصری

فراخوانیرشتهٔ خروجیآنچه کاربر می‌بیند
formatNumber(1234567)1,234,5671,234,567
formatNumber(1234567, 'short')1.2M1.2M
formatLargeNumber(1234567, 'fa')1.2 میلیون1.2 میلیون
formatLargeNumber(1234567, 'en')1.2M1.2M
formatJalaliDate(date, 'yyyy/MM/dd')1405/05/051405/05/05
getPersianMonthName(date)مردادمرداد
getPersianWeekdayName(date)دوشنبهدوشنبه
formatPersianDateRange(from, to)5 - 9 مرداد 14055 - 9 مرداد 1405

ستون دوم و سوم یک رشته‌اند. تفاوتشان کار فونت است: ویژگی ss01 گلیف ارقام لاتین را به فارسی نگاشت می‌کند، بدون آنکه codepoint عوض شود. به همین دلیل کپی‌کردن از ستون سوم، عددِ قابل‌استفاده می‌دهد. دقت کنید که کاما در هیچ ستونی فارسی نمی‌شود — ss01 فقط ارقام را می‌گیرد.


قوانین اجباری

برای عدد، یکی از این دو تابع

import { formatNumber, formatLargeNumber, formatPercentage } from '@partodata/ui'

formatNumber(1234567) // «1,234,567» — رقم لاتین؛ فونت آن را با رقم فارسی نشان می‌دهد
formatLargeNumber(1234567, 'fa') // «1.2 میلیون» — پسوند فارسی
formatLargeNumber(1234567, 'en') // «1.2M» — برای رابط لاتین
formatPercentage(12.5, 'fa') // «12.5٪» — نشانهٔ فارسی
formatPercentage(12.5, 'en') // «12.5%» — نشانهٔ لاتین
12 کمپین، 1.2 میلیون منشن
درست — formatLargeNumber(n, 'fa'): پسوند فارسی
12 کمپین، 1.2M منشن
نادرست — حالت 'short' در متن فارسی پسوند لاتین می‌آورد

حالت `'short'` را در رابط فارسی به کار نبرید

خروجی‌اش پسوند لاتین دارد، و چون ss01 زیر lang="fa" روشن است کاربر رقم فارسی را با پسوند لاتین کنار هم می‌بیند. برای مقدار مختصر در فارسی همیشه formatLargeNumber با locale فارسی. چرایی‌اش با اثبات، در فارسی‌محور بودن.

جداکننده‌ها را فونت عوض نمی‌کند

ss01 فقط رقم‌ها را جانشین می‌کند: رقم لاتین و رقم عربی-هندی، و دیگر هیچ. این با پارس مستقیم جدول GSUB سنجیده شده. کاما، نقطه، % و ٪ دست‌نخورده می‌مانند. پس formatNumber(1234567) با رقم فارسی و کامای لاتین دیده می‌شود، نه با جداکنندهٔ فارسی «٬». اگر جداکنندهٔ فارسی می‌خواهید باید صریحاً بنویسیدش؛ خودبه‌خود نمی‌آید.

1,234,567
درست — formatNumber؛ فونت رقم‌ها را فارسی نشان می‌دهد
1234567
نادرست — رشتهٔ خام؛ جداکنندهٔ هزارگان ندارد

برای تاریخ، از توابع شمسی — نه محاسبهٔ دستی

import { formatJalaliDate, getPersianMonthName, formatPersianDateRange, jalaliToGregorian } from '@partodata/ui'

formatJalaliDate(new Date(), 'yyyy/MM/dd')
getPersianMonthName(new Date()) // «مرداد»
formatPersianDateRange(from, to) // «3 - 7 مرداد 1405»
jalaliToGregorian(1405, 5, 3) // Date
انتشار: 8 مهر 1405
درست — formatJalaliDate؛ تاریخ شمسی
انتشار: 9/30/2026
نادرست — تاریخ میلادی ماه‌اول؛ برای کاربر فارسی مبهم

پشت همه‌ی این‌ها date-fns-jalali است، و این انتخاب قفل شده: moment-jalaali از 2019 بی‌نگهدارنده است و یک قانون ESLint هر import از moment* را می‌بندد. جایگزینش نکنید.


ساعت همیشه 24ساعته است

سیستم طراحی هیچ‌جا ق.ظ/ب.ظ یا AM/PM نشان نمی‌دهد — در fa، ar و en: «13:05» و «00:30»، نه «1:05 ب.ظ». قالب‌های date-fns با HH:mm نوشته می‌شوند و formatJalaliDate هر نشانهٔ 12ساعته (hh، a، p) را با force24Hour به 24ساعته برمی‌گرداند؛ Intl.DateTimeFormat با hourCycle: 'h23' صدا زده می‌شود. فیلد ساعت DateTimePicker هم یک فیلد HH:mm است، نه <input type="time"> بومی که ساعت و ارقامش را از زبان مرورگر می‌گیرد. ارقام پاپ‌اورهای تقویم و ساعت از lang خود پاپ‌اور می‌آیند (فارسی در fa، لاتین در en).

ساعت 13:05
درست — HH:mm؛ 24ساعته
ساعت 1:05 PM
نادرست — AM/PM؛ سیستم طراحی هرگز 12ساعته نشان نمی‌دهد

موارد استفاده رایج

زمان نسبی

import { formatAbsoluteLocaleTime, formatRelativeLocaleTime } from '@partodata/ui'

formatRelativeLocaleTime(post.createdAt, 'fa') // «3 ساعت پیش»
formatAbsoluteLocaleTime(post.createdAt, 'fa') // «27 مرداد 1405، 12:30»
formatAbsoluteLocaleTime(post.createdAt, 'en') // «August 18, 2026 at 12:30»

درصد و واحد

در رابط فارسی و عربی درصد را با نشانهٔ ٪ بنویسید، نه %. هیچ‌کدام از این دو را فونت عوض نمی‌کند؛ برای جلوگیری از ترکیب دستی و ناهماهنگی، مقدار درصد را به formatPercentage(value, locale) بسپارید. این تابع برای fa و ar نشانهٔ ٪ و برای en نشانهٔ % برمی‌گرداند.


چه نکنیم

  • تبدیل رقم — نه با toLocaleString('fa-IR') یا Intl.NumberFormat('fa-IR') (اگر Intl لازم است، numberingSystem: 'latn' بدهید)، نه با نگاشت دستی رقم، نه با رقم فارسیِ تایپ‌شده در متن. هیچ سودی ندارد و Ctrl+F، کپی به Excel، صفحه‌خوان و مرتب‌سازی را می‌شکند. convertToLocalNumbers و toPersianDigits منسوخ‌اند و حالا رقم لاتین برمی‌گردانند؛ صدایشان نزنید. آزمون check-digit-doctrine تبدیل رقم و رقم فارسیِ تایپ‌شده را در کد سیستم طراحی رد می‌کند.
  • تکیه بر PERSIAN_WEEKDAYS با فرض ترتیب هفتهٔ فارسی. این آرایه به ترتیب getDay() جاوااسکریپت است و با یکشنبه شروع می‌شود، نه شنبه. اگر با ایندکسِ روزِ فارسی سراغش بروید، روز اشتباه می‌گیرید. برای نام روز، getPersianWeekdayName(date) را صدا بزنید که خودش از locale faIR می‌خواند و به این آرایه کاری ندارد.
  • انتظار ارقام هم‌عرض. فونت یکان بخ ویژگی tnum ندارد و عرض ارقام فارسی‌اش از 260 تا 700 یونیت در یک em هزار‌یونیتی نوسان دارد. tabular-nums امروز بی‌اثر است؛ اگر پایداری چیدمان لازم دارید، عرض ظرف را تثبیت کنید. (گلیف‌های هم‌عرض در خود فونت هست ولی قابلیتی به آن‌ها نمی‌رسد؛ جزئیات در امکانات فونت.)

صفحات مرتبط