Internationalization
npm install @jalali-js/i18nMost apps never import this package directly: useCalendar()/format() are already wired up through locale props in @jalali-js/react and @jalali-js/vue. Reach for it directly when you need to format a date outside a component, or build your own display logic on the same locale data those bindings use.
format()
import { format, en, fa, ps } from '@jalali-js/i18n';
const date = {
precision: 'date' as const,
system: 'jalali' as const,
year: 1403,
month: 5,
day: 15,
};
format(date, en); // '15 Mordad 1403'
format(date, fa); // '۱۵ مرداد ۱۴۰۳'
format(date, ps); // '۱۵ زمری ۱۴۰۳' (the Afghan month names; see below)
format(date, en, { style: 'short' }); // '15 Mor 1403'
format(date, en, { weekday: true }); // 'Monday, 15 Mordad 1403'
format(date, fa, { numerals: 'latin' }); // '15 مرداد 1403' (Latin digits, Persian text)format() is display-only: it reads date.system's own month names (so a Gregorian-system date formats with Gregorian month names, a Jalali-system date with Jalali ones, in either locale), and never affects what toStorageValue() returns. See Display value vs. storage value.
Templates
When you need an exact shape, pass a template. It replaces the preset layout, so style and weekday are ignored. The numerals option still applies.
format(date, en, { template: 'YYYY/MM/DD' }); // '1403/05/15'
format(date, fa, { template: 'YYYY/MM/DD' }); // '۱۴۰۳/۰۵/۱۵'
format(date, en, { template: 'D MMMM YYYY' }); // '15 Mordad 1403'
format(date, en, { template: 'dddd D MMM YYYY' }); // 'Monday 15 Mor 1403'| Token | Meaning | Example |
|---|---|---|
YYYY | Year, 4 digits | 1403 |
MM | Month, 2 digits | 05 |
M | Month | 5 |
DD | Day, 2 digits | 15 |
D | Day | 15 |
MMMM | Month name, long | Mordad |
MMM | Month name, short | Mor |
dddd | Weekday name, long | Monday |
ddd | Weekday name, short | Mon |
Text between tokens passes through as-is. Keep it to punctuation and spaces: a letter that matches a token (for example the D in Day:) is read as that token.
parseTemplate()
The reverse of a template format: strict parsing of a known shape. Free-form input belongs to @jalali-js/nlp instead.
import { parseTemplate, en, fa } from '@jalali-js/i18n';
parseTemplate('1403/05/15', 'YYYY/MM/DD', en);
// { precision: 'date', system: 'jalali', year: 1403, month: 5, day: 15 }
parseTemplate('۱۴۰۳/۰۵/۱۵', 'YYYY/MM/DD', fa); // same date; native digits accepted
parseTemplate('15 Mordad 1403', 'D MMMM YYYY', en); // same date
parseTemplate('2024/08/05', 'YYYY/MM/DD', en, { system: 'gregorian' });It returns null instead of guessing:
- The input must match the template's exact shape, with nothing left over.
- Digits can be Latin or the locale's native set.
- The date must exist:
1402/12/30(Esfand 30 in a non-leap year) isnull. - A weekday name must match the parsed date:
'Tuesday 15 Mordad 1403'isnull, because that day is a Monday. - The template must produce a year, a month, and a day.
formatNumber()
The digit-formatting format() uses internally, available on its own:
import { formatNumber, fa } from '@jalali-js/i18n';
formatNumber(1403, 'native', fa.digits); // '۱۴۰۳'
formatNumber(1403, 'latin', fa.digits); // '1403'
formatNumber(5, 'latin', fa.digits, 2); // '05' (the optional minimum width zero-pads)Locale packs
en, fa, and ps are each a LocalePack: month names for both calendar systems (English transliterations of the Jalali months in en, Persian transliterations of the Gregorian months in fa), weekday names, defaultNumerals, digits, and text direction. Persian has no widely standardized abbreviated month form the way English does, so fa's short month names reuse long; weekday names do have a well-known one-letter short form, so those differ. Full shape: LocalePack.
ps is Pashto, one of Afghanistan's two official languages. Afghanistan uses the same Jalali solar calendar as Iran, and names its months after the zodiac signs. So ps's Jalali month names (وری, غویی, ..., کب) are alternative names for the same months, and share nothing with fa's Persian ones. The data comes from CLDR's own ps locale, read through ICU, not from memory. CLDR has no abbreviated Pashto forms, so short reuses long throughout.
Adding a locale
A LocalePack is plain data against one exported interface. Adding a locale changes no other code in this package: format() reads whatever pack you pass it. The steps, using ps as the worked example:
- Write the pack. Copy
fa.ts(RTL) oren.ts(LTR) inpackages/i18n/src/and fill in the fields:code,direction, the 10digitscharacters,defaultNumerals,weekdaySeparator, month names for both calendar systems, weekday names (index 0 is Sunday), and the two picker placeholders. Source the names from real data you can verify. CLDR through Node'sIntlworks well:new Intl.DateTimeFormat('ps-AF-u-ca-persian', { month: 'long' })gives the Jalali month names, andIntl.NumberFormatgives the digits. - Register it. Export the pack from
packages/i18n/src/index.ts, and add its code toLocaleCodeand the pack table inpackages/i18n/src/locale-packs.ts. That one table is what the React, Vue, and Web Components bindings re-export, so thelocaleprop accepts the new code with no binding change. - Add tests mirroring the existing
facoverage informat.test.tsandnumerals.test.ts: one formatted string per style, the weekday prefix, and an explicitnumerals: 'latin'override. - Optional: NLP phrases. A word list in
packages/nlp/src/word-list.tsadds natural-language parsing for the locale. This is separate from the display pack and ships only when the phrase set is well understood.Intl.RelativeTimeFormatis a verifiable source for the relative terms.