Arabicfmt Mcp

io.github.cc1a2b由 GitHub 帳號 cc1a2b 發布v0.1.1更新於 Oct 24, 2026

Arabic formatting: 22 Arab currencies (Saudi riyal U+20C1), Hijri dates, تفقيط, RTL/bidi fixes.

概覽

AI 產生的概覽

本機 MCP 伺服器,封裝 arabicfmt 函式庫,讓助理格式化阿拉伯貨幣、回曆日期、阿拉伯語數字讀法、تفقيط 及 RTL 文字。

可以做什麼
依 22 個阿拉伯國家貨幣格式化金額,包含符號、小數精度、區間,以及新 Unicode 符號的分段輸出,並為支票和發票提供 تفقيط 金額大寫。把數字轉為阿拉伯語詞彙、序數、分數、時長、檔案大小和相對時間,並把阿拉伯數字字串解析回數值。以固定的 Umm al-Qura 表產生回曆日期。處理阿拉伯語文字:複數形式、排序、slug、搜尋正規化和 RTL/雙向文字隔離。驗證 IBAN 和沙烏地身分證號。
適用情境
適合助理起草阿拉伯語發票、支票、合約、價目表或在地化介面,需要正確的貨幣符號、小數精度、回曆日期或金額大寫時使用。也適合修正混合方向文字以及處理阿拉伯語搜尋和 slug。
執行需求
以本機程序透過 stdio 執行,從 npm 套件 arabicfmt-mcp 安裝;需要 Node.js 18 或更新版本。未宣告驗證、環境變數或標頭。
安裝前請注意
格式化和驗證都是唯讀計算,但傳入工具的金額、日期和證件號碼會由本機伺服器處理。用於真實支票或法律文件前,請核對產生的 تفقيط 措辭和貨幣精度。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Arabicfmt Mcp,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

arabicfmt

Arabic-first formatting for JavaScript & TypeScript

Currency symbols · Hijri/Islamic calendar · number-to-words · تفقيط · 6 plural forms · RTL/bidi —
correct for all 22 Arab League countries, with zero dependencies and full TypeScript types.

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

[npm version] [downloads] [jsDelivr hits] [gzipped size] [zero dependencies] [types included]

[arabicfmt — interactive Arabic formatting playground]

npm · Live demo · GitHub

arabicfmt is the only JavaScript library that handles the entire Arabic formatting stack in one zero-dependency package — currency symbols, number precision, Hijri/Islamic calendar dates, RTL bidirectional text, Arabic number-to-words and تفقيط — with full TypeScript types. Works in Node, the browser, Deno, Bun and React Native.

New in 0.1.5: every currency sign from the Unicode 17.0/18.0 transition — SAR U+20C1, MVR U+20C2, AED U+20C3, OMR U+20C4 — plus the tooling to actually render them: typed parts, a generated @font-face, price ranges and a date-aware transition registry.

sh
npm install arabicfmt

What other libraries get wrong

ProblemOther librariesarabicfmt
Saudi riyal U+20C1Emits ﷼ (U+FDFC) — the Iranian rialCorrect U+20C1 with a safe text fallback
The Unicode 18.0 signs (MVR/AED/OMR)Unencoded, or still the old abbreviationAll four signs, plus transitionStatus() and a generated @font-face
Styling a new signRegex the symbol back out of a formatted stringformatCurrencyToParts() — the sign is its own typed part
Iraqi dinar (IQD) decimals0 (CLDR practical)3 decimals — ISO 4217 legal standard
Hijri date outputVaries between Node, Chrome, Safari, HermesFrozen Umm al-Qura tables — identical on every engine
Arabic plurals1–2 forms; Arabic legally needs 6Full CLDR 6-form system (zero/one/two/few/many/other)
Number to Arabic wordsNo zero-dep solutionarabicToWords(1234) → "ألف ومئتان وأربعة وثلاثون"
Spell money for cheques (تفقيط)Build it yourself, get the grammar wrongspellCurrency(1234.5, {currency:"SAR"}) → "ألف ومئتان وأربعة وثلاثون ريالاً وخمسون هللةً"
Ordinals (ترتيبية)Missing or gender-blindarabicOrdinal(25) → "الخامس والعشرون", gender-aware
Spoken durationsIntl.DurationFormat barely supportedformatDuration(7_500_000) → "ساعتان وخمس دقائق" with full agreement
RTL broken sentencesPhone numbers flip mid-sentenceUnicode isolates wrap LTR runs automatically
Eastern Arabic digit parsingparseInt("١٢٣") → NaNparseNumber("١٬٢٣٤٫٥٦") → 1234.56
Arabic URL slugsStrip to empty or mojibakeslugify("مدينة جدة") → "mdynh-jdh"
IBAN / Saudi ID checksRegex that accepts bad numbersReal ISO 7064 mod-97 + Luhn checksums

