Hussain Alsharman

Servidor MCP de formatação árabe: moeda para todos os 22 países da Liga Árabe (Rial saudita U+20C1), datas islâmicas Umm al-Qura, طيقفت, correções RTL/bidi

Documentação

arabicfmt

Formatação em árabe em primeiro lugar para JavaScript & TypeScript

Símbolos de moeda · calendário Hijri/islâmico · números por extenso · تفقيط · 6 formas plurais · RTL/bidi —
correto para todos os 22 países da Liga Árabe, com zero dependências e tipos TypeScript completos.

أرقامٌ وعملاتٌ وتواريخُ هجريةٌ ولغةٌ عربيةٌ سليمة — في سطرٍ واحد.

npm version downloads jsDelivr hits gzipped size zero dependencies types included

arabicfmt — interactive Arabic formatting playground

npm · Demonstração ao vivo · GitHub

arabicfmt é a única biblioteca JavaScript que lida com toda a pilha de formatação em árabe em um único pacote sem dependências — símbolos de moeda, precisão numérica, datas do calendário Hijri/islâmico, texto bidirecional RTL, números em árabe por extenso e تفقيط — com tipos TypeScript completos. Funciona em Node, navegador, Deno, Bun e React Native.

Novo na versão 0.1.5: todos os sinais de moeda da transição Unicode 17.0/18.0 — SAR U+20C1, MVR U+20C2, AED U+20C3, OMR U+20C4 — além das ferramentas para realmente renderizá-los: partes tipadas, um @font-face gerado, faixas de preço e um registro de transição ciente de datas.

npm install arabicfmt

O que outras bibliotecas fazem de errado

ProblemaOutras bibliotecasarabicfmt
Rial saudita U+20C1Emite ﷼ (U+FDFC) — o rial iranianoU+20C1 correto com fallback de texto seguro
Os sinais Unicode 18.0 (MVR/AED/OMR)Não codificados, ou ainda a abreviação antigaTodos os quatro sinais, além de transitionStatus() e um @font-face gerado
Estilizar um novo sinalRegex para extrair o símbolo de uma string formatadaformatCurrencyToParts() — o sinal é sua própria parte tipada
Decimais do dinar iraquiano (IQD)0 (prática CLDR)3 decimais — padrão legal ISO 4217
Saída de data HijriVaria entre Node, Chrome, Safari, HermesTabelas Umm al-Qura congeladas — idêntica em todos os motores
Plurais em árabe1–2 formas; o árabe legalmente precisa de 6Sistema completo de 6 formas CLDR (zero/um/dois/poucos/muitos/outro)
Número para palavras em árabeNenhuma solução sem dependênciasarabicToWords(1234) → "ألف ومئتان وأربعة وثلاثون"
Escrever dinheiro por extenso para cheques (تفقيط)Construa você mesmo, erre a gramáticaspellCurrency(1234.5, {currency:"SAR"}) → "ألف ومئتان وأربعة وثلاثون ريالاً وخمسون هللةً"
Ordinais (ترتيبية)Ausentes ou sem distinção de gêneroarabicOrdinal(25) → "الخامس والعشرون", ciente de gênero
Durações faladasIntl.DurationFormat mal suportadoformatDuration(7_500_000) → "ساعتان وخمس دقائق" com concordância completa
Frases RTL quebradasNúmeros de telefone invertem no meio da fraseIsolados Unicode envolvem execuções LTR automaticamente
Análise de dígitos arábicos orientaisparseInt("١٢٣") → NaNparseNumber("١٬٢٣٤٫٥٦") → 1234.56
Slugs de URL em árabeReduz a vazio ou mojibakeslugify("مدينة جدة") → "mdynh-jdh"
Verificação de IBAN / ID sauditaRegex que aceita números inválidosSomas de verificação reais ISO 7064 mod-97 + Luhn

Instalação

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

Requisitos: Node.js ≥ 18 · TypeScript ≥ 4.7 (opcional) · zero dependências em tempo de execução.

Navegador / CDN — sem etapa de build

Cada versão é espelhada automaticamente nos CDNs jsDelivr e unpkg. Importe o bundle ESM pronto para navegador diretamente de uma URL — sem instalação, sem 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>

