n2words 5.0.0 → 5.1.1

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 (309) hide show
  1. package/CHANGELOG.md +133 -40
  2. package/README.md +6 -4
  3. package/dist/am-ET.js +2 -2
  4. package/dist/am-ET.umd.js +2 -2
  5. package/dist/am-Latn-ET.js +2 -2
  6. package/dist/am-Latn-ET.umd.js +2 -2
  7. package/dist/ar-SA.js +2 -2
  8. package/dist/ar-SA.umd.js +2 -2
  9. package/dist/az-AZ.js +2 -2
  10. package/dist/az-AZ.umd.js +2 -2
  11. package/dist/bn-BD.js +2 -2
  12. package/dist/bn-BD.umd.js +2 -2
  13. package/dist/cs-CZ.js +2 -2
  14. package/dist/cs-CZ.umd.js +2 -2
  15. package/dist/da-DK.js +2 -2
  16. package/dist/da-DK.umd.js +2 -2
  17. package/dist/de-DE.js +2 -2
  18. package/dist/de-DE.umd.js +2 -2
  19. package/dist/el-GR.js +2 -2
  20. package/dist/el-GR.umd.js +2 -2
  21. package/dist/en-AU.js +2 -2
  22. package/dist/en-AU.umd.js +2 -2
  23. package/dist/en-BD.js +2 -2
  24. package/dist/en-BD.umd.js +2 -2
  25. package/dist/en-CA.js +2 -2
  26. package/dist/en-CA.umd.js +2 -2
  27. package/dist/en-GB.js +2 -2
  28. package/dist/en-GB.umd.js +2 -2
  29. package/dist/en-GH.js +2 -2
  30. package/dist/en-GH.umd.js +2 -2
  31. package/dist/en-IE.js +2 -2
  32. package/dist/en-IE.umd.js +2 -2
  33. package/dist/en-IN.js +2 -2
  34. package/dist/en-IN.umd.js +2 -2
  35. package/dist/en-KE.js +2 -2
  36. package/dist/en-KE.umd.js +2 -2
  37. package/dist/en-MY.js +2 -2
  38. package/dist/en-MY.umd.js +2 -2
  39. package/dist/en-NG.js +2 -2
  40. package/dist/en-NG.umd.js +2 -2
  41. package/dist/en-NZ.js +2 -2
  42. package/dist/en-NZ.umd.js +2 -2
  43. package/dist/en-PH.js +2 -2
  44. package/dist/en-PH.umd.js +2 -2
  45. package/dist/en-PK.js +2 -2
  46. package/dist/en-PK.umd.js +2 -2
  47. package/dist/en-SG.js +2 -2
  48. package/dist/en-SG.umd.js +2 -2
  49. package/dist/en-US.js +2 -2
  50. package/dist/en-US.umd.js +2 -2
  51. package/dist/en-ZA.js +2 -2
  52. package/dist/en-ZA.umd.js +2 -2
  53. package/dist/es-ES.js +2 -2
  54. package/dist/es-ES.umd.js +2 -2
  55. package/dist/es-MX.js +2 -2
  56. package/dist/es-MX.umd.js +2 -2
  57. package/dist/es-US.js +2 -2
  58. package/dist/es-US.umd.js +2 -2
  59. package/dist/fa-IR.js +2 -2
  60. package/dist/fa-IR.umd.js +2 -2
  61. package/dist/fi-FI.js +2 -2
  62. package/dist/fi-FI.umd.js +2 -2
  63. package/dist/fil-PH.js +2 -2
  64. package/dist/fil-PH.umd.js +2 -2
  65. package/dist/fr-BE.js +2 -2
  66. package/dist/fr-BE.umd.js +2 -2
  67. package/dist/fr-FR.js +2 -2
  68. package/dist/fr-FR.umd.js +2 -2
  69. package/dist/gu-IN.js +2 -2
  70. package/dist/gu-IN.umd.js +2 -2
  71. package/dist/ha-NG.js +2 -2
  72. package/dist/ha-NG.umd.js +2 -2
  73. package/dist/hbo-IL.js +2 -2
  74. package/dist/hbo-IL.umd.js +2 -2
  75. package/dist/he-IL.js +2 -2
  76. package/dist/he-IL.umd.js +2 -2
  77. package/dist/hi-IN.js +2 -2
  78. package/dist/hi-IN.umd.js +2 -2
  79. package/dist/hr-HR.js +2 -2
  80. package/dist/hr-HR.umd.js +2 -2
  81. package/dist/hu-HU.js +2 -2
  82. package/dist/hu-HU.umd.js +2 -2
  83. package/dist/id-ID.js +2 -2
  84. package/dist/id-ID.umd.js +2 -2
  85. package/dist/it-IT.js +2 -2
  86. package/dist/it-IT.umd.js +2 -2
  87. package/dist/ja-JP.js +2 -2
  88. package/dist/ja-JP.umd.js +2 -2
  89. package/dist/ka-GE.js +2 -2
  90. package/dist/ka-GE.umd.js +2 -2
  91. package/dist/kn-IN.js +2 -2
  92. package/dist/kn-IN.umd.js +2 -2
  93. package/dist/ko-KR.js +2 -2
  94. package/dist/ko-KR.umd.js +2 -2
  95. package/dist/lt-LT.js +2 -2
  96. package/dist/lt-LT.umd.js +2 -2
  97. package/dist/lv-LV.js +2 -2
  98. package/dist/lv-LV.umd.js +2 -2
  99. package/dist/mr-IN.js +2 -2
  100. package/dist/mr-IN.umd.js +2 -2
  101. package/dist/ms-MY.js +2 -2
  102. package/dist/ms-MY.umd.js +2 -2
  103. package/dist/nb-NO.js +2 -2
  104. package/dist/nb-NO.umd.js +2 -2
  105. package/dist/nl-NL.js +2 -2
  106. package/dist/nl-NL.umd.js +2 -2
  107. package/dist/pa-IN.js +2 -2
  108. package/dist/pa-IN.umd.js +2 -2
  109. package/dist/pl-PL.js +2 -2
  110. package/dist/pl-PL.umd.js +2 -2
  111. package/dist/pt-BR.js +2 -2
  112. package/dist/pt-BR.umd.js +2 -2
  113. package/dist/pt-PT.js +2 -2
  114. package/dist/pt-PT.umd.js +2 -2
  115. package/dist/ro-RO.js +2 -2
  116. package/dist/ro-RO.umd.js +2 -2
  117. package/dist/ru-RU.js +2 -2
  118. package/dist/ru-RU.umd.js +2 -2
  119. package/dist/sr-Cyrl-RS.js +2 -2
  120. package/dist/sr-Cyrl-RS.umd.js +2 -2
  121. package/dist/sr-Latn-RS.js +2 -2
  122. package/dist/sr-Latn-RS.umd.js +2 -2
  123. package/dist/sv-SE.js +2 -2
  124. package/dist/sv-SE.umd.js +2 -2
  125. package/dist/sw-KE.js +2 -2
  126. package/dist/sw-KE.umd.js +2 -2
  127. package/dist/ta-IN.js +2 -2
  128. package/dist/ta-IN.umd.js +2 -2
  129. package/dist/te-IN.js +2 -2
  130. package/dist/te-IN.umd.js +2 -2
  131. package/dist/th-TH.js +2 -2
  132. package/dist/th-TH.umd.js +2 -2
  133. package/dist/tr-TR.js +2 -2
  134. package/dist/tr-TR.umd.js +2 -2
  135. package/dist/uk-UA.js +2 -2
  136. package/dist/uk-UA.umd.js +2 -2
  137. package/dist/ur-PK.js +2 -2
  138. package/dist/ur-PK.umd.js +2 -2
  139. package/dist/vi-VN.js +2 -2
  140. package/dist/vi-VN.umd.js +2 -2
  141. package/dist/yo-NG.js +2 -2
  142. package/dist/yo-NG.umd.js +2 -2
  143. package/dist/zh-Hans-CN.js +2 -2
  144. package/dist/zh-Hans-CN.umd.js +2 -2
  145. package/dist/zh-Hant-TW.js +2 -2
  146. package/dist/zh-Hant-TW.umd.js +2 -2
  147. package/package.json +33 -24
  148. package/src/am-ET.d.ts +3 -5
  149. package/src/am-ET.js +41 -16
  150. package/src/am-Latn-ET.d.ts +3 -5
  151. package/src/am-Latn-ET.js +45 -16
  152. package/src/ar-SA.d.ts +44 -18
  153. package/src/ar-SA.js +93 -40
  154. package/src/az-AZ.d.ts +3 -5
  155. package/src/az-AZ.js +58 -20
  156. package/src/bn-BD.d.ts +3 -5
  157. package/src/bn-BD.js +32 -16
  158. package/src/cs-CZ.d.ts +3 -6
  159. package/src/cs-CZ.js +66 -42
  160. package/src/da-DK.d.ts +3 -6
  161. package/src/da-DK.js +53 -48
  162. package/src/de-DE.d.ts +17 -11
  163. package/src/de-DE.js +88 -57
  164. package/src/el-GR.d.ts +3 -6
  165. package/src/el-GR.js +45 -32
  166. package/src/en-AU.d.ts +17 -11
  167. package/src/en-AU.js +56 -41
  168. package/src/en-BD.d.ts +17 -11
  169. package/src/en-BD.js +60 -41
  170. package/src/en-CA.d.ts +36 -18
  171. package/src/en-CA.js +67 -46
  172. package/src/en-GB.d.ts +17 -11
  173. package/src/en-GB.js +56 -41
  174. package/src/en-GH.d.ts +32 -3
  175. package/src/en-GH.js +104 -26
  176. package/src/en-IE.d.ts +17 -11
  177. package/src/en-IE.js +56 -41
  178. package/src/en-IN.d.ts +17 -11
  179. package/src/en-IN.js +60 -41
  180. package/src/en-KE.d.ts +28 -3
  181. package/src/en-KE.js +93 -26
  182. package/src/en-MY.d.ts +26 -3
  183. package/src/en-MY.js +91 -26
  184. package/src/en-NG.d.ts +17 -11
  185. package/src/en-NG.js +56 -41
  186. package/src/en-NZ.d.ts +32 -3
  187. package/src/en-NZ.js +85 -31
  188. package/src/en-PH.d.ts +32 -3
  189. package/src/en-PH.js +97 -26
  190. package/src/en-PK.d.ts +17 -11
  191. package/src/en-PK.js +60 -41
  192. package/src/en-SG.d.ts +28 -3
  193. package/src/en-SG.js +93 -26
  194. package/src/en-US.d.ts +36 -18
  195. package/src/en-US.js +70 -47
  196. package/src/en-ZA.d.ts +17 -11
  197. package/src/en-ZA.js +56 -41
  198. package/src/es-ES.d.ts +53 -21
  199. package/src/es-ES.js +104 -56
  200. package/src/es-MX.d.ts +53 -21
  201. package/src/es-MX.js +104 -56
  202. package/src/es-US.d.ts +53 -21
  203. package/src/es-US.js +92 -51
  204. package/src/fa-IR.d.ts +3 -5
  205. package/src/fa-IR.js +28 -13
  206. package/src/fi-FI.d.ts +3 -6
  207. package/src/fi-FI.js +47 -29
  208. package/src/fil-PH.d.ts +3 -5
  209. package/src/fil-PH.js +61 -28
  210. package/src/fr-BE.d.ts +31 -15
  211. package/src/fr-BE.js +128 -57
  212. package/src/fr-FR.d.ts +31 -16
  213. package/src/fr-FR.js +97 -60
  214. package/src/gu-IN.d.ts +3 -5
  215. package/src/gu-IN.js +31 -16
  216. package/src/ha-NG.d.ts +3 -5
  217. package/src/ha-NG.js +55 -27
  218. package/src/hbo-IL.d.ts +26 -12
  219. package/src/hbo-IL.js +92 -51
  220. package/src/he-IL.d.ts +17 -10
  221. package/src/he-IL.js +92 -50
  222. package/src/hi-IN.d.ts +3 -5
  223. package/src/hi-IN.js +30 -17
  224. package/src/hr-HR.d.ts +21 -10
  225. package/src/hr-HR.js +89 -33
  226. package/src/hu-HU.d.ts +3 -5
  227. package/src/hu-HU.js +57 -23
  228. package/src/id-ID.d.ts +3 -5
  229. package/src/id-ID.js +56 -23
  230. package/src/it-IT.d.ts +17 -11
  231. package/src/it-IT.js +74 -43
  232. package/src/ja-JP.d.ts +3 -6
  233. package/src/ja-JP.js +39 -26
  234. package/src/ka-GE.d.ts +3 -6
  235. package/src/ka-GE.js +38 -26
  236. package/src/kn-IN.d.ts +3 -5
  237. package/src/kn-IN.js +31 -16
  238. package/src/ko-KR.d.ts +3 -6
  239. package/src/ko-KR.js +34 -26
  240. package/src/lt-LT.d.ts +21 -11
  241. package/src/lt-LT.js +64 -42
  242. package/src/lv-LV.d.ts +21 -11
  243. package/src/lv-LV.js +79 -51
  244. package/src/mr-IN.d.ts +3 -5
  245. package/src/mr-IN.js +31 -16
  246. package/src/ms-MY.d.ts +3 -5
  247. package/src/ms-MY.js +58 -24
  248. package/src/nb-NO.d.ts +3 -6
  249. package/src/nb-NO.js +54 -34
  250. package/src/nl-NL.d.ts +41 -20
  251. package/src/nl-NL.js +111 -69
  252. package/src/pa-IN.d.ts +3 -5
  253. package/src/pa-IN.js +32 -16
  254. package/src/pl-PL.d.ts +21 -11
  255. package/src/pl-PL.js +69 -45
  256. package/src/pt-BR.d.ts +22 -11
  257. package/src/pt-BR.js +93 -53
  258. package/src/pt-PT.d.ts +17 -11
  259. package/src/pt-PT.js +80 -48
  260. package/src/ro-RO.d.ts +21 -11
  261. package/src/ro-RO.js +77 -39
  262. package/src/ru-RU.d.ts +35 -15
  263. package/src/ru-RU.js +100 -38
  264. package/src/sr-Cyrl-RS.d.ts +35 -15
  265. package/src/sr-Cyrl-RS.js +106 -43
  266. package/src/sr-Latn-RS.d.ts +35 -15
  267. package/src/sr-Latn-RS.js +106 -43
  268. package/src/sv-SE.d.ts +3 -6
  269. package/src/sv-SE.js +53 -34
  270. package/src/sw-KE.d.ts +3 -5
  271. package/src/sw-KE.js +50 -20
  272. package/src/ta-IN.d.ts +3 -5
  273. package/src/ta-IN.js +29 -17
  274. package/src/te-IN.d.ts +3 -5
  275. package/src/te-IN.js +31 -16
  276. package/src/th-TH.d.ts +3 -5
  277. package/src/th-TH.js +42 -19
  278. package/src/tr-TR.d.ts +17 -11
  279. package/src/tr-TR.js +63 -37
  280. package/src/uk-UA.d.ts +21 -10
  281. package/src/uk-UA.js +89 -33
  282. package/src/ur-PK.d.ts +3 -5
  283. package/src/ur-PK.js +32 -16
  284. package/src/utils/check-max.d.ts +26 -0
  285. package/src/utils/check-max.js +33 -0
  286. package/src/utils/expand-scientific.d.ts +0 -4
  287. package/src/utils/expand-scientific.js +7 -9
  288. package/src/utils/is-plain-object.d.ts +3 -4
  289. package/src/utils/is-plain-object.js +3 -4
  290. package/src/utils/parse-cardinal.d.ts +1 -2
  291. package/src/utils/parse-cardinal.js +12 -9
  292. package/src/utils/parse-currency.d.ts +1 -2
  293. package/src/utils/parse-currency.js +9 -11
  294. package/src/utils/parse-ordinal.d.ts +0 -1
  295. package/src/utils/parse-ordinal.js +9 -10
  296. package/src/utils/resolve-options.d.ts +17 -0
  297. package/src/utils/resolve-options.js +56 -0
  298. package/src/utils/scale.d.ts +49 -0
  299. package/src/utils/scale.js +65 -0
  300. package/src/vi-VN.d.ts +3 -6
  301. package/src/vi-VN.js +41 -28
  302. package/src/yo-NG.d.ts +3 -5
  303. package/src/yo-NG.js +49 -33
  304. package/src/zh-Hans-CN.d.ts +45 -20
  305. package/src/zh-Hans-CN.js +84 -31
  306. package/src/zh-Hant-TW.d.ts +45 -20
  307. package/src/zh-Hant-TW.js +85 -34
  308. package/src/utils/validate-options.d.ts +0 -8
  309. package/src/utils/validate-options.js +0 -16
