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/sr-Cyrl-RS.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
@@ -56,7 +58,7 @@ const ORDINAL_SCALES = [
56
58
  'трилионити',
57
59
  'трилијардити',
58
60
  'квадрилионити',
59
- 'квадрилијардити'
61
+ 'квадрилијардити',
60
62
  ]
61
63
 
62
64
  // ============================================================================
@@ -79,14 +81,27 @@ const SCALE_FORMS = [
79
81
  ['трилион', 'трилиона', 'трилиона'],
80
82
  ['трилијарда', 'трилијарде', 'трилијарда'],
81
83
  ['квадрилион', 'квадрилиона', 'квадрилиона'],
82
- ['квадрилијарда', 'квадрилијарде', 'квадрилијарда']
84
+ ['квадрилијарда', 'квадрилијарде', 'квадрилијарда'],
83
85
  ]
84
86
 
87
+ // Supported magnitude ceilings (checked at the public entry points). Both the
88
+ // cardinal SCALE_FORMS and the ORDINAL_SCALES tables cover units + N scale
89
+ // groups, so values must stay below 10^((length + 1) * 3) = 10^30.
90
+ export const cardinalMax = western(SCALE_FORMS.length)
91
+ export const ordinalMax = western(ORDINAL_SCALES.length)
92
+ export const currencyMax = western(SCALE_FORMS.length)
93
+
85
94
  // ============================================================================
86
95
  // Segment Building
87
96
  // ============================================================================
88
97
 
