Hussain Alsharman

Servidor MCP de formato árabe: moneda para los 22 países de la Liga Árabe (rial saudí U+20C1), fechas Hégiras de Umm al-Qura, طيقفت, correcciones RTL/bidi

Documentación

arabicfmt

Formato árabe de primera clase para JavaScript y TypeScript

Símbolos de moneda · calendario hijri/islámico · números a palabras · تفقيط · 6 formas plurales · RTL/bidi —
correcto para los 22 países de la Liga Árabe, con cero dependencias y tipos TypeScript completos.

Números, monedas, fechas hijri y árabe correcto — en una sola línea.

npm version downloads jsDelivr hits gzipped size zero dependencies types included

arabicfmt — interactive Arabic formatting playground

npm · Demo en vivo · GitHub

arabicfmt es la única biblioteca de JavaScript que maneja toda la pila de formato árabe en un solo paquete sin dependencias: símbolos de moneda, precisión numérica, fechas del calendario hijri/islámico, texto bidireccional RTL, números árabes a palabras y تفقيط — con tipos TypeScript completos. Funciona en Node, navegador, Deno, Bun y React Native.

Nuevo en 0.1.5: cada signo de moneda de la transición Unicode 17.0/18.0 — SAR U+20C1, MVR U+20C2, AED U+20C3, OMR U+20C4 — además de las herramientas para renderizarlos: partes tipadas, un @font-face generado, rangos de precios y un registro de transición consciente de fechas.

npm install arabicfmt

Lo que otras bibliotecas hacen mal

ProblemaOtras bibliotecasarabicfmt
Riyal saudí U+20C1Emite ﷼ (U+FDFC) — el rial iraníU+20C1 correcto con un respaldo de texto seguro
Los signos Unicode 18.0 (MVR/AED/OMR)Sin codificar, o aún la abreviatura antiguaLos cuatro signos, más transitionStatus() y un @font-face generado
Estilizar un signo nuevoRegex para extraer el símbolo de una cadena formateadaformatCurrencyToParts() — el signo es su propia parte tipada
Decimales del dinar iraquí (IQD)0 (práctico CLDR)3 decimales — estándar legal ISO 4217
Salida de fecha hijriVaría entre Node, Chrome, Safari, HermesTablas Umm al-Qura congeladas — idénticas en cada motor
Plurales árabes1–2 formas; el árabe legalmente necesita 6Sistema completo CLDR de 6 formas (cero/uno/dos/pocos/muchos/otros)
Número a palabras árabesSin solución sin dependenciasarabicToWords(1234) → "ألف ومئتان وأربعة وثلاثون"
Escribir dinero para cheques (تفقيط)Constrúyelo tú, falla en la gramáticaspellCurrency(1234.5, {currency:"SAR"}) → "ألف ومئتان وأربعة وثلاثون ريالاً وخمسون هللةً"
Ordinales (ترتيبية)Ausentes o sin géneroarabicOrdinal(25) → "الخامس والعشرون", consciente del género
Duraciones habladasIntl.DurationFormat apenas soportadoformatDuration(7_500_000) → "ساعتان وخمس دقائق" con concordancia completa
Oraciones RTL rotasLos números de teléfono se invierten a mitad de fraseLos aislamientos Unicode envuelven automáticamente las secuencias LTR
Análisis de dígitos árabe-orientalparseInt("١٢٣") → NaNparseNumber("١٬٢٣٤٫٥٦") → 1234.56
Slugs de URL árabesSe vacían o producen mojibakeslugify("مدينة جدة") → "mdynh-jdh"
Verificación IBAN / ID saudíRegex que acepta números inválidosSumas de verificación reales ISO 7064 mod-97 + Luhn

Instalación

npm install arabicfmt
# or
yarn add arabicfmt
# or
pnpm add arabicfmt

Requisitos: Node.js ≥ 18 · TypeScript ≥ 4.7 (opcional) · cero dependencias en tiempo de ejecución.

Navegador / CDN — sin paso de compilación

Cada versión se replica automáticamente en los CDN jsDelivr y unpkg. Importa el paquete ESM listo para navegador directamente desde una URL — sin instalación, sin bundler:

<script type="module">
  import { formatCurrency, formatHijri } from "https://cdn.jsdelivr.net/npm/arabicfmt/+esm";

  console.log(formatCurrency(1234.5, { currency: "SAR", numerals: "arab" })); // ١٬٢٣٤٫٥٠ ر.س
  console.log(formatHijri(new Date(), { numerals: "arab" }));                 // ٢٧ ذو الحجة ١٤٤٧ هـ
</script>

Las subrutas también funcionan — p. ej. https://cdn.jsdelivr.net/npm/arabicfmt/dist/currency/index.js para solo el módulo de moneda. Fija una versión para producción, p. ej. arabicfmt@0.1.


Inicio rápido

