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