n2words 4.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 +53 -0
  2. package/README.md +14 -12
  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 -0
  112. package/dist/pt-BR.umd.js +2 -0
  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 +53 -36
  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 +42 -0
  257. package/src/pt-BR.js +574 -0
  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/fi-FI.js CHANGED
@@ -14,6 +14,8 @@
14
14
  import { parseCardinalValue } from './utils/parse-cardinal.js'
15
15
  import { parseCurrencyValue } from './utils/parse-currency.js'
16
16
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
17
+ import { checkMax } from './utils/check-max.js'
18
+ import { western } from './utils/scale.js'
17
19
 
18
20
  // ============================================================================
19
21
  // Vocabulary (module-level constants)
@@ -36,6 +38,15 @@ const DECIMAL_SEP = 'pilkku'
36
38
  // Long scale
37
39
  const SCALES = ['miljoona', 'miljardi', 'biljoona', 'triljoona']
38
40
 
41
+ // Supported magnitude ceiling (checked at the public entry points). SCALES is
42
+ // indexed [scaleIndex - 2] (units and thousands separate), so the ceiling is
43
+ // 10^((SCALES.length + 2) * 3) = 10^18. Ordinal (cardinal + suffix) and currency
44
+ // build on the cardinal, so they share it. Decimals are read digit-by-digit
45
+ // (no ceiling).
46
+ export const cardinalMax = western(SCALES.length + 1)
47
+ export const ordinalMax = western(SCALES.length + 1)
48
+ export const currencyMax = western(SCALES.length + 1)
49
+
39
50
  // ============================================================================
40
51
  // Ordinal Vocabulary
41
52
  // ============================================================================
@@ -66,8 +77,10 @@ const CENT_SINGULAR = 'sentti'
66
77
  /**
67
78
  * Builds segment word for 0-999.
68
79
  * Omits "yksi" before "sata" (hundred).
80
+ * @param {number} n - Integer 0-999 to convert
81
+ * @returns {string} Finnish words for the segment
69
82
  */