import {
  formatCurrency,      // correct symbol + precision for every Arab currency
  formatCurrencyRange, // "1,000.00 – 5,000.00 ر.س"
  formatCurrencyToParts, // typed parts — font-scope just the new Unicode sign
  transitionStatus,    // "none" | "announced" | "encoded" for any date
  signFontFaceCSS,     // the scoped @font-face the new signs need
  formatCompact,       // 1,200,000 → "1.2M" / "١٫٢ مليون"
  arabicToWords,       // 1234 → "ألف ومئتان وأربعة وثلاثون"
  spellCurrency,       // تفقيط: 1234.5 SAR → "...ريالاً وخمسون هللةً"
  arabicOrdinal,       // 25 → "الخامس والعشرون"
  formatDuration,      // 7_500_000ms → "ساعتان وخمس دقائق"
  formatFileSize,      // 1536 → "1.5 كيلوبايت"
  formatRelativeTime,  // "منذ ٣ أيام"
  formatList,          // ["أحمد","علي"] → "أحمد وعلي"
  parseCurrency,       // "١٬٢٣٤٫٥٠ ر.س" → 1234.5
  arabicPlural,        // full 6-form Arabic plural selection
  sortArabic,          // Arabic-locale collation
  slugify,             // "مدينة جدة" → "mdynh-jdh" (URL slugs)
  isValidIBAN,         // ISO 7064 mod-97 IBAN checksum
  isValidSaudiId,      // Saudi national ID / Iqama check digit
  isolateForeign,      // fix broken RTL sentences
  normalizeForSearch,  // search-key normalization
  detectLocale,        // auto-detect from browser / Node environment
} from "arabicfmt";

import { formatHijri, toHijri } from "arabicfmt/umalqura"; // deterministic Hijri calendar

// Currency
formatCurrency(1.2,   { currency: "KWD" });                      // "1.200 د.ك"
formatCurrency(1234,  { locale: "ar-SA", numerals: "arab" });    // "١٬٢٣٤٫٠٠ ر.س"
formatCurrency(-500,  { currency: "SAR", accounting: true });    // "(500.00 ر.س)"
formatCurrencyRange(1000, 5000, { currency: "SAR" });            // "1,000.00 – 5,000.00 ر.س"
formatCompact(1_500_000, { locale: "ar", numerals: "arab" });    // "١٫٥ مليون"

// The Unicode currency-sign transition, as data
transitionStatus("AED");                          // "encoded" (Unicode 18.0)
transitionStatus("AED", new Date("2026-01-01"));  // "announced"
transitionStatus("KWD");                          // "none" — Kuwait has no sign
signFontFaceCSS({ src: "/fonts/signs.woff2" });   // @font-face … unicode-range: U+20C1…U+20C4

// Number to Arabic words
arabicToWords(1234);                     // "ألف ومئتان وأربعة وثلاثون"
arabicToWords(1_000_000);               // "مليون"
arabicToWords(5, { gender: "female" }); // "خمس"

// Spell money for invoices & cheques (التفقيط)
spellCurrency(1234.5, { currency: "SAR" });
// "ألف ومئتان وأربعة وثلاثون ريالاً وخمسون هللةً"
spellCurrency(100, { currency: "SAR", suffix: true }); // "مئة ريال فقط لا غير"

// Ordinals — gender-aware
arabicOrdinal(1);                        // "الأول"
arabicOrdinal(25);                       // "الخامس والعشرون"
arabicOrdinal(1, { gender: "female" });  // "الأولى"

// Duration & file size
formatDuration(7_500_000);               // "ساعتان وخمس دقائق"
formatFileSize(1536);                    // "1.5 كيلوبايت"

// Lists
formatList(["أحمد", "محمد", "علي"]);                    // "أحمد ومحمد وعلي"
formatList(["تفاح", "موز"], { type: "disjunction" });   // "تفاح أو موز"

// Hijri dates (deterministic — same output on Node, Chrome, Safari, Hermes)
formatHijri(new Date("2025-09-23"));                            // "1 ربيع الآخر 1447 هـ"
formatHijri(new Date("2025-09-23"), { numerals: "arab" });     // "١ ربيع الآخر ١٤٤٧ هـ"
toHijri(new Date("2025-09-23"));                               // { year: 1447, month: 4, day: 1 }

// Relative time
formatRelativeTime(new Date(Date.now() - 3 * 86400_000));      // "منذ 3 أيام"

// Parse formatted strings back to numbers
parseCurrency("١٬٢٣٤٫٥٠ ر.س");   // 1234.5
parseCurrency("(500.00 SAR)");    // -500

// RTL
isolateForeign("اتصل على +1 (555) 234-5678 الآن"); // phone stays intact in RTL

// Locale auto-detection
detectLocale(); // "ar-SA" in a Saudi browser, "ar-EG" in Node with LANG=ar_EG

Formato de moneda

Estrategia de símbolo: symbolMode

El riyal saudí recibió su propio símbolo Unicode (U+20C1) en septiembre de 2025. La mayoría de las bibliotecas emiten la ligadura incorrecta (U+FDFC, el rial iraní) o recurren a SAR. arabicfmt te da control total:

import { formatCurrency, resolveCurrencySymbol, getCurrencyInfo } from "arabicfmt/currency";

formatCurrency(1234.5, { currency: "SAR" });
// → "1,234.50 ر.س"   (auto: safe text symbol, renders everywhere today)

formatCurrency(1234.5, { currency: "SAR", symbolMode: "new" });
// → "1,234.50 ⃁"     (U+20C1 — use with a webfont; see webfont guide below)

formatCurrency(1234.5, { currency: "SAR", symbolMode: "code" });
// → "1,234.50 SAR"   (ISO code — for accounting tables)
symbolModeSARAEDOMRCuándo usar
auto (predeterminado)ر.س⃃ U+20C3⃄ U+20C4Predeterminado. AED/OMR usan el signo dedicado; SAR permanece en texto seguro
new⃁ U+20C1⃃ U+20C3⃄ U+20C4Forzar el signo dedicado (requiere soporte de fuente)
textر.سد.إر.ع.Siempre el símbolo de texto seguro — se renderiza en todas partes
codeSARAEDOMRCódigo ISO

