Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fc05818
chore: PNG social preview, og regen script, repo metadata
brianfunk Oct 2, 2026
9e0082f
fix(site): restore exact ASCII art header from index.js
brianfunk Oct 6, 2026
1ffda3c
fix(site): stop per-line centering skewing the ASCII art
brianfunk Oct 6, 2026
dc61c6c
feat: vinculum notation in roman() up to 3,999,999,999
brianfunk Oct 6, 2026
892f55b
style(site): space out roman numerals so vinculum bars stay distinct
brianfunk Oct 6, 2026
6590cd2
feat: and option, nth(), compact(), fancy(), ancient numerals, formal…
brianfunk Oct 6, 2026
3448cef
fix: address Codex review on 1.2.0 features
brianfunk Oct 6, 2026
b4cde30
chore: drop mayan() and tally(); no system font coverage on macOS
brianfunk Oct 6, 2026
b3a7cdd
fix(site): show the fraction row for any denominator, not just up to …
brianfunk Oct 6, 2026
bc93a28
fix: compact() promotes sub-thousand values that round to 1000 into K
brianfunk Oct 6, 2026
c822d9e
feat: nato()/icao/military radio numerals, morse(), telephone oh option
brianfunk Oct 6, 2026
e3e601f
feat: scientific(), radix/binary/octal/hex, bytes(), clock(); emoji a…
brianfunk Oct 6, 2026
1d1bbd0
fix: Babylonian unit wedge is DIŠ (U+12079), not GESH2; nato() reads …
brianfunk Oct 6, 2026
3c2409f
feat: cap casing styles (camel, pascal, snake, kebab, constant, dot, …
brianfunk Oct 6, 2026
46abf5d
docs: full sweep for 1.2.0 (README at-a-glance table, AGENTS, CLAUDE,…
brianfunk Oct 6, 2026
ec0fcf6
chore: keep .netlify/ out of the npm package
brianfunk Oct 6, 2026
88a3a43
fix: nato() skips round folding on zero-padded input; bytes()/bits() …
brianfunk Oct 6, 2026
33a9b7a
site: single bytes row
brianfunk Oct 6, 2026
7a15e28
fix: case negative phrases once; integer rounding in bytes()/bits(); …
brianfunk Oct 6, 2026
82aee4c
feat: fancy('clock') maps each digit to a clock face; playground cloc…
brianfunk Oct 6, 2026
f6ec7da
feat: year() reads years beyond 9999 as cardinals, accepts BigInt
brianfunk Oct 6, 2026
68d2466
fix: comma()/group()/lang never throw; comma keeps decimals; add fuzz…
brianfunk Oct 6, 2026
a49d4c1
fix: clamp digits options to finite integers; formal Japanese zero is 零
brianfunk Oct 6, 2026
29f3f0c
test: non-finite digits falls back to the default
brianfunk Oct 6, 2026
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
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ Thumbs.db

# Dependencies
node_modules/

# Netlify local link state
.netlify/
25 changes: 19 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@
### Main Export: `numberstring(n, options)`
- 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: '!'|'?'|'.', lang: 'es'|'fr'|..., point: 'point' }`
- Options: `cap` (title, upper, lower, sentence, camel, pascal, snake, kebab, constant, dot), `punc` ('!' '?' '.'), `lang`, `point`, `and` (British), `formal` (zh/ja)

### 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`, ...)

English words: `ordinal`, `nth`, `decimal`, `fraction`, `percent`, `currency`, `year`, `telephone`, `negative`, `parse`, `toWords`

Spoken and coded: `nato` (aliases `icao`, `military`), `morse`

Notation: `compact`, `scientific`, `comma`, `group`, `binary`, `octal`, `hex`, `radix`, `bytes`, `bits`

Other numeral systems (`numerals.js`): `roman`, `greek`, `egyptian`, `babylonian`, `fancy`, `clock`

Languages (cardinals, non-negative integers): `spanish`, `french`, `german`, `danish`, `chinese`, `hindi`, `russian`, `portuguese`, `japanese`, `korean`, `arabic`, `italian`, `dutch`, `turkish`, `polish`, `swedish`, `indonesian`, `thai`, `norwegian`, `finnish`, `icelandic`. `chinese` and `japanese` accept `{ formal: true }`; `spanish` and `portuguese` accept `{ cap }`.

Constants: `CAP_STYLES`, `FANCY_STYLE_NAMES`

## Usage Examples