Subcaminhos também funcionam — por exemplo, https://cdn.jsdelivr.net/npm/arabicfmt/dist/currency/index.js para apenas o módulo de moeda. Fixe uma versão para produção, por exemplo, arabicfmt@0.1.


Início 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

Formatação de moeda

Estratégia de símbolo: symbolMode

O rial saudita recebeu seu próprio símbolo Unicode (U+20C1) em setembro de 2025. A maioria das bibliotecas emite a ligadura errada (U+FDFC, o rial iraniano) ou recorre a SAR. O arabicfmt oferece controle 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)
symbolModeSARAEDOMRUse quando
auto (padrão)ر.س⃃ U+20C3⃄ U+20C4Padrão. AED/OMR usam o sinal dedicado; SAR permanece em texto seguro
new⃁ U+20C1⃃ U+20C3⃄ U+20C4Força o sinal dedicado (requer suporte de fonte)
textر.سد.إر.ع.Sempre o símbolo de texto seguro — renderiza em qualquer lugar
codeSARAEDOMRCódigo ISO

Unicode 18.0 (16 de setembro de 2026): os sinais AED (U+20C3), OMR (U+20C4) e MVR (U+20C2) agora são live, e auto os prefere. Precisa de compatibilidade máxima hoje? Use symbolMode: "text". O rial saudita mantém seu texto seguro padrão por design. Veja a seção de transição para a tabela completa.

Precisão decimal correta — todos os 22 países da Liga Árabe

Gerado a partir do CLDR 48.2.0 no momento do build e verificado em cada build:

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
DecimaisMoedas
3KWD, BHD, OMR, JOD, IQD, LYD, TND
0DJF, KMF
2SAR, AED, QAR e as demais

Todas as opções de moeda

// 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: "ريال سعودي"
// }

Faixas de preço

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

Passe rangeSeparator para qualquer coisa diferente do travessão en espaçado padrão ({ rangeSeparator: " إلى " }), e isolate: true para envolver toda a faixa quando ela estiver dentro de uma frase de direção mista.

Renderizando os novos sinais

Um sinal totalmente novo precisa de duas coisas que uma string não pode fornecer: uma webfont com escopo para seu codepoint e uma maneira de envolver apenas o símbolo no elemento que usa essa fonte. Ambas são uma chamada cada.

1. A API de partes — a contraparte de moeda para 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 é true exatamente quando a parte contém um sinal Unicode dedicado, então um componente nunca precisa fazer correspondência de padrão em uma string finalizada (que é como bugs de bidi começam):

<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>

Os tipos de parte espelham Intl (integer, group, decimal, fraction, minusSign, plusSign, literal) mais quatro que o arabicfmt adiciona: currency, parenthesis (contábil), isolate (controles bidi) e rangeSeparator.

2. A regra @font-face — gerada, com o unicode-range que a torna gratuita em páginas que nunca imprimem um sinal:

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 o intervalo tem escopo para esses codepoints, o navegador baixa a fonte apenas quando um deles é realmente pintado — o texto do corpo não é afetado. Coloque a família primeiro em sua pilha e o restante cai naturalmente:

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

signUnicodeRange() retorna apenas a string de intervalo ("U+20C1, U+20C2, U+20C3, U+20C4") para CSS-in-JS. Ambos os helpers rejeitam qualquer coisa que possa escapar da regra, então uma URL de configuração não pode injetar CSS.


Formatação 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 para palavras em árabe (arabicToWords)

Converte inteiros para sua representação em palavras em árabe — lida com concordância de gênero e todos os seis níveis 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)  // "ثلثان"

Escrever dinheiro por extenso — التفقيط

spellCurrency é o tafqit que toda fatura, cheque e contrato em árabe precisa: transforma um valor numérico em sua redação legal completa em árabe, dividindo unidades principais e secundárias e flexionando cada substantivo para concordância gramatical correta (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 substantivos em árabe estão incluídos para todas as 22 moedas da Liga Árabe (SAR, AED, KWD, BHD, QAR, OMR, JOD, EGP, IQD, LYD, TND, DZD, MAD, SDG, LBP, SYP, YER, SOS, DJF, KMF, MRU). Inspecione ou estenda-os via a tabela exportada CURRENCY_WORDS.


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

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 })   // "خامس وعشرون"

