Convert Arabic and Persian Digits to a Number in JavaScript
Arabic-Indic and Persian digits are real digits, and Intl.NumberFormat prints them by default for locales like ar-EG and fa-IR. JavaScript cannot read them back.
The problem
const text = new Intl.NumberFormat("ar-EG").format(1234.56); // "١٬٢٣٤٫٥٦"
Number(text); // NaN
parseFloat(text); // NaN
Number("١٢٣"); // NaNThe usual trick and its gaps
A common snippet maps each digit with its char code:
const toLatin = (s) =>
s.replace(/[٠-٩]/g, (d) => d.charCodeAt(0) - 0x0660)
.replace(/[۰-۹]/g, (d) => d.charCodeAt(0) - 0x06f0);
Number(toLatin("١٢٣")); // 123, fine
Number(toLatin("١٬٢٣٤٫٥٦")); // NaN: ٬ and ٫ are still thereIt also misses the Arabic decimal separator ٫ (U+066B), the Arabic thousands separator ٬ (U+066C), the invisible bidi marks that Intl places around the minus sign, the Arabic percent sign ٪, and every other script: Devanagari, Bengali, Thai, full width and more.
The fix
import { parse } from "numberparrot";
parse("١٬٢٣٤٫٥٦", "ar-EG"); // 1234.56
parse("۱۲۳٫۴۵", "fa-IR"); // 123.45
parse("١٥٪", "ar-EG"); // 0.15
parse("१२,३४,५६७.८९", "hi-IN"); // 1234567.89 (Devanagari)
parse("๑,๒๓๔.๕", "th-TH"); // 1234.5 (Thai)
parse("১,২৩৪.৫", "bn-BD"); // 1234.5 (Bengali)
parse("1234.5", "ja-JP"); // 1234.5 (full width)Every Unicode decimal digit is supported, whatever locale you pass. Text with mixed scripts works too: parse("١٢٣", "en-US") is 123.
Normalize while the user types
import { normalizeInput } from "numberparrot";
normalizeInput("١٢٫٥", "ar-EG");
// { text: "12٫5", value: 12.5, valid: true, complete: true }Install
npm install numberparrotZero dependencies, ESM and CommonJS, TypeScript types included. Works in Node.js 18+, Deno, Bun and browsers. Try it in the playground.