Expand Down Expand Up @@ -50,10 +58,15 @@ npm run test:coverage # Run with coverage

## Code Architecture

- `index.js` - English core and all public helpers
- `index.js` - English core and all public helpers (ordinal, nato, scientific, bytes, ...)
- `numerals.js` - roman-adjacent systems (egyptian, babylonian, greek), `fancy()` digit styles, `clock()`
- `languages/*.js` - one module per language; `test/languages.test.js` is the per-language spot-check table
- `test/extras.test.js` - tests for everything added in 1.2.0
- `index.d.ts` - hand-written types; check with `npx tsc --noEmit --strict index.d.ts`
- `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
- **Zero runtime dependencies.** Never add a package to `dependencies`.
- Only use Unicode blocks that macOS renders with system fonts (Mayan numerals and tally marks were dropped for this reason)
- Frozen arrays for immutable word lists
- Full JSDoc type documentation
36 changes: 35 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,41 @@ 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.2.0] - 2026-10-06

### Added

- **Casing styles** - `cap` now also accepts `sentence`, `camel`, `pascal`, `snake`, `kebab` (alias `hyphen`), `constant` (alias `screaming`), and `dot`: `numberstring(123, { cap: 'snake' })` → `one_hundred_twenty_three`. Exported as `CAP_STYLES`.
- **British `and` option** - `numberstring(123, { and: true })` → "one hundred and twenty-three", `numberstring(1001, { and: true })` → "one thousand and one". Also honored by `ordinal()`.
- **`nth(n)`** - Numeric ordinal suffix: `1st`, `22nd`, `113th`.
- **`compact(n, opt)`** - `1.5K`, `2.3B`, `1Sx`, with `digits` and `long` ("1.5 million") options.
- **`fancy(n, style)`** - Digits in Unicode styles: circled ④②, superscript ⁴², subscript, fullwidth, bold, doublestruck 𝟜𝟚, sans, monospace, keycap 4️⃣2️⃣, braille ⠼⠙⠃.
- **Alternative numeral systems** in `numerals.js`: `egyptian()` hieroglyphs (to 9,999,999), `babylonian()` base-60 cuneiform, `greek()` Ionic letters (to 9999). Mayan numerals and tally marks were tried and dropped: no system font on macOS.
- **Financial numerals** - `chinese(n, { formal: true })` → 壹仟零壹 (大写), `japanese(n, { formal: true })` → 壱千壱 (大字). Also via `toWords(n, { lang: 'zh', formal: true })`.
- **`nato(n)`** (aliases `icao`, `military`) - ICAO radiotelephony numerals: `1984` → "wun niner ait fower", `2500` → "too tousand fife hundred", `121.5` → "wun too wun decimal fife".
- **`morse(n)`** - International Morse code digits.
- `telephone(n, { oh: true })` says "oh" for zero.
- **`scientific(n)`** - Exact-mantissa scientific notation: `1984` → "1.984 × 10³", with `caret`, `e`, and `words` formats and a `digits` option.
- **`binary()`, `octal()`, `hex()`, `radix(n, base)`** - Other bases with `prefix`, `upper`, `pad` options.
- **`bytes(n)`** and **`bits(n)`** - `1.5 KB`, `1.5 KiB`, `1.5 Mb`, or "one point five kilobytes".
- **`clock(time)`** - Clock-face emoji for an hour or `H:MM`.
- `fancy()` accepts `emoji` as an alias for `keycap`, and a `clock` style that turns each digit into a clock face (814 → 🕗🕐🕓).
- `roman()` now supports vinculum notation above 3999 (a bar multiplies by 1000, two bars by a million), up to 3,999,999,999.
- Open Graph and Twitter card tags on the playground, with a PNG preview image (`site/og.png`, rendered from `site/og.svg`).
- `comma()` keeps decimals (`1,234,567.89`) and accepts numeric strings.
- Fuzz test (`test/fuzz.test.js`) proves every public function returns a value, never throws, for hostile inputs and options.
- `year()` accepts years beyond 9999 (read as cardinals) and BigInt.
- `bahasa` accepted as an alias for Indonesian.

### Fixed

- Playground: ASCII art header restored to the exact index.js block and no longer skewed by per-line centering; a malformed URL hash no longer breaks the page.
- Type declarations: per-language converters no longer advertise a `cap` option they ignore (use `toWords()` for that); Spanish and Portuguese keep it, Chinese and Japanese gain `formal`.

### Changed

- Still zero runtime dependencies.

## [1.1.0] - 2026-10-02

### Added
Expand All @@ -14,7 +49,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **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
Expand Down
22 changes: 12 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ Key constants:
## Layout

- `index.js` - core English conversion plus all public helpers; `numberstring()` is forgiving and delegates to `negative()`, `decimal()`, `toWords()`
- `numerals.js` - alternative numeral systems (egyptian, babylonian, greek) and `fancy()` Unicode digit styles; table-driven, re-exported from index.js. Only add Unicode blocks that macOS renders out of the box (Mayan numerals and tally marks did not)
- `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/`
- `site/` - static playground deployed to Netlify (`netlify.toml`); `scripts/build-site.js` copies the library into `site/lib/`. `og.png` is the social preview; after editing `og.svg` run `npm run site:og` to re-render it
- `archive/server/` - old Express API, unmaintained, excluded from tests and lint; do not extend it

## Supported Languages
Expand All @@ -68,15 +69,16 @@ Key constants:

## Features

- Number to words (cardinal)
- Ordinals (1st, 2nd, 3rd)
- Decimals (3.14 → "three point one four")
- Currency ($1.23 → "one dollar and twenty-three cents")
- Fractions (1/2 → "one half")
- 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`
Everything is exported from `index.js`; every function returns `string | false`.

