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,178 @@
1
+ /**
2
+ * Abstract base class for language converters.
3
+ *
4
+ * This class provides the framework for converting numbers to words in any language.
5
+ * It handles the common conversion flow while delegating language-specific logic to subclasses.
6
+ *
7
+ * ## Responsibilities
8
+ *
9
+ * - Receives pre-validated and normalized input from the public API (n2words.js)
10
+ * - Handles negative number prefixing via `negativeWord`
11
+ * - Converts decimals via `decimalDigitsToWords()`, preserving leading zeros
12
+ * - Delegates integer conversion to `integerToWords()` (implemented by subclasses)
13
+ *
14
+ * ## Required Subclass Implementation
15
+ *
16
+ * Subclasses MUST provide:
17
+ * - `integerToWords(integerPart)` - Core conversion logic (abstract method)
18
+ * - `negativeWord` - Word preceding negative numbers (e.g., "minus"))
19
+ * - `zeroWord` - Word for the digit 0 (e.g., "zero")
20
+ * - `decimalSeparatorWord` - Word between whole and decimal parts (e.g., "point")
21
+ *
22
+ * ## Optional Overrides
23
+ *
24
+ * Subclasses MAY override:
25
+ * - `wordSeparator` - Character(s) between words (default: space, empty for CJK languages)
26
+ * - `usePerDigitDecimals` - Enable per-digit decimal mode (default: false)
27
+ * - `decimalIntegerToWords()` - Custom decimal conversion (e.g., Romanian masculine forms)
28
+ * - `decimalDigitsToWords()` - Complete decimal conversion override
29
+ * - `toWords()` - Override to capture integerPart for context-dependent rules (e.g., Czech)
30
+ *
31
+ * ## Input Contract
32
+ *
33
+ * Input validation and normalization happen at the public API boundary (n2words.js).
34
+ * This class assumes it receives clean, pre-processed data via `toWords()`.
35
+ *
36
+ * @abstract
37
+ */
38
+ export class AbstractLanguage {
39
+ /**
40
+ * Word that precedes negative numbers (e.g., "minus", "negative", "moins").
41
+ * @type {string}
42
+ */
43
+ negativeWord: string;
44
+ /**
45
+ * Word that separates integer and decimal parts (e.g., "point", "virgule", "comma").
46
+ * @type {string}
47
+ */
48
+ decimalSeparatorWord: string;
49
+ /**
50
+ * Word representation for the digit 0 (e.g., "zero", "zéro", "null").
51
+ * Used for zero values and leading zeros in decimals.
52
+ * @type {string}
53
+ */
54
+ zeroWord: string;
55
+ /**
56
+ * Character(s) used to separate words in the output.
57
+ *
58
+ * Defaults to a single space. Set to empty string for languages without
59
+ * word separators (e.g., Japanese, Thai, Chinese).
60
+ *
61
+ * @type {string}
62
+ */
63
+ wordSeparator: string;
64
+ /**
65
+ * Whether to convert decimal digits individually rather than grouped.
66
+ *
67
+ * - `false` (default): Leading zeros preserved, remaining digits grouped as a number
68
+ * - Example: "05" → ["zero", "five"], "14" → ["fourteen"]
69
+ * - Used by: English, Spanish, French, German, etc.
70
+ *
71
+ * - `true`: Each digit converted separately
72
+ * - Example: "05" → ["zero", "five"], "14" → ["one", "four"]
73
+ * - Used by: Japanese, Thai, Tamil, Telugu, Greek, Hebrew, Filipino
74
+ *
75
+ * @type {boolean}
76
+ */
77
+ usePerDigitDecimals: boolean;
78
+ /**
79
+ * Read-only access to options set via `setOptions()`.
80
+ * @type {Object}
81
+ */
82
+ get options(): Object;
83
+ /**
84
+ * Converts pre-normalized numeric components to words.
85
+ *
86
+ * This is the main entry point called by the public API (makeConverter in n2words.js).
87
+ * It assembles the final word representation from the provided components.
88
+ *
89
+ * **Caller contract (enforced by makeConverter):**
90
+ * - `integerPart` is a non-negative BigInt (>= 0n)
91
+ * - `decimalPart` is a string of digits only (no sign, no decimal point)
92
+ * - `isNegative` reflects the original input sign
93
+ *
94
+ * **Conversion flow:**
95
+ * 1. Prepend negative word if applicable
96
+ * 2. Convert integer part via `integerToWords()`
97
+ * 3. If decimals present: append separator and decimal words
98
+ * 4. Join all parts with `wordSeparator`
99
+ *
100
+ * Subclasses needing access to the integer part during decimal conversion
101
+ * (e.g., for context-dependent separator words) should override this
102
+ * method to cache the value before calling super.toWords().
103
+ *
104
+ * @public
105
+ * @param {boolean} isNegative Whether the original number was negative
106
+ * @param {bigint} integerPart The integer part (always non-negative)
107
+ * @param {string} [decimalPart] - Decimal digits if present (e.g., "14" for 3.14)
108
+ * @returns {string} The localized cardinal string
109
+ */
110
+ public toWords(isNegative: boolean, integerPart: bigint, decimalPart?: string): string;
111
+ /**
112
+ * Converts a BigInt integer part to its cardinal word representation.
113
+ *
114
+ * This is the core template method that subclasses MUST implement to provide
115
+ * language-specific number conversion logic.
116
+ *
117
+ * @abstract
118
+ * @param {bigint} integerPart The integer part to convert (always >= 0n)
119
+ * @returns {string} The cardinal representation in the target language
120
+ * @throws {Error} If not implemented by subclass
121
+ */
122
+ integerToWords(integerPart: bigint): string;
123
+ /**
124
+ * Sets options by merging language defaults with user-provided options.
125
+ *
126
+ * Merges defaults first, then user options (later keys override earlier ones).
127
+ * Stores the result in the private `#options` field, accessible via the
128
+ * read-only `options` getter.
129
+ *
130
+ * @protected
131
+ * @param {Object} [defaults={}] - Default option values for the language
132
+ * @param {Object} [userOptions={}] - Runtime options supplied by the caller
133
+ *
134
+ * @example
135
+ * constructor(options = {}) {
136
+ * super()
137
+ * this.setOptions({ gender: 'masculine' }, options)
138
+ * }
139
+ */
140
+ protected setOptions(defaults?: Object, userOptions?: Object): void;
141
+ /**
142
+ * Converts an integer to words in decimal context.
143
+ *
144
+ * By default, delegates to `integerToWords()`. Override this method
145
+ * when decimal conversion requires different behavior than integer conversion.
146
+ *
147
+ * Called with:
148
+ * - Single digits (0-9) when `usePerDigitDecimals = true`
149
+ * - Grouped numbers when `usePerDigitDecimals = false`
150
+ *
151
+ * **Use cases for overriding:**
152
+ * - Romanian: Decimals always use masculine forms regardless of gender option
153
+ * - Languages with different plural/gender rules for decimal vs integer parts
154
+ *
155
+ * @protected
156
+ * @param {bigint} integerPart The integer to convert (single digit or grouped)
157
+ * @returns {string} The word representation for use in decimal context
158
+ */
159
+ protected decimalIntegerToWords(integerPart: bigint): string;
160
+ /**
161
+ * Converts decimal fractional digits into an array of words.
162
+ *
163
+ * Leading zeros are always preserved individually. The remaining digits
164
+ * are converted based on `usePerDigitDecimals`:
165
+ *
166
+ * - `false` (default): Remaining digits grouped as a single number
167
+ * - "05" → ["zero", "five"], "14" → ["fourteen"]
168
+ *
169
+ * - `true`: Each remaining digit converted separately
170
+ * - "05" → ["zero", "five"], "14" → ["one", "four"]
171
+ *
172
+ * @protected
173
+ * @param {string} decimalPart Decimal digits as string (e.g., "05" for 3.05)
174
+ * @returns {string[]} Array of word tokens for the fractional part
175
+ */
176
+ protected decimalDigitsToWords(decimalPart: string): string[];
177
+ #private;
178
+ }
@@ -1,25 +1,55 @@
1
1
  /**
2
2
  * Abstract base class for language converters.
3
3
  *
4
- * What this class handles:
5
- * - Validates and normalizes caller input (`number | string | bigint`), rejecting NaN/invalid strings.
6
- * - Splits sign, whole, and decimal parts, caching the whole part for languages that need it.
7
- * - Delegates whole-number wording to `convertWholePart(wholeNumber)` implemented by subclasses.
8
- * - Converts decimals via `decimalDigitsToWords()`, preserving leading zeros and supporting per-digit mode when `convertDecimalsPerDigit` is true.
4
+ * This class provides the framework for converting numbers to words in any language.
5
+ * It handles the common conversion flow while delegating language-specific logic to subclasses.
9
6
  *
10
- * What subclasses must provide:
11
- * - `convertWholePart(wholeNumber)` method implementation (required; abstract).
12
- * - `negativeWord` class property (word preceding negative numbers, e.g., "minus").
13
- * - `zeroWord` class property (word for digit 0, e.g., "zero").
14
- * - `decimalSeparatorWord` class property (word between whole and decimal, e.g., "point").
15
- * - `wordSeparator` class property (word separator in output, typically a space).
16
- * Optional: override `convertDecimalsPerDigit`, `digits`, or `convertDigitToWord()` for custom behavior.
7
+ * ## Responsibilities
17
8
  *
18
- * This class stays minimal; language grammar lives in subclasses.
9
+ * - Receives pre-validated and normalized input from the public API (n2words.js)
10
+ * - Handles negative number prefixing via `negativeWord`
11
+ * - Converts decimals via `decimalDigitsToWords()`, preserving leading zeros
12
+ * - Delegates integer conversion to `integerToWords()` (implemented by subclasses)
13
+ *
14
+ * ## Required Subclass Implementation
15
+ *
16
+ * Subclasses MUST provide:
17
+ * - `integerToWords(integerPart)` - Core conversion logic (abstract method)
18
+ * - `negativeWord` - Word preceding negative numbers (e.g., "minus"))
19
+ * - `zeroWord` - Word for the digit 0 (e.g., "zero")
20
+ * - `decimalSeparatorWord` - Word between whole and decimal parts (e.g., "point")
21
+ *
22
+ * ## Optional Overrides
23
+ *
24
+ * Subclasses MAY override:
25
+ * - `wordSeparator` - Character(s) between words (default: space, empty for CJK languages)
26
+ * - `usePerDigitDecimals` - Enable per-digit decimal mode (default: false)
27
+ * - `decimalIntegerToWords()` - Custom decimal conversion (e.g., Romanian masculine forms)
28
+ * - `decimalDigitsToWords()` - Complete decimal conversion override
29
+ * - `toWords()` - Override to capture integerPart for context-dependent rules (e.g., Czech)
30
+ *
31
+ * ## Input Contract
32
+ *
33
+ * Input validation and normalization happen at the public API boundary (n2words.js).
34
+ * This class assumes it receives clean, pre-processed data via `toWords()`.
19
35
  *
20
36
  * @abstract
21
37
  */
