n2words 1.24.0 → 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 (349) hide show
  1. package/README.md +285 -156
  2. package/dist/ArabicConverter.js +3 -0
  3. package/dist/ArabicConverter.js.map +1 -0
  4. package/dist/AzerbaijaniConverter.js +3 -0
  5. package/dist/AzerbaijaniConverter.js.map +1 -0
  6. package/dist/BanglaConverter.js +3 -0
  7. package/dist/BanglaConverter.js.map +1 -0
  8. package/dist/BiblicalHebrewConverter.js +3 -0
  9. package/dist/BiblicalHebrewConverter.js.map +1 -0
  10. package/dist/CroatianConverter.js +3 -0
  11. package/dist/CroatianConverter.js.map +1 -0
  12. package/dist/CzechConverter.js +3 -0
  13. package/dist/CzechConverter.js.map +1 -0
  14. package/dist/DanishConverter.js +3 -0
  15. package/dist/DanishConverter.js.map +1 -0
  16. package/dist/DutchConverter.js +3 -0
  17. package/dist/DutchConverter.js.map +1 -0
  18. package/dist/EnglishConverter.js +3 -0
  19. package/dist/EnglishConverter.js.map +1 -0
  20. package/dist/FilipinoConverter.js +3 -0
  21. package/dist/FilipinoConverter.js.map +1 -0
  22. package/dist/FrenchBelgiumConverter.js +3 -0
  23. package/dist/FrenchBelgiumConverter.js.map +1 -0
  24. package/dist/FrenchConverter.js +3 -0
  25. package/dist/FrenchConverter.js.map +1 -0
  26. package/dist/GermanConverter.js +3 -0
  27. package/dist/GermanConverter.js.map +1 -0
  28. package/dist/GreekConverter.js +3 -0
  29. package/dist/GreekConverter.js.map +1 -0
  30. package/dist/GujaratiConverter.js +3 -0
  31. package/dist/GujaratiConverter.js.map +1 -0
  32. package/dist/HebrewConverter.js +3 -0
  33. package/dist/HebrewConverter.js.map +1 -0
  34. package/dist/HindiConverter.js +3 -0
  35. package/dist/HindiConverter.js.map +1 -0
  36. package/dist/HungarianConverter.js +3 -0
  37. package/dist/HungarianConverter.js.map +1 -0
  38. package/dist/IndonesianConverter.js +3 -0
  39. package/dist/IndonesianConverter.js.map +1 -0
  40. package/dist/ItalianConverter.js +3 -0
  41. package/dist/ItalianConverter.js.map +1 -0
  42. package/dist/JapaneseConverter.js +3 -0
  43. package/dist/JapaneseConverter.js.map +1 -0
  44. package/dist/KannadaConverter.js +3 -0
  45. package/dist/KannadaConverter.js.map +1 -0
  46. package/dist/KoreanConverter.js +3 -0
  47. package/dist/KoreanConverter.js.map +1 -0
  48. package/dist/LatvianConverter.js +3 -0
  49. package/dist/LatvianConverter.js.map +1 -0
  50. package/dist/LithuanianConverter.js +3 -0
  51. package/dist/LithuanianConverter.js.map +1 -0
  52. package/dist/MalayConverter.js +3 -0
  53. package/dist/MalayConverter.js.map +1 -0
  54. package/dist/MarathiConverter.js +3 -0
  55. package/dist/MarathiConverter.js.map +1 -0
  56. package/dist/NorwegianBokmalConverter.js +3 -0
  57. package/dist/NorwegianBokmalConverter.js.map +1 -0
  58. package/dist/PersianConverter.js +3 -0
  59. package/dist/PersianConverter.js.map +1 -0
  60. package/dist/PolishConverter.js +3 -0
  61. package/dist/PolishConverter.js.map +1 -0
  62. package/dist/PortugueseConverter.js +3 -0
  63. package/dist/PortugueseConverter.js.map +1 -0
  64. package/dist/PunjabiConverter.js +3 -0
  65. package/dist/PunjabiConverter.js.map +1 -0
  66. package/dist/RomanianConverter.js +3 -0
  67. package/dist/RomanianConverter.js.map +1 -0
  68. package/dist/RussianConverter.js +3 -0
  69. package/dist/RussianConverter.js.map +1 -0
  70. package/dist/SerbianCyrillicConverter.js +3 -0
  71. package/dist/SerbianCyrillicConverter.js.map +1 -0
  72. package/dist/SerbianLatinConverter.js +3 -0
  73. package/dist/SerbianLatinConverter.js.map +1 -0
  74. package/dist/SimplifiedChineseConverter.js +3 -0
  75. package/dist/SimplifiedChineseConverter.js.map +1 -0
  76. package/dist/SpanishConverter.js +3 -0
  77. package/dist/SpanishConverter.js.map +1 -0
  78. package/dist/SwahiliConverter.js +3 -0
  79. package/dist/SwahiliConverter.js.map +1 -0
  80. package/dist/SwedishConverter.js +3 -0
  81. package/dist/SwedishConverter.js.map +1 -0
  82. package/dist/TamilConverter.js +3 -0
  83. package/dist/TamilConverter.js.map +1 -0
  84. package/dist/TeluguConverter.js +3 -0
  85. package/dist/TeluguConverter.js.map +1 -0
  86. package/dist/ThaiConverter.js +3 -0
  87. package/dist/ThaiConverter.js.map +1 -0
  88. package/dist/TraditionalChineseConverter.js +3 -0
  89. package/dist/TraditionalChineseConverter.js.map +1 -0
  90. package/dist/TurkishConverter.js +3 -0
  91. package/dist/TurkishConverter.js.map +1 -0
  92. package/dist/UkrainianConverter.js +3 -0
  93. package/dist/UkrainianConverter.js.map +1 -0
  94. package/dist/UrduConverter.js +3 -0
  95. package/dist/UrduConverter.js.map +1 -0
  96. package/dist/VietnameseConverter.js +3 -0
  97. package/dist/VietnameseConverter.js.map +1 -0
  98. package/dist/n2words.js +3 -2
  99. package/dist/n2words.js.map +1 -1
  100. package/lib/classes/abstract-language.d.ts +178 -0
  101. package/lib/classes/abstract-language.js +192 -185
  102. package/lib/classes/greedy-scale-language.d.ts +109 -0
  103. package/lib/classes/greedy-scale-language.js +96 -90
  104. package/lib/classes/slavic-language.d.ts +148 -0
  105. package/lib/classes/slavic-language.js +136 -106
  106. package/lib/classes/south-asian-language.d.ts +70 -0
  107. package/lib/classes/south-asian-language.js +58 -65
  108. package/lib/classes/turkic-language.d.ts +26 -0
  109. package/lib/classes/turkic-language.js +22 -26
  110. package/lib/languages/ar.d.ts +30 -0
  111. package/lib/languages/ar.js +49 -133
  112. package/lib/languages/az.d.ts +12 -0
  113. package/lib/languages/az.js +7 -23
  114. package/lib/languages/bn.d.ts +11 -0
  115. package/lib/languages/bn.js +12 -7
  116. package/lib/languages/cs.d.ts +88 -0
  117. package/lib/languages/cs.js +44 -113
  118. package/lib/languages/da.d.ts +15 -0
  119. package/lib/languages/da.js +40 -87
  120. package/lib/languages/de.d.ts +14 -0
  121. package/lib/languages/de.js +34 -68
  122. package/lib/languages/el.d.ts +14 -0
  123. package/lib/languages/el.js +22 -48
  124. package/lib/languages/en.d.ts +16 -0
  125. package/lib/languages/en.js +22 -59
  126. package/lib/languages/es.d.ts +15 -0
  127. package/lib/languages/es.js +49 -81
  128. package/lib/languages/fa.d.ts +47 -0
  129. package/lib/languages/fa.js +90 -73
  130. package/lib/languages/fil.d.ts +16 -0
  131. package/lib/languages/fil.js +35 -76
  132. package/lib/languages/fr-BE.d.ts +11 -0
  133. package/lib/languages/fr-BE.js +15 -51
  134. package/lib/languages/fr.d.ts +15 -0
  135. package/lib/languages/fr.js +33 -72
  136. package/lib/languages/gu.d.ts +11 -0
  137. package/lib/languages/gu.js +10 -34
  138. package/lib/languages/hbo.d.ts +113 -0
  139. package/lib/languages/hbo.js +251 -0
  140. package/lib/languages/he.d.ts +80 -0
  141. package/lib/languages/he.js +41 -164
  142. package/lib/languages/hi.d.ts +11 -0
  143. package/lib/languages/hi.js +12 -7
  144. package/lib/languages/hr.d.ts +80 -0
  145. package/lib/languages/hr.js +51 -95
  146. package/lib/languages/hu.d.ts +22 -0
  147. package/lib/languages/hu.js +35 -53
  148. package/lib/languages/id.d.ts +37 -0
  149. package/lib/languages/id.js +29 -44
  150. package/lib/languages/it.d.ts +37 -0
  151. package/lib/languages/it.js +36 -52
  152. package/lib/languages/ja.d.ts +17 -0
  153. package/lib/languages/ja.js +22 -75
  154. package/lib/languages/kn.d.ts +11 -0
  155. package/lib/languages/kn.js +10 -39
  156. package/lib/languages/ko.d.ts +14 -0
  157. package/lib/languages/ko.js +17 -45
  158. package/lib/languages/lt.d.ts +70 -0
  159. package/lib/languages/lt.js +28 -63
  160. package/lib/languages/lv.d.ts +70 -0
  161. package/lib/languages/lv.js +35 -58
  162. package/lib/languages/mr.d.ts +11 -0
  163. package/lib/languages/mr.js +10 -34
  164. package/lib/languages/ms.d.ts +31 -0
  165. package/lib/languages/ms.js +24 -20
  166. package/lib/languages/nb.d.ts +12 -0
  167. package/lib/languages/nb.js +36 -56
  168. package/lib/languages/nl.d.ts +16 -0
  169. package/lib/languages/nl.js +58 -109
  170. package/lib/languages/pa.d.ts +11 -0
  171. package/lib/languages/{pa-Guru.js → pa.js} +12 -7
  172. package/lib/languages/pl.d.ts +80 -0
  173. package/lib/languages/pl.js +26 -105
  174. package/lib/languages/pt.d.ts +29 -0
  175. package/lib/languages/pt.js +29 -64
  176. package/lib/languages/ro.d.ts +158 -0
  177. package/lib/languages/ro.js +60 -167
  178. package/lib/languages/ru.d.ts +85 -0
  179. package/lib/languages/ru.js +17 -37
  180. package/lib/languages/sr-Cyrl.d.ts +80 -0
  181. package/lib/languages/sr-Cyrl.js +113 -0
  182. package/lib/languages/sr-Latn.d.ts +80 -0
  183. package/lib/languages/sr-Latn.js +54 -98
  184. package/lib/languages/sv.d.ts +14 -0
  185. package/lib/languages/sv.js +26 -63
  186. package/lib/languages/sw.d.ts +39 -0
  187. package/lib/languages/sw.js +26 -21
  188. package/lib/languages/ta.d.ts +20 -0
  189. package/lib/languages/ta.js +26 -26
  190. package/lib/languages/te.d.ts +22 -0
  191. package/lib/languages/te.js +28 -38
  192. package/lib/languages/th.d.ts +17 -0
  193. package/lib/languages/th.js +25 -31
  194. package/lib/languages/tr.d.ts +12 -0
  195. package/lib/languages/tr.js +11 -38
  196. package/lib/languages/uk.d.ts +85 -0
  197. package/lib/languages/uk.js +18 -44
  198. package/lib/languages/ur.d.ts +11 -0
  199. package/lib/languages/ur.js +12 -7
  200. package/lib/languages/vi.d.ts +72 -0
  201. package/lib/languages/vi.js +25 -71
  202. package/lib/languages/zh-Hans.d.ts +21 -0
  203. package/lib/languages/zh-Hans.js +33 -87
  204. package/lib/languages/zh-Hant.d.ts +21 -0
  205. package/lib/languages/zh-Hant.js +111 -0
  206. package/lib/n2words.d.ts +209 -0
  207. package/lib/n2words.js +474 -191
  208. package/package.json +106 -67
  209. package/dist/languages/ar.js +0 -2
  210. package/dist/languages/ar.js.map +0 -1
  211. package/dist/languages/az.js +0 -2
  212. package/dist/languages/az.js.map +0 -1
  213. package/dist/languages/bn.js +0 -2
  214. package/dist/languages/bn.js.map +0 -1
  215. package/dist/languages/cs.js +0 -2
  216. package/dist/languages/cs.js.map +0 -1
  217. package/dist/languages/da.js +0 -2
  218. package/dist/languages/da.js.map +0 -1
  219. package/dist/languages/de.js +0 -2
  220. package/dist/languages/de.js.map +0 -1
  221. package/dist/languages/el.js +0 -2
  222. package/dist/languages/el.js.map +0 -1
  223. package/dist/languages/en.js +0 -2
  224. package/dist/languages/en.js.map +0 -1
  225. package/dist/languages/es.js +0 -2
  226. package/dist/languages/es.js.map +0 -1
  227. package/dist/languages/fa.js +0 -2
  228. package/dist/languages/fa.js.map +0 -1
  229. package/dist/languages/fil.js +0 -2
  230. package/dist/languages/fil.js.map +0 -1
  231. package/dist/languages/fr-BE.js +0 -2
  232. package/dist/languages/fr-BE.js.map +0 -1
  233. package/dist/languages/fr.js +0 -2
  234. package/dist/languages/fr.js.map +0 -1
  235. package/dist/languages/gu.js +0 -2
  236. package/dist/languages/gu.js.map +0 -1
  237. package/dist/languages/he.js +0 -2
  238. package/dist/languages/he.js.map +0 -1
  239. package/dist/languages/hi.js +0 -2
  240. package/dist/languages/hi.js.map +0 -1
  241. package/dist/languages/hr.js +0 -2
  242. package/dist/languages/hr.js.map +0 -1
  243. package/dist/languages/hu.js +0 -2
  244. package/dist/languages/hu.js.map +0 -1
  245. package/dist/languages/id.js +0 -2
  246. package/dist/languages/id.js.map +0 -1
  247. package/dist/languages/it.js +0 -2
  248. package/dist/languages/it.js.map +0 -1
  249. package/dist/languages/ja.js +0 -2
  250. package/dist/languages/ja.js.map +0 -1
  251. package/dist/languages/kn.js +0 -2
  252. package/dist/languages/kn.js.map +0 -1
  253. package/dist/languages/ko.js +0 -2
  254. package/dist/languages/ko.js.map +0 -1
  255. package/dist/languages/lt.js +0 -2
  256. package/dist/languages/lt.js.map +0 -1
  257. package/dist/languages/lv.js +0 -2
  258. package/dist/languages/lv.js.map +0 -1
  259. package/dist/languages/mr.js +0 -2
  260. package/dist/languages/mr.js.map +0 -1
  261. package/dist/languages/ms.js +0 -2
  262. package/dist/languages/ms.js.map +0 -1
  263. package/dist/languages/nb.js +0 -2
  264. package/dist/languages/nb.js.map +0 -1
  265. package/dist/languages/nl.js +0 -2
  266. package/dist/languages/nl.js.map +0 -1
  267. package/dist/languages/pa-Guru.js +0 -2
  268. package/dist/languages/pa-Guru.js.map +0 -1
  269. package/dist/languages/pl.js +0 -2
  270. package/dist/languages/pl.js.map +0 -1
  271. package/dist/languages/pt.js +0 -2
  272. package/dist/languages/pt.js.map +0 -1
  273. package/dist/languages/ro.js +0 -2
  274. package/dist/languages/ro.js.map +0 -1
  275. package/dist/languages/ru.js +0 -2
  276. package/dist/languages/ru.js.map +0 -1
  277. package/dist/languages/sr-Latn.js +0 -2
  278. package/dist/languages/sr-Latn.js.map +0 -1
  279. package/dist/languages/sv.js +0 -2
  280. package/dist/languages/sv.js.map +0 -1
  281. package/dist/languages/sw.js +0 -2
  282. package/dist/languages/sw.js.map +0 -1
  283. package/dist/languages/ta.js +0 -2
  284. package/dist/languages/ta.js.map +0 -1
  285. package/dist/languages/te.js +0 -2
  286. package/dist/languages/te.js.map +0 -1
  287. package/dist/languages/th.js +0 -2
  288. package/dist/languages/th.js.map +0 -1
  289. package/dist/languages/tr.js +0 -2
  290. package/dist/languages/tr.js.map +0 -1
  291. package/dist/languages/uk.js +0 -2
  292. package/dist/languages/uk.js.map +0 -1
  293. package/dist/languages/ur.js +0 -2
  294. package/dist/languages/ur.js.map +0 -1
  295. package/dist/languages/vi.js +0 -2
  296. package/dist/languages/vi.js.map +0 -1
  297. package/dist/languages/zh-Hans.js +0 -2
  298. package/dist/languages/zh-Hans.js.map +0 -1
  299. package/typings/classes/abstract-language.d.ts +0 -144
  300. package/typings/classes/greedy-scale-language.d.ts +0 -148
  301. package/typings/classes/slavic-language.d.ts +0 -145
  302. package/typings/classes/south-asian-language.d.ts +0 -101
  303. package/typings/classes/turkic-language.d.ts +0 -42
  304. package/typings/languages/ar.d.ts +0 -93
  305. package/typings/languages/az.d.ts +0 -25
  306. package/typings/languages/bn.d.ts +0 -1
  307. package/typings/languages/cs.d.ts +0 -120
  308. package/typings/languages/da.d.ts +0 -53
  309. package/typings/languages/de.d.ts +0 -26
  310. package/typings/languages/el.d.ts +0 -11
  311. package/typings/languages/en.d.ts +0 -30
  312. package/typings/languages/es.d.ts +0 -43
  313. package/typings/languages/fa.d.ts +0 -81
  314. package/typings/languages/fil.d.ts +0 -12
  315. package/typings/languages/fr-BE.d.ts +0 -41
  316. package/typings/languages/fr.d.ts +0 -43
  317. package/typings/languages/gu.d.ts +0 -12
  318. package/typings/languages/he.d.ts +0 -197
  319. package/typings/languages/hi.d.ts +0 -1
  320. package/typings/languages/hr.d.ts +0 -110
  321. package/typings/languages/hu.d.ts +0 -37
  322. package/typings/languages/id.d.ts +0 -69
  323. package/typings/languages/it.d.ts +0 -51
  324. package/typings/languages/ja.d.ts +0 -58
  325. package/typings/languages/kn.d.ts +0 -11
  326. package/typings/languages/ko.d.ts +0 -25
  327. package/typings/languages/lt.d.ts +0 -110
  328. package/typings/languages/lv.d.ts +0 -99
  329. package/typings/languages/mr.d.ts +0 -12
  330. package/typings/languages/ms.d.ts +0 -37
  331. package/typings/languages/nb.d.ts +0 -27
  332. package/typings/languages/nl.d.ts +0 -65
  333. package/typings/languages/pa-Guru.d.ts +0 -1
  334. package/typings/languages/pl.d.ts +0 -116
  335. package/typings/languages/pt.d.ts +0 -39
  336. package/typings/languages/ro.d.ts +0 -229
  337. package/typings/languages/ru.d.ts +0 -108
  338. package/typings/languages/sr-Latn.d.ts +0 -98
  339. package/typings/languages/sv.d.ts +0 -30
  340. package/typings/languages/sw.d.ts +0 -1
  341. package/typings/languages/ta.d.ts +0 -1
  342. package/typings/languages/te.d.ts +0 -1
  343. package/typings/languages/th.d.ts +0 -1
  344. package/typings/languages/tr.d.ts +0 -46
  345. package/typings/languages/uk.d.ts +0 -117
  346. package/typings/languages/ur.d.ts +0 -1
  347. package/typings/languages/vi.d.ts +0 -116
  348. package/typings/languages/zh-Hans.d.ts +0 -57
  349. package/typings/n2words.d.ts +0 -177
