n2words 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/CHANGELOG.md +128 -42
  2. package/README.md +6 -4
  3. package/dist/am-ET.js +2 -2
  4. package/dist/am-ET.umd.js +2 -2
  5. package/dist/am-Latn-ET.js +2 -2
  6. package/dist/am-Latn-ET.umd.js +2 -2
  7. package/dist/ar-SA.js +2 -2
  8. package/dist/ar-SA.umd.js +2 -2
  9. package/dist/az-AZ.js +2 -2
  10. package/dist/az-AZ.umd.js +2 -2
  11. package/dist/bn-BD.js +2 -2
  12. package/dist/bn-BD.umd.js +2 -2
  13. package/dist/cs-CZ.js +2 -2
  14. package/dist/cs-CZ.umd.js +2 -2
  15. package/dist/da-DK.js +2 -2
  16. package/dist/da-DK.umd.js +2 -2
  17. package/dist/de-DE.js +2 -2
  18. package/dist/de-DE.umd.js +2 -2
  19. package/dist/el-GR.js +2 -2
  20. package/dist/el-GR.umd.js +2 -2
  21. package/dist/en-AU.js +2 -2
  22. package/dist/en-AU.umd.js +2 -2
  23. package/dist/en-BD.js +2 -2
  24. package/dist/en-BD.umd.js +2 -2
  25. package/dist/en-CA.js +2 -2
  26. package/dist/en-CA.umd.js +2 -2
  27. package/dist/en-GB.js +2 -2
  28. package/dist/en-GB.umd.js +2 -2
  29. package/dist/en-GH.js +2 -2
  30. package/dist/en-GH.umd.js +2 -2
  31. package/dist/en-IE.js +2 -2
  32. package/dist/en-IE.umd.js +2 -2
  33. package/dist/en-IN.js +2 -2
  34. package/dist/en-IN.umd.js +2 -2
  35. package/dist/en-KE.js +2 -2
  36. package/dist/en-KE.umd.js +2 -2
  37. package/dist/en-MY.js +2 -2
  38. package/dist/en-MY.umd.js +2 -2
  39. package/dist/en-NG.js +2 -2
  40. package/dist/en-NG.umd.js +2 -2
  41. package/dist/en-NZ.js +2 -2
  42. package/dist/en-NZ.umd.js +2 -2
  43. package/dist/en-PH.js +2 -2
  44. package/dist/en-PH.umd.js +2 -2
  45. package/dist/en-PK.js +2 -2
  46. package/dist/en-PK.umd.js +2 -2
  47. package/dist/en-SG.js +2 -2
  48. package/dist/en-SG.umd.js +2 -2
  49. package/dist/en-US.js +2 -2
  50. package/dist/en-US.umd.js +2 -2
  51. package/dist/en-ZA.js +2 -2
  52. package/dist/en-ZA.umd.js +2 -2
  53. package/dist/es-ES.js +2 -2
  54. package/dist/es-ES.umd.js +2 -2
  55. package/dist/es-MX.js +2 -2
  56. package/dist/es-MX.umd.js +2 -2
  57. package/dist/es-US.js +2 -2
  58. package/dist/es-US.umd.js +2 -2
  59. package/dist/fa-IR.js +2 -2
  60. package/dist/fa-IR.umd.js +2 -2
  61. package/dist/fi-FI.js +2 -2
  62. package/dist/fi-FI.umd.js +2 -2
  63. package/dist/fil-PH.js +2 -2
  64. package/dist/fil-PH.umd.js +2 -2
  65. package/dist/fr-BE.js +2 -2
  66. package/dist/fr-BE.umd.js +2 -2
  67. package/dist/fr-FR.js +2 -2
  68. package/dist/fr-FR.umd.js +2 -2
  69. package/dist/gu-IN.js +2 -2
  70. package/dist/gu-IN.umd.js +2 -2
  71. package/dist/ha-NG.js +2 -2
  72. package/dist/ha-NG.umd.js +2 -2
  73. package/dist/hbo-IL.js +2 -2
  74. package/dist/hbo-IL.umd.js +2 -2
  75. package/dist/he-IL.js +2 -2
  76. package/dist/he-IL.umd.js +2 -2
  77. package/dist/hi-IN.js +2 -2
  78. package/dist/hi-IN.umd.js +2 -2
  79. package/dist/hr-HR.js +2 -2
  80. package/dist/hr-HR.umd.js +2 -2
  81. package/dist/hu-HU.js +2 -2
  82. package/dist/hu-HU.umd.js +2 -2
  83. package/dist/id-ID.js +2 -2
  84. package/dist/id-ID.umd.js +2 -2
  85. package/dist/it-IT.js +2 -2
  86. package/dist/it-IT.umd.js +2 -2
  87. package/dist/ja-JP.js +2 -2
  88. package/dist/ja-JP.umd.js +2 -2
  89. package/dist/ka-GE.js +2 -2
  90. package/dist/ka-GE.umd.js +2 -2
  91. package/dist/kn-IN.js +2 -2
  92. package/dist/kn-IN.umd.js +2 -2
  93. package/dist/ko-KR.js +2 -2
  94. package/dist/ko-KR.umd.js +2 -2
  95. package/dist/lt-LT.js +2 -2
  96. package/dist/lt-LT.umd.js +2 -2
  97. package/dist/lv-LV.js +2 -2
  98. package/dist/lv-LV.umd.js +2 -2
  99. package/dist/mr-IN.js +2 -2
  100. package/dist/mr-IN.umd.js +2 -2
  101. package/dist/ms-MY.js +2 -2
  102. package/dist/ms-MY.umd.js +2 -2
  103. package/dist/nb-NO.js +2 -2
  104. package/dist/nb-NO.umd.js +2 -2
  105. package/dist/nl-NL.js +2 -2
  106. package/dist/nl-NL.umd.js +2 -2
  107. package/dist/pa-IN.js +2 -2
  108. package/dist/pa-IN.umd.js +2 -2
  109. package/dist/pl-PL.js +2 -2
  110. package/dist/pl-PL.umd.js +2 -2
  111. package/dist/pt-BR.js +2 -2
  112. package/dist/pt-BR.umd.js +2 -2
  113. package/dist/pt-PT.js +2 -2
  114. package/dist/pt-PT.umd.js +2 -2
  115. package/dist/ro-RO.js +2 -2
  116. package/dist/ro-RO.umd.js +2 -2
  117. package/dist/ru-RU.js +2 -2
  118. package/dist/ru-RU.umd.js +2 -2
  119. package/dist/sr-Cyrl-RS.js +2 -2
  120. package/dist/sr-Cyrl-RS.umd.js +2 -2
  121. package/dist/sr-Latn-RS.js +2 -2
  122. package/dist/sr-Latn-RS.umd.js +2 -2
  123. package/dist/sv-SE.js +2 -2
  124. package/dist/sv-SE.umd.js +2 -2
  125. package/dist/sw-KE.js +2 -2
  126. package/dist/sw-KE.umd.js +2 -2
  127. package/dist/ta-IN.js +2 -2
  128. package/dist/ta-IN.umd.js +2 -2
  129. package/dist/te-IN.js +2 -2
  130. package/dist/te-IN.umd.js +2 -2
  131. package/dist/th-TH.js +2 -2
  132. package/dist/th-TH.umd.js +2 -2
  133. package/dist/tr-TR.js +2 -2
  134. package/dist/tr-TR.umd.js +2 -2
  135. package/dist/uk-UA.js +2 -2
  136. package/dist/uk-UA.umd.js +2 -2
  137. package/dist/ur-PK.js +2 -2
  138. package/dist/ur-PK.umd.js +2 -2
  139. package/dist/vi-VN.js +2 -2
  140. package/dist/vi-VN.umd.js +2 -2
  141. package/dist/yo-NG.js +2 -2
  142. package/dist/yo-NG.umd.js +2 -2
  143. package/dist/zh-Hans-CN.js +2 -2
  144. package/dist/zh-Hans-CN.umd.js +2 -2
  145. package/dist/zh-Hant-TW.js +2 -2
  146. package/dist/zh-Hant-TW.umd.js +2 -2
  147. package/package.json +31 -22
  148. package/src/am-ET.d.ts +3 -5
  149. package/src/am-ET.js +41 -16
  150. package/src/am-Latn-ET.d.ts +3 -5
  151. package/src/am-Latn-ET.js +45 -16
  152. package/src/ar-SA.d.ts +44 -18
  153. package/src/ar-SA.js +93 -40
  154. package/src/az-AZ.d.ts +3 -5
  155. package/src/az-AZ.js +58 -20
  156. package/src/bn-BD.d.ts +3 -5
  157. package/src/bn-BD.js +32 -16
  158. package/src/cs-CZ.d.ts +3 -6
  159. package/src/cs-CZ.js +66 -42
  160. package/src/da-DK.d.ts +3 -6
  161. package/src/da-DK.js +53 -48
  162. package/src/de-DE.d.ts +17 -11
  163. package/src/de-DE.js +88 -57
  164. package/src/el-GR.d.ts +3 -6
  165. package/src/el-GR.js +45 -32
  166. package/src/en-AU.d.ts +17 -11
  167. package/src/en-AU.js +56 -41
  168. package/src/en-BD.d.ts +17 -11
  169. package/src/en-BD.js +60 -41
  170. package/src/en-CA.d.ts +36 -18
  171. package/src/en-CA.js +67 -46
  172. package/src/en-GB.d.ts +17 -11
  173. package/src/en-GB.js +56 -41
  174. package/src/en-GH.d.ts +32 -3
  175. package/src/en-GH.js +104 -26
  176. package/src/en-IE.d.ts +17 -11
  177. package/src/en-IE.js +56 -41
  178. package/src/en-IN.d.ts +17 -11
  179. package/src/en-IN.js +60 -41
  180. package/src/en-KE.d.ts +28 -3
  181. package/src/en-KE.js +93 -26
  182. package/src/en-MY.d.ts +26 -3
  183. package/src/en-MY.js +91 -26
  184. package/src/en-NG.d.ts +17 -11
  185. package/src/en-NG.js +56 -41
  186. package/src/en-NZ.d.ts +32 -3
  187. package/src/en-NZ.js +85 -31
  188. package/src/en-PH.d.ts +32 -3
  189. package/src/en-PH.js +97 -26
  190. package/src/en-PK.d.ts +17 -11
  191. package/src/en-PK.js +60 -41
  192. package/src/en-SG.d.ts +28 -3
  193. package/src/en-SG.js +93 -26
  194. package/src/en-US.d.ts +36 -18
  195. package/src/en-US.js +70 -47
  196. package/src/en-ZA.d.ts +17 -11
  197. package/src/en-ZA.js +56 -41
  198. package/src/es-ES.d.ts +53 -21
  199. package/src/es-ES.js +104 -56
  200. package/src/es-MX.d.ts +53 -21
  201. package/src/es-MX.js +104 -56
  202. package/src/es-US.d.ts +53 -21
  203. package/src/es-US.js +92 -51
  204. package/src/fa-IR.d.ts +3 -5
  205. package/src/fa-IR.js +28 -13
  206. package/src/fi-FI.d.ts +3 -6
  207. package/src/fi-FI.js +47 -29
  208. package/src/fil-PH.d.ts +3 -5
  209. package/src/fil-PH.js +61 -28
  210. package/src/fr-BE.d.ts +31 -15
  211. package/src/fr-BE.js +128 -57
  212. package/src/fr-FR.d.ts +31 -16
  213. package/src/fr-FR.js +97 -60
  214. package/src/gu-IN.d.ts +3 -5
  215. package/src/gu-IN.js +31 -16
  216. package/src/ha-NG.d.ts +3 -5
  217. package/src/ha-NG.js +55 -27
  218. package/src/hbo-IL.d.ts +26 -12
  219. package/src/hbo-IL.js +92 -51
  220. package/src/he-IL.d.ts +17 -10
  221. package/src/he-IL.js +92 -50
  222. package/src/hi-IN.d.ts +3 -5
  223. package/src/hi-IN.js +30 -17
  224. package/src/hr-HR.d.ts +21 -10
  225. package/src/hr-HR.js +89 -33
  226. package/src/hu-HU.d.ts +3 -5
  227. package/src/hu-HU.js +57 -23
  228. package/src/id-ID.d.ts +3 -5
  229. package/src/id-ID.js +56 -23
  230. package/src/it-IT.d.ts +17 -11
  231. package/src/it-IT.js +74 -43
  232. package/src/ja-JP.d.ts +3 -6
  233. package/src/ja-JP.js +39 -26
  234. package/src/ka-GE.d.ts +3 -6
  235. package/src/ka-GE.js +38 -26
  236. package/src/kn-IN.d.ts +3 -5
  237. package/src/kn-IN.js +31 -16
  238. package/src/ko-KR.d.ts +3 -6
  239. package/src/ko-KR.js +34 -26
  240. package/src/lt-LT.d.ts +21 -11
  241. package/src/lt-LT.js +64 -42
  242. package/src/lv-LV.d.ts +21 -11
  243. package/src/lv-LV.js +79 -51
  244. package/src/mr-IN.d.ts +3 -5
  245. package/src/mr-IN.js +31 -16
  246. package/src/ms-MY.d.ts +3 -5
  247. package/src/ms-MY.js +58 -24
  248. package/src/nb-NO.d.ts +3 -6
  249. package/src/nb-NO.js +54 -34
  250. package/src/nl-NL.d.ts +41 -20
  251. package/src/nl-NL.js +111 -69
  252. package/src/pa-IN.d.ts +3 -5
  253. package/src/pa-IN.js +32 -16
  254. package/src/pl-PL.d.ts +21 -11
  255. package/src/pl-PL.js +69 -45
  256. package/src/pt-BR.d.ts +22 -11
  257. package/src/pt-BR.js +93 -53
  258. package/src/pt-PT.d.ts +17 -11
  259. package/src/pt-PT.js +80 -48
  260. package/src/ro-RO.d.ts +21 -11
  261. package/src/ro-RO.js +77 -39
  262. package/src/ru-RU.d.ts +35 -15
  263. package/src/ru-RU.js +100 -38
  264. package/src/sr-Cyrl-RS.d.ts +35 -15
  265. package/src/sr-Cyrl-RS.js +100 -38
  266. package/src/sr-Latn-RS.d.ts +35 -15
  267. package/src/sr-Latn-RS.js +100 -38
  268. package/src/sv-SE.d.ts +3 -6
  269. package/src/sv-SE.js +53 -34
  270. package/src/sw-KE.d.ts +3 -5
  271. package/src/sw-KE.js +50 -20
  272. package/src/ta-IN.d.ts +3 -5
  273. package/src/ta-IN.js +29 -17
  274. package/src/te-IN.d.ts +3 -5
  275. package/src/te-IN.js +31 -16
  276. package/src/th-TH.d.ts +3 -5
  277. package/src/th-TH.js +42 -19
  278. package/src/tr-TR.d.ts +17 -11
  279. package/src/tr-TR.js +63 -37
  280. package/src/uk-UA.d.ts +21 -10
  281. package/src/uk-UA.js +89 -33
  282. package/src/ur-PK.d.ts +3 -5
  283. package/src/ur-PK.js +32 -16
  284. package/src/utils/check-max.d.ts +26 -0
  285. package/src/utils/check-max.js +33 -0
  286. package/src/utils/expand-scientific.d.ts +0 -4
  287. package/src/utils/expand-scientific.js +7 -9
  288. package/src/utils/is-plain-object.d.ts +3 -4
  289. package/src/utils/is-plain-object.js +3 -4
  290. package/src/utils/parse-cardinal.d.ts +1 -2
  291. package/src/utils/parse-cardinal.js +12 -9
  292. package/src/utils/parse-currency.d.ts +1 -2
  293. package/src/utils/parse-currency.js +9 -11
  294. package/src/utils/parse-ordinal.d.ts +0 -1
  295. package/src/utils/parse-ordinal.js +9 -10
  296. package/src/utils/resolve-options.d.ts +17 -0
  297. package/src/utils/resolve-options.js +56 -0
  298. package/src/utils/scale.d.ts +49 -0
  299. package/src/utils/scale.js +65 -0
  300. package/src/vi-VN.d.ts +3 -6
  301. package/src/vi-VN.js +41 -28
  302. package/src/yo-NG.d.ts +3 -5
  303. package/src/yo-NG.js +49 -33
  304. package/src/zh-Hans-CN.d.ts +45 -20
  305. package/src/zh-Hans-CN.js +84 -31
  306. package/src/zh-Hant-TW.d.ts +45 -20
  307. package/src/zh-Hant-TW.js +85 -34
  308. package/src/utils/validate-options.d.ts +0 -8
  309. package/src/utils/validate-options.js +0 -16