Install

sh
npm install arabicfmt# oryarn add arabicfmt# orpnpm add arabicfmt

Requirements: Node.js ≥ 18 · TypeScript ≥ 4.7 (optional) · zero runtime dependencies.

Browser / CDN — no build step

Every release is mirrored on the jsDelivr and unpkg CDNs automatically. Import the browser-ready ESM bundle straight from a URL — no install, no bundler:

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

Subpaths work too — e.g. https://cdn.jsdelivr.net/npm/arabicfmt/dist/currency/index.js for just the currency module. Pin a version for production, e.g. [email protected].


Quick start

ts
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
// CurrencyformatCurrency(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 datatransitionStatus("AED");                          // "encoded" (Unicode 18.0)transitionStatus("AED", new Date("2026-01-01"));  // "announced"transitionStatus("KWD");                          // "none" — Kuwait has no signsignFontFaceCSS({ src: "/fonts/signs.woff2" });   // @font-face … unicode-range: U+20C1…U+20C4
// Number to Arabic wordsarabicToWords(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-awarearabicOrdinal(1);                        // "الأول"arabicOrdinal(25);                       // "الخامس والعشرون"arabicOrdinal(1, { gender: "female" });  // "الأولى"
// Duration & file sizeformatDuration(7_500_000);               // "ساعتان وخمس دقائق"formatFileSize(1536);                    // "1.5 كيلوبايت"
// ListsformatList(["أحمد", "محمد", "علي"]);                    // "أحمد ومحمد وعلي"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 timeformatRelativeTime(new Date(Date.now() - 3 * 86400_000));      // "منذ 3 أيام"
// Parse formatted strings back to numbersparseCurrency("١٬٢٣٤٫٥٠ ر.س");   // 1234.5parseCurrency("(500.00 SAR)");    // -500
// RTLisolateForeign("اتصل على +1 (555) 234-5678 الآن"); // phone stays intact in RTL
// Locale auto-detectiondetectLocale(); // "ar-SA" in a Saudi browser, "ar-EG" in Node with LANG=ar_EG

Currency formatting

Symbol strategy: symbolMode

The Saudi riyal received its own Unicode symbol (U+20C1) in September 2025. Most libraries either emit the wrong ligature (U+FDFC, the Iranian rial) or fall back to SAR. arabicfmt gives you full control:

ts
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 when
auto (default)ر.س⃃ U+20C3⃄ U+20C4Default. AED/OMR use the dedicated sign; SAR stays on safe text
new⃁ U+20C1⃃ U+20C3⃄ U+20C4Force the dedicated sign (needs font support)
textر.سد.إر.ع.Always the safe text symbol — renders everywhere
codeSARAEDOMRISO code

Unicode 18.0 (16 September 2026): the AED (U+20C3), OMR (U+20C4) and MVR (U+20C2) signs are now live, and auto prefers them. Need maximum compatibility today? Use symbolMode: "text". The Saudi riyal keeps its safe text default by design. See the transition section for the full table.

Correct decimal precision — all 22 Arab League countries

Generated from CLDR 48.2.0 at build time and verified on every build:

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

All currency options

ts
// Resolve from locale region — no need to know the currency codeformatCurrency(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/overrideformatCurrency(100, { currency: "SAR", showSymbol: false, fractionDigits: 0 }); // "100"
// Currency metadatagetCurrencyInfo("SAR");// {//   code: "SAR", digits: 2,//   symbols: { auto: "ر.س", text: "ر.س", code: "SAR", new: "⃁" },//   unicode: { codepoint: "U+20C1", unicodeVersion: "17.0", live: true, autoDefault: false },//   displayName: "ريال سعودي"// }

Price ranges

