n2words 1.23.1 → 2.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 (322) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +317 -59
  3. package/dist/ArabicConverter.js +3 -0
  4. package/dist/ArabicConverter.js.map +1 -0
  5. package/dist/AzerbaijaniConverter.js +3 -0
  6. package/dist/AzerbaijaniConverter.js.map +1 -0
  7. package/dist/BanglaConverter.js +3 -0
  8. package/dist/BanglaConverter.js.map +1 -0
  9. package/dist/BiblicalHebrewConverter.js +3 -0
  10. package/dist/BiblicalHebrewConverter.js.map +1 -0
  11. package/dist/CroatianConverter.js +3 -0
  12. package/dist/CroatianConverter.js.map +1 -0
  13. package/dist/CzechConverter.js +3 -0
  14. package/dist/CzechConverter.js.map +1 -0
  15. package/dist/DanishConverter.js +3 -0
  16. package/dist/DanishConverter.js.map +1 -0
  17. package/dist/DutchConverter.js +3 -0
  18. package/dist/DutchConverter.js.map +1 -0
  19. package/dist/EnglishConverter.js +3 -0
  20. package/dist/EnglishConverter.js.map +1 -0
  21. package/dist/FilipinoConverter.js +3 -0
  22. package/dist/FilipinoConverter.js.map +1 -0
  23. package/dist/FrenchBelgiumConverter.js +3 -0
  24. package/dist/FrenchBelgiumConverter.js.map +1 -0
  25. package/dist/FrenchConverter.js +3 -0
  26. package/dist/FrenchConverter.js.map +1 -0
  27. package/dist/GermanConverter.js +3 -0
  28. package/dist/GermanConverter.js.map +1 -0
  29. package/dist/GreekConverter.js +3 -0
  30. package/dist/GreekConverter.js.map +1 -0
  31. package/dist/GujaratiConverter.js +3 -0
  32. package/dist/GujaratiConverter.js.map +1 -0
  33. package/dist/HebrewConverter.js +3 -0
  34. package/dist/HebrewConverter.js.map +1 -0
  35. package/dist/HindiConverter.js +3 -0
  36. package/dist/HindiConverter.js.map +1 -0
  37. package/dist/HungarianConverter.js +3 -0
  38. package/dist/HungarianConverter.js.map +1 -0
  39. package/dist/IndonesianConverter.js +3 -0
  40. package/dist/IndonesianConverter.js.map +1 -0
  41. package/dist/ItalianConverter.js +3 -0
  42. package/dist/ItalianConverter.js.map +1 -0
  43. package/dist/JapaneseConverter.js +3 -0
  44. package/dist/JapaneseConverter.js.map +1 -0
  45. package/dist/KannadaConverter.js +3 -0
  46. package/dist/KannadaConverter.js.map +1 -0
  47. package/dist/KoreanConverter.js +3 -0
  48. package/dist/KoreanConverter.js.map +1 -0
  49. package/dist/LatvianConverter.js +3 -0
  50. package/dist/LatvianConverter.js.map +1 -0
  51. package/dist/LithuanianConverter.js +3 -0
  52. package/dist/LithuanianConverter.js.map +1 -0
  53. package/dist/MalayConverter.js +3 -0
  54. package/dist/MalayConverter.js.map +1 -0
  55. package/dist/MarathiConverter.js +3 -0
  56. package/dist/MarathiConverter.js.map +1 -0
  57. package/dist/NorwegianBokmalConverter.js +3 -0
  58. package/dist/NorwegianBokmalConverter.js.map +1 -0
  59. package/dist/PersianConverter.js +3 -0
  60. package/dist/PersianConverter.js.map +1 -0
  61. package/dist/PolishConverter.js +3 -0
  62. package/dist/PolishConverter.js.map +1 -0
  63. package/dist/PortugueseConverter.js +3 -0
  64. package/dist/PortugueseConverter.js.map +1 -0
  65. package/dist/PunjabiConverter.js +3 -0
  66. package/dist/PunjabiConverter.js.map +1 -0
  67. package/dist/RomanianConverter.js +3 -0
  68. package/dist/RomanianConverter.js.map +1 -0
  69. package/dist/RussianConverter.js +3 -0
  70. package/dist/RussianConverter.js.map +1 -0
  71. package/dist/SerbianCyrillicConverter.js +3 -0
  72. package/dist/SerbianCyrillicConverter.js.map +1 -0
  73. package/dist/SerbianLatinConverter.js +3 -0
  74. package/dist/SerbianLatinConverter.js.map +1 -0
  75. package/dist/SimplifiedChineseConverter.js +3 -0
  76. package/dist/SimplifiedChineseConverter.js.map +1 -0
  77. package/dist/SpanishConverter.js +3 -0
  78. package/dist/SpanishConverter.js.map +1 -0
  79. package/dist/SwahiliConverter.js +3 -0
  80. package/dist/SwahiliConverter.js.map +1 -0
  81. package/dist/SwedishConverter.js +3 -0
  82. package/dist/SwedishConverter.js.map +1 -0
  83. package/dist/TamilConverter.js +3 -0
  84. package/dist/TamilConverter.js.map +1 -0
  85. package/dist/TeluguConverter.js +3 -0
  86. package/dist/TeluguConverter.js.map +1 -0
  87. package/dist/ThaiConverter.js +3 -0
  88. package/dist/ThaiConverter.js.map +1 -0
  89. package/dist/TraditionalChineseConverter.js +3 -0
  90. package/dist/TraditionalChineseConverter.js.map +1 -0
  91. package/dist/TurkishConverter.js +3 -0
  92. package/dist/TurkishConverter.js.map +1 -0
  93. package/dist/UkrainianConverter.js +3 -0
  94. package/dist/UkrainianConverter.js.map +1 -0
  95. package/dist/UrduConverter.js +3 -0
  96. package/dist/UrduConverter.js.map +1 -0
  97. package/dist/VietnameseConverter.js +3 -0
  98. package/dist/VietnameseConverter.js.map +1 -0
  99. package/dist/n2words.js +3 -2
  100. package/dist/n2words.js.map +1 -1
  101. package/lib/classes/abstract-language.d.ts +158 -34
  102. package/lib/classes/abstract-language.js +223 -115
  103. package/lib/classes/greedy-scale-language.d.ts +109 -0
  104. package/lib/classes/greedy-scale-language.js +201 -0
  105. package/lib/classes/slavic-language.d.ts +148 -0
  106. package/lib/classes/slavic-language.js +281 -0
  107. package/lib/classes/south-asian-language.d.ts +70 -0
  108. package/lib/classes/south-asian-language.js +154 -0
  109. package/lib/classes/turkic-language.d.ts +26 -0
  110. package/lib/classes/turkic-language.js +59 -0
  111. package/lib/languages/ar.d.ts +30 -0
  112. package/lib/languages/ar.js +159 -0
  113. package/lib/languages/az.d.ts +12 -0
  114. package/lib/languages/az.js +42 -0
  115. package/lib/languages/bn.d.ts +11 -0
  116. package/lib/languages/bn.js +131 -0
  117. package/lib/languages/cs.d.ts +88 -0
  118. package/lib/languages/cs.js +143 -0
  119. package/lib/languages/da.d.ts +15 -0
  120. package/lib/languages/da.js +120 -0
  121. package/lib/languages/de.d.ts +14 -0
  122. package/lib/languages/de.js +101 -0
  123. package/lib/languages/el.d.ts +14 -0
  124. package/lib/languages/el.js +90 -0
  125. package/lib/languages/en.d.ts +16 -0
  126. package/lib/languages/en.js +86 -0
  127. package/lib/languages/es.d.ts +15 -0
  128. package/lib/languages/es.js +121 -0
  129. package/lib/languages/fa.d.ts +47 -0
  130. package/lib/languages/fa.js +144 -0
  131. package/lib/languages/fil.d.ts +16 -0
  132. package/lib/languages/fil.js +121 -0
  133. package/lib/languages/fr-BE.d.ts +11 -0
  134. package/lib/languages/fr-BE.js +25 -0
  135. package/lib/languages/fr.d.ts +15 -0
  136. package/lib/languages/fr.js +106 -0
  137. package/lib/languages/gu.d.ts +11 -0
  138. package/lib/languages/gu.js +132 -0
  139. package/lib/languages/hbo.d.ts +113 -0
  140. package/lib/languages/hbo.js +251 -0
  141. package/lib/languages/he.d.ts +80 -0
  142. package/lib/languages/he.js +206 -0
  143. package/lib/languages/hi.d.ts +11 -0
  144. package/lib/languages/hi.js +131 -0
  145. package/lib/languages/hr.d.ts +80 -0
  146. package/lib/languages/hr.js +113 -0
  147. package/lib/languages/hu.d.ts +22 -0
  148. package/lib/languages/hu.js +137 -0
  149. package/lib/languages/id.d.ts +37 -0
  150. package/lib/languages/id.js +159 -0
  151. package/lib/languages/it.d.ts +37 -0
  152. package/lib/languages/it.js +132 -0
  153. package/lib/languages/ja.d.ts +17 -0
  154. package/lib/languages/ja.js +137 -0
  155. package/lib/languages/kn.d.ts +11 -0
  156. package/lib/languages/kn.js +42 -0
  157. package/lib/languages/ko.d.ts +14 -0
  158. package/lib/languages/ko.js +55 -0
  159. package/lib/{i18n/pl.d.ts → languages/lt.d.ts} +18 -15
  160. package/lib/languages/lt.js +136 -0
  161. package/lib/{i18n/lt.d.ts → languages/lv.d.ts} +16 -14
  162. package/lib/languages/lv.js +130 -0
  163. package/lib/languages/mr.d.ts +11 -0
  164. package/lib/languages/mr.js +132 -0
  165. package/lib/languages/ms.d.ts +31 -0
  166. package/lib/languages/ms.js +150 -0
  167. package/lib/languages/nb.d.ts +12 -0
  168. package/lib/languages/nb.js +100 -0
  169. package/lib/languages/nl.d.ts +16 -0
  170. package/lib/languages/nl.js +155 -0
  171. package/lib/languages/pa.d.ts +11 -0
  172. package/lib/languages/pa.js +131 -0
  173. package/lib/{i18n/uk.d.ts → languages/pl.d.ts} +16 -14
  174. package/lib/languages/pl.js +110 -0
  175. package/lib/languages/pt.d.ts +29 -0
  176. package/lib/languages/pt.js +112 -0
  177. package/lib/languages/ro.d.ts +158 -0
  178. package/lib/languages/ro.js +273 -0
  179. package/lib/languages/ru.d.ts +85 -0
  180. package/lib/languages/ru.js +96 -0
  181. package/lib/languages/sr-Cyrl.d.ts +80 -0
  182. package/lib/languages/sr-Cyrl.js +113 -0
  183. package/lib/{i18n/cz.d.ts → languages/sr-Latn.d.ts} +26 -14
  184. package/lib/languages/sr-Latn.js +113 -0
  185. package/lib/languages/sv.d.ts +14 -0
  186. package/lib/languages/sv.js +90 -0
  187. package/lib/languages/sw.d.ts +39 -0
  188. package/lib/languages/sw.js +126 -0
  189. package/lib/languages/ta.d.ts +20 -0
  190. package/lib/languages/ta.js +226 -0
  191. package/lib/languages/te.d.ts +22 -0
  192. package/lib/languages/te.js +219 -0
  193. package/lib/languages/th.d.ts +17 -0
  194. package/lib/languages/th.js +117 -0
  195. package/lib/languages/tr.d.ts +12 -0
  196. package/lib/languages/tr.js +56 -0
  197. package/lib/languages/uk.d.ts +85 -0
  198. package/lib/{i18n → languages}/uk.js +33 -32
  199. package/lib/languages/ur.d.ts +11 -0
  200. package/lib/languages/ur.js +131 -0
  201. package/lib/{i18n → languages}/vi.d.ts +15 -13
  202. package/lib/languages/vi.js +147 -0
  203. package/lib/languages/zh-Hans.d.ts +21 -0
  204. package/lib/languages/zh-Hans.js +111 -0
  205. package/lib/languages/zh-Hant.d.ts +21 -0
  206. package/lib/languages/zh-Hant.js +111 -0
  207. package/lib/n2words.d.ts +207 -7
  208. package/lib/n2words.js +535 -81
  209. package/package.json +126 -79
  210. package/dist/ar.js +0 -2
  211. package/dist/ar.js.map +0 -1
  212. package/dist/az.js +0 -2
  213. package/dist/az.js.map +0 -1
  214. package/dist/cz.js +0 -2
  215. package/dist/cz.js.map +0 -1
  216. package/dist/de.js +0 -2
  217. package/dist/de.js.map +0 -1
  218. package/dist/dk.js +0 -2
  219. package/dist/dk.js.map +0 -1
  220. package/dist/en.js +0 -2
  221. package/dist/en.js.map +0 -1
  222. package/dist/es.js +0 -2
  223. package/dist/es.js.map +0 -1
  224. package/dist/fa.js +0 -2
  225. package/dist/fa.js.map +0 -1
  226. package/dist/fr-BE.js +0 -2
  227. package/dist/fr-BE.js.map +0 -1
  228. package/dist/fr.js +0 -2
  229. package/dist/fr.js.map +0 -1
  230. package/dist/he.js +0 -2
  231. package/dist/he.js.map +0 -1
  232. package/dist/hr.js +0 -2
  233. package/dist/hr.js.map +0 -1
  234. package/dist/hu.js +0 -2
  235. package/dist/hu.js.map +0 -1
  236. package/dist/id.js +0 -2
  237. package/dist/id.js.map +0 -1
  238. package/dist/it.js +0 -2
  239. package/dist/it.js.map +0 -1
  240. package/dist/ko.js +0 -2
  241. package/dist/ko.js.map +0 -1
  242. package/dist/lt.js +0 -2
  243. package/dist/lt.js.map +0 -1
  244. package/dist/lv.js +0 -2
  245. package/dist/lv.js.map +0 -1
  246. package/dist/n2words.d.ts +0 -2
  247. package/dist/nl.js +0 -2
  248. package/dist/nl.js.map +0 -1
  249. package/dist/no.js +0 -2
  250. package/dist/no.js.map +0 -1
  251. package/dist/pl.js +0 -2
  252. package/dist/pl.js.map +0 -1
  253. package/dist/pt.js +0 -2
  254. package/dist/pt.js.map +0 -1
  255. package/dist/ro.js +0 -2
  256. package/dist/ro.js.map +0 -1
  257. package/dist/ru.js +0 -2
  258. package/dist/ru.js.map +0 -1
  259. package/dist/sr.js +0 -2
  260. package/dist/sr.js.map +0 -1
  261. package/dist/tr.js +0 -2
  262. package/dist/tr.js.map +0 -1
  263. package/dist/uk.js +0 -2
  264. package/dist/uk.js.map +0 -1
  265. package/dist/vi.js +0 -2
  266. package/dist/vi.js.map +0 -1
  267. package/dist/zh.js +0 -2
  268. package/dist/zh.js.map +0 -1
  269. package/lib/classes/base-language.d.ts +0 -58
  270. package/lib/classes/base-language.js +0 -172
  271. package/lib/i18n/ar.d.ts +0 -41
  272. package/lib/i18n/ar.js +0 -209
  273. package/lib/i18n/az.d.ts +0 -15
  274. package/lib/i18n/az.js +0 -66
  275. package/lib/i18n/cz.js +0 -135
  276. package/lib/i18n/de.d.ts +0 -17
  277. package/lib/i18n/de.js +0 -103
  278. package/lib/i18n/dk.d.ts +0 -14
  279. package/lib/i18n/dk.js +0 -110
  280. package/lib/i18n/en.d.ts +0 -22
  281. package/lib/i18n/en.js +0 -86
  282. package/lib/i18n/es.d.ts +0 -16
  283. package/lib/i18n/es.js +0 -110
  284. package/lib/i18n/fa.d.ts +0 -54
  285. package/lib/i18n/fa.js +0 -106
  286. package/lib/i18n/fr-BE.d.ts +0 -11
  287. package/lib/i18n/fr-BE.js +0 -20
  288. package/lib/i18n/fr.d.ts +0 -15
  289. package/lib/i18n/fr.js +0 -99
  290. package/lib/i18n/he.d.ts +0 -61
  291. package/lib/i18n/he.js +0 -132
  292. package/lib/i18n/hr.d.ts +0 -68
  293. package/lib/i18n/hr.js +0 -129
  294. package/lib/i18n/hu.d.ts +0 -17
  295. package/lib/i18n/hu.js +0 -135
  296. package/lib/i18n/id.d.ts +0 -43
  297. package/lib/i18n/id.js +0 -156
  298. package/lib/i18n/it.d.ts +0 -29
  299. package/lib/i18n/it.js +0 -137
  300. package/lib/i18n/ko.d.ts +0 -15
  301. package/lib/i18n/ko.js +0 -56
  302. package/lib/i18n/lt.js +0 -138
  303. package/lib/i18n/lv.d.ts +0 -57
  304. package/lib/i18n/lv.js +0 -120
  305. package/lib/i18n/nl.d.ts +0 -20
  306. package/lib/i18n/nl.js +0 -125
  307. package/lib/i18n/no.d.ts +0 -15
  308. package/lib/i18n/no.js +0 -77
  309. package/lib/i18n/pl.js +0 -126
  310. package/lib/i18n/pt.d.ts +0 -26
  311. package/lib/i18n/pt.js +0 -118
  312. package/lib/i18n/ro.d.ts +0 -109
  313. package/lib/i18n/ro.js +0 -360
  314. package/lib/i18n/ru.d.ts +0 -30
  315. package/lib/i18n/ru.js +0 -198
  316. package/lib/i18n/sr.d.ts +0 -56
  317. package/lib/i18n/sr.js +0 -127
  318. package/lib/i18n/tr.d.ts +0 -15
  319. package/lib/i18n/tr.js +0 -64
  320. package/lib/i18n/vi.js +0 -151
  321. package/lib/i18n/zh.d.ts +0 -18
  322. package/lib/i18n/zh.js +0 -78
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Greedy scale language converter implementing the "highest-matching scale word" algorithm.
3
+ *
4
+ * Responsibilities:
5
+ * - Decompose an integer into a sequence of word-sets using greedy matching.
6
+ * - Provide helpers to reduce and post-process matched word-sets.
7
+ * - Inherits decimal handling from AbstractLanguage (supports grouped and per-digit
8
+ * modes via the `usePerDigitDecimals` class property).
9
+ *
10
+ * Subclass requirements:
11
+ * - Define `scaleWords` (ordered descending) as `[bigint, string]` tuples.
12
+ * - Implement 'combineWordSets(preceding, following)' to combine adjacent word-sets
13
+ * per language grammar.
14
+ *
15
+ * Scale words specification:
16
+ * - `scaleWords` is an Array of 2-tuples: `[bigint, string]` where the first element
17
+ * is the numeric scale value and the second is the word for that value.
18
+ * - Scale words MUST be ordered from largest to smallest (descending) for the algorithm
19
+ * to function correctly.
20
+ *
21
+ * Terminology:
22
+ * - **Scale**: A magnitude value (100n, 1000n, 1000000n)
23
+ * - **Word-set**: An object `{ word: bigint }` representing a partial result
24
+ * - **Scale word**: The word for a scale value ("hundred", "thousand")
25
+ *
26
+ * @abstract
27
+ * @extends AbstractLanguage
28
+ */
29
+ export class GreedyScaleLanguage extends AbstractLanguage {
30
+ /**
31
+ * Array of scale words mapping numeric values to their word representations.
32
+ *
33
+ * Each element is a 2-tuple: `[bigint, string]` where the first element is the
34
+ * numeric scale value and the second is the word for that value. The array MUST be
35
+ * ordered from largest to smallest (descending) for the greedy algorithm to work correctly.
36
+ *
37
+ * @type {Array<[bigint, string]>}
38
+ * @example
39
+ * // English scale words (descending order):
40
+ * // [[1000000000n, 'billion'], [1000000n, 'million'], [1000n, 'thousand'], [100n, 'hundred'], ..., [1n, 'one']]
41
+ */
42
+ scaleWords: Array<[bigint, string]>;
43
+ /**
44
+ * Returns the word for an exact scale value.
45
+ *
46
+ * @param {bigint} scale The scale value to look up (prefer BigInt for exact matching).
47
+ * @returns {string|undefined} The word for the provided scale, or `undefined`.
48
+ */
49
+ wordForScale(scale: bigint): string | undefined;
50
+ /**
51
+ * Decomposes an integer into a sequence of word-sets.
52
+ *
53
+ * This internal helper returns a nested structure that represents quantities and
54
+ * their matching scale words (e.g. `[{ 'one': 1n }, { 'hundred': 100n }, ...]`).
55
+ * The result is designed to be reduced by `reduceWordSets()` using language-specific `combineWordSets()`.
56
+ *
57
+ * For quantities > 1, the multiplier is recursively decomposed. For quantity = 1,
58
+ * the implicit "one" is represented with `{ 'one': 1n }` and typically omitted during combineWordSets().
59
+ *
60
+ * @protected
61
+ * @param {bigint} integerPart The integer to decompose.
62
+ * @returns {Array<Object|Array>} An array of word-set objects and possibly nested arrays.
63
+ */
64
+ protected decomposeInteger(integerPart: bigint): Array<Object | any[]>;
65
+ /**
66
+ * Reduces a nested array of word-sets into a single word-set object.
67
+ *
68
+ * This method repeatedly applies the subclass `combineWordSets()` operation until a single
69
+ * object remains. It normalizes nested arrays by recursively reducing them.
70
+ *
71
+ * @protected
72
+ * @param {Array<Object|Array>} wordSets Array of word-set objects and nested arrays.
73
+ * @returns {Object} Reduced word-set where the single object key is the language string
74
+ * and its value is the numeric BigInt result for that string.
75
+ */
76
+ protected reduceWordSets(wordSets: Array<Object | any[]>): Object;
77
+ /**
78
+ * Combines two adjacent word-sets into a single word-set.
79
+ *
80
+ * This is the core language-specific method that must be implemented by subclasses
81
+ * to define how adjacent word-sets are combined according to the language's grammar.
82
+ * For example, English combines "twenty" + "three" → "twenty-three", while
83
+ * French might combine "quatre-vingts" + "dix" → "quatre-vingt-dix".
84
+ *
85
+ * @abstract
86
+ * @protected
87
+ * @param {Object} preceding Preceding word-set as `{ word: bigint }`.
88
+ * @param {Object} following Following word-set as `{ word: bigint }`.
89
+ * @returns {Object} Combined word-set with merged text and resulting numeric value.
90
+ *
91
+ * @example
92
+ * // English implementation might handle:
93
+ * // combineWordSets({ 'twenty': 20n }, { 'three': 3n }) → { 'twenty-three': 23n }
94
+ * // combineWordSets({ 'one': 1n }, { 'hundred': 100n }) → { 'one hundred': 100n }
95
+ */
96
+ protected combineWordSets(preceding: Object, following: Object): Object;
97
+ /**
98
+ * Final string post-processing hook.
99
+ *
100
+ * Subclasses may override to apply language-specific whitespace, punctuation or
101
+ * orthographic corrections.
102
+ *
103
+ * @protected
104
+ * @param {string} output Language string produced by the conversion flow.
105
+ * @returns {string} Final formatted string.
106
+ */
107
+ protected finalizeWords(output: string): string;
108
+ }
109
+ import { AbstractLanguage } from './abstract-language.js';
@@ -0,0 +1,201 @@
1
+ import { AbstractLanguage } from './abstract-language.js'
2
+
3
+ /**
4
+ * Greedy scale language converter implementing the "highest-matching scale word" algorithm.
5
+ *
6
+ * Responsibilities:
7
+ * - Decompose an integer into a sequence of word-sets using greedy matching.
8
+ * - Provide helpers to reduce and post-process matched word-sets.
9
+ * - Inherits decimal handling from AbstractLanguage (supports grouped and per-digit
10
+ * modes via the `usePerDigitDecimals` class property).
11
+ *
12
+ * Subclass requirements:
13
+ * - Define `scaleWords` (ordered descending) as `[bigint, string]` tuples.
14
+ * - Implement 'combineWordSets(preceding, following)' to combine adjacent word-sets
15
+ * per language grammar.
16
+ *
17
+ * Scale words specification:
18
+ * - `scaleWords` is an Array of 2-tuples: `[bigint, string]` where the first element
19
+ * is the numeric scale value and the second is the word for that value.
20
+ * - Scale words MUST be ordered from largest to smallest (descending) for the algorithm
21
+ * to function correctly.
22
+ *
23
+ * Terminology:
24
+ * - **Scale**: A magnitude value (100n, 1000n, 1000000n)
25
+ * - **Word-set**: An object `{ word: bigint }` representing a partial result
26
+ * - **Scale word**: The word for a scale value ("hundred", "thousand")
27
+ *
28
+ * @abstract
29
+ * @extends AbstractLanguage
30
+ */
31
+
32
+ export class GreedyScaleLanguage extends AbstractLanguage {
33
+ // ============================================================================
34
+ // Required Properties (subclasses must define)
35
+ // ============================================================================
36
+
37
+ /**
38
+ * Array of scale words mapping numeric values to their word representations.
39
+ *
40
+ * Each element is a 2-tuple: `[bigint, string]` where the first element is the
41
+ * numeric scale value and the second is the word for that value. The array MUST be
42
+ * ordered from largest to smallest (descending) for the greedy algorithm to work correctly.
43
+ *
44
+ * @type {Array<[bigint, string]>}
45
+ * @example
46
+ * // English scale words (descending order):
47
+ * // [[1000000000n, 'billion'], [1000000n, 'million'], [1000n, 'thousand'], [100n, 'hundred'], ..., [1n, 'one']]
48
+ */
49
+ scaleWords
50
+
51
+ // ============================================================================
52
+ // Public Methods
53
+ // ============================================================================
54
+
55
+ /**
56
+ * Returns the word for an exact scale value.
57
+ *
58
+ * @param {bigint} scale The scale value to look up (prefer BigInt for exact matching).
59
+ * @returns {string|undefined} The word for the provided scale, or `undefined`.
60
+ */
61
+ wordForScale (scale) {
62
+ const match = this.scaleWords.find((pair) => pair[0] === scale)
63
+ return match?.[1]
64
+ }
65
+
66
+ // ============================================================================
67
+ // Protected Methods (subclasses may call or override)
68
+ // ============================================================================
69
+
70
+ /**
71
+ * Decomposes an integer into a sequence of word-sets.
72
+ *
73
+ * This internal helper returns a nested structure that represents quantities and
74
+ * their matching scale words (e.g. `[{ 'one': 1n }, { 'hundred': 100n }, ...]`).
75
+ * The result is designed to be reduced by `reduceWordSets()` using language-specific `combineWordSets()`.
76
+ *
77
+ * For quantities > 1, the multiplier is recursively decomposed. For quantity = 1,
78
+ * the implicit "one" is represented with `{ 'one': 1n }` and typically omitted during combineWordSets().
79
+ *
80
+ * @protected
81
+ * @param {bigint} integerPart The integer to decompose.
82
+ * @returns {Array<Object|Array>} An array of word-set objects and possibly nested arrays.
83
+ */
84
+ decomposeInteger (integerPart) {
85
+ const wordSets = []
86
+ let remaining = integerPart
87
+
88
+ do {
89
+ const match = this.scaleWords.find((pair) => remaining >= pair[0])
90
+ if (!match) break
91
+
92
+ const multiplier = remaining === 0n ? 1n : remaining / match[0]
93
+
94
+ if (multiplier === 1n) {
95
+ wordSets.push({ [this.wordForScale(1n)]: 1n })
96
+ } else {
97
+ wordSets.push(this.decomposeInteger(multiplier))
98
+ }
99
+
100
+ wordSets.push({ [match[1]]: match[0] })
101
+
102
+ remaining = remaining === 0n ? 0n : remaining % match[0]
103
+ } while (remaining > 0n)
104
+
105
+ return wordSets
106
+ }
107
+
108
+ /**
109
+ * Reduces a nested array of word-sets into a single word-set object.
110
+ *
111
+ * This method repeatedly applies the subclass `combineWordSets()` operation until a single
112
+ * object remains. It normalizes nested arrays by recursively reducing them.
113
+ *
114
+ * @protected
115
+ * @param {Array<Object|Array>} wordSets Array of word-set objects and nested arrays.
116
+ * @returns {Object} Reduced word-set where the single object key is the language string
117
+ * and its value is the numeric BigInt result for that string.
118
+ */
119
+ reduceWordSets (wordSets) {
120
+ while (wordSets.length > 1) {
121
+ const [first, second, ...rest] = wordSets
122
+
123
+ if (!Array.isArray(first) && !Array.isArray(second)) {
124
+ const combined = this.combineWordSets(first, second)
125
+ wordSets = rest.length > 0 ? [combined, rest] : [combined]
126
+ continue
127
+ }
128
+
129
+ const normalized = wordSets.map((element) => {
130
+ if (!Array.isArray(element)) return element
131
+ return element.length === 1 ? element[0] : this.reduceWordSets(element)
132
+ })
133
+
134
+ wordSets = normalized
135
+ }
136
+
137
+ return wordSets[0]
138
+ }
139
+
140
+ // ============================================================================
141
+ // Abstract Methods (subclasses must implement)
142
+ // ============================================================================
143
+
144
+ /**
145
+ * Combines two adjacent word-sets into a single word-set.
146
+ *
147
+ * This is the core language-specific method that must be implemented by subclasses
148
+ * to define how adjacent word-sets are combined according to the language's grammar.
149
+ * For example, English combines "twenty" + "three" → "twenty-three", while
150
+ * French might combine "quatre-vingts" + "dix" → "quatre-vingt-dix".
151
+ *
152
+ * @abstract
153
+ * @protected
154
+ * @param {Object} preceding Preceding word-set as `{ word: bigint }`.
155
+ * @param {Object} following Following word-set as `{ word: bigint }`.
156
+ * @returns {Object} Combined word-set with merged text and resulting numeric value.
157
+ *
158
+ * @example
159
+ * // English implementation might handle:
160
+ * // combineWordSets({ 'twenty': 20n }, { 'three': 3n }) → { 'twenty-three': 23n }
161
+ * // combineWordSets({ 'one': 1n }, { 'hundred': 100n }) → { 'one hundred': 100n }
162
+ */
163
+ combineWordSets (preceding, following) {
164
+ throw new Error('combineWordSets() must be implemented by subclass')
165
+ }
166
+
167
+ // ============================================================================
168
+ // Optional Methods (subclasses may override)
169
+ // ============================================================================
170
+
171
+ /**
172
+ * Final string post-processing hook.
173
+ *
174
+ * Subclasses may override to apply language-specific whitespace, punctuation or
175
+ * orthographic corrections.
176
+ *
177
+ * @protected
178
+ * @param {string} output Language string produced by the conversion flow.
179
+ * @returns {string} Final formatted string.
180
+ */
181
+ finalizeWords (output) {
182
+ return output.trimEnd()
183
+ }
184
+
185
+ /**
186
+ * Converts an integer to its language-specific cardinal words.
187
+ *
188
+ * This method orchestrates decomposition, reduction, and final formatting. It does
189
+ * not handle decimals or sign; those concerns are implemented in
190
+ * `AbstractLanguage.toWords` which calls this method for the integer part.
191
+ *
192
+ * @param {bigint} integerPart The integer to convert.
193
+ * @returns {string} The cardinal representation for the integer in the language.
194
+ */
195
+ integerToWords (integerPart) {
196
+ const wordSets = this.decomposeInteger(integerPart)
197
+ const reduced = this.reduceWordSets(wordSets)
198
+ const result = Object.keys(reduced)[0]
199
+ return this.finalizeWords(result)
200
+ }
201
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Base class for Slavic and related languages with complex pluralization.
3
+ *
4
+ * This class provides a reusable implementation for languages that share:
5
+ * - Three-form pluralization (singular/few/many)
6
+ * - Gender-aware number forms (masculine/feminine for 1-9)
7
+ * - Hundreds, tens, ones decomposition pattern
8
+ * - Segment-based large number handling (thousands, millions, etc.)
9
+ * - Inherits decimal handling from AbstractLanguage (supports both grouped and
10
+ * per-digit modes via the `usePerDigitDecimals` class property).
11
+ *
12
+ * Used by: Russian (ru), Czech (cs), Polish (pl), Ukrainian (uk), Serbian (sr-Latn),
13
+ * Croatian (hr), Lithuanian (lt), Latvian (lv), Hebrew (he), and Biblical Hebrew (hbo).
14
+ *
15
+ * Subclasses MUST define these properties with language-specific vocabulary:
16
+ * - `onesWords` - Object mapping 1-9 to masculine forms (or default forms)
17
+ * - `onesFeminineWords` - Object mapping 1-9 to feminine forms (if gender distinction exists)
18
+ * - `teensWords` - Object mapping 0-9 to teen numbers (10-19)
19
+ * - `twentiesWords` - Object mapping 2-9 to tens (20-90)
20
+ * - `hundredsWords` - Object mapping 1-9 to hundreds (100-900) or special hundreds handling
21
+ * - `pluralForms` - Object mapping segment indices to [singular, few, many] plural forms
22
+ *
23
+ * Optional properties:
24
+ * - `scaleGenders` - Object mapping segment indices to boolean (true = feminine scale word)
25
+ * If not defined, defaults to thousands (index 1) being feminine, others masculine.
26
+ *
27
+ * @abstract
28
+ * @extends AbstractLanguage
29
+ */
30
+ export class SlavicLanguage extends AbstractLanguage {
31
+ /**
32
+ * Constructs a SlavicLanguage instance with optional configuration.
33
+ *
34
+ * @param {Object} [options] Configuration options.
35
+ * @param {('masculine'|'feminine')} [options.gender='masculine'] Grammatical gender for number forms.
36
+ */
37
+ constructor(options?: {
38
+ gender?: "feminine" | "masculine" | undefined;
39
+ });
40
+ /**
41
+ * Masculine forms for digits 1-9 (or default forms if no gender distinction).
42
+ *
43
+ * @type {Object.<number, string>}
44
+ */
45
+ onesWords: {
46
+ [x: number]: string;
47
+ };
48
+ /**
49
+ * Feminine forms for digits 1-9 (if language has gender distinction).
50
+ *
51
+ * @type {Object.<number, string>}
52
+ */
53
+ onesFeminineWords: {
54
+ [x: number]: string;
55
+ };
56
+ /**
57
+ * Words for teen numbers (10-19).
58
+ *
59
+ * @type {Object.<number, string>}
60
+ */
61
+ teensWords: {
62
+ [x: number]: string;
63
+ };
64
+ /**
65
+ * Words for multiples of ten (20, 30, 40, etc.).
66
+ *
67
+ * @type {Object.<number, string>}
68
+ */
69
+ twentiesWords: {
70
+ [x: number]: string;
71
+ };
72
+ /**
73
+ * Words for hundreds (100, 200, 300, etc.) or special hundreds handling.
74
+ *
75
+ * @type {Object.<number, string>}
76
+ */
77
+ hundredsWords: {
78
+ [x: number]: string;
79
+ };
80
+ /**
81
+ * Plural forms for scale words (thousands, millions, billions, etc.).
82
+ * Maps segment indices to [singular, few, many] forms.
83
+ *
84
+ * @type {Object.<number, string[]>}
85
+ */
86
+ pluralForms: {
87
+ [x: number]: string[];
88
+ };
89
+ /**
90
+ * Gender of each scale word.
91
+ * Maps segment indices to boolean: true = feminine, false = masculine.
92
+ * Default is empty (all masculine). Languages with feminine thousands
93
+ * (Russian, Ukrainian, Serbian, Croatian) should set `{ 1: true }`.
94
+ *
95
+ * @type {Object.<number, boolean>}
96
+ */
97
+ scaleGenders: {
98
+ [x: number]: boolean;
99
+ };
100
+ /**
101
+ * Whether to omit "one" before scale words (e.g., "thousand" instead of "one thousand").
102
+ * When true, 1000 becomes "tysiąc" (Polish) instead of "jeden tysiąc".
103
+ * Used by Polish, Czech, and similar languages.
104
+ *
105
+ * @type {boolean}
106
+ */
107
+ omitOneBeforeScale: boolean;
108
+ /**
109
+ * Splits a number string into segments of specified size from right to left.
110
+ *
111
+ * Example: splitToSegments('1234567', 3) => [1n, 234n, 567n]
112
+ * This represents: 1 million + 234 thousand + 567 ones
113
+ *
114
+ * @param {string} numberString The number as a string.
115
+ * @param {number} segmentSize Segment size (typically 3 for thousands grouping).
116
+ * @returns {bigint[]} Array of BigInt segments from highest to lowest scale.
117
+ */
118
+ splitToSegments(numberString: string, segmentSize: number): bigint[];
119
+ /**
120
+ * Extracts individual digits from a number (units, tens, hundreds).
121
+ *
122
+ * Returns digits in reverse order: [ones, tens, hundreds]
123
+ * Example: 456 => [6n, 5n, 4n]
124
+ *
125
+ * @param {bigint} value The number to extract digits from (0-999).
126
+ * @returns {bigint[]} Array of [ones, tens, hundreds] as BigInts.
127
+ */
128
+ extractDigits(value: bigint): bigint[];
129
+ /**
130
+ * Selects the correct plural form based on Slavic pluralization rules.
131
+ *
132
+ * Slavic languages typically use three forms:
133
+ * - Form 0 (singular): numbers ending in 1, except 11 (1, 21, 31, 101...)
134
+ * - Form 1 (few): numbers ending in 2-4, except 12-14 (2-4, 22-24, 32-34...)
135
+ * - Form 2 (many): all other numbers (0, 5-20, 25-30, 100, 111-119...)
136
+ *
137
+ * Examples using Russian тысяча (thousand):
138
+ * - 1, 21, 31... ⇒ тысяча (form 0, singular)
139
+ * - 2-4, 22-24, 32-34... ⇒ тысячи (form 1, few)
140
+ * - 0, 5-20, 25-30, 100... ⇒ тысяч (form 2, many)
141
+ *
142
+ * @param {bigint} number The number to check.
143
+ * @param {string[]} pluralForms Array of [singular, few, many] forms.
144
+ * @returns {string} The appropriate form for the number.
145
+ */
146
+ pluralize(number: bigint, pluralForms: string[]): string;
147
+ }
148
+ import { AbstractLanguage } from './abstract-language.js';