package/src/sw-KE.js CHANGED
@@ -12,16 +12,24 @@
12
12
  import { parseCardinalValue } from './utils/parse-cardinal.js'
13
13
  import { parseCurrencyValue } from './utils/parse-currency.js'
14
14
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
15
+ import { checkMax } from './utils/check-max.js'
16
+ import { western } from './utils/scale.js'
15
17
 
16
18
  // ============================================================================
17
19
  // Vocabulary
18
20
  // ============================================================================
19
21
 
20
22
  const ONES = ['sifuri', 'moja', 'mbili', 'tatu', 'nne', 'tano', 'sita', 'saba', 'nane', 'tisa']
23
+ /** @type {Record<number, string>} */
21
24
  const TENS = { 10: 'kumi', 20: 'ishirini', 30: 'thelathini', 40: 'arobaini', 50: 'hamsini', 60: 'sitini', 70: 'sabini', 80: 'themanini', 90: 'tisini' }
22
25
 
23
26
  const SCALE_WORDS = ['', 'elfu', 'milioni', 'bilioni', 'trilioni', 'kwadrilioni', 'kwintilioni']
24
27
 
28
+ // Supported magnitude ceiling (checked at the public entry points), derived from the scale table.
29
+ export const cardinalMax = western(SCALE_WORDS.length - 1)
30
+ export const ordinalMax = western(SCALE_WORDS.length - 1)
31
+ export const currencyMax = western(SCALE_WORDS.length - 1)
32
+
25
33
  const ZERO = 'sifuri'