ts
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 ⃃"  — collapsesformatCurrencyRange(500, 100, { currency: "SAR" }) // throws: reversed range

Pass rangeSeparator for anything other than the default spaced en dash ({ rangeSeparator: " إلى " }), and isolate: true to wrap the whole range when it sits inside a mixed-direction sentence.

Rendering the new signs

A brand-new sign needs two things a string cannot give you: a webfont scoped to its codepoint, and a way to wrap only the symbol in the element that uses that font. Both are one call each.

1. The parts API — the currency counterpart to Intl.NumberFormat.prototype.formatToParts:

ts
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 is true exactly when the part holds a dedicated Unicode sign, so a component never has to pattern-match a finished string (which is how bidi bugs start):

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

Part types mirror Intl (integer, group, decimal, fraction, minusSign, plusSign, literal) plus four arabicfmt adds: currency, parenthesis (accounting), isolate (bidi controls) and rangeSeparator.

2. The @font-face rule — generated, with the unicode-range that makes it free on pages that never print a sign:

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

Because the range is scoped to those codepoints, the browser downloads the font only when one of them is actually painted — body text is untouched. Put the family first in your stack and the rest falls through:

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

signUnicodeRange() returns just the range string ("U+20C1, U+20C2, U+20C3, U+20C4") for CSS-in-JS. Both helpers reject anything that would break out of the rule, so a URL from config can't inject CSS.


Number formatting

ts
import {  formatNumber, formatCompact, formatPercent,  toArabicDigits, toExtendedArabicDigits, toLatinDigits,  parseNumber, parseCurrency,  arabicToWords,  formatRelativeTime,} from "arabicfmt/number";
// StandardformatNumber(1_234_567.89, { locale: "en" });              // "1,234,567.89"formatNumber(1234.5,       { numerals: "arab" });           // "١٬٢٣٤٫٥"
// Compact / short notation — dashboards and data cardsformatCompact(1_500_000);                                   // "1.5M"formatCompact(1_500_000, { locale: "ar" });                 // "1.5 مليون"formatCompact(1_500_000, { locale: "ar", numerals: "arab" }); // "١٫٥ مليون"
// PercentformatPercent(0.853, { locale: "en" });                    // "85.3%"
// Three numeral systems: latn (default), arab, arabextformatNumber(1234.5, { numerals: "arab" });                // "١٬٢٣٤٫٥"  Eastern ArabicformatNumber(1234.5, { numerals: "arabext" });             // "۱٬۲۳۴٫۵"  Persian/Urdu
// TransliterationtoArabicDigits("Order #2026");                             // "Order #٢٠٢٦"toExtendedArabicDigits("2026");                            // "۲۰۲۶"toLatinDigits("٢٠٢٦");                                     // "2026"  (handles Persian ۰–۹ too)
// Parsing — round-trip supportparseNumber("١٬٢٣٤٫٥٦");         // 1234.56  (Eastern Arabic digits + separators)parseNumber("1,234.56");          // 1234.56  (Western)parseCurrency("١٬٢٣٤٫٥٠ ر.س");  // 1234.5parseCurrency("(500.00 SAR)");   // -500      (accounting notation)
// Relative timeformatRelativeTime(new Date(Date.now() - 3 * 86400_000));            // "منذ 3 أيام"formatRelativeTime(new Date(Date.now() + 3600_000), new Date(), { locale: "en" }); // "in 1 hour"

Number to Arabic words (arabicToWords)

Convert integers to their Arabic word representation — handles gender agreement and all six scale levels.

ts
import { arabicToWords } from "arabicfmt";
// BasicarabicToWords(0)       // "صفر"arabicToWords(1)       // "واحد"arabicToWords(2)       // "اثنان"arabicToWords(11)      // "أحد عشر"arabicToWords(25)      // "خمسة وعشرون"arabicToWords(100)     // "مئة"arabicToWords(350)     // "ثلاثمئة وخمسون"
// ThousandsarabicToWords(1000)    // "ألف"arabicToWords(2000)    // "ألفان"arabicToWords(5000)    // "خمسة آلاف"arabicToWords(11000)   // "أحد عشر ألفاً"arabicToWords(100000)  // "مئة ألف"
// Millions / billionsarabicToWords(1_000_000)    // "مليون"arabicToWords(2_000_000)    // "مليونان"arabicToWords(5_000_000)    // "خمسة ملايين"arabicToWords(1_000_000_000)// "مليار"
// Large compositearabicToWords(1_234_567)// "مليون ومئتان وأربعة وثلاثون ألفاً وخمسمئة وسبعة وستون"
// Gender agreement — feminine noun (ليرة، روبية…)arabicToWords(3, { gender: "female" })  // "ثلاث"arabicToWords(5, { gender: "female" })  // "خمس"
// NegativearabicToWords(-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)  // "ثلثان"

