Skip to content

Commit 6031073

Browse files
brianfunkclaude
andcommitted
feat: add 13 new languages, bringing total to 22
New languages: Japanese, Korean, Arabic, Italian, Dutch, Turkish, Polish, Swedish, Indonesian, Thai, Norwegian, Finnish, Icelandic. Full Scandinavian coverage (Danish, Swedish, Norwegian, Finnish, Icelandic). All languages support BigInt up to 10^36. 265 tests passing. Version bumped to 1.0.1. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4b3dfe7 commit 6031073

19 files changed

Lines changed: 1997 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.1] - 2026-02-08
9+
10+
### Added
11+
12+
- **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`)
13+
- Total language support now at 22 languages
14+
- Full Scandinavian coverage: Danish, Swedish, Norwegian, Finnish, Icelandic
15+
- All new languages support BigInt up to 10^36
16+
- 49 new tests for all new languages (265 total)
17+
818
## [1.0.0] - 2026-02-01
919

1020
### Breaking Changes

README.md

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,16 @@
1111

1212
> Number One Way to Makes Words from Numbers
1313
14-
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!
14+
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!
1515

1616
## Why numberstring?
1717

1818
- **Zero dependencies** - Lightweight and fast
19-
- **9 languages** - English, Spanish, French, German, Danish, Chinese, Hindi, Russian, Portuguese
19+
- **22 languages** - English, Spanish, French, German, Danish, Chinese, Hindi, Russian, Portuguese, Japanese, Korean, Arabic, Italian, Dutch, Turkish, Polish, Swedish, Indonesian, Thai, Norwegian, Finnish, Icelandic
2020
- **Huge range** - Supports 0 to decillions (10^36) with BigInt
2121
- **Feature-rich** - Ordinals, decimals, currency, fractions, years, phone numbers
2222
- **Roman numerals** - Convert to and from Roman numerals
23-
- **Well tested** - 216 tests with 90%+ coverage
23+
- **Well tested** - 265 tests with 90%+ coverage
2424
- **Modern ES modules** - Tree-shakeable, TypeScript-friendly
2525

2626
## Installation
@@ -191,10 +191,10 @@ comma(1234567); // '1,234,567'
191191

192192
## Multi-Language Support
193193

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

196196
```javascript
197-
import { toWords, spanish, french, german, danish, chinese, hindi, russian, portuguese } from 'numberstring';
197+
import { toWords } from 'numberstring';
198198

