npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tafgeet-arabic

v1.4.0

Published

Converts currency digits into written Arabic words

Readme

tafgeet-arabic

Convert numeric currency amounts into written Arabic words — for invoices, receipts, contracts, and legal documents.

npm version CI license npm downloads

new Tafgeet('1234.56', 'EGP').parse();
// → ألف ومائتان وأربعة وثلاثون جنيه مصري وستة وخمسون قرش فقط لا غير
  • ⚡ ~900,000 ops/sec — pure JS, zero runtime dependencies
  • ✅ Grammatically correct Arabic — full gender agreement & noun forms (details)
  • 📦 Dual ESM + CJS build, ships TypeScript .d.ts files, sideEffects: false
  • 🛡 Typed error classes (InvalidAmountError, AmountOutOfRangeError, …) with discriminated code fields
  • 🔢 Accepts Latin, Arabic-Indic (١٢٣), Eastern Arabic-Indic (۱۲۳), commas, spaces, underscores
  • ➗ Configurable rounding ('round', 'floor', 'ceil', 'bankers', 'truncate')
  • 🌍 24 currencies covering the Arab region plus major international codes (see the full table)

Requirements

  • Node.js ≥ 18.18.0

Install

npm install tafgeet-arabic

Usage

Quick start

// ES Module / TypeScript
import { Tafgeet } from 'tafgeet-arabic';

// CommonJS
const { Tafgeet } = require('tafgeet-arabic');

new Tafgeet('1234.56').parse();
// → 'ألف ومائتان وأربعة وثلاثون جنيه مصري وستة وخمسون قرش فقط لا غير'

Tip: prefer passing the amount as a string ('1234.56') over a JS number. JavaScript's Number loses precision above 253 and can mis-render trailing decimals (1.1 + 0.2 is 1.3000000000000003, not 1.3). The package accepts both, but strings round-trip cleanly.

Multiple currencies

new Tafgeet('500', 'SAR').parse();
// → 'خمسمائة ريال سعودي فقط لا غير'

new Tafgeet('1.005', 'KWD').parse();  // Kuwaiti Dinar uses 3 decimals
// → 'دينار كويتي واحد وخمسة فلوس فقط لا غير'

new Tafgeet('1234567', 'USD').parse();
// → 'مليون ومائتان وأربعة وثلاثون ألف وخمسمائة وسبعة وستون دولار أمريكي فقط لا غير'

No-currency mode

Pass an empty string to render the number alone, without a currency suffix:

new Tafgeet('7564654', '').parse();
// → 'سبعة ملايين وخمسمائة وأربعة وستون ألف وستمائة وأربعة وخمسون فقط لا غير'

Flexible input formats

Beyond plain Latin ('1234.56'), the constructor accepts amounts in many forms — useful for input from Arabic forms, CSVs, copy-pasted spreadsheets, etc.:

new Tafgeet('١٢٣٤.٥٦').parse();          // Arabic-Indic digits
new Tafgeet('۱۲۳۴.۵۶').parse();          // Eastern Arabic-Indic (Farsi/Urdu)
new Tafgeet('1,234.56').parse();         // ASCII comma
new Tafgeet('1_234_567').parse();        // JS underscore separator
new Tafgeet('1 234 567').parse();        // space (regular / NBSP / narrow NBSP)
new Tafgeet('١٬٥٠٠٫٢٥', 'EGP').parse(); // fully Arabic with Arabic separators
// All produce the same output as the plain-Latin equivalent.

Rounding

By default decimals beyond the currency's precision are truncated (preserving v1.1 behavior byte-for-byte). Opt in to other modes via the third constructor argument:

new Tafgeet('1.995', 'EGP', { rounding: 'round' }).parse();
// → 'جنيهان مصريان فقط لا غير'   (1.995 rounded to 2.00 → dual noun)

new Tafgeet('1.001', 'EGP', { rounding: 'ceil' }).parse();
// → 'جنيه مصري واحد وقرش واحد فقط لا غير'

new Tafgeet('1.225', 'EGP', { rounding: 'bankers' }).parse();
// → 'جنيه مصري واحد واثنان وعشرون قرش فقط لا غير'  (half-to-even)