Unicode 18.0 (16 de septiembre de 2026): los signos AED (U+20C3), OMR (U+20C4) y MVR (U+20C2) ahora son live, y auto los prefiere. ¿Necesitas máxima compatibilidad hoy? Usa symbolMode: "text". El riyal saudí mantiene su texto seguro predeterminado por diseño. Consulta la sección de transición para la tabla completa.

Precisión decimal correcta — los 22 países de la Liga Árabe

Generado desde CLDR 48.2.0 en tiempo de compilación y verificado en cada compilación:

formatCurrency(1.2, { currency: "KWD" });  // "1.200 د.ك"  ← 3 decimals
formatCurrency(1.2, { currency: "BHD" });  // "1.200 د.ب"  ← 3 decimals
formatCurrency(1.2, { currency: "IQD" });  // "1.200 ع.د"  ← 3 decimals (ISO 4217, not CLDR's 0)
formatCurrency(500, { currency: "KMF" });  // "500 ف.ج.ق"  ← 0 decimals
formatCurrency(500, { currency: "SAR" });  // "500.00 ر.س" ← 2 decimals
DecimalesMonedas
3KWD, BHD, OMR, JOD, IQD, LYD, TND
0DJF, KMF
2SAR, AED, QAR, y el resto

Todas las opciones de moneda

// Resolve from locale region — no need to know the currency code
formatCurrency(99.9,  { locale: "ar-BH" });                  // "99.900 د.ب"
formatCurrency(1234,  { locale: "ar-AE", numerals: "arab", symbolMode: "text" }); // "١٬٢٣٤٫٠٠ د.إ"  (auto → U+20C3 sign)

// Accounting notation (negatives in parentheses)
formatCurrency(-1234.5, { currency: "SAR", accounting: true }); // "(1,234.50 ر.س)"

// Hide/override
formatCurrency(100, { currency: "SAR", showSymbol: false, fractionDigits: 0 }); // "100"

// Currency metadata
getCurrencyInfo("SAR");
// {
//   code: "SAR", digits: 2,
//   symbols: { auto: "ر.س", text: "ر.س", code: "SAR", new: "⃁" },
//   unicode: { codepoint: "U+20C1", unicodeVersion: "17.0", live: true, autoDefault: false },
//   displayName: "ريال سعودي"
// }

Rangos de precios

import { formatCurrencyRange } from "arabicfmt/currency";

formatCurrencyRange(1000, 5000, { currency: "SAR" })
// "1,000.00 – 5,000.00 ر.س"     ← one symbol, the way ranges are actually set

formatCurrencyRange(1000, 5000, { currency: "SAR", symbolEach: true })
// "1,000.00 ر.س – 5,000.00 ر.س"

formatCurrencyRange(1.2, 2, { currency: "KWD" })
// "1.200 – 2.000 د.ك"           ← both ends share the currency's precision

formatCurrencyRange(1000, 5000, { currency: "SAR", numerals: "arab" })
// "١٬٠٠٠٫٠٠ – ٥٬٠٠٠٫٠٠ ر.س"

formatCurrencyRange(50, 50, { currency: "AED" })   // "50.00 ⃃"  — collapses
formatCurrencyRange(500, 100, { currency: "SAR" }) // throws: reversed range

Pasa rangeSeparator para cualquier cosa distinta de la raya en espaciada predeterminada ({ rangeSeparator: " إلى " }), y isolate: true para envolver todo el rango cuando se encuentra dentro de una oración de dirección mixta.

Renderizar los signos nuevos

Un signo completamente nuevo necesita dos cosas que una cadena no puede darte: una fuente web limitada a su punto de código, y una forma de envolver solo el símbolo en el elemento que usa esa fuente. Ambas son una llamada cada una.

1. La API de partes — la contraparte de moneda de Intl.NumberFormat.prototype.formatToParts:

import { formatCurrencyToParts } from "arabicfmt/currency";

formatCurrencyToParts(1234.5, { currency: "SAR", symbolMode: "new" });
// [ { type: "integer",  value: "1" },
//   { type: "group",    value: "," },
//   { type: "integer",  value: "234" },
//   { type: "decimal",  value: "." },
//   { type: "fraction", value: "50" },
//   { type: "literal",  value: " " },
//   { type: "currency", value: "⃁", symbolMode: "new",
//     codepoint: "U+20C1", needsFont: true } ]

needsFont es true exactamente cuando la parte contiene un signo Unicode dedicado, por lo que un componente nunca tiene que hacer coincidencia de patrones en una cadena terminada (que es como comienzan los errores de bidi):

<span dir="rtl">
  {formatCurrencyToParts(total, { currency: "SAR", symbolMode: "new" }).map(
    (part, i) =>
      part.needsFont
        ? <span key={i} className="riyal-sign">{part.value}</span>
        : <span key={i}>{part.value}</span>,
  )}
</span>

Los tipos de partes reflejan Intl (integer, group, decimal, fraction, minusSign, plusSign, literal) más cuatro que arabicfmt añade: currency, parenthesis (contabilidad), isolate (controles bidi) y rangeSeparator.

2. La regla @font-face — generada, con el unicode-range que la hace gratuita en páginas que nunca imprimen un signo:

import { signFontFaceCSS } from "arabicfmt/currency";

