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.
Files changed (280) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +183 -156
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -2
  8. package/dist/languages/ar.js.map +1 -1
  9. package/dist/languages/az.js +3 -2
  10. package/dist/languages/az.js.map +1 -1
  11. package/dist/languages/bn.js +3 -2
  12. package/dist/languages/bn.js.map +1 -1
  13. package/dist/languages/cs.js +3 -2
  14. package/dist/languages/cs.js.map +1 -1
  15. package/dist/languages/da.js +3 -2
  16. package/dist/languages/da.js.map +1 -1
  17. package/dist/languages/de.js +3 -2
  18. package/dist/languages/de.js.map +1 -1
  19. package/dist/languages/el.js +3 -2
  20. package/dist/languages/el.js.map +1 -1
  21. package/dist/languages/en.js +3 -2
  22. package/dist/languages/en.js.map +1 -1
  23. package/dist/languages/es.js +3 -2
  24. package/dist/languages/es.js.map +1 -1
  25. package/dist/languages/fa.js +3 -2
  26. package/dist/languages/fa.js.map +1 -1
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -2
  30. package/dist/languages/fil.js.map +1 -1
  31. package/dist/languages/fr-BE.js +3 -2
  32. package/dist/languages/fr-BE.js.map +1 -1
  33. package/dist/languages/fr.js +3 -2
  34. package/dist/languages/fr.js.map +1 -1
  35. package/dist/languages/gu.js +3 -2
  36. package/dist/languages/gu.js.map +1 -1
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -2
  42. package/dist/languages/he.js.map +1 -1
  43. package/dist/languages/hi.js +3 -2
  44. package/dist/languages/hi.js.map +1 -1
  45. package/dist/languages/hr.js +3 -2
  46. package/dist/languages/hr.js.map +1 -1
  47. package/dist/languages/hu.js +3 -2
  48. package/dist/languages/hu.js.map +1 -1
  49. package/dist/languages/id.js +3 -2
  50. package/dist/languages/id.js.map +1 -1
  51. package/dist/languages/it.js +3 -2
  52. package/dist/languages/it.js.map +1 -1
  53. package/dist/languages/ja.js +3 -2
  54. package/dist/languages/ja.js.map +1 -1
  55. package/dist/languages/kn.js +3 -2
  56. package/dist/languages/kn.js.map +1 -1
  57. package/dist/languages/ko.js +3 -2
  58. package/dist/languages/ko.js.map +1 -1
  59. package/dist/languages/lt.js +3 -2
  60. package/dist/languages/lt.js.map +1 -1
  61. package/dist/languages/lv.js +3 -2
  62. package/dist/languages/lv.js.map +1 -1
  63. package/dist/languages/mr.js +3 -2
  64. package/dist/languages/mr.js.map +1 -1
  65. package/dist/languages/ms.js +3 -2
  66. package/dist/languages/ms.js.map +1 -1
  67. package/dist/languages/nb.js +3 -2
  68. package/dist/languages/nb.js.map +1 -1
  69. package/dist/languages/nl.js +3 -2
  70. package/dist/languages/nl.js.map +1 -1
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -2
  74. package/dist/languages/pl.js.map +1 -1
  75. package/dist/languages/pt.js +3 -2
  76. package/dist/languages/pt.js.map +1 -1
  77. package/dist/languages/ro.js +3 -2
  78. package/dist/languages/ro.js.map +1 -1
  79. package/dist/languages/ru.js +3 -2
  80. package/dist/languages/ru.js.map +1 -1
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -2
  84. package/dist/languages/sr-Latn.js.map +1 -1
  85. package/dist/languages/sv.js +3 -2
  86. package/dist/languages/sv.js.map +1 -1
  87. package/dist/languages/sw.js +3 -2
  88. package/dist/languages/sw.js.map +1 -1
  89. package/dist/languages/ta.js +3 -2
  90. package/dist/languages/ta.js.map +1 -1
  91. package/dist/languages/te.js +3 -2
  92. package/dist/languages/te.js.map +1 -1
  93. package/dist/languages/th.js +3 -2
  94. package/dist/languages/th.js.map +1 -1
  95. package/dist/languages/tr.js +3 -2
  96. package/dist/languages/tr.js.map +1 -1
  97. package/dist/languages/uk.js +3 -2
  98. package/dist/languages/uk.js.map +1 -1
  99. package/dist/languages/ur.js +3 -2
  100. package/dist/languages/ur.js.map +1 -1
  101. package/dist/languages/vi.js +3 -2
  102. package/dist/languages/vi.js.map +1 -1
  103. package/dist/languages/zh-Hans.js +3 -2
  104. package/dist/languages/zh-Hans.js.map +1 -1
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +3 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +17 -0
  114. package/lib/languages/ar.js +171 -209
  115. package/lib/languages/az.d.ts +7 -0
  116. package/lib/languages/az.js +167 -49
  117. package/lib/languages/bn.d.ts +7 -0
  118. package/lib/languages/bn.js +142 -123
  119. package/lib/languages/cs.d.ts +18 -0
  120. package/lib/languages/cs.js +303 -176
  121. package/lib/languages/da.d.ts +14 -0
  122. package/lib/languages/da.js +267 -139
  123. package/lib/languages/de.d.ts +17 -0
  124. package/lib/languages/de.js +310 -113
  125. package/lib/languages/el.d.ts +14 -0
  126. package/lib/languages/el.js +225 -98
  127. package/lib/languages/en.d.ts +17 -0
  128. package/lib/languages/en.js +235 -102
  129. package/lib/languages/es.d.ts +21 -0
  130. package/lib/languages/es.js +307 -125
  131. package/lib/languages/fa.d.ts +7 -0
  132. package/lib/languages/fa.js +115 -108
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +7 -0
  136. package/lib/languages/fil.js +199 -139
  137. package/lib/languages/fr-BE.d.ts +11 -0
  138. package/lib/languages/fr-BE.js +287 -48
  139. package/lib/languages/fr.d.ts +21 -0
  140. package/lib/languages/fr.js +343 -119
  141. package/lib/languages/gu.d.ts +7 -0
  142. package/lib/languages/gu.js +125 -144
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +13 -0
  146. package/lib/languages/hbo.js +300 -0
  147. package/lib/languages/he.d.ts +13 -0
  148. package/lib/languages/he.js +230 -283
  149. package/lib/languages/hi.d.ts +7 -0
  150. package/lib/languages/hi.js +142 -123
  151. package/lib/languages/hr.d.ts +11 -0
  152. package/lib/languages/hr.js +190 -129
  153. package/lib/languages/hu.d.ts +7 -0
  154. package/lib/languages/hu.js +194 -133
  155. package/lib/languages/id.d.ts +7 -0
  156. package/lib/languages/id.js +167 -140
  157. package/lib/languages/it.d.ts +19 -0
  158. package/lib/languages/it.js +337 -108
  159. package/lib/languages/ja.d.ts +17 -0
  160. package/lib/languages/ja.js +224 -155
  161. package/lib/languages/kn.d.ts +7 -0
  162. package/lib/languages/kn.js +128 -62
  163. package/lib/languages/ko.d.ts +14 -0
  164. package/lib/languages/ko.js +250 -70
  165. package/lib/languages/lt.d.ts +18 -0
  166. package/lib/languages/lt.js +287 -148
  167. package/lib/languages/lv.d.ts +18 -0
  168. package/lib/languages/lv.js +291 -123
  169. package/lib/languages/mr.d.ts +7 -0
  170. package/lib/languages/mr.js +125 -144
  171. package/lib/languages/ms.d.ts +7 -0
  172. package/lib/languages/ms.js +171 -112
  173. package/lib/languages/nb.d.ts +14 -0
  174. package/lib/languages/nb.js +275 -100
  175. package/lib/languages/nl.d.ts +26 -0
  176. package/lib/languages/nl.js +307 -174
  177. package/lib/languages/pa.d.ts +7 -0
  178. package/lib/languages/pa.js +163 -0
  179. package/lib/languages/pl.d.ts +22 -0
  180. package/lib/languages/pl.js +299 -158
  181. package/lib/languages/pt.d.ts +17 -0
  182. package/lib/languages/pt.js +279 -120
  183. package/lib/languages/ro.d.ts +18 -0
  184. package/lib/languages/ro.js +214 -337
  185. package/lib/languages/ru.d.ts +11 -0
  186. package/lib/languages/ru.js +219 -95
  187. package/lib/languages/sr-Cyrl.d.ts +11 -0
  188. package/lib/languages/sr-Cyrl.js +215 -0
  189. package/lib/languages/sr-Latn.d.ts +11 -0
  190. package/lib/languages/sr-Latn.js +190 -132
  191. package/lib/languages/sv.d.ts +14 -0
  192. package/lib/languages/sv.js +280 -103
  193. package/lib/languages/sw.d.ts +7 -0
  194. package/lib/languages/sw.js +135 -103
  195. package/lib/languages/ta.d.ts +7 -0
  196. package/lib/languages/ta.js +133 -205
  197. package/lib/languages/te.d.ts +7 -0
  198. package/lib/languages/te.js +148 -213
  199. package/lib/languages/th.d.ts +7 -0
  200. package/lib/languages/th.js +139 -101
  201. package/lib/languages/tr.d.ts +18 -0
  202. package/lib/languages/tr.js +246 -66
  203. package/lib/languages/uk.d.ts +11 -0
  204. package/lib/languages/uk.js +197 -101
  205. package/lib/languages/ur.d.ts +7 -0
  206. package/lib/languages/ur.js +160 -123
  207. package/lib/languages/vi.d.ts +17 -0
  208. package/lib/languages/vi.js +287 -164
  209. package/lib/languages/zh-Hans.d.ts +11 -0
  210. package/lib/languages/zh-Hans.js +159 -142
  211. package/lib/languages/zh-Hant.d.ts +11 -0
  212. package/lib/languages/zh-Hant.js +202 -0
  213. package/lib/n2words.d.ts +53 -0
  214. package/lib/n2words.js +91 -227
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +118 -67
  222. package/dist/languages/pa-Guru.js +0 -2
  223. package/dist/languages/pa-Guru.js.map +0 -1
  224. package/lib/classes/abstract-language.js +0 -261
  225. package/lib/classes/greedy-scale-language.js +0 -195
  226. package/lib/classes/slavic-language.js +0 -251
  227. package/lib/classes/south-asian-language.js +0 -161
  228. package/lib/classes/turkic-language.js +0 -63
  229. package/lib/languages/pa-Guru.js +0 -126
  230. package/typings/classes/abstract-language.d.ts +0 -144
  231. package/typings/classes/greedy-scale-language.d.ts +0 -148
  232. package/typings/classes/slavic-language.d.ts +0 -145
  233. package/typings/classes/south-asian-language.d.ts +0 -101
  234. package/typings/classes/turkic-language.d.ts +0 -42
  235. package/typings/languages/ar.d.ts +0 -93
  236. package/typings/languages/az.d.ts +0 -25
  237. package/typings/languages/bn.d.ts +0 -1
  238. package/typings/languages/cs.d.ts +0 -120
  239. package/typings/languages/da.d.ts +0 -53
  240. package/typings/languages/de.d.ts +0 -26
  241. package/typings/languages/el.d.ts +0 -11
  242. package/typings/languages/en.d.ts +0 -30
  243. package/typings/languages/es.d.ts +0 -43
  244. package/typings/languages/fa.d.ts +0 -81
  245. package/typings/languages/fil.d.ts +0 -12
  246. package/typings/languages/fr-BE.d.ts +0 -41
  247. package/typings/languages/fr.d.ts +0 -43
  248. package/typings/languages/gu.d.ts +0 -12
  249. package/typings/languages/he.d.ts +0 -197
  250. package/typings/languages/hi.d.ts +0 -1
  251. package/typings/languages/hr.d.ts +0 -110
  252. package/typings/languages/hu.d.ts +0 -37
  253. package/typings/languages/id.d.ts +0 -69
  254. package/typings/languages/it.d.ts +0 -51
  255. package/typings/languages/ja.d.ts +0 -58
  256. package/typings/languages/kn.d.ts +0 -11
  257. package/typings/languages/ko.d.ts +0 -25
  258. package/typings/languages/lt.d.ts +0 -110
  259. package/typings/languages/lv.d.ts +0 -99
  260. package/typings/languages/mr.d.ts +0 -12
  261. package/typings/languages/ms.d.ts +0 -37
  262. package/typings/languages/nb.d.ts +0 -27
  263. package/typings/languages/nl.d.ts +0 -65
  264. package/typings/languages/pa-Guru.d.ts +0 -1
  265. package/typings/languages/pl.d.ts +0 -116
  266. package/typings/languages/pt.d.ts +0 -39
  267. package/typings/languages/ro.d.ts +0 -229
  268. package/typings/languages/ru.d.ts +0 -108
  269. package/typings/languages/sr-Latn.d.ts +0 -98
  270. package/typings/languages/sv.d.ts +0 -30
  271. package/typings/languages/sw.d.ts +0 -1
  272. package/typings/languages/ta.d.ts +0 -1
  273. package/typings/languages/te.d.ts +0 -1
  274. package/typings/languages/th.d.ts +0 -1
  275. package/typings/languages/tr.d.ts +0 -46
  276. package/typings/languages/uk.d.ts +0 -117
  277. package/typings/languages/ur.d.ts +0 -1
  278. package/typings/languages/vi.d.ts +0 -116
  279. package/typings/languages/zh-Hans.d.ts +0 -57
  280. package/typings/n2words.d.ts +0 -177
