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).
250 lines (241 loc) • 11.4 kB
TypeScript
import { N as NumeralSystem } from '../types-CK7PVYeU.js';
import { C as CountedNoun } from '../count-C4U_RQ2h.js';
/**
* How to render the currency symbol:
* - `auto` — the safe default: a text symbol that renders in every font today.
* - `new` — the dedicated Unicode sign (e.g. U+20C1 for SAR). Needs font support.
* - `text` — always the Arabic text symbol (e.g. "ر.س").
* - `code` — the ISO 4217 code (e.g. "SAR").
*/
type SymbolMode = "auto" | "new" | "text" | "code";
/** Where the symbol sits relative to the amount (before bidi resolution). */
type SymbolPosition = "before" | "after";
interface ResolveSymbolOptions {
mode?: SymbolMode;
/** Locale used only to look up a fallback symbol for non-Arab currencies. */
locale?: string;
}
/**
* Resolve the display symbol for a currency under a given {@link SymbolMode}.
* Falls back to `Intl`'s symbol for currencies outside the curated Arab set, and
* to the ISO code when nothing else is available.
*/
declare function resolveCurrencySymbol(code: string, options?: ResolveSymbolOptions): string;
interface FormatCurrencyOptions {
/** ISO 4217 code. If omitted, derived from the locale's region. */
currency?: string;
/** BCP-47 locale. Default `"ar"`. */
locale?: string;
/** Symbol rendering strategy. Default `"auto"`. */
symbolMode?: SymbolMode;
/** Digit shaping. Default `"latn"` (Eastern Arabic numerals are opt-in). */
numerals?: NumeralSystem;
/** Force a fixed number of fraction digits (sets both min and max). */
fractionDigits?: number;
minimumFractionDigits?: number;
maximumFractionDigits?: number;
/** Thousands grouping. Default `true`. */
grouping?: boolean;
/** Include the symbol. Default `true`. */
showSymbol?: boolean;
/** Override symbol placement. Default: locale-appropriate (via Intl). */
symbolPosition?: SymbolPosition;
/** Separator between amount and symbol. Default non-breaking space. */
symbolSpacing?: string;
/** Wrap the result in a directional isolate for safe embedding. Default false. */
isolate?: boolean;
/**
* Accounting notation: wrap negative values in parentheses instead of
* using a minus sign. E.g. `(1,234.50 ر.س)`. Default `false`.
*/
accounting?: boolean;
/**
* Sign display. Default `"auto"` (negative sign only).
* `"always"` always shows `+` / `−`.
*/
signDisplay?: "auto" | "always" | "exceptZero" | "never";
}
/**
* Format a monetary amount with the correct symbol and minor-unit precision for
* its currency.
*
* @example formatCurrency(1234.5, { currency: "SAR" }) // "1,234.50 ر.س"
* @example formatCurrency(1.2, { currency: "KWD" }) // "1.200 د.ك" (3 decimals)
* @example formatCurrency(1234.5, { locale: "ar-SA", numerals: "arab" }) // Eastern digits
*/
declare function formatCurrency(amount: number, options?: FormatCurrencyOptions): string;
interface CurrencyInfo {
/** ISO 4217 code. */
code: string;
/** Minor-unit (decimal) digits. */
digits: number;
/** The symbol under each mode. `new` is present only when one exists. */
symbols: {
auto: string;
text: string;
code: string;
new?: string;
};
/** Unicode transition metadata, when the currency has a dedicated sign. */
unicode?: {
codepoint: string;
unicodeVersion: string;
released: string;
live: boolean;
autoDefault: boolean;
};
/** Localized currency display name via Intl, when available. */
displayName?: string;
}
/** Everything arabicfmt knows about a currency, for a given display locale. */
declare function getCurrencyInfo(code: string, locale?: string): CurrencyInfo;
/** Version of the CLDR dataset these tables were generated from. */
declare const CLDR_VERSION = "48.2.0";
/**
* Deliberate precision overrides on top of CLDR.
*
* CLDR records the *practical* number of decimals shown for cash, which for the
* Iraqi dinar is 0 (fils coins left circulation during hyperinflation). ISO 4217
* still defines IQD with **3** minor-unit digits, and a correctness-focused
* formatter should preserve that, so we override it here. This is the only place
* the library departs from CLDR precision — and it is asserted by the build-time
* verifier so it can never drift silently.
*/
declare const PRECISION_OVERRIDES: Readonly<Record<string, number>>;
/** Minor-unit (decimal) digit count for an ISO 4217 code. */
declare function currencyDigits(code: string): number;
/** Primary currency for an ISO 3166-1 alpha-2 region, per CLDR. */
declare function currencyForRegion(region: string): string | undefined;
/** Primary currency implied by a BCP-47 locale's region subtag, if any. */
declare function currencyForLocale(locale: string): string | undefined;
/**
* Curated currency-symbol data — the heart of arabicfmt.
*
* CLDR and `Intl` still ship the *old* symbols for the currencies caught up in
* the 2025–2026 Unicode transition, so the symbols below are maintained by hand
* and cross-checked against the Unicode proposals. Precision (minor-unit digits)
* is NOT here — that comes from CLDR via ./data.generated.ts.
*/
/**
* The legacy "Rial" ligature, U+FDFC (﷼). It encodes the **Iranian** rial and
* must never be used for the Saudi riyal (which now has its own sign, U+20C1).
* Exported so callers and tests can guard against it explicitly.
*/
declare const LEGACY_RIAL_LIGATURE = "\uFDFC";
interface UnicodeSymbol {
/** The dedicated sign itself, e.g. "" for the Saudi riyal. */
readonly char: string;
/** Human-readable codepoint label, e.g. "U+20C1". */
readonly codepoint: string;
/** Unicode version that introduces (or introduced) the sign. */
readonly unicodeVersion: string;
/** Month the introducing Unicode version is/was released. */
readonly released: string;
/** True once the introducing Unicode version has shipped. */
readonly live: boolean;
/**
* Whether `symbolMode: "auto"` should prefer this sign. Since Unicode 18.0
* (September 2026) this is `true` for the AED and OMR signs. The Saudi
* riyal (U+20C1) deliberately stays `false` — it is the
* most-traded Gulf currency, so its default remains the safe text symbol that
* renders everywhere; opt into the sign with `symbolMode: "new"`.
*/
readonly autoDefault: boolean;
}
interface CurrencySymbolData {
/** ISO 4217 code. */
readonly code: string;
/** Arabic abbreviation that renders in every font, e.g. "ر.س". */
readonly text: string;
/** Dedicated Unicode sign + transition metadata, when one exists. */
readonly unicode?: UnicodeSymbol;
}
/**
* Curated symbols for the 22 Arab League currencies (plus ILS, which circulates
* in the Palestinian territories). Keyed by ISO 4217 code.
*/
declare const CURRENCY_SYMBOLS: Readonly<Record<string, CurrencySymbolData>>;
/** Look up curated symbol data for an ISO 4217 code (case-insensitive). */
declare function getSymbolData(code: string): CurrencySymbolData | undefined;
interface ArabLeagueCountry {
/** ISO 3166-1 alpha-2 region code. */
readonly region: string;
/** English country name. */
readonly nameEn: string;
/** Arabic country name. */
readonly nameAr: string;
}
/**
* The 22 member states of the Arab League. Each country's primary currency is
* resolved from CLDR via {@link countryCurrency} rather than stored here, so the
* currency mapping has a single, verifiable source of truth.
*/
declare const ARAB_LEAGUE_COUNTRIES: readonly ArabLeagueCountry[];
/** Primary currency for an Arab League country (or any region), per CLDR. */
declare function countryCurrency(region: string): string | undefined;
/**
* التفقيط — spell a monetary amount in full Arabic words.
*
* This is the operation every Arabic invoice, cheque and contract needs:
* turning `1234.50 SAR` into "ألف ومئتان وأربعة وثلاثون ريالاً وخمسون هللة".
*
* The grammar of the counted noun (العدد والمعدود) is non-trivial:
* - 1 → singular (ريال واحد)
* - 2 → dual (ريالان)
* - n%100 3–10 → plural (ثلاثة ريالات)
* - n%100 11–99 → accusative singular (أحد عشر ريالاً)
* - n%100 0/1/2 → genitive singular (مئة ريال)
* The number itself is produced by {@link arabicToWords}; this module only
* supplies the correctly-inflected unit noun and joins the parts.
*/
/**
* The four inflected forms a currency noun needs for Arabic number agreement.
* Structurally identical to {@link CountedNoun} (re-exported for discoverability).
*/
type CurrencyNoun = CountedNoun;
interface CurrencyWords {
/** Major unit, e.g. the riyal. */
major: CurrencyNoun;
/** Minor unit, e.g. the halala. Omitted for currencies with no subunit. */
minor?: CurrencyNoun;
}
/**
* Arabic noun paradigms for the 22 Arab League currencies (+ neighbours).
* The number of minor units per major is derived from CLDR precision, so it is
* never stated twice — see {@link currencyDigits}.
*/
declare const CURRENCY_WORDS: Readonly<Record<string, CurrencyWords>>;
interface SpellCurrencyOptions {
/** ISO 4217 code (e.g. `"SAR"`). Required unless `locale` implies one. */
currency?: string;
/** BCP-47 locale; its region selects a currency when `currency` is absent. */
locale?: string;
/**
* Append a closing phrase. `true` adds the classic cheque ending
* "فقط لا غير"; a string appends that string verbatim.
*/
suffix?: boolean | string;
/** Prefix for negative amounts. Default `"سالب"`. */
negativePrefix?: string;
}
/**
* Spell a monetary amount in full Arabic words (التفقيط).
*
* Splits the amount into major and minor units using the currency's CLDR
* precision (so KWD yields 1000 fils, SAR yields 100 halalas), inflects each
* unit noun for correct agreement, and joins them with و.
*
* @example
* spellCurrency(1234.5, { currency: "SAR" })
* // "ألف ومئتان وأربعة وثلاثون ريالاً وخمسون هللة"
*
* @example
* spellCurrency(2, { currency: "KWD" }) // "ديناران"
* spellCurrency(1.5, { currency: "KWD" }) // "دينار واحد وخمسمئة فلس"
* spellCurrency(0.75, { currency: "SAR" }) // "خمسة وسبعون هللةً"
* spellCurrency(100, { currency: "EGP", suffix: true })
* // "مئة جنيه فقط لا غير"
* spellCurrency(-5, { locale: "ar-AE" }) // "سالب خمسة دراهم"
*/
declare function spellCurrency(amount: number, options?: SpellCurrencyOptions): string;
export { ARAB_LEAGUE_COUNTRIES, type ArabLeagueCountry, CLDR_VERSION, CURRENCY_SYMBOLS, CURRENCY_WORDS, type CurrencyInfo, type CurrencyNoun, type CurrencySymbolData, type CurrencyWords, type FormatCurrencyOptions, LEGACY_RIAL_LIGATURE, PRECISION_OVERRIDES, type ResolveSymbolOptions, type SpellCurrencyOptions, type SymbolMode, type SymbolPosition, type UnicodeSymbol, countryCurrency, currencyDigits, currencyForLocale, currencyForRegion, formatCurrency, getCurrencyInfo, getSymbolData, resolveCurrencySymbol, spellCurrency };