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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ 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.0.1] - 2026-02-08

### Added

- **13 New Languages** - Japanese (`ja`), Korean (`ko`), Arabic (`ar`), Italian (`it`), Dutch (`nl`), Turkish (`tr`), Polish (`pl`), Swedish (`sv`), Indonesian (`id`), Thai (`th`), Norwegian (`no`), Finnish (`fi`), Icelandic (`is`)
- Total language support now at 22 languages
- Full Scandinavian coverage: Danish, Swedish, Norwegian, Finnish, Icelandic
- All new languages support BigInt up to 10^36
- 49 new tests for all new languages (265 total)

## [1.0.0] - 2026-02-01

### Breaking Changes
Expand Down
58 changes: 45 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@

> Number One Way to Makes Words from Numbers

Transform any number into beautiful words. From `42` to `"forty-two"`, from `1000000` to `"one million"`. Supports **8 languages**, ordinals, currency, Roman numerals, and more!
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!

## Why numberstring?

- **Zero dependencies** - Lightweight and fast
- **9 languages** - English, Spanish, French, German, Danish, Chinese, Hindi, Russian, Portuguese
- **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** - 216 tests with 90%+ coverage
- **Well tested** - 265 tests with 90%+ coverage
- **Modern ES modules** - Tree-shakeable, TypeScript-friendly

## Installation
Expand Down Expand Up @@ -191,10 +191,10 @@ comma(1234567); // '1,234,567'

## Multi-Language Support

numberstring supports 9 languages! Each language is in a separate file for easy tree-shaking.
numberstring supports 22 languages! Each language is in a separate file for easy tree-shaking.

```javascript
import { toWords, spanish, french, german, danish, chinese, hindi, russian, portuguese } from 'numberstring';
import { toWords } from 'numberstring';

// Using toWords with lang option
toWords(42, { lang: 'es' }); // 'cuarenta y dos'
Expand All @@ -205,16 +205,48 @@ toWords(42, { lang: 'zh' }); // '四十二'
toWords(42, { lang: 'hi' }); // 'बयालीस'
toWords(42, { lang: 'ru' }); // 'сорок два'
toWords(42, { lang: 'pt' }); // 'quarenta e dois'

// Or use language functions directly
spanish(1000); // 'mil'
french(80); // 'quatre-vingts'
german(21); // 'einundzwanzig'
chinese(10000); // '一万'
russian(2000); // 'две тысячи'
portuguese(100); // 'cem'
toWords(42, { lang: 'ja' }); // '四十二'
toWords(42, { lang: 'ko' }); // '사십이'
toWords(42, { lang: 'ar' }); // 'اثنان وأربعون'
toWords(42, { lang: 'it' }); // 'quarantadue'
toWords(42, { lang: 'nl' }); // 'tweeënveertig'
toWords(42, { lang: 'tr' }); // 'kırk iki'
toWords(42, { lang: 'pl' }); // 'czterdzieści dwa'
toWords(42, { lang: 'sv' }); // 'fyrtiotvå'
toWords(42, { lang: 'id' }); // 'empat puluh dua'
toWords(42, { lang: 'th' }); // 'สี่สิบสอง'
toWords(42, { lang: 'no' }); // 'førtito'
toWords(42, { lang: 'fi' }); // 'neljäkymmentäkaksi'
toWords(42, { lang: 'is' }); // 'fjörutíu og tveir'
```

### Supported Languages

| Code | Language | Example (42) |
|------|----------|-------------|
| `en` | English | forty-two |
| `es` | Spanish | cuarenta y dos |
| `fr` | French | quarante-deux |
| `de` | German | zweiundvierzig |
| `da` | Danish | toogfyrre |
| `zh` | Chinese | 四十二 |
| `hi` | Hindi | बयालीस |
| `ru` | Russian | сорок два |
| `pt` | Portuguese | quarenta e dois |
| `ja` | Japanese | 四十二 |
| `ko` | Korean | 사십이 |
| `ar` | Arabic | اثنان وأربعون |
| `it` | Italian | quarantadue |
| `nl` | Dutch | tweeënveertig |
| `tr` | Turkish | kırk iki |
| `pl` | Polish | czterdzieści dwa |
| `sv` | Swedish | fyrtiotvå |
| `id` | Indonesian | empat puluh dua |
| `th` | Thai | สี่สิบสอง |
| `no` | Norwegian | førtito |
| `fi` | Finnish | neljäkymmentäkaksi |
| `is` | Icelandic | fjörutíu og tveir |

