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
@@ -1,54 +1,178 @@
1
- export default AbstractLanguage;
2
1
  /**
3
- * Creates new common language class that processes decimals separately.
4
- * Requires implementing `toCardinal`.
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
5
37
  */
6
- declare class AbstractLanguage {
38
+ export class AbstractLanguage {
7
39
  /**
8
- * @param {object} options Options for class.
9
- * @param {string} [options.negativeWord] Word that precedes a negative number (if any).
10
- * @param {string} options.separatorWord Word that separates cardinal numbers (i.e. "and").
11
- * @param {string} options.zero Word for 0 (i.e. "zero").
12
- * @param {string} [options.spaceSeparator] Character that separates words.
40
+ * Word that precedes negative numbers (e.g., "minus", "negative", "moins").
41
+ * @type {string}
13
42
  */
14
- constructor(options: {
15
- negativeWord?: string;
16
- separatorWord: string;
17
- zero: string;
18
- spaceSeparator?: string;
19
- });
43
+ negativeWord: string;
20
44
  /**
21
- * @returns {string} Word that precedes a negative number (if any).
45
+ * Word that separates integer and decimal parts (e.g., "point", "virgule", "comma").
46
+ * @type {string}
22
47
  */
23
- get negativeWord(): string;
48
+ decimalSeparatorWord: string;
24
49
  /**
25
- * @returns {string} Word that separates cardinal numbers (i.e. "and").
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}
26
53
  */
27
- get separatorWord(): string;
54
+ zeroWord: string;
28
55
  /**
29
- * @returns {string} Word for 0 (i.e. "zero").
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}
30
62
  */
31
- get zero(): string;
63
+ wordSeparator: string;
32
64
  /**
33
- * @returns {string} Character that separates words.
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}
34
76
  */
35
- get spaceSeparator(): string;
77
+ usePerDigitDecimals: boolean;
36
78
  /**
37
- * @returns {number} Input value without decimal.
79
+ * Read-only access to options set via `setOptions()`.
80
+ * @type {Object}
38
81
  */
39
- get wholeNumber(): number;
82
+ get options(): Object;
40
83
  /**
41
- * Convert ONLY decimal portion of number (processing leading zeros) to a string array of cardinal numbers.
42
- * @param {string} decimal Decimal string to convert.
43
- * @returns {string} Value in written format.
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
44
109
  */
45
- decimalToCardinal(decimal: string): string;
110
+ public toWords(isNegative: boolean, integerPart: bigint, decimalPart?: string): string;
46
111
  /**
47
- * Convert a number to cardinal form.
48
- * @param {number|string|bigint} value Number to be convert.
49
- * @returns {string} Value in written format.
50
- * @throws {Error} Value must be a valid number.
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
51
121
  */
52
- floatToCardinal(value: number | string | bigint): string;
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[];
53
177
  #private;
54
178
  }
@@ -1,160 +1,268 @@
1
1
  /**
2
- * Creates new common language class that processes decimals separately.
3
- * Requires implementing `toCardinal`.
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
4
37
  */
5
- class AbstractLanguage {
6
- #negativeWord;
7
- #separatorWord;
8
- #zero;
9
- #spaceSeparator;
10
- #wholeNumber;
38
+ export class AbstractLanguage {
39
+ // ============================================================================
40
+ // Private Fields
41
+ // ============================================================================
11
42
 
12
43
  /**
13
- * @param {object} options Options for class.
14
- * @param {string} [options.negativeWord] Word that precedes a negative number (if any).
15
- * @param {string} options.separatorWord Word that separates cardinal numbers (i.e. "and").
16
- * @param {string} options.zero Word for 0 (i.e. "zero").
17
- * @param {string} [options.spaceSeparator] Character that separates words.
44
+ * Private storage for options.
45
+ * @type {Object}
18
46
  */
19
- constructor(options) {
20
- // Merge supplied options with defaults
21
- options = Object.assign({
22
- negativeWord: '',
23
- separatorWord: '',
24
- zero: '',
25
- spaceSeparator: ' '
26
- }, options);
27
-
28
- // Make options available to class
29
- this.#negativeWord = options.negativeWord;
30
- this.#separatorWord = options.separatorWord;
31
- this.#zero = options.zero;
32
- this.#spaceSeparator = options.spaceSeparator;
33
- }
47
+ #options = {}
48
+
49
+ // ============================================================================
50
+ // Required Properties (subclasses must define)
51
+ // ============================================================================
34
52
 
35
53
  /**
36
- * @returns {string} Word that precedes a negative number (if any).
54
+ * Word that precedes negative numbers (e.g., "minus", "negative", "moins").
55
+ * @type {string}
37
56
  */
38
- get negativeWord() {
39
- return this.#negativeWord;
40
- }
57
+ negativeWord = ''
41
58
 
42
59
  /**
43
- * @returns {string} Word that separates cardinal numbers (i.e. "and").
60
+ * Word that separates integer and decimal parts (e.g., "point", "virgule", "comma").
61
+ * @type {string}
44
62
  */
45
- get separatorWord() {
46
- return this.#separatorWord;
47
- }
63
+ decimalSeparatorWord = ''
48
64
 
49
65
  /**
50
- * @returns {string} Word for 0 (i.e. "zero").
66
+ * Word representation for the digit 0 (e.g., "zero", "zéro", "null").
67
+ * Used for zero values and leading zeros in decimals.
68
+ * @type {string}
51
69
  */
52
- get zero() {
53
- return this.#zero;
54
- }
70
+ zeroWord = ''
71
+
72
+ // ============================================================================
73
+ // Optional Properties (subclasses may override)
74
+ // ============================================================================
55
75
 
56
76
  /**
57
- * @returns {string} Character that separates words.
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
+ *
82
+ * @type {string}
58
83
  */
59
- get spaceSeparator() {
60
- return this.#spaceSeparator;
61
- }
84
+ wordSeparator = ' '
62
85
 
63
86
  /**
64
- * @returns {number} Input value without decimal.
87
+ * Whether to convert decimal digits individually rather than grouped.
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
+ *
97
+ * @type {boolean}
65
98
  */
66
- get wholeNumber() {
67
- return this.#wholeNumber;
99
+ usePerDigitDecimals = false
100
+
101
+ // ============================================================================
102
+ // Public Accessors
103
+ // ============================================================================
104
+
105
+ /**
106
+ * Read-only access to options set via `setOptions()`.
107
+ * @type {Object}
108
+ */
109
+ get options () {
110
+ return this.#options
68
111
  }
69
112
 
113
+ // ============================================================================
114
+ // Public Methods
115
+ // ============================================================================
116
+
70
117
  /**
71
- * Convert ONLY decimal portion of number (processing leading zeros) to a string array of cardinal numbers.
72
- * @param {string} decimal Decimal string to convert.
73
- * @returns {string} Value in written format.
118
+ * Converts pre-normalized numeric components to words.
119
+ *
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.
122
+ *
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
127
+ *
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
74
143
  */
75
- decimalToCardinal(decimal) {
76
- const words = [];
144
+ toWords (isNegative, integerPart, decimalPart) {
145
+ const words = []
77
146
 
78
- // Split decimal string into an array of characters
79
- const chars = [...decimal];
147
+ if (isNegative) words.push(this.negativeWord)
80
148
 
81
- // Loop through characters adding leading zeros to words array
82
- let index = 0;
83
- while (index < chars.length && chars[index] === '0') {
84
- words.push(this.zero);
85
- index++;
86
- }
149
+ words.push(this.integerToWords(integerPart))
87
150
 
88
- // Prevent further processing if entire string was zeros
89
- if (index === chars.length) {
90
- return words;
151
+ if (decimalPart) {
152
+ words.push(this.decimalSeparatorWord)
153
+ words.push(...this.decimalDigitsToWords(decimalPart))
91
154
  }
92
155
 
93
- // Convert and add remaining then return words array
94
- return [...words, this.toCardinal(BigInt(decimal))];
156
+ return words.join(this.wordSeparator)
95
157
  }
96
158
 
159
+ // ============================================================================
160
+ // Abstract Methods (subclasses must implement)
161
+ // ============================================================================
162
+
97
163
  /**
98
- * Convert a number to cardinal form.
99
- * @param {number|string|bigint} value Number to be convert.
100
- * @returns {string} Value in written format.
101
- * @throws {Error} Value must be a valid number.
164
+ * Converts a BigInt integer part to its cardinal word representation.
165
+ *
166
+ * This is the core template method that subclasses MUST implement to provide
167
+ * language-specific number conversion logic.
168
+ *
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
102
173
  */
103
- floatToCardinal(value) {
104
- // Validate user input value and convert to string (excluding BigInt)
105
- if (typeof value == 'number') {
106
- if (Number.isNaN(value)) {
107
- throw new TypeError('NaN is not an accepted number.');
108
- }
109
- value = value.toString();
110
- } else if (typeof value == 'string') {
111
- value = value.trim();
112
- if (value.length === 0 || Number.isNaN(Number(value))) {
113
- throw new Error('"' + value + '" is not a valid number.');
114
- }
115
- } else if (typeof value != 'bigint') {
116
- throw new TypeError('Invalid variable type: ' + typeof value);
117
- }
118
-
119
- let words = [];
120
- let wholeNumber;
121
- let decimalNumber;
174
+ integerToWords (integerPart) {
175
+ throw new Error('integerToWords() must be implemented by subclass')
176
+ }
122
177
 
123
- // If negative number add negative word
124
- if (value < 0) {
125
- words.push(this.negativeWord);
126
- }
178
+ // ============================================================================
179
+ // Protected Methods (subclasses may override or call)
180
+ // ============================================================================
127
181
 
128
- // Split value decimal (if any) then convert to BigInt
129
- if (typeof value == 'bigint') {
130
- wholeNumber = value;
131
- } else {
132
- const splitValue = value.split('.');
133
- wholeNumber = BigInt(splitValue[0]);
134
- decimalNumber = splitValue[1];
182
+ /**
183
+ * Sets options by merging language defaults with user-provided options.
184
+ *
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.
188
+ *
189
+ * @protected
190
+ * @param {Object} [defaults={}] - Default option values for the language
191
+ * @param {Object} [userOptions={}] - Runtime options supplied by the caller
192
+ *
193
+ * @example
194
+ * constructor(options = {}) {
195
+ * super()
196
+ * this.setOptions({ gender: 'masculine' }, options)
197
+ * }
198
+ */
199
+ setOptions (defaults = {}, userOptions = {}) {
200
+ this.#options = {
201
+ ...defaults,
202
+ ...userOptions
135
203
  }
204
+ }
136
205
 
137
- // Convert whole number to positive (if negative)
138
- if (wholeNumber < 0) {
139
- wholeNumber = -wholeNumber;
140
- }
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)
226
+ }
141
227
 
142
- // NOTE: Only needed for CZ
143
- this.#wholeNumber = wholeNumber;
228
+ /**
229
+ * Converts decimal fractional digits into an array of words.
230
+ *
231
+ * Leading zeros are always preserved individually. The remaining digits
232
+ * are converted based on `usePerDigitDecimals`:
233
+ *
234
+ * - `false` (default): Remaining digits grouped as a single number
235
+ * - "05" → ["zero", "five"], "14" → ["fourteen"]
236
+ *
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
243
+ */
244
+ decimalDigitsToWords (decimalPart) {
245
+ const words = []
144
246
 
145
- // Add whole number in written form
146
- words = [...words, this.toCardinal(wholeNumber)];
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++
252
+ }
147
253
 
148
- // Add decimal number in written form (if any)
149
- if (decimalNumber) {
150
- words.push(this.separatorWord);
254
+ const remainder = decimalPart.slice(i)
255
+ if (!remainder) return words
151
256
 
152
- words = [...words, ...this.decimalToCardinal(decimalNumber)];
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)))
261
+ }
262
+ } else {
263
+ words.push(this.decimalIntegerToWords(BigInt(remainder)))
153
264
  }
154
265
 
155
- // Join words with spaces
156
- return words.join(this.spaceSeparator);
266
+ return words
157
267
  }
158
268
  }
159
-
160
- export default AbstractLanguage;