arabicfmt
Version:
Arabic-first formatting for numbers, currency, dates and bidirectional text across all 22 Arab League countries — with correct handling of the 2025–2026 Unicode currency-symbol transition (Saudi Riyal U+20C1, UAE Dirham U+20C3, Omani Rial U+20C4).
260 lines (248 loc) • 12.2 kB
text/typescript
import { N as NumeralSystem } from '../types-CK7PVYeU.cjs';
export { C as CountedNoun, c as countedNoun } from '../count-C4U_RQ2h.cjs';
interface FormatNumberOptions {
/** BCP-47 locale. Default `"ar"`. */
locale?: string;
/** Digit shaping. Default `"latn"` — Eastern Arabic digits are opt-in. */
numerals?: NumeralSystem;
/** Fixed number of fraction digits (sets both min and max). */
fractionDigits?: number;
minimumFractionDigits?: number;
maximumFractionDigits?: number;
/** Thousands grouping. Default `true`. */
grouping?: boolean;
/** Number style. Default `"decimal"`. */
style?: "decimal" | "percent";
signDisplay?: "auto" | "always" | "exceptZero" | "never";
/** Compact / scientific notation. Default `"standard"`. */
notation?: "standard" | "compact" | "scientific" | "engineering";
/** Compact display style when `notation` is `"compact"`. Default `"short"`. */
compactDisplay?: "short" | "long";
}
/**
* Locale-aware number formatting on top of `Intl.NumberFormat`. Eastern Arabic
* numerals are never forced — pass `numerals: "arab"` to opt in.
*
* @example formatNumber(1234567.89) // "1,234,567.89"
* @example formatNumber(1234.5, { numerals: "arab" }) // "١٬٢٣٤٫٥"
* @example formatNumber(0.42, { style: "percent" }) // "42%"
*/
declare function formatNumber(value: number, options?: FormatNumberOptions): string;
/** Format a ratio (0.42 → "42%") with the same options as {@link formatNumber}. */
declare function formatPercent(value: number, options?: Omit<FormatNumberOptions, "style">): string;
/**
* Format a number in compact notation ("short" by default): 1,200,000 → "1.2M"
* or in Arabic "١٫٢ مليون" (when `numerals: "arab"` and an Arabic locale).
*
* @example formatCompact(1_200_000) // "1.2M"
* @example formatCompact(1_200_000, { locale: "ar" }) // "1.2 مليون"
* @example formatCompact(1_200_000, { numerals: "arab" }) // "١٫٢ مليون"
*/
declare function formatCompact(value: number, options?: Omit<FormatNumberOptions, "notation" | "compactDisplay"> & {
compactDisplay?: "short" | "long";
}): string;
/** Arabic-Indic digits ٠١٢٣٤٥٦٧٨٩ (U+0660–U+0669), used across the Arab world. */
declare const ARABIC_INDIC_DIGITS: readonly string[];
/** Extended Arabic-Indic digits ۰۱۲۳۴۵۶۷۸۹ (U+06F0–U+06F9), used in Persian/Urdu. */
declare const EXTENDED_ARABIC_INDIC_DIGITS: readonly string[];
/** Convert Western digits (0-9) in `input` to Eastern Arabic-Indic digits. */
declare function toArabicDigits(input: string): string;
/**
* Convert Eastern Arabic-Indic and Extended (Persian) digits in `input` back to
* Western digits (0-9). Useful before parsing user-entered numbers.
*/
declare function toLatinDigits(input: string): string;
/**
* Parse a localized number string — including Eastern Arabic digits, Arabic
* thousands separators (٬) and Arabic decimal separator (٫) — back to a JS
* `number`. Returns `NaN` when the input cannot be parsed.
*
* @example parseNumber("١٬٢٣٤٫٥٦") // 1234.56
* @example parseNumber("1,234.56") // 1234.56
* @example parseNumber("٢٠٢٦") // 2026
*/
declare function parseNumber(input: string): number;
/**
* Parse a formatted currency string back to its numeric value. Strips known
* Arab currency symbols, ISO codes, bidi controls and whitespace before
* delegating to {@link parseNumber}.
*
* @example parseCurrency("1,234.50 ر.س") // 1234.5
* @example parseCurrency("١٬٢٣٤٫٥٠٠ د.ك") // 1234.5
* @example parseCurrency("(500.00 SAR)") // -500 (accounting notation)
*/
declare function parseCurrency(input: string): number;
/**
* Convert an integer to its Arabic word representation.
*
* Arabic number words follow strict grammar rules:
* - Ones 3–9 use gender polarity (opposite gender to the counted noun).
* - Teens 11–19 are compound; 10/11/12 have special forms.
* - Scale words (thousands, millions…) use singular (1), dual (2),
* plural (3–10), or tanwin-singular (11–99) forms.
*/
type Gender$1 = "male" | "female";
interface ArabicWordsOptions {
/**
* Grammatical gender of the counted noun. Affects ones (1–2) and the
* final group (3–9). Default `"male"`.
*
* Use `"male"` for masculine nouns (ريال، دينار…) and most standalone
* numbers. Use `"female"` for feminine nouns (ليرة، روبية…).
*/
gender?: Gender$1;
/** Prepend "سالب" for negative values. Default `true`. */
negative?: boolean;
/**
* How to read a fractional part, after the decimal word "فاصلة".
* - `"off"` — truncate to the integer (default; backward compatible).
* - `"digits"` — read each fractional digit (3.14 → "ثلاثة فاصلة واحد أربعة").
* - `"number"` — read the fraction as a whole number (3.14 → "ثلاثة فاصلة أربعة عشر").
*/
fraction?: "off" | "digits" | "number";
}
/**
* Convert a number to its Arabic word representation.
*
* Integers from 0 to ±999 trillion are spelled in full. By default a fractional
* part is truncated; pass {@link ArabicWordsOptions.fraction} to read it too.
*
* @example arabicToWords(0) // "صفر"
* @example arabicToWords(11) // "أحد عشر"
* @example arabicToWords(25) // "خمسة وعشرون"
* @example arabicToWords(5000) // "خمسة آلاف"
* @example arabicToWords(1_234_567)
* // "مليون ومئتان وأربعة وثلاثون ألفاً وخمسمئة وسبعة وستون"
* @example arabicToWords(-42) // "سالب اثنان وأربعون"
* @example arabicToWords(5, { gender: "female" }) // "خمس"
* @example arabicToWords(3.14, { fraction: "digits" }) // "ثلاثة فاصلة واحد أربعة"
* @example arabicToWords(3.5, { fraction: "number" }) // "ثلاثة فاصلة خمسة"
*/
declare function arabicToWords(n: number, options?: ArabicWordsOptions): string;
/**
* Common Arabic fraction words (الكسور): نصف، ثلث، ربع …
*
* Denominators 2–10 have dedicated nouns. The numerator follows the usual
* counted-noun pattern: 1 → singular (ربع), 2 → dual (ربعان), 3–10 → number +
* plural (ثلاثة أرباع).
*/
/**
* Spell a simple fraction in Arabic words.
*
* Supports denominators 2–10 (the fractions with dedicated nouns). Returns an
* empty string for unsupported denominators or non-positive numerators.
*
* @example arabicFraction(1, 2) // "نصف"
* @example arabicFraction(1, 4) // "ربع"
* @example arabicFraction(3, 4) // "ثلاثة أرباع"
* @example arabicFraction(2, 3) // "ثلثان"
* @example arabicFraction(5, 8) // "خمسة أثمان"
*/
declare function arabicFraction(numerator: number, denominator: number): string;
/**
* Arabic ordinal numbers (الأعداد الترتيبية).
*
* Ordinals 1–19 have dedicated forms; 20–99 combine a unit ordinal with a tens
* word ("الخامس والعشرون"). Each form agrees in gender with the noun it
* describes. Values ≥ 100 fall back to a definite cardinal ("ال" + words),
* which is the form used in everyday writing (الصفحة المئة وخمسة وعشرون).
*/
type Gender = "male" | "female";
interface ArabicOrdinalOptions {
/** Gender of the described noun. Default `"male"`. */
gender?: Gender;
/** Include the definite article ال. Default `true`. */
definite?: boolean;
}
/**
* Return the Arabic ordinal word for a positive integer.
*
* @example arabicOrdinal(1) // "الأول"
* @example arabicOrdinal(2) // "الثاني"
* @example arabicOrdinal(11) // "الحادي عشر"
* @example arabicOrdinal(25) // "الخامس والعشرون"
* @example arabicOrdinal(1, { gender: "female" }) // "الأولى"
* @example arabicOrdinal(3, { definite: false }) // "ثالث"
* @example arabicOrdinal(100) // "المئة"
*/
declare function arabicOrdinal(n: number, options?: ArabicOrdinalOptions): string;
/**
* Spell a time span in grammatical Arabic ("ساعتان وخمس دقائق").
*
* Unlike `Intl.DurationFormat` (still poorly supported), this produces the fully
* inflected spoken form: it picks the largest non-zero units, applies dual /
* plural / accusative agreement to each unit noun, and joins them with و.
*/
type DurationUnit = "day" | "hour" | "minute" | "second";
interface FormatDurationOptions {
/** Interpret the input as `"ms"` (default) or `"s"`. */
input?: "ms" | "s";
/** Maximum number of units to include, largest first. Default `2`. */
largest?: number;
/** Restrict the units considered. Default day/hour/minute/second. */
units?: DurationUnit[];
}
/**
* Format a duration as spelled Arabic.
*
* @example formatDuration(7_500_000) // "ساعتان وخمس دقائق"
* @example formatDuration(90, { input: "s" }) // "دقيقة واحدة وثلاثون ثانيةً"
* @example formatDuration(3_600_000, { largest: 1 })// "ساعة واحدة"
* @example formatDuration(500) // "أقل من ثانية"
*/
declare function formatDuration(value: number, options?: FormatDurationOptions): string;
/**
* Format a byte count as a human-readable data size with Arabic (or Latin)
* unit names: 1536 → "1.5 كيلوبايت".
*/
interface FormatFileSizeOptions {
/** BCP-47 locale for the number. Default `"ar"`. */
locale?: string;
/** Digit shaping. Default `"latn"`. */
numerals?: NumeralSystem;
/** Divisor: `1024` (binary, default) or `1000` (decimal/SI). */
base?: 1024 | 1000;
/** Maximum fraction digits for the scaled value. Default `1`. */
precision?: number;
/** Unit vocabulary. `"arabic"` (default) or `"latin"` (B/KB/MB…). */
unitStyle?: "arabic" | "latin";
}
/**
* Format a byte count with Arabic data-size units.
*
* @example formatFileSize(0) // "0 بايت"
* @example formatFileSize(1536) // "1.5 كيلوبايت"
* @example formatFileSize(5_242_880) // "5 ميجابايت"
* @example formatFileSize(1_500_000, { base: 1000 }) // "1.5 ميجابايت"
* @example formatFileSize(2048, { numerals: "arab" }) // "٢ كيلوبايت"
* @example formatFileSize(2048, { unitStyle: "latin" })// "2 KB"
*/
declare function formatFileSize(bytes: number, options?: FormatFileSizeOptions): string;
interface FormatRelativeTimeOptions {
/** BCP-47 locale. Default `"ar"`. */
locale?: string;
/** Digit shaping. Default `"latn"`. */
numerals?: NumeralSystem;
/**
* - `"auto"` — uses "yesterday" / "tomorrow" style when available.
* - `"always"` — always numeric: "1 يوم مضى".
* Default `"auto"`.
*/
numeric?: "auto" | "always";
/** Width of the unit name. Default `"long"`. */
style?: "long" | "short" | "narrow";
}
/**
* Format the time difference between `date` and `base` as a human-readable
* relative string ("منذ ٣ أيام", "بعد ساعة", "3 days ago").
*
* Picks the most appropriate unit automatically. Works on all engines via
* `Intl.RelativeTimeFormat`.
*
* @example formatRelativeTime(new Date(Date.now() - 3 * 86400_000))
* // "منذ ٣ أيام" (locale "ar", numerals "latn" → "منذ 3 أيام")
* @example formatRelativeTime(new Date(Date.now() + 3600_000), new Date(), { locale: "en" })
* // "in 1 hour"
*/
declare function formatRelativeTime(date: Date, base?: Date, options?: FormatRelativeTimeOptions): string;
export { ARABIC_INDIC_DIGITS, type ArabicOrdinalOptions, type ArabicWordsOptions, type DurationUnit, EXTENDED_ARABIC_INDIC_DIGITS, type FormatDurationOptions, type FormatFileSizeOptions, type FormatNumberOptions, type FormatRelativeTimeOptions, arabicFraction, arabicOrdinal, arabicToWords, formatCompact, formatDuration, formatFileSize, formatNumber, formatPercent, formatRelativeTime, parseCurrency, parseNumber, toArabicDigits, toLatinDigits };