### Adding a New Language

Languages are modular! To add a new language:
Expand Down
47 changes: 43 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@

/**
* numberstring - Convert numbers to their word representation
* Supports English, Spanish, French, German, Danish, Mandarin Chinese, Hindi, Russian, and Portuguese
* Supports 22 languages including English, Spanish, French, German, Danish, Chinese, Hindi, Russian, Portuguese, Japanese, Korean, Arabic, Italian, Dutch, Turkish, Polish, Swedish, Indonesian, Thai, Norwegian, Finnish, and Icelandic
* @module numberstring
*/

// Import language functions
import { english, spanish, french, german, danish, chinese, hindi, russian, portuguese, LANGUAGES } from './languages/index.js';
import { english, spanish, french, german, danish, chinese, hindi, russian, portuguese, japanese, korean, arabic, italian, dutch, turkish, polish, swedish, indonesian, thai, norwegian, finnish, icelandic, LANGUAGES } from './languages/index.js';

// Re-export language functions
export { spanish, french, german, danish, chinese, hindi, russian, portuguese };
export { spanish, french, german, danish, chinese, hindi, russian, portuguese, japanese, korean, arabic, italian, dutch, turkish, polish, swedish, indonesian, thai, norwegian, finnish, icelandic };

// ============================================================================
// CONSTANTS
Expand Down Expand Up @@ -597,7 +597,7 @@ const percent = (pct, opt) => {
* Convert a number to words in a specified language
* @param {number|bigint} n - The number to convert
* @param {Object} [opt] - Options object
* @param {string} [opt.lang] - Language code: 'en', 'es', 'fr', 'de', 'da', 'zh', 'hi', 'ru'
* @param {string} [opt.lang] - Language code: 'en', 'es', 'fr', 'de', 'da', 'zh', 'hi', 'ru', 'pt', 'ja', 'ko', 'ar', 'it', 'nl', 'tr', 'pl', 'sv', 'id', 'th', 'no', 'fi', 'is'
* @returns {string|false} The word representation
*/
const toWords = (n, opt) => {
Expand Down Expand Up @@ -633,6 +633,45 @@ const toWords = (n, opt) => {
case 'portuguese':
result = portuguese(n);
break;
case 'japanese':
result = japanese(n);
break;
case 'korean':
result = korean(n);
break;
case 'arabic':
result = arabic(n);
break;
case 'italian':
result = italian(n);
break;
case 'dutch':
result = dutch(n);
break;
case 'turkish':
result = turkish(n);
break;
case 'polish':
result = polish(n);
break;
case 'swedish':
result = swedish(n);
break;
case 'indonesian':
result = indonesian(n);
break;
case 'thai':
result = thai(n);
break;
case 'norwegian':
result = norwegian(n);
break;
case 'finnish':
result = finnish(n);
break;
case 'icelandic':
result = icelandic(n);
break;
default:
result = english(n);
}
Expand Down
128 changes: 128 additions & 0 deletions languages/ar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* Arabic number-to-words converter
* Uses masculine default forms with standard group-of-3 pattern
* @module languages/ar
*/

const AR_ONES = Object.freeze(['', 'واحد', 'اثنان', 'ثلاثة', 'أربعة', 'خمسة', 'ستة', 'سبعة', 'ثمانية', 'تسعة']);
const AR_TEENS = Object.freeze(['عشرة', 'أحد عشر', 'اثنا عشر', 'ثلاثة عشر', 'أربعة عشر', 'خمسة عشر', 'ستة عشر', 'سبعة عشر', 'ثمانية عشر', 'تسعة عشر']);
const AR_TENS = Object.freeze(['', '', 'عشرون', 'ثلاثون', 'أربعون', 'خمسون', 'ستون', 'سبعون', 'ثمانون', 'تسعون']);
const AR_HUNDREDS = Object.freeze(['', 'مائة', 'مائتان', 'ثلاثمائة', 'أربعمائة', 'خمسمائة', 'ستمائة', 'سبعمائة', 'ثمانمائة', 'تسعمائة']);
const AR_ILLIONS = Object.freeze([
['', '', ''], // ones
['ألف', 'ألفان', 'آلاف'], // thousands
['مليون', 'مليونان', 'ملايين'], // millions
['مليار', 'ملياران', 'مليارات'], // billions
['تريليون', 'تريليونان', 'تريليونات'], // trillions
['كوادريليون', 'كوادريليونان', 'كوادريليونات'], // quadrillions
['كوينتيليون', 'كوينتيليونان', 'كوينتيليونات'], // quintillions
['سكستيليون', 'سكستيليونان', 'سكستيليونات'], // sextillions
['سبتيليون', 'سبتيليونان', 'سبتيليونات'], // septillions
['أوكتيليون', 'أوكتيليونان', 'أوكتيليونات'], // octillions
['نونيليون', 'نونيليونان', 'نونيليونات'], // nonillions
['ديسيليون', 'ديسيليونان', 'ديسيليونات'] // decillions
]);

/** Maximum supported value (10^36 - 1, up to decillions) */
const MAX_VALUE = 10n ** 36n - 1n;

const group = (n) => Math.ceil(n.toString().length / 3) - 1;
const power = (g) => 10n ** BigInt(g * 3);
const segment = (n, g) => n % power(g + 1);
const hundment = (n, g) => Number(segment(n, g) / power(g));
const tenment = (n, g) => hundment(n, g) % 100;

/**
* Get the correct Arabic scale word form
* Arabic has 3 forms: singular (1), dual (2), plural (3-10)
* For 11+, use the singular form
*/
const getArScale = (n, forms) => {
if (n === 1) return forms[0];
if (n === 2) return forms[1];
if (n >= 3 && n <= 10) return forms[2];
return forms[0]; // 11+ uses singular
};

const hundredAr = (n) => {
if (n < 100 || n >= 1000) return '';
return AR_HUNDREDS[Math.floor(n / 100)];
};

const tenAr = (n) => {
if (n === 0) return '';
if (n < 10) return AR_ONES[n];
if (n < 20) return AR_TEENS[n - 10];
const onesDigit = n % 10;
const tensDigit = Math.floor(n / 10);
if (onesDigit === 0) return AR_TENS[tensDigit];
// In Arabic, ones come before tens: واحد وعشرون (one and twenty)
return `${AR_ONES[onesDigit]} و${AR_TENS[tensDigit]}`;
};

/**
* Convert a number to Arabic words
* @param {number|bigint} n - The number to convert
* @returns {string|false} The Arabic word representation
*
* @example
* arabic(42) // 'اثنان وأربعون'
* arabic(1000) // 'ألف'
*/
const arabic = (n) => {
let num;

if (typeof n === 'bigint') {
if (n < 0n || n > MAX_VALUE) return false;
num = n;
} else if (typeof n === 'number') {
if (isNaN(n) || n < 0 || !Number.isInteger(n)) return false;
if (n > Number.MAX_SAFE_INTEGER) return false;
num = BigInt(n);
} else {
return false;
}

if (num === 0n) return 'صفر';

const parts = [];
for (let i = group(num); i >= 0; i--) {
const h = hundment(num, i);
if (h === 0) continue;

let chunk = '';
const hund = hundredAr(h);
const t = tenment(num, i);
const tenWord = tenAr(t);

if (hund && tenWord) {
chunk = `${hund} و${tenWord}`;
} else if (hund) {
chunk = hund;
} else if (tenWord) {
chunk = tenWord;
}

if (i > 0) {
const forms = AR_ILLIONS[i];
if (h === 1) {
// Just the scale word alone (e.g., ألف for 1000)
chunk = forms[0];
} else if (h === 2) {
// Dual form (e.g., ألفان for 2000)
chunk = forms[1];
} else {
// 3+: chunk + scale word
const scaleWord = getArScale(h, forms);
chunk = `${chunk} ${scaleWord}`;
}
}

parts.push(chunk);
}

return parts.join(' و');
};

export default arabic;
export { arabic, AR_ONES, AR_TENS, AR_TEENS, AR_HUNDREDS, AR_ILLIONS, MAX_VALUE };
Loading