26
34
  const NEGATIVE = 'minus'
27
35
  const DECIMAL_SEP = 'nukta'
@@ -32,6 +40,7 @@ const DECIMAL_SEP = 'nukta'
32
40
 
33
41
  // Swahili ordinals use "wa" + cardinal: wa kwanza (1st), wa pili (2nd)
34
42
  // First few have special forms
43
+ /** @type {Record<number, string>} */
35
44
  const ORDINAL_ONES = {
36
45
  1: 'wa kwanza',
37
46
  2: 'wa pili',
@@ -41,7 +50,7 @@ const ORDINAL_ONES = {
41
50
  6: 'wa sita',
42
51
  7: 'wa saba',
43
52
  8: 'wa nane',
44
- 9: 'wa tisa'
53
+ 9: 'wa tisa',
45
54
  }
46
55
  const ORDINAL_PREFIX = 'wa'
47
56
 
@@ -56,7 +65,11 @@ const CENT = 'senti'
56
65
  // Conversion Functions
57
66
  // ============================================================================
58
67
 
59
- function wordsUnder100 (n) {
68
+ /**
69
+ * @param {number} n The integer (0-99) to convert
70
+ * @returns {string} The number in Swahili words
71
+ */
72
+ function wordsUnder100(n) {
60
73
  if (n < 10) return ONES[n]
61
74
  if (n === 10) return TENS[10]
62
75
  if (n < 20) {
@@ -69,7 +82,11 @@ function wordsUnder100 (n) {
69
82
  return TENS[tens] + ' na ' + ONES[ones]
70
83
  }
71
84
 
72
- function wordsUnder1000 (n) {
85
+ /**
86
+ * @param {number} n The integer (0-999) to convert
87
+ * @returns {string} The number in Swahili words
88
+ */
89
+ function wordsUnder1000(n) {
73
90
  if (n < 100) return wordsUnder100(n)
74
91
  if (n === 100) return 'mia moja'
75
92
  const hundreds = Math.trunc(n / 100)
@@ -81,7 +98,8 @@ function wordsUnder1000 (n) {
81
98
  if (rest > 0) {
82
99
  if (rest < 10) {
83
100
  parts.push('na ' + ONES[rest])
84
- } else {
101
+ }
102
+ else {
85
103
  parts.push(wordsUnder100(rest))
86
104
  }
87
105
  }
@@ -89,7 +107,11 @@ function wordsUnder1000 (n) {
89
107
  return parts.join(' ')
90
108
  }
91
109
 
92
- function extractSegments (n) {
110
+ /**
111
+ * @param {bigint} n The integer to split into 3-digit segments
112
+ * @returns {number[]} The segments, least-significant first
113
+ */
114
+ function extractSegments(n) {
93
115
  const segments = []
94
116
  let temp = n
95
117
  while (temp > 0n) {
@@ -99,7 +121,11 @@ function extractSegments (n) {
99
121
  return segments
100
122
  }
101
123
 
102
- function integerToWords (n) {
124
+ /**
125
+ * @param {bigint} n The integer to convert
126
+ * @returns {string} The number in Swahili words
127
+ */
128
+ function integerToWords(n) {
103
129
  if (n === 0n) return ZERO
104
130
 
105
131
  // segments stored least-significant first: [ones, thousands, millions, ...]
@@ -115,13 +141,16 @@ function integerToWords (n) {
115
141
  // Units segment
116
142
  if (val < 10 && parts.length > 0) {
117
143
  parts.push('na ' + ONES[val])
118
- } else if (val === 100 && parts.length > 0) {
144
+ }
145
+ else if (val === 100 && parts.length > 0) {
119
146
  // In compound numbers (e.g., 1100 -> 'elfu moja mia'), use 'mia' not 'mia moja'
120
147
  parts.push('mia')
121
- } else {
148
+ }
149
+ else {
122
150
  parts.push(wordsUnder1000(val))
123
151
  }
124
- } else {
152
+ }
153
+ else {
125
154
  // Scale segments: 'elfu moja', 'milioni mbili'
126
155
  const unit = (val === 1) ? 'moja' : wordsUnder1000(val)
127
156
  parts.push(SCALE_WORDS[scaleIndex] + ' ' + unit)
@@ -131,7 +160,11 @@ function integerToWords (n) {
131
160
  return parts.join(' ').trim()
132
161
  }
133
162
 
134
- function decimalPartToWords (decimalPart) {
163
+ /**
164
+ * @param {string} decimalPart The digits after the decimal point
165
+ * @returns {string} The decimal digits in Swahili words
166
+ */
167
+ function decimalPartToWords(decimalPart) {
135
168
  let result = ''
136
169
  let i = 0
137
170
 
@@ -152,12 +185,12 @@ function decimalPartToWords (decimalPart) {
152
185
 
153
186
  /**
154
187
  * Converts a numeric value to Swahili words.
155
- *
156
188
  * @param {number | string | bigint} value - The numeric value to convert
157
189
  * @returns {string} The number in Swahili words
158
190
  */
159
- function toCardinal (value) {
191
+ function toCardinal(value) {
160
192
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
193
+ checkMax(integerPart, cardinalMax, decimalPart)
161
194
 
162
195
  let result = ''
163
196
 
@@ -182,11 +215,10 @@ function toCardinal (value) {
182
215
  * Converts a non-negative integer to Swahili ordinal words.
183
216
  *
184
217
  * Swahili ordinals: wa kwanza (1st), wa pili (2nd), wa tatu (3rd), etc.
185
- *
186
218
  * @param {bigint} n - Positive integer to convert
187
219
  * @returns {string} Swahili ordinal words
188
220
  */
189
- function integerToOrdinal (n) {
221
+ function integerToOrdinal(n) {
190
222
  // Special forms for 1-9
191
223
  if (n >= 1n && n <= 9n) {
192
224
  return ORDINAL_ONES[Number(n)]
@@ -198,19 +230,18 @@ function integerToOrdinal (n) {
198
230
 
199
231
  /**
200
232
  * Converts a numeric value to Swahili ordinal words.
201
- *
202
233
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
203
234
  * @returns {string} The number as ordinal words
204
235
  * @throws {TypeError} If value is not a valid numeric type
205
236
  * @throws {RangeError} If value is negative, zero, or has a decimal part
206
- *
207
237
  * @example
208
238
  * toOrdinal(1) // 'wa kwanza'
209
239
  * toOrdinal(2) // 'wa pili'
210
240
  * toOrdinal(10) // 'wa kumi'
211
241
  */
212
- function toOrdinal (value) {
242
+ function toOrdinal(value) {
213
243
  const integerPart = parseOrdinalValue(value)
244
+ checkMax(integerPart, ordinalMax)
214
245
  return integerToOrdinal(integerPart)
215
246
  }
216
247
 
@@ -222,19 +253,18 @@ function toOrdinal (value) {
222
253
  * Converts a numeric value to Swahili currency words (Kenyan Shilling).
223
254
  *
224
255
  * Uses shilingi and senti (100 senti = 1 shilingi).
225
- *
226
256
  * @param {number | string | bigint} value - The currency amount to convert
227
257
  * @returns {string} The amount in Swahili currency words
228
258
  * @throws {TypeError} If value is not a valid numeric type
229
259
  * @throws {Error} If value is not a valid number format
230
- *
231
260
  * @example
232
261
  * toCurrency(42) // 'shilingi arobaini na mbili'
233
262
  * toCurrency(1.50) // 'shilingi moja na senti hamsini'
234
263
  * toCurrency(-5) // 'minus shilingi tano'
235
264
  */
236
- function toCurrency (value) {
265
+ function toCurrency(value) {
237
266
  const { isNegative, dollars: shillings, cents: senti } = parseCurrencyValue(value)
267
+ checkMax(shillings, currencyMax)
238
268
 
239
269
  let result = ''
240
270
  if (isNegative) {
package/src/ta-IN.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 Tamil words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Tamil words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Tamil 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) // 'முதல்'
18
18
  * toOrdinal(2) // 'இரண்டாவது'
@@ -21,12 +21,10 @@ export function toCardinal(value: number | string | bigint): string;
21
21
  export function toOrdinal(value: number | string | bigint): string;
22
22
  /**
23
23
  * Converts a numeric value to Tamil currency words (Indian Rupee).
24
- *
25
24
  * @param {number | string | bigint} value - The currency amount to convert
26
25
  * @returns {string} The amount in Tamil currency words
27
26
  * @throws {TypeError} If value is not a valid numeric type
28
27
  * @throws {Error} If value is not a valid number format
29
- *
30
28
  * @example
31
29
  * toCurrency(42.50) // 'நாற்பத்திரண்டு ரூபாய் ஐம்பது பைசா'
32
30
  * toCurrency(1) // 'ஒன்று ரூபாய்'
package/src/ta-IN.js CHANGED
@@ -16,6 +16,8 @@
16
16
  import { parseCardinalValue } from './utils/parse-cardinal.js'
17
17
  import { parseCurrencyValue } from './utils/parse-currency.js'
18
18
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
19
+ import { checkMax } from './utils/check-max.js'
20
+ import { indian } from './utils/scale.js'
19
21
 
20
22
  // ============================================================================
21
23
  // Vocabulary
@@ -55,7 +57,7 @@ const BELOW_HUNDRED = [
55
57
  'அறுபது', 'அறுபத்தொன்று', 'அறுபத்திரண்டு', 'அறுபத்திமூன்று', 'அறுபத்திநான்கு', 'அறுபத்தைந்து', 'அறுபத்தாறு', 'அறுபத்தேழு', 'அறுபத்தெட்டு', 'அறுபத்தொன்பது',
56
58
  'எழுபது', 'எழுபத்தொன்று', 'எழுபத்திரண்டு', 'எழுபத்திமூன்று', 'எழுபத்திநான்கு', 'எழுபத்தைந்து', 'எழுபத்தாறு', 'எழுபத்தேழு', 'எழுபத்தெட்டு', 'எழுபத்தொன்பது',
57
59
  'எண்பது', 'எண்பத்தொன்று', 'எண்பத்திரண்டு', 'எண்பத்திமூன்று', 'எண்பத்திநான்கு', 'எண்பத்தைந்து', 'எண்பத்தாறு', 'எண்பத்தேழு', 'எண்பத்தெட்டு', 'எண்பத்தொன்பது',
58
- 'தொண்ணூறு', 'தொண்ணூற்று ஒன்று', 'தொண்ணூற்று இரண்டு', 'தொண்ணூற்று மூன்று', 'தொண்ணூற்று நான்கு', 'தொண்ணூற்று ஐந்து', 'தொண்ணூற்று ஆறு', 'தொண்ணூற்று ஏழு', 'தொண்ணூற்று எட்டு', 'தொண்ணூற்று ஒன்பது'
60
+ 'தொண்ணூறு', 'தொண்ணூற்று ஒன்று', 'தொண்ணூற்று இரண்டு', 'தொண்ணூற்று மூன்று', 'தொண்ணூற்று நான்கு', 'தொண்ணூற்று ஐந்து', 'தொண்ணூற்று ஆறு', 'தொண்ணூற்று ஏழு', 'தொண்ணூற்று எட்டு', 'தொண்ணூற்று ஒன்பது',
59
61
  ]
60
62
 
61
63
  // Standalone hundreds (when not followed by remainder)
@@ -70,17 +72,23 @@ const ONES = ['ஒன்று', 'இரண்டு', 'மூன்று', '
70
72
  // Scale words: index 0 = units, 1 = thousand, 2 = lakh, etc.
71
73
  const SCALE_WORDS = ['', 'ஆயிரம்', 'லட்சம்', 'கோடி', 'அரபு', 'கராபு', 'நீல்', 'பத்ம', 'சங்கு']
72
74
 
75
+ // 3-2-2 Indian grouping: a 3-digit base segment, then 2 digits per scale word
76
+ // (SCALE_WORDS[0] = '' is the units slot). Past the table the scale word is
77
+ // dropped, which collapses the magnitude — so cap there.
78
+ export const cardinalMax = indian(SCALE_WORDS.length)
79
+ export const ordinalMax = indian(SCALE_WORDS.length)
80
+ export const currencyMax = indian(SCALE_WORDS.length)
81
+
73
82
  // ============================================================================
74
83
  // Segment Building
75
84
  // ============================================================================
76
85
 
77
86
  /**
78
87
  * Builds words for a 0-999 segment.
79
- *
80
88
  * @param {number} n - Number 0-999
81
89
  * @returns {string} Tamil words for the segment
82
90
  */
83
- function buildSegment (n) {
91
+ function buildSegment(n) {
84
92
  if (n === 0) return ''
85
93
  if (n < 100) return BELOW_HUNDRED[n]
86
94
 
@@ -104,11 +112,10 @@ function buildSegment (n) {
104
112
  *
105
113
  * Uses BigInt modulo for segment extraction (faster than string slicing).
106
114
  * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
107
- *
108
115
  * @param {bigint} n - Non-negative integer to convert
109
116
  * @returns {string} Tamil words
110
117
  */
111
- function integerToWords (n) {
118
+ function integerToWords(n) {
112
119
  if (n === 0n) return ZERO
113
120
 
114
121
  // Fast path: numbers < 1000
@@ -134,7 +141,8 @@ function integerToWords (n) {
134
141
 
135
142
  if (i === 0) {
136
143
  words.push(buildSegment(segment))
137
- } else {
144
+ }
145
+ else {
138
146
  // Use 'ஒரு' for 1 at scale positions
139
147
  const groupWords = (segment === 1) ? 'ஒரு' : BELOW_HUNDRED[segment]
140
148
  words.push(groupWords)
@@ -148,7 +156,12 @@ function integerToWords (n) {
148
156
  return words.join(' ')
149
157
  }
150
158
 
151
- function decimalPartToWords (decimalPart) {
159
+ /**
160
+ * Converts the decimal portion of a number to Tamil words (per-digit reading).
161
+ * @param {string} decimalPart - The fractional digits as a string
162
+ * @returns {string} Tamil words for each decimal digit
163
+ */
164
+ function decimalPartToWords(decimalPart) {
152
165
  // Per-digit decimal reading
153
166
  const digits = []
154
167
  for (const char of decimalPart) {
@@ -160,12 +173,13 @@ function decimalPartToWords (decimalPart) {
160
173
 
161
174
  /**
162
175
  * Converts a numeric value to Tamil words.
163
- *
164
176
  * @param {number | string | bigint} value - The numeric value to convert
165
177
  * @returns {string} The number in Tamil words
166
178
  */
167
- function toCardinal (value) {
179
+ function toCardinal(value) {
168
180
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
181
+ // The fraction is spelled digit by digit, so only the integer part has a ceiling.
182
+ checkMax(integerPart, cardinalMax)
169
183
 
170
184
  let result = ''
171
185
 
@@ -190,11 +204,10 @@ function toCardinal (value) {
190
204
  * Converts a positive integer to Tamil ordinal words.
191
205
  *
192
206
  * Tamil ordinals: First has special form, 2-6 have special suffixes, then -ஆவது suffix.
193
- *
194
207
  * @param {bigint} n - Positive integer to convert
195
208
  * @returns {string} Tamil ordinal words
196
209
  */
197
- function integerToOrdinal (n) {
210
+ function integerToOrdinal(n) {
198
211
  // Special ordinals for 1-6
199
212
  if (n >= 1n && n <= 6n) {
200
213
  return ORDINAL_SPECIAL[Number(n)]
@@ -207,19 +220,19 @@ function integerToOrdinal (n) {
207
220
 
208
221
  /**
209
222
  * Converts a numeric value to Tamil ordinal words.
210
- *
211
223
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
212
224
  * @returns {string} The number as ordinal words
213
225
  * @throws {TypeError} If value is not a valid numeric type
214
226
  * @throws {RangeError} If value is negative, zero, or has a decimal part
215
- *
216
227
  * @example
217
228
  * toOrdinal(1) // 'முதல்'
218
229
  * toOrdinal(2) // 'இரண்டாவது'
219
230
  * toOrdinal(10) // 'பத்துஆவது'
220
231
  */
221
- function toOrdinal (value) {
232
+ function toOrdinal(value) {
222
233
  const integerPart = parseOrdinalValue(value)
234
+ // Ordinals build on the cardinal speller, so they share its ceiling.
235
+ checkMax(integerPart, ordinalMax)
223
236
  return integerToOrdinal(integerPart)
224
237
  }
225
238
 
@@ -229,19 +242,18 @@ function toOrdinal (value) {
229
242
 
230
243
  /**
231
244
  * Converts a numeric value to Tamil currency words (Indian Rupee).
232
- *
233
245
  * @param {number | string | bigint} value - The currency amount to convert
234
246
  * @returns {string} The amount in Tamil currency words
235
247
  * @throws {TypeError} If value is not a valid numeric type
236
248
  * @throws {Error} If value is not a valid number format
237
- *
238
249
  * @example
239
250
  * toCurrency(42.50) // 'நாற்பத்திரண்டு ரூபாய் ஐம்பது பைசா'
240
251
  * toCurrency(1) // 'ஒன்று ரூபாய்'
241
252
  * toCurrency(0.01) // 'ஒன்று பைசா'
242
253
  */
243
- function toCurrency (value) {
254
+ function toCurrency(value) {
244
255
  const { isNegative, dollars: rupees, cents: paise } = parseCurrencyValue(value)
256
+ checkMax(rupees, currencyMax)
245
257
 
246
258
  // Build result
247
259
  let result = ''
package/src/te-IN.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 Telugu words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Telugu words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Telugu 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) // 'మొదటి'
18
18
  * toOrdinal(2) // 'రెండవ'
@@ -21,12 +21,10 @@ export function toCardinal(value: number | string | bigint): string;
21
21
  export function toOrdinal(value: number | string | bigint): string;
22
22
  /**
23
23
  * Converts a numeric value to Telugu currency words (Indian Rupee).
24
- *
25
24
  * @param {number | string | bigint} value - The currency amount to convert
26
25
  * @returns {string} The amount in Telugu currency words
27
26
  * @throws {TypeError} If value is not a valid numeric type
28
27
  * @throws {Error} If value is not a valid number format
29
- *
30
28
  * @example
31
29
  * toCurrency(42.50) // 'నలభై రెండు రూపాయలు యాభై పైసలు'
32
30
  * toCurrency(1) // 'ఒకటి రూపాయి'
package/src/te-IN.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 { indian } from './utils/scale.js'
17
19
 
18
20
  // ============================================================================
19
21
  // Vocabulary
@@ -55,7 +57,7 @@ const BELOW_HUNDRED = [
55
57
  'అరవై', 'అరవై ఒకటి', 'అరవై రెండు', 'అరవై మూడు', 'అరవై నాలుగు', 'అరవై ఐదు', 'అరవై ఆరు', 'అరవై ఏడు', 'అరవై ఎనిమిది', 'అరవై తొమ్మిది',
56
58
  'డెబ్బై', 'డెబ్బై ఒకటి', 'డెబ్బై రెండు', 'డెబ్బై మూడు', 'డెబ్బై నాలుగు', 'డెబ్బై ఐదు', 'డెబ్బై ఆరు', 'డెబ్బై ఏడు', 'డెబ్బై ఎనిమిది', 'డెబ్బై తొమ్మిది',
57
59
  'ఎనభై', 'ఎనభై ఒకటి', 'ఎనభై రెండు', 'ఎనభై మూడు', 'ఎనభై నాలుగు', 'ఎనభై ఐదు', 'ఎనభై ఆరు', 'ఎనభై ఏడు', 'ఎనభై ఎనిమిది', 'ఎనభై తొమ్మిది',
58
- 'తొంభై', 'తొంభై ఒకటి', 'తొంభై రెండు', 'తొంభై మూడు', 'తొంభై నాలుగు', 'తొంభై ఐదు', 'తొంభై ఆరు', 'తొంభై ఏడు', 'తొంభై ఎనిమిది', 'తొంభై తొమ్మిది'
60
+ 'తొంభై', 'తొంభై ఒకటి', 'తొంభై రెండు', 'తొంభై మూడు', 'తొంభై నాలుగు', 'తొంభై ఐదు', 'తొంభై ఆరు', 'తొంభై ఏడు', 'తొంభై ఎనిమిది', 'తొంభై తొమ్మిది',
59
61
  ]
60
62
 
61
63
  const HUNDREDS = ['', 'వంద', 'రెండు వందలు', 'మూడు వందలు', 'నాలుగు వందలు', 'ఐదు వందలు', 'ఆరు వందలు', 'ఏడు వందలు', 'ఎనిమిది వందలు', 'తొమ్మిది వందలు']
@@ -66,14 +68,23 @@ const ONES = ['ఒకటి', 'రెండు', 'మూడు', 'నాలు
66
68
  // Scale words: index 0 = units, 1 = thousand, 2 = lakh, etc.
67
69
  const SCALE_WORDS = ['', 'వెయ్యి', 'లక్ష', 'కోటి', 'అరబ్', 'ఖరబ్', 'నిల్', 'పడ్మ', 'శంకు']
68
70
 
71
+ // 3-2-2 Indian grouping: a 3-digit base segment, then 2 digits per scale word
72
+ // (SCALE_WORDS[0] = '' is the units slot). Past the table the scale word is
73
+ // dropped, which collapses the magnitude — so cap there.
74
+ export const cardinalMax = indian(SCALE_WORDS.length)
75
+ export const ordinalMax = indian(SCALE_WORDS.length)
76
+ export const currencyMax = indian(SCALE_WORDS.length)
77
+
69
78
  // ============================================================================
70
79
  // Segment Building
71
80
  // ============================================================================
72
81
 
73
82
  /**
74
83
  * Builds words for a 0-999 segment.
84
+ * @param {number} n - Segment value (0-999)
85
+ * @returns {string} Telugu words for the segment
75
86
  */
76
- function buildSegment (n) {
87
+ function buildSegment(n) {
77
88
  if (n === 0) return ''
78
89
  if (n < 100) return BELOW_HUNDRED[n]
79
90
 
@@ -95,11 +106,10 @@ function buildSegment (n) {
95
106
  *
96
107
  * Uses BigInt modulo for segment extraction (faster than string slicing).
97
108
  * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
98
- *
99
109
  * @param {bigint} n - Non-negative integer to convert
100
110
  * @returns {string} Telugu words
101
111
  */
102
- function integerToWords (n) {
112
+ function integerToWords(n) {
103
113
  if (n === 0n) return ZERO
104
114
 
105
115
  // Fast path: numbers < 1000 (direct lookup)
@@ -125,7 +135,8 @@ function integerToWords (n) {
125
135
 
126
136
  if (i === 0) {
127
137
  words.push(buildSegment(segment))
128
- } else {
138
+ }
139
+ else {
129
140
  // Use 'ఒక' for 1 at scale positions
130
141
  const groupWords = (segment === 1) ? 'ఒక' : BELOW_HUNDRED[segment]
131
142
  words.push(groupWords)
@@ -139,7 +150,12 @@ function integerToWords (n) {
139
150
  return words.join(' ')
140
151
  }
141
152
 
142
- function decimalPartToWords (decimalPart) {
153
+ /**
154
+ * Reads a decimal fraction digit-by-digit in Telugu.
155
+ * @param {string} decimalPart - Fractional digits as a string
156
+ * @returns {string} Telugu words for each digit
157
+ */
158
+ function decimalPartToWords(decimalPart) {
143
159
  // Per-digit decimal reading
144
160
  const digits = []
145
161
  for (const char of decimalPart) {
@@ -151,12 +167,13 @@ function decimalPartToWords (decimalPart) {
151
167
 
152
168
  /**
153
169
  * Converts a numeric value to Telugu words.
154
- *
155
170
  * @param {number | string | bigint} value - The numeric value to convert
156
171
  * @returns {string} The number in Telugu words
157
172
  */
158
- function toCardinal (value) {
173
+ function toCardinal(value) {
159
174
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
175
+ // The fraction is spelled digit by digit, so only the integer part has a ceiling.
176
+ checkMax(integerPart, cardinalMax)
160
177
 
161
178
  let result = ''
162
179
 
@@ -181,11 +198,10 @@ function toCardinal (value) {
181
198
  * Converts a positive integer to Telugu ordinal words.
182
199
  *
183
200
  * Telugu ordinals: First 6 are irregular, then add -వ suffix.
184
- *
185
201
  * @param {bigint} n - Positive integer to convert
186
202
  * @returns {string} Telugu ordinal words
187
203
  */
188
- function integerToOrdinal (n) {
204
+ function integerToOrdinal(n) {
189
205
  // Special ordinals for 1-6
190
206
  if (n >= 1n && n <= 6n) {
191
207
  return ORDINAL_SPECIAL[Number(n)]
@@ -198,19 +214,19 @@ function integerToOrdinal (n) {
198
214
 
199
215
  /**
200
216
  * Converts a numeric value to Telugu ordinal words.
201
- *
202
217
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
203
218
  * @returns {string} The number as ordinal words
204
219
  * @throws {TypeError} If value is not a valid numeric type
205
220
  * @throws {RangeError} If value is negative, zero, or has a decimal part
206
- *
207
221
  * @example
208
222
  * toOrdinal(1) // 'మొదటి'
209
223
  * toOrdinal(2) // 'రెండవ'
210
224
  * toOrdinal(10) // 'పదివ'
211
225
  */
212
- function toOrdinal (value) {
226
+ function toOrdinal(value) {
213
227
  const integerPart = parseOrdinalValue(value)
228
+ // Ordinals build on the cardinal speller, so they share its ceiling.
229
+ checkMax(integerPart, ordinalMax)
214
230
  return integerToOrdinal(integerPart)
215
231
  }
216
232
 
@@ -220,19 +236,18 @@ function toOrdinal (value) {
220
236
 
221
237
  /**
222
238
  * Converts a numeric value to Telugu currency words (Indian Rupee).
223
- *
224
239
  * @param {number | string | bigint} value - The currency amount to convert
225
240
  * @returns {string} The amount in Telugu currency words
226
241
  * @throws {TypeError} If value is not a valid numeric type
227
242
  * @throws {Error} If value is not a valid number format
228
- *
229
243
  * @example
230
244
  * toCurrency(42.50) // 'నలభై రెండు రూపాయలు యాభై పైసలు'
231
245
  * toCurrency(1) // 'ఒకటి రూపాయి'
232
246
  * toCurrency(0.01) // 'ఒకటి పైసా'
233
247
  */
234
- function toCurrency (value) {
248
+ function toCurrency(value) {
235
249
  const { isNegative, dollars: rupees, cents: paise } = parseCurrencyValue(value)
250
+ checkMax(rupees, currencyMax)
236
251
 
237
252
  // Build result
238
253
  let result = ''
package/src/th-TH.d.ts CHANGED
@@ -1,18 +1,18 @@
1
+ export const cardinalMax: null;
2
+ export const ordinalMax: null;
3
+ export const currencyMax: null;
1
4
  /**
2
5
  * Converts a numeric value to Thai words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Thai words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Thai 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) // 'ที่หนึ่ง'
18
18
  * toOrdinal(2) // 'ที่สอง'
@@ -24,12 +24,10 @@ export function toOrdinal(value: number | string | bigint): string;
24
24
  *
25
25
  * Thai Baht uses satang as subunit (100 satang = 1 baht).
26
26
  * When whole amounts, adds "ถ้วน" (exactly) suffix.
27
- *
28
27
  * @param {number | string | bigint} value - The currency amount to convert
29
28
  * @returns {string} The amount in Thai currency words
30
29
  * @throws {TypeError} If value is not a valid numeric type
31
30
  * @throws {Error} If value is not a valid number format
32
- *
33
31
  * @example
34
32
  * toCurrency(42) // 'สี่สิบสองบาทถ้วน'
35
33
  * toCurrency(1.50) // 'หนึ่งบาทห้าสิบสตางค์'