n2words 1.24.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 (280) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +183 -156
  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 -2
  8. package/dist/languages/ar.js.map +1 -1
  9. package/dist/languages/az.js +3 -2
  10. package/dist/languages/az.js.map +1 -1
  11. package/dist/languages/bn.js +3 -2
  12. package/dist/languages/bn.js.map +1 -1
  13. package/dist/languages/cs.js +3 -2
  14. package/dist/languages/cs.js.map +1 -1
  15. package/dist/languages/da.js +3 -2
  16. package/dist/languages/da.js.map +1 -1
  17. package/dist/languages/de.js +3 -2
  18. package/dist/languages/de.js.map +1 -1
  19. package/dist/languages/el.js +3 -2
  20. package/dist/languages/el.js.map +1 -1
  21. package/dist/languages/en.js +3 -2
  22. package/dist/languages/en.js.map +1 -1
  23. package/dist/languages/es.js +3 -2
  24. package/dist/languages/es.js.map +1 -1
  25. package/dist/languages/fa.js +3 -2
  26. package/dist/languages/fa.js.map +1 -1
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -2
  30. package/dist/languages/fil.js.map +1 -1
  31. package/dist/languages/fr-BE.js +3 -2
  32. package/dist/languages/fr-BE.js.map +1 -1
  33. package/dist/languages/fr.js +3 -2
  34. package/dist/languages/fr.js.map +1 -1
  35. package/dist/languages/gu.js +3 -2
  36. package/dist/languages/gu.js.map +1 -1
  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 -2
  42. package/dist/languages/he.js.map +1 -1
  43. package/dist/languages/hi.js +3 -2
  44. package/dist/languages/hi.js.map +1 -1
  45. package/dist/languages/hr.js +3 -2
  46. package/dist/languages/hr.js.map +1 -1
  47. package/dist/languages/hu.js +3 -2
  48. package/dist/languages/hu.js.map +1 -1
  49. package/dist/languages/id.js +3 -2
  50. package/dist/languages/id.js.map +1 -1
  51. package/dist/languages/it.js +3 -2
  52. package/dist/languages/it.js.map +1 -1
  53. package/dist/languages/ja.js +3 -2
  54. package/dist/languages/ja.js.map +1 -1
  55. package/dist/languages/kn.js +3 -2
  56. package/dist/languages/kn.js.map +1 -1
  57. package/dist/languages/ko.js +3 -2
  58. package/dist/languages/ko.js.map +1 -1
  59. package/dist/languages/lt.js +3 -2
  60. package/dist/languages/lt.js.map +1 -1
  61. package/dist/languages/lv.js +3 -2
  62. package/dist/languages/lv.js.map +1 -1
  63. package/dist/languages/mr.js +3 -2
  64. package/dist/languages/mr.js.map +1 -1
  65. package/dist/languages/ms.js +3 -2
  66. package/dist/languages/ms.js.map +1 -1
  67. package/dist/languages/nb.js +3 -2
  68. package/dist/languages/nb.js.map +1 -1
  69. package/dist/languages/nl.js +3 -2
  70. package/dist/languages/nl.js.map +1 -1
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -2
  74. package/dist/languages/pl.js.map +1 -1
  75. package/dist/languages/pt.js +3 -2
  76. package/dist/languages/pt.js.map +1 -1
  77. package/dist/languages/ro.js +3 -2
  78. package/dist/languages/ro.js.map +1 -1
  79. package/dist/languages/ru.js +3 -2
  80. package/dist/languages/ru.js.map +1 -1
  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 -2
  84. package/dist/languages/sr-Latn.js.map +1 -1
  85. package/dist/languages/sv.js +3 -2
  86. package/dist/languages/sv.js.map +1 -1
  87. package/dist/languages/sw.js +3 -2
  88. package/dist/languages/sw.js.map +1 -1
  89. package/dist/languages/ta.js +3 -2
  90. package/dist/languages/ta.js.map +1 -1
  91. package/dist/languages/te.js +3 -2
  92. package/dist/languages/te.js.map +1 -1
  93. package/dist/languages/th.js +3 -2
  94. package/dist/languages/th.js.map +1 -1
  95. package/dist/languages/tr.js +3 -2
  96. package/dist/languages/tr.js.map +1 -1
  97. package/dist/languages/uk.js +3 -2
  98. package/dist/languages/uk.js.map +1 -1
  99. package/dist/languages/ur.js +3 -2
  100. package/dist/languages/ur.js.map +1 -1
  101. package/dist/languages/vi.js +3 -2
  102. package/dist/languages/vi.js.map +1 -1
  103. package/dist/languages/zh-Hans.js +3 -2
  104. package/dist/languages/zh-Hans.js.map +1 -1
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +3 -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 +17 -0
  114. package/lib/languages/ar.js +171 -209
  115. package/lib/languages/az.d.ts +7 -0
  116. package/lib/languages/az.js +167 -49
  117. package/lib/languages/bn.d.ts +7 -0
  118. package/lib/languages/bn.js +142 -123
  119. package/lib/languages/cs.d.ts +18 -0
  120. package/lib/languages/cs.js +303 -176
  121. package/lib/languages/da.d.ts +14 -0
  122. package/lib/languages/da.js +267 -139
  123. package/lib/languages/de.d.ts +17 -0
  124. package/lib/languages/de.js +310 -113
  125. package/lib/languages/el.d.ts +14 -0
  126. package/lib/languages/el.js +225 -98
  127. package/lib/languages/en.d.ts +17 -0
  128. package/lib/languages/en.js +235 -102
  129. package/lib/languages/es.d.ts +21 -0
  130. package/lib/languages/es.js +307 -125
  131. package/lib/languages/fa.d.ts +7 -0
  132. package/lib/languages/fa.js +115 -108
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +7 -0
  136. package/lib/languages/fil.js +199 -139
  137. package/lib/languages/fr-BE.d.ts +11 -0
  138. package/lib/languages/fr-BE.js +287 -48
  139. package/lib/languages/fr.d.ts +21 -0
  140. package/lib/languages/fr.js +343 -119
  141. package/lib/languages/gu.d.ts +7 -0
  142. package/lib/languages/gu.js +125 -144
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +13 -0
  146. package/lib/languages/hbo.js +300 -0
  147. package/lib/languages/he.d.ts +13 -0
  148. package/lib/languages/he.js +230 -283
  149. package/lib/languages/hi.d.ts +7 -0
  150. package/lib/languages/hi.js +142 -123
  151. package/lib/languages/hr.d.ts +11 -0
  152. package/lib/languages/hr.js +190 -129
  153. package/lib/languages/hu.d.ts +7 -0
  154. package/lib/languages/hu.js +194 -133
  155. package/lib/languages/id.d.ts +7 -0
  156. package/lib/languages/id.js +167 -140
  157. package/lib/languages/it.d.ts +19 -0
  158. package/lib/languages/it.js +337 -108
  159. package/lib/languages/ja.d.ts +17 -0
  160. package/lib/languages/ja.js +224 -155
  161. package/lib/languages/kn.d.ts +7 -0
  162. package/lib/languages/kn.js +128 -62
  163. package/lib/languages/ko.d.ts +14 -0
  164. package/lib/languages/ko.js +250 -70
  165. package/lib/languages/lt.d.ts +18 -0
  166. package/lib/languages/lt.js +287 -148
  167. package/lib/languages/lv.d.ts +18 -0
  168. package/lib/languages/lv.js +291 -123
  169. package/lib/languages/mr.d.ts +7 -0
  170. package/lib/languages/mr.js +125 -144
  171. package/lib/languages/ms.d.ts +7 -0
  172. package/lib/languages/ms.js +171 -112
  173. package/lib/languages/nb.d.ts +14 -0
  174. package/lib/languages/nb.js +275 -100
  175. package/lib/languages/nl.d.ts +26 -0
  176. package/lib/languages/nl.js +307 -174
  177. package/lib/languages/pa.d.ts +7 -0
  178. package/lib/languages/pa.js +163 -0
  179. package/lib/languages/pl.d.ts +22 -0
  180. package/lib/languages/pl.js +299 -158
  181. package/lib/languages/pt.d.ts +17 -0
  182. package/lib/languages/pt.js +279 -120
  183. package/lib/languages/ro.d.ts +18 -0
  184. package/lib/languages/ro.js +214 -337
  185. package/lib/languages/ru.d.ts +11 -0
  186. package/lib/languages/ru.js +219 -95
  187. package/lib/languages/sr-Cyrl.d.ts +11 -0
  188. package/lib/languages/sr-Cyrl.js +215 -0
  189. package/lib/languages/sr-Latn.d.ts +11 -0
  190. package/lib/languages/sr-Latn.js +190 -132
  191. package/lib/languages/sv.d.ts +14 -0
  192. package/lib/languages/sv.js +280 -103
  193. package/lib/languages/sw.d.ts +7 -0
  194. package/lib/languages/sw.js +135 -103
  195. package/lib/languages/ta.d.ts +7 -0
  196. package/lib/languages/ta.js +133 -205
  197. package/lib/languages/te.d.ts +7 -0
  198. package/lib/languages/te.js +148 -213
  199. package/lib/languages/th.d.ts +7 -0
  200. package/lib/languages/th.js +139 -101
  201. package/lib/languages/tr.d.ts +18 -0
  202. package/lib/languages/tr.js +246 -66
  203. package/lib/languages/uk.d.ts +11 -0
  204. package/lib/languages/uk.js +197 -101
  205. package/lib/languages/ur.d.ts +7 -0
  206. package/lib/languages/ur.js +160 -123
  207. package/lib/languages/vi.d.ts +17 -0
  208. package/lib/languages/vi.js +287 -164
  209. package/lib/languages/zh-Hans.d.ts +11 -0
  210. package/lib/languages/zh-Hans.js +159 -142
  211. package/lib/languages/zh-Hant.d.ts +11 -0
  212. package/lib/languages/zh-Hant.js +202 -0
  213. package/lib/n2words.d.ts +53 -0
  214. package/lib/n2words.js +91 -227
  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 +118 -67
  222. package/dist/languages/pa-Guru.js +0 -2
  223. package/dist/languages/pa-Guru.js.map +0 -1
  224. package/lib/classes/abstract-language.js +0 -261
  225. package/lib/classes/greedy-scale-language.js +0 -195
  226. package/lib/classes/slavic-language.js +0 -251
  227. package/lib/classes/south-asian-language.js +0 -161
  228. package/lib/classes/turkic-language.js +0 -63
  229. package/lib/languages/pa-Guru.js +0 -126
  230. package/typings/classes/abstract-language.d.ts +0 -144
  231. package/typings/classes/greedy-scale-language.d.ts +0 -148
  232. package/typings/classes/slavic-language.d.ts +0 -145
  233. package/typings/classes/south-asian-language.d.ts +0 -101
  234. package/typings/classes/turkic-language.d.ts +0 -42
  235. package/typings/languages/ar.d.ts +0 -93
  236. package/typings/languages/az.d.ts +0 -25
  237. package/typings/languages/bn.d.ts +0 -1
  238. package/typings/languages/cs.d.ts +0 -120
  239. package/typings/languages/da.d.ts +0 -53
  240. package/typings/languages/de.d.ts +0 -26
  241. package/typings/languages/el.d.ts +0 -11
  242. package/typings/languages/en.d.ts +0 -30
  243. package/typings/languages/es.d.ts +0 -43
  244. package/typings/languages/fa.d.ts +0 -81
  245. package/typings/languages/fil.d.ts +0 -12
  246. package/typings/languages/fr-BE.d.ts +0 -41
  247. package/typings/languages/fr.d.ts +0 -43
  248. package/typings/languages/gu.d.ts +0 -12
  249. package/typings/languages/he.d.ts +0 -197
  250. package/typings/languages/hi.d.ts +0 -1
  251. package/typings/languages/hr.d.ts +0 -110
  252. package/typings/languages/hu.d.ts +0 -37
  253. package/typings/languages/id.d.ts +0 -69
  254. package/typings/languages/it.d.ts +0 -51
  255. package/typings/languages/ja.d.ts +0 -58
  256. package/typings/languages/kn.d.ts +0 -11
  257. package/typings/languages/ko.d.ts +0 -25
  258. package/typings/languages/lt.d.ts +0 -110
  259. package/typings/languages/lv.d.ts +0 -99
  260. package/typings/languages/mr.d.ts +0 -12
  261. package/typings/languages/ms.d.ts +0 -37
  262. package/typings/languages/nb.d.ts +0 -27
  263. package/typings/languages/nl.d.ts +0 -65
  264. package/typings/languages/pa-Guru.d.ts +0 -1
  265. package/typings/languages/pl.d.ts +0 -116
  266. package/typings/languages/pt.d.ts +0 -39
  267. package/typings/languages/ro.d.ts +0 -229
  268. package/typings/languages/ru.d.ts +0 -108
  269. package/typings/languages/sr-Latn.d.ts +0 -98
  270. package/typings/languages/sv.d.ts +0 -30
  271. package/typings/languages/sw.d.ts +0 -1
  272. package/typings/languages/ta.d.ts +0 -1
  273. package/typings/languages/te.d.ts +0 -1
  274. package/typings/languages/th.d.ts +0 -1
  275. package/typings/languages/tr.d.ts +0 -46
  276. package/typings/languages/uk.d.ts +0 -117
  277. package/typings/languages/ur.d.ts +0 -1
  278. package/typings/languages/vi.d.ts +0 -116
  279. package/typings/languages/zh-Hans.d.ts +0 -57
  280. package/typings/n2words.d.ts +0 -177