89
- function pluralize (n, forms) {
98
+ /**
99
+ * Selects the correct plural form for a count.
100
+ * @param {number | bigint} n - The count
101
+ * @param {string[]} forms - Plural forms [one, few, many]
102
+ * @returns {string} The selected plural form
103
+ */
104
+ function pluralize(n, forms) {
90
105
  const num = typeof n === 'bigint' ? Number(n) : n
91
106
  const lastDigit = num % 10
92
107
  const lastTwoDigits = num % 100
@@ -100,7 +115,12 @@ function pluralize (n, forms) {
100
115
  return forms[2]
101
116
  }
102
117
 
103
- function buildSegmentMasc (n) {
118
+ /**
119
+ * Builds the masculine word form for a 0-999 segment.
120
+ * @param {number} n - Number 0-999
121
+ * @returns {string} The segment words
122
+ */
123
+ function buildSegmentMasc(n) {
104
124
  if (n === 0) return ''
105
125
 
106
126
  const onesDigit = n % 10
@@ -119,14 +139,20 @@ function buildSegmentMasc (n) {
119
139
 
120
140
  if (tensDigit === 1) {
121
141
  parts.push(TEENS[onesDigit])
122
- } else if (onesDigit > 0) {
142
+ }
143
+ else if (onesDigit > 0) {
123
144
  parts.push(ONES_MASC[onesDigit])
124
145
  }
125
146
 
126
147
  return parts.join(' ')
127
148
  }
128
149
 
129
- function buildSegmentFem (n) {
150
+ /**
151
+ * Builds the feminine word form for a 0-999 segment.
152
+ * @param {number} n - Number 0-999
153
+ * @returns {string} The segment words
154
+ */
155
+ function buildSegmentFem(n) {
130
156
  if (n === 0) return ''
131
157
 
132
158
  const onesDigit = n % 10
@@ -145,7 +171,8 @@ function buildSegmentFem (n) {
145
171
 
146
172
  if (tensDigit === 1) {
147
173
  parts.push(TEENS[onesDigit])
148
- } else if (onesDigit > 0) {
174
+ }
175
+ else if (onesDigit > 0) {
149
176
  parts.push(ONES_FEM[onesDigit])
150
177
  }
151
178
 
@@ -156,7 +183,13 @@ function buildSegmentFem (n) {
156
183
  // Conversion Functions
157
184
  // ============================================================================
158
185
 
159
- function integerToWords (n, gender) {
186
+ /**
187
+ * Converts a non-negative integer to Serbian words.
188
+ * @param {bigint} n - The integer to convert
189
+ * @param {('masculine'|'feminine')} gender - Grammatical gender
190
+ * @returns {string} The integer in words
191
+ */
192
+ function integerToWords(n, gender) {
160
193
  if (n === 0n) return ZERO
161
194
 
162
195
  if (n < 1000n) {
@@ -166,7 +199,13 @@ function integerToWords (n, gender) {
166
199
  return buildLargeNumberWords(n, gender)
167
200
  }
168
201
 
169
- function buildLargeNumberWords (n, gender) {
202
+ /**
203
+ * Builds words for integers >= 1000 using scale decomposition.
204
+ * @param {bigint} n - The integer to convert (>= 1000)
205
+ * @param {('masculine'|'feminine')} gender - Grammatical gender
206
+ * @returns {string} The integer in words
207
+ */
208
+ function buildLargeNumberWords(n, gender) {
170
209
  const numStr = n.toString()
171
210
  const len = numStr.length
172
211
 
@@ -193,7 +232,8 @@ function buildLargeNumberWords (n, gender) {
193
232
  if (segment !== 0) {
194
233
  if (scaleIndex === 0) {
195
234
  parts.push(gender === 'feminine' ? buildSegmentFem(segment) : buildSegmentMasc(segment))
196
- } else {
235
+ }
236
+ else {
197
237
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
198
238
  const scaleWord = pluralize(segment, scaleForms)
199
239
  // Thousands (scaleIndex=1) are feminine, others masculine
@@ -209,7 +249,13 @@ function buildLargeNumberWords (n, gender) {
209
249
  return parts.join(' ')
210
250
  }
211
251
 
212
- function decimalPartToWords (decimalPart, gender) {
252
+ /**
253
+ * Converts the decimal-part digit string to Serbian words.
254
+ * @param {string} decimalPart - The decimal digits as a string
255
+ * @param {('masculine'|'feminine')} gender - Grammatical gender
256
+ * @returns {string} The decimal part in words
257
+ */
258
+ function decimalPartToWords(decimalPart, gender) {
213
259
  let result = ''
214
260
  let i = 0
215
261
 
@@ -228,20 +274,31 @@ function decimalPartToWords (decimalPart, gender) {
228
274
  return result
229
275
  }
230
276
 
277
+ /**
278
+ * @typedef {object} CardinalOptions
279
+ * @property {('masculine'|'feminine')} [gender] - Grammatical gender
280
+ */
281
+
282
+ /** @type {Required<CardinalOptions>} */
283
+ export const cardinalDefaults = { gender: 'masculine' }
284
+
285
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
286
+ export const cardinalValues = { gender: ['masculine', 'feminine'] }
287
+
231
288
  /**
232
289
  * Converts a numeric value to Serbian (Cyrillic) words.
233
- *
234
290
  * @param {number | string | bigint} value - The numeric value to convert
235
- * @param {Object} [options] - Optional configuration
236
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
291
+ * @param {CardinalOptions} [options] - Optional configuration
237
292
  * @returns {string} The number in Serbian Cyrillic words
238
293
  */
239
- function toCardinal (value, options) {
240
- options = validateOptions(options)
294
+ function toCardinal(value, options) {
241
295
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
296
+ // Both the integer part and the decimal's significant digits are spelled via
297
+ // the scale builder, so both must clear the ceiling.
298
+ checkMax(integerPart, cardinalMax, decimalPart)
242
299
 
243
300
  // Apply option defaults
244
- const { gender = 'masculine' } = options
301
+ const { gender } = resolveOptions(options, cardinalDefaults, cardinalValues)
245
302
 
246
303
  let result = ''
247
304
 
@@ -265,11 +322,10 @@ function toCardinal (value, options) {
265
322
  /**
266
323
  * Builds ordinal for a 0-99 segment when it's the final (ordinal) part.
267
324
  * Returns ordinal form: "први", "двадесет први", etc.
268
- *
269
325
  * @param {number} n - Number 0-99
270
326
  * @returns {string} Ordinal words
271
327
  */
272
- function buildOrdinalTensOnes (n) {
328
+ function buildOrdinalTensOnes(n) {
273
329
  if (n === 0) return ''
274
330
 
275
331
  const onesDigit = n % 10
@@ -300,11 +356,10 @@ function buildOrdinalTensOnes (n) {
300
356
  *
301
357
  * In Serbian ordinals, only the LAST component becomes ordinal.
302
358
  * E.g., 121 = "сто двадесет први" (one hundred twenty first)
303
- *
304
359
  * @param {bigint} n - Positive integer to convert
305
360
  * @returns {string} Ordinal Serbian words
306
361
  */
307
- function integerToOrdinal (n) {
362
+ function integerToOrdinal(n) {
308
363
  // Fast path: numbers < 100
309
364
  if (n < 100n) {
310
365
  return buildOrdinalTensOnes(Number(n))
@@ -352,11 +407,10 @@ function integerToOrdinal (n) {
352
407
  /**
353
408
  * Builds ordinal words for numbers >= 1,000,000.
354
409
  * All segments except the final one are cardinal; final segment is ordinal.
355
- *
356
410
  * @param {bigint} n - Number >= 1,000,000
357
411
  * @returns {string} Ordinal Serbian words
358
412
  */
359
- function buildLargeOrdinal (n) {
413
+ function buildLargeOrdinal(n) {
360
414
  const numStr = n.toString()
361
415
  const len = numStr.length
362
416
 
@@ -394,22 +448,26 @@ function buildLargeOrdinal (n) {
394
448
  // Units position (no scale)
395
449
  if (isLastNonZero) {
396
450
  parts.push(integerToOrdinal(BigInt(segment)))
397
- } else {
451
+ }
452
+ else {
398
453
  parts.push(buildSegmentMasc(segment))
399
454
  }
400
- } else {
455
+ }
456
+ else {
401
457
  // Has scale word
402
458
  if (isLastNonZero) {
403
459
  // This scale position is the final ordinal
404
460
  if (segment === 1) {
405
461
  parts.push(ORDINAL_SCALES[scaleIndex - 1])
406
- } else {
462
+ }
463
+ else {
407
464
  // Use cardinal segment + ordinal scale
408
465
  const isFeminine = scaleIndex === 1 // thousands are feminine
409
466
  const segmentWord = isFeminine ? buildSegmentFem(segment) : buildSegmentMasc(segment)
410
467
  parts.push(segmentWord + ' ' + ORDINAL_SCALES[scaleIndex - 1])
411
468
  }
412
- } else {
469
+ }
470
+ else {
413
471
  // Not the final segment: use cardinal
414
472
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
415
473
  const scaleWord = pluralize(segment, scaleForms)
@@ -428,12 +486,10 @@ function buildLargeOrdinal (n) {
428
486
 
429
487
  /**
430
488
  * Converts a numeric value to Serbian ordinal words (masculine nominative).
431
- *
432
489
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
433
490
  * @returns {string} The number as ordinal words (e.g., "први", "четрдесет други")
434
491
  * @throws {TypeError} If value is not a valid numeric type
435
492
  * @throws {RangeError} If value is negative, zero, or has a decimal part
436
- *
437
493
  * @example
438
494
  * toOrdinal(1) // 'први'
439
495
  * toOrdinal(2) // 'други'
@@ -443,8 +499,9 @@ function buildLargeOrdinal (n) {
443
499
  * toOrdinal(100) // 'стоти'
444
500
  * toOrdinal(1000) // 'хиљадити'
445
501
  */
446
- function toOrdinal (value) {
502
+ function toOrdinal(value) {
447
503
  const integerPart = parseOrdinalValue(value)
504
+ checkMax(integerPart, ordinalMax)
448
505
  return integerToOrdinal(integerPart)
449
506
  }
450
507
 
@@ -452,16 +509,21 @@ function toOrdinal (value) {
452
509
  // CURRENCY: toCurrency(value, options?)
453
510
  // ============================================================================
454
511
 
512
+ /**
513
+ * @typedef {object} CurrencyOptions
514
+ * @property {boolean} [and] - Use "и" between dinars and para
515
+ */
516
+
517
+ /** @type {Required<CurrencyOptions>} */
518
+ export const currencyDefaults = { and: true }
519
+
455
520
  /**
456
521
  * Converts a numeric value to Serbian currency words (Serbian Dinar).
457
- *
458
522
  * @param {number | string | bigint} value - The currency amount to convert
459
- * @param {Object} [options] - Optional configuration
460
- * @param {boolean} [options.and=true] - Use "и" between dinars and para
523
+ * @param {CurrencyOptions} [options] - Optional configuration
461
524
  * @returns {string} The amount in Serbian currency words
462
525
  * @throws {TypeError} If value is not a valid numeric type
463
526
  * @throws {Error} If value is not a valid number format
464
- *
465
527
  * @example
466
528
  * toCurrency(42.50) // 'четрдесет два динара и педесет пара'
467
529
  * toCurrency(1) // 'један динар'
@@ -469,10 +531,10 @@ function toOrdinal (value) {
469
531
  * toCurrency(0.01) // 'једна пара'
470
532
  * toCurrency(42.50, { and: false }) // 'четрдесет два динара педесет пара'
471
533
  */
472
- function toCurrency (value, options) {
473
- options = validateOptions(options)
534
+ function toCurrency(value, options) {
474
535
  const { isNegative, dollars: dinars, cents: para } = parseCurrencyValue(value)
475
- const { and: useAnd = true } = options
536
+ checkMax(dinars, currencyMax)
537
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
476
538
 
477
539
  // Build result
478
540
  let result = ''
@@ -1,22 +1,47 @@
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] - Grammatical gender
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
+ /**
15
+ * @typedef {object} CurrencyOptions
16
+ * @property {boolean} [and] - Use "i" between dinars and para
17
+ */
18
+ /** @type {Required<CurrencyOptions>} */
19
+ export const currencyDefaults: Required<CurrencyOptions>;
20
+ export type CardinalOptions = {
21
+ /**
22
+ * - Grammatical gender
23
+ */
24
+ gender?: "feminine" | "masculine" | undefined;
25
+ };
26
+ export type CurrencyOptions = {
27
+ /**
28
+ * - Use "i" between dinars and para
29
+ */
30
+ and?: boolean | undefined;
31
+ };
1
32
  /**
2
33
  * Converts a numeric value to Serbian (Latin) words.
3
- *
4
34
  * @param {number | string | bigint} value - The numeric value to convert
5
- * @param {Object} [options] - Optional configuration
6
- * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
35
+ * @param {CardinalOptions} [options] - Optional configuration
7
36
  * @returns {string} The number in Serbian Latin words
8
37
  */
9
- export function toCardinal(value: number | string | bigint, options?: {
10
- gender?: "masculine" | "feminine" | undefined;
11
- }): string;
38
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
12
39
  /**
13
40
  * Converts a numeric value to Serbian ordinal words (masculine nominative).
14
- *
15
41
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
16
42
  * @returns {string} The number as ordinal words (e.g., "prvi", "četrdeset drugi")
17
43
  * @throws {TypeError} If value is not a valid numeric type
18
44
  * @throws {RangeError} If value is negative, zero, or has a decimal part
19
- *
20
45
  * @example
21
46
  * toOrdinal(1) // 'prvi'
22
47
  * toOrdinal(2) // 'drugi'
@@ -29,14 +54,11 @@ export function toCardinal(value: number | string | bigint, options?: {
29
54
  export function toOrdinal(value: number | string | bigint): string;
30
55
  /**
31
56
  * Converts a numeric value to Serbian currency words (Serbian Dinar).
32
- *
33
57
  * @param {number | string | bigint} value - The currency amount to convert
34
- * @param {Object} [options] - Optional configuration
35
- * @param {boolean} [options.and=true] - Use "i" between dinars and para
58
+ * @param {CurrencyOptions} [options] - Optional configuration
36
59
  * @returns {string} The amount in Serbian currency words
37
60
  * @throws {TypeError} If value is not a valid numeric type
38
61
  * @throws {Error} If value is not a valid number format
39
- *
40
62
  * @example
41
63
  * toCurrency(42.50) // 'četrdeset dva dinara i pedeset para'
42
64
  * toCurrency(1) // 'jedan dinar'
@@ -44,6 +66,4 @@ export function toOrdinal(value: number | string | bigint): string;
44
66
  * toCurrency(0.01) // 'jedna para'
45
67
  * toCurrency(42.50, { and: false }) // 'četrdeset dva dinara pedeset para'
46
68
  */
47
- export function toCurrency(value: number | string | bigint, options?: {
48
- and?: boolean | undefined;
49
- }): string;
69
+ export function toCurrency(value: number | string | bigint, options?: CurrencyOptions): string;