- English words: `numberstring` (cardinal, forgiving input), `ordinal`, `nth`, `decimal`, `fraction`, `percent`, `currency`, `year`, `telephone` (`oh` option), `negative`, `parse` (words → number)
- 22 languages via `toWords(n, { lang })` or the named exports; `chinese`/`japanese` take `formal` for 大写/大字
- Spoken and coded: `nato` (`icao`, `military`), `morse`
- Notation: `compact` (1.5K), `scientific` (1.984 × 10³), `comma`, `binary`/`octal`/`hex`/`radix`, `bytes`/`bits`
- Other numeral systems: `roman` (vinculum above 3999), `greek`, `egyptian`, `babylonian`, `fancy` (circled, superscript, doublestruck, keycap/emoji, braille, ...), `clock`
- Options: `cap` casing styles (title, upper, lower, sentence, camel, pascal, snake, kebab, constant, dot), `punc`, `and` (British), `lang`, `point`, `formal`
- BigInt support up to 10^36; invalid input returns `false`
- **Zero runtime dependencies, always.** Never add a package to `dependencies`.

---

Expand Down
26 changes: 18 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,38 @@ We'd love help adding more languages! Here's how:

1. Create a new file in `languages/` (e.g., `pt.js` for Portuguese)
2. Follow the pattern in `languages/en.js`
3. Export a default function that converts numbers to words
4. Add your language to `languages/index.js`
5. Add tests in `test/index.test.js`
6. Update README.md with the new language
7. Submit a PR!
3. Export a default function that converts non-negative integers to words
4. Add your language and its aliases to `languages/index.js`, re-export it from `index.js`, and add it to the `toWords` switch
5. Add a row to the spot-check table in `test/languages.test.js` and a named export in `index.d.ts`
6. Update README.md (feature list, language table, direct exports) and CHANGELOG.md
7. Submit a PR against `dev`

### Pull Request Process

1. Fork the repo and create your branch from `main`
1. Fork the repo and create your branch from `dev` (PRs target `dev`; `master` is the release branch)
2. Run `npm install` to install dependencies
3. Make your changes
4. Run `npm test` to ensure tests pass
5. Run `npm run lint` to check code style
6. Update documentation if needed
7. Submit your PR!

### Adding a New Conversion

1. Add the function to `index.js` (or `numerals.js` for glyph-based systems) with JSDoc and an `@example`
2. Return `false` for input you cannot convert; never throw
3. Add it to the export block, `index.d.ts`, the README at-a-glance table and API section, and CHANGELOG.md
4. Add tests in `test/extras.test.js`
5. Add a row to the playground in `site/app.js` if it is worth seeing

### Code Style

- ES2022+ syntax (const/let, arrow functions, async/await)
- ES2022+ syntax (const/let, arrow functions, template literals)
- ESM modules only
- **Zero runtime dependencies.** Dev dependencies only.
- Add JSDoc comments for public functions
- Maintain test coverage
- Maintain test coverage (`npm run test:coverage`)
- Unicode output must render with macOS system fonts out of the box

## Code of Conduct

Expand Down
Loading
Loading