signFontFaceCSS({ src: "/fonts/currency-signs.woff2" });
// @font-face {
//   font-family: "Arabicfmt Signs";
//   src: url("/fonts/currency-signs.woff2") format("woff2");
//   font-display: swap;
//   unicode-range: U+20C1, U+20C2, U+20C3, U+20C4;
// }

// Saudi riyal only, into a family name you already use:
signFontFaceCSS({ src: "/fonts/riyal.woff2", family: "Riyal", currencies: ["SAR"] });
// unicode-range: U+20C1;

Como el rango está limitado a esos puntos de código, el navegador descarga la fuente solo cuando uno de ellos se pinta realmente — el texto del cuerpo no se toca. Pon la familia primero en tu pila y el resto se resuelve:

:root { font-family: "Arabicfmt Signs", "Noto Naskh Arabic", sans-serif; }

signUnicodeRange() devuelve solo la cadena de rango ("U+20C1, U+20C2, U+20C3, U+20C4") para CSS-in-JS. Ambos ayudantes rechazan cualquier cosa que pudiera salirse de la regla, por lo que una URL desde configuración no puede inyectar CSS.


Formato de números

import {
  formatNumber, formatCompact, formatPercent,
  toArabicDigits, toExtendedArabicDigits, toLatinDigits,
  parseNumber, parseCurrency,
  arabicToWords,
  formatRelativeTime,
} from "arabicfmt/number";

// Standard
formatNumber(1_234_567.89, { locale: "en" });              // "1,234,567.89"
formatNumber(1234.5,       { numerals: "arab" });           // "١٬٢٣٤٫٥"

// Compact / short notation — dashboards and data cards
formatCompact(1_500_000);                                   // "1.5M"
formatCompact(1_500_000, { locale: "ar" });                 // "1.5 مليون"
formatCompact(1_500_000, { locale: "ar", numerals: "arab" }); // "١٫٥ مليون"

// Percent
formatPercent(0.853, { locale: "en" });                    // "85.3%"

// Three numeral systems: latn (default), arab, arabext
formatNumber(1234.5, { numerals: "arab" });                // "١٬٢٣٤٫٥"  Eastern Arabic
formatNumber(1234.5, { numerals: "arabext" });             // "۱٬۲۳۴٫۵"  Persian/Urdu

// Transliteration
toArabicDigits("Order #2026");                             // "Order #٢٠٢٦"
toExtendedArabicDigits("2026");                            // "۲۰۲۶"
toLatinDigits("٢٠٢٦");                                     // "2026"  (handles Persian ۰–۹ too)

// Parsing — round-trip support
parseNumber("١٬٢٣٤٫٥٦");         // 1234.56  (Eastern Arabic digits + separators)
parseNumber("1,234.56");          // 1234.56  (Western)
parseCurrency("١٬٢٣٤٫٥٠ ر.س");  // 1234.5
parseCurrency("(500.00 SAR)");   // -500      (accounting notation)

// Relative time
formatRelativeTime(new Date(Date.now() - 3 * 86400_000));            // "منذ 3 أيام"
formatRelativeTime(new Date(Date.now() + 3600_000), new Date(), { locale: "en" }); // "in 1 hour"

Número a palabras árabes (arabicToWords)

Convierte enteros a su representación en palabras árabes — maneja concordancia de género y los seis niveles de escala.

import { arabicToWords } from "arabicfmt";

// Basic
arabicToWords(0)       // "صفر"
arabicToWords(1)       // "واحد"
arabicToWords(2)       // "اثنان"
arabicToWords(11)      // "أحد عشر"
arabicToWords(25)      // "خمسة وعشرون"
arabicToWords(100)     // "مئة"
arabicToWords(350)     // "ثلاثمئة وخمسون"

// Thousands
arabicToWords(1000)    // "ألف"
arabicToWords(2000)    // "ألفان"
arabicToWords(5000)    // "خمسة آلاف"
arabicToWords(11000)   // "أحد عشر ألفاً"
arabicToWords(100000)  // "مئة ألف"

// Millions / billions
arabicToWords(1_000_000)    // "مليون"
arabicToWords(2_000_000)    // "مليونان"
arabicToWords(5_000_000)    // "خمسة ملايين"
arabicToWords(1_000_000_000)// "مليار"

// Large composite
arabicToWords(1_234_567)
// "مليون ومئتان وأربعة وثلاثون ألفاً وخمسمئة وسبعة وستون"

// Gender agreement — feminine noun (ليرة، روبية…)
arabicToWords(3, { gender: "female" })  // "ثلاث"
arabicToWords(5, { gender: "female" })  // "خمس"

// Negative
arabicToWords(-42)  // "سالب اثنان وأربعون"

// Decimals — opt in (default truncates, stays backward compatible)
arabicToWords(3.14, { fraction: "digits" })  // "ثلاثة فاصلة واحد أربعة"
arabicToWords(3.14, { fraction: "number" })  // "ثلاثة فاصلة أربعة عشر"

// Common fractions (denominators 2–10)
import { arabicFraction } from "arabicfmt";
arabicFraction(1, 2)  // "نصف"
arabicFraction(3, 4)  // "ثلاثة أرباع"
arabicFraction(2, 3)  // "ثلثان"

Escribir dinero en palabras — التفقيط

spellCurrency es el tafqit que toda factura, cheque y contrato árabe necesita: convierte un monto numérico en su redacción legal árabe completa, dividiendo unidades mayores y menores e inflexionando cada sustantivo para una concordancia gramatical correcta (singular / dual / plural / acusativo).

