n2words 2.0.0 → 3.0.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 (327) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +86 -188
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -0
  8. package/dist/languages/ar.js.map +1 -0
  9. package/dist/languages/az.js +3 -0
  10. package/dist/languages/az.js.map +1 -0
  11. package/dist/languages/bn.js +3 -0
  12. package/dist/languages/bn.js.map +1 -0
  13. package/dist/languages/cs.js +3 -0
  14. package/dist/languages/cs.js.map +1 -0
  15. package/dist/languages/da.js +3 -0
  16. package/dist/languages/da.js.map +1 -0
  17. package/dist/languages/de.js +3 -0
  18. package/dist/languages/de.js.map +1 -0
  19. package/dist/languages/el.js +3 -0
  20. package/dist/languages/el.js.map +1 -0
  21. package/dist/languages/en.js +3 -0
  22. package/dist/languages/en.js.map +1 -0
  23. package/dist/languages/es.js +3 -0
  24. package/dist/languages/es.js.map +1 -0
  25. package/dist/languages/fa.js +3 -0
  26. package/dist/languages/fa.js.map +1 -0
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -0
  30. package/dist/languages/fil.js.map +1 -0
  31. package/dist/languages/fr-BE.js +3 -0
  32. package/dist/languages/fr-BE.js.map +1 -0
  33. package/dist/languages/fr.js +3 -0
  34. package/dist/languages/fr.js.map +1 -0
  35. package/dist/languages/gu.js +3 -0
  36. package/dist/languages/gu.js.map +1 -0
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -0
  42. package/dist/languages/he.js.map +1 -0
  43. package/dist/languages/hi.js +3 -0
  44. package/dist/languages/hi.js.map +1 -0
  45. package/dist/languages/hr.js +3 -0
  46. package/dist/languages/hr.js.map +1 -0
  47. package/dist/languages/hu.js +3 -0
  48. package/dist/languages/hu.js.map +1 -0
  49. package/dist/languages/id.js +3 -0
  50. package/dist/languages/id.js.map +1 -0
  51. package/dist/languages/it.js +3 -0
  52. package/dist/languages/it.js.map +1 -0
  53. package/dist/languages/ja.js +3 -0
  54. package/dist/languages/ja.js.map +1 -0
  55. package/dist/languages/kn.js +3 -0
  56. package/dist/languages/kn.js.map +1 -0
  57. package/dist/languages/ko.js +3 -0
  58. package/dist/languages/ko.js.map +1 -0
  59. package/dist/languages/lt.js +3 -0
  60. package/dist/languages/lt.js.map +1 -0
  61. package/dist/languages/lv.js +3 -0
  62. package/dist/languages/lv.js.map +1 -0
  63. package/dist/languages/mr.js +3 -0
  64. package/dist/languages/mr.js.map +1 -0
  65. package/dist/languages/ms.js +3 -0
  66. package/dist/languages/ms.js.map +1 -0
  67. package/dist/languages/nb.js +3 -0
  68. package/dist/languages/nb.js.map +1 -0
  69. package/dist/languages/nl.js +3 -0
  70. package/dist/languages/nl.js.map +1 -0
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -0
  74. package/dist/languages/pl.js.map +1 -0
  75. package/dist/languages/pt.js +3 -0
  76. package/dist/languages/pt.js.map +1 -0
  77. package/dist/languages/ro.js +3 -0
  78. package/dist/languages/ro.js.map +1 -0
  79. package/dist/languages/ru.js +3 -0
  80. package/dist/languages/ru.js.map +1 -0
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -0
  84. package/dist/languages/sr-Latn.js.map +1 -0
  85. package/dist/languages/sv.js +3 -0
  86. package/dist/languages/sv.js.map +1 -0
  87. package/dist/languages/sw.js +3 -0
  88. package/dist/languages/sw.js.map +1 -0
  89. package/dist/languages/ta.js +3 -0
  90. package/dist/languages/ta.js.map +1 -0
  91. package/dist/languages/te.js +3 -0
  92. package/dist/languages/te.js.map +1 -0
  93. package/dist/languages/th.js +3 -0
  94. package/dist/languages/th.js.map +1 -0
  95. package/dist/languages/tr.js +3 -0
  96. package/dist/languages/tr.js.map +1 -0
  97. package/dist/languages/uk.js +3 -0
  98. package/dist/languages/uk.js.map +1 -0
  99. package/dist/languages/ur.js +3 -0
  100. package/dist/languages/ur.js.map +1 -0
  101. package/dist/languages/vi.js +3 -0
  102. package/dist/languages/vi.js.map +1 -0
  103. package/dist/languages/zh-Hans.js +3 -0
  104. package/dist/languages/zh-Hans.js.map +1 -0
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +2 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +14 -27
  114. package/lib/languages/ar.js +175 -129
  115. package/lib/languages/az.d.ts +4 -9
  116. package/lib/languages/az.js +171 -37
  117. package/lib/languages/bn.d.ts +4 -8
  118. package/lib/languages/bn.js +138 -124
  119. package/lib/languages/cs.d.ts +15 -85
  120. package/lib/languages/cs.js +310 -114
  121. package/lib/languages/da.d.ts +11 -12
  122. package/lib/languages/da.js +276 -101
  123. package/lib/languages/de.d.ts +14 -11
  124. package/lib/languages/de.js +317 -86
  125. package/lib/languages/el.d.ts +11 -11
  126. package/lib/languages/el.js +231 -78
  127. package/lib/languages/en.d.ts +14 -13
  128. package/lib/languages/en.js +242 -72
  129. package/lib/languages/es.d.ts +18 -12
  130. package/lib/languages/es.js +317 -103
  131. package/lib/languages/fa.d.ts +4 -44
  132. package/lib/languages/fa.js +112 -122
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +4 -13
  136. package/lib/languages/fil.js +207 -106
  137. package/lib/languages/fr-BE.d.ts +8 -8
  138. package/lib/languages/fr-BE.js +294 -19
  139. package/lib/languages/fr.d.ts +18 -12
  140. package/lib/languages/fr.js +352 -89
  141. package/lib/languages/gu.d.ts +4 -8
  142. package/lib/languages/gu.js +130 -125
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +10 -110
  146. package/lib/languages/hbo.js +263 -214
  147. package/lib/languages/he.d.ts +10 -77
  148. package/lib/languages/he.js +242 -172
  149. package/lib/languages/hi.d.ts +4 -8
  150. package/lib/languages/hi.js +138 -124
  151. package/lib/languages/hr.d.ts +8 -77
  152. package/lib/languages/hr.js +194 -89
  153. package/lib/languages/hu.d.ts +4 -19
  154. package/lib/languages/hu.js +198 -119
  155. package/lib/languages/id.d.ts +4 -34
  156. package/lib/languages/id.js +171 -129
  157. package/lib/languages/it.d.ts +16 -34
  158. package/lib/languages/it.js +339 -94
  159. package/lib/languages/ja.d.ts +14 -14
  160. package/lib/languages/ja.js +233 -111
  161. package/lib/languages/kn.d.ts +4 -8
  162. package/lib/languages/kn.js +130 -35
  163. package/lib/languages/ko.d.ts +11 -11
  164. package/lib/languages/ko.js +257 -49
  165. package/lib/languages/lt.d.ts +15 -67
  166. package/lib/languages/lt.js +296 -122
  167. package/lib/languages/lv.d.ts +15 -67
  168. package/lib/languages/lv.js +297 -106
  169. package/lib/languages/mr.d.ts +4 -8
  170. package/lib/languages/mr.js +130 -125
  171. package/lib/languages/ms.d.ts +4 -28
  172. package/lib/languages/ms.js +171 -116
  173. package/lib/languages/nb.d.ts +11 -9
  174. package/lib/languages/nb.js +282 -87
  175. package/lib/languages/nl.d.ts +23 -13
  176. package/lib/languages/nl.js +317 -133
  177. package/lib/languages/pa.d.ts +4 -8
  178. package/lib/languages/pa.js +156 -124
  179. package/lib/languages/pl.d.ts +19 -77
  180. package/lib/languages/pl.js +307 -87
  181. package/lib/languages/pt.d.ts +14 -26
  182. package/lib/languages/pt.js +286 -92
  183. package/lib/languages/ro.d.ts +15 -155
  184. package/lib/languages/ro.js +219 -235
  185. package/lib/languages/ru.d.ts +8 -82
  186. package/lib/languages/ru.js +222 -78
  187. package/lib/languages/sr-Cyrl.d.ts +8 -77
  188. package/lib/languages/sr-Cyrl.js +191 -89
  189. package/lib/languages/sr-Latn.d.ts +8 -77
  190. package/lib/languages/sr-Latn.js +191 -89
  191. package/lib/languages/sv.d.ts +11 -11
  192. package/lib/languages/sv.js +288 -74
  193. package/lib/languages/sw.d.ts +4 -36
  194. package/lib/languages/sw.js +133 -106
  195. package/lib/languages/ta.d.ts +4 -17
  196. package/lib/languages/ta.js +129 -201
  197. package/lib/languages/te.d.ts +4 -19
  198. package/lib/languages/te.js +141 -196
  199. package/lib/languages/th.d.ts +4 -14
  200. package/lib/languages/th.js +135 -91
  201. package/lib/languages/tr.d.ts +15 -9
  202. package/lib/languages/tr.js +256 -49
  203. package/lib/languages/uk.d.ts +8 -82
  204. package/lib/languages/uk.js +200 -78
  205. package/lib/languages/ur.d.ts +4 -8
  206. package/lib/languages/ur.js +156 -124
  207. package/lib/languages/vi.d.ts +14 -69
  208. package/lib/languages/vi.js +294 -125
  209. package/lib/languages/zh-Hans.d.ts +8 -18
  210. package/lib/languages/zh-Hans.js +163 -92
  211. package/lib/languages/zh-Hant.d.ts +8 -18
  212. package/lib/languages/zh-Hant.js +181 -90
  213. package/lib/n2words.d.ts +53 -209
  214. package/lib/n2words.js +111 -530
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +26 -14
  222. package/dist/ArabicConverter.js +0 -3
  223. package/dist/ArabicConverter.js.map +0 -1
  224. package/dist/AzerbaijaniConverter.js +0 -3
  225. package/dist/AzerbaijaniConverter.js.map +0 -1
  226. package/dist/BanglaConverter.js +0 -3
  227. package/dist/BanglaConverter.js.map +0 -1
  228. package/dist/BiblicalHebrewConverter.js +0 -3
  229. package/dist/BiblicalHebrewConverter.js.map +0 -1
  230. package/dist/CroatianConverter.js +0 -3
  231. package/dist/CroatianConverter.js.map +0 -1
  232. package/dist/CzechConverter.js +0 -3
  233. package/dist/CzechConverter.js.map +0 -1
  234. package/dist/DanishConverter.js +0 -3
  235. package/dist/DanishConverter.js.map +0 -1
  236. package/dist/DutchConverter.js +0 -3
  237. package/dist/DutchConverter.js.map +0 -1
  238. package/dist/EnglishConverter.js +0 -3
  239. package/dist/EnglishConverter.js.map +0 -1
  240. package/dist/FilipinoConverter.js +0 -3
  241. package/dist/FilipinoConverter.js.map +0 -1
  242. package/dist/FrenchBelgiumConverter.js +0 -3
  243. package/dist/FrenchBelgiumConverter.js.map +0 -1
  244. package/dist/FrenchConverter.js +0 -3
  245. package/dist/FrenchConverter.js.map +0 -1
  246. package/dist/GermanConverter.js +0 -3
  247. package/dist/GermanConverter.js.map +0 -1
  248. package/dist/GreekConverter.js +0 -3
  249. package/dist/GreekConverter.js.map +0 -1
  250. package/dist/GujaratiConverter.js +0 -3
  251. package/dist/GujaratiConverter.js.map +0 -1
  252. package/dist/HebrewConverter.js +0 -3
  253. package/dist/HebrewConverter.js.map +0 -1
  254. package/dist/HindiConverter.js +0 -3
  255. package/dist/HindiConverter.js.map +0 -1
  256. package/dist/HungarianConverter.js +0 -3
  257. package/dist/HungarianConverter.js.map +0 -1
  258. package/dist/IndonesianConverter.js +0 -3
  259. package/dist/IndonesianConverter.js.map +0 -1
  260. package/dist/ItalianConverter.js +0 -3
  261. package/dist/ItalianConverter.js.map +0 -1
  262. package/dist/JapaneseConverter.js +0 -3
  263. package/dist/JapaneseConverter.js.map +0 -1
  264. package/dist/KannadaConverter.js +0 -3
  265. package/dist/KannadaConverter.js.map +0 -1
  266. package/dist/KoreanConverter.js +0 -3
  267. package/dist/KoreanConverter.js.map +0 -1
  268. package/dist/LatvianConverter.js +0 -3
  269. package/dist/LatvianConverter.js.map +0 -1
  270. package/dist/LithuanianConverter.js +0 -3
  271. package/dist/LithuanianConverter.js.map +0 -1
  272. package/dist/MalayConverter.js +0 -3
  273. package/dist/MalayConverter.js.map +0 -1
  274. package/dist/MarathiConverter.js +0 -3
  275. package/dist/MarathiConverter.js.map +0 -1
  276. package/dist/NorwegianBokmalConverter.js +0 -3
  277. package/dist/NorwegianBokmalConverter.js.map +0 -1
  278. package/dist/PersianConverter.js +0 -3
  279. package/dist/PersianConverter.js.map +0 -1
  280. package/dist/PolishConverter.js +0 -3
  281. package/dist/PolishConverter.js.map +0 -1
  282. package/dist/PortugueseConverter.js +0 -3
  283. package/dist/PortugueseConverter.js.map +0 -1
  284. package/dist/PunjabiConverter.js +0 -3
  285. package/dist/PunjabiConverter.js.map +0 -1
  286. package/dist/RomanianConverter.js +0 -3
  287. package/dist/RomanianConverter.js.map +0 -1
  288. package/dist/RussianConverter.js +0 -3
  289. package/dist/RussianConverter.js.map +0 -1
  290. package/dist/SerbianCyrillicConverter.js +0 -3
  291. package/dist/SerbianCyrillicConverter.js.map +0 -1
  292. package/dist/SerbianLatinConverter.js +0 -3
  293. package/dist/SerbianLatinConverter.js.map +0 -1
  294. package/dist/SimplifiedChineseConverter.js +0 -3
  295. package/dist/SimplifiedChineseConverter.js.map +0 -1
  296. package/dist/SpanishConverter.js +0 -3
  297. package/dist/SpanishConverter.js.map +0 -1
  298. package/dist/SwahiliConverter.js +0 -3
  299. package/dist/SwahiliConverter.js.map +0 -1
  300. package/dist/SwedishConverter.js +0 -3
  301. package/dist/SwedishConverter.js.map +0 -1
  302. package/dist/TamilConverter.js +0 -3
  303. package/dist/TamilConverter.js.map +0 -1
  304. package/dist/TeluguConverter.js +0 -3
  305. package/dist/TeluguConverter.js.map +0 -1
  306. package/dist/ThaiConverter.js +0 -3
  307. package/dist/ThaiConverter.js.map +0 -1
  308. package/dist/TraditionalChineseConverter.js +0 -3
  309. package/dist/TraditionalChineseConverter.js.map +0 -1
  310. package/dist/TurkishConverter.js +0 -3
  311. package/dist/TurkishConverter.js.map +0 -1
  312. package/dist/UkrainianConverter.js +0 -3
  313. package/dist/UkrainianConverter.js.map +0 -1
  314. package/dist/UrduConverter.js +0 -3
  315. package/dist/UrduConverter.js.map +0 -1
  316. package/dist/VietnameseConverter.js +0 -3
  317. package/dist/VietnameseConverter.js.map +0 -1
  318. package/lib/classes/abstract-language.d.ts +0 -178
  319. package/lib/classes/abstract-language.js +0 -268
  320. package/lib/classes/greedy-scale-language.d.ts +0 -109
  321. package/lib/classes/greedy-scale-language.js +0 -201
  322. package/lib/classes/slavic-language.d.ts +0 -148
  323. package/lib/classes/slavic-language.js +0 -281
  324. package/lib/classes/south-asian-language.d.ts +0 -70
  325. package/lib/classes/south-asian-language.js +0 -154
  326. package/lib/classes/turkic-language.d.ts +0 -26
  327. package/lib/classes/turkic-language.js +0 -59
