Skip to content
Devix Open Source

Guide

Getting started

npm install @devix-labs/number-words
import { toWords, toMoney, toCheque, toOrdinal, counted } from '@devix-labs/number-words';

Numbers

toWords(1234);                                  // one thousand two hundred thirty-four
toWords(1234, { language: 'en-GB' });           // one thousand two hundred and thirty-four
toWords(1234, { language: 'ar' });              // ألف ومئتان وأربعة وثلاثون
toWords(1234, { language: 'ur' });              // ایک ہزار دو سو چونتیس
toWords(12345678, { system: 'south-asian' });   // one crore twenty-three lakh forty-five thousand…
toWords(-3.14);                                 // minus three point one four

Urdu counts in lakh and crore by default; English does so when you ask.

Money, in any language

This is the part other libraries will not do: the currency and the language are independent, so a Dubai invoice written in English can be priced in dirhams.

toMoney(1234.5, { currency: 'AED' });                    // one thousand two hundred thirty-four dirhams and fifty fils
toMoney(1234.5, { currency: 'AED', language: 'ar' });    // ألف ومئتان وأربعة وثلاثون درهمًا وخمسون فلسًا
toMoney(1234.5, { currency: 'PKR', language: 'ur' });    // ایک ہزار دو سو چونتیس روپے پچاس پیسے
toMoney(1.5, { currency: 'KWD' });                       // one dinar and five hundred fils

Thirteen currencies are built in — AED, SAR, QAR, KWD, BHD, JOD, OMR, EGP, PKR, INR, USD, EUR, GBP — each with words in all three languages. The four that divide into a thousand keep all three digits.

The cheque line

toCheque(12345.5, { currency: 'AED' });
// AED twelve thousand three hundred forty-five dirhams and fifty fils only

toCheque(12345.5, { currency: 'PKR', language: 'ur' });
// PKR بارہ ہزار تین سو پینتالیس روپے پچاس پیسے صرف

A cheque names both halves even when one is zero — toCheque(0.3, …) says "zero dollars and thirty cents only", where toMoney reads it as a person would: "thirty cents".

Arabic, properly

Three rules, and they are why a spellout that only knows the words gets invoices wrong:

// 1. Three to nine take the opposite gender of what they count.
toWords(3, { language: 'ar', gender: 'masculine' });   // ثلاثة
toWords(3, { language: 'ar', gender: 'feminine' });    // ثلاث

// 2. The counted noun changes form.
toMoney(2, { currency: 'AED', language: 'ar' });       // درهمان        ← a dual
toMoney(11, { currency: 'AED', language: 'ar' });      // أحد عشر درهمًا  ← the tamyīz
toMoney(100, { currency: 'AED', language: 'ar' });     // مئة درهم      ← genitive singular
toMoney(200, { currency: 'AED', language: 'ar' });     // مئتا درهم     ← the construct state

// 3. The last number governs.
toMoney(1234, { currency: 'AED', language: 'ar' });    // …وأربعة وثلاثون درهمًا

hundred: 'مائة' switches to the other spelling throughout.

Counting your own nouns

Give Arabic the four forms once, and every number agrees:

const invoices = { singular: 'فاتورة', dual: 'فاتورتان', plural: 'فواتير', counted: 'فاتورةً', gender: 'feminine' };

counted(1, invoices, { language: 'ar' });      // فاتورة
counted(2, invoices, { language: 'ar' });      // فاتورتان
counted(3, invoices, { language: 'ar' });      // ثلاث فواتير
counted(11, invoices, { language: 'ar' });     // إحدى عشرة فاتورةً
counted(2000, invoices, { language: 'ar' });   // ألفا فاتورة

English and Urdu need two forms and use them the obvious way.

Ordinals

toOrdinal(21);                                 // twenty-first
toOrdinal(21, { language: 'ar' });             // الحادي والعشرون
toOrdinal(21, { language: 'ar', gender: 'feminine' }); // الحادية والعشرون
toOrdinal(7, { language: 'ur' });              // ساتواں

Money is never a float

Amounts are split and rounded on the digits:

toMoney(0.1 + 0.2, { currency: 'USD' });       // thirty cents
toMoney('1234.005', { currency: 'USD' });      // …and one cent
toMoney('9007199254740993.75', { currency: 'USD' }); // exact, past what a double holds

Pass a string for amounts that came from a database — '1234.50' is exact, 1234.50 is a float.

Your own currency, your own language

import { registerCurrency, registerLanguage } from '@devix-labs/number-words';

registerCurrency({
  code: 'BTC',
  subunits: 100_000_000,
  unit: { en: { singular: 'bitcoin', plural: 'bitcoin' } },
  fraction: { en: { singular: 'satoshi', plural: 'satoshis' } },
});

registerLanguage() takes anything implementing the Language interface — cardinal, ordinal, noun, money and a few words.

On the server

devix-labs/laravel-number-words is the same rules in PHP, with Blade directives and a facade. 796 shared vectors assert the two agree, so an invoice rendered server-side matches the one in the browser.

Updated 15 Sep 2026