import { spellCurrency } from "arabicfmt";

spellCurrency(1234.5, { currency: "SAR" })
// "ألف ومئتان وأربعة وثلاثون ريالاً وخمسون هللةً"

// Unit agreement is automatic (العدد والمعدود)
spellCurrency(1,   { currency: "SAR" })   // "ريال واحد"      (singular)
spellCurrency(2,   { currency: "SAR" })   // "ريالان"         (dual)
spellCurrency(3,   { currency: "SAR" })   // "ثلاثة ريالات"   (plural, 3–10)
spellCurrency(11,  { currency: "SAR" })   // "أحد عشر ريالاً" (accusative, 11–99)
spellCurrency(100, { currency: "SAR" })   // "مئة ريال"       (genitive singular)

// Minor-unit precision comes from CLDR — KWD = 1000 fils, SAR = 100 halalas
spellCurrency(1.5, { currency: "KWD" })   // "دينار واحد وخمسمئة فلس"
spellCurrency(0.75, { currency: "SAR" })  // "خمس وسبعون هللةً"

// Cheque-ready ending and locale-derived currency
spellCurrency(100, { currency: "SAR", suffix: true }) // "مئة ريال فقط لا غير"
spellCurrency(-5,  { locale: "ar-AE" })               // "سالب خمسة دراهم"

Paradigmas completos de sustantivos árabes están incluidos para las 22 monedas de la Liga Árabe (SAR, AED, KWD, BHD, QAR, OMR, JOD, EGP, IQD, LYD, TND, DZD, MAD, SDG, LBP, SYP, YER, SOS, DJF, KMF, MRU). Inspecciónalos o extiéndelos mediante la tabla exportada CURRENCY_WORDS.


Números ordinales — الأعداد الترتيبية

import { arabicOrdinal } from "arabicfmt";

arabicOrdinal(1)    // "الأول"
arabicOrdinal(2)    // "الثاني"
arabicOrdinal(10)   // "العاشر"
arabicOrdinal(11)   // "الحادي عشر"
arabicOrdinal(25)   // "الخامس والعشرون"

// Gender agreement
arabicOrdinal(1, { gender: "female" })   // "الأولى"
arabicOrdinal(25, { gender: "female" })  // "الخامسة والعشرون"

// Indefinite (drop the article ال)
arabicOrdinal(3, { definite: false })    // "ثالث"
arabicOrdinal(25, { definite: false })   // "خامس وعشرون"

Duración — árabe hablado

formatDuration convierte un lapso de tiempo en su forma árabe hablada, con concordancia correcta de dual/plural/acusativo en cada unidad — algo que Intl.DurationFormat (todavía apenas soportado) no te da.

import { formatDuration } from "arabicfmt";

formatDuration(7_500_000)                  // "ساعتان وخمس دقائق"  (2h 5m)
formatDuration(90, { input: "s" })         // "دقيقة واحدة وثلاثون ثانيةً"
formatDuration(3_600_000, { largest: 1 })  // "ساعة واحدة"
formatDuration(2 * 86_400_000)             // "يومان"
formatDuration(500)                        // "أقل من ثانية"

// Restrict the units considered
formatDuration(125 * 60_000, { units: ["minute"], largest: 1 })
// "مئة وخمس وعشرون دقيقةً"

largest (predeterminado 2) limita cuántas unidades aparecen, de mayor a menor. ¿Quieres controlar la concordancia de sustantivos tú mismo? countedNoun(n, forms) se exporta para cualquier sustantivo contado personalizado.


Tamaño de archivo — unidades de datos árabes

import { formatFileSize } from "arabicfmt";

formatFileSize(0)                          // "0 بايت"
formatFileSize(1536)                       // "1.5 كيلوبايت"
formatFileSize(5 * 1024 * 1024)            // "5 ميجابايت"
formatFileSize(1_500_000, { base: 1000 })  // "1.5 ميجابايت"  (decimal/SI)
formatFileSize(2048, { numerals: "arab" }) // "٢ كيلوبايت"
formatFileSize(2048, { unitStyle: "latin" })// "2 KB"

Las unidades escalan a través de بايت · كيلوبايت · ميجابايت · جيجابايت · تيرابايت · بيتابايت, con base: 1024 (binario, predeterminado) o base: 1000 (decimal).


Reglas plurales árabes (6 formas)

El árabe tiene seis formas plurales — más que cualquier otro idioma importante. Las bibliotecas i18n estándar manejan 1–2 formas y fallan con el árabe.

import { arabicPluralForm, arabicPlural } from "arabicfmt";

// Get the CLDR form name
arabicPluralForm(0)    // "zero"
arabicPluralForm(1)    // "one"
arabicPluralForm(2)    // "two"
arabicPluralForm(5)    // "few"   (3–10)
arabicPluralForm(15)   // "many"  (11–99)
arabicPluralForm(100)  // "other"

// Select the right string
const forms = {
  zero:  "لا كتب",
  one:   "كتاب واحد",
  two:   "كتابان",
  few:   "كتب",       // 3–10
  many:  "كتاباً",    // 11–99
  other: "كتاب",
};

arabicPlural(0,   forms)  // "لا كتب"
arabicPlural(1,   forms)  // "كتاب واحد"
arabicPlural(2,   forms)  // "كتابان"
arabicPlural(5,   forms)  // "كتب"
arabicPlural(25,  forms)  // "كتاباً"
arabicPlural(100, forms)  // "كتاب"

Fechas del calendario hijri / islámico