Spell money in words — التفقيط

spellCurrency is the tafqit every Arabic invoice, cheque and contract needs: it turns a numeric amount into its full legal Arabic wording, splitting major and minor units and inflecting every noun for correct grammatical agreement (singular / dual / plural / accusative).

ts
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 halalasspellCurrency(1.5, { currency: "KWD" })   // "دينار واحد وخمسمئة فلس"spellCurrency(0.75, { currency: "SAR" })  // "خمس وسبعون هللةً"
// Cheque-ready ending and locale-derived currencyspellCurrency(100, { currency: "SAR", suffix: true }) // "مئة ريال فقط لا غير"spellCurrency(-5,  { locale: "ar-AE" })               // "سالب خمسة دراهم"

Full Arabic noun paradigms are bundled for all 22 Arab League currencies (SAR, AED, KWD, BHD, QAR, OMR, JOD, EGP, IQD, LYD, TND, DZD, MAD, SDG, LBP, SYP, YER, SOS, DJF, KMF, MRU). Inspect or extend them via the exported CURRENCY_WORDS table.


Ordinal numbers — الأعداد الترتيبية

ts
import { arabicOrdinal } from "arabicfmt";
arabicOrdinal(1)    // "الأول"arabicOrdinal(2)    // "الثاني"arabicOrdinal(10)   // "العاشر"arabicOrdinal(11)   // "الحادي عشر"arabicOrdinal(25)   // "الخامس والعشرون"
// Gender agreementarabicOrdinal(1, { gender: "female" })   // "الأولى"arabicOrdinal(25, { gender: "female" })  // "الخامسة والعشرون"
// Indefinite (drop the article ال)arabicOrdinal(3, { definite: false })    // "ثالث"arabicOrdinal(25, { definite: false })   // "خامس وعشرون"

Duration — spelled Arabic

formatDuration turns a time span into its spoken Arabic form, with correct dual/plural/accusative agreement on every unit — something Intl.DurationFormat (still barely supported) does not give you.

ts
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 consideredformatDuration(125 * 60_000, { units: ["minute"], largest: 1 })// "مئة وخمس وعشرون دقيقةً"

largest (default 2) caps how many units appear, biggest first. Want to drive the noun agreement yourself? countedNoun(n, forms) is exported for any custom counted noun.


File size — Arabic data units

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

Units scale through بايت · كيلوبايت · ميجابايت · جيجابايت · تيرابايت · بيتابايت, with base: 1024 (binary, default) or base: 1000 (decimal).


Arabic plural rules (6 forms)

Arabic has six plural forms — more than any other major language. Standard i18n libraries handle 1–2 forms and break for Arabic.

ts
import { arabicPluralForm, arabicPlural } from "arabicfmt";
// Get the CLDR form namearabicPluralForm(0)    // "zero"arabicPluralForm(1)    // "one"arabicPluralForm(2)    // "two"arabicPluralForm(5)    // "few"   (3–10)arabicPluralForm(15)   // "many"  (11–99)arabicPluralForm(100)  // "other"
// Select the right stringconst 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)  // "كتاب"

Hijri / Islamic calendar dates

Two engines with an identical API:

