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 · 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, MVRU+20C2, AEDU+20C3, OMRU+20C4— además de las herramientas para renderizarlos: partes tipadas, un@font-facegenerado, rangos de precios y un registro de transición consciente de fechas.
npm install arabicfmt
Lo que otras bibliotecas hacen mal
| Problema | Otras bibliotecas | arabicfmt |
|---|---|---|
| Riyal saudí U+20C1 | Emite ﷼ (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 antigua | Los cuatro signos, más transitionStatus() y un @font-face generado |
| Estilizar un signo nuevo | Regex para extraer el símbolo de una cadena formateada | formatCurrencyToParts() — 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 hijri | Varía entre Node, Chrome, Safari, Hermes | Tablas Umm al-Qura congeladas — idénticas en cada motor |
| Plurales árabes | 1–2 formas; el árabe legalmente necesita 6 | Sistema completo CLDR de 6 formas (cero/uno/dos/pocos/muchos/otros) |
| Número a palabras árabes | Sin solución sin dependencias | arabicToWords(1234) → "ألف ومئتان وأربعة وثلاثون" |
| Escribir dinero para cheques (تفقيط) | Constrúyelo tú, falla en la gramática | spellCurrency(1234.5, {currency:"SAR"}) → "ألف ومئتان وأربعة وثلاثون ريالاً وخمسون هللةً" |
| Ordinales (ترتيبية) | Ausentes o sin género | arabicOrdinal(25) → "الخامس والعشرون", consciente del género |
| Duraciones habladas | Intl.DurationFormat apenas soportado | formatDuration(7_500_000) → "ساعتان وخمس دقائق" con concordancia completa |
| Oraciones RTL rotas | Los números de teléfono se invierten a mitad de frase | Los aislamientos Unicode envuelven automáticamente las secuencias LTR |
| Análisis de dígitos árabe-oriental | parseInt("١٢٣") → NaN | parseNumber("١٬٢٣٤٫٥٦") → 1234.56 |
| Slugs de URL árabes | Se vacían o producen mojibake | slugify("مدينة جدة") → "mdynh-jdh" |
| Verificación IBAN / ID saudí | Regex que acepta números inválidos | Sumas 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)
symbolMode | SAR | AED | OMR | Cuándo usar |
|---|---|---|---|---|
auto (predeterminado) | ر.س | U+20C3 | U+20C4 | Predeterminado. AED/OMR usan el signo dedicado; SAR permanece en texto seguro |
new | U+20C1 | U+20C3 | U+20C4 | Forzar el signo dedicado (requiere soporte de fuente) |
text | ر.س | د.إ | ر.ع. | Siempre el símbolo de texto seguro — se renderiza en todas partes |
code | SAR | AED | OMR | Có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, yautolos prefiere. ¿Necesitas máxima compatibilidad hoy? UsasymbolMode: "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
| Decimales | Monedas |
|---|---|
| 3 | KWD, BHD, OMR, JOD, IQD, LYD, TND |
| 0 | DJF, KMF |
| 2 | SAR, 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/date | arabicfmt/umalqura | |
|---|---|---|
| Algoritmo | Aritmética tabular | Tablas oficiales Umm al-Qura |
| Precisión | ±1–2 días | Exacta |
| Paquete | Diminuto (sin tablas) | Más grande (tablas ICU congeladas) |
| Rango | Cualquier año | AH 1300–1599 |
| Determinista | Sí | 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ón | Lo que obtienes | min + gzip |
|---|---|---|
arabicfmt | todo lo siguiente | 13.2 kB |
arabicfmt/currency | 22 monedas, تفقيط, partes, rangos, registro de transición | 7.6 kB |
arabicfmt/number | palabras, ordinales, fracciones, análisis, duración, … | 3.6 kB |
arabicfmt/umalqura | 300 años de tablas oficiales Umm al-Qura | 2.4 kB |
arabicfmt/text | normalizar, plurales, cotejo, listas, slugs | 1.7 kB |
arabicfmt/date | núcleo hijri tabular | 1.6 kB |
arabicfmt/bidi | detección de dirección + aislamientos | 0.8 kB |
arabicfmt/validate | sumas 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ódulo | Funciones |
|---|---|
arabicfmt/currency | formatCurrency · formatCurrencyToParts · formatCurrencyRange · spellCurrency · getCurrencyInfo · resolveCurrencySymbol · transitionStatus · getCurrencyTransition · listCurrencyTransitions · signFontFaceCSS · signUnicodeRange |
arabicfmt/number | formatNumber · formatPercent · formatCompact · parseNumber · parseCurrency · toArabicDigits · toExtendedArabicDigits · toLatinDigits · shapeDigits · arabicToWords · arabicOrdinal · arabicFraction · countedNoun · formatDuration · formatFileSize · formatRelativeTime |
arabicfmt/umalqura | formatHijri · toHijri · fromHijri · gregorianToUmalqura · umalquraToGregorian |
arabicfmt/date | formatHijri · toHijri · fromHijri (núcleo tabular) |
arabicfmt/text | stripTashkeel · removeTatweel · normalizeArabic · normalizeForSearch · arabicPlural · arabicPluralForm · sortArabic · compareArabic · createArabicCollator · formatList · transliterate · slugify |
arabicfmt/bidi | isolateForeign · isolate · wrapLTR · wrapRTL · stripBidi · detectDirection · isRTL · charDirection |
arabicfmt/validate | isValidIBAN · 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
| Dependencias | Cero dependencias en tiempo de ejecución |
| Tamaño | ~13,2 kB min+gzip para toda la biblioteca; importaciones de subrutas desde 0,7 kB |
| Formatos | ESM + CJS dual, tipos completos de .d.ts / .d.cts |
| Tree-shaking | "sideEffects": false — paga solo por lo que importas |
| Fuente de datos | CLDR 48.2.0 + ICU — verificado en tiempo de compilación, no escrito a mano |
| Cobertura de pruebas | 242 pruebas — transición de moneda, partes, rangos, precisión, Hijri, plurales, palabras, tafqit, duraciones, IBAN/ID |
| Plataformas | Node ≥ 18, todos los navegadores modernos, React Native / Hermes, Deno, Bun |
| Publicado con | procedencia 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".
| Moneda | Signo | Punto de código | Unicode | Anunciado | auto predeterminado |
|---|---|---|---|---|---|
| Riyal saudí (SAR) | | U+20C1 | 17.0 (9 sep 2025) | SAMA, 20 feb 2025 | texto ر.س (conservador) |
| Rufiyaa de Maldivas (MVR) | | U+20C2 | 18.0 (16 sep 2026) | MMA, 3 jul 2022 | signo |
| Dírham de EAU (AED) | | U+20C3 | 18.0 (16 sep 2026) | CBUAE, 27 mar 2025 | signo |
| Rial omaní (OMR) | | U+20C4 | 18.0 (16 sep 2026) | CBO, 19 nov 2025 | signo |
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.
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.

