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/bn-BD.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 { indian } from './utils/scale.js'
16
18
 
17
19
  // ============================================================================
18
20
  // Vocabulary
@@ -53,20 +55,29 @@ const BELOW_HUNDRED = [
53
55
  'ষাট', 'একষট্টি', 'বাষট্টি', 'তেষট্টি', 'চৌষট্টি', 'পঁয়ষট্টি', 'ছেষট্টি', 'সাতষট্টি', 'আটষট্টি', 'ঊনসত্তর',
54
56
  'সত্তর', 'একাত্তর', 'বাহাত্তর', 'তেহাত্তর', 'চুয়াত্তর', 'পঁচাত্তর', 'ছিয়াত্তর', 'সাতাত্তর', 'আটাত্তর', 'উনআশি',
55
57
  'আশি', 'একাশি', 'বিরাশি', 'তিরাশি', 'চুরাশি', 'পঁচাশি', 'ছিয়াশি', 'সাতাশি', 'আটাশি', 'উননব্বই',
56
- 'নব্বই', 'একানব্বই', 'বিরানব্বই', 'তিরানব্বই', 'চুরানব্বই', 'পঁচানব্বই', 'ছিয়ানব্বই', 'সাতানব্বই', 'আটানব্বই', 'নিরানব্বই'
58
+ 'নব্বই', 'একানব্বই', 'বিরানব্বই', 'তিরানব্বই', 'চুরানব্বই', 'পঁচানব্বই', 'ছিয়ানব্বই', 'সাতানব্বই', 'আটানব্বই', 'নিরানব্বই',
57
59
  ]
58
60
 
59
61
  // Scale words: index 0 = units (empty), 1 = thousand, 2 = lakh, 3 = crore, etc.
60
62
  const SCALE_WORDS = ['', 'হাজার', 'লাখ', 'কোটি', 'আরব', 'খরব', 'নীল', 'পদ্ম', 'শঙ্খ']
61
63
 
64
+ // 3-2-2 Indian grouping: a 3-digit base segment, then 2 digits per scale word
65
+ // (SCALE_WORDS[0] = '' is the units slot). Past the table the scale word is
66
+ // dropped, which collapses the magnitude — so cap there.
67
+ export const cardinalMax = indian(SCALE_WORDS.length)
68
+ export const ordinalMax = indian(SCALE_WORDS.length)
69
+ export const currencyMax = indian(SCALE_WORDS.length)
70
+
62
71
  // ============================================================================
63
72
  // Segment Building
64
73
  // ============================================================================
65
74
 
66
75
  /**
67
76
  * Builds words for a 0-999 segment.
77
+ * @param {number} n - Segment value in the range 0-999
78
+ * @returns {string} Bengali words for the segment
68
79
  */