@@ -1,153 +1,321 @@
1
- import SlavicLanguage from '../classes/slavic-language.js'
1
+ /**
2
+ * Latvian language converter - Functional Implementation
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
+ *
6
+ * Key features:
7
+ * - Two-form pluralization (singular for 1 except 11, plural otherwise)
8
+ * - Gender agreement (masculine/feminine for numbers < 1000)
9
+ * - Special hundreds forms (simts/simti/simtu)
10
+ * - Omit "one" before scale words
11
+ * - Long scale naming
12
+ */
13
+
14
+ import { parseNumericValue } from '../utils/parse-numeric.js'
15
+ import { validateOptions } from '../utils/validate-options.js'
16
+
17
+ // ============================================================================
18
+ // Vocabulary (module-level constants)
19
+ // ============================================================================
20
+
21
+ const ONES_MASC = ['', 'viens', 'divi', 'trīs', 'četri', 'pieci', 'seši', 'septiņi', 'astoņi', 'deviņi']
22
+ const ONES_FEM = ['', 'viena', 'divas', 'trīs', 'četras', 'piecas', 'sešas', 'septiņas', 'astoņas', 'deviņas']
23
+
24
+ const TEENS = ['desmit', 'vienpadsmit', 'divpadsmit', 'trīspadsmit', 'četrpadsmit', 'piecpadsmit', 'sešpadsmit', 'septiņpadsmit', 'astoņpadsmit', 'deviņpadsmit']
25
+ const TENS = ['', '', 'divdesmit', 'trīsdesmit', 'četrdesmit', 'piecdesmit', 'sešdesmit', 'septiņdesmit', 'astoņdesmit', 'deviņdesmit']
26
+
27
+ // Hundreds: simts (100, 110-199), simti (200-999), simtu (101-109)
28
+ const HUNDRED_SINGULAR = 'simts'
29
+ const HUNDRED_PLURAL = 'simti'
30
+ const HUNDRED_GENITIVE = 'simtu'
31
+
32
+ const ZERO = 'nulle'
33
+ const NEGATIVE = 'mīnus'
34
+ const DECIMAL_SEP = 'komats'
35
+
36
+ // Scale words: [singular, plural, genitive]
37
+ const SCALE_FORMS = [
38
+ ['tūkstotis', 'tūkstoši', 'tūkstošu'],
39
+ ['miljons', 'miljoni', 'miljonu'],
40
+ ['miljards', 'miljardi', 'miljardu'],
41
+ ['triljons', 'triljoni', 'triljonu'],
42
+ ['kvadriljons', 'kvadriljoni', 'kvadriljonu'],
43
+ ['kvintiljons', 'kvintiljoni', 'kvintiljonu'],
44
+ ['sikstiljons', 'sikstiljoni', 'sikstiljonu'],
45
+ ['septiljons', 'septiljoni', 'septiljonu'],
46
+ ['oktiljons', 'oktiljoni', 'oktiljonu']
47
+ ]
48
+
49
+ // ============================================================================
50
+ // Precomputed Lookup Tables (built once at module load)
51
+ // ============================================================================
2
52
 