arabicfmt/datearabicfmt/umalqura
AlgorithmTabular arithmeticOfficial Umm al-Qura tables
Accuracy±1–2 daysExact
BundleTiny (no tables)Larger (frozen ICU tables)
RangeAny yearAH 1300–1599
DeterministicYesYes — same on Node/Chrome/Safari/Hermes
ts
import { toHijri, fromHijri, formatHijri, umalquraToGregorian } from "arabicfmt/umalqura";
// ConverttoHijri(new Date("2025-09-23"))        // { year: 1447, month: 4, day: 1 }umalquraToGregorian(1447, 9, 1)        // JavaScript Date — first day of Ramadan 1447
// Format — ArabicformatHijri(new Date("2025-09-23"))// "1 ربيع الآخر 1447 هـ"
formatHijri(new Date("2025-09-23"), { numerals: "arab" })// "١ ربيع الآخر ١٤٤٧ هـ"
// Format — EnglishformatHijri(new Date("2025-09-23"), { locale: "en" })// "1 Rabi al-Thani 1447 AH"
// Format — ISO-style numericformatHijri(new Date("2025-09-23"), {  locale: "en", month: "2-digit", day: "2-digit", order: "ymd", era: false,})// "1447/04/01"

Month and weekday name tables

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

Bidirectional (RTL) text helpers

Stop phone numbers and English words from scrambling Arabic sentences:

ts
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 intactisolateForeign("اتصل على +1 (555) 234-5678 الآن");
detectDirection("مرحبا");   // "rtl"detectDirection("Hello");   // "ltr"
isolate("9:41 AM");         // FSI … PDI isolate around a mixed runstripBidi(dirtyStr);        // remove every Unicode bidi control character

Text normalization for Arabic search

Match Arabic text despite diacritics, alef variants, hamza and taa marbuta differences:

ts
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 collationsortArabic(["ياسر", "أحمد", "بسام"])   // ["أحمد", "بسام", "ياسر"]["ج", "أ", "ب"].sort(compareArabic)    // ["أ", "ب", "ج"]

List formatting

Join values into a grammatical Arabic list. Wraps Intl.ListFormat and degrades gracefully on runtimes without it.

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

Transliteration & URL slugs

Romanize Arabic script to readable Latin, or turn it into URL-safe slugs for routes, filenames and CMS permalinks. Deterministic — short vowels appear only when the text is vowelled (carries tashkeel).

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

Note: this is a pragmatic, reversible-ish romanization, not a strict academic transliteration (DIN 31635 / ISO 233). It is built for slugs, search keys and readable IDs.


Validation — IBAN & Saudi ID

Real checksums, not regex guesses. isValidIBAN runs the ISO 7064 mod-97 algorithm with SWIFT-registry length checks; isValidSaudiId runs the Luhn check digit and classifies citizen vs. resident.

ts
import { isValidIBAN, formatIBAN, isValidSaudiId, saudiIdType } from "arabicfmt";
isValidIBAN("SA03 8000 0000 6080 1016 7519")  // trueisValidIBAN("SA03 8000 0000 6080 1016 7510")  // false (bad checksum)formatIBAN("SA0380000000608010167519")        // "SA03 8000 0000 6080 1016 7519"
isValidSaudiId("1012345672")                  // truesaudiIdType("1012345672")                     // "citizen"saudiIdType("2100000005")                     // "resident"  (Iqama)

Registry lengths are enforced for SA, AE, KW, BH, QA, JO, LB, EG, IQ, PS, TN, MR, LY (plus common partners). Unknown-country IBANs are validated by checksum and the general 15–34 length bound, never accepted on structure alone.


Framework usage

React / Next.js

tsx
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

ts
import { formatCurrency } from "arabicfmt";
// composableexport function useArabicCurrency(currency: string) {  return (amount: number) =>    formatCurrency(amount, { currency, numerals: "arab" });}

Node.js / Express

ts
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 });});

Locale auto-detection

ts
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 });

Subpath imports — tree-shakeable

Pick only what you need for the smallest possible bundle:

ts
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-inimport { isolateForeign }        from "arabicfmt/bidi";import { normalizeForSearch, arabicPlural, slugify } from "arabicfmt/text";import { isValidIBAN, isValidSaudiId }      from "arabicfmt/validate";

Measured cost of each entry point (esbuild --bundle --minify, gzipped — v0.1.5):

ImportWhat you getmin + gzip
arabicfmteverything below13.2 kB
arabicfmt/currency22 currencies, تفقيط, parts, ranges, transition registry7.6 kB
arabicfmt/numberwords, ordinals, fractions, parse, duration, …3.6 kB
arabicfmt/umalqura300 years of official Umm al-Qura tables2.4 kB
arabicfmt/textnormalize, plurals, collation, lists, slugs1.7 kB
arabicfmt/datetabular Hijri core1.6 kB
arabicfmt/bididirection detection + isolates0.8 kB
arabicfmt/validateIBAN + Saudi ID checksums0.7 kB

