Docs
Enough to be productive in five minutes. The full reference lives in the README, LANGUAGES.md and the docs/ directory.
Install
npm install n2words
Zero dependencies. Node.js 22+, or any browser with BigInt support.
Command line
The package ships an n2words command, so a number can be
spelled without writing any code. npx runs it without
installing anything first.
npx n2words 42 --lang en # forty-two
npx n2words 42 -l fr-FR --ordinal # quarante-deuxième
npx n2words 42.50 -l en-US --currency EUR # forty-two euro and fifty cents
--lang takes any entry point this site lists — a bare tag
(en, de) or a region/script-qualified code
(en-GB, zh-Hans-CN), in any casing.
--cardinal (the default), --ordinal and
--currency pick the form; --currency <CODE>
names the currency and selects the form in one flag, while
--form currency keeps the locale's own default.
The options are the language's own. Every flag past the
built-ins is derived at runtime from the module you selected — from the
same <form>Defaults and <form>Values
declarations the demo builds its options panel from — so the CLI offers
exactly what that language and form accept, and nothing else:
npx n2words --list # every entry point and the forms it exports
npx n2words --help -l es-ES # es-ES's forms, their ceilings, and its options
npx n2words 101 -l es-ES --gender feminine
npx n2words 1500 -l en --hundred-pairing
With no values it reads stdin, one per line, streaming — so it composes in a pipeline. Values are read as text and passed through untouched, so precision is never lost.
printf '1\n2\n3\n' | npx n2words -l de
seq 1 100 | npx n2words -l fr --json > numbers.jsonl
--json emits one record per line
({input, output, lang, form, options}), with failures
reported as {input, error} rather than on stderr. Exit codes
are part of the contract: 0 success, 1 a usage
error, 2 at least one value the library refused. A bad line
in a pipe doesn't stop the rest.
The three forms
Each language exports one, two or all three of these as plain functions — no classes, no configuration, no shared state.
import { toCardinal, toOrdinal, toCurrency } from 'n2words/en-US'
toCardinal(1234) // 'one thousand two hundred thirty-four'
toOrdinal(1234) // 'one thousand two hundred thirty-fourth'
toCurrency(42.50) // 'forty-two dollars and fifty cents'
Cardinal and currency accept negatives and decimals; ordinal is positive
integers only. Values can be a number, a numeric
string, or a bigint — pass a string or a bigint
when the value is larger than Number.MAX_SAFE_INTEGER and it
keeps full precision.
Entry points
Import one language, not the library: there is no barrel file, so you only ever ship the languages you name. Most languages resolve from a bare BCP 47 primary subtag.
import { toCardinal } from 'n2words/de' // bare tag — the primary way
import { toCardinal } from 'n2words/en-GB' // a specific variant
import { toCardinal } from 'n2words/zh-Hans-CN' // zh has no bare tag
import { toCardinal as fr } from 'n2words/fr' // rename when mixing
A bare tag is a documented alias for one specific variant. Chinese, Portuguese, Serbian and Amharic have none — their variants diverge in script or core numbering grammar, not just vocabulary — so they're always imported by full code.
When does the region matter?
Less often than 72 variants suggests. A region changes two independent things, and usually only the second: which words the numbers get, and which currency the amount defaults to. English has 16 regions but only four spellings —
import { toCardinal as us } from 'n2words/en-US'
import { toCardinal as gb } from 'n2words/en-GB'
import { toCardinal as ind } from 'n2words/en-IN'
import { toCardinal as au } from 'n2words/en-AU'
us(101) // 'one hundred one'
gb(101) // 'one hundred and one'
ind(12345678) // 'one crore twenty-three lakh forty-five thousand six hundred and seventy-eight'
au(101) // 'one hundred and one' — identical to en-GB; only its default currency differs (AUD)
The languages table groups every language's regions by the words they actually produce, so you can see at a glance whether a region is a real spelling or only a currency default.
Options
A form that takes options also exports its defaults, and any option with a fixed set of values exports that set — so the accepted values are readable at runtime and narrowed to a literal union in TypeScript, instead of being something you discover by catching an error.
import { toCardinal, cardinalDefaults } from 'n2words/en-US'
cardinalDefaults // { hundredPairing: false, and: false }
toCardinal(1500) // 'one thousand five hundred'
toCardinal(1500, { hundredPairing: true }) // 'fifteen hundred'
toCardinal(101, { and: true }) // 'one hundred and one'
An unknown key or a wrong-typed value throws a TypeError; a
value outside a declared set throws a RangeError. The
demo renders each language's real options panel, and
LANGUAGES.md
lists every option, type and default.
Currency
A bare tag names a language, and a default currency belongs to a
country — so toCurrency is the one form a bare tag
won't guess at.
import { toCurrency } from 'n2words/en'
import { toCurrency as usd } from 'n2words/en-US'
// `en` is a language, so it has no default currency to fall back on.
toCurrency(42.50) // TypeError
toCurrency(42.50, { currency: 'GBP' }) // 'forty-two pounds and fifty pence'
usd(42.50) // 'forty-two dollars and fifty cents'
Any language can name a currency it has words for, and the set it knows is
validated rather than guessed at — read it from currencyValues:
import { currencyDefaults, currencyValues } from 'n2words/pt-BR'
currencyDefaults.currency // 'BRL'
currencyValues.currency // ['BRL', 'USD', 'EUR', 'GBP', 'JPY']
Upgrading from v5? All three breaking changes are in toCurrency —
see the
v6 migration guide.
Range and errors
Each form spells values up to the largest scale word its language knows,
then throws a RangeError rather than inventing vocabulary.
The ceiling varies by language and by form — es-ES
cardinals reach 1030 − 1, its ordinals only 109 − 1.
For any input, a form returns a well-formed string or throws; it never
returns something malformed.
import { toCardinal } from 'n2words/en-US'
toCardinal(10n ** 65n) // 'one hundred vigintillion …'
toCardinal(10n ** 66n) // RangeError: the largest supported value is 10^66 - 1
The languages table lists each variant's range.
Browser and CDN
<!-- ESM (recommended) -->
<script type="module">
import { toCardinal } from 'https://cdn.jsdelivr.net/npm/n2words/dist/en.js'
console.log(toCardinal(42)) // 'forty-two'
</script>
<!-- UMD (legacy script tags) -->
<script src="https://cdn.jsdelivr.net/npm/n2words/dist/en.umd.js"></script>
<script>
n2words.en(42) // 'forty-two'
n2words.ordinal.en(42) // 'forty-second'
n2words.currency.en(42.50, { currency: 'USD' }) // 'forty-two dollars and fifty cents'
</script>
dist/{code}.js carries all three forms. A page that needs only
one can fetch just that form from dist/{code}/{form}.js — under
half the bytes for cardinal or ordinal. That's exactly what this site does:
the demo imports dist/{code}/{form}.js for whichever form you
selected.
<script type="module">
import { toCurrency } from 'https://cdn.jsdelivr.net/npm/n2words/dist/en-US/currency.js'
console.log(toCurrency(42.50)) // 'forty-two dollars and fifty cents'
</script>
Installing from npm with a bundler? There's nothing to opt into — the
package is sideEffects-free and each form is an independent
export, so importing one form drops the others.
TypeScript
Declarations ship with the package; no @types install. Option
values are literal unions, so a typo in a currency code or a gender fails
at compile time as well as at runtime.
import { toCurrency, type CurrencyOptions } from 'n2words/en-US'
toCurrency(42.50, { currency: 'GBP' }) // ok
toCurrency(42.50, { currency: 'XYZ' }) // compile error
Compatibility
- Node.js 22+
- Browsers Chrome 67+, Firefox 68+, Safari 14+, Edge 79+ — anything with BigInt
- Runtimes Deno, Bun, Cloudflare Workers
BigInt is required and cannot be polyfilled.
Going further
- README — the full reference
- LANGUAGES.md — every language, option and default
- docs/range-contract.md — how ceilings are derived and enforced
- docs/currency-vocab.md — the cross-language currency matrix
- CONTRIBUTING.md — adding a language
- Issues — bugs, wrong words, missing languages