Dos motores con una API idéntica:

arabicfmt/datearabicfmt/umalqura
AlgoritmoAritmética tabularTablas oficiales Umm al-Qura
Precisión±1–2 díasExacta
PaqueteDiminuto (sin tablas)Más grande (tablas ICU congeladas)
RangoCualquier añoAH 1300–1599
DeterministaSíSí — igual en Node/Chrome/Safari/Hermes
import { toHijri, fromHijri, formatHijri, umalquraToGregorian } from "arabicfmt/umalqura";

// Convert
toHijri(new Date("2025-09-23"))        // { year: 1447, month: 4, day: 1 }
umalquraToGregorian(1447, 9, 1)        // JavaScript Date — first day of Ramadan 1447

// Format — Arabic
formatHijri(new Date("2025-09-23"))
// "1 ربيع الآخر 1447 هـ"

formatHijri(new Date("2025-09-23"), { numerals: "arab" })
// "١ ربيع الآخر ١٤٤٧ هـ"

// Format — English
formatHijri(new Date("2025-09-23"), { locale: "en" })
// "1 Rabi al-Thani 1447 AH"

// Format — ISO-style numeric
formatHijri(new Date("2025-09-23"), {
  locale: "en", month: "2-digit", day: "2-digit", order: "ymd", era: false,
})
// "1447/04/01"

Tablas de nombres de meses y días de la semana

import {
  HIJRI_MONTHS_AR,     // Arabic Hijri month names
  HIJRI_MONTHS_EN,     // English Hijri month names
  GREGORIAN_MONTHS_AR, // Arabic Gregorian month names (يناير، فبراير…)
  GREGORIAN_MONTHS_EN,
  ARABIC_WEEKDAYS_AR,  // Arabic weekday names (الأحد، الاثنين…)
  ARABIC_WEEKDAYS_EN,
} from "arabicfmt/date";

HIJRI_MONTHS_AR[8]        // "رمضان"  (index 0 = Muharram)
GREGORIAN_MONTHS_AR[0]    // "يناير"  (index 0 = January)
ARABIC_WEEKDAYS_AR[5]     // "الجمعة" (index 0 = Sunday)

Ayudantes de texto bidireccional (RTL)

Evita que los números de teléfono y las palabras en inglés desordenen las oraciones árabes:

import { detectDirection, isolate, isolateForeign, stripBidi } from "arabicfmt/bidi";

// Before fix: "+1 (555) 234-5678" flips the area code in RTL context
// After fix:  the phone number is wrapped in Unicode isolates — sentence intact
isolateForeign("اتصل على +1 (555) 234-5678 الآن");

detectDirection("مرحبا");   // "rtl"
detectDirection("Hello");   // "ltr"

isolate("9:41 AM");         // FSI … PDI isolate around a mixed run
stripBidi(dirtyStr);        // remove every Unicode bidi control character

Normalización de texto para búsqueda en árabe

Coincide texto árabe a pesar de diacríticos, variantes de alef, hamza y taa marbuta:

import {
  stripTashkeel,
  normalizeArabic,
  normalizeForSearch,
  sortArabic,
  compareArabic,
} from "arabicfmt/text";

stripTashkeel("مُحَمَّد")        // "محمد"
normalizeArabic("الأحمد")        // "الاحمد"  (alef variants unified)

// Robust search — these two strings produce the same key:
normalizeForSearch("مُؤسَّسة") === normalizeForSearch("موسسه")  // true

// Arabic-locale collation
sortArabic(["ياسر", "أحمد", "بسام"])   // ["أحمد", "بسام", "ياسر"]
["ج", "أ", "ب"].sort(compareArabic)    // ["أ", "ب", "ج"]

Formato de listas

Une valores en una lista árabe gramatical. Envuelve Intl.ListFormat y degrada con elegancia en entornos sin él.

import { formatList } from "arabicfmt";

formatList(["أحمد", "محمد", "علي"])                      // "أحمد ومحمد وعلي"
formatList(["تفاح", "موز", "برتقال"], { type: "disjunction" }) // "تفاح أو موز أو برتقال"
formatList([1, 2, 3], { numerals: "arab" })              // "١ و٢ و٣"

Transliteración y slugs de URL

Romaniza escritura árabe a latín legible, o conviértela en slugs seguros para URL en rutas, nombres de archivo y enlaces permanentes de CMS. Determinista — las vocales cortas aparecen solo cuando el texto está vocalizado (lleva tashkeel).

import { transliterate, slugify } from "arabicfmt";

transliterate("مُحَمَّد")    // "muhammad"   (vowelled)
transliterate("محمد")        // "mhmd"       (bare → consonant-only)
transliterate("الرياض")      // "alryad"
transliterate("غرفة ٢٠١")    // "ghrfh 201"  (digits converted)

slugify("مدينة جدة")                      // "mdynh-jdh"
slugify("الرياض 2026")                    // "alryad-2026"
slugify("Hello العالم", { separator: "_" }) // "hello_alalm"
slugify("Hello World", { lowercase: false }) // "Hello-World"

Nota: esta es una romanización pragmática, aproximadamente reversible, no una transliteración académica estricta (DIN 31635 / ISO 233). Está construida para slugs, claves de búsqueda e IDs legibles.


Validación — IBAN e ID saudí

Sumas de verificación reales, no suposiciones de regex. isValidIBAN ejecuta el algoritmo ISO 7064 mod-97 con verificaciones de longitud del registro SWIFT; isValidSaudiId ejecuta el dígito de verificación Luhn y clasifica ciudadano vs. residente.