Available modes: 'truncate' (default), 'round' (half-up), 'floor', 'ceil', 'bankers' (IEEE 754 half-to-even — recommended for accounting).

Error handling

Invalid input throws a typed error rather than producing garbage output:

try {
  new Tafgeet(-100, 'EGP');
} catch (err) {
  // err instanceof RangeError
  // err.message === 'Tafgeet: amount must be non-negative, got -100'
}

try {
  new Tafgeet(1, 'XYZ');
} catch (err) {
  // err instanceof Error
  // err.message === 'Tafgeet: unknown currency "XYZ". Supported: SDG, SAR, QAR, AED, EGP, KWD, USD, AUD, TND, TRY, BHD, OMR, JOD, IQD, LYD, LBP, MAD, DZD, SYP, YER, EUR, GBP, CHF, CAD'
}

See API → Errors for the full list.

TypeScript

All public types are re-exported from the package entry point:

import {
  Tafgeet,
  SUPPORTED_CURRENCIES,
  isTafgeetError,
  InvalidAmountError,
  AmountOutOfRangeError,
  UnsupportedCurrencyError,
} from 'tafgeet-arabic';
import type {
  Currency,
  Currencies,
  CurrencyCode,
  CurrencyInput,
  NumberProperties,
  RoundingMode,
  TafgeetOptions,
  TafgeetErrorCode,
} from 'tafgeet-arabic';

API

new Tafgeet(amount, currency?, options?)

Creates a new Tafgeet instance. The constructor validates the input eagerly and throws a typed error if anything is malformed.

| Parameter | Type | Default | Notes | |---|---|---|---| | amount | string \| number | — | Integer part must be ≥ 1 and ≤ 15 digits. Pass a string to avoid float precision loss. Arabic-Indic digits and thousands separators are accepted. | | currency | CurrencyInput | 'EGP' | A CurrencyCode, an empty string for no-currency mode, or any string (runtime validated). | | options | TafgeetOptions | {} | See Options below. |

Options

interface TafgeetOptions {
  rounding?: 'truncate' | 'round' | 'floor' | 'ceil' | 'bankers';
}

| Option | Default | Behavior | |---|---|---| | rounding | 'truncate' | How to handle decimals beyond the currency's natural precision. 'truncate' preserves v1.1 behavior exactly. 'round' is half-up. 'bankers' is IEEE 754 half-to-even (recommended for accounting). Rounding can carry into the integer part (1.995 EGP with 'round'2 EGP). |

.parse(): string

Renders the full amount as Arabic words, including the currency suffix and the closing فقط لا غير.

new Tafgeet('123.45', 'EGP').parse();
// → 'مائة وثلاثة وعشرون جنيه مصري وخمسة وأربعون قرش فقط لا غير'

new Tafgeet('1.995', 'EGP', { rounding: 'round' }).parse();
// → 'جنيهان مصريان فقط لا غير'  (rounded up to 2 → dual noun)

.read(d: number): string

Renders a number in 0–999 as Arabic words (masculine), with no column suffix and no currency. Useful for custom formatting.

const t = new Tafgeet('1');   // any instance
t.read(42);  // → 'اثنان وأربعون'
t.read(100); // → 'مائة'
t.read(999); // → 'تسعمائة وتسعة وتسعون'

Errors

Each error class extends the appropriate built-in, so existing instanceof TypeError / RangeError checks keep working. The code field allows structured handling:

| Class | Extends | code | When | |---|---|---|---| | InvalidAmountError | TypeError | 'INVALID_AMOUNT' | amount is null, undefined, wrong type, non-numeric string, empty string, or scientific notation. Also thrown when currency is not a string. | | AmountOutOfRangeError | RangeError | 'AMOUNT_OUT_OF_RANGE' | amount is NaN, Infinity, negative, zero (integer part < 1), or has more than 15 integer digits. | | UnsupportedCurrencyError | Error | 'UNSUPPORTED_CURRENCY' | currency is a string but not one of the codes in SUPPORTED_CURRENCIES. |

import { isTafgeetError } from 'tafgeet-arabic';