@@ -1,113 +1,215 @@
1
- import { SlavicLanguage } from '../classes/slavic-language.js'
2
-
3
1
  /**
4
- * Serbian language converter (Cyrillic script).
2
+ * Serbian Cyrillic language converter - Functional Implementation
3
+ *
4
+ * Self-contained converter using shared Slavic utilities.
5
5
  *
6
- * Supports:
6
+ * Key features:
7
7
  * - Three-form pluralization (one/few/many)
8
- * - Gender agreement (један/једна, два/две)
9
- * - Cyrillic script representation
8
+ * - Gender: thousands are feminine, millions+ are masculine
9
+ * - Irregular hundreds
10
+ * - Long scale naming with -ard forms
11
+ * - Cyrillic script
10
12
  */
11
- export class SerbianCyrillic extends SlavicLanguage {
12
- negativeWord = 'минус'
13
- decimalSeparatorWord = 'запета'
14
- zeroWord = 'нула'
15
-
16
- onesWords = {
17
- 1: 'један',
18
- 2: 'два',
19
- 3: 'три',
20
- 4: 'четири',
21
- 5: 'пет',
22
- 6: 'шест',
23
- 7: 'седам',
24
- 8: 'осам',
25
- 9: 'девет'
13
+
14
+ import { parseNumericValue } from '../utils/parse-numeric.js'
15
+ import { validateOptions } from '../utils/validate-options.js'
16
+
17
+ // ============================================================================
18
+ // Slavic Utilities (inlined for performance)
19
+ // ============================================================================
20
+
21
+ function pluralize (n, forms) {
22
+ const num = typeof n === 'bigint' ? Number(n) : n
23
+ const lastDigit = num % 10
24
+ const lastTwoDigits = num % 100
25
+
26
+ if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {
27
+ return forms[2]
26
28
  }
27
29
 
28
- onesFeminineWords = {
29
- 1: 'једна',
30
- 2: 'две',
31
- 3: 'три',
32
- 4: 'четири',
33
- 5: 'пет',
34
- 6: 'шест',
35
- 7: 'седам',
36
- 8: 'осам',
37
- 9: 'девет'
30
+ if (lastDigit === 1) return forms[0]
31
+ if (lastDigit >= 2 && lastDigit <= 4) return forms[1]
32
+ return forms[2]
33
+ }
34
+
35
+ function buildAllSegments (onesMasc, onesFem, teens, tens, hundreds) {
36
+ const masc = new Array(1000)
37
+ const fem = new Array(1000)
38
+
39
+ for (let i = 0; i < 1000; i++) {
40
+ masc[i] = buildSegment(i, onesMasc, teens, tens, hundreds)
41
+ fem[i] = buildSegment(i, onesFem, teens, tens, hundreds)
38
42
  }
39
43
 
40
- teensWords = {
41
- 0: 'десет',
42
- 1: 'једанаест',
43
- 2: 'дванаест',
44
- 3: 'тринаест',
45
- 4: 'четрнаест',
46
- 5: 'петнаест',
47
- 6: 'шеснаест',
48
- 7: 'седамнаест',
49
- 8: 'осамнаест',
50
- 9: 'деветнаест'
44
+ return { masc, fem }
45
+ }
46
+
47
+ function buildSegment (n, ones, teens, tens, hundreds) {
48
+ if (n === 0) return ''
49
+
50
+ const onesDigit = n % 10
51
+ const tensDigit = Math.floor(n / 10) % 10
52
+ const hundredsDigit = Math.floor(n / 100)
53
+
54
+ const parts = []
55
+
56
+ if (hundredsDigit > 0) {
57
+ parts.push(hundreds[hundredsDigit])
51
58
  }
52
59
 
53
- twentiesWords = {
54
- 2: 'двадесет',
55
- 3: 'тридесет',
56
- 4: 'четрдесет',
57
- 5: 'педесет',
58
- 6: 'шездесет',
59
- 7: 'седамдесет',
60
- 8: 'осамдесет',
61
- 9: 'деведесет'
60
+ if (tensDigit > 1) {
61
+ parts.push(tens[tensDigit])
62
62
  }
63
63
 
64
- hundredsWords = {
65
- 1: 'сто',
66
- 2: 'двеста',
67
- 3: 'триста',
68
- 4: 'четиристо',
69
- 5: 'петсто',
70
- 6: 'шесто',
71
- 7: 'седамсто',
72
- 8: 'осамсто',
73
- 9: 'девестo'
64
+ if (tensDigit === 1) {
65
+ parts.push(teens[onesDigit])
66
+ } else if (onesDigit > 0) {
67
+ parts.push(ones[onesDigit])
74
68
  }
75
69
 
76
- pluralForms = {
77
- 1: ['хиљада', 'хиљаде', 'хиљада'], // 10 ^ 3
78
- 2: ['милион', 'милиона', 'милиона'], // 10 ^ 6
79
- 3: ['милијарда', 'милијарде', 'милијарда'], // 10 ^ 9
80
- 4: ['билион', 'билиона', 'билиона'], // 10 ^ 12
81
- 5: ['билијарда', 'билијарде', 'билијарда'], // 10 ^ 15
82
- 6: ['трилион', 'трилиона', 'трилиона'], // 10 ^ 18
83
- 7: ['трилијарда', 'трилијарде', 'трилијарда'], // 10 ^ 21
84
- 8: ['квадрилион', 'квадрилиона', 'квадрилиона'], // 10 ^ 24
85
- 9: ['квадрилијарда', 'квадрилијарде', 'квадрилијарда'], // 10 ^ 27
86
- 10: ['квинтилион', 'квинтилиона', 'квинтилиона'] // 10 ^ 30
70
+ return parts.join(' ')
71
+ }
72
+
73
+ // ============================================================================
74
+ // Vocabulary
75
+ // ============================================================================
76
+
77
+ const ONES_MASC = ['', 'један', 'два', 'три', 'четири', 'пет', 'шест', 'седам', 'осам', 'девет']
78
+ const ONES_FEM = ['', 'једна', 'две', 'три', 'четири', 'пет', 'шест', 'седам', 'осам', 'девет']
79
+ const TEENS = ['десет', 'једанаест', 'дванаест', 'тринаест', 'четрнаест', 'петнаест', 'шеснаест', 'седамнаест', 'осамнаест', 'деветнаест']
80
+ const TENS = ['', '', 'двадесет', 'тридесет', 'четрдесет', 'педесет', 'шездесет', 'седамдесет', 'осамдесет', 'деведесет']
81
+ const HUNDREDS = ['', 'сто', 'двеста', 'триста', 'четиристо', 'петсто', 'шесто', 'седамсто', 'осамсто', 'девестo']
82
+
83
+ const ZERO = 'нула'
84
+ const NEGATIVE = 'минус'
85
+ const DECIMAL_SEP = 'запета'
86
+
87
+ // Scale words: [singular, few, many]
88
+ const SCALE_FORMS = [
89
+ ['хиљада', 'хиљаде', 'хиљада'],
90
+ ['милион', 'милиона', 'милиона'],
91
+ ['милијарда', 'милијарде', 'милијарда'],
92
+ ['билион', 'билиона', 'билиона'],
93
+ ['билијарда', 'билијарде', 'билијарда'],
94
+ ['трилион', 'трилиона', 'трилиона'],
95
+ ['трилијарда', 'трилијарде', 'трилијарда'],
96
+ ['квадрилион', 'квадрилиона', 'квадрилиона'],
97
+ ['квадрилијарда', 'квадрилијарде', 'квадрилијарда']
98
+ ]
99
+
100
+ // ============================================================================
101
+ // Precomputed Lookup Tables
102
+ // ============================================================================
103
+
104
+ const { masc: SEGMENTS_MASC, fem: SEGMENTS_FEM } = buildAllSegments(ONES_MASC, ONES_FEM, TEENS, TENS, HUNDREDS)
105
+
106
+ // ============================================================================
107
+ // Conversion Functions
108
+ // ============================================================================
109
+
110
+ function integerToWords (n, options = {}) {
111
+ if (n === 0n) return ZERO
112
+
113
+ if (n < 1000n) {
114
+ const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
115
+ return segments[Number(n)]
87
116
  }
88
117
 
89
- /**
90
- * Maps segment indices to whether they are grammatically feminine.
91
- * In Serbian, thousands (index 1) are feminine, others are masculine.
92
- * @type {Object.<number, boolean>}
93
- */
94
- scaleGenders = {
95
- 1: true // thousands are feminine (others default to false)
118
+ return buildLargeNumberWords(n, options)
119
+ }
120
+
121
+ function buildLargeNumberWords (n, options) {
122
+ const numStr = n.toString()
123
+ const len = numStr.length
124
+
125
+ const segments = []
126
+ const segmentSize = 3
127
+
128
+ const remainderLen = len % segmentSize
129
+ let pos = 0
130
+ if (remainderLen > 0) {
131
+ segments.push(Number(numStr.slice(0, remainderLen)))
132
+ pos = remainderLen
133
+ }
134
+ while (pos < len) {
135
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
136
+ pos += segmentSize
96
137
  }
97
138
 
98
- /** Selects Serbian plural form: 1 = singular, 2-4 = few, else = many. */
99
- pluralize (n, forms) {
100
- const lastDigit = n % 10n
101
- const lastTwoDigits = n % 100n
139
+ const parts = []
140
+ let scaleIndex = segments.length - 1
102
141
 
103
- if ((lastTwoDigits < 10n || lastTwoDigits > 20n) && lastDigit === 1n) {
104
- return forms[0]
105
- }
142
+ for (let i = 0; i < segments.length; i++) {
143
+ const segment = segments[i]
106
144
 
107
- if ((lastTwoDigits < 10n || lastTwoDigits > 20n) && lastDigit > 1n && lastDigit < 5n) {
108
- return forms[1]
145
+ if (segment !== 0) {
146
+ if (scaleIndex === 0) {
147
+ const segmentWords = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
148
+ parts.push(segmentWords[segment])
149
+ } else {
150
+ const scaleForms = SCALE_FORMS[scaleIndex - 1]
151
+ const scaleWord = pluralize(segment, scaleForms)
152
+ // Thousands (scaleIndex=1) are feminine, others masculine
153
+ const isFeminine = scaleIndex === 1
154
+ const segmentWords = isFeminine ? SEGMENTS_FEM : SEGMENTS_MASC
155
+ parts.push(segmentWords[segment] + ' ' + scaleWord)
156
+ }
109
157
  }
110
158
 
111
- return forms[2]
159
+ scaleIndex--
160
+ }
161
+
162
+ return parts.join(' ')
163
+ }
164
+
165
+ function decimalPartToWords (decimalPart, options) {
166
+ let result = ''
167
+ let i = 0
168
+
169
+ while (i < decimalPart.length && decimalPart[i] === '0') {
170
+ if (result) result += ' '
171
+ result += ZERO
172
+ i++
112
173
  }
174
+
175
+ const remainder = decimalPart.slice(i)
176
+ if (remainder) {
177
+ if (result) result += ' '
178
+ result += integerToWords(BigInt(remainder), options)
179
+ }
180
+
181
+ return result
113
182
  }
183
+
184
+ /**
185
+ * Converts a numeric value to Serbian (Cyrillic) words.
186
+ *
187
+ * @param {number | string | bigint} value - The numeric value to convert
188
+ * @param {Object} [options] - Optional configuration
189
+ * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
190
+ * @returns {string} The number in Serbian Cyrillic words
191
+ */
192
+ function toWords (value, options) {
193
+ options = validateOptions(options)
194
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
195
+
196
+ let result = ''
197
+
198
+ if (isNegative) {
199
+ result = NEGATIVE + ' '
200
+ }
201
+
202
+ result += integerToWords(integerPart, options)
203
+
204
+ if (decimalPart) {
205
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, options)
206
+ }
207
+
208
+ return result
209
+ }
210
+
211
+ // ============================================================================
212
+ // Exports
213
+ // ============================================================================
214
+
215
+ export { toWords }
@@ -1,80 +1,11 @@
1
1
  /**
2
- * Serbian Latin language converter.
2
+ * Converts a numeric value to Serbian (Latin) words.
3
3
  *
4
- * Supports:
5
- * - Three-form pluralization (one/few/many)
6
- * - Gender agreement (jedan/jedna, dva/dve)
7
- * - Latin script representation
4
+ * @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
7
+ * @returns {string} The number in Serbian Latin words
8
8
  */
9
- export class SerbianLatin extends SlavicLanguage {
10
- onesWords: {
11
- 1: string;
12
- 2: string;
13
- 3: string;
14
- 4: string;
15
- 5: string;
16
- 6: string;
17
- 7: string;
18
- 8: string;
19
- 9: string;
20
- };
21
- onesFeminineWords: {
22
- 1: string;
23
- 2: string;
24
- 3: string;
25
- 4: string;
26
- 5: string;
27
- 6: string;
28
- 7: string;
29
- 8: string;
30
- 9: string;
31
- };
32
- teensWords: {
33
- 0: string;
34
- 1: string;
35
- 2: string;
36
- 3: string;
37
- 4: string;
38
- 5: string;
39
- 6: string;
40
- 7: string;
41
- 8: string;
42
- 9: string;
43
- };
44
- twentiesWords: {
45
- 2: string;
46
- 3: string;
47
- 4: string;
48
- 5: string;
49
- 6: string;
50
- 7: string;
51
- 8: string;
52
- 9: string;
53
- };
54
- hundredsWords: {
55
- 1: string;
56
- 2: string;
57
- 3: string;
58
- 4: string;
59
- 5: string;
60
- 6: string;
61
- 7: string;
62
- 8: string;
63
- 9: string;
64
- };
65
- pluralForms: {
66
- 1: string[];
67
- 2: string[];
68
- 3: string[];
69
- 4: string[];
70
- 5: string[];
71
- 6: string[];
72
- 7: string[];
73
- 8: string[];
74
- 9: string[];
75
- 10: string[];
76
- };
77
- /** Selects Serbian plural form: 1 = singular, 2-4 = few, else = many. */
78
- pluralize(n: any, forms: any): any;
79
- }
80
- import { SlavicLanguage } from '../classes/slavic-language.js';
9
+ export function toWords(value: number | string | bigint, options?: {
10
+ gender?: "masculine" | "feminine" | undefined;
11
+ }): string;