package/src/en-IE.js CHANGED
@@ -23,7 +23,9 @@
23
23
  import { parseCardinalValue } from './utils/parse-cardinal.js'
24
24
  import { parseCurrencyValue } from './utils/parse-currency.js'
25
25
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
26
- import { validateOptions } from './utils/validate-options.js'
26
+ import { checkMax } from './utils/check-max.js'
27
+ import { western } from './utils/scale.js'
28
+ import { resolveOptions } from './utils/resolve-options.js'
27
29
 
28
30
  // ============================================================================
29
31
  // Vocabulary (module-level constants)
@@ -38,9 +40,13 @@ const SCALES = [
38
40
  'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion',
39
41
  'decillion', 'undecillion', 'duodecillion', 'tredecillion', 'quattuordecillion',
40
42
  'quindecillion', 'sexdecillion', 'septendecillion', 'octodecillion', 'novemdecillion',
41
- 'vigintillion'
43
+ 'vigintillion',
42
44
  ]
43
45
 
46
+ export const cardinalMax = western(SCALES.length)
47
+ export const ordinalMax = western(SCALES.length)
48
+ export const currencyMax = western(SCALES.length)
49
+
44
50
  const HUNDRED = 'hundred'
45
51
  const ZERO = 'zero'
46
52
  const NEGATIVE = 'minus'
