n2words 5.0.0 → 5.1.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 (309) hide show
  1. package/CHANGELOG.md +128 -42
  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 +31 -22
  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 +100 -38
  266. package/src/sr-Latn-RS.d.ts +35 -15
  267. package/src/sr-Latn-RS.js +100 -38
  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-NG.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'
@@ -64,11 +70,10 @@ const segmentResult = { word: '', hasHundred: false }
64
70
 
65
71
  /**
66
72
  * Builds words for a 0-999 segment.
67
- *
68
73
  * @param {number} n - Number 0-999
69
- * @returns {{ word: string, hasHundred: boolean }}
74
+ * @returns {{ word: string, hasHundred: boolean }} The segment words and whether it includes a hundreds place
70
75
  */
71
- function buildSegment (n) {
76
+ function buildSegment(n) {
72
77
  if (n === 0) {
73
78
  segmentResult.word = ''
74
79
  segmentResult.hasHundred = false
@@ -83,9 +88,11 @@ function buildSegment (n) {
83
88
  let tensOnes = ''
84
89
  if (tens === 1) {
85
90
  tensOnes = TEENS[ones]
86
- } else if (tens >= 2) {
91
+ }
92
+ else if (tens >= 2) {
87
93
  tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
88
- } else if (ones > 0) {
94
+ }
95
+ else if (ones > 0) {
89
96
  tensOnes = ONES[ones]
90
97
  }
91
98
 
@@ -93,11 +100,13 @@ function buildSegment (n) {
93
100
  if (hundreds > 0) {
94
101
  if (tensOnes) {
95
102
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + ' and ' + tensOnes
96
- } else {
103
+ }
104
+ else {
97
105
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
98
106
  }
99
107
  segmentResult.hasHundred = true
100
- } else {
108
+ }
109
+ else {
101
110
  segmentResult.word = tensOnes
102
111
  segmentResult.hasHundred = false
103
112
  }
@@ -111,11 +120,10 @@ function buildSegment (n) {
111
120
 
112
121
  /**
113
122
  * Converts a non-negative integer to English words.
114
- *
115
123
  * @param {bigint} n - Non-negative integer to convert
116
124
  * @returns {string} English words
117
125
  */
118
- function integerToWords (n) {
126
+ function integerToWords(n) {
119
127
  if (n === 0n) return ZERO
120
128
 
121
129
  // Fast path: numbers < 1000
@@ -146,11 +154,10 @@ function integerToWords (n) {
146
154
  /**
147
155
  * Builds words for numbers >= 1,000,000.
148
156
  * Uses BigInt division for faster segment extraction.
149
- *
150
157
  * @param {bigint} n - Number >= 1,000,000
151
158
  * @returns {string} English words
152
159
  */
153
- function buildLargeNumberWords (n) {
160
+ function buildLargeNumberWords(n) {
154
161
  // Extract segments using BigInt division
155
162
  // Segments are stored least-significant first (index 0 = ones, 1 = thousands, etc.)
156
163
  const segments = []
@@ -193,7 +200,8 @@ function buildLargeNumberWords (n) {
193
200
  if (i > 0) {
194
201
  result += ' ' + SCALES[i - 1]
195
202
  prevWasScale = true
196
- } else {
203
+ }
204
+ else {
197
205
  prevWasScale = false
198
206
  }
199
207
  }
@@ -203,11 +211,10 @@ function buildLargeNumberWords (n) {
203
211
 
204
212
  /**
205
213
  * Converts decimal digits to English words.
206
- *
207
214
  * @param {string} decimalPart - Decimal digits (without the point)
208
215
  * @returns {string} English words for decimal part
209
216
  */
210
- function decimalPartToWords (decimalPart) {
217
+ function decimalPartToWords(decimalPart) {
211
218
  let result = ''
212
219
 
213
220
  // Handle leading zeros
@@ -233,19 +240,20 @@ function decimalPartToWords (decimalPart) {
233
240
  *
234
241
  * This is the main public API. It accepts any valid numeric input
235
242
  * (number, string, or bigint) and handles parsing internally.
236
- *
237
243
  * @param {number | string | bigint} value - The numeric value to convert
238
244
  * @returns {string} The number in English words
239
245
  * @throws {TypeError} If value is not a valid numeric type
240
246
  * @throws {Error} If value is not a valid number format
241
- *
242
247
  * @example
243
248
  * toCardinal(42) // 'forty-two'
244
249
  * toCardinal(-3.14) // 'minus three point fourteen'
245
250
  * toCardinal('1000000') // 'one million'
246
251
  */
247
- function toCardinal (value) {
252
+ function toCardinal(value) {
248
253
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
254
+ // Both the integer part and the decimal's significant digits are spelled via
255
+ // the scale builder, so both must clear the ceiling.
256
+ checkMax(integerPart, cardinalMax, decimalPart)
249
257
 
250
258
  let result = ''
251
259
 
@@ -269,11 +277,10 @@ function toCardinal (value) {
269
277
  /**
270
278
  * Builds ordinal words for a 0-999 segment (final segment only).
271
279
  * Returns ordinal form: "first", "twenty-third", "one hundred forty-fifth"
272
- *
273
280
  * @param {number} n - Number 0-999
274
281
  * @returns {string} Ordinal words for this segment
275
282
  */
276
- function buildOrdinalSegment (n) {
283
+ function buildOrdinalSegment(n) {
277
284
  const ones = n % 10
278
285
  const tens = Math.trunc(n / 10) % 10
279
286
  const hundreds = Math.trunc(n / 100)
@@ -283,15 +290,18 @@ function buildOrdinalSegment (n) {
283
290
  if (tens === 1) {
284
291
  // Teens: 10-19 → "tenth" through "nineteenth"
285
292
  tensOnesOrdinal = ORDINAL_TEENS[ones]
286
- } else if (tens >= 2) {
293
+ }
294
+ else if (tens >= 2) {
287
295
  if (ones > 0) {
288
296
  // Compound: "twenty-first", "thirty-second", etc.
289
297
  tensOnesOrdinal = TENS[tens] + '-' + ORDINAL_ONES[ones]
290
- } else {
298
+ }
299
+ else {
291
300
  // Round tens: "twentieth", "thirtieth", etc.
292
301
  tensOnesOrdinal = ORDINAL_TENS[tens]
293
302
  }
294
- } else if (ones > 0) {
303
+ }
304
+ else if (ones > 0) {
295
305
  // Single digit: "first", "second", etc.
296
306
  tensOnesOrdinal = ORDINAL_ONES[ones]
297
307
  }
@@ -301,7 +311,8 @@ function buildOrdinalSegment (n) {
301
311
  if (tensOnesOrdinal) {
302
312
  // "one hundred twenty-first"
303
313
  return ONES[hundreds] + ' ' + HUNDRED + ' ' + tensOnesOrdinal
304
- } else {
314
+ }
315
+ else {
305
316
  // "one hundredth", "two hundredth", etc.
306
317
  return ONES[hundreds] + ' hundredth'
307
318
  }
@@ -313,11 +324,10 @@ function buildOrdinalSegment (n) {
313
324
  /**
314
325
  * Converts a positive integer to ordinal words.
315
326
  * Generates ordinals directly without string manipulation.
316
- *
317
327
  * @param {bigint} n - Positive integer to convert
318
328
  * @returns {string} Ordinal English words
319
329
  */
320
- function integerToOrdinal (n) {
330
+ function integerToOrdinal(n) {
321
331
  // Fast path: numbers < 1000
322
332
  if (n < 1000n) {
323
333
  return buildOrdinalSegment(Number(n))
@@ -345,11 +355,10 @@ function integerToOrdinal (n) {
345
355
  /**
346
356
  * Builds ordinal words for numbers >= 1,000,000.
347
357
  * All segments except the final one are cardinal; final segment is ordinal.
348
- *
349
358
  * @param {bigint} n - Number >= 1,000,000
350
359
  * @returns {string} Ordinal English words
351
360
  */
352
- function buildLargeOrdinal (n) {
361
+ function buildLargeOrdinal(n) {
353
362
  // Extract segments (least-significant first)
354
363
  const segments = []
355
364
  let temp = n
@@ -383,11 +392,13 @@ function buildLargeOrdinal (n) {
383
392
  if (i === 0) {
384
393
  // Units position: use ordinal segment
385
394
  result += buildOrdinalSegment(segment)
386
- } else {
395
+ }
396
+ else {
387
397
  // Scale position with no remainder below: "one millionth"
388
398
  result += buildSegment(segment).word + ' ' + SCALES[i - 1] + 'th'
389
399
  }
390
- } else {
400
+ }
401
+ else {
391
402
  // Non-final segments are cardinal
392
403
  result += buildSegment(segment).word
393
404
  if (i > 0) {
@@ -401,12 +412,10 @@ function buildLargeOrdinal (n) {
401
412
 
402
413
  /**
403
414
  * Converts a numeric value to Nigerian English ordinal words.
404
- *
405
415
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
406
416
  * @returns {string} The number as ordinal words (e.g., "first", "forty-second")
407
417
  * @throws {TypeError} If value is not a valid numeric type
408
418
  * @throws {RangeError} If value is negative, zero, or has a decimal part
409
- *
410
419
  * @example
411
420
  * toOrdinal(1) // 'first'
412
421
  * toOrdinal(2) // 'second'
@@ -417,8 +426,9 @@ function buildLargeOrdinal (n) {
417
426
  * toOrdinal(101) // 'one hundred first'
418
427
  * toOrdinal(1000) // 'one thousandth'
419
428
  */
420
- function toOrdinal (value) {
429
+ function toOrdinal(value) {
421
430
  const integerPart = parseOrdinalValue(value)
431
+ checkMax(integerPart, ordinalMax)
422
432
  return integerToOrdinal(integerPart)
423
433
  }
424
434
 
@@ -426,16 +436,21 @@ function toOrdinal (value) {
426
436
  // CURRENCY: toCurrency(value, options?)
427
437
  // ============================================================================
428
438
 
439
+ /**
440
+ * @typedef {object} CurrencyOptions
441
+ * @property {boolean} [and] - Use "and" between naira and kobo (e.g., "one naira and fifty kobo")
442
+ */
443
+
444
+ /** @type {Required<CurrencyOptions>} */
445
+ export const currencyDefaults = { and: true }
446
+
429
447
  /**
430
448
  * Converts a numeric value to Nigerian English currency words.
431
- *
432
449
  * @param {number | string | bigint} value - The currency amount to convert
433
- * @param {Object} [options] - Optional configuration
434
- * @param {boolean} [options.and=true] - Use "and" between naira and kobo (e.g., "one naira and fifty kobo")
450
+ * @param {CurrencyOptions} [options] - Optional configuration
435
451
  * @returns {string} The amount in Nigerian English currency words
436
452
  * @throws {TypeError} If value is not a valid numeric type
437
453
  * @throws {Error} If value is not a valid number format
438
- *
439
454
  * @example
440
455
  * toCurrency(42.50) // 'forty-two naira and fifty kobo'
441
456
  * toCurrency(1) // 'one naira'
@@ -443,10 +458,10 @@ function toOrdinal (value) {
443
458
  * toCurrency(0.01) // 'one kobo'
444
459
  * toCurrency(42.50, { and: false }) // 'forty-two naira fifty kobo'
445
460
  */
446
- function toCurrency (value, options) {
447
- options = validateOptions(options)
461
+ function toCurrency(value, options) {
448
462
  const { isNegative, dollars: naira, cents: kobo } = parseCurrencyValue(value)
449
- const { and: useAnd = true } = options
463
+ checkMax(naira, currencyMax)
464
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
450
465
 
451
466
  // Build result
452
467
  let result = ''
package/src/en-NZ.d.ts CHANGED
@@ -1,11 +1,40 @@
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 dollars and cents
7
+ */
8
+ /** @type {Required<CurrencyOptions>} */
9
+ export const currencyDefaults: Required<CurrencyOptions>;
10
+ export type CurrencyOptions = {
11
+ /**
12
+ * - Use "and" between dollars and cents
13
+ */
14
+ and?: boolean | undefined;
15
+ };
1
16
  /**
2
17
  * Converts a numeric value to New Zealand 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 New Zealand English ordinal words.
26
+ * @param {number | string | bigint} value - The numeric value to convert (positive integer)
27
+ * @returns {string} The number as ordinal words
28
+ * @throws {TypeError} If value is not a valid numeric type
29
+ * @throws {RangeError} If value is negative, zero, or has a decimal part
30
+ */
31
+ export function toOrdinal(value: number | string | bigint): string;
32
+ /**
33
+ * Converts a numeric value to New Zealand English currency words (New Zealand Dollar).
34
+ * @param {number | string | bigint} value - The currency amount to convert
35
+ * @param {CurrencyOptions} [options] - Optional configuration
36
+ * @returns {string} The amount in New Zealand English currency words
37
+ * @throws {TypeError} If value is not a valid numeric type
38
+ * @throws {Error} If value is not a valid number format
39
+ */
40
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;
package/src/en-NZ.js CHANGED
@@ -20,7 +20,9 @@
20
20
  import { parseCardinalValue } from './utils/parse-cardinal.js'
21
21
  import { parseCurrencyValue } from './utils/parse-currency.js'
22
22
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
23
- import { validateOptions } from './utils/validate-options.js'
23
+ import { checkMax } from './utils/check-max.js'
24
+ import { western } from './utils/scale.js'
25
+ import { resolveOptions } from './utils/resolve-options.js'
24
26
 
25
27
  // ============================================================================
26
28
  // Vocabulary (module-level constants)
@@ -35,9 +37,13 @@ const SCALES = [
35
37
  'quintillion', 'sextillion', 'septillion', 'octillion', 'nonillion',
36
38
  'decillion', 'undecillion', 'duodecillion', 'tredecillion', 'quattuordecillion',
37
39
  'quindecillion', 'sexdecillion', 'septendecillion', 'octodecillion', 'novemdecillion',
38
- 'vigintillion'
40
+ 'vigintillion',
39
41
  ]
40
42
 
43
+ export const cardinalMax = western(SCALES.length)
44
+ export const ordinalMax = western(SCALES.length)
45
+ export const currencyMax = western(SCALES.length)
46
+
41
47
  const HUNDRED = 'hundred'
42
48
  const ZERO = 'zero'
43
49
  const NEGATIVE = 'minus'
@@ -63,11 +69,10 @@ const segmentResult = { word: '', hasHundred: false }
63
69
 
64
70
  /**
65
71
  * Builds words for a 0-999 segment.
66
- *
67
72
  * @param {number} n - Number 0-999
68
- * @returns {{ word: string, hasHundred: boolean }}
73
+ * @returns {{ word: string, hasHundred: boolean }} The segment words and whether a hundreds part is present
69
74
  */
70
- function buildSegment (n) {
75
+ function buildSegment(n) {
71
76
  if (n === 0) {
72
77
  segmentResult.word = ''
73
78
  segmentResult.hasHundred = false
@@ -82,9 +87,11 @@ function buildSegment (n) {
82
87
  let tensOnes = ''
83
88
  if (tens === 1) {
84
89
  tensOnes = TEENS[ones]
85
- } else if (tens >= 2) {
90
+ }
91
+ else if (tens >= 2) {
86
92
  tensOnes = ones > 0 ? TENS[tens] + '-' + ONES[ones] : TENS[tens]
87
- } else if (ones > 0) {
93
+ }
94
+ else if (ones > 0) {
88
95
  tensOnes = ONES[ones]
89
96
  }
90
97
 
@@ -92,11 +99,13 @@ function buildSegment (n) {
92
99
  if (hundreds > 0) {
93
100
  if (tensOnes) {
94
101
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED + ' and ' + tensOnes
95
- } else {
102
+ }
103
+ else {
96
104
  segmentResult.word = ONES[hundreds] + ' ' + HUNDRED
97
105
  }
98
106
  segmentResult.hasHundred = true
99
- } else {
107
+ }
108
+ else {
100
109
  segmentResult.word = tensOnes
101
110
  segmentResult.hasHundred = false
102
111
  }
@@ -110,11 +119,10 @@ function buildSegment (n) {
110
119
 
111
120
  /**
112
121
  * Converts a non-negative integer to English words.
113
- *
114
122
  * @param {bigint} n - Non-negative integer to convert
115
123
  * @returns {string} English words
116
124
  */
117
- function integerToWords (n) {
125
+ function integerToWords(n) {
118
126
  if (n === 0n) return ZERO
119
127
 
120
128
  // Fast path: numbers < 1000
@@ -144,11 +152,10 @@ function integerToWords (n) {
144
152
 
145
153
  /**
146
154
  * Builds words for numbers >= 1,000,000.
147
- *
148
155
  * @param {bigint} n - Number >= 1,000,000
149
156
  * @returns {string} English words
150
157
  */
151
- function buildLargeNumberWords (n) {
158
+ function buildLargeNumberWords(n) {
152
159
  const segments = []
153
160
  let temp = n
154
161
  while (temp > 0n) {
@@ -184,7 +191,8 @@ function buildLargeNumberWords (n) {
184
191
  if (i > 0) {
185
192
  result += ' ' + SCALES[i - 1]
186
193
  prevWasScale = true
187
- } else {
194
+ }
195
+ else {
188
196
  prevWasScale = false
189
197
  }
190
198
  }
@@ -194,11 +202,10 @@ function buildLargeNumberWords (n) {
194
202
 
195
203
  /**
196
204
  * Converts decimal digits to English words.
197
- *
198
205
  * @param {string} decimalPart - Decimal digits (without the point)
199
206
  * @returns {string} English words for decimal part
200
207
  */
201
- function decimalPartToWords (decimalPart) {
208
+ function decimalPartToWords(decimalPart) {
202
209
  let result = ''
203
210
 
204
211
  let i = 0
@@ -219,14 +226,16 @@ function decimalPartToWords (decimalPart) {
219
226
 
220
227
  /**
221
228
  * Converts a numeric value to New Zealand English words.
222
- *
223
229
  * @param {number | string | bigint} value - The numeric value to convert
224
230
  * @returns {string} The number in English words
225
231
  * @throws {TypeError} If value is not a valid numeric type
226
232
  * @throws {Error} If value is not a valid number format
227
233
  */
228
- function toCardinal (value) {
234
+ function toCardinal(value) {
229
235
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
236
+ // Both the integer part and the decimal's significant digits are spelled via
237
+ // the scale builder, so both must clear the ceiling.
238
+ checkMax(integerPart, cardinalMax, decimalPart)
230
239
 
231
240
  let result = ''
232
241
 
@@ -247,7 +256,12 @@ function toCardinal (value) {
247
256
  // ORDINAL
248
257
  // ============================================================================
249
258
 
250
- function buildOrdinalSegment (n) {
259
+ /**
260
+ * Builds ordinal words for a 0-999 segment.
261
+ * @param {number} n - Number 0-999
262
+ * @returns {string} Ordinal words
263
+ */
264
+ function buildOrdinalSegment(n) {
251
265
  const ones = n % 10
252
266
  const tens = Math.trunc(n / 10) % 10
253
267
  const hundreds = Math.trunc(n / 100)
@@ -255,20 +269,24 @@ function buildOrdinalSegment (n) {
255
269
  let tensOnesOrdinal = ''
256
270
  if (tens === 1) {
257
271
  tensOnesOrdinal = ORDINAL_TEENS[ones]
258
- } else if (tens >= 2) {
272
+ }
273
+ else if (tens >= 2) {
259
274
  if (ones > 0) {
260
275
  tensOnesOrdinal = TENS[tens] + '-' + ORDINAL_ONES[ones]
261
- } else {
276
+ }
277
+ else {
262
278
  tensOnesOrdinal = ORDINAL_TENS[tens]
263
279
  }
264
- } else if (ones > 0) {
280
+ }
281
+ else if (ones > 0) {
265
282
  tensOnesOrdinal = ORDINAL_ONES[ones]
266
283
  }
267
284
 
268
285
  if (hundreds > 0) {
269
286
  if (tensOnesOrdinal) {
270
287
  return ONES[hundreds] + ' ' + HUNDRED + ' ' + tensOnesOrdinal
271
- } else {
288
+ }
289
+ else {
272
290
  return ONES[hundreds] + ' hundredth'
273
291
  }
274
292
  }
@@ -276,7 +294,12 @@ function buildOrdinalSegment (n) {
276
294
  return tensOnesOrdinal
277
295
  }
278
296
 
279
- function integerToOrdinal (n) {
297
+ /**
298
+ * Converts a non-negative integer to English ordinal words.
299
+ * @param {bigint} n - Non-negative integer to convert
300
+ * @returns {string} English ordinal words
301
+ */
302
+ function integerToOrdinal(n) {
280
303
  if (n < 1000n) {
281
304
  return buildOrdinalSegment(Number(n))
282
305
  }
@@ -296,7 +319,12 @@ function integerToOrdinal (n) {
296
319
  return buildLargeOrdinal(n)
297
320
  }
298
321
 
299
- function buildLargeOrdinal (n) {
322
+ /**
323
+ * Builds ordinal words for numbers >= 1,000,000.
324
+ * @param {bigint} n - Number >= 1,000,000
325
+ * @returns {string} English ordinal words
326
+ */
327
+ function buildLargeOrdinal(n) {
300
328
  const segments = []
301
329
  let temp = n
302
330
  while (temp > 0n) {
@@ -325,10 +353,12 @@ function buildLargeOrdinal (n) {
325
353
  if (isLowestSegment) {
326
354
  if (i === 0) {
327
355
  result += buildOrdinalSegment(segment)
328
- } else {
356
+ }
357
+ else {
329
358
  result += buildSegment(segment).word + ' ' + SCALES[i - 1] + 'th'
330
359
  }
331
- } else {
360
+ }
361
+ else {
332
362
  result += buildSegment(segment).word
333
363
  if (i > 0) {
334
364
  result += ' ' + SCALES[i - 1]
@@ -339,8 +369,16 @@ function buildLargeOrdinal (n) {
339
369
  return result
340
370
  }
341
371
 
342
- function toOrdinal (value) {
372
+ /**
373
+ * Converts a numeric value to New Zealand English ordinal words.
374
+ * @param {number | string | bigint} value - The numeric value to convert (positive integer)
375
+ * @returns {string} The number as ordinal words
376
+ * @throws {TypeError} If value is not a valid numeric type
377
+ * @throws {RangeError} If value is negative, zero, or has a decimal part
378
+ */
379
+ function toOrdinal(value) {
343
380
  const integerPart = parseOrdinalValue(value)
381
+ checkMax(integerPart, ordinalMax)
344
382
  return integerToOrdinal(integerPart)
345
383
  }
346
384
 
@@ -348,10 +386,26 @@ function toOrdinal (value) {
348
386
  // CURRENCY
349
387
  // ============================================================================
350
388
 
351
- function toCurrency (value, options) {
352
- options = validateOptions(options)
389
+ /**
390
+ * @typedef {object} CurrencyOptions
391
+ * @property {boolean} [and] - Use "and" between dollars and cents
392
+ */
393
+
394
+ /** @type {Required<CurrencyOptions>} */
395
+ export const currencyDefaults = { and: true }
396
+
397
+ /**
398
+ * Converts a numeric value to New Zealand English currency words (New Zealand Dollar).
399
+ * @param {number | string | bigint} value - The currency amount to convert
400
+ * @param {CurrencyOptions} [options] - Optional configuration
401
+ * @returns {string} The amount in New Zealand English currency words
402
+ * @throws {TypeError} If value is not a valid numeric type
403
+ * @throws {Error} If value is not a valid number format
404
+ */
405
+ function toCurrency(value, options) {
353
406
  const { isNegative, dollars, cents } = parseCurrencyValue(value)
354
- const { and: useAnd = true } = options
407
+ checkMax(dollars, currencyMax)
408
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
355
409
 
356
410
  let result = ''
357
411
  if (isNegative) result = NEGATIVE + ' '
package/src/en-PH.d.ts CHANGED
@@ -1,11 +1,40 @@
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 pesos and centavos
7
+ */
8
+ /** @type {Required<CurrencyOptions>} */
9
+ export const currencyDefaults: Required<CurrencyOptions>;
10
+ export type CurrencyOptions = {
11
+ /**
12
+ * - Use "and" between pesos and centavos
13
+ */
14
+ and?: boolean | undefined;
15
+ };
1
16
  /**
2
17
  * Converts a numeric value to Philippine 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 Philippine English ordinal words.
26
+ * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
27
+ * @returns {string} The number as ordinal words (e.g., "first", "forty-second")
28
+ * @throws {TypeError} If value is not a valid numeric type
29
+ * @throws {RangeError} If value is negative, zero, or has a decimal part
30
+ */
31
+ export function toOrdinal(value: number | string | bigint): string;
32
+ /**
33
+ * Converts a numeric value to Philippine English currency words.
34
+ * @param {number | string | bigint} value - The numeric value to convert
35
+ * @param {CurrencyOptions} [options] - Optional configuration
36
+ * @returns {string} The amount in Philippine English currency words
37
+ * @throws {TypeError} If value is not a valid numeric type
38
+ * @throws {Error} If value is not a valid number format
39
+ */
40
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;