@@ -1,145 +1,369 @@
1
- import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
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
- * @typedef {Object} FrenchOptions
5
- * @property {boolean} [withHyphenSeparator=false] Use hyphens (true) instead of spaces (false) in compounds.
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 language converter.
177
+ * Converts a non-negative integer to French words.
10
178
  *
11
- * Special handling:
12
- * - Pluralization of "cent" (hundred) and other words
13
- * - "et" (and) before odd numbers in tens place
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
- export class French extends GreedyScaleLanguage {
18
- negativeWord = 'moins'
19
- decimalSeparatorWord = 'virgule'
20
- zeroWord = 'zéro'
21
- scaleWordPairs = [
22
- [1_000_000_000_000_000_000_000_000_000n, 'quadrilliard'],
23
- [1_000_000_000_000_000_000_000_000n, 'quadrillion'],
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
- * Merges two adjacent word-number pairs according to French grammar rules.
79
- *
80
- * French-specific rules:
81
- * - Removes trailing 's' from multiples of 80 or 100 when followed by smaller numbers
82
- * - Adds 's' to hundreds when appropriate
83
- * - Uses "et" (and) when joining odd numbers to tens (e.g., "vingt et un")
84
- * - Hyphens for compound tens (e.g., "vingt-deux")
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
- ((currentNumber - 80n) % 100n === 0n || (currentNumber % 100n === 0n && currentNumber < 1000n)) &&
103
- nextNumber < 1_000_000n &&
104
- currentWord.at(-1) === 's'
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
- if (
110
- currentNumber < 1000n && nextNumber !== 1000n &&
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 (nextNumber < currentNumber && currentNumber < 100n) {
119
- if (nextNumber % 10n === 1n && currentNumber !== 80n) {
120
- return { [`${currentWord}${this.wordSeparator}et${this.wordSeparator}${nextWord}`]: currentNumber + nextNumber }
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
- if (nextNumber > currentNumber) return { [`${currentWord}${this.wordSeparator}${nextWord}`]: currentNumber * nextNumber }
126
- return { [`${currentWord}${this.wordSeparator}${nextWord}`]: currentNumber + nextNumber }
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 number to French cardinal (written) form.
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 number to convert.
134
- * @param {Object} [options] Conversion options (see FR class).
135
- * @returns {string} The number expressed in French words.
136
- * @throws {TypeError} If value is NaN or invalid type.
137
- * @throws {Error} If value is an invalid number string.
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
- * convertToWords(42, { lang: 'fr' }); // 'quarante-deux'
141
- * convertToWords(81, { lang: 'fr' }); // 'quatre-vingt-un'
340
+ * toWords(21) // 'vingt et un'
341
+ * toWords(80) // 'quatre-vingts'
342
+ * toWords(1000000) // 'un million'
142
343
  */
143
- export default function convertToWords (value, options = {}) {
144
- return new French(options).convertToWords(value)
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 }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a numeric value to Gujarati words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Gujarati words
6
+ */
7
+ export function toWords(value: number | string | bigint): string;