Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ jspm_packages

# Optional REPL history
.node_repl_history

# Playground build output (copied from index.js + languages/)
site/lib/

# Netlify local state
.netlify/
7 changes: 5 additions & 2 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Server (API) - repo only, not published to npm
server/
# Archived code and the playground site - repo only, not published to npm
archive/
site/
scripts/
netlify.toml

# Tests
test/
Expand Down
21 changes: 14 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,30 @@

## Overview

`numberstring` is a JavaScript library that converts numbers to their English word representation. It supports numbers from 0 to `Number.MAX_SAFE_INTEGER` (~9 quadrillion).
`numberstring` is a zero-dependency JavaScript library that converts numbers to words in 22 languages, with ordinals, decimals, currency, fractions, Roman numerals, and BigInt support up to 10^36. A static playground in `site/` is deployed to Netlify.

## Key Functions

### Main Export: `numberstring(n, options)`
- Converts a number to English words
- Converts a number to words; forgiving: accepts integers, negatives, decimals, numeric strings, BigInt
- Returns `string` on success, `false` on invalid input
- Options: `{ cap: 'title'|'upper'|'lower', punc: '!'|'?'|'.' }`
- Options: `{ cap: 'title'|'upper'|'lower', punc: '!'|'?'|'.', lang: 'es'|'fr'|..., point: 'point' }`

### Named Exports
- `ordinal`, `decimal`, `currency`, `roman`, `parse`, `negative`, `fraction`, `year`, `telephone`, `percent`, `toWords`
- `comma(n)` - Format number with comma separators
- `group(n)` - Get magnitude group (0=ones, 1=thousands, 2=millions, etc.)
- One named export per language (`spanish`, `french`, ...)

## Usage Examples

