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/pt-PT.js CHANGED
@@ -14,7 +14,9 @@
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 { validateOptions } from './utils/validate-options.js'
17
+ import { checkMax } from './utils/check-max.js'
18
+ import { western } from './utils/scale.js'
19
+ import { resolveOptions } from './utils/resolve-options.js'
18
20
 
19
21
  // ============================================================================
20
22
  // Vocabulary (module-level constants)
@@ -51,8 +53,10 @@ const CENTIMOS = 'cêntimos'
51
53
  /**
52
54
  * Builds segment word for 0-999 with Portuguese "e" rules.
53
55
  * Returns the word and whether it's an exact hundred (for "cem" handling).
56
+ * @param {number} n - Number 0-999
57
+ * @returns {{ word: string, isExactHundred: boolean, startsWithHundreds?: boolean }} The segment word and "e"/"cem" handling flags
54
58
  */
55
- function buildSegment (n) {
59
+ function buildSegment(n) {
56
60
  if (n === 0) return { word: '', isExactHundred: false }
57
61
 
58
62
  // Special case: exact 100 is "cem"
@@ -73,14 +77,17 @@ function buildSegment (n) {
73
77
  if (tens === 1) {
74
78
  // Teens (10-19)
75
79
  parts.push(TEENS[ones])
76
- } else if (tens >= 2) {
80
+ }
81
+ else if (tens >= 2) {
77
82
  if (ones > 0) {
78
83
  // Tens + ones with "e": "vinte e um"
79
84
  parts.push(TENS[tens] + ' e ' + ONES[ones])
80
- } else {
85
+ }
86
+ else {
81
87
  parts.push(TENS[tens])
82
88
  }
83
- } else if (ones > 0) {
89
+ }
90
+ else if (ones > 0) {
84
91
  parts.push(ONES[ones])
85
92
  }
86
93
 
@@ -105,7 +112,7 @@ const SCALE_WORDS_SINGULAR = [
105
112
  'mil biliões', // 5: 10^15 (compound)
106
113
  'trilião', // 6: 10^18
107
114
  'mil triliões', // 7: 10^21 (compound)
108
- 'quatrilião' // 8: 10^24
115
+ 'quatrilião', // 8: 10^24
109
116
  ]
110
117
 
111
118
  const SCALE_WORDS_PLURAL = [
@@ -117,20 +124,32 @@ const SCALE_WORDS_PLURAL = [
117
124
  'mil biliões', // 5: 10^15 (compound, same)
118
125
  'triliões', // 6: 10^18
119
126
  'mil triliões', // 7: 10^21 (compound, same)
120
- 'quatriliões' // 8: 10^24
127
+ 'quatriliões', // 8: 10^24
121
128
  ]
122
129
 
130
+ // Scale ordinal words (long scale for pt-PT). Module-scope so its length can
131
+ // derive the ordinal ceiling and so it isn't rebuilt on every call.
132
+ const SCALE_ORDINAL = ['', 'milésimo', 'milionésimo', 'mil milionésimo', 'bilionésimo', 'mil bilionésimo', 'trilionésimo']
133
+
134
+ // Supported magnitude ceilings (checked at the public entry points). Cardinal
135
+ // scale words reach index SCALE_WORDS_SINGULAR.length-1 (quatrilião, 10^24), so
136
+ // cardinals/currency must stay below 10^27. The ordinal of a number whose
137
+ // lowest non-zero group is a scale group uses SCALE_ORDINAL, which is shorter,
138
+ // so ordinals must stay below 10^(SCALE_ORDINAL.length * 3).
139
+ export const cardinalMax = western(SCALE_WORDS_SINGULAR.length - 1)
140
+ export const ordinalMax = western(SCALE_ORDINAL.length - 1)
141
+ export const currencyMax = western(SCALE_WORDS_SINGULAR.length - 1)
142
+
123
143
  // ============================================================================
124
144
  // Conversion Functions
125
145
  // ============================================================================
126
146
 
127
147
  /**
128
148
  * Converts a non-negative integer to Portuguese words.
129
- *
130
149
  * @param {bigint} n - Non-negative integer to convert
131
150
  * @returns {string} Portuguese words
132
151
  */
133
- function integerToWords (n) {
152
+ function integerToWords(n) {
134
153
  if (n === 0n) return ZERO
135
154
 
136
155
  // Fast path: numbers < 1000
@@ -147,7 +166,8 @@ function integerToWords (n) {
147
166
  if (thousands === 1) {
148
167
  // "mil" not "um mil"
149
168
  result = THOUSAND
150
- } else {
169
+ }
170
+ else {
151
171
  result = buildSegment(thousands).word + ' ' + THOUSAND
152
172
  }
153
173
 
@@ -156,7 +176,8 @@ function integerToWords (n) {
156
176
  // Insert "e" before remainder if it doesn't start with hundreds (< 100)
157
177
  if (!remainderResult.startsWithHundreds) {
158
178
  result += ' e ' + remainderResult.word
159
- } else {
179
+ }
180
+ else {
160
181
  result += ' ' + remainderResult.word
161
182
  }
162
183
  }
@@ -171,11 +192,10 @@ function integerToWords (n) {
171
192
  /**
172
193
  * Builds words for numbers >= 1,000,000.
173
194
  * Uses BigInt division for faster segment extraction.
174
- *
175
195
  * @param {bigint} n - Number >= 1,000,000
176
196
  * @returns {string} Portuguese words
177
197
  */
178
- function buildLargeNumberWords (n) {
198
+ function buildLargeNumberWords(n) {
179
199
  // Extract segments using BigInt division
180
200
  // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
181
201
  const segments = []
@@ -216,20 +236,24 @@ function buildLargeNumberWords (n) {
216
236
  // Units segment
217
237
  result += segmentResult.word
218
238
  prevWasScale = false
219
- } else if (i === 1) {
239
+ }
240
+ else if (i === 1) {
220
241
  // Thousands
221
242
  if (segment === 1) {
222
243
  result += THOUSAND
223
- } else {
244
+ }
245
+ else {
224
246
  result += segmentResult.word + ' ' + THOUSAND
225
247
  }
226
248
  prevWasScale = true
227
- } else {
249
+ }
250
+ else {
228
251
  // Million and above - use scale arrays
229
252
  const scaleWord = segment === 1 ? SCALE_WORDS_SINGULAR[i] : SCALE_WORDS_PLURAL[i]
230
253
  if (segment === 1) {
231
254
  result += 'um ' + scaleWord
232
- } else {
255
+ }
256
+ else {
233
257
  result += segmentResult.word + ' ' + scaleWord
234
258
  }
235
259
  prevWasScale = true
@@ -241,11 +265,10 @@ function buildLargeNumberWords (n) {
241
265
 
242
266
  /**
243
267
  * Converts decimal digits to Portuguese words.
244
- *
245
268
  * @param {string} decimalPart - Decimal digits (without the point)
246
269
  * @returns {string} Portuguese words for decimal part
247
270
  */
248
- function decimalPartToWords (decimalPart) {
271
+ function decimalPartToWords(decimalPart) {
249
272
  let result = ''
250
273
 
251
274
  // Handle leading zeros
@@ -271,19 +294,20 @@ function decimalPartToWords (decimalPart) {
271
294
  *
272
295
  * This is the main public API. It accepts any valid numeric input
273
296
  * (number, string, or bigint) and handles parsing internally.
274
- *
275
297
  * @param {number | string | bigint} value - The numeric value to convert
276
298
  * @returns {string} The number in Portuguese words
277
299
  * @throws {TypeError} If value is not a valid numeric type
278
300
  * @throws {Error} If value is not a valid number format
279
- *
280
301
  * @example
281
302
  * toCardinal(21) // 'vinte e um'
282
303
  * toCardinal(100) // 'cem'
283
304
  * toCardinal(1000000) // 'um milhão'
284
305
  */
285
- function toCardinal (value) {
306
+ function toCardinal(value) {
286
307
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
308
+ // Both the integer part and the decimal's significant digits are spelled via
309
+ // the scale builder, so both must clear the ceiling.
310
+ checkMax(integerPart, cardinalMax, decimalPart)
287
311
 
288
312
  let result = ''
289
313
 
@@ -306,11 +330,10 @@ function toCardinal (value) {
306
330
 
307
331
  /**
308
332
  * Builds ordinal words for 0-999.
309
- *
310
333
  * @param {number} n - Number 0-999
311
334
  * @returns {string} Portuguese ordinal words
312
335
  */
313
- function buildOrdinalSegment (n) {
336
+ function buildOrdinalSegment(n) {
314
337
  if (n === 0) return ''
315
338
 
316
339
  const ones = n % 10
@@ -328,12 +351,14 @@ function buildOrdinalSegment (n) {
328
351
  if (tens === 1) {
329
352
  // 10-19: use teens array (décimo, décimo primeiro, etc.)
330
353
  parts.push(ORDINAL_TEENS[ones])
331
- } else if (tens >= 2) {
354
+ }
355
+ else if (tens >= 2) {
332
356
  parts.push(ORDINAL_TENS[tens])
333
357
  if (ones > 0) {
334
358
  parts.push(ORDINAL_ONES[ones])
335
359
  }
336
- } else if (ones > 0) {
360
+ }
361
+ else if (ones > 0) {
337
362
  parts.push(ORDINAL_ONES[ones])
338
363
  }
339
364
 
@@ -342,11 +367,10 @@ function buildOrdinalSegment (n) {
342
367
 
343
368
  /**
344
369
  * Builds ordinal words for large numbers.
345
- *
346
370
  * @param {bigint} n - Non-negative integer
347
371
  * @returns {string} Portuguese ordinal words
348
372
  */
349
- function buildLargeOrdinal (n) {
373
+ function buildLargeOrdinal(n) {
350
374
  // Extract segments
351
375
  const segments = []
352
376
  let temp = n
@@ -364,9 +388,6 @@ function buildLargeOrdinal (n) {
364
388
  }
365
389
  }
366
390
 
367
- // Scale ordinal words (singular forms)
368
- const SCALE_ORDINAL = ['', 'milésimo', 'milionésimo', 'mil milionésimo', 'bilionésimo', 'mil bilionésimo', 'trilionésimo']
369
-
370
391
  let result = ''
371
392
 
372
393
  for (let i = segments.length - 1; i >= 0; i--) {
@@ -380,28 +401,35 @@ function buildLargeOrdinal (n) {
380
401
  if (i === 0) {
381
402
  // Units: just ordinal
382
403
  result += buildOrdinalSegment(segment)
383
- } else if (segment === 1 && i > 0) {
404
+ }
405
+ else if (segment === 1 && i > 0) {
384
406
  // Exact scale: "milésimo", "milionésimo", etc.
385
407
  result += SCALE_ORDINAL[i]
386
- } else {
408
+ }
409
+ else {
387
410
  // Segment + scale ordinal
388
411
  result += buildOrdinalSegment(segment) + ' ' + SCALE_ORDINAL[i]
389
412
  }
390
- } else {
413
+ }
414
+ else {
391
415
  // Higher segments use cardinal form
392
416
  if (i === 0) {
393
417
  result += buildSegment(segment).word
394
- } else if (i === 1) {
418
+ }
419
+ else if (i === 1) {
395
420
  if (segment === 1) {
396
421
  result += THOUSAND
397
- } else {
422
+ }
423
+ else {
398
424
  result += buildSegment(segment).word + ' ' + THOUSAND
399
425
  }
400
- } else {
426
+ }
427
+ else {
401
428
  const scaleWord = segment === 1 ? SCALE_WORDS_SINGULAR[i] : SCALE_WORDS_PLURAL[i]
402
429
  if (segment === 1) {
403
430
  result += 'um ' + scaleWord
404
- } else {
431
+ }
432
+ else {
405
433
  result += buildSegment(segment).word + ' ' + scaleWord
406
434
  }
407
435
  }
@@ -413,17 +441,16 @@ function buildLargeOrdinal (n) {
413
441
 
414
442
  /**
415
443
  * Converts a number to Portuguese ordinal words.
416
- *
417
444
  * @param {number | string | bigint} value - The number to convert
418
445
  * @returns {string} Portuguese ordinal words
419
- *
420
446
  * @example
421
447
  * toOrdinal(1) // 'primeiro'
422
448
  * toOrdinal(21) // 'vigésimo primeiro'
423
449
  * toOrdinal(100) // 'centésimo'
424
450
  */
425
- function toOrdinal (value) {
451
+ function toOrdinal(value) {
426
452
  const n = parseOrdinalValue(value)
453
+ checkMax(n, ordinalMax)
427
454
 
428
455
  // Fast path: 1-9
429
456
  if (n < 10n) {
@@ -458,23 +485,28 @@ function toOrdinal (value) {
458
485
  // Currency Functions
459
486
  // ============================================================================
460
487
 
488
+ /**
489
+ * @typedef {object} CurrencyOptions
490
+ * @property {boolean} [and] - Include "e" between euros and cents
491
+ */
492
+
493
+ /** @type {Required<CurrencyOptions>} */
494
+ export const currencyDefaults = { and: true }
495
+
461
496
  /**
462
497
  * Converts a number to Portuguese currency words (Euro).
463
- *
464
498
  * @param {number | string | bigint} value - The amount to convert
465
- * @param {Object} [options]
466
- * @param {boolean} [options.and=true] - Include "e" between euros and cents
499
+ * @param {CurrencyOptions} [options] Currency formatting options
467
500
  * @returns {string} Portuguese currency words
468
- *
469
501
  * @example
470
502
  * toCurrency(42.50) // 'quarenta e dois euros e cinquenta cêntimos'
471
503
  * toCurrency(1) // 'um euro'
472
504
  * toCurrency(0.01) // 'um cêntimo'
473
505
  */
474
- function toCurrency (value, options) {
475
- options = validateOptions(options)
506
+ function toCurrency(value, options) {
476
507
  const { isNegative, dollars: euros, cents } = parseCurrencyValue(value)
477
- const { and = true } = options
508
+ checkMax(euros, currencyMax)
509
+ const { and } = resolveOptions(options, currencyDefaults)
478
510
 
479
511
  let result = ''
480
512
 
package/src/ro-RO.d.ts CHANGED
@@ -1,29 +1,41 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CardinalOptions
6
+ * @property {('masculine'|'feminine')} [gender] - Gender for numbers
7
+ */
8
+ /** @type {Required<CardinalOptions>} */
9
+ export const cardinalDefaults: Required<CardinalOptions>;
10
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
11
+ export const cardinalValues: {
12
+ gender: ReadonlyArray<Required<CardinalOptions>["gender"]>;
13
+ };
14
+ export type CardinalOptions = {
15
+ /**
16
+ * - Gender for numbers
17
+ */
18
+ gender?: "feminine" | "masculine" | undefined;
19
+ };
1
20
  /**
2
21
  * Converts a numeric value to Romanian words.
3
- *
4
22
  * @param {number | string | bigint} value - The numeric value to convert
5
- * @param {Object} [options] - Conversion options
6
- * @param {string} [options.gender='masculine'] - Gender for numbers
23
+ * @param {CardinalOptions} [options] - Conversion options
7
24
  * @returns {string} The number in Romanian words
8
25
  * @throws {TypeError} If value is not a valid numeric type
9
26
  * @throws {Error} If value is not a valid number format
10
- *
11
27
  * @example
12
28
  * toCardinal(21) // 'douăzeci și unu'
13
29
  * toCardinal(1, { gender: 'feminine' }) // 'una'
14
30
  * toCardinal(1000) // 'o mie'
15
31
  */
16
- export function toCardinal(value: number | string | bigint, options?: {
17
- gender?: string | undefined;
18
- }): string;
32
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
19
33
  /**
20
34
  * Converts a numeric value to Romanian ordinal words.
21
- *
22
35
  * @param {number | string | bigint} value - The numeric value to convert
23
36
  * @returns {string} The ordinal in Romanian words
24
37
  * @throws {TypeError} If value is not a valid numeric type
25
38
  * @throws {Error} If value is not a positive integer
26
- *
27
39
  * @example
28
40
  * toOrdinal(1) // 'primul'
29
41
  * toOrdinal(21) // 'douăzeci și primul'
@@ -31,12 +43,10 @@ export function toCardinal(value: number | string | bigint, options?: {
31
43
  export function toOrdinal(value: number | string | bigint): string;
32
44
  /**
33
45
  * Converts a numeric value to Romanian Leu currency words.
34
- *
35
46
  * @param {number | string | bigint} value - The numeric value to convert
36
47
  * @returns {string} The currency in Romanian words
37
48
  * @throws {TypeError} If value is not a valid numeric type
38
49
  * @throws {Error} If value is not a valid number format
39
- *
40
50
  * @example
41
51
  * toCurrency(1) // 'un leu'
42
52
  * toCurrency(2.50) // 'doi lei cincizeci de bani'