n2words
Numbers to words. 50 languages, 72 regional variants. Arbitrary precision.
The Rust port of n2words, the
JavaScript library, with the same languages, the same words and the same
contracts, adapted to Rust: typed per-language option structs, Result
instead of exceptions, and BigInt-exact input through strings and
num-bigint.
Why n2words?
- Plain functions: each language is a module of free functions. No
builders to configure, no global state, no
unsafe. - Three forms: cardinal ("forty-two"), ordinal ("forty-second") and currency ("forty-two dollars and fifty cents").
- 50 languages, 72 regional variants: European, Asian, Middle Eastern,
African, and more, most reachable through a bare language tag (
n2words::de). - Exact: input as any integer, float, numeric string or
BigInt, so large values keep full precision; every form declares the largest value it spells and refuses anything past it instead of inventing words. - Tiny dependency footprint:
num-bigintandnum-traitsonly. - A CLI too:
cargo install n2wordsships ann2wordscommand whose flags are derived from each language's own options.
Quick start
use ;
assert_eq!;
assert_eq!;
# Ok::
Forms
| Form | Function | Example |
|---|---|---|
| Cardinal | to_cardinal(42) |
"forty-two" |
| Ordinal | to_ordinal(42) |
"forty-second" |
| Currency | to_currency(42.50) |
"forty-two dollars and fifty cents" |
use ;
assert_eq!;
assert_eq!;
assert_eq!;
# Ok::
Currency 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::en takes the
currency explicitly while a region-qualified module has its own default:
use ;
assert_eq!;
assert_eq!;
# Ok::
A language can name any currency it has words for, not just its own default,
and asking for one it lacks is an Error::UnsupportedCurrency, never a guess:
use ;
assert_eq!;
let eur = CurrencyOptions ;
assert_eq!;
assert!;
# Ok::
Each language implements one or more forms; LANGUAGES.md lists per-language coverage, ceilings and options.
Range: each form spells values up to the largest scale word it knows, then returns
Error::OutOfRangerather than inventing vocabulary. The ceiling varies by language and form (es_escardinals reach 1030 − 1, ordinals only 109 − 1) and is exported as a constant (en_us::CARDINAL_MAX). Cardinal and currency accept negatives and decimals; ordinal is positive integers only.
Input
Every function takes impl Into<Number>: any primitive integer, f64/f32,
a numeric &str/String (arbitrary precision, scientific notation accepted),
or a num_bigint::BigInt/BigUint:
use en;
assert_eq!;
assert_eq!;
assert_eq!;
# Ok::
Options
A form with options has a to_<form>_with(value, &options) variant taking a
struct whose Default is the language's own defaults:
use ;
assert_eq!;
let informal = CardinalOptions ;
assert_eq!;
# Ok::
Enum-valued options are Rust enums (Gender::Feminine, Currency::EUR), so
an invalid value is a compile error rather than a runtime one. The dynamic API
below and the CLI address the same options by name.
Picking a language at runtime
language(code) looks an entry point up by BCP 47 code (any casing, - or
_) and exposes the same conversions through string-keyed options, with each
form's ceiling and option metadata attached:
use ;
let lang = language.expect;
assert_eq!;
assert_eq!;
for in lang.forms
# Ok::
CLI
--lang takes any entry point the crate exports, 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 comes from
the language and form you selected, so the CLI offers exactly what they
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.
With no values it reads stdin, one per line, and --json emits one record per
line ({input, output, lang, form, options}, or {input, error}):
|
|
Exit codes: 0 success, 1 a usage error (unknown flag, unknown language,
missing --lang), 2 at least one value could not be converted. A bad line
in a pipe doesn't stop the rest.
Supported languages
See LANGUAGES.md for the complete list with codes, ceilings and options.
Highlights: Arabic, Chinese (Simplified/Traditional), English, French, German, Hindi, Japanese, Korean, Portuguese, Russian, Spanish, and many more.
Relation to the JavaScript library
Same languages, same test fixtures, same words. What changed is what Rust
changes: toCardinal(value, options?) became to_cardinal(value) plus
to_cardinal_with(value, &options); TypeError/RangeError became the
Error enum; cardinalMax became CARDINAL_MAX: Max; option names are
snake_case (hundred_pairing). The fixtures in tests/fixtures are imported
from the JavaScript repository verbatim, so the two libraries are checked
against the same expectations.
Contributing
Bug reports, new languages and corrections from native speakers are welcome. See CONTRIBUTING.md, and docs/porting.md for how a language file is structured.
License
MIT © Wael TELLAT & contributors