22
- class AbstractLanguage {
38
+ export class AbstractLanguage {
39
+ // ============================================================================
40
+ // Private Fields
41
+ // ============================================================================
42
+
43
+ /**
44
+ * Private storage for options.
45
+ * @type {Object}
46
+ */
47
+ #options = {}
48
+
49
+ // ============================================================================
50
+ // Required Properties (subclasses must define)
51
+ // ============================================================================
52
+
23
53
  /**
24
54
  * Word that precedes negative numbers (e.g., "minus", "negative", "moins").
25
55
  * @type {string}
@@ -27,235 +57,212 @@ class AbstractLanguage {
27
57
  negativeWord = ''
28
58
 
29
59
  /**
30
- * Word that separates whole and decimal parts (e.g., "point", "virgule", "comma").
60
+ * Word that separates integer and decimal parts (e.g., "point", "virgule", "comma").
31
61
  * @type {string}
32
62
  */
33
63
  decimalSeparatorWord = ''
34
64
 
35
65
  /**
36
66
  * Word representation for the digit 0 (e.g., "zero", "zéro", "null").
67
+ * Used for zero values and leading zeros in decimals.
37
68
  * @type {string}
38
69
  */
39
70
  zeroWord = ''
40
71
 
72
+ // ============================================================================
73
+ // Optional Properties (subclasses may override)
74
+ // ============================================================================
75
+
41
76
  /**
42
- * Character(s) used to separate words in the output (typically a space).
77
+ * Character(s) used to separate words in the output.
78
+ *
79
+ * Defaults to a single space. Set to empty string for languages without
80
+ * word separators (e.g., Japanese, Thai, Chinese).
81
+ *
43
82
  * @type {string}
44
83
  */
45
84
  wordSeparator = ' '
46
85
 
47
- /**
48
- * Cached whole number portion from the most recent conversion.
49
- * Some languages need access to this value during conversion for
50
- * pluralization rules or special cases (e.g., Czech, Hebrew).
51
- * @type {bigint}
52
- */
53
- cachedWholeNumber = 0n
54
-
55
86
  /**
56
87
  * Whether to convert decimal digits individually rather than grouped.
57
- * - `true`: Each digit converted separately (e.g., "05" → "zero five")
58
- * - `false`: Leading zeros preserved, remaining grouped (e.g., "14" → "fourteen")
59
- * Used by languages like Japanese, Thai, Tamil, Telugu.
88
+ *
89
+ * - `false` (default): Leading zeros preserved, remaining digits grouped as a number
90
+ * - Example: "05" ["zero", "five"], "14" → ["fourteen"]
91
+ * - Used by: English, Spanish, French, German, etc.
92
+ *
93
+ * - `true`: Each digit converted separately
94
+ * - Example: "05" → ["zero", "five"], "14" → ["one", "four"]
95
+ * - Used by: Japanese, Thai, Tamil, Telugu, Greek, Hebrew, Filipino
96
+ *
60
97
  * @type {boolean}
61
98
  */
62
- convertDecimalsPerDigit = false
99
+ usePerDigitDecimals = false
100
+
101
+ // ============================================================================
102
+ // Public Accessors
103
+ // ============================================================================
63
104
 
64
105
  /**
65
- * Optional array of digit words for direct lookup in `convertDigitToWord()`.
66
- * - Length 10: indices 0–9 map directly to digit words
67
- * - Length 9: indices 0–8 map to words for digits 1–9
68
- * - `null`: Falls back to `convertWholePart()` for digit conversion
69
- * @type {string[]|null}
106
+ * Read-only access to options set via `setOptions()`.
107
+ * @type {Object}
70
108
  */
71
- digits = null
109
+ get options () {
110
+ return this.#options
111
+ }
112
+
113
+ // ============================================================================
114
+ // Public Methods
115
+ // ============================================================================
72
116
 
73
117
  /**
74
- * Convert a single decimal digit (0-9) to its word representation.
118
+ * Converts pre-normalized numeric components to words.
75
119
  *
76
- * Default behavior:
77
- * - 0 returns the language's `zeroWord`
78
- * - If a `digits` array is present, use it for direct lookup
79
- * - Length 10: indices 0–9 map directly
80
- * - Length 9: indices 1–9 map via `idx - 1`
81
- * - Otherwise delegate to `convertWholePart(digit)`
120
+ * This is the main entry point called by the public API (makeConverter in n2words.js).
121
+ * It assembles the final word representation from the provided components.
82
122
  *
83
- * Subclasses may override this for custom logic.
123
+ * **Caller contract (enforced by makeConverter):**
124
+ * - `integerPart` is a non-negative BigInt (>= 0n)
125
+ * - `decimalPart` is a string of digits only (no sign, no decimal point)
126
+ * - `isNegative` reflects the original input sign
84
127
  *
85
- * @protected
86
- * @param {bigint} digit A single digit value (0-9) as BigInt
87
- * @returns {string} The word representation of the digit
128
+ * **Conversion flow:**
129
+ * 1. Prepend negative word if applicable
130
+ * 2. Convert integer part via `integerToWords()`
131
+ * 3. If decimals present: append separator and decimal words
132
+ * 4. Join all parts with `wordSeparator`
133
+ *
134
+ * Subclasses needing access to the integer part during decimal conversion
135
+ * (e.g., for context-dependent separator words) should override this
136
+ * method to cache the value before calling super.toWords().
137
+ *
138
+ * @public
139
+ * @param {boolean} isNegative Whether the original number was negative
140
+ * @param {bigint} integerPart The integer part (always non-negative)
141
+ * @param {string} [decimalPart] - Decimal digits if present (e.g., "14" for 3.14)
142
+ * @returns {string} The localized cardinal string
88
143
  */
89
- convertDigitToWord (digit) {
90
- const idx = Number(digit)
91
- if (idx === 0) return this.zeroWord
144
+ toWords (isNegative, integerPart, decimalPart) {
145
+ const words = []
92
146
 
93
- if (Array.isArray(this.digits)) {
94
- if (this.digits.length === 10) {
95
- return this.digits[idx] ?? this.zeroWord
96
- }
97
- if (this.digits.length === 9) {
98
- return this.digits[idx - 1] ?? this.zeroWord
99
- }
147
+ if (isNegative) words.push(this.negativeWord)
148
+
149
+ words.push(this.integerToWords(integerPart))
150
+
151
+ if (decimalPart) {
152
+ words.push(this.decimalSeparatorWord)
153
+ words.push(...this.decimalDigitsToWords(decimalPart))
100
154
  }
101
155
 
102
- return this.convertWholePart(digit)
156
+ return words.join(this.wordSeparator)
103
157
  }
104
158
 
159
+ // ============================================================================
160
+ // Abstract Methods (subclasses must implement)
161
+ // ============================================================================
162
+
105
163
  /**
106
- * Convert the decimal fractional digits into words.
164
+ * Converts a BigInt integer part to its cardinal word representation.
107
165
  *
108
- * Behavior depends on the `convertDecimalsPerDigit` class property:
166
+ * This is the core template method that subclasses MUST implement to provide
167
+ * language-specific number conversion logic.
109
168
  *
110
- * **Per-digit mode** (`convertDecimalsPerDigit: true`):
111
- * - Each decimal digit is converted individually using `convertDigitToWord()`
112
- * - Example: "05" -> [zero, 'five'], "14" -> ['one', 'four']
113
- * - Used by: Japanese, Thai, Tamil, Telugu
169
+ * @abstract
170
+ * @param {bigint} integerPart The integer part to convert (always >= 0n)
171
+ * @returns {string} The cardinal representation in the target language
172
+ * @throws {Error} If not implemented by subclass
173
+ */
174
+ integerToWords (integerPart) {
175
+ throw new Error('integerToWords() must be implemented by subclass')
176
+ }
177
+
178
+ // ============================================================================
179
+ // Protected Methods (subclasses may override or call)
180
+ // ============================================================================
181
+
182
+ /**
183
+ * Sets options by merging language defaults with user-provided options.
114
184
  *
115
- * **Grouped mode** (default, `convertDecimalsPerDigit: false`):
116
- * - Leading zeros are preserved as individual `zeroWord` entries
117
- * - Remaining digits are grouped and converted as a number
118
- * - Example: "05" -> [zero, 'five'], "14" -> ['fourteen']
119
- * - Used by: Most languages (English, Spanish, French, etc.)
185
+ * Merges defaults first, then user options (later keys override earlier ones).
186
+ * Stores the result in the private `#options` field, accessible via the
187
+ * read-only `options` getter.
120
188
  *
121
189
  * @protected
122
- * @param {string} decimalString The decimal digits as a string (e.g. `'05'` for 3.05).
123
- * @returns {Array<string>} Array of word tokens representing the fractional part.
190
+ * @param {Object} [defaults={}] - Default option values for the language
191
+ * @param {Object} [userOptions={}] - Runtime options supplied by the caller
124
192
  *
125
193
  * @example
126
- * // Per-digit mode
127
- * decimalDigitsToWords('05'); // -> [this.zeroWord, 'five']
128
- * decimalDigitsToWords('14'); // -> ['one', 'four']
129
- *
130
- * // Grouped mode
131
- * decimalDigitsToWords('05'); // -> [this.zeroWord, 'five']
132
- * decimalDigitsToWords('14'); // -> ['fourteen']
194
+ * constructor(options = {}) {
195
+ * super()
196
+ * this.setOptions({ gender: 'masculine' }, options)
197
+ * }
133
198
  */
134
- decimalDigitsToWords (decimalString) {
135
- const words = []
136
- const len = decimalString.length
137
-
138
- if (this.convertDecimalsPerDigit) {
139
- for (let i = 0; i < len; i++) {
140
- const decimalDigit = BigInt(decimalString[i])
141
- words.push(this.convertDigitToWord(decimalDigit))
142
- }
143
- return words
199
+ setOptions (defaults = {}, userOptions = {}) {
200
+ this.#options = {
201
+ ...defaults,
202
+ ...userOptions
144
203
  }
204
+ }
145
205
 
146
- // Default grouped-decimal behavior with leading zero preservation
147
- let i = 0
148
- while (i < len && decimalString[i] === '0') {
149
- words.push(this.zeroWord)
150
- i++
151
- }
152
-
153
- if (i === len) return words
154
-
155
- const remainingDigits = decimalString.slice(i)
156
- words.push(this.convertWholePart(BigInt(remainingDigits)))
157
-
158
- return words
206
+ /**
207
+ * Converts an integer to words in decimal context.
208
+ *
209
+ * By default, delegates to `integerToWords()`. Override this method
210
+ * when decimal conversion requires different behavior than integer conversion.
211
+ *
212
+ * Called with:
213
+ * - Single digits (0-9) when `usePerDigitDecimals = true`
214
+ * - Grouped numbers when `usePerDigitDecimals = false`
215
+ *
216
+ * **Use cases for overriding:**
217
+ * - Romanian: Decimals always use masculine forms regardless of gender option
218
+ * - Languages with different plural/gender rules for decimal vs integer parts
219
+ *
220
+ * @protected
221
+ * @param {bigint} integerPart The integer to convert (single digit or grouped)
222
+ * @returns {string} The word representation for use in decimal context
223
+ */
224
+ decimalIntegerToWords (integerPart) {
225
+ return this.integerToWords(integerPart)
159
226
  }
160
227
 
161
228
  /**
162
- * Convert a numeric input into its language cardinal representation.
229
+ * Converts decimal fractional digits into an array of words.
163
230
  *
164
- * This is the public entry point used by consumers. It normalizes the input
165
- * (accepting `number | string | bigint`), validates it, splits sign and
166
- * fractional parts, and delegates whole-number conversion to
167
- * `convertWholePart(BigInt)` and fractional conversion to `decimalDigitsToWords()`.
231
+ * Leading zeros are always preserved individually. The remaining digits
232
+ * are converted based on `usePerDigitDecimals`:
168
233
  *
169
- * Errors and validation:
170
- * - Passing `NaN` (as `number`) throws `TypeError`.
171
- * - Passing a non-numeric `string` throws `Error`.
172
- * - Passing an unsupported type throws `TypeError`.
234
+ * - `false` (default): Remaining digits grouped as a single number
235
+ * - "05" ["zero", "five"], "14" → ["fourteen"]
173
236
  *
174
- * @public
175
- * @param {number|string|bigint} value Numeric input to convert. Strings may include a single `.` decimal marker.
176
- * @returns {string} The localized cardinal string.
177
- * @throws {TypeError|Error} For invalid input as described above.
237
+ * - `true`: Each remaining digit converted separately
238
+ * - "05" ["zero", "five"], "14" ["one", "four"]
239
+ *
240
+ * @protected
241
+ * @param {string} decimalPart Decimal digits as string (e.g., "05" for 3.05)
242
+ * @returns {string[]} Array of word tokens for the fractional part
178
243
  */
179
- convertToWords (value) {
180
- // Normalize and validate input
181
- const inputType = typeof value
244
+ decimalDigitsToWords (decimalPart) {
245
+ const words = []
182
246
 
183
- if (inputType === 'number') {
184
- if (Number.isNaN(value)) throw new TypeError('NaN is not an accepted number.')
185
- value = value.toString()
186
- } else if (inputType === 'string') {
187
- value = value.trim()
188
- if (value.length === 0 || Number.isNaN(Number(value))) { throw new Error('Invalid number format: "' + value + '"') }
189
- } else if (inputType !== 'bigint') {
190
- throw new TypeError(
191
- 'Invalid variable type, expected number|string|bigint, received: ' + inputType
192
- )
247
+ // Always preserve leading zeros individually
248
+ let i = 0
249
+ while (i < decimalPart.length && decimalPart[i] === '0') {
250
+ words.push(this.zeroWord)
251
+ i++
193
252
  }
194
253
 
195
- const words = []
254
+ const remainder = decimalPart.slice(i)
255
+ if (!remainder) return words
196
256
 
197
- // Detect negativity and strip sign for further processing.
198
- // Must check before type coercion for BigInt (comparison works but slice does not).
199
- let isNegative = false
200
- if (inputType === 'bigint') {
201
- if (value < 0n) {
202
- isNegative = true
203
- value = -value
204
- }
205
- } else {
206
- // For strings, check first character before normalization
207
- if (value[0] === '-') {
208
- isNegative = true
209
- value = value.slice(1)
257
+ // Convert remainder: per-digit or as single number
258
+ if (this.usePerDigitDecimals) {
259
+ for (const char of remainder) {
260
+ words.push(this.decimalIntegerToWords(BigInt(char)))
210
261
  }
211
- }
212
-
213
- // Extract whole and decimal parts based on type.
214
- // BigInt has no decimal point; strings may contain a single '.'.
215
- // Default whole part to '0' if empty (e.g., '.5' -> '0' + '.5')
216
- let wholeNumber
217
- let decimalPart
218
- if (inputType === 'bigint') {
219
- wholeNumber = value
220
262
  } else {
221
- const decimalPointIndex = value.indexOf('.')
222
- if (decimalPointIndex === -1) {
223
- wholeNumber = BigInt(value)
224
- } else {
225
- const wholePartString = value.slice(0, decimalPointIndex) || '0'
226
- wholeNumber = BigInt(wholePartString)
227
- decimalPart = value.slice(decimalPointIndex + 1)
228
- }
229
- }
230
-
231
- this.cachedWholeNumber = wholeNumber
232
-
233
- if (isNegative) words.push(this.negativeWord)
234
-
235
- words.push(this.convertWholePart(wholeNumber))
236
-
237
- // Append decimal portion if present (separator + fractional digits)
238
- if (decimalPart) {
239
- words.push(this.decimalSeparatorWord)
240
- words.push(...this.decimalDigitsToWords(decimalPart))
263
+ words.push(this.decimalIntegerToWords(BigInt(remainder)))
241
264
  }
242
265
 
243
- return words.join(this.wordSeparator)
244
- }
245
-
246
- /**
247
- * Convert a BigInt whole number to its cardinal word representation.
248
- *
249
- * This is a template method that subclasses MUST implement to provide
250
- * language-specific number conversion logic.
251
- *
252
- * @abstract
253
- * @param {bigint} wholeNumber The whole number part to convert
254
- * @returns {string} The cardinal representation in the target language
255
- */
256
- convertWholePart (wholeNumber) {
257
- throw new Error('convertWholePart() must be implemented by subclass')
266
+ return words
258
267
  }
259
268
  }
260
-
261
- export default AbstractLanguage