The complete Arabic formatting stack costs less than a single small image.


Full API reference

Every public function, by module. Full signatures and options are in the sections above and in the bundled TypeScript types.

ModuleFunctions
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 (tabular core)
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 (root)re-exports everything above + detectLocale

MCP server — use arabicfmt from AI agents

AI agents (Claude Desktop, Claude Code, Cursor) can call arabicfmt directly through the arabicfmt-mcp Model Context Protocol server — 21 tools (format_currency, format_currency_range, currency_transition, spell_currency, format_hijri, arabic_to_words, isolate_foreign, validate_iban, …). Add it to your client's mcpServers config:

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

Source and full tool list: mcp/.

Examples

Runnable scripts for every feature live in examples/:

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

Engineering

DependenciesZero runtime dependencies
Size~13.2 kB min+gzip for the whole library; subpath imports from 0.7 kB
FormatsDual ESM + CJS, full .d.ts / .d.cts types
Tree-shaking"sideEffects": false — pay only for what you import
Data sourceCLDR 48.2.0 + ICU — verified at build time, not hand-typed
Test coverage242 tests — currency transition, parts, ranges, precision, Hijri, plurals, words, tafqit, durations, IBAN/ID
PlatformsNode ≥ 18, all evergreen browsers, React Native / Hermes, Deno, Bun
Published withnpm provenance (GitHub Actions attestation)

Unicode currency-sign transition

Complete as of Unicode 18.0 — released 16 September 2026

Four currencies moved from an ad-hoc abbreviation to a dedicated Unicode sign in this cycle, and arabicfmt carries all four. Note what is not on this list: Kuwait has never issued a dinar sign — KWD is still د.ك / KD, and transitionStatus("KWD") returns "none".

CurrencySignCodepointUnicodeAnnouncedauto default
Saudi riyal (SAR)⃁U+20C117.0 (9 Sep 2025)SAMA, 20 Feb 2025text ر.س (conservative)
Maldivian rufiyaa (MVR)⃂U+20C218.0 (16 Sep 2026)MMA, 3 Jul 2022sign
UAE dirham (AED)⃃U+20C318.0 (16 Sep 2026)CBUAE, 27 Mar 2025sign
Omani rial (OMR)⃄U+20C418.0 (16 Sep 2026)CBO, 19 Nov 2025sign

The rufiyaa is not an Arab League currency, but it was encoded in the same batch and sits between the dirham and rial in the Currency Symbols block — carrying it keeps the range contiguous, so one unicode-range covers the whole transition.

Because system-font coverage for brand-new signs still varies, symbolMode: "text" always returns the safe abbreviation, and the Saudi riyal keeps the text symbol as its auto default by design.

The transition as data

Encoding is not rendering. A sign exists in the standard months or years before system fonts draw it, and every product shipping in that window has to decide what to print. That timeline is queryable:

ts
import {  transitionStatus,  listCurrencyTransitions,  getCurrencyTransition,} from "arabicfmt/currency";
transitionStatus("SAR")                          // "encoded"transitionStatus("SAR", new Date("2025-03-01"))  // "announced" — SAMA had it, Unicode didn'ttransitionStatus("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 takes a moment in time, so a historical invoice can be re-rendered with the symbol that was correct on its own date.


Live demo

arabicfmt.vercel.app — the whole library, interactive and computed live in your browser. Change any input and watch the Arabic update in real time: currency studio, تفقيط, Hijri converter, plurals, RTL fixes and more.

[arabicfmt interactive playground]

Run it locally:

sh
cd demo && npm install && npm run dev

Contributing

Issues and pull requests are welcome on GitHub.


License

MIT — free for commercial and personal use.


Author & more projects

Built and maintained by cc1a2b.

If arabicfmt saves you time, please star it on GitHub — it helps other Arabic developers find it. Explore my other open-source projects, or open an issue with ideas, bugs and feature requests.

Built for Arabic-first software

來源:README.md,提交 946ad4a

工具

0
SourceWeft 尚未在沙箱中啟動過該軟體包,因此其工具未知。

版本歷史

1
  1. v0.1.1最新Sep 16, 2026

評分

已滿足適用檢查項的 50%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分