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.
أرقامٌ وعملاتٌ وتواريخُ هجريةٌ ولغةٌ عربيةٌ سليمة — في سطرٍ واحد.
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, MVRU+20C2, AEDU+20C3, OMRU+20C4— além das ferramentas para realmente renderizá-los: partes tipadas, um@font-facegerado, faixas de preço e um registro de transição ciente de datas.
npm install arabicfmt
O que outras bibliotecas fazem de errado
| Problema | Outras bibliotecas | arabicfmt |
|---|---|---|
| Rial saudita U+20C1 | Emite ﷼ (U+FDFC) — o rial iraniano | U+20C1 correto com fallback de texto seguro |
| Os sinais Unicode 18.0 (MVR/AED/OMR) | Não codificados, ou ainda a abreviação antiga | Todos os quatro sinais, além de transitionStatus() e um @font-face gerado |
| Estilizar um novo sinal | Regex para extrair o símbolo de uma string formatada | formatCurrencyToParts() — 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 Hijri | Varia entre Node, Chrome, Safari, Hermes | Tabelas Umm al-Qura congeladas — idêntica em todos os motores |
| Plurais em árabe | 1–2 formas; o árabe legalmente precisa de 6 | Sistema completo de 6 formas CLDR (zero/um/dois/poucos/muitos/outro) |
| Número para palavras em árabe | Nenhuma solução sem dependências | arabicToWords(1234) → "ألف ومئتان وأربعة وثلاثون" |
| Escrever dinheiro por extenso para cheques (تفقيط) | Construa você mesmo, erre a gramática | spellCurrency(1234.5, {currency:"SAR"}) → "ألف ومئتان وأربعة وثلاثون ريالاً وخمسون هللةً" |
| Ordinais (ترتيبية) | Ausentes ou sem distinção de gênero | arabicOrdinal(25) → "الخامس والعشرون", ciente de gênero |
| Durações faladas | Intl.DurationFormat mal suportado | formatDuration(7_500_000) → "ساعتان وخمس دقائق" com concordância completa |
| Frases RTL quebradas | Números de telefone invertem no meio da frase | Isolados Unicode envolvem execuções LTR automaticamente |
| Análise de dígitos arábicos orientais | parseInt("١٢٣") → NaN | parseNumber("١٬٢٣٤٫٥٦") → 1234.56 |
| Slugs de URL em árabe | Reduz a vazio ou mojibake | slugify("مدينة جدة") → "mdynh-jdh" |
| Verificação de IBAN / ID saudita | Regex que aceita números inválidos | Somas 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)
symbolMode | SAR | AED | OMR | Use quando |
|---|---|---|---|---|
auto (padrão) | ر.س | U+20C3 | U+20C4 | Padrão. AED/OMR usam o sinal dedicado; SAR permanece em texto seguro |
new | U+20C1 | U+20C3 | U+20C4 | Força o sinal dedicado (requer suporte de fonte) |
text | ر.س | د.إ | ر.ع. | Sempre o símbolo de texto seguro — renderiza em qualquer lugar |
code | SAR | AED | OMR | Có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, eautoos prefere. Precisa de compatibilidade máxima hoje? UsesymbolMode: "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
| Decimais | Moedas |
|---|---|
| 3 | KWD, BHD, OMR, JOD, IQD, LYD, TND |
| 0 | DJF, KMF |
| 2 | SAR, 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/date | arabicfmt/umalqura | |
|---|---|---|
| Algoritmo | Aritmética tabular | Tabelas oficiais Umm al-Qura |
| Precisão | ±1–2 dias | Exata |
| Bundle | Minúsculo (sem tabelas) | Maior (tabelas ICU congeladas) |
| Intervalo | Qualquer ano | AH 1300–1599 |
| Determinístico | Sim | Sim — 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):
| Import | O que você obtém | min + gzip |
|---|---|---|
arabicfmt | tudo abaixo | 13.2 kB |
arabicfmt/currency | 22 moedas, تفقيط, partes, faixas, registro de transição | 7.6 kB |
arabicfmt/number | palavras, ordinais, frações, análise, duração, … | 3.6 kB |
arabicfmt/umalqura | 300 anos de tabelas oficiais Umm al-Qura | 2.4 kB |
arabicfmt/text | normalizar, plurais, colação, listas, slugs | 1.7 kB |
arabicfmt/date | núcleo Hijri tabular | 1.6 kB |
arabicfmt/bidi | detecção de direção + isolados | 0.8 kB |
arabicfmt/validate | somas de verificação IBAN + ID saudita | 0.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ódulo | Funções |
|---|---|
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 (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ências | Zero 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 |
| Formatos | ESM + CJS duplo, tipos completos de .d.ts / .d.cts |
| Tree-shaking | "sideEffects": false — pague apenas pelo que importar |
| Fonte de dados | CLDR 48.2.0 + ICU — verificado no momento da compilação, não digitado manualmente |
| Cobertura de testes | 242 testes — transição de moeda, partes, intervalos, precisão, Hijri, plurais, palavras, tafqit, durações, IBAN/ID |
| Plataformas | Node ≥ 18, todos os navegadores modernos, React Native / Hermes, Deno, Bun |
| Publicado com | proveniê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".
| Moeda | Símbolo | Codepoint | Unicode | Anunciado | Padrão de auto |
|---|---|---|---|---|---|
| Rial saudita (SAR) | | U+20C1 | 17.0 (9 set 2025) | SAMA, 20 fev 2025 | texto ر.س (conservador) |
| Rufiyaa maldiva (MVR) | | U+20C2 | 18.0 (16 set 2026) | MMA, 3 jul 2022 | símbolo |
| Dirham dos Emirados (AED) | | U+20C3 | 18.0 (16 set 2026) | CBUAE, 27 mar 2025 | símbolo |
| Rial omanense (OMR) | | U+20C4 | 18.0 (16 set 2026) | CBO, 19 nov 2025 | sí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.
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.