Duração — em árabe falado

formatDuration transforma um intervalo de tempo em sua forma falada em árabe, com concordância correta de dual/plural/acusativo em cada unidade — algo que Intl.DurationFormat (ainda mal suportado) não oferece.

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 (padrão 2) limita quantas unidades aparecem, da maior para a menor. Quer controlar a concordância do substantivo você mesmo? countedNoun(n, forms) é exportado para qualquer substantivo contado personalizado.


Tamanho de arquivo — unidades de dados em árabe

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"

As unidades escalam através de بايت · كيلوبايت · ميجابايت · جيجابايت · تيرابايت · بيتابايت, com base: 1024 (binário, padrão) ou base: 1000 (decimal).


Regras plurais em árabe (6 formas)

O árabe tem seis formas plurais — mais do que qualquer outra língua importante. Bibliotecas i18n padrão lidam com 1–2 formas e quebram para o á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)  // "كتاب"

Datas do calendário Hijri / islâmico

Dois motores com uma API idêntica:

arabicfmt/datearabicfmt/umalqura
AlgoritmoAritmética tabularTabelas oficiais Umm al-Qura
Precisão±1–2 diasExata
BundleMinúsculo (sem tabelas)Maior (tabelas ICU congeladas)
IntervaloQualquer anoAH 1300–1599
DeterminísticoSimSim — igual em 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"

Tabelas de nomes de meses e dias da 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)

Helpers de texto bidirecional (RTL)

Impeça que números de telefone e palavras em inglês embaralhem frases em árabe:

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

Normalização de texto para busca em árabe

Corresponda texto em árabe apesar de diacríticos, variantes de alef, hamza e diferenças de 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)    // ["أ", "ب", "ج"]

Formatação de listas

Junte valores em uma lista gramatical em árabe. Envolve Intl.ListFormat e degrada graciosamente em runtimes sem ele.

import { formatList } from "arabicfmt";

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

Transliteração e slugs de URL

Romanize a escrita árabe para latim legível, ou transforme-a em slugs seguros para URL em rotas, nomes de arquivo e permalinks de CMS. Determinístico — vogais curtas aparecem apenas quando o texto é vocalizado (carrega 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 é uma romanização pragmática, aproximadamente reversível, não uma transliteração acadêmica estrita (DIN 31635 / ISO 233). Ela é construída para slugs, chaves de busca e IDs legíveis.


Validação — IBAN e ID saudita

Somas de verificação reais, não suposições de regex. isValidIBAN executa o algoritmo ISO 7064 mod-97 com verificações de comprimento do registro SWIFT; isValidSaudiId executa o dígito verificador Luhn e classifica cidadão 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)

Comprimentos de registro são aplicados para SA, AE, KW, BH, QA, JO, LB, EG, IQ, PS, TN, MR, LY (além de parceiros comuns). IBANs de países desconhecidos são validados por soma de verificação e o limite geral de comprimento de 15–34, nunca aceitos apenas pela estrutura.


Uso em 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 });
});

Detecção 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 });

Imports por subcaminho — tree-shakeable

Escolha apenas o que você precisa para o menor bundle possível:

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";

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

ImportO que você obtémmin + gzip
arabicfmttudo abaixo13.2 kB
arabicfmt/currency22 moedas, تفقيط, partes, faixas, registro de transição7.6 kB
arabicfmt/numberpalavras, ordinais, frações, análise, duração, …3.6 kB
arabicfmt/umalqura300 anos de tabelas oficiais Umm al-Qura2.4 kB
arabicfmt/textnormalizar, plurais, colação, listas, slugs1.7 kB
arabicfmt/datenúcleo Hijri tabular1.6 kB
arabicfmt/bididetecção de direção + isolados0.8 kB
arabicfmt/validatesomas de verificação IBAN + ID saudita0.7 kB

A pilha completa de formatação em árabe custa menos do que uma única imagem pequena.


Referência completa da API

Cada função pública, por módulo. Assinaturas completas e opções estão nas seções acima e nos tipos TypeScript incluídos.