```javascript
import numberstring, { comma, group } from 'numberstring';

numberstring(42); // 'forty-two'
numberstring(1000000); // 'one million'
numberstring(-3.14); // 'negative three point one four'
numberstring('1000000'); // 'one million'
numberstring(42, { lang: 'es' }); // 'cuarenta y dos'
numberstring(100, { cap: 'title' }); // 'One Hundred'
numberstring(50, { punc: '!' }); // 'fifty!'

Expand All @@ -31,8 +35,8 @@ group(1000000); // 2 (millions)

## Important Notes

- Only accepts non-negative integers within safe integer range
- Returns `false` for: NaN, Infinity, negative numbers, strings, objects
- Returns `false` for: NaN, Infinity, non-numeric strings, objects, values beyond 10^36
- Language modules handle non-negative integers only; negatives/decimals with a non-English `lang` return `false`
- Hyphenates compound numbers (e.g., "forty-two", "ninety-nine")
- This is an ESM-only package (use `import`, not `require`)

Expand All @@ -46,7 +50,10 @@ npm run test:coverage # Run with coverage

## Code Architecture

- `index.js` - Single file containing all logic
- `index.js` - English core and all public helpers
- `languages/*.js` - one module per language; `test/languages.test.js` is the per-language spot-check table
- `site/` - playground; `scripts/build-site.js` stages the library into `site/lib/`
- `archive/` - unmaintained code (old Express server), excluded from tests and lint
- Pure functions, no side effects
- Frozen arrays for immutable word lists
- Full JSDoc type documentation
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.0] - 2026-10-02

### Added

- **Forgiving default export** - `numberstring()` now accepts negatives, decimals, numeric strings, and a `lang` option, delegating to `negative()`, `decimal()`, and `toWords()`. Invalid input still returns `false`.
- **Playground** - Static site in `site/` deployed to Netlify. Type a number, see it in 22 languages plus ordinal, Roman, year, currency, and more.
- **Per-language spot-check tests** - `test/languages.test.js` locks in tricky numbers (21, 71, 80, 91, 100, 101, 1000, 1001, 2000, 21000, 1M, 2M, 21M) for all 22 languages.
- **TypeScript declarations** - `index.d.ts` covering the default export, every helper, options, and the language functions.
- Numbers above `Number.MAX_SAFE_INTEGER` (e.g. `1e21`) are widened to BigInt and converted instead of returning `false`.
- Open Graph and Twitter card tags on the playground.
- `npm run site` and `npm run site:build` scripts.

### Fixed

- Spanish: `100` is now `cien` (was `ciento`); `uno` apocopates before scale words (`un millón`, `veintiún mil`, `ciento un millones`).
- French: `million`/`milliard` pluralize (`deux millions`); `cents` and `quatre-vingts` drop the `s` before `mille` (`deux cent mille`).
- Italian: `-uno` elides before `mila` (`ventunmila`).
- Japanese: `一千` is kept after a higher scale word (`二万一千`, was `二万千`).
- Icelandic: gender agreement for 1–4 before `hundrað`/`þúsund` (neuter: `tvö þúsund`), `milljón` (feminine: `ein milljón`, `tvær milljónir`), and singular after numbers ending in 1 (`tuttugu og ein milljón`).
- `decimal(1e21)` returned `"one"`; exponent-form integers are now expanded, exponent-form fractions return `false`.
- `decimal()` kept only 15 digits of precision for long integer parts in strings.
- `parse('nineteen eighty-four')` returned `103`; adjacent simple words other than tens + ones now return `false`.

### Changed

- The Express REST server moved to `archive/server/` and is no longer tested, linted, or maintained. The playground supersedes it.
- Removed `express` and `supertest` from devDependencies.

## [1.0.1] - 2026-02-08

### Added
Expand Down
15 changes: 13 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ npm install # Install dev dependencies
npm test # Run Vitest tests
npm run lint # Run ESLint
npm run test:coverage # Run tests with coverage report
npm run site # Build + serve the playground at http://localhost:8080
```

## Code Style

- ES2022+ syntax (const/let, arrow functions, template literals)
- ESM modules only (`import`/`export`)
- Full JSDoc documentation
- 100% test coverage target
- Keep coverage at or above 90% (current level); 100% on functions

## Architecture

Expand All @@ -33,7 +34,7 @@ Key constants:
## When Making Changes

1. **ALWAYS run lint and tests before committing**: `npm run lint && npm test`
2. Maintain 100% coverage: `npm run test:coverage`
2. Keep coverage from dropping: `npm run test:coverage`
3. Update CHANGELOG.md for any user-facing changes
4. Preserve the fun flair (ASCII art header, tagline)

Expand All @@ -44,6 +45,14 @@ Key constants:
- Fix valid issues before pushing new commits
- Use `gh pr view <number> --comments` to fetch PR comments

## Layout

- `index.js` - core English conversion plus all public helpers; `numberstring()` is forgiving and delegates to `negative()`, `decimal()`, `toWords()`
- `languages/` - one module per language, cardinals only, non-negative integers only
- `test/languages.test.js` - per-language spot-check table; update expectations when fixing a language
- `site/` - static playground deployed to Netlify (`netlify.toml`); `scripts/build-site.js` copies the library into `site/lib/`
- `archive/server/` - old Express API, unmaintained, excluded from tests and lint; do not extend it

## Supported Languages

- English (default)
Expand All @@ -55,6 +64,7 @@ Key constants:
- Hindi (`hi`, `hindi`, `हिन्दी`)
- Russian (`ru`, `russian`, `русский`)
- Portuguese (`pt`, `portuguese`, `português`)
- Plus Japanese, Korean, Arabic, Italian, Dutch, Turkish, Polish, Swedish, Indonesian, Thai, Norwegian, Finnish, Icelandic (22 total)

## Features

Expand All @@ -66,6 +76,7 @@ Key constants:
- Roman numerals (42 → "XLII")
- Negative numbers
- BigInt support up to 10^36
- Forgiving input: `numberstring(-3.14)`, `numberstring('42')`, `numberstring(42, { lang: 'de' })` all work; invalid input returns `false`

---

Expand Down
55 changes: 22 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@

Transform any number into beautiful words. From `42` to `"forty-two"`, from `1000000` to `"one million"`. Supports **22 languages**, ordinals, currency, Roman numerals, and more!

**Try it:** [numberstring.netlify.app](https://numberstring.netlify.app) — type a number, see it in 22 languages. Runs entirely in your browser.

## Why numberstring?

- **Zero dependencies** - Lightweight and fast
- **22 languages** - English, Spanish, French, German, Danish, Chinese, Hindi, Russian, Portuguese, Japanese, Korean, Arabic, Italian, Dutch, Turkish, Polish, Swedish, Indonesian, Thai, Norwegian, Finnish, Icelandic
- **Huge range** - Supports 0 to decillions (10^36) with BigInt
- **Feature-rich** - Ordinals, decimals, currency, fractions, years, phone numbers
- **Roman numerals** - Convert to and from Roman numerals
- **Well tested** - 265 tests with 90%+ coverage
- **Modern ES modules** - Tree-shakeable, TypeScript-friendly
- **Forgiving input** - Integers, negatives, decimals, numeric strings, BigInt. It just works
- **Well tested** - 660+ tests with 90%+ coverage, including per-language spot checks
- **Modern ES modules** - Tree-shakeable, with bundled TypeScript declarations

## Installation

Expand All @@ -37,6 +40,9 @@ import numberstring from 'numberstring';
numberstring(42); // 'forty-two'
numberstring(1000000); // 'one million'
numberstring(10n ** 18n); // 'one quintillion' (BigInt!)
numberstring(-3.14); // 'negative three point one four'
numberstring('1000'); // 'one thousand'
numberstring(42, { lang: 'es' }); // 'cuarenta y dos'
numberstring(123, { cap: 'title' }); // 'One Hundred Twenty-Three'
```

Expand All @@ -46,14 +52,21 @@ numberstring(123, { cap: 'title' }); // 'One Hundred Twenty-Three'

#### `numberstring(n, [options])`

Convert a number to English words.
Convert a number to words. Forgiving by design: accepts integers, negatives, decimals, numeric strings, and BigInt, and honors `lang`. Returns `false` for anything it cannot say.

```javascript
numberstring(42); // 'forty-two'
numberstring(-7); // 'negative seven'
numberstring(2.5); // 'two point five'
numberstring('1000000'); // 'one million'
numberstring(42, { lang: 'fr' }); // 'quarante-deux'
numberstring(100, { cap: 'title' }); // 'One Hundred'
numberstring(100, { punc: '!' }); // 'one hundred!'
numberstring('abc'); // false
```

Negatives and decimals are English-only; with another `lang` they return `false` rather than falling back to English.

#### `ordinal(n, [options])`

Convert to ordinal words (first, second, etc.).
Expand Down Expand Up @@ -262,7 +275,7 @@ Languages are modular! To add a new language:
|--------|------|-------------|
| `cap` | `string` | Capitalization: `'title'`, `'upper'`, or `'lower'` |
| `punc` | `string` | Punctuation: `'!'`, `'?'`, or `'.'` |
| `lang` | `string` | Language code for `toWords()` |
| `lang` | `string` | Language code for `numberstring()` and `toWords()` |
| `point` | `string` | Word for decimal point (default: `'point'`) |
| `lower` | `boolean` | Lowercase Roman numerals |

Expand All @@ -283,40 +296,15 @@ Languages are modular! To add a new language:
| **Nonillions** | 10^30 | `five nonillion` *(BigInt)* |
| **Decillions** | 10^33 | `five decillion` *(BigInt)* |

## REST API Server
## Playground

numberstring includes a ready-to-use REST API server!
The playground at [numberstring.netlify.app](https://numberstring.netlify.app) is a single static page in `site/` that imports the library directly. No framework, no backend.

```bash
cd server
npm install
npm start
# Server running at http://localhost:3456
npm run site # stage the library into site/lib and serve at http://localhost:8080
```

### Endpoints

| Endpoint | Description | Example |
|----------|-------------|---------|
| `GET /convert/:n` | Number to words | `/convert/42` → `"forty-two"` |
| `GET /convert/:n?lang=es` | Multi-language | `/convert/42?lang=es` → `"cuarenta y dos"` |
| `GET /ordinal/:n` | Ordinal words | `/ordinal/3` → `"third"` |
| `GET /decimal/:n` | Decimal words | `/decimal/3.14` → `"three point one four"` |
| `GET /currency/:amt` | Currency words | `/currency/$99.99` → `"ninety-nine dollars..."` |
| `GET /roman/:n` | Roman numerals | `/roman/2024` → `"MMXXIV"` |
| `GET /parse/:words` | Words to number | `/parse/forty-two` → `42` |
| `GET /comma/:n` | Format with commas | `/comma/1000000` → `"1,000,000"` |
| `GET /languages` | List languages | Returns supported language codes |

### Example

```bash
curl http://localhost:3456/convert/1000000000000000
# {"input":"1000000000000000","output":"one quadrillion","lang":"en"}

curl "http://localhost:3456/convert/42?lang=fr"
# {"input":"42","output":"quarante-deux","lang":"fr"}
```
The former Express REST server lives in `archive/server/`, unmaintained.

## Development

Expand All @@ -325,6 +313,7 @@ npm install # Install dependencies
npm test # Run tests
npm run lint # Run linter
npm run test:coverage # Test with coverage
npm run site # Run the playground locally
```

## Contributing
Expand Down
10 changes: 10 additions & 0 deletions archive/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Archive

Code kept for reference but no longer maintained, tested, linted, or published.

## server/

An Express REST API wrapping numberstring. Never deployed. Superseded by the
static playground in `site/`, which runs the library directly in the browser
with no backend. If a hosted API is ever wanted, these handlers port directly
to Netlify Functions. Slated for deletion.
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 5 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export default [
}
},
{
ignores: ['node_modules/', 'coverage/']
files: ['site/**/*.js'],
languageOptions: { globals: { document: 'readonly', window: 'readonly', location: 'readonly', history: 'readonly' } }
},
{
ignores: ['node_modules/', 'coverage/', 'archive/', 'site/lib/']
}
];
Loading
Loading