3
53
  /**
4
- * @typedef {Object} SlavicOptions
5
- * @property {boolean} [feminine=false] Use feminine forms for numbers.
54
+ * Builds segment word for 0-999 (masculine form).
55
+ * Does NOT include special handling for segment=1 (omitOneBeforeScale).
56
+ * That's handled at join time.
6
57
  */
58
+ function buildSegment (n) {
59
+ if (n === 0) return ''
60
+
61
+ const ones = n % 10
62
+ const tens = Math.floor(n / 10) % 10
63
+ const hundreds = Math.floor(n / 100)
64
+
65
+ const parts = []
66
+
67
+ // Hundreds - Latvian has special forms
68
+ if (hundreds > 0) {
69
+ if (hundreds === 1 && tens === 0 && ones > 0) {
70
+ // 101-109: use genitive form "simtu"
71
+ parts.push(HUNDRED_GENITIVE)
72
+ } else if (hundreds > 1) {
73
+ // 200-999: use plural "simti"
74
+ parts.push(ONES_MASC[hundreds])
75
+ parts.push(HUNDRED_PLURAL)
76
+ } else {
77
+ // 100, 110-199: use singular "simts"
78
+ parts.push(HUNDRED_SINGULAR)
79
+ }
80
+ }
81
+
82
+ // Tens
83
+ if (tens > 1) {
84
+ parts.push(TENS[tens])
85
+ }
86
+
87
+ // Teens or ones
88
+ if (tens === 1) {
89
+ parts.push(TEENS[ones])
90
+ } else if (ones > 0) {
91
+ parts.push(ONES_MASC[ones])
92
+ }
93
+
94
+ return parts.join(' ')
95
+ }
7
96
 