MóduloFunções
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 (raiz)reexporta tudo acima + detectLocale

Servidor MCP — use arabicfmt a partir de agentes de IA

Agentes de IA (Claude Desktop, Claude Code, Cursor) podem chamar arabicfmt diretamente através do servidor arabicfmt-mcp Model Context Protocol — 21 ferramentas (format_currency, format_currency_range, currency_transition, spell_currency, format_hijri, arabic_to_words, isolate_foreign, validate_iban, …). Adicione-o à configuração de mcpServers do seu cliente:

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

Fonte e lista completa de ferramentas: mcp/.

Exemplos

Scripts executáveis para cada recurso estão em examples/:

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

Engenharia

DependênciasZero dependências em tempo de execução
Tamanho~13,2 kB min+gzip para a biblioteca inteira; imports de subcaminho a partir de 0,7 kB
FormatosESM + CJS duplo, tipos completos de .d.ts / .d.cts
Tree-shaking"sideEffects": false — pague apenas pelo que importar
Fonte de dadosCLDR 48.2.0 + ICU — verificado no momento da compilação, não digitado manualmente
Cobertura de testes242 testes — transição de moeda, partes, intervalos, precisão, Hijri, plurais, palavras, tafqit, durações, IBAN/ID
PlataformasNode ≥ 18, todos os navegadores modernos, React Native / Hermes, Deno, Bun
Publicado comproveniência npm (atestação GitHub Actions)

Transição de símbolo de moeda Unicode

Completa a partir do Unicode 18.0 — lançado em 16 de setembro de 2026

Quatro moedas passaram de uma abreviação ad-hoc para um símbolo Unicode dedicado neste ciclo, e o arabicfmt carrega todas as quatro. Observe o que não está nesta lista: o Kuwait nunca emitiu um símbolo de dinar — KWD ainda é د.ك / KD, e transitionStatus("KWD") retorna "none".

MoedaSímboloCodepointUnicodeAnunciadoPadrão de auto
Rial saudita (SAR)⃁U+20C117.0 (9 set 2025)SAMA, 20 fev 2025texto ر.س (conservador)
Rufiyaa maldiva (MVR)⃂U+20C218.0 (16 set 2026)MMA, 3 jul 2022símbolo
Dirham dos Emirados (AED)⃃U+20C318.0 (16 set 2026)CBUAE, 27 mar 2025símbolo
Rial omanense (OMR)⃄U+20C418.0 (16 set 2026)CBO, 19 nov 2025símbolo

A rufiyaa não é uma moeda da Liga Árabe, mas foi codificada no mesmo lote e fica entre o dirham e o rial no bloco de Símbolos de Moeda — mantê-la mantém o intervalo contíguo, então um único unicode-range cobre toda a transição.

Como a cobertura de fontes do sistema para símbolos novos ainda varia, symbolMode: "text" sempre retorna a abreviação segura, e o rial saudita mantém o símbolo de texto como seu padrão de auto por design.

A transição como dados

Codificação não é renderização. Um símbolo existe no padrão meses ou anos antes das fontes do sistema desenhá-lo, e todo produto que lança nessa janela precisa decidir o que imprimir. Essa linha do tempo é consultável:

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 recebe um momento no tempo, então uma fatura histórica pode ser re-renderizada com o símbolo que estava correto na sua própria data.


Demonstração ao vivo

arabicfmt.vercel.app — a biblioteca inteira, interativa e computada ao vivo no seu navegador. Altere qualquer entrada e veja o árabe atualizar em tempo real: estúdio de moeda, تفقيط, conversor Hijri, plurais, correções RTL e mais.

arabicfmt interactive playground

Execute localmente:

cd demo && npm install && npm run dev

Contribuindo

Issues e pull requests são bem-vindos no GitHub.


Licença

MIT — gratuito para uso comercial e pessoal.


Autor & mais projetos

Construído e mantido por cc1a2b.

Se o arabicfmt economizar seu tempo, por favor dê uma estrela no GitHub — isso ajuda outros desenvolvedores árabes a encontrá-lo. Explore meus outros projetos de código aberto, ou abra uma issue com ideias, bugs e solicitações de recursos.

Construído para software com prioridade em árabe