69
- function buildSegment (n) {
80
+ function buildSegment(n) {
70
81
  if (n === 0) return ''
71
82
  if (n < 100) return BELOW_HUNDRED[n]
72
83
 
@@ -88,11 +99,10 @@ function buildSegment (n) {
88
99
  *
89
100
  * Uses BigInt modulo for segment extraction (faster than string slicing).
90
101
  * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
91
- *
92
102
  * @param {bigint} n - Non-negative integer to convert
93
103
  * @returns {string} Bengali words
94
104
  */
95
- function integerToWords (n) {
105
+ function integerToWords(n) {
96
106
  if (n === 0n) return ZERO
97
107
 
98
108
  // Fast path: numbers < 1000 (direct lookup)
@@ -124,7 +134,8 @@ function integerToWords (n) {
124
134
  if (i === 0) {
125
135
  // First segment (units place) can be 0-999
126
136
  words.push(buildSegment(segment))
127
- } else {
137
+ }
138
+ else {
128
139
  // Other segments are 0-99
129
140
  words.push(BELOW_HUNDRED[segment])
130
141
  }
@@ -138,7 +149,12 @@ function integerToWords (n) {
138
149
  return words.join(' ')
139
150
  }
140
151
 
141
- function decimalPartToWords (decimalPart) {
152
+ /**
153
+ * Converts the fractional digits string to Bengali words.
154
+ * @param {string} decimalPart - The fractional digits (e.g. '05')
155
+ * @returns {string} Bengali words for the decimal part
156
+ */
157
+ function decimalPartToWords(decimalPart) {
142
158
  let result = ''
143
159
  let i = 0
144
160
 
@@ -159,12 +175,14 @@ function decimalPartToWords (decimalPart) {
159
175
 
160
176
  /**
161
177
  * Converts a numeric value to Bengali words.
162
- *
163
178
  * @param {number | string | bigint} value - The numeric value to convert
164
179
  * @returns {string} The number in Bengali words
165
180
  */
166
- function toCardinal (value) {
181
+ function toCardinal(value) {
167
182
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
183
+ // Both the integer part and the decimal's significant digits are spelled via
184
+ // the scale builder, so both must clear the ceiling.
185
+ checkMax(integerPart, cardinalMax, decimalPart)
168
186
 
169
187
  let result = ''
170
188
 
@@ -189,11 +207,10 @@ function toCardinal (value) {
189
207
  * Converts a positive integer to Bengali ordinal words.
190
208
  *
191
209
  * Bengali ordinals: First 6 are irregular, then add -তম suffix.
192
- *
193
210
  * @param {bigint} n - Positive integer to convert
194
211
  * @returns {string} Bengali ordinal words
195
212
  */
196
- function integerToOrdinal (n) {
213
+ function integerToOrdinal(n) {
197
214
  // Special ordinals for 1-6
198
215
  if (n >= 1n && n <= 6n) {
199
216
  return ORDINAL_SPECIAL[Number(n)]
@@ -206,20 +223,20 @@ function integerToOrdinal (n) {
206
223
 
207
224
  /**
208
225
  * Converts a numeric value to Bengali ordinal words.
209
- *
210
226
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
211
227
  * @returns {string} The number as ordinal words
212
228
  * @throws {TypeError} If value is not a valid numeric type
213
229
  * @throws {RangeError} If value is negative, zero, or has a decimal part
214
- *
215
230
  * @example
216
231
  * toOrdinal(1) // 'প্রথম'
217
232
  * toOrdinal(2) // 'দ্বিতীয়'
218
233
  * toOrdinal(3) // 'তৃতীয়'
219
234
  * toOrdinal(10) // 'দশতম'
220
235
  */
221
- function toOrdinal (value) {
236
+ function toOrdinal(value) {
222
237
  const integerPart = parseOrdinalValue(value)
238
+ // Ordinals build on the cardinal speller, so they share its ceiling.
239
+ checkMax(integerPart, ordinalMax)
223
240
  return integerToOrdinal(integerPart)
224
241
  }
225
242
 
@@ -229,19 +246,18 @@ function toOrdinal (value) {
229
246
 
230
247
  /**
231
248
  * Converts a numeric value to Bengali currency words (Bangladeshi Taka).
232
- *
233
249
  * @param {number | string | bigint} value - The currency amount to convert
234
250
  * @returns {string} The amount in Bengali currency words
235
251
  * @throws {TypeError} If value is not a valid numeric type
236
252
  * @throws {Error} If value is not a valid number format
237
- *
238
253
  * @example
239
254
  * toCurrency(42.50) // 'বেয়াল্লিশ টাকা পঞ্চাশ পয়সা'
240
255
  * toCurrency(1) // 'এক টাকা'
241
256
  * toCurrency(0.01) // 'এক পয়সা'
242
257
  */
243
- function toCurrency (value) {
258
+ function toCurrency(value) {
244
259
  const { isNegative, dollars: taka, cents: paisa } = parseCurrencyValue(value)
260
+ checkMax(taka, currencyMax)
245
261
 
246
262
  // Build result
247
263
  let result = ''
package/src/cs-CZ.d.ts CHANGED
@@ -1,14 +1,15 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
1
4
  /**
2
5
  * Converts a numeric value to Czech words.
3
6
  *
4
7
  * This is the main public API. It accepts any valid numeric input
5
8
  * (number, string, or bigint) and handles parsing internally.
6
- *
7
9
  * @param {number | string | bigint} value - The numeric value to convert
8
10
  * @returns {string} The number in Czech words
9
11
  * @throws {TypeError} If value is not a valid numeric type
10
12
  * @throws {Error} If value is not a valid number format
11
- *
12
13
  * @example
13
14
  * toCardinal(21) // 'dvacet jedna'
14
15
  * toCardinal(1000) // 'tisíc'
@@ -18,12 +19,10 @@
18
19
  export function toCardinal(value: number | string | bigint): string;
19
20
  /**
20
21
  * Converts a numeric value to Czech ordinal words (masculine nominative).
21
- *
22
22
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
23
23
  * @returns {string} The number as ordinal words
24
24
  * @throws {TypeError} If value is not a valid numeric type
25
25
  * @throws {RangeError} If value is negative, zero, or has a decimal part
26
- *
27
26
  * @example
28
27
  * toOrdinal(1) // 'první'
29
28
  * toOrdinal(2) // 'druhý'
@@ -34,12 +33,10 @@ export function toCardinal(value: number | string | bigint): string;
34
33
  export function toOrdinal(value: number | string | bigint): string;
35
34
  /**
36
35
  * Converts a numeric value to Czech currency words (Koruna).
37
- *
38
36
  * @param {number | string | bigint} value - The currency amount to convert
39
37
  * @returns {string} The amount in Czech currency words
40
38
  * @throws {TypeError} If value is not a valid numeric type
41
39
  * @throws {Error} If value is not a valid number format
42
- *
43
40
  * @example
44
41
  * toCurrency(42) // 'čtyřicet dva koruny'
45
42
  * toCurrency(1) // 'jedna koruna'
package/src/cs-CZ.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)
@@ -32,6 +34,7 @@ const TENS = ['', '', 'dvacet', 'třicet', 'čtyřicet', 'padesát', 'šedesát'
32
34
  const HUNDREDS = ['', 'sto', 'dvě stě', 'tři sta', 'čtyři sta', 'pět set', 'šest set', 'sedm set', 'osm set', 'devět set']
33
35
 
34
36
  // Scale plural forms [singular, few (2-4), many (5+)]
37
+ /** @type {Record<number, string[]>} */
35
38
  const PLURAL_FORMS = {
36
39
  1: ['tisíc', 'tisíce', 'tisíc'], // 10^3
37
40
  2: ['milion', 'miliony', 'milionů'], // 10^6
@@ -41,9 +44,16 @@ const PLURAL_FORMS = {
41
44
  6: ['trilion', 'triliony', 'trilionů'], // 10^18
42
45
  7: ['triliarda', 'triliardy', 'triliard'], // 10^21
43
46
  8: ['kvadrilion', 'kvadriliony', 'kvadrilionů'], // 10^24
44
- 9: ['kvadriliarda', 'kvadriliardy', 'kvadriliard'] // 10^27
47
+ 9: ['kvadriliarda', 'kvadriliardy', 'kvadriliard'], // 10^27
45
48
  }
46
49
 
50
+ // PLURAL_FORMS is keyed by 1000-power; past its largest key the builder drops the
51
+ // scale word (cardinal) or dereferences undefined (ordinal), so cap there.
52
+ const MAX_SCALE_KEY = Math.max(...Object.keys(PLURAL_FORMS).map(Number))
53
+ export const cardinalMax = western(MAX_SCALE_KEY)
54
+ export const ordinalMax = western(MAX_SCALE_KEY)
55
+ export const currencyMax = western(MAX_SCALE_KEY)
56
+
47
57
  const ZERO = 'nula'
48
58
  const NEGATIVE = 'mínus'
49
59
 
@@ -81,8 +91,10 @@ const HALER_FORMS = ['haléř', 'haléře', 'haléřů']
81
91
 
82
92
  /**
83
93
  * Builds segment word for 0-999 (masculine, default form).
94
+ * @param {number} n - Number 0-999
95
+ * @returns {string} Czech words
84
96
  */
85
- function buildSegment (n) {
97
+ function buildSegment(n) {
86
98
  if (n === 0) return ''
87
99
 
88
100
  const ones = n % 10
@@ -100,12 +112,14 @@ function buildSegment (n) {
100
112
  if (tens === 1) {
101
113
  // Teens
102
114
  parts.push(TEENS[ones])
103
- } else if (tens >= 2) {
115
+ }
116
+ else if (tens >= 2) {
104
117
  parts.push(TENS[tens])
105
118
  if (ones > 0) {
106
119
  parts.push(ONES[ones])
107
120
  }
108
- } else if (ones > 0) {
121
+ }
122
+ else if (ones > 0) {
109
123
  parts.push(ONES[ones])
110
124
  }
111
125
 
@@ -115,8 +129,10 @@ function buildSegment (n) {
115
129
  /**
116
130
  * Builds segment word for 0-999 with feminine hundreds.
117
131
  * Hundreds use irregular forms (dvě stě, tři sta) but ones remain masculine.
132
+ * @param {number} n - Number 0-999
133
+ * @returns {string} Czech words
118
134
  */
119
- function buildSegmentWithHundreds (n) {
135
+ function buildSegmentWithHundreds(n) {
120
136
  if (n === 0) return ''
121
137
 
122
138
  const ones = n % 10
@@ -133,12 +149,14 @@ function buildSegmentWithHundreds (n) {
133
149
  // Tens and ones use masculine form
134
150
  if (tens === 1) {
135
151
  parts.push(TEENS[ones])
136
- } else if (tens >= 2) {
152
+ }
153
+ else if (tens >= 2) {
137
154
  parts.push(TENS[tens])
138
155
  if (ones > 0) {
139
156
  parts.push(ONES[ones]) // masculine
140
157
  }
141
- } else if (ones > 0) {
158
+ }
159
+ else if (ones > 0) {
142
160
  parts.push(ONES[ones]) // masculine
143
161
  }
144
162
 
@@ -152,12 +170,11 @@ function buildSegmentWithHundreds (n) {
152
170
  /**
153
171
  * Czech pluralization: 1 = singular, 2-4 = few, else = many.
154
172
  * Teens (11-19) always use "many" form.
155
- *
156
173
  * @param {bigint} n - The number
157
174
  * @param {string[]} forms - [singular, few, many]
158
175
  * @returns {string} The appropriate form
159
176
  */
160
- function pluralize (n, forms) {
177
+ function pluralize(n, forms) {
161
178
  if (n === 1n) return forms[0]
162
179
 
163
180
  const lastDigit = n % 10n
@@ -174,13 +191,17 @@ function pluralize (n, forms) {
174
191
  /**
175
192
  * Gets the decimal separator word based on integer part.
176
193
  * celá (0-1), celé (2-4), celých (5+)
194
+ * @param {bigint} integerPart - The integer part of the value
195
+ * @returns {string} The decimal separator word
177
196
  */
178
- function getDecimalSeparator (integerPart) {
197
+ function getDecimalSeparator(integerPart) {
179
198
  if (integerPart === 0n || integerPart === 1n) {
180
199
  return 'celá'
181
- } else if (integerPart >= 2n && integerPart <= 4n) {
200
+ }
201
+ else if (integerPart >= 2n && integerPart <= 4n) {
182
202
  return 'celé'
183
- } else {
203
+ }
204
+ else {
184
205
  return 'celých'
185
206
  }
186
207
  }
@@ -191,11 +212,10 @@ function getDecimalSeparator (integerPart) {
191
212
 
192
213
  /**
193
214
  * Converts a non-negative integer to Czech words.
194
- *
195
215
  * @param {bigint} n - Non-negative integer to convert
196
216
  * @returns {string} Czech words
197
217
  */
198
- function integerToWords (n) {
218
+ function integerToWords(n) {
199
219
  if (n === 0n) return ZERO
200
220
 
201
221
  // Fast path: numbers < 1000
@@ -214,7 +234,8 @@ function integerToWords (n) {
214
234
  if (thousands === 1n) {
215
235
  // Omit "one" before tisíc
216
236
  result = scaleWord
217
- } else {
237
+ }
238
+ else {
218
239
  result = buildSegment(Number(thousands)) + ' ' + scaleWord
219
240
  }
220
241
 
@@ -233,11 +254,10 @@ function integerToWords (n) {
233
254
  /**
234
255
  * Builds words for numbers >= 1,000,000.
235
256
  * Uses BigInt division for faster segment extraction.
236
- *
237
257
  * @param {bigint} n - Number >= 1,000,000
238
258
  * @returns {string} Czech words
239
259
  */
240
- function buildLargeNumberWords (n) {
260
+ function buildLargeNumberWords(n) {
241
261
  // Extract segments using BigInt division (faster than string slicing)
242
262
  // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
243
263
  const segmentValues = []
@@ -259,7 +279,8 @@ function buildLargeNumberWords (n) {
259
279
  if (i === 0) {
260
280
  // Units segment (no scale word) - use form with irregular hundreds
261
281
  result += buildSegmentWithHundreds(Number(segment))
262
- } else {
282
+ }
283
+ else {
263
284
  // Scale word needed
264
285
  const forms = PLURAL_FORMS[i]
265
286
  if (forms) {
@@ -268,11 +289,13 @@ function buildLargeNumberWords (n) {
268
289
  if (segment === 1n) {
269
290
  // Omit "one" before scale words
270
291
  result += scaleWord
271
- } else {
292
+ }
293
+ else {
272
294
  // Use masculine form for multiplier before scale words
273
295
  result += buildSegment(Number(segment)) + ' ' + scaleWord
274
296
  }
275
- } else {
297
+ }
298
+ else {
276
299
  // Fallback for very large scales without defined forms
277
300
  result += buildSegment(Number(segment))
278
301
  }
@@ -284,11 +307,10 @@ function buildLargeNumberWords (n) {
284
307
 
285
308
  /**
286
309
  * Converts decimal digits to Czech words.
287
- *
288
310
  * @param {string} decimalPart - Decimal digits (without the point)
289
311
  * @returns {string} Czech words for decimal part
290
312
  */
291
- function decimalPartToWords (decimalPart) {
313
+ function decimalPartToWords(decimalPart) {
292
314
  let result = ''
293
315
 
294
316
  // Handle leading zeros
@@ -314,20 +336,21 @@ function decimalPartToWords (decimalPart) {
314
336
  *
315
337
  * This is the main public API. It accepts any valid numeric input
316
338
  * (number, string, or bigint) and handles parsing internally.
317
- *
318
339
  * @param {number | string | bigint} value - The numeric value to convert
319
340
  * @returns {string} The number in Czech words
320
341
  * @throws {TypeError} If value is not a valid numeric type
321
342
  * @throws {Error} If value is not a valid number format
322
- *
323
343
  * @example
324
344
  * toCardinal(21) // 'dvacet jedna'
325
345
  * toCardinal(1000) // 'tisíc'
326
346
  * toCardinal(2000) // 'dva tisíce'
327
347
  * toCardinal(5000) // 'pět tisíc'
328
348
  */
329
- function toCardinal (value) {
349
+ function toCardinal(value) {
330
350
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
351
+ // Both the integer part and the decimal's significant digits are spelled via
352
+ // the scale builder, so both must clear the ceiling.
353
+ checkMax(integerPart, cardinalMax, decimalPart)
331
354
 
332
355
  let result = ''
333
356
 
@@ -351,11 +374,10 @@ function toCardinal (value) {
351
374
 
352
375
  /**
353
376
  * Builds ordinal for a 0-99 segment when it's the final (ordinal) part.
354
- *
355
377
  * @param {number} n - Number 0-99
356
378
  * @returns {string} Ordinal words
357
379
  */
358
- function buildOrdinalTensOnes (n) {
380
+ function buildOrdinalTensOnes(n) {
359
381
  if (n === 0) return ''
360
382
 
361
383
  const onesDigit = n % 10
@@ -378,11 +400,10 @@ function buildOrdinalTensOnes (n) {
378
400
 
379
401
  /**
380
402
  * Converts a positive integer to Czech ordinal words (masculine nominative).
381
- *
382
403
  * @param {bigint} n - Positive integer to convert
383
404
  * @returns {string} Ordinal Czech words
384
405
  */
385
- function integerToOrdinal (n) {
406
+ function integerToOrdinal(n) {
386
407
  if (n < 100n) {
387
408
  return buildOrdinalTensOnes(Number(n))
388
409
  }
@@ -420,11 +441,10 @@ function integerToOrdinal (n) {
420
441
 
421
442
  /**
422
443
  * Builds ordinal words for numbers >= 1,000,000.
423
- *
424
444
  * @param {bigint} n - Number >= 1,000,000
425
445
  * @returns {string} Ordinal Czech words
426
446
  */
427
- function buildLargeOrdinal (n) {
447
+ function buildLargeOrdinal(n) {
428
448
  const segmentValues = []
429
449
  let temp = n
430
450
  while (temp > 0n) {
@@ -450,23 +470,28 @@ function buildLargeOrdinal (n) {
450
470
  if (i === 0) {
451
471
  if (isLastNonZero) {
452
472
  parts.push(integerToOrdinal(segment))
453
- } else {
473
+ }
474
+ else {
454
475
  parts.push(buildSegmentWithHundreds(Number(segment)))
455
476
  }
456
- } else {
477
+ }
478
+ else {
457
479
  if (isLastNonZero) {
458
480
  if (segment === 1n) {
459
481
  parts.push(ORDINAL_SCALES[i - 1] || PLURAL_FORMS[i][0])
460
- } else {
482
+ }
483
+ else {
461
484
  parts.push(buildSegment(Number(segment)) + ' ' + (ORDINAL_SCALES[i - 1] || PLURAL_FORMS[i][0]))
462
485
  }
463
- } else {
486
+ }
487
+ else {
464
488
  const forms = PLURAL_FORMS[i]
465
489
  if (forms) {
466
490
  const scaleWord = pluralize(segment, forms)
467
491
  if (segment === 1n) {
468
492
  parts.push(scaleWord)
469
- } else {
493
+ }
494
+ else {
470
495
  parts.push(buildSegment(Number(segment)) + ' ' + scaleWord)
471
496
  }
472
497
  }
@@ -479,12 +504,10 @@ function buildLargeOrdinal (n) {
479
504
 
480
505
  /**
481
506
  * Converts a numeric value to Czech ordinal words (masculine nominative).
482
- *
483
507
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
484
508
  * @returns {string} The number as ordinal words
485
509
  * @throws {TypeError} If value is not a valid numeric type
486
510
  * @throws {RangeError} If value is negative, zero, or has a decimal part
487
- *
488
511
  * @example
489
512
  * toOrdinal(1) // 'první'
490
513
  * toOrdinal(2) // 'druhý'
@@ -492,8 +515,10 @@ function buildLargeOrdinal (n) {
492
515
  * toOrdinal(100) // 'stý'
493
516
  * toOrdinal(1000) // 'tisící'
494
517
  */
495
- function toOrdinal (value) {
518
+ function toOrdinal(value) {
496
519
  const integerPart = parseOrdinalValue(value)
520
+ // Ordinals reuse the cardinal scale table, so they share its ceiling.
521
+ checkMax(integerPart, ordinalMax)
497
522
  return integerToOrdinal(integerPart)
498
523
  }
499
524
 
@@ -503,20 +528,19 @@ function toOrdinal (value) {
503
528
 
504
529
  /**
505
530
  * Converts a numeric value to Czech currency words (Koruna).
506
- *
507
531
  * @param {number | string | bigint} value - The currency amount to convert
508
532
  * @returns {string} The amount in Czech currency words
509
533
  * @throws {TypeError} If value is not a valid numeric type
510
534
  * @throws {Error} If value is not a valid number format
511
- *
512
535
  * @example
513
536
  * toCurrency(42) // 'čtyřicet dva koruny'
514
537
  * toCurrency(1) // 'jedna koruna'
515
538
  * toCurrency(1.50) // 'jedna koruna padesát haléřů'
516
539
  * toCurrency(-5) // 'mínus pět korun'
517
540
  */
518
- function toCurrency (value) {
541
+ function toCurrency(value) {
519
542
  const { isNegative, dollars: koruny, cents: halere } = parseCurrencyValue(value)
543
+ checkMax(koruny, currencyMax)
520
544
 
521
545
  let result = ''
522
546
  if (isNegative) {
package/src/da-DK.d.ts CHANGED
@@ -1,11 +1,12 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
1
4
  /**
2
5
  * Converts a numeric value to Danish words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Danish words
6
8
  * @throws {TypeError} If value is not a valid numeric type
7
9
  * @throws {Error} If value is not a valid number format
8
- *
9
10
  * @example
10
11
  * toCardinal(21) // 'enogtyve'
11
12
  * toCardinal(1000) // 'ettusind'
@@ -14,12 +15,10 @@
14
15
  export function toCardinal(value: number | string | bigint): string;
15
16
  /**
16
17
  * Converts a numeric value to Danish ordinal words.
17
- *
18
18
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
19
19
  * @returns {string} The number as ordinal words
20
20
  * @throws {TypeError} If value is not a valid numeric type
21
21
  * @throws {RangeError} If value is negative, zero, or has a decimal part
22
- *
23
22
  * @example
24
23
  * toOrdinal(1) // 'første'
25
24
  * toOrdinal(2) // 'anden'
@@ -30,12 +29,10 @@ export function toOrdinal(value: number | string | bigint): string;
30
29
  * Converts a numeric value to Danish currency words (Danish Krone).
31
30
  *
32
31
  * Uses krone/kroner and øre (100 øre = 1 krone).
33
- *
34
32
  * @param {number | string | bigint} value - The currency amount to convert
35
33
  * @returns {string} The amount in Danish currency words
36
34
  * @throws {TypeError} If value is not a valid numeric type
37
35
  * @throws {Error} If value is not a valid number format
38
- *
39
36
  * @example
40
37
  * toCurrency(1) // 'en krone'
41
38
  * toCurrency(42) // 'toogfyrre kroner'