199199
// Using toWords with lang option
200200
toWords(42, { lang: 'es' }); // 'cuarenta y dos'
@@ -205,16 +205,48 @@ toWords(42, { lang: 'zh' }); // '四十二'
205205
toWords(42, { lang: 'hi' }); // 'बयालीस'
206206
toWords(42, { lang: 'ru' }); // 'сорок два'
207207
toWords(42, { lang: 'pt' }); // 'quarenta e dois'
208-
209-
// Or use language functions directly
210-
spanish(1000); // 'mil'
211-
french(80); // 'quatre-vingts'
212-
german(21); // 'einundzwanzig'
213-
chinese(10000); // '一万'
214-
russian(2000); // 'две тысячи'
215-
portuguese(100); // 'cem'
208+
toWords(42, { lang: 'ja' }); // '四十二'
209+
toWords(42, { lang: 'ko' }); // '사십이'
210+
toWords(42, { lang: 'ar' }); // 'اثنان وأربعون'
211+
toWords(42, { lang: 'it' }); // 'quarantadue'
212+
toWords(42, { lang: 'nl' }); // 'tweeënveertig'
213+
toWords(42, { lang: 'tr' }); // 'kırk iki'
214+
toWords(42, { lang: 'pl' }); // 'czterdzieści dwa'
215+
toWords(42, { lang: 'sv' }); // 'fyrtiotvå'
216+
toWords(42, { lang: 'id' }); // 'empat puluh dua'
217+
toWords(42, { lang: 'th' }); // 'สี่สิบสอง'
218+
toWords(42, { lang: 'no' }); // 'førtito'
219+
toWords(42, { lang: 'fi' }); // 'neljäkymmentäkaksi'
220+
toWords(42, { lang: 'is' }); // 'fjörutíu og tveir'
216221
```
217222

223+
### Supported Languages
224+
225+
| Code | Language | Example (42) |
226+
|------|----------|-------------|
227+
| `en` | English | forty-two |
228+
| `es` | Spanish | cuarenta y dos |
229+
| `fr` | French | quarante-deux |
230+
| `de` | German | zweiundvierzig |
231+
| `da` | Danish | toogfyrre |
232+
| `zh` | Chinese | 四十二 |
233+
| `hi` | Hindi | बयालीस |
234+
| `ru` | Russian | сорок два |
235+
| `pt` | Portuguese | quarenta e dois |
236+
| `ja` | Japanese | 四十二 |
237+
| `ko` | Korean | 사십이 |
238+
| `ar` | Arabic | اثنان وأربعون |
239+
| `it` | Italian | quarantadue |
240+
| `nl` | Dutch | tweeënveertig |
241+
| `tr` | Turkish | kırk iki |
242+
| `pl` | Polish | czterdzieści dwa |
243+
| `sv` | Swedish | fyrtiotvå |
244+
| `id` | Indonesian | empat puluh dua |
245+
| `th` | Thai | สี่สิบสอง |
246+
| `no` | Norwegian | førtito |
247+
| `fi` | Finnish | neljäkymmentäkaksi |
248+
| `is` | Icelandic | fjörutíu og tveir |
249+
218250
### Adding a New Language
219251

220252
Languages are modular! To add a new language:

index.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414

1515
/**
1616
* numberstring - Convert numbers to their word representation
17-
* Supports English, Spanish, French, German, Danish, Mandarin Chinese, Hindi, Russian, and Portuguese
17+
* 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
1818
* @module numberstring
1919
*/
2020

2121
// Import language functions
22-
import { english, spanish, french, german, danish, chinese, hindi, russian, portuguese, LANGUAGES } from './languages/index.js';
22+
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';
2323

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

2727
// ============================================================================
2828
// CONSTANTS
@@ -597,7 +597,7 @@ const percent = (pct, opt) => {
597597
* Convert a number to words in a specified language
598598
* @param {number|bigint} n - The number to convert
599599
* @param {Object} [opt] - Options object
600-
* @param {string} [opt.lang] - Language code: 'en', 'es', 'fr', 'de', 'da', 'zh', 'hi', 'ru'
600+
* @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'
601601
* @returns {string|false} The word representation
602602
*/
603603
const toWords = (n, opt) => {
@@ -633,6 +633,45 @@ const toWords = (n, opt) => {
633633
case 'portuguese':
634634
result = portuguese(n);
635635
break;
636+
case 'japanese':
637+
result = japanese(n);
638+
break;
639+
case 'korean':
640+
result = korean(n);
641+
break;
642+
case 'arabic':
643+
result = arabic(n);
644+
break;
645+
case 'italian':
646+
result = italian(n);
647+
break;
648+
case 'dutch':
649+
result = dutch(n);
650+
break;
651+
case 'turkish':
652+
result = turkish(n);
653+
break;
654+
case 'polish':
655+
result = polish(n);
656+
break;
657+
case 'swedish':
658+
result = swedish(n);
659+
break;
660+
case 'indonesian':
661+
result = indonesian(n);
662+
break;
663+
case 'thai':
664+
result = thai(n);
665+
break;
666+
case 'norwegian':
667+
result = norwegian(n);
668+
break;
669+
case 'finnish':
670+
result = finnish(n);
671+
break;
672+
case 'icelandic':
673+
result = icelandic(n);
674+
break;
636675
default:
637676
result = english(n);
638677
}

languages/ar.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* Arabic number-to-words converter
3+
* Uses masculine default forms with standard group-of-3 pattern
4+
* @module languages/ar
5+
*/
6+
7+
const AR_ONES = Object.freeze(['', 'واحد', 'اثنان', 'ثلاثة', 'أربعة', 'خمسة', 'ستة', 'سبعة', 'ثمانية', 'تسعة']);
8+
const AR_TEENS = Object.freeze(['عشرة', 'أحد عشر', 'اثنا عشر', 'ثلاثة عشر', 'أربعة عشر', 'خمسة عشر', 'ستة عشر', 'سبعة عشر', 'ثمانية عشر', 'تسعة عشر']);
9+
const AR_TENS = Object.freeze(['', '', 'عشرون', 'ثلاثون', 'أربعون', 'خمسون', 'ستون', 'سبعون', 'ثمانون', 'تسعون']);
10+
const AR_HUNDREDS = Object.freeze(['', 'مائة', 'مائتان', 'ثلاثمائة', 'أربعمائة', 'خمسمائة', 'ستمائة', 'سبعمائة', 'ثمانمائة', 'تسعمائة']);
11+
const AR_ILLIONS = Object.freeze([
12+
['', '', ''], // ones
13+
['ألف', 'ألفان', 'آلاف'], // thousands
14+
['مليون', 'مليونان', 'ملايين'], // millions
15+
['مليار', 'ملياران', 'مليارات'], // billions
16+
['تريليون', 'تريليونان', 'تريليونات'], // trillions
17+
['كوادريليون', 'كوادريليونان', 'كوادريليونات'], // quadrillions
18+
['كوينتيليون', 'كوينتيليونان', 'كوينتيليونات'], // quintillions
19+
['سكستيليون', 'سكستيليونان', 'سكستيليونات'], // sextillions
20+
['سبتيليون', 'سبتيليونان', 'سبتيليونات'], // septillions
21+
['أوكتيليون', 'أوكتيليونان', 'أوكتيليونات'], // octillions
22+
['نونيليون', 'نونيليونان', 'نونيليونات'], // nonillions
23+
['ديسيليون', 'ديسيليونان', 'ديسيليونات'] // decillions
24+
]);
25+
26+
/** Maximum supported value (10^36 - 1, up to decillions) */
27+
const MAX_VALUE = 10n ** 36n - 1n;
28+
29+
const group = (n) => Math.ceil(n.toString().length / 3) - 1;
30+
const power = (g) => 10n ** BigInt(g * 3);
31+
const segment = (n, g) => n % power(g + 1);
32+
const hundment = (n, g) => Number(segment(n, g) / power(g));
33+
const tenment = (n, g) => hundment(n, g) % 100;
34+
35+
/**
36+
* Get the correct Arabic scale word form
37+
* Arabic has 3 forms: singular (1), dual (2), plural (3-10)
38+
* For 11+, use the singular form
39+
*/
40+
const getArScale = (n, forms) => {
41+
if (n === 1) return forms[0];
42+
if (n === 2) return forms[1];
43+
if (n >= 3 && n <= 10) return forms[2];
44+
return forms[0]; // 11+ uses singular
45+
};
46+
47+
const hundredAr = (n) => {
48+
if (n < 100 || n >= 1000) return '';
49+
return AR_HUNDREDS[Math.floor(n / 100)];
50+
};
51+
52+
const tenAr = (n) => {
53+
if (n === 0) return '';
54+
if (n < 10) return AR_ONES[n];
55+
if (n < 20) return AR_TEENS[n - 10];
56+
const onesDigit = n % 10;
57+
const tensDigit = Math.floor(n / 10);
58+
if (onesDigit === 0) return AR_TENS[tensDigit];
59+
// In Arabic, ones come before tens: واحد وعشرون (one and twenty)
60+
return `${AR_ONES[onesDigit]} و${AR_TENS[tensDigit]}`;
61+
};
62+
63+
/**
64+
* Convert a number to Arabic words
65+
* @param {number|bigint} n - The number to convert
66+
* @returns {string|false} The Arabic word representation
67+
*
68+
* @example
69+
* arabic(42) // 'اثنان وأربعون'
70+
* arabic(1000) // 'ألف'
71+
*/
72+
const arabic = (n) => {
73+
let num;
74+
75+
if (typeof n === 'bigint') {
76+
if (n < 0n || n > MAX_VALUE) return false;
77+
num = n;
78+
} else if (typeof n === 'number') {
79+
if (isNaN(n) || n < 0 || !Number.isInteger(n)) return false;
80+
if (n > Number.MAX_SAFE_INTEGER) return false;
81+
num = BigInt(n);
82+
} else {
83+
return false;
84+
}
85+
86+
if (num === 0n) return 'صفر';
87+
88+
const parts = [];
89+
for (let i = group(num); i >= 0; i--) {
90+
const h = hundment(num, i);
91+
if (h === 0) continue;
92+
93+
let chunk = '';
94+
const hund = hundredAr(h);
95+
const t = tenment(num, i);
96+
const tenWord = tenAr(t);
97+
98+
if (hund && tenWord) {
99+
chunk = `${hund} و${tenWord}`;
100+
} else if (hund) {
101+
chunk = hund;
102+
} else if (tenWord) {
103+
chunk = tenWord;
104+
}
105+
106+
if (i > 0) {
107+
const forms = AR_ILLIONS[i];
108+
if (h === 1) {
109+
// Just the scale word alone (e.g., ألف for 1000)
110+
chunk = forms[0];
111+
} else if (h === 2) {
112+
// Dual form (e.g., ألفان for 2000)
113+
chunk = forms[1];
114+
} else {
115+
// 3+: chunk + scale word
116+
const scaleWord = getArScale(h, forms);
117+
chunk = `${chunk} ${scaleWord}`;
118+
}
119+
}
120+
121+
parts.push(chunk);
122+
}
123+
124+
return parts.join(' و');
125+
};
126+
127+
export default arabic;
128+
export { arabic, AR_ONES, AR_TENS, AR_TEENS, AR_HUNDREDS, AR_ILLIONS, MAX_VALUE };

0 commit comments

Comments
 (0)