8
97
  /**
9
- * Latvian language converter.
10
- *
11
- * Implements Latvian number words using the Slavic language pattern:
12
- * - Latvian number words (viens, divi, trīs, četri, pieci...)
13
- * - Latvian-specific pluralization patterns
14
- * - Baltic grammatical structure
15
- * - Simplified gender handling compared to Lithuanian
16
- *
17
- * Key Features:
18
- * - Three-form pluralization system shared across Slavic/Baltic languages
19
- * * Form 1 (singular): 1 (e.g., "tūkstotis")
20
- * * Form 2 (few): 2-4, 22-24, 32-34... excluding teens (e.g., "tūkstoši")
21
- * * Form 3 (many): all other numbers (e.g., "tūkstošu")
22
- * - Chunk-based decomposition (splits into groups of 3 digits: ones, thousands, millions, etc.)
23
- * - Large number handling via thousands[] array with indexed [singular, few, many] forms
24
- *
25
- * Features:
26
- * - Latvian diacritical marks (ī, ā, ē, ū, etc.)
27
- * - Three-form pluralization (adapted for Latvian)
28
- * - Baltic number naming conventions
29
- * - Compound number formation (divdesmit, trīsdesmit)
30
- *
31
- * Inherits from SlavicLanguage for pluralization algorithms.
98
+ * Builds segment word for 0-999 (feminine form - only differs in ones).
32
99
  */