import { isValidIBAN, formatIBAN, isValidSaudiId, saudiIdType } from "arabicfmt";

isValidIBAN("SA03 8000 0000 6080 1016 7519")  // true
isValidIBAN("SA03 8000 0000 6080 1016 7510")  // false (bad checksum)
formatIBAN("SA0380000000608010167519")        // "SA03 8000 0000 6080 1016 7519"

isValidSaudiId("1012345672")                  // true
saudiIdType("1012345672")                     // "citizen"
saudiIdType("2100000005")                     // "resident"  (Iqama)

Las longitudes de registro se aplican para SA, AE, KW, BH, QA, JO, LB, EG, IQ, PS, TN, MR, LY (más socios comunes). Los IBAN de países desconocidos se validan por suma de verificación y el límite general de longitud 15–34, nunca se aceptan solo por estructura.


Uso en frameworks

React / Next.js

import { formatCurrency, detectLocale } from "arabicfmt";
import { formatHijri } from "arabicfmt/umalqura";

export function PriceTag({ amount, currency }: { amount: number; currency: string }) {
  const locale = detectLocale();
  return (
    <span dir="rtl">
      {formatCurrency(amount, { currency, locale })}
    </span>
  );
}

export function HijriDate({ date }: { date: Date }) {
  return <time>{formatHijri(date, { numerals: "arab" })}</time>;
}

Vue 3

import { formatCurrency } from "arabicfmt";

// composable
export function useArabicCurrency(currency: string) {
  return (amount: number) =>
    formatCurrency(amount, { currency, numerals: "arab" });
}

Node.js / Express

import { formatCurrency, detectLocale } from "arabicfmt";
import { formatHijri } from "arabicfmt/umalqura";

app.get("/invoice/:id", (req, res) => {
  const locale = req.headers["accept-language"]?.split(",")[0] ?? "ar-SA";
  const total  = formatCurrency(order.total, { locale });
  const date   = formatHijri(order.date, { locale: "ar" });
  res.json({ total, date });
});

Detección automática de locale

import { detectLocale } from "arabicfmt";

// Browser: reads navigator.language
// Node.js: reads LANG / LANGUAGE / LC_ALL / LC_MESSAGES env vars
// Fallback: "ar"

const locale = detectLocale(); // "ar-SA", "ar-EG", "en-US", …
formatCurrency(1234, { locale });

Importaciones de subruta — tree-shakeable

Elige solo lo que necesitas para el paquete más pequeño posible:

import { formatCurrency, spellCurrency } from "arabicfmt/currency";
import { formatNumber, arabicToWords, formatDuration, formatFileSize } from "arabicfmt/number";
import { formatHijri, toHijri }  from "arabicfmt/date";       // tabular core (tiny)
import { formatHijri, toHijri }  from "arabicfmt/umalqura";   // accurate, opt-in
import { isolateForeign }        from "arabicfmt/bidi";
import { normalizeForSearch, arabicPlural, slugify } from "arabicfmt/text";
import { isValidIBAN, isValidSaudiId }      from "arabicfmt/validate";

Costo medido de cada punto de entrada (esbuild --bundle --minify, gzipped — v0.1.5):

ImportaciónLo que obtienesmin + gzip
arabicfmttodo lo siguiente13.2 kB
arabicfmt/currency22 monedas, تفقيط, partes, rangos, registro de transición7.6 kB
arabicfmt/numberpalabras, ordinales, fracciones, análisis, duración, …3.6 kB
arabicfmt/umalqura300 años de tablas oficiales Umm al-Qura2.4 kB
arabicfmt/textnormalizar, plurales, cotejo, listas, slugs1.7 kB
arabicfmt/datenúcleo hijri tabular1.6 kB
arabicfmt/bididetección de dirección + aislamientos0.8 kB
arabicfmt/validatesumas de verificación IBAN + ID saudí0.7 kB

La pila completa de formato árabe cuesta menos que una sola imagen pequeña.


Referencia completa de la API

Cada función pública, por módulo. Las firmas completas y las opciones están en las secciones anteriores y en los tipos TypeScript incluidos.

MóduloFunciones
arabicfmt/currencyformatCurrency · formatCurrencyToParts · formatCurrencyRange · spellCurrency · getCurrencyInfo · resolveCurrencySymbol · transitionStatus · getCurrencyTransition · listCurrencyTransitions · signFontFaceCSS · signUnicodeRange
arabicfmt/numberformatNumber · formatPercent · formatCompact · parseNumber · parseCurrency · toArabicDigits · toExtendedArabicDigits · toLatinDigits · shapeDigits · arabicToWords · arabicOrdinal · arabicFraction · countedNoun · formatDuration · formatFileSize · formatRelativeTime
arabicfmt/umalquraformatHijri · toHijri · fromHijri · gregorianToUmalqura · umalquraToGregorian
arabicfmt/dateformatHijri · toHijri · fromHijri (núcleo tabular)
arabicfmt/textstripTashkeel · removeTatweel · normalizeArabic · normalizeForSearch · arabicPlural · arabicPluralForm · sortArabic · compareArabic · createArabicCollator · formatList · transliterate · slugify
arabicfmt/bidiisolateForeign · isolate · wrapLTR · wrapRTL · stripBidi · detectDirection · isRTL · charDirection
arabicfmt/validateisValidIBAN · formatIBAN · normalizeIBAN · isValidSaudiId · saudiIdType
arabicfmt (raíz)reexporta todo lo anterior + detectLocale