70
- function buildSegment (n) {
83
+ function buildSegment(n) {
71
84
  if (n === 0) return ''
72
85
 
73
86
  const ones = n % 10
@@ -80,7 +93,8 @@ function buildSegment (n) {
80
93
  if (hundreds > 0) {
81
94
  if (hundreds === 1) {
82
95
  parts.push(HUNDRED)
83
- } else {
96
+ }
97
+ else {
84
98
  parts.push(ONES[hundreds] + ' ' + HUNDRED)
85
99
  }
86
100
  }
@@ -90,13 +104,17 @@ function buildSegment (n) {
90
104
 
91
105
  if (tensOnes === 0) {
92
106
  // Just hundreds
93
- } else if (tensOnes < 10) {
107
+ }
108
+ else if (tensOnes < 10) {
94
109
  parts.push(ONES[ones])
95
- } else if (tensOnes < 20) {
110
+ }
111
+ else if (tensOnes < 20) {
96
112
  parts.push(TEENS[ones])
97
- } else if (ones === 0) {
113
+ }
114
+ else if (ones === 0) {
98
115
  parts.push(TENS[tens])
99
- } else {
116
+ }
117
+ else {
100
118
  // Compound: "kaksikymmentäyksi" (no space)
101
119
  parts.push(TENS[tens] + ONES[ones])
102
120
  }
@@ -110,11 +128,10 @@ function buildSegment (n) {
110
128
 
111
129
  /**
112
130
  * Converts a non-negative integer to Finnish words.
113
- *
114
131
  * @param {bigint} n - Non-negative integer to convert
115
132
  * @returns {string} Finnish words
116
133
  */
117
- function integerToWords (n) {
134
+ function integerToWords(n) {
118
135
  if (n === 0n) return ZERO
119
136
 
120
137
  // Fast path: numbers < 1000 (direct lookup)
@@ -131,7 +148,8 @@ function integerToWords (n) {
131
148
  let result
132
149
  if (thousands === 1) {
133
150
  result = THOUSAND
134
- } else {
151
+ }
152
+ else {
135
153
  result = buildSegment(thousands) + ' ' + THOUSAND
136
154
  }
137
155
 
@@ -148,11 +166,10 @@ function integerToWords (n) {
148
166
 
149
167
  /**
150
168
  * Builds words for numbers >= 1,000,000.
151
- *
152
169
  * @param {bigint} n - Number >= 1,000,000
153
170
  * @returns {string} Finnish words
154
171
  */
155
- function buildLargeNumberWords (n) {
172
+ function buildLargeNumberWords(n) {
156
173
  const numStr = n.toString()
157
174
  const len = numStr.length
158
175
 
@@ -184,14 +201,17 @@ function buildLargeNumberWords (n) {
184
201
  if (scaleIndex === 0) {
185
202
  // Units segment
186
203
  parts.push(segmentWord)
187
- } else if (scaleIndex === 1) {
204
+ }
205
+ else if (scaleIndex === 1) {
188
206
  // Thousands - omit "yksi" before tuhat
189
207
  if (segment === 1) {
190
208
  parts.push(THOUSAND)
191
- } else {
209
+ }
210
+ else {
192
211
  parts.push(segmentWord + ' ' + THOUSAND)
193
212
  }
194
- } else {
213
+ }
214
+ else {
195
215
  // Millions+ - keep "yksi" before scale words
196
216
  const scaleWord = SCALES[scaleIndex - 2]
197
217
  parts.push(segmentWord + ' ' + scaleWord)
@@ -206,18 +226,18 @@ function buildLargeNumberWords (n) {
206
226
 
207
227
  /**
208
228
  * Converts decimal digits to Finnish words (per-digit).
209
- *
210
229
  * @param {string} decimalPart - Decimal digits (without the point)
211
230
  * @returns {string} Finnish words for decimal part
212
231
  */
213
- function decimalPartToWords (decimalPart) {
232
+ function decimalPartToWords(decimalPart) {
214
233
  const parts = []
215
234
 
216
235
  for (const digit of decimalPart) {
217
236
  const d = parseInt(digit, 10)
218
237
  if (d === 0) {
219
238
  parts.push(ZERO)
220
- } else {
239
+ }
240
+ else {
221
241
  parts.push(ONES[d])
222
242
  }
223
243
  }
@@ -227,19 +247,18 @@ function decimalPartToWords (decimalPart) {
227
247
 
228
248
  /**
229
249
  * Converts a numeric value to Finnish words.
230
- *
231
250
  * @param {number | string | bigint} value - The numeric value to convert
232
251
  * @returns {string} The number in Finnish words
233
252
  * @throws {TypeError} If value is not a valid numeric type
234
253
  * @throws {Error} If value is not a valid number format
235
- *
236
254
  * @example
237
255
  * toCardinal(21) // 'kaksikymmentäyksi'
238
256
  * toCardinal(1000) // 'tuhat'
239
257
  * toCardinal('3.14') // 'kolme pilkku yksi neljä'
240
258
  */
241
- function toCardinal (value) {
259
+ function toCardinal(value) {
242
260
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
261
+ checkMax(integerPart, cardinalMax)
243
262
 
244
263
  let result = ''
245
264
 
@@ -263,8 +282,10 @@ function toCardinal (value) {
263
282
  /**
264
283
  * Builds ordinal segment for 0-99.
265
284
  * Finnish ordinals have special forms.
285
+ * @param {number} n - Integer 0-99 to convert
286
+ * @returns {string} Finnish ordinal words for the segment
266
287
  */
267
- function buildOrdinalSegment (n) {
288
+ function buildOrdinalSegment(n) {
268
289
  if (n === 0) return ''
269
290
  if (n < 10) return ORDINAL_ONES[n]
270
291
  if (n < 20) return ORDINAL_TEENS[n - 10]
@@ -286,11 +307,10 @@ function buildOrdinalSegment (n) {
286
307
  *
287
308
  * Finnish ordinals: ensimmäinen (1st), toinen (2nd), kolmas (3rd), etc.
288
309
  * For larger numbers, use cardinal + ordinal ending.
289
- *
290
310
  * @param {bigint} n - Positive integer to convert
291
311
  * @returns {string} Finnish ordinal words
292
312
  */
293
- function integerToOrdinal (n) {
313
+ function integerToOrdinal(n) {
294
314
  // Special cases
295
315
  if (n === 1n) return ORDINAL_FIRST
296
316
  if (n === 2n) return ORDINAL_SECOND
@@ -309,20 +329,19 @@ function integerToOrdinal (n) {
309
329
 
310
330
  /**
311
331
  * Converts a numeric value to Finnish ordinal words.
312
- *
313
332
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
314
333
  * @returns {string} The number as ordinal words
315
334
  * @throws {TypeError} If value is not a valid numeric type
316
335
  * @throws {RangeError} If value is negative, zero, or has a decimal part
317
- *
318
336
  * @example
319
337
  * toOrdinal(1) // 'ensimmäinen'
320
338
  * toOrdinal(2) // 'toinen'
321
339
  * toOrdinal(3) // 'kolmas'
322
340
  * toOrdinal(10) // 'kymmenes'
323
341
  */
324
- function toOrdinal (value) {
342
+ function toOrdinal(value) {
325
343
  const integerPart = parseOrdinalValue(value)
344
+ checkMax(integerPart, ordinalMax)
326
345
  return integerToOrdinal(integerPart)
327
346
  }
328
347
 
@@ -335,19 +354,18 @@ function toOrdinal (value) {
335
354
  *
336
355
  * Euro uses sentti as subunit (100 senttiä = 1 euro).
337
356
  * Finnish has singular/plural: 1 euro vs 2 euroa, 1 sentti vs 2 senttiä.
338
- *
339
357
  * @param {number | string | bigint} value - The currency amount to convert
340
358
  * @returns {string} The amount in Finnish currency words
341
359
  * @throws {TypeError} If value is not a valid numeric type
342
360
  * @throws {Error} If value is not a valid number format
343
- *
344
361
  * @example
345
362
  * toCurrency(1) // 'yksi euro'
346
363
  * toCurrency(42) // 'neljäkymmentäkaksi euroa'
347
364
  * toCurrency(1.50) // 'yksi euro viisikymmentä senttiä'
348
365
  */
349
- function toCurrency (value) {
366
+ function toCurrency(value) {
350
367
  const { isNegative, dollars: euros, cents } = parseCurrencyValue(value)
368
+ checkMax(euros, currencyMax)
351
369
 
352
370
  let result = ''
353
371
  if (isNegative) {
package/src/fil-PH.d.ts CHANGED
@@ -1,18 +1,18 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
1
4
  /**
2
5
  * Converts a numeric value to Filipino words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Filipino words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Filipino ordinal words.
10
- *
11
12
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
12
13
  * @returns {string} The number as ordinal words
13
14
  * @throws {TypeError} If value is not a valid numeric type
14
15
  * @throws {RangeError} If value is negative, zero, or has a decimal part
15
- *
16
16
  * @example
17
17
  * toOrdinal(1) // 'una'
18
18
  * toOrdinal(2) // 'ikalawa'
@@ -23,12 +23,10 @@ export function toOrdinal(value: number | string | bigint): string;
23
23
  * Converts a numeric value to Filipino currency words (Philippine Peso).
24
24
  *
25
25
  * Uses piso (peso) and sentimo (centavo).
26
- *
27
26
  * @param {number | string | bigint} value - The currency amount to convert
28
27
  * @returns {string} The amount in Filipino currency words
29
28
  * @throws {TypeError} If value is not a valid numeric type
30
29
  * @throws {Error} If value is not a valid number format
31
- *
32
30
  * @example
33
31
  * toCurrency(42) // 'apatnapu dalawang piso'
34
32
  * toCurrency(1.50) // 'isang piso at limampung sentimo'
package/src/fil-PH.js CHANGED
@@ -13,6 +13,8 @@
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 { checkMax } from './utils/check-max.js'
17
+ import { western } from './utils/scale.js'
16
18
 
17
19
  // ============================================================================
18
20
  // Vocabulary
@@ -33,6 +35,11 @@ const DECIMAL_SEP = 'punto'
33
35
  // Short scale with linker
34
36
  const SCALE_WORDS = ['', THOUSAND, 'milyong', 'bilyong', 'trilyong']
35
37
 
38
+ // Supported magnitude ceiling (checked at the public entry points), derived from the scale table.
39
+ export const cardinalMax = western(SCALE_WORDS.length - 1)
40
+ export const ordinalMax = western(SCALE_WORDS.length - 1)
41
+ export const currencyMax = western(SCALE_WORDS.length - 1)
42
+
36
43
  // ============================================================================
37
44
  // Ordinal Vocabulary
38
45
  // ============================================================================
@@ -42,7 +49,7 @@ const SCALE_WORDS = ['', THOUSAND, 'milyong', 'bilyong', 'trilyong']
42
49
  const ORDINAL_PREFIX = 'ika'
43
50
  const ORDINAL_SPECIAL = {
44
51
  1: 'una', // first
45
- 2: 'ikalawa' // second (contracted form)
52
+ 2: 'ikalawa', // second (contracted form)
46
53
  }
47
54
 
48
55
  // ============================================================================
@@ -58,7 +65,11 @@ const SENTIMO = 'sentimo'
58
65
 
59
66
  const VOWELS = ['a', 'e', 'i', 'o', 'u']
60
67
 
61
- function addLinker (word) {
68
+ /**
69
+ * @param {string} word - Word to append a linker to
70
+ * @returns {string} Word with "ng" or " na" linker appended
71
+ */
72
+ function addLinker(word) {
62
73
  const lastChar = word[word.length - 1]
63
74
  if (VOWELS.includes(lastChar)) {
64
75
  return word + 'ng'
@@ -70,7 +81,11 @@ function addLinker (word) {
70
81
  // Segment Building
71
82
  // ============================================================================
72
83
 
73
- function buildSegment (n) {
84
+ /**
85
+ * @param {number} n - Value 0-999 to convert to words
86
+ * @returns {string} Filipino words for the segment
87
+ */
88
+ function buildSegment(n) {
74
89
  if (n === 0) return ''
75
90
 
76
91
  const ones = n % 10
@@ -90,21 +105,26 @@ function buildSegment (n) {
90
105
 
91
106
  if (tensOnes === 0) {
92
107
  // Just hundreds
93
- } else if (tensOnes < 10) {
108
+ }
109
+ else if (tensOnes < 10) {
94
110
  // Single digit
95
111
  parts.push(ONES[ones])
96
- } else if (tensOnes < 20) {
112
+ }
113
+ else if (tensOnes < 20) {
97
114
  // Teens (10-19)
98
115
  parts.push(TEENS[ones])
99
- } else if (ones === 0) {
116
+ }
117
+ else if (ones === 0) {
100
118
  // Even tens (20, 30, 40, etc.)
101
119
  parts.push(TENS[tensDigit])
102
- } else {
120
+ }
121
+ else {
103
122
  // Tens + ones
104
123
  // limampu (50) gets special linker: "limampung anim" (56)
105
124
  if (tensDigit === 5) {
106
125
  parts.push(TENS[tensDigit] + 'ng ' + ONES[ones])
107
- } else {
126
+ }
127
+ else {
108
128
  parts.push(TENS[tensDigit] + ' ' + ONES[ones])
109
129
  }
110
130
  }
@@ -114,8 +134,10 @@ function buildSegment (n) {
114
134
 
115
135
  /**
116
136
  * Builds segment with linker added to last word (for use before scale words).
137
+ * @param {number} n - Value 0-999 to convert to words
138
+ * @returns {string} Filipino words with a trailing linker
117
139
  */
118
- function buildSegmentWithLinker (n) {
140
+ function buildSegmentWithLinker(n) {
119
141
  const segmentWord = buildSegment(n)
120
142
  if (!segmentWord) return ''
121
143
 
@@ -144,7 +166,11 @@ function buildSegmentWithLinker (n) {
144
166
  // Conversion Functions
145
167
  // ============================================================================
146
168
 
147
- function integerToWords (n) {
169
+ /**
170
+ * @param {bigint} n - Non-negative integer to convert to words
171
+ * @returns {string} Filipino words for the integer
172
+ */
173
+ function integerToWords(n) {
148
174
  if (n === 0n) return ZERO
149
175
 
150
176
  if (n < 1000n) {
@@ -159,7 +185,7 @@ function integerToWords (n) {
159
185
  * @param {bigint} n - Number >= 1000
160
186
  * @returns {string} Filipino words
161
187
  */
162
- function buildLargeNumberWords (n) {
188
+ function buildLargeNumberWords(n) {
163
189
  // Extract segments using BigInt division (faster than string slicing)
164
190
  const segmentValues = []
165
191
  let temp = n
@@ -181,7 +207,8 @@ function buildLargeNumberWords (n) {
181
207
 
182
208
  if (i === 0) {
183
209
  result += buildSegment(segment)
184
- } else {
210
+ }
211
+ else {
185
212
  // Add linker to segment before scale word
186
213
  const segmentWord = buildSegmentWithLinker(segment)
187
214
  result += segmentWord + ' ' + scaleWord
@@ -191,7 +218,11 @@ function buildLargeNumberWords (n) {
191
218
  return result
192
219
  }
193
220
 
194
- function decimalPartToWords (decimalPart) {
221
+ /**
222
+ * @param {string} decimalPart - Digits after the decimal separator
223
+ * @returns {string} Per-digit Filipino reading of the decimal part
224
+ */
225
+ function decimalPartToWords(decimalPart) {
195
226
  // Per-digit decimal reading
196
227
  const digits = []
197
228
  for (const char of decimalPart) {
@@ -203,12 +234,12 @@ function decimalPartToWords (decimalPart) {
203
234
 
204
235
  /**
205
236
  * Converts a numeric value to Filipino words.
206
- *
207
237
  * @param {number | string | bigint} value - The numeric value to convert
208
238
  * @returns {string} The number in Filipino words
209
239
  */
210
- function toCardinal (value) {
240
+ function toCardinal(value) {
211
241
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
242
+ checkMax(integerPart, cardinalMax)
212
243
 
213
244
  let result = ''
214
245
 
@@ -233,11 +264,10 @@ function toCardinal (value) {
233
264
  * Converts a non-negative integer to Filipino ordinal words.
234
265
  *
235
266
  * Filipino ordinals: una (1st), ikalawa (2nd), then ika- + cardinal.
236
- *
237
267
  * @param {bigint} n - Positive integer to convert
238
268
  * @returns {string} Filipino ordinal words
239
269
  */
240
- function integerToOrdinal (n) {
270
+ function integerToOrdinal(n) {
241
271
  // Special forms for 1st and 2nd
242
272
  if (n === 1n) return ORDINAL_SPECIAL[1]
243
273
  if (n === 2n) return ORDINAL_SPECIAL[2]
@@ -248,19 +278,18 @@ function integerToOrdinal (n) {
248
278
 
249
279
  /**
250
280
  * Converts a numeric value to Filipino ordinal words.
251
- *
252
281
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
253
282
  * @returns {string} The number as ordinal words
254
283
  * @throws {TypeError} If value is not a valid numeric type
255
284
  * @throws {RangeError} If value is negative, zero, or has a decimal part
256
- *
257
285
  * @example
258
286
  * toOrdinal(1) // 'una'
259
287
  * toOrdinal(2) // 'ikalawa'
260
288
  * toOrdinal(3) // 'ika-tatlo'
261
289
  */
262
- function toOrdinal (value) {
290
+ function toOrdinal(value) {
263
291
  const integerPart = parseOrdinalValue(value)
292
+ checkMax(integerPart, ordinalMax)
264
293
  return integerToOrdinal(integerPart)
265
294
  }
266
295
 
@@ -272,19 +301,18 @@ function toOrdinal (value) {
272
301
  * Converts a numeric value to Filipino currency words (Philippine Peso).
273
302
  *
274
303
  * Uses piso (peso) and sentimo (centavo).
275
- *
276
304
  * @param {number | string | bigint} value - The currency amount to convert
277
305
  * @returns {string} The amount in Filipino currency words
278
306
  * @throws {TypeError} If value is not a valid numeric type
279
307
  * @throws {Error} If value is not a valid number format
280
- *
281
308
  * @example
282
309
  * toCurrency(42) // 'apatnapu dalawang piso'
283
310
  * toCurrency(1.50) // 'isang piso at limampung sentimo'
284
311
  * toCurrency(-5) // 'negatibo limang piso'
285
312
  */
286
- function toCurrency (value) {
313
+ function toCurrency(value) {
287
314
  const { isNegative, dollars: pesos, cents: sentimos } = parseCurrencyValue(value)
315
+ checkMax(pesos, currencyMax)
288
316
 
289
317
  let result = ''
290
318
  if (isNegative) {
@@ -297,18 +325,21 @@ function toCurrency (value) {
297
325
  const pesoWords = integerToWords(pesos)
298
326
  if (pesos === 0n) {
299
327
  result += pesoWords + ' ' + PESO
300
- } else {
328
+ }
329
+ else {
301
330
  // Need to add linker to the last word before "piso"
302
331
  const lastSpaceIdx = pesoWords.lastIndexOf(' ')
303
332
  if (lastSpaceIdx === -1) {
304
333
  // Single word
305
334
  result += addLinker(pesoWords) + ' ' + PESO
306
- } else {
335
+ }
336
+ else {
307
337
  const prefix = pesoWords.slice(0, lastSpaceIdx + 1)
308
338
  const lastWord = pesoWords.slice(lastSpaceIdx + 1)
309
339
  if (lastWord.endsWith('ng')) {
310
340
  result += pesoWords + ' ' + PESO
311
- } else {
341
+ }
342
+ else {
312
343
  result += prefix + addLinker(lastWord) + ' ' + PESO
313
344
  }
314
345
  }
@@ -326,12 +357,14 @@ function toCurrency (value) {
326
357
  if (lastSpaceIdx === -1) {
327
358
  // Single word
328
359
  result += addLinker(sentimoWords) + ' ' + SENTIMO
329
- } else {
360
+ }
361
+ else {
330
362
  const prefix = sentimoWords.slice(0, lastSpaceIdx + 1)
331
363
  const lastWord = sentimoWords.slice(lastSpaceIdx + 1)
332
364
  if (lastWord.endsWith('ng')) {
333
365
  result += sentimoWords + ' ' + SENTIMO
334
- } else {
366
+ }
367
+ else {
335
368
  result += prefix + addLinker(lastWord) + ' ' + SENTIMO
336
369
  }
337
370
  }
package/src/fr-BE.d.ts CHANGED
@@ -1,25 +1,46 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CardinalOptions
6
+ * @property {boolean} [withHyphenSeparator] - Use hyphens between words
7
+ */
8
+ /** @type {Required<CardinalOptions>} */
9
+ export const cardinalDefaults: Required<CardinalOptions>;
10
+ /**
11
+ * @typedef {object} CurrencyOptions
12
+ * @property {boolean} [and] - Use "et" between euros and centimes
13
+ */
14
+ /** @type {Required<CurrencyOptions>} */
15
+ export const currencyDefaults: Required<CurrencyOptions>;
16
+ export type CardinalOptions = {
17
+ /**
18
+ * - Use hyphens between words
19
+ */
20
+ withHyphenSeparator?: boolean | undefined;
21
+ };
22
+ export type CurrencyOptions = {
23
+ /**
24
+ * - Use "et" between euros and centimes
25
+ */
26
+ and?: boolean | undefined;
27
+ };
1
28
  /**
2
29
  * Converts a numeric value to Belgian French words.
3
- *
4
30
  * @param {number | string | bigint} value - The numeric value to convert
5
- * @param {Object} [options] - Optional configuration
6
- * @param {boolean} [options.withHyphenSeparator=false] - Use hyphens between words
31
+ * @param {CardinalOptions} [options] - Optional configuration
7
32
  * @returns {string} The number in Belgian French words
8
33
  */
9
- export function toCardinal(value: number | string | bigint, options?: {
10
- withHyphenSeparator?: boolean | undefined;
11
- }): string;
34
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
12
35
  /**
13
36
  * Converts a numeric value to Belgian French ordinal words.
14
37
  *
15
38
  * Belgian French ordinals: premier (1st), then cardinal + ième.
16
39
  * Special rules: quatre→quatrième, cinq→cinquième, neuf→neuvième.
17
- *
18
40
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
19
41
  * @returns {string} The number as ordinal words
20
42
  * @throws {TypeError} If value is not a valid numeric type
21
43
  * @throws {RangeError} If value is negative, zero, or has a decimal part
22
- *
23
44
  * @example
24
45
  * toOrdinal(1) // 'premier'
25
46
  * toOrdinal(2) // 'deuxième'
@@ -29,14 +50,11 @@ export function toCardinal(value: number | string | bigint, options?: {
29
50
  export function toOrdinal(value: number | string | bigint): string;
30
51
  /**
31
52
  * Converts a numeric value to Belgian French currency words (Euro).
32
- *
33
53
  * @param {number | string | bigint} value - The currency amount to convert
34
- * @param {Object} [options] - Optional configuration
35
- * @param {boolean} [options.and=true] - Use "et" between euros and centimes
54
+ * @param {CurrencyOptions} [options] - Optional configuration
36
55
  * @returns {string} The amount in Belgian French currency words
37
56
  * @throws {TypeError} If value is not a valid numeric type
38
57
  * @throws {Error} If value is not a valid number format
39
- *
40
58
  * @example
41
59
  * toCurrency(42.50) // 'quarante-deux euros et cinquante centimes'
42
60
  * toCurrency(1) // 'un euro'
@@ -44,6 +62,4 @@ export function toOrdinal(value: number | string | bigint): string;
44
62
  * toCurrency(0.01) // 'un centime'
45
63
  * toCurrency(42.50, { and: false }) // 'quarante-deux euros cinquante centimes'
46
64
  */
47
- export function toCurrency(value: number | string | bigint, options?: {
48
- and?: boolean | undefined;
49
- }): string;
65
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;