try {
  new Tafgeet(input, currency).parse();
} catch (e) {
  if (isTafgeetError(e)) {
    switch (e.code) {
      case 'INVALID_AMOUNT':       reportToUser('Bad number');   break;
      case 'AMOUNT_OUT_OF_RANGE':  reportToUser('Out of range'); break;
      case 'UNSUPPORTED_CURRENCY': reportToUser('Bad currency'); break;
    }
  } else {
    throw e;
  }
}

Arabic grammar

Since v1.4 the output is grammatically correct Modern Standard Arabic (فصحى), undiacritized (no tashkīl). The number agrees with the noun it counts — the currency unit for the integer part, the fractional unit for the fraction — and each built-in currency carries its own gender, so you never configure it.

| Count | Rule | Masculine (جنيه) | Feminine (ليرة) | |---|---|---|---| | 1 | noun + agreeing adjective | جنيه مصري واحد | ليرة تركية واحدة | | 2 | the dual noun (no number word) | جنيهان مصريان | ليرتان تركيتان | | 3–10 | number opposes gender + plural noun | ثلاثة جنيهات | ثلاث ليرات | | 11–99 | number + singular noun | أحد عشر جنيه | إحدى عشرة ليرة | | 100/1000+ | scale word + singular noun | مائة جنيه، ألف جنيه | مائة ليرة |

The noun form follows the number that directly governs it, so 103 → مائة وثلاثة جنيهات (plural) and 120 → مائة وعشرون جنيه (singular). Duals are nominative (اثنان، مائتان، ألفان) and drop their nūn when مضاف to the counted noun (مائتا جنيه، ألفا جنيه، مليونا جنيه). No-currency mode renders the bare number in the masculine.

new Tafgeet('3', 'EGP').parse();   // → 'ثلاثة جنيهات مصرية فقط لا غير'  (masc)
new Tafgeet('3', 'TRY').parse();   // → 'ثلاث ليرات تركية فقط لا غير'    (fem)
new Tafgeet('2', 'EGP').parse();   // → 'جنيهان مصريان فقط لا غير'        (dual)

A native-speaker review settled these forms. If you spot a regional or stylistic preference that differs, please open an issue.

Common pitfalls

Things that surprise users — most are guarded by typed errors since v1.2.1, but worth knowing up front.

Pass strings for amounts you computed

JavaScript floats lose precision above 2⁵³ and produce surprises like 0.1 + 0.2 = 0.30000000000000004. The package documents string | number input, but strings round-trip cleanly:

// Risky — arithmetic results may render unexpectedly:
const total = subtotal + tax;
new Tafgeet(total, 'EGP').parse();

// Safe — string preserves your computed precision:
new Tafgeet(total.toFixed(2), 'EGP').parse();

Amounts must be ≥ 1

new Tafgeet('0', 'EGP') and new Tafgeet('0.50', 'EGP') both throw AmountOutOfRangeError. The Arabic dictionary has no word for zero and the column logic assumes at least one integer digit. If you compute an amount that might round below 1:

const a = Math.max(1, Math.ceil(amount));
new Tafgeet(a.toString(), 'EGP').parse();

Very large or very small numbers must be strings

Numbers ≥ 10²¹ and < 10⁻⁶ stringify in scientific notation ('1e+21'), which the package rejects since v1.2.1 (it used to silently corrupt them). Pass them as strings:

// Throws AmountOutOfRangeError:
new Tafgeet(1e21, 'EGP');

// Works:
new Tafgeet('1000000000000000000000', 'EGP');

No-currency mode is for integers only

new Tafgeet('1.50', '') throws since v1.2.1 (silently dropped the cents pre-1.2.1). Either pass an integer or specify a currency:

new Tafgeet('150', '').parse();          // ok — pure integer
new Tafgeet('1.50', 'EGP').parse();     // ok — with currency
new Tafgeet('1.50', '');                 // throws — ambiguous
new Tafgeet('1.0', '').parse();          // ok — all-zero fraction

read() validates strictly

Tafgeet.prototype.read(d) throws on anything outside 0..999 since v1.2.1 (used to silently return ''). Always pass a finite integer in range.

Strict options validation

