n2words 1.24.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +183 -156
- package/dist/languages/am-Latn.js +3 -0
- package/dist/languages/am-Latn.js.map +1 -0
- package/dist/languages/am.js +3 -0
- package/dist/languages/am.js.map +1 -0
- package/dist/languages/ar.js +3 -2
- package/dist/languages/ar.js.map +1 -1
- package/dist/languages/az.js +3 -2
- package/dist/languages/az.js.map +1 -1
- package/dist/languages/bn.js +3 -2
- package/dist/languages/bn.js.map +1 -1
- package/dist/languages/cs.js +3 -2
- package/dist/languages/cs.js.map +1 -1
- package/dist/languages/da.js +3 -2
- package/dist/languages/da.js.map +1 -1
- package/dist/languages/de.js +3 -2
- package/dist/languages/de.js.map +1 -1
- package/dist/languages/el.js +3 -2
- package/dist/languages/el.js.map +1 -1
- package/dist/languages/en.js +3 -2
- package/dist/languages/en.js.map +1 -1
- package/dist/languages/es.js +3 -2
- package/dist/languages/es.js.map +1 -1
- package/dist/languages/fa.js +3 -2
- package/dist/languages/fa.js.map +1 -1
- package/dist/languages/fi.js +3 -0
- package/dist/languages/fi.js.map +1 -0
- package/dist/languages/fil.js +3 -2
- package/dist/languages/fil.js.map +1 -1
- package/dist/languages/fr-BE.js +3 -2
- package/dist/languages/fr-BE.js.map +1 -1
- package/dist/languages/fr.js +3 -2
- package/dist/languages/fr.js.map +1 -1
- package/dist/languages/gu.js +3 -2
- package/dist/languages/gu.js.map +1 -1
- package/dist/languages/ha.js +3 -0
- package/dist/languages/ha.js.map +1 -0
- package/dist/languages/hbo.js +3 -0
- package/dist/languages/hbo.js.map +1 -0
- package/dist/languages/he.js +3 -2
- package/dist/languages/he.js.map +1 -1
- package/dist/languages/hi.js +3 -2
- package/dist/languages/hi.js.map +1 -1
- package/dist/languages/hr.js +3 -2
- package/dist/languages/hr.js.map +1 -1
- package/dist/languages/hu.js +3 -2
- package/dist/languages/hu.js.map +1 -1
- package/dist/languages/id.js +3 -2
- package/dist/languages/id.js.map +1 -1
- package/dist/languages/it.js +3 -2
- package/dist/languages/it.js.map +1 -1
- package/dist/languages/ja.js +3 -2
- package/dist/languages/ja.js.map +1 -1
- package/dist/languages/kn.js +3 -2
- package/dist/languages/kn.js.map +1 -1
- package/dist/languages/ko.js +3 -2
- package/dist/languages/ko.js.map +1 -1
- package/dist/languages/lt.js +3 -2
- package/dist/languages/lt.js.map +1 -1
- package/dist/languages/lv.js +3 -2
- package/dist/languages/lv.js.map +1 -1
- package/dist/languages/mr.js +3 -2
- package/dist/languages/mr.js.map +1 -1
- package/dist/languages/ms.js +3 -2
- package/dist/languages/ms.js.map +1 -1
- package/dist/languages/nb.js +3 -2
- package/dist/languages/nb.js.map +1 -1
- package/dist/languages/nl.js +3 -2
- package/dist/languages/nl.js.map +1 -1
- package/dist/languages/pa.js +3 -0
- package/dist/languages/pa.js.map +1 -0
- package/dist/languages/pl.js +3 -2
- package/dist/languages/pl.js.map +1 -1
- package/dist/languages/pt.js +3 -2
- package/dist/languages/pt.js.map +1 -1
- package/dist/languages/ro.js +3 -2
- package/dist/languages/ro.js.map +1 -1
- package/dist/languages/ru.js +3 -2
- package/dist/languages/ru.js.map +1 -1
- package/dist/languages/sr-Cyrl.js +3 -0
- package/dist/languages/sr-Cyrl.js.map +1 -0
- package/dist/languages/sr-Latn.js +3 -2
- package/dist/languages/sr-Latn.js.map +1 -1
- package/dist/languages/sv.js +3 -2
- package/dist/languages/sv.js.map +1 -1
- package/dist/languages/sw.js +3 -2
- package/dist/languages/sw.js.map +1 -1
- package/dist/languages/ta.js +3 -2
- package/dist/languages/ta.js.map +1 -1
- package/dist/languages/te.js +3 -2
- package/dist/languages/te.js.map +1 -1
- package/dist/languages/th.js +3 -2
- package/dist/languages/th.js.map +1 -1
- package/dist/languages/tr.js +3 -2
- package/dist/languages/tr.js.map +1 -1
- package/dist/languages/uk.js +3 -2
- package/dist/languages/uk.js.map +1 -1
- package/dist/languages/ur.js +3 -2
- package/dist/languages/ur.js.map +1 -1
- package/dist/languages/vi.js +3 -2
- package/dist/languages/vi.js.map +1 -1
- package/dist/languages/zh-Hans.js +3 -2
- package/dist/languages/zh-Hans.js.map +1 -1
- package/dist/languages/zh-Hant.js +3 -0
- package/dist/languages/zh-Hant.js.map +1 -0
- package/dist/n2words.js +3 -2
- package/dist/n2words.js.map +1 -1
- package/lib/languages/am-Latn.d.ts +7 -0
- package/lib/languages/am-Latn.js +164 -0
- package/lib/languages/am.d.ts +7 -0
- package/lib/languages/am.js +164 -0
- package/lib/languages/ar.d.ts +17 -0
- package/lib/languages/ar.js +171 -209
- package/lib/languages/az.d.ts +7 -0
- package/lib/languages/az.js +167 -49
- package/lib/languages/bn.d.ts +7 -0
- package/lib/languages/bn.js +142 -123
- package/lib/languages/cs.d.ts +18 -0
- package/lib/languages/cs.js +303 -176
- package/lib/languages/da.d.ts +14 -0
- package/lib/languages/da.js +267 -139
- package/lib/languages/de.d.ts +17 -0
- package/lib/languages/de.js +310 -113
- package/lib/languages/el.d.ts +14 -0
- package/lib/languages/el.js +225 -98
- package/lib/languages/en.d.ts +17 -0
- package/lib/languages/en.js +235 -102
- package/lib/languages/es.d.ts +21 -0
- package/lib/languages/es.js +307 -125
- package/lib/languages/fa.d.ts +7 -0
- package/lib/languages/fa.js +115 -108
- package/lib/languages/fi.d.ts +14 -0
- package/lib/languages/fi.js +245 -0
- package/lib/languages/fil.d.ts +7 -0
- package/lib/languages/fil.js +199 -139
- package/lib/languages/fr-BE.d.ts +11 -0
- package/lib/languages/fr-BE.js +287 -48
- package/lib/languages/fr.d.ts +21 -0
- package/lib/languages/fr.js +343 -119
- package/lib/languages/gu.d.ts +7 -0
- package/lib/languages/gu.js +125 -144
- package/lib/languages/ha.d.ts +7 -0
- package/lib/languages/ha.js +230 -0
- package/lib/languages/hbo.d.ts +13 -0
- package/lib/languages/hbo.js +300 -0
- package/lib/languages/he.d.ts +13 -0
- package/lib/languages/he.js +230 -283
- package/lib/languages/hi.d.ts +7 -0
- package/lib/languages/hi.js +142 -123
- package/lib/languages/hr.d.ts +11 -0
- package/lib/languages/hr.js +190 -129
- package/lib/languages/hu.d.ts +7 -0
- package/lib/languages/hu.js +194 -133
- package/lib/languages/id.d.ts +7 -0
- package/lib/languages/id.js +167 -140
- package/lib/languages/it.d.ts +19 -0
- package/lib/languages/it.js +337 -108
- package/lib/languages/ja.d.ts +17 -0
- package/lib/languages/ja.js +224 -155
- package/lib/languages/kn.d.ts +7 -0
- package/lib/languages/kn.js +128 -62
- package/lib/languages/ko.d.ts +14 -0
- package/lib/languages/ko.js +250 -70
- package/lib/languages/lt.d.ts +18 -0
- package/lib/languages/lt.js +287 -148
- package/lib/languages/lv.d.ts +18 -0
- package/lib/languages/lv.js +291 -123
- package/lib/languages/mr.d.ts +7 -0
- package/lib/languages/mr.js +125 -144
- package/lib/languages/ms.d.ts +7 -0
- package/lib/languages/ms.js +171 -112
- package/lib/languages/nb.d.ts +14 -0
- package/lib/languages/nb.js +275 -100
- package/lib/languages/nl.d.ts +26 -0
- package/lib/languages/nl.js +307 -174
- package/lib/languages/pa.d.ts +7 -0
- package/lib/languages/pa.js +163 -0
- package/lib/languages/pl.d.ts +22 -0
- package/lib/languages/pl.js +299 -158
- package/lib/languages/pt.d.ts +17 -0
- package/lib/languages/pt.js +279 -120
- package/lib/languages/ro.d.ts +18 -0
- package/lib/languages/ro.js +214 -337
- package/lib/languages/ru.d.ts +11 -0
- package/lib/languages/ru.js +219 -95
- package/lib/languages/sr-Cyrl.d.ts +11 -0
- package/lib/languages/sr-Cyrl.js +215 -0
- package/lib/languages/sr-Latn.d.ts +11 -0
- package/lib/languages/sr-Latn.js +190 -132
- package/lib/languages/sv.d.ts +14 -0
- package/lib/languages/sv.js +280 -103
- package/lib/languages/sw.d.ts +7 -0
- package/lib/languages/sw.js +135 -103
- package/lib/languages/ta.d.ts +7 -0
- package/lib/languages/ta.js +133 -205
- package/lib/languages/te.d.ts +7 -0
- package/lib/languages/te.js +148 -213
- package/lib/languages/th.d.ts +7 -0
- package/lib/languages/th.js +139 -101
- package/lib/languages/tr.d.ts +18 -0
- package/lib/languages/tr.js +246 -66
- package/lib/languages/uk.d.ts +11 -0
- package/lib/languages/uk.js +197 -101
- package/lib/languages/ur.d.ts +7 -0
- package/lib/languages/ur.js +160 -123
- package/lib/languages/vi.d.ts +17 -0
- package/lib/languages/vi.js +287 -164
- package/lib/languages/zh-Hans.d.ts +11 -0
- package/lib/languages/zh-Hans.js +159 -142
- package/lib/languages/zh-Hant.d.ts +11 -0
- package/lib/languages/zh-Hant.js +202 -0
- package/lib/n2words.d.ts +53 -0
- package/lib/n2words.js +91 -227
- package/lib/utils/is-plain-object.d.ts +13 -0
- package/lib/utils/is-plain-object.js +17 -0
- package/lib/utils/parse-numeric.d.ts +17 -0
- package/lib/utils/parse-numeric.js +108 -0
- package/lib/utils/validate-options.d.ts +8 -0
- package/lib/utils/validate-options.js +16 -0
- package/package.json +118 -67
- package/dist/languages/pa-Guru.js +0 -2
- package/dist/languages/pa-Guru.js.map +0 -1
- package/lib/classes/abstract-language.js +0 -261
- package/lib/classes/greedy-scale-language.js +0 -195
- package/lib/classes/slavic-language.js +0 -251
- package/lib/classes/south-asian-language.js +0 -161
- package/lib/classes/turkic-language.js +0 -63
- package/lib/languages/pa-Guru.js +0 -126
- package/typings/classes/abstract-language.d.ts +0 -144
- package/typings/classes/greedy-scale-language.d.ts +0 -148
- package/typings/classes/slavic-language.d.ts +0 -145
- package/typings/classes/south-asian-language.d.ts +0 -101
- package/typings/classes/turkic-language.d.ts +0 -42
- package/typings/languages/ar.d.ts +0 -93
- package/typings/languages/az.d.ts +0 -25
- package/typings/languages/bn.d.ts +0 -1
- package/typings/languages/cs.d.ts +0 -120
- package/typings/languages/da.d.ts +0 -53
- package/typings/languages/de.d.ts +0 -26
- package/typings/languages/el.d.ts +0 -11
- package/typings/languages/en.d.ts +0 -30
- package/typings/languages/es.d.ts +0 -43
- package/typings/languages/fa.d.ts +0 -81
- package/typings/languages/fil.d.ts +0 -12
- package/typings/languages/fr-BE.d.ts +0 -41
- package/typings/languages/fr.d.ts +0 -43
- package/typings/languages/gu.d.ts +0 -12
- package/typings/languages/he.d.ts +0 -197
- package/typings/languages/hi.d.ts +0 -1
- package/typings/languages/hr.d.ts +0 -110
- package/typings/languages/hu.d.ts +0 -37
- package/typings/languages/id.d.ts +0 -69
- package/typings/languages/it.d.ts +0 -51
- package/typings/languages/ja.d.ts +0 -58
- package/typings/languages/kn.d.ts +0 -11
- package/typings/languages/ko.d.ts +0 -25
- package/typings/languages/lt.d.ts +0 -110
- package/typings/languages/lv.d.ts +0 -99
- package/typings/languages/mr.d.ts +0 -12
- package/typings/languages/ms.d.ts +0 -37
- package/typings/languages/nb.d.ts +0 -27
- package/typings/languages/nl.d.ts +0 -65
- package/typings/languages/pa-Guru.d.ts +0 -1
- package/typings/languages/pl.d.ts +0 -116
- package/typings/languages/pt.d.ts +0 -39
- package/typings/languages/ro.d.ts +0 -229
- package/typings/languages/ru.d.ts +0 -108
- package/typings/languages/sr-Latn.d.ts +0 -98
- package/typings/languages/sv.d.ts +0 -30
- package/typings/languages/sw.d.ts +0 -1
- package/typings/languages/ta.d.ts +0 -1
- package/typings/languages/te.d.ts +0 -1
- package/typings/languages/th.d.ts +0 -1
- package/typings/languages/tr.d.ts +0 -46
- package/typings/languages/uk.d.ts +0 -117
- package/typings/languages/ur.d.ts +0 -1
- package/typings/languages/vi.d.ts +0 -116
- package/typings/languages/zh-Hans.d.ts +0 -57
- package/typings/n2words.d.ts +0 -177
package/lib/languages/fr.js
CHANGED
|
@@ -1,145 +1,369 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* French language converter - Functional Implementation
|
|
3
|
+
*
|
|
4
|
+
* A performance-optimized number-to-words converter using precomputed lookup tables.
|
|
5
|
+
* Self-contained module with its own input validation, ready for subpath exports.
|
|
6
|
+
*
|
|
7
|
+
* Key optimization: Precompute all segment values (0-999) at module load.
|
|
8
|
+
* This eliminates all per-call string manipulation for segment conversion.
|
|
9
|
+
*
|
|
10
|
+
* French-specific rules (handled in precomputation):
|
|
11
|
+
* - Vigesimal patterns: 70 = soixante-dix, 80 = quatre-vingts, 90 = quatre-vingt-dix
|
|
12
|
+
* - "et" conjunction: vingt et un (21), soixante et onze (71), but NOT quatre-vingt-un
|
|
13
|
+
* - Pluralization: "cents" loses 's' when followed by more digits
|
|
14
|
+
* - Long scale with -ard forms: milliard, billiard, trilliard
|
|
15
|
+
* - Omit "un" before mille
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
19
|
+
import { validateOptions } from '../utils/validate-options.js'
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Vocabulary (module-level constants)
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
const ONES = ['', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept', 'huit', 'neuf']
|
|
26
|
+
const TEENS = ['dix', 'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize', 'dix-sept', 'dix-huit', 'dix-neuf']
|
|
27
|
+
const TENS = ['', '', 'vingt', 'trente', 'quarante', 'cinquante', 'soixante']
|
|
28
|
+
|
|
29
|
+
// Scale words (even indices: million, billion, trillion, quadrillion)
|
|
30
|
+
const SCALES = ['million', 'billion', 'trillion', 'quadrillion']
|
|
31
|
+
const SCALES_ARD = ['milliard', 'billiard', 'trilliard', 'quadrilliard']
|
|
32
|
+
|
|
33
|
+
const THOUSAND = 'mille'
|
|
34
|
+
const HUNDRED = 'cent'
|
|
35
|
+
const ZERO = 'zéro'
|
|
36
|
+
const NEGATIVE = 'moins'
|
|
37
|
+
const DECIMAL_SEP = 'virgule'
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Precomputed Lookup Tables (built once at module load)
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Builds segment word for 0-999.
|
|
45
|
+
* Returns object with { word, endsWithCents, endsWithVingts } for pluralization handling.
|
|
46
|
+
*/
|
|
47
|
+
function buildSegment (n) {
|
|
48
|
+
if (n === 0) return { word: '', endsWithCents: false, endsWithVingts: false }
|
|
49
|
+
|
|
50
|
+
const tensOnes = n % 100
|
|
51
|
+
const hundreds = Math.floor(n / 100)
|
|
52
|
+
|
|
53
|
+
const parts = []
|
|
54
|
+
let endsWithCents = false
|
|
55
|
+
let endsWithVingts = false
|
|
56
|
+
|
|
57
|
+
// Hundreds
|
|
58
|
+
if (hundreds > 0) {
|
|
59
|
+
if (hundreds === 1) {
|
|
60
|
+
if (tensOnes === 0) {
|
|
61
|
+
parts.push(HUNDRED)
|
|
62
|
+
} else {
|
|
63
|
+
parts.push(HUNDRED)
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
if (tensOnes === 0) {
|
|
67
|
+
// "deux cents", "trois cents" (with 's')
|
|
68
|
+
parts.push(ONES[hundreds] + ' ' + HUNDRED + 's')
|
|
69
|
+
endsWithCents = true
|
|
70
|
+
} else {
|
|
71
|
+
// "deux cent", "trois cent" (no 's' when followed by more)
|
|
72
|
+
parts.push(ONES[hundreds] + ' ' + HUNDRED)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Tens and ones - vigesimal pattern
|
|
78
|
+
if (tensOnes === 0) {
|
|
79
|
+
// Just hundreds, nothing more
|
|
80
|
+
} else if (tensOnes < 10) {
|
|
81
|
+
// Single digit
|
|
82
|
+
parts.push(ONES[tensOnes])
|
|
83
|
+
} else if (tensOnes < 17) {
|
|
84
|
+
// 10-16: regular teens
|
|
85
|
+
parts.push(TEENS[tensOnes - 10])
|
|
86
|
+
} else if (tensOnes < 20) {
|
|
87
|
+
// 17-19: dix-sept, dix-huit, dix-neuf
|
|
88
|
+
parts.push(TEENS[tensOnes - 10])
|
|
89
|
+
} else if (tensOnes < 70) {
|
|
90
|
+
// 20-69: standard tens + ones
|
|
91
|
+
const t = Math.floor(tensOnes / 10)
|
|
92
|
+
const o = tensOnes % 10
|
|
93
|
+
if (o === 0) {
|
|
94
|
+
parts.push(TENS[t])
|
|
95
|
+
} else if (o === 1) {
|
|
96
|
+
// "et un" for 21, 31, 41, 51, 61
|
|
97
|
+
parts.push(TENS[t] + ' et ' + ONES[1])
|
|
98
|
+
} else {
|
|
99
|
+
parts.push(TENS[t] + '-' + ONES[o])
|
|
100
|
+
}
|
|
101
|
+
} else if (tensOnes < 80) {
|
|
102
|
+
// 70-79: soixante-dix, soixante et onze, soixante-douze...
|
|
103
|
+
const remainder = tensOnes - 60
|
|
104
|
+
if (remainder === 11) {
|
|
105
|
+
// 71: soixante et onze
|
|
106
|
+
parts.push('soixante et onze')
|
|
107
|
+
} else {
|
|
108
|
+
// 70, 72-79: soixante-dix, soixante-douze...
|
|
109
|
+
parts.push('soixante-' + TEENS[remainder - 10])
|
|
110
|
+
}
|
|
111
|
+
} else if (tensOnes === 80) {
|
|
112
|
+
// 80: quatre-vingts (with 's')
|
|
113
|
+
parts.push('quatre-vingts')
|
|
114
|
+
endsWithVingts = true
|
|
115
|
+
} else if (tensOnes < 100) {
|
|
116
|
+
// 81-99: quatre-vingt-un, quatre-vingt-dix...
|
|
117
|
+
const remainder = tensOnes - 80
|
|
118
|
+
if (remainder < 10) {
|
|
119
|
+
// 81-89
|
|
120
|
+
parts.push('quatre-vingt-' + ONES[remainder])
|
|
121
|
+
} else {
|
|
122
|
+
// 90-99
|
|
123
|
+
parts.push('quatre-vingt-' + TEENS[remainder - 10])
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Join parts with space (between hundreds and rest)
|
|
128
|
+
return { word: parts.join(' '), endsWithCents, endsWithVingts }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Precompute all 1000 segment words (0-999)
|
|
132
|
+
const SEGMENTS = new Array(1000)
|
|
133
|
+
const SEGMENTS_ENDS_CENTS = new Array(1000)
|
|
134
|
+
const SEGMENTS_ENDS_VINGTS = new Array(1000)
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < 1000; i++) {
|
|
137
|
+
const result = buildSegment(i)
|
|
138
|
+
SEGMENTS[i] = result.word
|
|
139
|
+
SEGMENTS_ENDS_CENTS[i] = result.endsWithCents
|
|
140
|
+
SEGMENTS_ENDS_VINGTS[i] = result.endsWithVingts
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ============================================================================
|
|
144
|
+
// Helper Functions
|
|
145
|
+
// ============================================================================
|
|
2
146
|
|
|
3
147
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
148
|
+
* Gets scale word for French long scale with -ard pattern.
|
|
149
|
+
*
|
|
150
|
+
* @param {number} scaleIndex - Scale level (1 = thousand, 2 = million, etc.)
|
|
151
|
+
* @param {bigint} segment - Segment value for pluralization
|
|
152
|
+
* @returns {string} Scale word
|
|
6
153
|
*/
|
|
154
|
+
function getScaleWord (scaleIndex, segment) {
|
|
155
|
+
if (scaleIndex === 1) return THOUSAND
|
|
156
|
+
|
|
157
|
+
// Even indices (2, 4, 6, 8): million, billion, trillion, quadrillion
|
|
158
|
+
// Odd indices > 1 (3, 5, 7, 9): milliard, billiard, trilliard, quadrilliard
|
|
159
|
+
if (scaleIndex % 2 === 0) {
|
|
160
|
+
const arrayIndex = (scaleIndex / 2) - 1
|
|
161
|
+
const baseWord = SCALES[arrayIndex]
|
|
162
|
+
if (!baseWord) return ''
|
|
163
|
+
return segment > 1n ? baseWord + 's' : baseWord
|
|
164
|
+
} else {
|
|
165
|
+
const arrayIndex = ((scaleIndex - 1) / 2) - 1
|
|
166
|
+
const ardWord = SCALES_ARD[arrayIndex]
|
|
167
|
+
if (!ardWord) return THOUSAND
|
|
168
|
+
return segment > 1n ? ardWord + 's' : ardWord
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Conversion Functions
|
|
174
|
+
// ============================================================================
|
|
7
175
|
|
|
8
176
|
/**
|
|
9
|
-
* French
|
|
177
|
+
* Converts a non-negative integer to French words.
|
|
10
178
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* - Hyphenation for compound numbers
|
|
15
|
-
* - Regional number word variations
|
|
179
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
180
|
+
* @param {boolean} withHyphen - Whether to use hyphen separators
|
|
181
|
+
* @returns {string} French words
|
|
16
182
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
[
|
|
23
|
-
|
|
24
|
-
[1_000_000_000_000_000_000_000n, 'trilliard'],
|
|
25
|
-
[1_000_000_000_000_000_000n, 'trillion'],
|
|
26
|
-
[1_000_000_000_000_000n, 'billiard'],
|
|
27
|
-
[1_000_000_000_000n, 'billion'],
|
|
28
|
-
[1_000_000_000n, 'milliard'],
|
|
29
|
-
[1_000_000n, 'million'],
|
|
30
|
-
[1000n, 'mille'],
|
|
31
|
-
[100n, 'cent'],
|
|
32
|
-
[],
|
|
33
|
-
[80n, 'quatre-vingts'],
|
|
34
|
-
[],
|
|
35
|
-
[60n, 'soixante'],
|
|
36
|
-
[50n, 'cinquante'],
|
|
37
|
-
[40n, 'quarante'],
|
|
38
|
-
[30n, 'trente'],
|
|
39
|
-
[20n, 'vingt'],
|
|
40
|
-
[19n, 'dix-neuf'],
|
|
41
|
-
[18n, 'dix-huit'],
|
|
42
|
-
[17n, 'dix-sept'],
|
|
43
|
-
[16n, 'seize'],
|
|
44
|
-
[15n, 'quinze'],
|
|
45
|
-
[14n, 'quatorze'],
|
|
46
|
-
[13n, 'treize'],
|
|
47
|
-
[12n, 'douze'],
|
|
48
|
-
[11n, 'onze'],
|
|
49
|
-
[10n, 'dix'],
|
|
50
|
-
[9n, 'neuf'],
|
|
51
|
-
[8n, 'huit'],
|
|
52
|
-
[7n, 'sept'],
|
|
53
|
-
[6n, 'six'],
|
|
54
|
-
[5n, 'cinq'],
|
|
55
|
-
[4n, 'quatre'],
|
|
56
|
-
[3n, 'trois'],
|
|
57
|
-
[2n, 'deux'],
|
|
58
|
-
[1n, 'un'],
|
|
59
|
-
[0n, 'zéro']
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Initializes the French converter with language-specific options.
|
|
64
|
-
*
|
|
65
|
-
* @param {FrenchOptions} [options={}] Configuration options.
|
|
66
|
-
*/
|
|
67
|
-
constructor ({ withHyphenSeparator = false } = {}) {
|
|
68
|
-
super()
|
|
69
|
-
|
|
70
|
-
this.withHyphenSeparator = withHyphenSeparator
|
|
71
|
-
|
|
72
|
-
if (this.withHyphenSeparator) {
|
|
73
|
-
this.wordSeparator = '-'
|
|
74
|
-
}
|
|
183
|
+
function integerToWords (n, withHyphen = false) {
|
|
184
|
+
if (n === 0n) return ZERO
|
|
185
|
+
|
|
186
|
+
// Fast path: numbers < 1000 (direct lookup)
|
|
187
|
+
if (n < 1000n) {
|
|
188
|
+
const word = SEGMENTS[Number(n)]
|
|
189
|
+
return withHyphen ? word.replace(/ /g, '-') : word
|
|
75
190
|
}
|
|
76
191
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
* @param {Object} currentPair The left operand as `{ word: number }`.
|
|
87
|
-
* @param {Object} nextPair The right operand as `{ word: number }`.
|
|
88
|
-
* @returns {Object} Merged pair with combined word and resulting number.
|
|
89
|
-
*/
|
|
90
|
-
mergeScales (currentPair, nextPair) {
|
|
91
|
-
let currentWord = Object.keys(currentPair)[0]
|
|
92
|
-
let nextWord = Object.keys(nextPair)[0]
|
|
93
|
-
const currentNumber = Object.values(currentPair)[0]
|
|
94
|
-
const nextNumber = Object.values(nextPair)[0]
|
|
95
|
-
|
|
96
|
-
if (currentNumber === 1n) {
|
|
97
|
-
if (nextNumber < 1_000_000n) {
|
|
98
|
-
return nextPair
|
|
99
|
-
}
|
|
192
|
+
// Fast path: numbers < 1,000,000 (thousands)
|
|
193
|
+
if (n < 1_000_000n) {
|
|
194
|
+
const thousands = Number(n / 1000n)
|
|
195
|
+
const remainder = Number(n % 1000n)
|
|
196
|
+
|
|
197
|
+
let result
|
|
198
|
+
if (thousands === 1) {
|
|
199
|
+
// "mille" not "un mille"
|
|
200
|
+
result = THOUSAND
|
|
100
201
|
} else {
|
|
101
|
-
if
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
) {
|
|
106
|
-
currentWord = currentWord.slice(0, -1)
|
|
202
|
+
// Check if segment ends with "cents" or "vingts" - need to strip 's' before mille
|
|
203
|
+
let thousandsWord = SEGMENTS[thousands]
|
|
204
|
+
if (SEGMENTS_ENDS_CENTS[thousands] || SEGMENTS_ENDS_VINGTS[thousands]) {
|
|
205
|
+
thousandsWord = thousandsWord.slice(0, -1) // Remove trailing 's'
|
|
107
206
|
}
|
|
207
|
+
result = thousandsWord + (withHyphen ? '-' : ' ') + THOUSAND
|
|
208
|
+
}
|
|
108
209
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
nextWord.at(-1) !== 's' &&
|
|
112
|
-
nextNumber % 100n === 0n
|
|
113
|
-
) {
|
|
114
|
-
nextWord += 's'
|
|
115
|
-
}
|
|
210
|
+
if (remainder > 0) {
|
|
211
|
+
result += (withHyphen ? '-' : ' ') + SEGMENTS[remainder]
|
|
116
212
|
}
|
|
117
213
|
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
214
|
+
if (withHyphen) {
|
|
215
|
+
result = result.replace(/ /g, '-')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return result
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// For numbers >= 1,000,000, use scale decomposition
|
|
222
|
+
return buildLargeNumberWords(n, withHyphen)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Builds words for numbers >= 1,000,000.
|
|
227
|
+
*
|
|
228
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
229
|
+
* @param {boolean} withHyphen - Whether to use hyphen separators
|
|
230
|
+
* @returns {string} French words
|
|
231
|
+
*/
|
|
232
|
+
function buildLargeNumberWords (n, withHyphen) {
|
|
233
|
+
const numStr = n.toString()
|
|
234
|
+
const len = numStr.length
|
|
235
|
+
|
|
236
|
+
// Build segments of 3 digits from right to left
|
|
237
|
+
const segments = []
|
|
238
|
+
const segmentSize = 3
|
|
239
|
+
|
|
240
|
+
const remainderLen = len % segmentSize
|
|
241
|
+
let pos = 0
|
|
242
|
+
if (remainderLen > 0) {
|
|
243
|
+
segments.push(Number(numStr.slice(0, remainderLen)))
|
|
244
|
+
pos = remainderLen
|
|
245
|
+
}
|
|
246
|
+
while (pos < len) {
|
|
247
|
+
segments.push(Number(numStr.slice(pos, pos + segmentSize)))
|
|
248
|
+
pos += segmentSize
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Convert segments to words
|
|
252
|
+
const parts = []
|
|
253
|
+
let scaleIndex = segments.length - 1
|
|
254
|
+
|
|
255
|
+
for (let i = 0; i < segments.length; i++) {
|
|
256
|
+
const segment = segments[i]
|
|
257
|
+
|
|
258
|
+
if (segment !== 0) {
|
|
259
|
+
const scaleWord = scaleIndex > 0 ? getScaleWord(scaleIndex, BigInt(segment)) : ''
|
|
260
|
+
|
|
261
|
+
if (scaleIndex === 0) {
|
|
262
|
+
// Units segment
|
|
263
|
+
parts.push(SEGMENTS[segment])
|
|
264
|
+
} else if (scaleIndex === 1) {
|
|
265
|
+
// Thousands: "mille" not "un mille"
|
|
266
|
+
if (segment === 1) {
|
|
267
|
+
parts.push(THOUSAND)
|
|
268
|
+
} else {
|
|
269
|
+
let segWords = SEGMENTS[segment]
|
|
270
|
+
// Strip 's' from cents/vingts before mille
|
|
271
|
+
if (SEGMENTS_ENDS_CENTS[segment] || SEGMENTS_ENDS_VINGTS[segment]) {
|
|
272
|
+
segWords = segWords.slice(0, -1)
|
|
273
|
+
}
|
|
274
|
+
parts.push(segWords)
|
|
275
|
+
parts.push(scaleWord)
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
// Million and above
|
|
279
|
+
parts.push(SEGMENTS[segment])
|
|
280
|
+
parts.push(scaleWord)
|
|
121
281
|
}
|
|
122
|
-
return { [`${currentWord}-${nextWord}`]: currentNumber + nextNumber }
|
|
123
282
|
}
|
|
124
283
|
|
|
125
|
-
|
|
126
|
-
|
|
284
|
+
scaleIndex--
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const sep = withHyphen ? '-' : ' '
|
|
288
|
+
let result = parts.join(sep)
|
|
289
|
+
|
|
290
|
+
if (withHyphen) {
|
|
291
|
+
result = result.replace(/ /g, '-')
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return result
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Converts decimal digits to French words.
|
|
299
|
+
*
|
|
300
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
301
|
+
* @param {boolean} withHyphen - Whether to use hyphen separators
|
|
302
|
+
* @returns {string} French words for decimal part
|
|
303
|
+
*/
|
|
304
|
+
function decimalPartToWords (decimalPart, withHyphen) {
|
|
305
|
+
let result = ''
|
|
306
|
+
const sep = withHyphen ? '-' : ' '
|
|
307
|
+
|
|
308
|
+
// Handle leading zeros
|
|
309
|
+
let i = 0
|
|
310
|
+
while (i < decimalPart.length && decimalPart[i] === '0') {
|
|
311
|
+
if (result) result += sep
|
|
312
|
+
result += ZERO
|
|
313
|
+
i++
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Convert remainder as a single number
|
|
317
|
+
const remainder = decimalPart.slice(i)
|
|
318
|
+
if (remainder) {
|
|
319
|
+
if (result) result += sep
|
|
320
|
+
result += integerToWords(BigInt(remainder), withHyphen)
|
|
127
321
|
}
|
|
322
|
+
|
|
323
|
+
return result
|
|
128
324
|
}
|
|
129
325
|
|
|
130
326
|
/**
|
|
131
|
-
* Converts a
|
|
327
|
+
* Converts a numeric value to French words.
|
|
328
|
+
*
|
|
329
|
+
* This is the main public API. It accepts any valid numeric input
|
|
330
|
+
* (number, string, or bigint) and handles parsing internally.
|
|
132
331
|
*
|
|
133
|
-
* @param {number|string|bigint} value The
|
|
134
|
-
* @param {Object} [options]
|
|
135
|
-
* @
|
|
136
|
-
* @
|
|
137
|
-
* @throws {
|
|
332
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
333
|
+
* @param {Object} [options] - Optional configuration
|
|
334
|
+
* @param {boolean} [options.withHyphenSeparator=false] - Use hyphens between all words
|
|
335
|
+
* @returns {string} The number in French words
|
|
336
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
337
|
+
* @throws {Error} If value is not a valid number format
|
|
138
338
|
*
|
|
139
339
|
* @example
|
|
140
|
-
*
|
|
141
|
-
*
|
|
340
|
+
* toWords(21) // 'vingt et un'
|
|
341
|
+
* toWords(80) // 'quatre-vingts'
|
|
342
|
+
* toWords(1000000) // 'un million'
|
|
142
343
|
*/
|
|
143
|
-
|
|
144
|
-
|
|
344
|
+
function toWords (value, options) {
|
|
345
|
+
options = validateOptions(options)
|
|
346
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
347
|
+
const withHyphen = options.withHyphenSeparator || false
|
|
348
|
+
|
|
349
|
+
let result = ''
|
|
350
|
+
const sep = withHyphen ? '-' : ' '
|
|
351
|
+
|
|
352
|
+
if (isNegative) {
|
|
353
|
+
result = NEGATIVE + sep
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
result += integerToWords(integerPart, withHyphen)
|
|
357
|
+
|
|
358
|
+
if (decimalPart) {
|
|
359
|
+
result += sep + DECIMAL_SEP + sep + decimalPartToWords(decimalPart, withHyphen)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return result
|
|
145
363
|
}
|
|
364
|
+
|
|
365
|
+
// ============================================================================
|
|
366
|
+
// Public API
|
|
367
|
+
// ============================================================================
|
|
368
|
+
|
|
369
|
+
export { toWords }
|