33
- export class Latvian extends SlavicLanguage {
34
- negativeWord = 'mīnus'
35
- decimalSeparatorWord = 'komats'
36
- zeroWord = 'nulle'
37
- ones = {
38
- 1: 'viens',
39
- 2: 'divi',
40
- 3: 'trīs',
41
- 4: 'četri',
42
- 5: 'pieci',
43
- 6: 'seši',
44
- 7: 'septiņi',
45
- 8: 'astoņi',
46
- 9: 'deviņi'
100
+ function buildSegmentFeminine (n) {
101
+ if (n === 0) return ''
102
+
103
+ const ones = n % 10
104
+ const tens = Math.floor(n / 10) % 10
105
+ const hundreds = Math.floor(n / 100)
106
+
107
+ const parts = []
108
+
109
+ // Hundreds - always masculine
110
+ if (hundreds > 0) {
111
+ if (hundreds === 1 && tens === 0 && ones > 0) {
112
+ parts.push(HUNDRED_GENITIVE)
113
+ } else if (hundreds > 1) {
114
+ parts.push(ONES_MASC[hundreds])
115
+ parts.push(HUNDRED_PLURAL)
116
+ } else {
117
+ parts.push(HUNDRED_SINGULAR)
118
+ }
47
119
  }
48
120
 
49
- tens = {
50
- 0: 'desmit',
51
- 1: 'vienpadsmit',
52
- 2: 'divpadsmit',
53
- 3: 'trīspadsmit',
54
- 4: 'četrpadsmit',
55
- 5: 'piecpadsmit',
56
- 6: 'sešpadsmit',
57
- 7: 'septiņpadsmit',
58
- 8: 'astoņpadsmit',
59
- 9: 'deviņpadsmit'
121
+ // Tens
122
+ if (tens > 1) {
123
+ parts.push(TENS[tens])
60
124
  }
61
125
 
62
- twenties = {
63
- 2: 'divdesmit',
64
- 3: 'trīsdesmit',
65
- 4: 'četrdesmit',
66
- 5: 'piecdesmit',
67
- 6: 'sešdesmit',
68
- 7: 'septiņdesmit',
69
- 8: 'astoņdesmit',
70
- 9: 'deviņdesmit'
126
+ // Teens or ones - feminine for ones only
127
+ if (tens === 1) {
128
+ parts.push(TEENS[ones])
129
+ } else if (ones > 0) {
130
+ parts.push(ONES_FEM[ones])
71
131
  }
72
132
 
73
- hundreds = ['simts', 'simti', 'simtu']
74
-
75
- thousands = {
76
- 1: ['tūkstotis', 'tūkstoši', 'tūkstošu'],
77
- 2: ['miljons', 'miljoni', 'miljonu'],
78
- 3: ['miljards', 'miljardi', 'miljardu'],
79
- 4: ['triljons', 'triljoni', 'triljonu'],
80
- 5: ['kvadriljons', 'kvadriljoni', 'kvadriljonu'],
81
- 6: ['kvintiljons', 'kvintiljoni', 'kvintiljonu'],
82
- 7: ['sikstiljons', 'sikstiljoni', 'sikstiljonu'],
83
- 8: ['septiljons', 'septiljoni', 'septiljonu'],
84
- 9: ['oktiljons', 'oktiljoni', 'oktiljonu'],
85
- 10: ['nontiljons', 'nontiljoni', 'nontiljonu']
133
+ return parts.join(' ')
134
+ }
135
+
136
+ // Precompute all 1000 segment words (0-999)
137
+ const SEGMENTS_MASC = new Array(1000)
138
+ const SEGMENTS_FEM = new Array(1000)
139
+
140
+ for (let i = 0; i < 1000; i++) {
141
+ SEGMENTS_MASC[i] = buildSegment(i)
142
+ SEGMENTS_FEM[i] = buildSegmentFeminine(i)
143
+ }
144
+
145
+ // ============================================================================
146
+ // Pluralization
147
+ // ============================================================================
148
+
149
+ /**
150
+ * Latvian pluralization - simpler than Slavic.
151
+ * Singular: ends in 1 (except 11)
152
+ * Plural: everything else
153
+ *
154
+ * @param {number} n - The segment value
155
+ * @param {string[]} forms - [singular, plural, genitive]
156
+ * @returns {string} The appropriate form
157
+ */
158
+ function pluralize (n, forms) {
159
+ if (n === 0) return forms[2]
160
+
161
+ const lastDigit = n % 10
162
+ const lastTwoDigits = n % 100
163
+
164
+ if (lastDigit === 1 && lastTwoDigits !== 11) {
165
+ return forms[0]
86
166
  }
87
167
 
88
- pluralize (n, forms) {
89
- if (n === 0n) {
90
- return forms[2]
91
- }
168
+ return forms[1]
169
+ }
92
170
 
93
- const lastDigit = n % 10n
94
- const lastTwoDigits = n % 100n
171
+ // ============================================================================
172
+ // Conversion Functions
173
+ // ============================================================================
95
174
 
96
- if (lastDigit === 1n && lastTwoDigits !== 11n) {
97
- return forms[0]
98
- }
175
+ /**
176
+ * Converts a non-negative integer to Latvian words.
177
+ *
178
+ * @param {bigint} n - Non-negative integer to convert
179
+ * @param {Object} options - Conversion options
180
+ * @returns {string} Latvian words
181
+ */
182
+ function integerToWords (n, options = {}) {
183
+ if (n === 0n) return ZERO
99
184
 
100
- return forms[1]
185
+ // Fast path: numbers < 1000 (direct lookup)
186
+ if (n < 1000n) {
187
+ const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
188
+ return segments[Number(n)]
101
189
  }
102
190
 
103
- convertWholePart (number) {
104
- if (number === 0n) {
105
- return this.zeroWord
106
- }
107
- const words = []
108
- const chunks = this.splitByX(number.toString(), 3)
109
- let index = chunks.length
110
- for (const x of chunks) {
111
- index = index - 1
112
- if (x === 0n) {
113
- continue
114
- }
115
- const [n1, n2, n3] = this.getDigits(x)
116
- if (n3 > 0n) {
117
- if (n3 === 1n && n2 === 0n && n1 > 0n) {
118
- words.push(this.hundreds[2])
119
- } else if (n3 > 1n) {
120
- words.push(this.ones[n3], this.hundreds[1])
191
+ // For numbers >= 1000, feminine only applies to final segment if < 1000
192
+ // But we use masculine for all segments when n >= 1000
193
+ return buildLargeNumberWords(n, options)
194
+ }
195
+
196
+ /**
197
+ * Builds words for numbers >= 1000.
198
+ *
199
+ * @param {bigint} n - Number >= 1000
200
+ * @param {Object} options - Conversion options
201
+ * @returns {string} Latvian words
202
+ */
203
+ function buildLargeNumberWords (n, options) {
204
+ const numStr = n.toString()
205
+ const len = numStr.length
206
+
207
+ // Build segments of 3 digits from right to left
208
+ const segments = []
209
+ const segmentSize = 3
210
+
211
+ const remainderLen = len % segmentSize
212
+ let pos = 0
213
+ if (remainderLen > 0) {
214
+ segments.push(Number(numStr.slice(0, remainderLen)))
215
+ pos = remainderLen
216
+ }
217
+ while (pos < len) {
218
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
219
+ pos += segmentSize
220
+ }
221
+
222
+ // Convert segments to words
223
+ const parts = []
224
+ let scaleIndex = segments.length - 1
225
+
226
+ for (let i = 0; i < segments.length; i++) {
227
+ const segment = segments[i]
228
+
229
+ if (segment !== 0) {
230
+ const segmentWord = SEGMENTS_MASC[segment]
231
+
232
+ if (scaleIndex === 0) {
233
+ // Units segment - use masculine (feminine doesn't apply when n >= 1000)
234
+ parts.push(segmentWord)
235
+ } else {
236
+ // Segment with scale word
237
+ const scaleForms = SCALE_FORMS[scaleIndex - 1]
238
+ const scaleWord = pluralize(segment, scaleForms)
239
+
240
+ // Latvian omits "one" before scale words
241
+ if (segment === 1) {
242
+ parts.push(scaleWord)
121
243
  } else {
122
- words.push(this.hundreds[0])
244
+ parts.push(segmentWord + ' ' + scaleWord)
123
245
  }
124
246
  }
125
- if (n2 > 1n) {
126
- words.push(this.twenties[n2])
127
- }
128
- if (n2 === 1n) {
129
- words.push(this.tens[n1])
130
- } else if (n1 > 0n && !(index > 0 && x === 1n)) {
131
- words.push(this.ones[n1])
132
- }
133
- if (index > 0) {
134
- words.push(this.pluralize(x, this.thousands[index]))
135
- }
136
247
  }
137
- return words.join(' ')
248
+
249
+ scaleIndex--
250
+ }
251
+
252
+ return parts.join(' ')
253
+ }
254
+
255
+ /**
256
+ * Converts decimal digits to Latvian words.
257
+ *
258
+ * @param {string} decimalPart - Decimal digits (without the point)
259
+ * @param {Object} options - Conversion options
260
+ * @returns {string} Latvian words for decimal part
261
+ */
262
+ function decimalPartToWords (decimalPart, options) {
263
+ let result = ''
264
+
265
+ // Handle leading zeros
266
+ let i = 0
267
+ while (i < decimalPart.length && decimalPart[i] === '0') {
268
+ if (result) result += ' '
269
+ result += ZERO
270
+ i++
271
+ }
272
+
273
+ // Convert remainder as a single number
274
+ const remainder = decimalPart.slice(i)
275
+ if (remainder) {
276
+ if (result) result += ' '
277
+ result += integerToWords(BigInt(remainder), options)
138
278
  }
279
+
280
+ return result
139
281
  }
140
282
 
141
283
  /**
142
- * Converts a number to Latvian cardinal (written) form.
284
+ * Converts a numeric value to Latvian words.
285
+ *
286
+ * @param {number | string | bigint} value - The numeric value to convert
287
+ * @param {Object} [options] - Conversion options
288
+ * @param {string} [options.gender='masculine'] - Gender for numbers < 1000
289
+ * @returns {string} The number in Latvian words
290
+ * @throws {TypeError} If value is not a valid numeric type
291
+ * @throws {Error} If value is not a valid number format
143
292
  *
144
- * @param {number|string|bigint} value The number to convert.
145
- * @param {Object} [options={}] Configuration options.
146
- * @param {boolean} [options.feminine=false] Use feminine forms for numbers.
147
- * @returns {string} The number expressed in Latvian words.
148
- * @throws {TypeError} If value is NaN or invalid type.
149
- * @throws {Error} If value is an invalid number string.
293
+ * @example
294
+ * toWords(42) // 'četrdesmit divi'
295
+ * toWords(1, { gender: 'feminine' }) // 'viena'
296
+ * toWords(1000) // 'tūkstotis'
150
297
  */
151
- export default function convertToWords (value, options = {}) {
152
- return new Latvian(options).convertToWords(value)
298
+ function toWords (value, options) {
299
+ options = validateOptions(options)
300
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
301
+
302
+ let result = ''
303
+
304
+ if (isNegative) {
305
+ result = NEGATIVE + ' '
306
+ }
307
+
308
+ result += integerToWords(integerPart, options)
309
+
310
+ if (decimalPart) {
311
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, options)
312
+ }
313
+
314
+ return result
153
315
  }
316
+
317
+ // ============================================================================
318
+ // Public API
319
+ // ============================================================================
320
+
321
+ export { toWords }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a numeric value to Marathi words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Marathi words
6
+ */
7
+ export function toWords(value: number | string | bigint): string;