Servidor MCP — usa arabicfmt desde agentes de IA

Los agentes de IA (Claude Desktop, Claude Code, Cursor) pueden llamar a arabicfmt directamente a través del servidor arabicfmt-mcp Model Context Protocol — 21 herramientas (format_currency, format_currency_range, currency_transition, spell_currency, format_hijri, arabic_to_words, isolate_foreign, validate_iban, …). Añádelo a la configuración de mcpServers de tu cliente:

{
  "mcpServers": {
    "arabicfmt": { "command": "npx", "args": ["-y", "arabicfmt-mcp"] }
  }
}

Fuente y lista completa de herramientas: mcp/.

Ejemplos

Los scripts ejecutables para cada función viven en examples/:

cd examples && npm install
node currency.mjs   # or numbers / words / dates / text / bidi / validate

Ingeniería

DependenciasCero dependencias en tiempo de ejecución
Tamaño~13,2 kB min+gzip para toda la biblioteca; importaciones de subrutas desde 0,7 kB
FormatosESM + CJS dual, tipos completos de .d.ts / .d.cts
Tree-shaking"sideEffects": false — paga solo por lo que importas
Fuente de datosCLDR 48.2.0 + ICU — verificado en tiempo de compilación, no escrito a mano
Cobertura de pruebas242 pruebas — transición de moneda, partes, rangos, precisión, Hijri, plurales, palabras, tafqit, duraciones, IBAN/ID
PlataformasNode ≥ 18, todos los navegadores modernos, React Native / Hermes, Deno, Bun
Publicado conprocedencia npm (atestación de GitHub Actions)

Transición de signos de moneda Unicode

Completa a partir de Unicode 18.0 — publicado el 16 de septiembre de 2026

Cuatro monedas pasaron de una abreviatura ad hoc a un signo Unicode dedicado en este ciclo, y arabicfmt incluye las cuatro. Ten en cuenta lo que no está en esta lista: Kuwait nunca ha emitido un signo de dinar — KWD sigue siendo د.ك / KD, y transitionStatus("KWD") devuelve "none".

MonedaSignoPunto de códigoUnicodeAnunciadoauto predeterminado
Riyal saudí (SAR)⃁U+20C117.0 (9 sep 2025)SAMA, 20 feb 2025texto ر.س (conservador)
Rufiyaa de Maldivas (MVR)⃂U+20C218.0 (16 sep 2026)MMA, 3 jul 2022signo
Dírham de EAU (AED)⃃U+20C318.0 (16 sep 2026)CBUAE, 27 mar 2025signo
Rial omaní (OMR)⃄U+20C418.0 (16 sep 2026)CBO, 19 nov 2025signo

La rufiyaa no es una moneda de la Liga Árabe, pero se codificó en el mismo lote y se sitúa entre el dírham y el rial en el bloque de Símbolos de Moneda — incluirla mantiene el rango contiguo, por lo que un solo unicode-range cubre toda la transición.

Debido a que la cobertura de las fuentes del sistema para signos nuevos aún varía, symbolMode: "text" siempre devuelve la abreviatura segura, y el riyal saudí mantiene el símbolo de texto como su auto predeterminado por diseño.

La transición como datos

La codificación no es renderizado. Un signo existe en el estándar meses o años antes de que las fuentes del sistema lo dibujen, y cada producto que se lanza en esa ventana tiene que decidir qué imprimir. Esa línea de tiempo es consultable:

import {
  transitionStatus,
  listCurrencyTransitions,
  getCurrencyTransition,
} from "arabicfmt/currency";

transitionStatus("SAR")                          // "encoded"
transitionStatus("SAR", new Date("2025-03-01"))  // "announced" — SAMA had it, Unicode didn't
transitionStatus("AED", new Date("2026-01-01"))  // "announced"
transitionStatus("KWD")                          // "none" — no sign exists

listCurrencyTransitions({ unicodeVersion: "18.0" }).map((t) => t.code);
// ["MVR", "AED", "OMR"]

getCurrencyTransition("OMR");
// {
//   code: "OMR", sign: "⃄", codepoint: "U+20C4", name: "OMANI RIAL SIGN",
//   unicodeVersion: "18.0", unicodeReleased: "2026-09-16",
//   announced: "2025-11-19", authority: "Central Bank of Oman (CBO)",
//   autoDefault: true, textSymbol: "ر.ع.",
// }

transitionStatus toma un momento en el tiempo, por lo que una factura histórica puede re-renderizarse con el símbolo que era correcto en su propia fecha.


Demo en vivo

arabicfmt.vercel.app — toda la biblioteca, interactiva y calculada en vivo en tu navegador. Cambia cualquier entrada y observa la actualización en árabe en tiempo real: estudio de moneda, تفقيط, conversor Hijri, plurales, correcciones RTL y más.

arabicfmt interactive playground

Ejecútalo localmente:

cd demo && npm install && npm run dev

Contribuciones

Las incidencias y las solicitudes de extracción son bienvenidas en GitHub.


Licencia

MIT — gratuito para uso comercial y personal.


Autor y más proyectos

Construido y mantenido por cc1a2b.

Si arabicfmt te ahorra tiempo, por favor dale una estrella en GitHub — ayuda a otros desarrolladores árabes a encontrarlo. Explora mis otros proyectos de código abierto, o abre una incidencia con ideas, errores y solicitudes de funciones.

Construido para software con prioridad árabe