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,281 @@
1
+ import { AbstractLanguage } from './abstract-language.js'
2
+
3
+ /**
4
+ * Base class for Slavic and related languages with complex pluralization.
5
+ *
6
+ * This class provides a reusable implementation for languages that share:
7
+ * - Three-form pluralization (singular/few/many)
8
+ * - Gender-aware number forms (masculine/feminine for 1-9)
9
+ * - Hundreds, tens, ones decomposition pattern
10
+ * - Segment-based large number handling (thousands, millions, etc.)
11
+ * - Inherits decimal handling from AbstractLanguage (supports both grouped and
12
+ * per-digit modes via the `usePerDigitDecimals` class property).
13
+ *
14
+ * Used by: Russian (ru), Czech (cs), Polish (pl), Ukrainian (uk), Serbian (sr-Latn),
15
+ * Croatian (hr), Lithuanian (lt), Latvian (lv), Hebrew (he), and Biblical Hebrew (hbo).
16
+ *
17
+ * Subclasses MUST define these properties with language-specific vocabulary:
18
+ * - `onesWords` - Object mapping 1-9 to masculine forms (or default forms)
19
+ * - `onesFeminineWords` - Object mapping 1-9 to feminine forms (if gender distinction exists)
20
+ * - `teensWords` - Object mapping 0-9 to teen numbers (10-19)
21
+ * - `twentiesWords` - Object mapping 2-9 to tens (20-90)
22
+ * - `hundredsWords` - Object mapping 1-9 to hundreds (100-900) or special hundreds handling
23
+ * - `pluralForms` - Object mapping segment indices to [singular, few, many] plural forms
24
+ *
25
+ * Optional properties:
26
+ * - `scaleGenders` - Object mapping segment indices to boolean (true = feminine scale word)
27
+ * If not defined, defaults to thousands (index 1) being feminine, others masculine.
28
+ *
29
+ * @abstract
30
+ * @extends AbstractLanguage
31
+ */
32
+ export class SlavicLanguage extends AbstractLanguage {
33
+ // ============================================================================
34
+ // Required Properties (subclasses must define)
35
+ // ============================================================================
36
+
37
+ /**
38
+ * Masculine forms for digits 1-9 (or default forms if no gender distinction).
39
+ *
40
+ * @type {Object.<number, string>}
41
+ */
42
+ onesWords = {}
43
+
44
+ /**
45
+ * Feminine forms for digits 1-9 (if language has gender distinction).
46
+ *
47
+ * @type {Object.<number, string>}
48
+ */
49
+ onesFeminineWords = {}
50
+
51
+ /**
52
+ * Words for teen numbers (10-19).
53
+ *
54
+ * @type {Object.<number, string>}
55
+ */
56
+ teensWords = {}
57
+
58
+ /**
59
+ * Words for multiples of ten (20, 30, 40, etc.).
60
+ *
61
+ * @type {Object.<number, string>}
62
+ */
63
+ twentiesWords = {}
64
+
65
+ /**
66
+ * Words for hundreds (100, 200, 300, etc.) or special hundreds handling.
67
+ *
68
+ * @type {Object.<number, string>}
69
+ */
70
+ hundredsWords = {}
71
+
72
+ /**
73
+ * Plural forms for scale words (thousands, millions, billions, etc.).
74
+ * Maps segment indices to [singular, few, many] forms.
75
+ *
76
+ * @type {Object.<number, string[]>}
77
+ */
78
+ pluralForms = {}
79
+
80
+ // ============================================================================
81
+ // Optional Properties (subclasses may override)
82
+ // ============================================================================
83
+
84
+ /**
85
+ * Gender of each scale word.
86
+ * Maps segment indices to boolean: true = feminine, false = masculine.
87
+ * Default is empty (all masculine). Languages with feminine thousands
88
+ * (Russian, Ukrainian, Serbian, Croatian) should set `{ 1: true }`.
89
+ *
90
+ * @type {Object.<number, boolean>}
91
+ */
92
+ scaleGenders = {}
93
+
94
+ /**
95
+ * Whether to omit "one" before scale words (e.g., "thousand" instead of "one thousand").
96
+ * When true, 1000 becomes "tysiąc" (Polish) instead of "jeden tysiąc".
97
+ * Used by Polish, Czech, and similar languages.
98
+ *
99
+ * @type {boolean}
100
+ */
101
+ omitOneBeforeScale = false
102
+
103
+ // ============================================================================
104
+ // Constructor
105
+ // ============================================================================
106
+
107
+ /**
108
+ * Constructs a SlavicLanguage instance with optional configuration.
109
+ *
110
+ * @param {Object} [options] Configuration options.
111
+ * @param {('masculine'|'feminine')} [options.gender='masculine'] Grammatical gender for number forms.
112
+ */
113
+ constructor (options = {}) {
114
+ super()
115
+
116
+ this.setOptions({
117
+ gender: 'masculine'
118
+ }, options)
119
+ }
120
+
121
+ // ============================================================================
122
+ // Public Methods
123
+ // ============================================================================
124
+
125
+ /**
126
+ * Converts an integer to its word representation.
127
+ *
128
+ * This method implements the Slavic number construction algorithm:
129
+ * 1. Split number into 3-digit segments from right to left (567, 234, 1 for 1,234,567)
130
+ * 2. For each segment (processing left to right): convert hundreds, tens, ones
131
+ * 3. Apply gender rules: feminine forms for thousands segment or when feminine=true
132
+ * 4. Add appropriate pluralized scale word (thousand/million/billion/etc.)
133
+ * 5. Join all parts with spaces
134
+ *
135
+ * @param {bigint} integerPart The integer to convert (non-negative).
136
+ * @returns {string} The number in words.
137
+ */
138
+ integerToWords (integerPart) {
139
+ if (integerPart === 0n) {
140
+ return this.zeroWord
141
+ }
142
+
143
+ const words = []
144
+ const segments = this.splitToSegments(integerPart.toString(), 3)
145
+ let segmentIndex = segments.length
146
+
147
+ for (const segmentValue of segments) {
148
+ segmentIndex = segmentIndex - 1
149
+
150
+ if (segmentValue === 0n) {
151
+ continue
152
+ }
153
+
154
+ const [onesDigit, tensDigit, hundredsDigit] = this.extractDigits(segmentValue)
155
+
156
+ if (hundredsDigit > 0n) {
157
+ words.push(this.hundredsWords[hundredsDigit])
158
+ }
159
+
160
+ if (tensDigit > 1n) {
161
+ words.push(this.twentiesWords[tensDigit])
162
+ }
163
+
164
+ // Handle teens (10-19) or ones (1-9)
165
+ if (tensDigit === 1n) {
166
+ // Teens: use teensWords array directly
167
+ words.push(this.teensWords[onesDigit])
168
+ } else if (onesDigit > 0n) {
169
+ // Skip "one" before scale words if omitOneBeforeScale is set
170
+ // e.g., Polish says "tysiąc" not "jeden tysiąc" for 1000
171
+ const shouldOmitOne = this.omitOneBeforeScale && segmentIndex > 0 && segmentValue === 1n
172
+
173
+ if (!shouldOmitOne) {
174
+ // Determine if feminine forms should be used:
175
+ // 1. Check scaleGenders for this segment index (e.g., thousands = index 1)
176
+ // 2. Also use feminine if user requested gender='feminine' for the ones segment
177
+ const isScaleFeminine = this.scaleGenders[segmentIndex] === true
178
+ const isFeminine = isScaleFeminine || (this.options.gender === 'feminine' && segmentIndex === 0)
179
+ const onesArray = isFeminine ? this.onesFeminineWords : this.onesWords
180
+ words.push(onesArray[onesDigit])
181
+ }
182
+ }
183
+
184
+ // Add power word (thousand, million, etc.) with proper pluralization
185
+ if (segmentIndex > 0) {
186
+ words.push(this.pluralize(segmentValue, this.pluralForms[segmentIndex]))
187
+ }
188
+ }
189
+
190
+ return words.join(' ')
191
+ }
192
+
193
+ // ============================================================================
194
+ // Protected Methods (subclasses may call or override)
195
+ // ============================================================================
196
+
197
+ /**
198
+ * Splits a number string into segments of specified size from right to left.
199
+ *
200
+ * Example: splitToSegments('1234567', 3) => [1n, 234n, 567n]
201
+ * This represents: 1 million + 234 thousand + 567 ones
202
+ *
203
+ * @param {string} numberString The number as a string.
204
+ * @param {number} segmentSize Segment size (typically 3 for thousands grouping).
205
+ * @returns {bigint[]} Array of BigInt segments from highest to lowest scale.
206
+ */
207
+ splitToSegments (numberString, segmentSize) {
208
+ const segments = []
209
+ const stringLength = numberString.length
210
+
211
+ if (stringLength > segmentSize) {
212
+ const remainderLength = stringLength % segmentSize
213
+
214
+ if (remainderLength > 0) {
215
+ segments.push(BigInt(numberString.slice(0, remainderLength)))
216
+ }
217
+
218
+ for (let i = remainderLength; i < stringLength; i += segmentSize) {
219
+ segments.push(BigInt(numberString.slice(i, i + segmentSize)))
220
+ }
221
+ } else {
222
+ segments.push(BigInt(numberString))
223
+ }
224
+
225
+ return segments
226
+ }
227
+
228
+ /**
229
+ * Extracts individual digits from a number (units, tens, hundreds).
230
+ *
231
+ * Returns digits in reverse order: [ones, tens, hundreds]
232
+ * Example: 456 => [6n, 5n, 4n]
233
+ *
234
+ * @param {bigint} value The number to extract digits from (0-999).
235
+ * @returns {bigint[]} Array of [ones, tens, hundreds] as BigInts.
236
+ */
237
+ extractDigits (value) {
238
+ // Direct BigInt arithmetic is faster than string manipulation
239
+ const onesPlace = value % 10n
240
+ const tensPlace = (value / 10n) % 10n
241
+ const hundredsPlace = value / 100n
242
+ return [onesPlace, tensPlace, hundredsPlace]
243
+ }
244
+
245
+ /**
246
+ * Selects the correct plural form based on Slavic pluralization rules.
247
+ *
248
+ * Slavic languages typically use three forms:
249
+ * - Form 0 (singular): numbers ending in 1, except 11 (1, 21, 31, 101...)
250
+ * - Form 1 (few): numbers ending in 2-4, except 12-14 (2-4, 22-24, 32-34...)
251
+ * - Form 2 (many): all other numbers (0, 5-20, 25-30, 100, 111-119...)
252
+ *
253
+ * Examples using Russian тысяча (thousand):
254
+ * - 1, 21, 31... ⇒ тысяча (form 0, singular)
255
+ * - 2-4, 22-24, 32-34... ⇒ тысячи (form 1, few)
256
+ * - 0, 5-20, 25-30, 100... ⇒ тысяч (form 2, many)
257
+ *
258
+ * @param {bigint} number The number to check.
259
+ * @param {string[]} pluralForms Array of [singular, few, many] forms.
260
+ * @returns {string} The appropriate form for the number.
261
+ */
262
+ pluralize (number, pluralForms) {
263
+ const remainder100 = number % 100n
264
+ const remainder10 = number % 10n
265
+
266
+ // Check if in 11-19 range (special case)
267
+ if (remainder100 >= 10n && remainder100 <= 20n) {
268
+ return pluralForms[2] // Always use "many" form for 11-20
269
+ }
270
+
271
+ if (remainder10 === 1n) {
272
+ return pluralForms[0] // Singular
273
+ }
274
+
275
+ if (remainder10 >= 2n && remainder10 <= 4n) {
276
+ return pluralForms[1] // Few (2-4)
277
+ }
278
+
279
+ return pluralForms[2] // Many
280
+ }
281
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Base class for South Asian languages with shared grouping patterns.
3
+ *
4
+ * This class provides a reusable implementation for South Asian languages that share:
5
+ * - Indian-style number grouping: last 3 digits, then 2-2 (1,23,45,67,89)
6
+ * - Lakh (100,000), Crore (10,000,000), Arab (1,000,000,000) scale words
7
+ * - Standard negative and decimal handling (inherits AbstractLanguage decimal logic,
8
+ * including `usePerDigitDecimals` support when set by subclasses)
9
+ *
10
+ * Used by: Hindi (hi), Bengali (bn), Urdu (ur), Punjabi (pa), Marathi (mr), Gujarati (gu), Kannada (kn)
11
+ *
12
+ * Subclasses MUST define language-specific vocabulary via class properties:
13
+ * - `belowHundredWords` array with digit and teen words (0-99)
14
+ * - `hundredWord` string used inside `segmentToWords`
15
+ * - `scaleWords` array with grouping words (hazaar, lakh, crore, etc.) indexed by grouping level
16
+ * - `negativeWord`, `decimalSeparatorWord`, `zeroWord`, `wordSeparator`
17
+ *
18
+ * @abstract
19
+ * @extends AbstractLanguage
20
+ */
21
+ export class SouthAsianLanguage extends AbstractLanguage {
22
+ /**
23
+ * Array of words for numbers 0-99 (digits and teens).
24
+ * Index directly: belowHundredWords[0] through belowHundredWords[99].
25
+ * @type {Array<string>}
26
+ */
27
+ belowHundredWords: Array<string>;
28
+ /**
29
+ * Word for "hundred" in the language (e.g., 'सौ' in Hindi, 'শত' in Bengali).
30
+ * Used to construct hundreds (e.g., "1 hundred", "2 hundred").
31
+ * @type {string}
32
+ */
33
+ hundredWord: string;
34
+ /**
35
+ * Array of scale words for Indian-style grouping (hazaar, lakh, crore, arab, etc.).
36
+ * Index 0 contains empty string (ones place has no scale word).
37
+ * Index 1 is for thousands, Index 2 for lakhs, Index 3 for crores, etc.
38
+ * @type {Array<string>}
39
+ */
40
+ scaleWords: Array<string>;
41
+ /**
42
+ * Splits a number into Indian numbering system segments.
43
+ *
44
+ * The Indian system segments differently than Western (3-3-3) systems:
45
+ * - First segment (rightmost): Up to 3 digits (ones, tens, hundreds)
46
+ * - Subsequent segments: Exactly 2 digits each (thousands, lakhs, crores, etc.)
47
+ *
48
+ * This creates the familiar Indian comma pattern: 1,23,45,67,890
49
+ *
50
+ * @protected
51
+ * @param {bigint} integerPart The integer to split into segments.
52
+ * @returns {Array<number>} Array of segments from most significant to least significant.
53
+ *
54
+ * @example
55
+ * // splitToSegments(1234567n) → [12, 34, 567]
56
+ * // Reads as: 12 lakhs, 34 thousands, 567 units
57
+ * // splitToSegments(98765432n) → [9, 87, 65, 432]
58
+ * // Reads as: 9 crores, 87 lakhs, 65 thousands, 432 units
59
+ */
60
+ protected splitToSegments(integerPart: bigint): Array<number>;
61
+ /**
62
+ * Converts a segment (0-999) to words.
63
+ *
64
+ * @protected
65
+ * @param {number} segmentValue Value between 0 and 999.
66
+ * @returns {string} Language-specific word representation.
67
+ */
68
+ protected segmentToWords(segmentValue: number): string;
69
+ }
70
+ import { AbstractLanguage } from './abstract-language.js';
@@ -0,0 +1,154 @@
1
+ import { AbstractLanguage } from './abstract-language.js'
2
+
3
+ /**
4
+ * Base class for South Asian languages with shared grouping patterns.
5
+ *
6
+ * This class provides a reusable implementation for South Asian languages that share:
7
+ * - Indian-style number grouping: last 3 digits, then 2-2 (1,23,45,67,89)
8
+ * - Lakh (100,000), Crore (10,000,000), Arab (1,000,000,000) scale words
9
+ * - Standard negative and decimal handling (inherits AbstractLanguage decimal logic,
10
+ * including `usePerDigitDecimals` support when set by subclasses)
11
+ *
12
+ * Used by: Hindi (hi), Bengali (bn), Urdu (ur), Punjabi (pa), Marathi (mr), Gujarati (gu), Kannada (kn)
13
+ *
14
+ * Subclasses MUST define language-specific vocabulary via class properties:
15
+ * - `belowHundredWords` array with digit and teen words (0-99)
16
+ * - `hundredWord` string used inside `segmentToWords`
17
+ * - `scaleWords` array with grouping words (hazaar, lakh, crore, etc.) indexed by grouping level
18
+ * - `negativeWord`, `decimalSeparatorWord`, `zeroWord`, `wordSeparator`
19
+ *
20
+ * @abstract
21
+ * @extends AbstractLanguage
22
+ */
23
+ export class SouthAsianLanguage extends AbstractLanguage {
24
+ // ============================================================================
25
+ // Required Properties (subclasses must define)
26
+ // ============================================================================
27
+
28
+ /**
29
+ * Array of words for numbers 0-99 (digits and teens).
30
+ * Index directly: belowHundredWords[0] through belowHundredWords[99].
31
+ * @type {Array<string>}
32
+ */
33
+ belowHundredWords
34
+
35
+ /**
36
+ * Word for "hundred" in the language (e.g., 'सौ' in Hindi, 'শত' in Bengali).
37
+ * Used to construct hundreds (e.g., "1 hundred", "2 hundred").
38
+ * @type {string}
39
+ */
40
+ hundredWord
41
+
42
+ /**
43
+ * Array of scale words for Indian-style grouping (hazaar, lakh, crore, arab, etc.).
44
+ * Index 0 contains empty string (ones place has no scale word).
45
+ * Index 1 is for thousands, Index 2 for lakhs, Index 3 for crores, etc.
46
+ * @type {Array<string>}
47
+ */
48
+ scaleWords
49
+
50
+ // ============================================================================
51
+ // Protected Methods (subclasses may call or override)
52
+ // ============================================================================
53
+
54
+ /**
55
+ * Splits a number into Indian numbering system segments.
56
+ *
57
+ * The Indian system segments differently than Western (3-3-3) systems:
58
+ * - First segment (rightmost): Up to 3 digits (ones, tens, hundreds)
59
+ * - Subsequent segments: Exactly 2 digits each (thousands, lakhs, crores, etc.)
60
+ *
61
+ * This creates the familiar Indian comma pattern: 1,23,45,67,890
62
+ *
63
+ * @protected
64
+ * @param {bigint} integerPart The integer to split into segments.
65
+ * @returns {Array<number>} Array of segments from most significant to least significant.
66
+ *
67
+ * @example
68
+ * // splitToSegments(1234567n) → [12, 34, 567]
69
+ * // Reads as: 12 lakhs, 34 thousands, 567 units
70
+ * // splitToSegments(98765432n) → [9, 87, 65, 432]
71
+ * // Reads as: 9 crores, 87 lakhs, 65 thousands, 432 units
72
+ */
73
+ splitToSegments (integerPart) {
74
+ const numStr = integerPart.toString()
75
+
76
+ if (numStr.length <= 3) {
77
+ return [Number(numStr)]
78
+ }
79
+
80
+ const segments = []
81
+ const last3 = numStr.slice(-3)
82
+ segments.unshift(Number(last3))
83
+
84
+ let remaining = numStr.slice(0, -3)
85
+ while (remaining.length > 0) {
86
+ const segment = remaining.slice(-2)
87
+ segments.unshift(Number(segment))
88
+ remaining = remaining.slice(0, -2)
89
+ }
90
+
91
+ return segments
92
+ }
93
+
94
+ /**
95
+ * Converts a segment (0-999) to words.
96
+ *
97
+ * @protected
98
+ * @param {number} segmentValue Value between 0 and 999.
99
+ * @returns {string} Language-specific word representation.
100
+ */
101
+ segmentToWords (segmentValue) {
102
+ if (segmentValue === 0) return ''
103
+ if (segmentValue < 100) return this.belowHundredWords[segmentValue]
104
+
105
+ const hundreds = Math.trunc(segmentValue / 100)
106
+ const remainder = segmentValue % 100
107
+ const parts = []
108
+
109
+ if (hundreds === 1) {
110
+ parts.push(this.belowHundredWords[1] + ' ' + this.hundredWord)
111
+ } else {
112
+ parts.push(this.belowHundredWords[hundreds] + ' ' + this.hundredWord)
113
+ }
114
+
115
+ if (remainder > 0) {
116
+ parts.push(this.belowHundredWords[remainder])
117
+ }
118
+
119
+ return parts.join(' ')
120
+ }
121
+
122
+ // ============================================================================
123
+ // Public Methods
124
+ // ============================================================================
125
+
126
+ /**
127
+ * Converts integer to cardinal words using South Asian grouping.
128
+ *
129
+ * @param {bigint} integerPart Number to convert.
130
+ * @returns {string} Cardinal representation.
131
+ */
132
+ integerToWords (integerPart) {
133
+ if (integerPart === 0n) {
134
+ return this.zeroWord
135
+ }
136
+
137
+ const segments = this.splitToSegments(integerPart)
138
+ const segmentCount = segments.length
139
+ const words = []
140
+
141
+ for (let i = 0; i < segmentCount; i++) {
142
+ const segmentValue = segments[i]
143
+ if (segmentValue === 0) continue
144
+
145
+ const scaleIndex = segmentCount - i - 1
146
+ words.push(this.segmentToWords(segmentValue))
147
+ if (scaleIndex > 0 && this.scaleWords[scaleIndex]) {
148
+ words.push(this.scaleWords[scaleIndex])
149
+ }
150
+ }
151
+
152
+ return words.join(' ').trim()
153
+ }
154
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Base class for Turkic languages with shared grammar patterns.
3
+ *
4
+ * This class provides a reusable implementation for Turkic languages that share:
5
+ * - Space-separated number combinations
6
+ * - Implicit 'bir' (one) before hundreds and thousands
7
+ * - Simple multiplication/addition logic
8
+ * - Consistent magnitude handling
9
+ * - Inherits decimal handling from AbstractLanguage via GreedyScaleLanguage
10
+ * (supports both grouped and per-digit modes via the `usePerDigitDecimals` class property).
11
+ *
12
+ * Used by: Turkish (TR), Azerbaijani (AZ)
13
+ *
14
+ * Subclasses MUST define (from GreedyScaleLanguage requirements):
15
+ * - `scaleWords` array of [value, word] tuples as a class property (ordered descending by value).
16
+ * Optionally, language-specific class properties (e.g., `negativeWord`, `zeroWord`, `decimalSeparatorWord`, `wordSeparator`).
17
+ *
18
+ * TurkicLanguage provides a default `combineWordSets()` implementation; subclasses may override
19
+ * if specialized combine logic is needed (unlikely for Turkic languages).
20
+ *
21
+ * @abstract
22
+ * @extends GreedyScaleLanguage
23
+ */
24
+ export class TurkicLanguage extends GreedyScaleLanguage {
25
+ }
26
+ import { GreedyScaleLanguage } from './greedy-scale-language.js';
@@ -0,0 +1,59 @@
1
+ import { GreedyScaleLanguage } from './greedy-scale-language.js'
2
+
3
+ /**
4
+ * Base class for Turkic languages with shared grammar patterns.
5
+ *
6
+ * This class provides a reusable implementation for Turkic languages that share:
7
+ * - Space-separated number combinations
8
+ * - Implicit 'bir' (one) before hundreds and thousands
9
+ * - Simple multiplication/addition logic
10
+ * - Consistent magnitude handling
11
+ * - Inherits decimal handling from AbstractLanguage via GreedyScaleLanguage
12
+ * (supports both grouped and per-digit modes via the `usePerDigitDecimals` class property).
13
+ *
14
+ * Used by: Turkish (TR), Azerbaijani (AZ)
15
+ *
16
+ * Subclasses MUST define (from GreedyScaleLanguage requirements):
17
+ * - `scaleWords` array of [value, word] tuples as a class property (ordered descending by value).
18
+ * Optionally, language-specific class properties (e.g., `negativeWord`, `zeroWord`, `decimalSeparatorWord`, `wordSeparator`).
19
+ *
20
+ * TurkicLanguage provides a default `combineWordSets()` implementation; subclasses may override
21
+ * if specialized combine logic is needed (unlikely for Turkic languages).
22
+ *
23
+ * @abstract
24
+ * @extends GreedyScaleLanguage
25
+ */
26
+ export class TurkicLanguage extends GreedyScaleLanguage {
27
+ // ============================================================================
28
+ // Inherited Implementation (overrides GreedyScaleLanguage)
29
+ // ============================================================================
30
+
31
+ /**
32
+ * Combines two adjacent word-sets according to Turkic grammar rules.
33
+ *
34
+ * Shared Turkic patterns:
35
+ * - Implicit 'bir' (one) before hundreds and thousands
36
+ * - Space separator (wordSeparator property) for all combinations
37
+ * - Multiplies when following > preceding (crossing magnitude boundary)
38
+ * - Adds otherwise (combining same-magnitude components)
39
+ *
40
+ * @param {Object} preceding Preceding word-set as { word: bigint }.
41
+ * @param {Object} following Following word-set as { word: bigint }.
42
+ * @returns {Object} Combined word-set with merged word and resulting number.
43
+ */
44
+ combineWordSets (preceding, following) {
45
+ const [[precedingWord, precedingValue]] = Object.entries(preceding)
46
+ const [[followingWord, followingValue]] = Object.entries(following)
47
+
48
+ // Implicit 'bir' (one) before certain magnitudes:
49
+ // Omit '1' before hundreds (100n) and thousands (1000n) to form natural combinations
50
+ if (precedingValue === 1n && (followingValue <= 100n || followingValue === 1000n)) {
51
+ return following // Return just the magnitude word (e.g., "yüz", not "bir yüz")
52
+ }
53
+
54
+ // Combine numbers with space separator (wordSeparator from GreedyScaleLanguage):
55
+ // Multiply when crossing magnitude boundary, add otherwise
56
+ const resultNumber = followingValue > precedingValue ? precedingValue * followingValue : precedingValue + followingValue
57
+ return { [`${precedingWord}${this.wordSeparator}${followingWord}`]: resultNumber }
58
+ }
59
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Arabic language converter.
3
+ *
4
+ * Supports:
5
+ * - Gender agreement (masculine/feminine forms)
6
+ * - Complex pluralization (singular/dual/plural)
7
+ * - Traditional Arabic number naming conventions
8
+ * - Right-to-left text orientation
9
+ */
10
+ export class Arabic extends AbstractLanguage {
11
+ constructor(options?: {});
12
+ tensWords: string[];
13
+ hundredsWords: string[];
14
+ scaleWords: string[];
15
+ scaleAppendedWords: string[];
16
+ scalePluralWords: string[];
17
+ dualWords: string[];
18
+ dualAppendedWords: string[];
19
+ ones: {
20
+ masculine: string[];
21
+ feminine: string[];
22
+ };
23
+ /** Selects masculine or feminine number forms based on options. */
24
+ get selectedOnes(): string[];
25
+ /** Converts a 3-digit segment (0-999) to Arabic words with gender/plural rules. */
26
+ segmentToWords(groupNumber: any, groupLevel: any, fullNumber: any): string;
27
+ /** Converts integer part to Arabic words by processing 3-digit groups. */
28
+ integerToWords(integerPart: any): string;
29
+ }
30
+ import { AbstractLanguage } from '../classes/abstract-language.js';