n2words-anycurrency
v6.1.2
Published
Convert numbers to words in 50 languages (72 regional variants) with zero dependencies. Supports BigInt, decimals, and browser/Node.js environments. Fork of n2words: every language can name any ISO 4217 currency via an explicit Intl.DisplayNames fallback.
Maintainers
Readme
n2words-anycurrency
Numbers to words. 50 languages, 72 regional variants. Zero dependencies.
Fork of
n2wordswith one deliberate divergence: every language can name any ISO 4217 currency. Upstream throwsRangeErrorfor a currency a language has no native words for; this fork falls back toIntl.DisplayNamesin the quoting language plus home-language subunit words (see docs/currency-vocab.md).
Try it live → — an interactive demo running this library in your browser.
Why n2words-anycurrency?
- Pure Functions — Each language exports standalone functions. No classes, no configuration, no side effects.
- Tree-Shakeable — Import only what you need. Unused exports are eliminated by modern bundlers.
- Tiny Bundles — ~2.5-3.5 KB gzipped per language with all three forms, less per form. No bloat.
- Multiple Forms — Cardinal ("forty-two"), ordinal ("forty-second"), and currency ("forty-two dollars")
- 50 Languages, 72 Regional Variants — European, Asian, Middle Eastern, African, and more — most importable via a single bare-tag code (
n2words-anycurrency/de), no region required - Zero Dependencies — Works everywhere: Node.js, browsers, Deno, Bun
- BigInt Support — Accepts
bigint(and numeric-string) input, so large values keep full precision - Type-Safe — Full TypeScript support with generated
.d.tsdeclarations
Quick Start
npm install n2words-anycurrencyimport { toCardinal } from 'n2words-anycurrency/en'
import { toCardinal as es } from 'n2words-anycurrency/es'
toCardinal(42) // 'forty-two'
es(42) // 'cuarenta y dos'Forms
n2words-anycurrency converts numbers to words in multiple forms:
| Form | Function | Example |
| -------- | ------------------------------------- | ----------------------------------- |
| Cardinal | toCardinal(42) | "forty-two" |
| Ordinal | toOrdinal(42) | "forty-second" |
| Currency | toCurrency(42.50, { currency }) | "forty-two dollars and fifty cents" |
import { toCardinal, toOrdinal, toCurrency } from 'n2words-anycurrency/en'
toCardinal(1234) // 'one thousand two hundred thirty-four'
toOrdinal(1234) // 'one thousand two hundred thirty-fourth'
toCurrency(1234.56, { currency: 'USD' }) // 'one thousand two hundred thirty-four dollars and fifty-six cents'toCurrency is the one form a bare tag won't guess at. en names a language,
and a default currency belongs to a country, so n2words-anycurrency/en requires the
currency to be named. Import the region-qualified code when you want its
default:
import { toCurrency } from 'n2words-anycurrency/en'
import { toCurrency as usd } from 'n2words-anycurrency/en-US'
toCurrency(42.50) // throws TypeError — en is a language, not a locale
toCurrency(42.50, { currency: 'GBP' }) // 'forty-two pounds and fifty pence'
usd(42.50) // 'forty-two dollars and fifty cents'A currency-supporting language can also name an amount in a currency other than its own
default, via the currency option — any ISO 4217 code is accepted. A language first
uses its own native currency words (so existing outputs are unchanged); past those it
falls back to Intl.DisplayNames in the quoting language for the major unit plus
home-language subunit words. Only a genuinely unknown code — or a subunit with no words
yet while the amount has a nonzero minor part — throws RangeError.
pt-BR is one of the few languages
with no bare-tag entry point (Brazilian and European Portuguese use different numbering
systems, not just different currencies — see LANGUAGES.md), so it's
imported by its full code:
import { toCurrency } from 'n2words-anycurrency/pt-BR'
toCurrency(42.50) // 'quarenta e dois reais e cinquenta centavos' (BRL, the default)
toCurrency(42.50, { currency: 'EUR' }) // 'quarenta e dois euros e cinquenta centavos'
toCurrency(42.50, { currency: 'CAD' }) // fallback: DisplayNames major + home minorEvery currency-supporting language still exports its default, readable at runtime:
import { currencyDefaults, currencyValues } from 'n2words-anycurrency/pt-BR'
currencyDefaults.currency // 'BRL'
currencyValues // {} — open set on purpose (any ISO 4217 code accepted)This generalizes: every form that takes options exports its defaults
(cardinalDefaults, ordinalDefaults, currencyDefaults), and any option with a fixed set
of allowed values — gender, but no longer currency — also exports that set as <form>Values. Boolean
and free-string options have no such set, so they have no Values entry.
LANGUAGES.md lists every option, type and default per
language.
Each language implements one or more of these forms — see LANGUAGES.md for per-language coverage.
Range: each form spells values up to the largest scale word it knows, then throws a
RangeErrorrather than inventing vocabulary — and the ceiling varies by language and form (e.g.es-EScardinals reach 10^30 − 1, ordinals only 10^9 − 1). Cardinal and currency accept negatives and decimals; ordinal is positive integers only.
Usage
ESM (Node.js, modern bundlers):
import { toCardinal } from 'n2words-anycurrency/de' // Single form, bare-tag entry point
import { toCardinal, toOrdinal } from 'n2words-anycurrency/de' // Multiple forms
import { toCardinal as fr } from 'n2words-anycurrency/fr' // Aliased importMost languages are imported this way — a bare BCP 47 primary subtag
(n2words-anycurrency/de, n2words-anycurrency/en, n2words-anycurrency/fr, ...), no region required. Each bare
tag is a documented alias for one specific regional/script variant (see
LANGUAGES.md's "Languages" table for the full list and each
one's default). Import the full region/script-qualified code instead when you
need a specific variant (n2words-anycurrency/en-GB, n2words-anycurrency/fr-BE) or when the
language has no single safe default — a handful of languages whose variants
genuinely diverge in script or core numbering grammar (Chinese, Portuguese,
Serbian, Amharic) require the full code:
import { toCardinal } from 'n2words-anycurrency/zh-Hans-CN'A bare tag forwards toCardinal and toOrdinal untouched — they're the same
functions its target exports. The one difference is toCurrency, which
requires an explicit currency because a language tag carries no country to
take a default from (see
docs/bare-tag-aliases.md).
Browser (CDN):
<!-- ESM (recommended) -->
<script type="module">
import { toCardinal } from 'https://cdn.jsdelivr.net/npm/n2words-anycurrency/dist/en.js'
console.log(toCardinal(42)) // 'forty-two'
</script>
<!-- UMD (legacy script tags) -->
<script src="https://cdn.jsdelivr.net/npm/n2words-anycurrency/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'
// `en` is a language, so currency must be named — or load dist/en-US.umd.js
// and call n2words.currency.enUS(42.50) for a locale's own default.
</script>dist/{code}.js carries all three forms. A page that needs only one can fetch
just that form from dist/{code}/{form}.js — cardinal, ordinal or
currency:
<script type="module">
import { toCurrency } from 'https://cdn.jsdelivr.net/npm/n2words-anycurrency/dist/en-US/currency.js'
console.log(toCurrency(42.50)) // 'forty-two dollars and fifty cents'
</script>Under half the bytes for cardinal or ordinal, since the other two forms and
everything only they reach are never in the file. Currency is the heaviest of
the three: it carries the shared currency-name matrix plus the any-ISO fallback, so en-US can quote any
ISO 4217 currency, not just USD.
|en-US|bytes|
|-------|-----|
|dist/en-US.js (all three forms)|9,227|
|dist/en-US/cardinal.js|4,293|
|dist/en-US/ordinal.js|3,815|
|dist/en-US/currency.js|5,258|
This is for CDN consumers only. If you install from npm and use a bundler,
import { toCurrency } from 'n2words-anycurrency/en-US' already drops the forms you don't
import — the package is sideEffects-free and each form is an independent
export, so there is nothing to opt into.
See LANGUAGES.md for all language codes and available forms.
CLI
The package ships an n2words-anycurrency command, so a number can be spelled without writing any code:
npx n2words-anycurrency 42 --lang en # forty-two
npx n2words-anycurrency 42 -l fr-FR --ordinal # quarante-deuxième
npx n2words-anycurrency 42.50 -l en-US --currency EUR # forty-two euro and fifty cents
npx n2words-anycurrency 101 -l es-ES --gender feminine # ciento una--lang takes any entry point the library exports — 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.
Options are the language's own. Every flag past the built-ins is derived at runtime from
the module you selected, so the CLI offers exactly what that language and form declare —
--gender for Spanish, --formal for Chinese, --and/--no-and for English currency.
Booleans also accept --flag=false, and --option key=value reaches any option by its
declared name.
Two commands make the whole library explorable:
npx n2words-anycurrency --list # every entry point, its kind, and the forms it exports
npx n2words-anycurrency --help --lang es-ES # es-ES's forms, their ceilings, and its optionsIt composes in pipelines. With no values it reads stdin, one per line, streaming:
printf '1\n2\n3\n' | npx n2words-anycurrency -l de
seq 1 100 | npx n2words-anycurrency -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. Values are read as text and passed
through untouched, so precision is never lost — n2words-anycurrency 12345678901234567890 -l en is
exact.
Exit codes: 0 success, 1 a usage error (unknown flag, unknown language, missing
--lang), 2 at least one value could not be converted — an out-of-range number, or an
option value the language rejects. A bad line in a pipe doesn't stop the rest.
Supported Languages
See LANGUAGES.md for the complete list with codes and options.
Highlights: Arabic, Chinese (Simplified/Traditional), English, French, German, Hindi, Japanese, Korean, Portuguese, Russian, Spanish, and many more.
Compatibility
- Node.js: 22+
- Browsers: Chrome 67+, Firefox 68+, Safari 14+, Edge 79+ — any browser with BigInt support
- Runtimes: Deno, Bun, Cloudflare Workers
Requires BigInt support (cannot be polyfilled).
Performance
n2words-anycurrency is optimized for both size and speed:
- ~2.5-3.5 KB gzipped per language, all three forms included
- Individual language imports enable tree-shaking
- No runtime dependencies
- BigInt modulo operations (no string manipulation)
- Pure functions with no shared state
- Minimal memory allocation per conversion
Run npm run bench to measure on your hardware.
Contributing
We welcome contributions! Add a new language or improve existing ones:
npm run lang:add -- <code> # Scaffold a new language (BCP 47 code)
npm test # Run full test suiteAlso welcome: bug reports, feature requests, and documentation improvements.
- Contributing Guide — How to contribute and add languages
- Code of Conduct — Community standards
License
MIT © Wael TELLAT, Tyler Vigario & contributors