@@ -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';
@@ -1,144 +1,148 @@
1
- import AbstractLanguage from './abstract-language.js'
2
-
3
- /**
4
- * @typedef {Object} WordSet
5
- * @property {string} word - The language word or phrase
6
- * @property {bigint} value - The numeric value represented by the word
7
- */
8
-
9
- /**
10
- * @typedef {Array.<Array.<(bigint|string)>>} ScaleWordPairs
11
- * Array of scale word pairs in descending order. Each pair contains:
12
- * - [0]: BigInt numeric value
13
- * - [1]: String word representation
14
- * Must be ordered largest to smallest for the greedy algorithm.
15
- */
1
+ import { AbstractLanguage } from './abstract-language.js'
16
2
 
17
3
  /**
18
4
  * Greedy scale language converter implementing the "highest-matching scale word" algorithm.
19
5
  *
20
6
  * Responsibilities:
21
- * - Decompose a whole-number value into a sequence of scale word-sets.
22
- * - Provide helpers to merge and post-process matched word-sets.
7
+ * - Decompose an integer into a sequence of word-sets using greedy matching.
8
+ * - Provide helpers to reduce and post-process matched word-sets.
23
9
  * - Inherits decimal handling from AbstractLanguage (supports grouped and per-digit
24
- * modes via the `convertDecimalsPerDigit` class property).
10
+ * modes via the `usePerDigitDecimals` class property).
25
11
  *
26
12
  * Subclass requirements:
27
- * - Define `scaleWordPairs` (ordered descending) as `[BigInt, string]` tuples.
28
- * - Implement `mergeScales(leftWordSet, rightWordSet)` to combine adjacent word-sets
13
+ * - Define `scaleWords` (ordered descending) as `[bigint, string]` tuples.
14
+ * - Implement 'combineWordSets(preceding, following)' to combine adjacent word-sets
29
15
  * per language grammar.
30
16
  *
31
17
  * Scale words specification:
32
- * - `scaleWordPairs` is an Array of 2-tuples: `[BigInt, string]` where the first element
33
- * is the numeric value (BigInt) and the second is the word used for that value.
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.
34
20
  * - Scale words MUST be ordered from largest to smallest (descending) for the algorithm
35
21
  * to function correctly.
36
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
+ *
37
28
  * @abstract
38
29
  * @extends AbstractLanguage
39
- * @example
40
- * // Example `scaleWordPairs` for English (descending order):
41
- * // [[1000000000n, 'billion'], [1000000n, 'million'], [1000n, 'thousand'], [100n, 'hundred'], ..., [1n, 'one']]
42
30
  */