@@ -65,11 +71,10 @@ const segmentResult = { word: '', hasHundred: false }
65
71
 
66
72
  /**
67
73
  * Builds words for a 0-999 segment.
68
- *
69
74
  * @param {number} n - Number 0-999
70
- * @returns {{ word: string, hasHundred: boolean }}
75
+ * @returns {{ word: string, hasHundred: boolean }} The segment words and whether a hundreds part is present.
71
76
  */
72
- function buildSegment (n) {
77
+ function buildSegment(n) {
73
78
  if (n === 0) {
74
79
  segmentResult.word = ''
75
80
  segmentResult.hasHundred = false
@@ -84,9 +89,11 @@ function buildSegment (n) {
84
89
  let tensOnes = ''
85
90
  if (tens === 1) {
86
91
  tensOnes = TEENS[ones]
87
- } else if (tens >= 2) {
92
+ }
93
+ else if (tens >= 2) {
88
94
  tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
89
- } else if (ones > 0) {
95
+ }
96
+ else if (ones > 0) {
90
97
  tensOnes = ONES[ones]
91
98
  }
92
99
 
@@ -94,11 +101,13 @@ function buildSegment (n) {
94
101
  if (hundreds > 0) {
95
102
  if (tensOnes) {
96
103
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + ' and ' + tensOnes
97
- } else {
104
+ }
105
+ else {
98
106
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
99
107
  }
100
108
  segmentResult.hasHundred = true
101
- } else {
109
+ }
110
+ else {
102
111
  segmentResult.word = tensOnes
103
112
  segmentResult.hasHundred = false
104
113
  }
@@ -112,11 +121,10 @@ function buildSegment (n) {
112
121
 
113
122
  /**
114
123
  * Converts a non-negative integer to English words.
115
- *
116
124
  * @param {bigint} n - Non-negative integer to convert
117
125
  * @returns {string} English words
118
126
  */
119
- function integerToWords (n) {
127
+ function integerToWords(n) {
120
128
  if (n === 0n) return ZERO
121
129
 
122
130
  // Fast path: numbers < 1000
@@ -147,11 +155,10 @@ function integerToWords (n) {
147
155
  /**
148
156
  * Builds words for numbers >= 1,000,000.
149
157
  * Uses BigInt division for faster segment extraction.
150
- *
151
158
  * @param {bigint} n - Number >= 1,000,000
152
159
  * @returns {string} English words
153
160
  */
154
- function buildLargeNumberWords (n) {
161
+ function buildLargeNumberWords(n) {
155
162
  // Extract segments using BigInt division
156
163
  // Segments are stored least-significant first (index 0 = ones, 1 = thousands, etc.)
157
164
  const segments = []
@@ -194,7 +201,8 @@ function buildLargeNumberWords (n) {
194
201
  if (i > 0) {
195
202
  result += ' ' + SCALES[i - 1]
196
203
  prevWasScale = true
197
- } else {
204
+ }
205
+ else {
198
206
  prevWasScale = false
199
207
  }
200
208
  }
@@ -204,11 +212,10 @@ function buildLargeNumberWords (n) {
204
212
 
205
213
  /**
206
214
  * Converts decimal digits to English words.
207
- *
208
215
  * @param {string} decimalPart - Decimal digits (without the point)
209
216
  * @returns {string} English words for decimal part
210
217
  */
211
- function decimalPartToWords (decimalPart) {
218
+ function decimalPartToWords(decimalPart) {
212
219
  let result = ''
213
220
 
214
221
  // Handle leading zeros
@@ -234,19 +241,20 @@ function decimalPartToWords (decimalPart) {
234
241
  *
235
242
  * This is the main public API. It accepts any valid numeric input
236
243
  * (number, string, or bigint) and handles parsing internally.
237
- *
238
244
  * @param {number | string | bigint} value - The numeric value to convert
239
245
  * @returns {string} The number in English words
240
246
  * @throws {TypeError} If value is not a valid numeric type
241
247
  * @throws {Error} If value is not a valid number format
242
- *
243
248
  * @example
244
249
  * toCardinal(42) // 'forty-two'
245
250
  * toCardinal(-3.14) // 'minus three point fourteen'
246
251
  * toCardinal('1000000') // 'one million'
247
252
  */
248
- function toCardinal (value) {
253
+ function toCardinal(value) {
249
254
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
255
+ // Both the integer part and the decimal's significant digits are spelled via
256
+ // the scale builder, so both must clear the ceiling.
257
+ checkMax(integerPart, cardinalMax, decimalPart)
250
258
 
251
259
  let result = ''
252
260
 
@@ -270,11 +278,10 @@ function toCardinal (value) {
270
278
  /**
271
279
  * Builds ordinal words for a 0-999 segment (final segment only).
272
280
  * Returns ordinal form: "first", "twenty-third", "one hundred forty-fifth"
273
- *
274
281
  * @param {number} n - Number 0-999
275
282
  * @returns {string} Ordinal words for this segment
276
283
  */
277
- function buildOrdinalSegment (n) {
284
+ function buildOrdinalSegment(n) {
278
285
  const ones = n % 10
279
286
  const tens = Math.trunc(n / 10) % 10
280
287
  const hundreds = Math.trunc(n / 100)
@@ -284,15 +291,18 @@ function buildOrdinalSegment (n) {
284
291
  if (tens === 1) {
285
292
  // Teens: 10-19 → "tenth" through "nineteenth"
286
293
  tensOnesOrdinal = ORDINAL_TEENS[ones]
287
- } else if (tens >= 2) {
294
+ }
295
+ else if (tens >= 2) {
288
296
  if (ones > 0) {
289
297
  // Compound: "twenty-first", "thirty-second", etc.
290
298
  tensOnesOrdinal = TENS[tens] + '-' + ORDINAL_ONES[ones]
291
- } else {
299
+ }
300
+ else {
292
301
  // Round tens: "twentieth", "thirtieth", etc.
293
302
  tensOnesOrdinal = ORDINAL_TENS[tens]
294
303
  }
295
- } else if (ones > 0) {
304
+ }
305
+ else if (ones > 0) {
296
306
  // Single digit: "first", "second", etc.
297
307
  tensOnesOrdinal = ORDINAL_ONES[ones]
298
308
  }
@@ -302,7 +312,8 @@ function buildOrdinalSegment (n) {
302
312
  if (tensOnesOrdinal) {
303
313
  // "one hundred twenty-first"
304
314
  return ONES[hundreds] + ' ' + HUNDRED + ' ' + tensOnesOrdinal
305
- } else {
315
+ }
316
+ else {
306
317
  // "one hundredth", "two hundredth", etc.
307
318
  return ONES[hundreds] + ' hundredth'
308
319
  }
@@ -314,11 +325,10 @@ function buildOrdinalSegment (n) {
314
325
  /**
315
326
  * Converts a positive integer to ordinal words.
316
327
  * Generates ordinals directly without string manipulation.
317
- *
318
328
  * @param {bigint} n - Positive integer to convert
319
329
  * @returns {string} Ordinal English words
320
330
  */
321
- function integerToOrdinal (n) {
331
+ function integerToOrdinal(n) {
322
332
  // Fast path: numbers < 1000
323
333
  if (n < 1000n) {
324
334
  return buildOrdinalSegment(Number(n))
@@ -346,11 +356,10 @@ function integerToOrdinal (n) {
346
356
  /**
347
357
  * Builds ordinal words for numbers >= 1,000,000.
348
358
  * All segments except the final one are cardinal; final segment is ordinal.
349
- *
350
359
  * @param {bigint} n - Number >= 1,000,000
351
360
  * @returns {string} Ordinal English words
352
361
  */
353
- function buildLargeOrdinal (n) {
362
+ function buildLargeOrdinal(n) {
354
363
  // Extract segments (least-significant first)
355
364
  const segments = []
356
365
  let temp = n
@@ -384,11 +393,13 @@ function buildLargeOrdinal (n) {
384
393
  if (i === 0) {
385
394
  // Units position: use ordinal segment
386
395
  result += buildOrdinalSegment(segment)
387
- } else {
396
+ }
397
+ else {
388
398
  // Scale position with no remainder below: "one millionth"
389
399
  result += buildSegment(segment).word + ' ' + SCALES[i - 1] + 'th'
390
400
  }
391
- } else {
401
+ }
402
+ else {
392
403
  // Non-final segments are cardinal
393
404
  result += buildSegment(segment).word
394
405
  if (i > 0) {
@@ -402,12 +413,10 @@ function buildLargeOrdinal (n) {
402
413
 
403
414
  /**
404
415
  * Converts a numeric value to Irish English ordinal words.
405
- *
406
416
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
407
417
  * @returns {string} The number as ordinal words (e.g., "first", "forty-second")
408
418
  * @throws {TypeError} If value is not a valid numeric type
409
419
  * @throws {RangeError} If value is negative, zero, or has a decimal part
410
- *
411
420
  * @example
412
421
  * toOrdinal(1) // 'first'
413
422
  * toOrdinal(2) // 'second'
@@ -418,8 +427,9 @@ function buildLargeOrdinal (n) {
418
427
  * toOrdinal(101) // 'one hundred first'
419
428
  * toOrdinal(1000) // 'one thousandth'
420
429
  */
421
- function toOrdinal (value) {
430
+ function toOrdinal(value) {
422
431
  const integerPart = parseOrdinalValue(value)
432
+ checkMax(integerPart, ordinalMax)
423
433
  return integerToOrdinal(integerPart)
424
434
  }
425
435
 
@@ -427,16 +437,21 @@ function toOrdinal (value) {
427
437
  // CURRENCY: toCurrency(value, options?)
428
438
  // ============================================================================
429
439
 
440
+ /**
441
+ * @typedef {object} CurrencyOptions
442
+ * @property {boolean} [and] - Use "and" between euro and cent (e.g., "one euro and fifty cents")
443
+ */
444
+
445
+ /** @type {Required<CurrencyOptions>} */
446
+ export const currencyDefaults = { and: true }
447
+
430
448
  /**
431
449
  * Converts a numeric value to Irish English currency words.
432
- *
433
450
  * @param {number | string | bigint} value - The currency amount to convert
434
- * @param {Object} [options] - Optional configuration
435
- * @param {boolean} [options.and=true] - Use "and" between euro and cent (e.g., "one euro and fifty cents")
451
+ * @param {CurrencyOptions} [options] - Optional configuration
436
452
  * @returns {string} The amount in Irish English currency words
437
453
  * @throws {TypeError} If value is not a valid numeric type
438
454
  * @throws {Error} If value is not a valid number format
439
- *
440
455
  * @example
441
456
  * toCurrency(42.50) // 'forty-two euro and fifty cents'
442
457
  * toCurrency(1) // 'one euro'
@@ -444,10 +459,10 @@ function toOrdinal (value) {
444
459
  * toCurrency(0.01) // 'one cent'
445
460
  * toCurrency(42.50, { and: false }) // 'forty-two euro fifty cents'
446
461
  */
447
- function toCurrency (value, options) {
448
- options = validateOptions(options)
462
+ function toCurrency(value, options) {
449
463
  const { isNegative, dollars: euros, cents } = parseCurrencyValue(value)
450
- const { and: useAnd = true } = options
464
+ checkMax(euros, currencyMax)
465
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
451
466
 
452
467
  // Build result
453
468
  let result = ''
package/src/en-IN.d.ts CHANGED
@@ -1,11 +1,24 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CurrencyOptions
6
+ * @property {boolean} [and] - Use "and" between rupees and paise
7
+ */
8
+ /** @type {Required<CurrencyOptions>} */
9
+ export const currencyDefaults: Required<CurrencyOptions>;
10
+ export type CurrencyOptions = {
11
+ /**
12
+ * - Use "and" between rupees and paise
13
+ */
14
+ and?: boolean | undefined;
15
+ };
1
16
  /**
2
17
  * Converts a numeric value to English words using Indian numbering.
3
- *
4
18
  * @param {number | string | bigint} value - The numeric value to convert
5
19
  * @returns {string} The number in English words
6
20
  * @throws {TypeError} If value is not a valid numeric type
7
21
  * @throws {Error} If value is not a valid number format
8
- *
9
22
  * @example
10
23
  * toCardinal(42) // 'forty-two'
11
24
  * toCardinal(100000) // 'one lakh'
@@ -15,12 +28,10 @@
15
28
  export function toCardinal(value: number | string | bigint): string;
16
29
  /**
17
30
  * Converts a numeric value to English ordinal words using Indian numbering.
18
- *
19
31
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
20
32
  * @returns {string} The number as ordinal words
21
33
  * @throws {TypeError} If value is not a valid numeric type
22
34
  * @throws {RangeError} If value is negative, zero, or has a decimal part
23
- *
24
35
  * @example
25
36
  * toOrdinal(1) // 'first'
26
37
  * toOrdinal(100000) // 'one lakhth'
@@ -29,14 +40,11 @@ export function toCardinal(value: number | string | bigint): string;
29
40
  export function toOrdinal(value: number | string | bigint): string;
30
41
  /**
31
42
  * Converts a numeric value to Indian English currency words.
32
- *
33
43
  * @param {number | string | bigint} value - The currency amount to convert
34
- * @param {Object} [options] - Optional configuration
35
- * @param {boolean} [options.and=true] - Use "and" between rupees and paise
44
+ * @param {CurrencyOptions} [options] - Optional configuration
36
45
  * @returns {string} The amount in Indian English currency words
37
46
  * @throws {TypeError} If value is not a valid numeric type
38
47
  * @throws {Error} If value is not a valid number format
39
- *
40
48
  * @example
41
49
  * toCurrency(42.50) // 'forty-two rupees and fifty paise'
42
50
  * toCurrency(100000) // 'one lakh rupees'
@@ -44,6 +52,4 @@ export function toOrdinal(value: number | string | bigint): string;
44
52
  * toCurrency(0.50) // 'fifty paise'
45
53
  * toCurrency(42.50, { and: false }) // 'forty-two rupees fifty paise'
46
54
  */
47
- export function toCurrency(value: number | string | bigint, options?: {
48
- and?: boolean | undefined;
49
- }): string;
55
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;
package/src/en-IN.js CHANGED
@@ -13,7 +13,9 @@
13
13
  import { parseCardinalValue } from './utils/parse-cardinal.js'
14
14
  import { parseCurrencyValue } from './utils/parse-currency.js'
15
15
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
16
- import { validateOptions } from './utils/validate-options.js'
16
+ import { checkMax } from './utils/check-max.js'
17
+ import { indian } from './utils/scale.js'
18
+ import { resolveOptions } from './utils/resolve-options.js'
17
19
 
18
20
  // ============================================================================
19
21
  // Vocabulary (module-level constants)
@@ -26,6 +28,11 @@ const TENS = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy',
26
28
  // Indian numbering scales: 10^3, 10^5, 10^7, 10^9, 10^11, 10^13, 10^15, 10^17
27
29
  const SCALES = ['thousand', 'lakh', 'crore', 'arab', 'kharab', 'neel', 'padma', 'shankh']
28
30
 
31
+ // 3-2-2 grouping: a 3-digit base segment then 2 digits per Indian scale word.
32
+ export const cardinalMax = indian(SCALES.length + 1)
33
+ export const ordinalMax = indian(SCALES.length + 1)
34
+ export const currencyMax = indian(SCALES.length + 1)
35
+
29
36
  const HUNDRED = 'hundred'
30
37
  const ZERO = 'zero'
31
38
  const NEGATIVE = 'minus'
@@ -51,11 +58,10 @@ const segmentResult = { word: '', hasHundred: false }
51
58
 
52
59
  /**
53
60
  * Builds words for a 0-999 segment (British-style with "and" after hundreds).
54
- *
55
61
  * @param {number} n - Number 0-999
56
- * @returns {{ word: string, hasHundred: boolean }}
62
+ * @returns {{ word: string, hasHundred: boolean }} The segment words and whether a hundred was included
57
63
  */
58
- function buildSegment (n) {
64
+ function buildSegment(n) {
59
65
  if (n === 0) {
60
66
  segmentResult.word = ''
61
67
  segmentResult.hasHundred = false
@@ -70,9 +76,11 @@ function buildSegment (n) {
70
76
  let tensOnes = ''
71
77
  if (tens === 1) {
72
78
  tensOnes = TEENS[ones]
73
- } else if (tens >= 2) {
79
+ }
80
+ else if (tens >= 2) {
74
81
  tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
75
- } else if (ones > 0) {
82
+ }
83
+ else if (ones > 0) {
76
84
  tensOnes = ONES[ones]
77
85
  }
78
86
 
@@ -80,11 +88,13 @@ function buildSegment (n) {
80
88
  if (hundreds > 0) {
81
89
  if (tensOnes) {
82
90
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + ' and ' + tensOnes
83
- } else {
91
+ }
92
+ else {
84
93
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
85
94
  }
86
95
  segmentResult.hasHundred = true
87
- } else {
96
+ }
97
+ else {
88
98
  segmentResult.word = tensOnes
89
99
  segmentResult.hasHundred = false
90
100
  }
@@ -94,11 +104,10 @@ function buildSegment (n) {
94
104
 
95
105
  /**
96
106
  * Builds words for a 0-99 segment (no hundreds).
97
- *
98
107
  * @param {number} n - Number 0-99
99
- * @returns {string}
108
+ * @returns {string} The segment in words
100
109
  */
101
- function buildSmallSegment (n) {
110
+ function buildSmallSegment(n) {
102
111
  if (n === 0) return ''
103
112
 
104
113
  const ones = n % 10
@@ -106,7 +115,8 @@ function buildSmallSegment (n) {
106
115
 
107
116
  if (tens === 1) {
108
117
  return TEENS[ones]
109
- } else if (tens >= 2) {
118
+ }
119
+ else if (tens >= 2) {
110
120
  return ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
111
121
  }
112
122
  return ONES[ones]
@@ -121,11 +131,10 @@ function buildSmallSegment (n) {
121
131
  *
122
132
  * Uses BigInt modulo for segment extraction.
123
133
  * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
124
- *
125
134
  * @param {bigint} n - Non-negative integer to convert
126
135
  * @returns {string} English words
127
136
  */
128
- function integerToWords (n) {
137
+ function integerToWords(n) {
129
138
  if (n === 0n) return ZERO
130
139
 
131
140
  // Fast path: numbers < 1000
@@ -162,7 +171,8 @@ function integerToWords (n) {
162
171
  words.push('and')
163
172
  }
164
173
  words.push(word)
165
- } else {
174
+ }
175
+ else {
166
176
  // Other segments are 0-99
167
177
  words.push(buildSmallSegment(segment))
168
178
  // Add scale word
@@ -177,11 +187,10 @@ function integerToWords (n) {
177
187
 
178
188
  /**
179
189
  * Converts decimal digits to English words.
180
- *
181
190
  * @param {string} decimalPart - Decimal digits (without the point)
182
191
  * @returns {string} English words for decimal part
183
192
  */
184
- function decimalPartToWords (decimalPart) {
193
+ function decimalPartToWords(decimalPart) {
185
194
  let result = ''
186
195
 
187
196
  // Handle leading zeros
@@ -204,20 +213,21 @@ function decimalPartToWords (decimalPart) {
204
213
 
205
214
  /**
206
215
  * Converts a numeric value to English words using Indian numbering.
207
- *
208
216
  * @param {number | string | bigint} value - The numeric value to convert
209
217
  * @returns {string} The number in English words
210
218
  * @throws {TypeError} If value is not a valid numeric type
211
219
  * @throws {Error} If value is not a valid number format
212
- *
213
220
  * @example
214
221
  * toCardinal(42) // 'forty-two'
215
222
  * toCardinal(100000) // 'one lakh'
216
223
  * toCardinal(10000000) // 'one crore'
217
224
  * toCardinal(1234567) // 'twelve lakh thirty-four thousand five hundred and sixty-seven'
218
225
  */
219
- function toCardinal (value) {
226
+ function toCardinal(value) {
220
227
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
228
+ // Both the integer part and the decimal's significant digits are spelled via
229
+ // the scale builder, so both must clear the ceiling.
230
+ checkMax(integerPart, cardinalMax, decimalPart)
221
231
 
222
232
  let result = ''
223
233
 
@@ -240,11 +250,10 @@ function toCardinal (value) {
240
250
 
241
251
  /**
242
252
  * Builds ordinal words for a 0-999 segment (final segment only).
243
- *
244
253
  * @param {number} n - Number 0-999
245
254
  * @returns {string} Ordinal words for this segment
246
255
  */
247
- function buildOrdinalSegment (n) {
256
+ function buildOrdinalSegment(n) {
248
257
  const ones = n % 10
249
258
  const tens = Math.trunc(n / 10) % 10
250
259
  const hundreds = Math.trunc(n / 100)
@@ -253,13 +262,16 @@ function buildOrdinalSegment (n) {
253
262
  let tensOnesOrdinal = ''
254
263
  if (tens === 1) {
255
264
  tensOnesOrdinal = ORDINAL_TEENS[ones]
256
- } else if (tens >= 2) {
265
+ }
266
+ else if (tens >= 2) {
257
267
  if (ones > 0) {
258
268
  tensOnesOrdinal = TENS[tens] + '-' + ORDINAL_ONES[ones]
259
- } else {
269
+ }
270
+ else {
260
271
  tensOnesOrdinal = ORDINAL_TENS[tens]
261
272
  }
262
- } else if (ones > 0) {
273
+ }
274
+ else if (ones > 0) {
263
275
  tensOnesOrdinal = ORDINAL_ONES[ones]
264
276
  }
265
277
 
@@ -267,7 +279,8 @@ function buildOrdinalSegment (n) {
267
279
  if (hundreds > 0) {
268
280
  if (tensOnesOrdinal) {
269
281
  return ONES[hundreds] + ' ' + HUNDRED + ' ' + tensOnesOrdinal
270
- } else {
282
+ }
283
+ else {
271
284
  return ONES[hundreds] + ' hundredth'
272
285
  }
273
286
  }
@@ -277,11 +290,10 @@ function buildOrdinalSegment (n) {
277
290
 
278
291
  /**
279
292
  * Converts a positive integer to ordinal words using Indian numbering.
280
- *
281
293
  * @param {bigint} n - Positive integer to convert
282
294
  * @returns {string} Ordinal English words
283
295
  */
284
- function integerToOrdinal (n) {
296
+ function integerToOrdinal(n) {
285
297
  // Fast path: numbers < 1000
286
298
  if (n < 1000n) {
287
299
  return buildOrdinalSegment(Number(n))
@@ -323,16 +335,19 @@ function integerToOrdinal (n) {
323
335
  if (i === 0) {
324
336
  // Units position: use ordinal segment
325
337
  words.push(buildOrdinalSegment(segment))
326
- } else {
338
+ }
339
+ else {
327
340
  // Scale position with no remainder below: "one lakhth"
328
341
  words.push(buildSmallSegment(segment))
329
342
  words.push(SCALES[i - 1] + 'th')
330
343
  }
331
- } else {
344
+ }
345
+ else {
332
346
  // Non-final segments are cardinal
333
347
  if (i === 0) {
334
348
  words.push(buildSegment(segment).word)
335
- } else {
349
+ }
350
+ else {
336
351
  words.push(buildSmallSegment(segment))
337
352
  words.push(SCALES[i - 1])
338
353
  }
@@ -344,19 +359,18 @@ function integerToOrdinal (n) {
344
359
 
345
360
  /**
346
361
  * Converts a numeric value to English ordinal words using Indian numbering.
347
- *
348
362
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
349
363
  * @returns {string} The number as ordinal words
350
364
  * @throws {TypeError} If value is not a valid numeric type
351
365
  * @throws {RangeError} If value is negative, zero, or has a decimal part
352
- *
353
366
  * @example
354
367
  * toOrdinal(1) // 'first'
355
368
  * toOrdinal(100000) // 'one lakhth'
356
369
  * toOrdinal(100001) // 'one lakh first'
357
370
  */
358
- function toOrdinal (value) {
371
+ function toOrdinal(value) {
359
372
  const integerPart = parseOrdinalValue(value)
373
+ checkMax(integerPart, ordinalMax)
360
374
  return integerToOrdinal(integerPart)
361
375
  }
362
376
 
@@ -364,16 +378,21 @@ function toOrdinal (value) {
364
378
  // CURRENCY: toCurrency(value, options?)
365
379
  // ============================================================================
366
380
 
381
+ /**
382
+ * @typedef {object} CurrencyOptions
383
+ * @property {boolean} [and] - Use "and" between rupees and paise
384
+ */
385
+
386
+ /** @type {Required<CurrencyOptions>} */
387
+ export const currencyDefaults = { and: true }
388
+
367
389
  /**
368
390
  * Converts a numeric value to Indian English currency words.
369
- *
370
391
  * @param {number | string | bigint} value - The currency amount to convert
371
- * @param {Object} [options] - Optional configuration
372
- * @param {boolean} [options.and=true] - Use "and" between rupees and paise
392
+ * @param {CurrencyOptions} [options] - Optional configuration
373
393
  * @returns {string} The amount in Indian English currency words
374
394
  * @throws {TypeError} If value is not a valid numeric type
375
395
  * @throws {Error} If value is not a valid number format
376
- *
377
396
  * @example
378
397
  * toCurrency(42.50) // 'forty-two rupees and fifty paise'
379
398
  * toCurrency(100000) // 'one lakh rupees'
@@ -381,10 +400,10 @@ function toOrdinal (value) {
381
400
  * toCurrency(0.50) // 'fifty paise'
382
401
  * toCurrency(42.50, { and: false }) // 'forty-two rupees fifty paise'
383
402
  */
384
- function toCurrency (value, options) {
385
- options = validateOptions(options)
403
+ function toCurrency(value, options) {
386
404
  const { isNegative, dollars: rupees, cents: paise } = parseCurrencyValue(value)
387
- const { and: useAnd = true } = options
405
+ checkMax(rupees, currencyMax)
406
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
388
407
 
389
408
  // Build result
390
409
  let result = ''
package/src/en-KE.d.ts CHANGED
@@ -1,11 +1,36 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CurrencyOptions
6
+ * @property {boolean} [and] - Use "and" between shillings and cents
7
+ */
8
+ /** @type {Required<CurrencyOptions>} */
9
+ export const currencyDefaults: Required<CurrencyOptions>;
10
+ export type CurrencyOptions = {
11
+ /**
12
+ * - Use "and" between shillings and cents
13
+ */
14
+ and?: boolean | undefined;
15
+ };
1
16
  /**
2
17
  * Converts a numeric value to Kenyan English words.
3
- *
4
18
  * @param {number | string | bigint} value - The numeric value to convert
5
19
  * @returns {string} The number in English words
6
20
  * @throws {TypeError} If value is not a valid numeric type
7
21
  * @throws {Error} If value is not a valid number format
8
22
  */
9
23
  export function toCardinal(value: number | string | bigint): string;
10
- export function toOrdinal(value: any): string;
11
- export function toCurrency(value: any, options: any): string;
24
+ /**
25
+ * Converts a numeric value to Kenyan English ordinal words.
26
+ * @param {number | string | bigint} value - The numeric value to convert
27
+ * @returns {string} The ordinal in English words
28
+ */
29
+ export function toOrdinal(value: number | string | bigint): string;
30
+ /**
31
+ * Converts a numeric value to Kenyan English currency words.
32
+ * @param {number | string | bigint} value - The numeric value to convert
33
+ * @param {CurrencyOptions} [options] - Optional configuration
34
+ * @returns {string} The currency in English words
35
+ */
36
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;