Unknown option keys throw — including typos like { Rounding: 'round' }. Stick to the documented TafgeetOptions shape; the v1.2.1 strict mode catches typos at runtime that TypeScript catches at compile time.

Supported currencies

24 currencies, sorted alphabetically by ISO code (matches SUPPORTED_CURRENCIES).

| Code | Currency | Decimals | |---|---|---| | AED | Emarati Dirham — درهم أماراتي | 2 | | AUD | Australian Dollar — دولار أسترالي | 2 | | BHD | Bahraini Dinar — دينار بحريني | 3 | | CAD | Canadian Dollar — دولار كندي | 2 | | CHF | Swiss Franc — فرنك سويسري | 2 | | DZD | Algerian Dinar — دينار جزائري | 2 | | EGP (default) | Egyptian Pound — جنيه مصري | 2 | | EUR | Euro — يورو | 2 | | GBP | British Pound Sterling — جنيه إسترليني | 2 | | IQD | Iraqi Dinar — دينار عراقي | 3 | | JOD | Jordanian Dinar — دينار أردني | 3 | | KWD | Kuwaiti Dinar — دينار كويتي | 3 | | LBP | Lebanese Pound — ليرة لبنانية | 2 | | LYD | Libyan Dinar — دينار ليبي | 3 | | MAD | Moroccan Dirham — درهم مغربي | 2 | | OMR | Omani Rial — ريال عماني | 3 | | QAR | Qatari Riyal — ريال قطري | 2 | | SAR | Saudi Riyal — ريال سعودي | 2 | | SDG | Sudanese Pound — جنيه سوداني | 2 | | SYP | Syrian Pound — ليرة سورية | 2 | | TND | Tunisian Dinar — دينار تونسي | 3 | | TRY | Turkish Lira — ليرة تركية | 2 | | USD | US Dollar — دولار أمريكي | 2 | | YER | Yemeni Rial — ريال يمني | 2 |

Missing a currency? Open an issue or send a PR.

Changelog

See CHANGELOG.md.

v1.4.0 (latest) — a native-speaker grammar pass. Output is now grammatically correct فصحى with full gender agreement (e.g. ثلاث ليرات for the feminine lira, ثلاثة جنيهات for the masculine pound), correct dual/singular/plural noun selection, and the classical forms for 1 (جنيه مصري واحد) and 2 (جنيهان مصريان). Spelling fixes (اثنان، اثنا عشر، آلاف، إماراتي). This deliberately changes output for many inputs — see the CHANGELOG. No new options; gender is derived from the currency.

v1.3.0 — 14 new bundled currencies bring the total to 24: 10 Arab-region (BHD, OMR, JOD, IQD, LYD, LBP, MAD, DZD, SYP, YER) and 4 major international (EUR, GBP, CHF, CAD) for cross-border invoicing. No new APIs, no breaking changes.

v1.2.1 — eight correctness and hardening fixes found across two audit passes. Most notably: '1.5' EGP now correctly renders 50 piasters (not 5); Number.MAX_VALUE and other exponential-notation numbers now throw instead of silently corrupting; rounding can now carry from 0.999 up to 1; strict options validation catches typos. See the v1.2.1 entry in the CHANGELOG for the full list. No new APIs.

v1.2.0 — dual ESM/CJS build, CurrencyCode union type, Arabic-Indic & thousands-separator input, typed error classes, rounding option, 262 snapshot tests.

v1.1.0 — 3.5–4.7× faster, 52% smaller install, fixes for issues #7 / #8 / fraction-plural, KWD added, input validation.

Roadmap

  • Optional diacritized / accusative output mode (tashkīl + case endings, e.g. جنيهًا) — v1.4 ships undiacritized by design
  • Optional legal mode for unambiguous cheque/contract wording
  • Optional: functional API tafgeet(amount, currency?) alongside the class

Contributing

PRs and issues welcome. To set up locally:

git clone https://github.com/omar-ehab/tafgeet-arabic
cd tafgeet-arabic
npm install
npm test               # 1064 tests (726 snapshot + 338 unit/feature/regression)
npm run lint           # ESLint
npm run test:js-compat # CJS + ESM smoke tests against built dist/

License

MIT © Omar Ehab