43
31
 
44
- class GreedyScaleLanguage extends AbstractLanguage {
32
+ export class GreedyScaleLanguage extends AbstractLanguage {
33
+ // ============================================================================
34
+ // Required Properties (subclasses must define)
35
+ // ============================================================================
36
+
45
37
  /**
46
- * Array of scale word pairs mapping numeric values to their word representations.
38
+ * Array of scale words mapping numeric values to their word representations.
47
39
  *
48
- * Each element is a 2-tuple: `[BigInt, string]` where the first element is the
49
- * numeric value and the second is the word for that value. The array MUST be
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
50
42
  * ordered from largest to smallest (descending) for the greedy algorithm to work correctly.
51
43
  *
52
- * @type {Array.<Array.<(bigint|string)>>}
44
+ * @type {Array<[bigint, string]>}
53
45
  * @example
54
46
  * // English scale words (descending order):
55
47
  * // [[1000000000n, 'billion'], [1000000n, 'million'], [1000n, 'thousand'], [100n, 'hundred'], ..., [1n, 'one']]
56
48
  */
57
- scaleWordPairs
49
+ scaleWords
50
+
51
+ // ============================================================================
52
+ // Public Methods
53
+ // ============================================================================
58
54
 
59
55
  /**
60
- * Return the word associated with an exact scale word-set numeric value.
56
+ * Returns the word for an exact scale value.
61
57
  *
62
- * @param {bigint|number} scaleValue Numeric word-set object key (prefer BigInt for exact matching).
63
- * @returns {string|undefined} The word for the provided scale value, or `undefined`.
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`.
64
60
  */
65
- getScaleWord (scaleValue) {
66
- const matchingPair = this.scaleWordPairs.find((pair) => pair[0] === scaleValue)
67
- return matchingPair?.[1]
61
+ wordForScale (scale) {
62
+ const match = this.scaleWords.find((pair) => pair[0] === scale)
63
+ return match?.[1]
68
64
  }
69
65
 
66
+ // ============================================================================
67
+ // Protected Methods (subclasses may call or override)
68
+ // ============================================================================
69
+
70
70
  /**
71
- * Decompose a whole-number into a sequence of scale word-sets.
71
+ * Decomposes an integer into a sequence of word-sets.
72
72
  *
73
73
  * This internal helper returns a nested structure that represents quantities and
74
74
  * their matching scale words (e.g. `[{ 'one': 1n }, { 'hundred': 100n }, ...]`).
75
- * The result is designed to be reduced by `mergeWordSets()` using language-specific `mergeScales()`.
75
+ * The result is designed to be reduced by `reduceWordSets()` using language-specific `combineWordSets()`.
76
76
  *
77
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 mergeScales().
78
+ * the implicit "one" is represented with `{ 'one': 1n }` and typically omitted during combineWordSets().
79
79
  *
80
80
  * @protected
81
- * @param {bigint} wholeNumber The integer value to decompose (BigInt preferred).
81
+ * @param {bigint} integerPart The integer to decompose.
82
82
  * @returns {Array<Object|Array>} An array of word-set objects and possibly nested arrays.
83
83
  */
84
- decomposeToScales (wholeNumber) {
85
- const decomposeWordSets = []
86
- let remainingValue = wholeNumber
84
+ decomposeInteger (integerPart) {
85
+ const wordSets = []
86
+ let remaining = integerPart
87
87
 
88
88
  do {
89
- const matchingScaleWordPair = this.scaleWordPairs.find((scaleWordPair) => remainingValue >= scaleWordPair[0])
90
- if (!matchingScaleWordPair) break
89
+ const match = this.scaleWords.find((pair) => remaining >= pair[0])
90
+ if (!match) break
91
91
 
92
- const multiplier = remainingValue === 0n ? 1n : remainingValue / matchingScaleWordPair[0]
92
+ const multiplier = remaining === 0n ? 1n : remaining / match[0]
93
93
 
94
94
  if (multiplier === 1n) {
95
- decomposeWordSets.push({ [this.getScaleWord(1n)]: 1n })
95
+ wordSets.push({ [this.wordForScale(1n)]: 1n })
96
96
  } else {
97
- decomposeWordSets.push(this.decomposeToScales(multiplier))
97
+ wordSets.push(this.decomposeInteger(multiplier))
98
98
  }
99
99
 
100
- decomposeWordSets.push({ [matchingScaleWordPair[1]]: matchingScaleWordPair[0] })
100
+ wordSets.push({ [match[1]]: match[0] })
101
101
 
102
- remainingValue = remainingValue === 0n ? 0n : remainingValue % matchingScaleWordPair[0]
103
- } while (remainingValue > 0n)
102
+ remaining = remaining === 0n ? 0n : remaining % match[0]
103
+ } while (remaining > 0n)
104
104
 
105
- return decomposeWordSets
105
+ return wordSets
106
106
  }
107
107
 
108
108
  /**
109
- * Reduce a nested array of word-sets into a single merged word-set object.
109
+ * Reduces a nested array of word-sets into a single word-set object.
110
110
  *
111
- * This method repeatedly applies the subclass `mergeScales()` operation until a single
112
- * object remains. It normalizes nested arrays by recursively merging them.
111
+ * This method repeatedly applies the subclass `combineWordSets()` operation until a single
112
+ * object remains. It normalizes nested arrays by recursively reducing them.
113
113
  *
114
114
  * @protected
115
- * @param {Array<Object|Array>} mergeWordSetsList Array of word-set objects and nested arrays.
116
- * @returns {Object} Merged word-set where the single object key is the language string
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
117
  * and its value is the numeric BigInt result for that string.
118
118
  */
119
- mergeWordSets (mergeWordSetsList) {
120
- while (mergeWordSetsList.length > 1) {
121
- const [firstWordSet, secondWordSet, ...remainingWordSets] = mergeWordSetsList
119
+ reduceWordSets (wordSets) {
120
+ while (wordSets.length > 1) {
121
+ const [first, second, ...rest] = wordSets
122
122
 
123
- if (!Array.isArray(firstWordSet) && !Array.isArray(secondWordSet)) {
124
- const merged = this.mergeScales(firstWordSet, secondWordSet)
125
- mergeWordSetsList = remainingWordSets.length > 0 ? [merged, remainingWordSets] : [merged]
123
+ if (!Array.isArray(first) && !Array.isArray(second)) {
124
+ const combined = this.combineWordSets(first, second)
125
+ wordSets = rest.length > 0 ? [combined, rest] : [combined]
126
126
  continue
127
127
  }
128
128
 
129
- const normalizedWordSets = mergeWordSetsList.map((wordSetElement) => {
130
- if (!Array.isArray(wordSetElement)) return wordSetElement
131
- return wordSetElement.length === 1 ? wordSetElement[0] : this.mergeWordSets(wordSetElement)
129
+ const normalized = wordSets.map((element) => {
130
+ if (!Array.isArray(element)) return element
131
+ return element.length === 1 ? element[0] : this.reduceWordSets(element)
132
132
  })
133
133
 
134
- mergeWordSetsList = normalizedWordSets
134
+ wordSets = normalized
135
135
  }
136
136
 
137
- return mergeWordSetsList[0]
137
+ return wordSets[0]
138
138
  }
139
139
 
140
+ // ============================================================================
141
+ // Abstract Methods (subclasses must implement)
142
+ // ============================================================================
143
+
140
144
  /**
141
- * Combine two adjacent word-sets into a single merged word-set.
145
+ * Combines two adjacent word-sets into a single word-set.
142
146
  *
143
147
  * This is the core language-specific method that must be implemented by subclasses
144
148
  * to define how adjacent word-sets are combined according to the language's grammar.
@@ -147,19 +151,23 @@ class GreedyScaleLanguage extends AbstractLanguage {
147
151
  *
148
152
  * @abstract
149
153
  * @protected
150
- * @param {Object} leftWordSet Left operand as `{ word: bigint }` pair.
151
- * @param {Object} rightWordSet Right operand as `{ word: bigint }` pair.
152
- * @returns {Object} Single merged word-set object with combined text and numeric value.
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.
153
157
  *
154
158
  * @example
155
159
  * // English implementation might handle:
156
- * // mergeScales({ 'twenty': 20n }, { 'three': 3n }) → { 'twenty-three': 23n }
157
- * // mergeScales({ 'one': 1n }, { 'hundred': 100n }) → { 'one hundred': 100n }
160
+ * // combineWordSets({ 'twenty': 20n }, { 'three': 3n }) → { 'twenty-three': 23n }
161
+ * // combineWordSets({ 'one': 1n }, { 'hundred': 100n }) → { 'one hundred': 100n }
158
162
  */
159
- mergeScales (leftWordSet, rightWordSet) {
160
- throw new Error('mergeScales() must be implemented by subclass')
163
+ combineWordSets (preceding, following) {
164
+ throw new Error('combineWordSets() must be implemented by subclass')
161
165
  }
162
166
 
167
+ // ============================================================================
168
+ // Optional Methods (subclasses may override)
169
+ // ============================================================================
170
+
163
171
  /**
164
172
  * Final string post-processing hook.
165
173
  *
@@ -167,7 +175,7 @@ class GreedyScaleLanguage extends AbstractLanguage {
167
175
  * orthographic corrections.
168
176
  *
169
177
  * @protected
170
- * @param {string} output Merged language string produced by `convertWholePart` flow.
178
+ * @param {string} output Language string produced by the conversion flow.
171
179
  * @returns {string} Final formatted string.
172
180
  */
173
181
  finalizeWords (output) {
@@ -175,21 +183,19 @@ class GreedyScaleLanguage extends AbstractLanguage {
175
183
  }
176
184
 
177
185
  /**
178
- * Convert an integer value to its language-specific cardinal words.
186
+ * Converts an integer to its language-specific cardinal words.
179
187
  *
180
- * This method orchestrates decomposition, merging and final formatting. It does
188
+ * This method orchestrates decomposition, reduction, and final formatting. It does
181
189
  * not handle decimals or sign; those concerns are implemented in
182
- * `AbstractLanguage.convertToWords` which calls this helper for the whole-number part.
190
+ * `AbstractLanguage.toWords` which calls this method for the integer part.
183
191
  *
184
- * @param {bigint|number} wholeNumber Whole-number value to convert (BigInt is preferred).
185
- * @returns {string} The cardinal representation for `value` in the language.
192
+ * @param {bigint} integerPart The integer to convert.
193
+ * @returns {string} The cardinal representation for the integer in the language.
186
194
  */
187
- convertWholePart (wholeNumber) {
188
- const decomposeWordSets = this.decomposeToScales(wholeNumber)
189
- const mergedWordSet = this.mergeWordSets(decomposeWordSets)
190
- const resultString = Object.keys(mergedWordSet)[0]
191
- return this.finalizeWords(resultString)
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)
192
200
  }
193
201
  }
194
-
195
- export default GreedyScaleLanguage
@@ -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';