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.
- package/LICENSE +1 -1
- package/README.md +317 -59
- package/dist/ArabicConverter.js +3 -0
- package/dist/ArabicConverter.js.map +1 -0
- package/dist/AzerbaijaniConverter.js +3 -0
- package/dist/AzerbaijaniConverter.js.map +1 -0
- package/dist/BanglaConverter.js +3 -0
- package/dist/BanglaConverter.js.map +1 -0
- package/dist/BiblicalHebrewConverter.js +3 -0
- package/dist/BiblicalHebrewConverter.js.map +1 -0
- package/dist/CroatianConverter.js +3 -0
- package/dist/CroatianConverter.js.map +1 -0
- package/dist/CzechConverter.js +3 -0
- package/dist/CzechConverter.js.map +1 -0
- package/dist/DanishConverter.js +3 -0
- package/dist/DanishConverter.js.map +1 -0
- package/dist/DutchConverter.js +3 -0
- package/dist/DutchConverter.js.map +1 -0
- package/dist/EnglishConverter.js +3 -0
- package/dist/EnglishConverter.js.map +1 -0
- package/dist/FilipinoConverter.js +3 -0
- package/dist/FilipinoConverter.js.map +1 -0
- package/dist/FrenchBelgiumConverter.js +3 -0
- package/dist/FrenchBelgiumConverter.js.map +1 -0
- package/dist/FrenchConverter.js +3 -0
- package/dist/FrenchConverter.js.map +1 -0
- package/dist/GermanConverter.js +3 -0
- package/dist/GermanConverter.js.map +1 -0
- package/dist/GreekConverter.js +3 -0
- package/dist/GreekConverter.js.map +1 -0
- package/dist/GujaratiConverter.js +3 -0
- package/dist/GujaratiConverter.js.map +1 -0
- package/dist/HebrewConverter.js +3 -0
- package/dist/HebrewConverter.js.map +1 -0
- package/dist/HindiConverter.js +3 -0
- package/dist/HindiConverter.js.map +1 -0
- package/dist/HungarianConverter.js +3 -0
- package/dist/HungarianConverter.js.map +1 -0
- package/dist/IndonesianConverter.js +3 -0
- package/dist/IndonesianConverter.js.map +1 -0
- package/dist/ItalianConverter.js +3 -0
- package/dist/ItalianConverter.js.map +1 -0
- package/dist/JapaneseConverter.js +3 -0
- package/dist/JapaneseConverter.js.map +1 -0
- package/dist/KannadaConverter.js +3 -0
- package/dist/KannadaConverter.js.map +1 -0
- package/dist/KoreanConverter.js +3 -0
- package/dist/KoreanConverter.js.map +1 -0
- package/dist/LatvianConverter.js +3 -0
- package/dist/LatvianConverter.js.map +1 -0
- package/dist/LithuanianConverter.js +3 -0
- package/dist/LithuanianConverter.js.map +1 -0
- package/dist/MalayConverter.js +3 -0
- package/dist/MalayConverter.js.map +1 -0
- package/dist/MarathiConverter.js +3 -0
- package/dist/MarathiConverter.js.map +1 -0
- package/dist/NorwegianBokmalConverter.js +3 -0
- package/dist/NorwegianBokmalConverter.js.map +1 -0
- package/dist/PersianConverter.js +3 -0
- package/dist/PersianConverter.js.map +1 -0
- package/dist/PolishConverter.js +3 -0
- package/dist/PolishConverter.js.map +1 -0
- package/dist/PortugueseConverter.js +3 -0
- package/dist/PortugueseConverter.js.map +1 -0
- package/dist/PunjabiConverter.js +3 -0
- package/dist/PunjabiConverter.js.map +1 -0
- package/dist/RomanianConverter.js +3 -0
- package/dist/RomanianConverter.js.map +1 -0
- package/dist/RussianConverter.js +3 -0
- package/dist/RussianConverter.js.map +1 -0
- package/dist/SerbianCyrillicConverter.js +3 -0
- package/dist/SerbianCyrillicConverter.js.map +1 -0
- package/dist/SerbianLatinConverter.js +3 -0
- package/dist/SerbianLatinConverter.js.map +1 -0
- package/dist/SimplifiedChineseConverter.js +3 -0
- package/dist/SimplifiedChineseConverter.js.map +1 -0
- package/dist/SpanishConverter.js +3 -0
- package/dist/SpanishConverter.js.map +1 -0
- package/dist/SwahiliConverter.js +3 -0
- package/dist/SwahiliConverter.js.map +1 -0
- package/dist/SwedishConverter.js +3 -0
- package/dist/SwedishConverter.js.map +1 -0
- package/dist/TamilConverter.js +3 -0
- package/dist/TamilConverter.js.map +1 -0
- package/dist/TeluguConverter.js +3 -0
- package/dist/TeluguConverter.js.map +1 -0
- package/dist/ThaiConverter.js +3 -0
- package/dist/ThaiConverter.js.map +1 -0
- package/dist/TraditionalChineseConverter.js +3 -0
- package/dist/TraditionalChineseConverter.js.map +1 -0
- package/dist/TurkishConverter.js +3 -0
- package/dist/TurkishConverter.js.map +1 -0
- package/dist/UkrainianConverter.js +3 -0
- package/dist/UkrainianConverter.js.map +1 -0
- package/dist/UrduConverter.js +3 -0
- package/dist/UrduConverter.js.map +1 -0
- package/dist/VietnameseConverter.js +3 -0
- package/dist/VietnameseConverter.js.map +1 -0
- package/dist/n2words.js +3 -2
- package/dist/n2words.js.map +1 -1
- package/lib/classes/abstract-language.d.ts +158 -34
- package/lib/classes/abstract-language.js +223 -115
- package/lib/classes/greedy-scale-language.d.ts +109 -0
- package/lib/classes/greedy-scale-language.js +201 -0
- package/lib/classes/slavic-language.d.ts +148 -0
- package/lib/classes/slavic-language.js +281 -0
- package/lib/classes/south-asian-language.d.ts +70 -0
- package/lib/classes/south-asian-language.js +154 -0
- package/lib/classes/turkic-language.d.ts +26 -0
- package/lib/classes/turkic-language.js +59 -0
- package/lib/languages/ar.d.ts +30 -0
- package/lib/languages/ar.js +159 -0
- package/lib/languages/az.d.ts +12 -0
- package/lib/languages/az.js +42 -0
- package/lib/languages/bn.d.ts +11 -0
- package/lib/languages/bn.js +131 -0
- package/lib/languages/cs.d.ts +88 -0
- package/lib/languages/cs.js +143 -0
- package/lib/languages/da.d.ts +15 -0
- package/lib/languages/da.js +120 -0
- package/lib/languages/de.d.ts +14 -0
- package/lib/languages/de.js +101 -0
- package/lib/languages/el.d.ts +14 -0
- package/lib/languages/el.js +90 -0
- package/lib/languages/en.d.ts +16 -0
- package/lib/languages/en.js +86 -0
- package/lib/languages/es.d.ts +15 -0
- package/lib/languages/es.js +121 -0
- package/lib/languages/fa.d.ts +47 -0
- package/lib/languages/fa.js +144 -0
- package/lib/languages/fil.d.ts +16 -0
- package/lib/languages/fil.js +121 -0
- package/lib/languages/fr-BE.d.ts +11 -0
- package/lib/languages/fr-BE.js +25 -0
- package/lib/languages/fr.d.ts +15 -0
- package/lib/languages/fr.js +106 -0
- package/lib/languages/gu.d.ts +11 -0
- package/lib/languages/gu.js +132 -0
- package/lib/languages/hbo.d.ts +113 -0
- package/lib/languages/hbo.js +251 -0
- package/lib/languages/he.d.ts +80 -0
- package/lib/languages/he.js +206 -0
- package/lib/languages/hi.d.ts +11 -0
- package/lib/languages/hi.js +131 -0
- package/lib/languages/hr.d.ts +80 -0
- package/lib/languages/hr.js +113 -0
- package/lib/languages/hu.d.ts +22 -0
- package/lib/languages/hu.js +137 -0
- package/lib/languages/id.d.ts +37 -0
- package/lib/languages/id.js +159 -0
- package/lib/languages/it.d.ts +37 -0
- package/lib/languages/it.js +132 -0
- package/lib/languages/ja.d.ts +17 -0
- package/lib/languages/ja.js +137 -0
- package/lib/languages/kn.d.ts +11 -0
- package/lib/languages/kn.js +42 -0
- package/lib/languages/ko.d.ts +14 -0
- package/lib/languages/ko.js +55 -0
- package/lib/{i18n/pl.d.ts → languages/lt.d.ts} +18 -15
- package/lib/languages/lt.js +136 -0
- package/lib/{i18n/lt.d.ts → languages/lv.d.ts} +16 -14
- package/lib/languages/lv.js +130 -0
- package/lib/languages/mr.d.ts +11 -0
- package/lib/languages/mr.js +132 -0
- package/lib/languages/ms.d.ts +31 -0
- package/lib/languages/ms.js +150 -0
- package/lib/languages/nb.d.ts +12 -0
- package/lib/languages/nb.js +100 -0
- package/lib/languages/nl.d.ts +16 -0
- package/lib/languages/nl.js +155 -0
- package/lib/languages/pa.d.ts +11 -0
- package/lib/languages/pa.js +131 -0
- package/lib/{i18n/uk.d.ts → languages/pl.d.ts} +16 -14
- package/lib/languages/pl.js +110 -0
- package/lib/languages/pt.d.ts +29 -0
- package/lib/languages/pt.js +112 -0
- package/lib/languages/ro.d.ts +158 -0
- package/lib/languages/ro.js +273 -0
- package/lib/languages/ru.d.ts +85 -0
- package/lib/languages/ru.js +96 -0
- package/lib/languages/sr-Cyrl.d.ts +80 -0
- package/lib/languages/sr-Cyrl.js +113 -0
- package/lib/{i18n/cz.d.ts → languages/sr-Latn.d.ts} +26 -14
- package/lib/languages/sr-Latn.js +113 -0
- package/lib/languages/sv.d.ts +14 -0
- package/lib/languages/sv.js +90 -0
- package/lib/languages/sw.d.ts +39 -0
- package/lib/languages/sw.js +126 -0
- package/lib/languages/ta.d.ts +20 -0
- package/lib/languages/ta.js +226 -0
- package/lib/languages/te.d.ts +22 -0
- package/lib/languages/te.js +219 -0
- package/lib/languages/th.d.ts +17 -0
- package/lib/languages/th.js +117 -0
- package/lib/languages/tr.d.ts +12 -0
- package/lib/languages/tr.js +56 -0
- package/lib/languages/uk.d.ts +85 -0
- package/lib/{i18n → languages}/uk.js +33 -32
- package/lib/languages/ur.d.ts +11 -0
- package/lib/languages/ur.js +131 -0
- package/lib/{i18n → languages}/vi.d.ts +15 -13
- package/lib/languages/vi.js +147 -0
- package/lib/languages/zh-Hans.d.ts +21 -0
- package/lib/languages/zh-Hans.js +111 -0
- package/lib/languages/zh-Hant.d.ts +21 -0
- package/lib/languages/zh-Hant.js +111 -0
- package/lib/n2words.d.ts +207 -7
- package/lib/n2words.js +535 -81
- package/package.json +126 -79
- package/dist/ar.js +0 -2
- package/dist/ar.js.map +0 -1
- package/dist/az.js +0 -2
- package/dist/az.js.map +0 -1
- package/dist/cz.js +0 -2
- package/dist/cz.js.map +0 -1
- package/dist/de.js +0 -2
- package/dist/de.js.map +0 -1
- package/dist/dk.js +0 -2
- package/dist/dk.js.map +0 -1
- package/dist/en.js +0 -2
- package/dist/en.js.map +0 -1
- package/dist/es.js +0 -2
- package/dist/es.js.map +0 -1
- package/dist/fa.js +0 -2
- package/dist/fa.js.map +0 -1
- package/dist/fr-BE.js +0 -2
- package/dist/fr-BE.js.map +0 -1
- package/dist/fr.js +0 -2
- package/dist/fr.js.map +0 -1
- package/dist/he.js +0 -2
- package/dist/he.js.map +0 -1
- package/dist/hr.js +0 -2
- package/dist/hr.js.map +0 -1
- package/dist/hu.js +0 -2
- package/dist/hu.js.map +0 -1
- package/dist/id.js +0 -2
- package/dist/id.js.map +0 -1
- package/dist/it.js +0 -2
- package/dist/it.js.map +0 -1
- package/dist/ko.js +0 -2
- package/dist/ko.js.map +0 -1
- package/dist/lt.js +0 -2
- package/dist/lt.js.map +0 -1
- package/dist/lv.js +0 -2
- package/dist/lv.js.map +0 -1
- package/dist/n2words.d.ts +0 -2
- package/dist/nl.js +0 -2
- package/dist/nl.js.map +0 -1
- package/dist/no.js +0 -2
- package/dist/no.js.map +0 -1
- package/dist/pl.js +0 -2
- package/dist/pl.js.map +0 -1
- package/dist/pt.js +0 -2
- package/dist/pt.js.map +0 -1
- package/dist/ro.js +0 -2
- package/dist/ro.js.map +0 -1
- package/dist/ru.js +0 -2
- package/dist/ru.js.map +0 -1
- package/dist/sr.js +0 -2
- package/dist/sr.js.map +0 -1
- package/dist/tr.js +0 -2
- package/dist/tr.js.map +0 -1
- package/dist/uk.js +0 -2
- package/dist/uk.js.map +0 -1
- package/dist/vi.js +0 -2
- package/dist/vi.js.map +0 -1
- package/dist/zh.js +0 -2
- package/dist/zh.js.map +0 -1
- package/lib/classes/base-language.d.ts +0 -58
- package/lib/classes/base-language.js +0 -172
- package/lib/i18n/ar.d.ts +0 -41
- package/lib/i18n/ar.js +0 -209
- package/lib/i18n/az.d.ts +0 -15
- package/lib/i18n/az.js +0 -66
- package/lib/i18n/cz.js +0 -135
- package/lib/i18n/de.d.ts +0 -17
- package/lib/i18n/de.js +0 -103
- package/lib/i18n/dk.d.ts +0 -14
- package/lib/i18n/dk.js +0 -110
- package/lib/i18n/en.d.ts +0 -22
- package/lib/i18n/en.js +0 -86
- package/lib/i18n/es.d.ts +0 -16
- package/lib/i18n/es.js +0 -110
- package/lib/i18n/fa.d.ts +0 -54
- package/lib/i18n/fa.js +0 -106
- package/lib/i18n/fr-BE.d.ts +0 -11
- package/lib/i18n/fr-BE.js +0 -20
- package/lib/i18n/fr.d.ts +0 -15
- package/lib/i18n/fr.js +0 -99
- package/lib/i18n/he.d.ts +0 -61
- package/lib/i18n/he.js +0 -132
- package/lib/i18n/hr.d.ts +0 -68
- package/lib/i18n/hr.js +0 -129
- package/lib/i18n/hu.d.ts +0 -17
- package/lib/i18n/hu.js +0 -135
- package/lib/i18n/id.d.ts +0 -43
- package/lib/i18n/id.js +0 -156
- package/lib/i18n/it.d.ts +0 -29
- package/lib/i18n/it.js +0 -137
- package/lib/i18n/ko.d.ts +0 -15
- package/lib/i18n/ko.js +0 -56
- package/lib/i18n/lt.js +0 -138
- package/lib/i18n/lv.d.ts +0 -57
- package/lib/i18n/lv.js +0 -120
- package/lib/i18n/nl.d.ts +0 -20
- package/lib/i18n/nl.js +0 -125
- package/lib/i18n/no.d.ts +0 -15
- package/lib/i18n/no.js +0 -77
- package/lib/i18n/pl.js +0 -126
- package/lib/i18n/pt.d.ts +0 -26
- package/lib/i18n/pt.js +0 -118
- package/lib/i18n/ro.d.ts +0 -109
- package/lib/i18n/ro.js +0 -360
- package/lib/i18n/ru.d.ts +0 -30
- package/lib/i18n/ru.js +0 -198
- package/lib/i18n/sr.d.ts +0 -56
- package/lib/i18n/sr.js +0 -127
- package/lib/i18n/tr.d.ts +0 -15
- package/lib/i18n/tr.js +0 -64
- package/lib/i18n/vi.js +0 -151
- package/lib/i18n/zh.d.ts +0 -18
- package/lib/i18n/zh.js +0 -78
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Greedy scale language converter implementing the "highest-matching scale word" algorithm.
|
|
3
|
+
*
|
|
4
|
+
* Responsibilities:
|
|
5
|
+
* - Decompose an integer into a sequence of word-sets using greedy matching.
|
|
6
|
+
* - Provide helpers to reduce and post-process matched word-sets.
|
|
7
|
+
* - Inherits decimal handling from AbstractLanguage (supports grouped and per-digit
|
|
8
|
+
* modes via the `usePerDigitDecimals` class property).
|
|
9
|
+
*
|
|
10
|
+
* Subclass requirements:
|
|
11
|
+
* - Define `scaleWords` (ordered descending) as `[bigint, string]` tuples.
|
|
12
|
+
* - Implement 'combineWordSets(preceding, following)' to combine adjacent word-sets
|
|
13
|
+
* per language grammar.
|
|
14
|
+
*
|
|
15
|
+
* Scale words specification:
|
|
16
|
+
* - `scaleWords` is an Array of 2-tuples: `[bigint, string]` where the first element
|
|
17
|
+
* is the numeric scale value and the second is the word for that value.
|
|
18
|
+
* - Scale words MUST be ordered from largest to smallest (descending) for the algorithm
|
|
19
|
+
* to function correctly.
|
|
20
|
+
*
|
|
21
|
+
* Terminology:
|
|
22
|
+
* - **Scale**: A magnitude value (100n, 1000n, 1000000n)
|
|
23
|
+
* - **Word-set**: An object `{ word: bigint }` representing a partial result
|
|
24
|
+
* - **Scale word**: The word for a scale value ("hundred", "thousand")
|
|
25
|
+
*
|
|
26
|
+
* @abstract
|
|
27
|
+
* @extends AbstractLanguage
|
|
28
|
+
*/
|
|
29
|
+
export class GreedyScaleLanguage extends AbstractLanguage {
|
|
30
|
+
/**
|
|
31
|
+
* Array of scale words mapping numeric values to their word representations.
|
|
32
|
+
*
|
|
33
|
+
* Each element is a 2-tuple: `[bigint, string]` where the first element is the
|
|
34
|
+
* numeric scale value and the second is the word for that value. The array MUST be
|
|
35
|
+
* ordered from largest to smallest (descending) for the greedy algorithm to work correctly.
|
|
36
|
+
*
|
|
37
|
+
* @type {Array<[bigint, string]>}
|
|
38
|
+
* @example
|
|
39
|
+
* // English scale words (descending order):
|
|
40
|
+
* // [[1000000000n, 'billion'], [1000000n, 'million'], [1000n, 'thousand'], [100n, 'hundred'], ..., [1n, 'one']]
|
|
41
|
+
*/
|
|
42
|
+
scaleWords: Array<[bigint, string]>;
|
|
43
|
+
/**
|
|
44
|
+
* Returns the word for an exact scale value.
|
|
45
|
+
*
|
|
46
|
+
* @param {bigint} scale The scale value to look up (prefer BigInt for exact matching).
|
|
47
|
+
* @returns {string|undefined} The word for the provided scale, or `undefined`.
|
|
48
|
+
*/
|
|
49
|
+
wordForScale(scale: bigint): string | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Decomposes an integer into a sequence of word-sets.
|
|
52
|
+
*
|
|
53
|
+
* This internal helper returns a nested structure that represents quantities and
|
|
54
|
+
* their matching scale words (e.g. `[{ 'one': 1n }, { 'hundred': 100n }, ...]`).
|
|
55
|
+
* The result is designed to be reduced by `reduceWordSets()` using language-specific `combineWordSets()`.
|
|
56
|
+
*
|
|
57
|
+
* For quantities > 1, the multiplier is recursively decomposed. For quantity = 1,
|
|
58
|
+
* the implicit "one" is represented with `{ 'one': 1n }` and typically omitted during combineWordSets().
|
|
59
|
+
*
|
|
60
|
+
* @protected
|
|
61
|
+
* @param {bigint} integerPart The integer to decompose.
|
|
62
|
+
* @returns {Array<Object|Array>} An array of word-set objects and possibly nested arrays.
|
|
63
|
+
*/
|
|
64
|
+
protected decomposeInteger(integerPart: bigint): Array<Object | any[]>;
|
|
65
|
+
/**
|
|
66
|
+
* Reduces a nested array of word-sets into a single word-set object.
|
|
67
|
+
*
|
|
68
|
+
* This method repeatedly applies the subclass `combineWordSets()` operation until a single
|
|
69
|
+
* object remains. It normalizes nested arrays by recursively reducing them.
|
|
70
|
+
*
|
|
71
|
+
* @protected
|
|
72
|
+
* @param {Array<Object|Array>} wordSets Array of word-set objects and nested arrays.
|
|
73
|
+
* @returns {Object} Reduced word-set where the single object key is the language string
|
|
74
|
+
* and its value is the numeric BigInt result for that string.
|
|
75
|
+
*/
|
|
76
|
+
protected reduceWordSets(wordSets: Array<Object | any[]>): Object;
|
|
77
|
+
/**
|
|
78
|
+
* Combines two adjacent word-sets into a single word-set.
|
|
79
|
+
*
|
|
80
|
+
* This is the core language-specific method that must be implemented by subclasses
|
|
81
|
+
* to define how adjacent word-sets are combined according to the language's grammar.
|
|
82
|
+
* For example, English combines "twenty" + "three" → "twenty-three", while
|
|
83
|
+
* French might combine "quatre-vingts" + "dix" → "quatre-vingt-dix".
|
|
84
|
+
*
|
|
85
|
+
* @abstract
|
|
86
|
+
* @protected
|
|
87
|
+
* @param {Object} preceding Preceding word-set as `{ word: bigint }`.
|
|
88
|
+
* @param {Object} following Following word-set as `{ word: bigint }`.
|
|
89
|
+
* @returns {Object} Combined word-set with merged text and resulting numeric value.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* // English implementation might handle:
|
|
93
|
+
* // combineWordSets({ 'twenty': 20n }, { 'three': 3n }) → { 'twenty-three': 23n }
|
|
94
|
+
* // combineWordSets({ 'one': 1n }, { 'hundred': 100n }) → { 'one hundred': 100n }
|
|
95
|
+
*/
|
|
96
|
+
protected combineWordSets(preceding: Object, following: Object): Object;
|
|
97
|
+
/**
|
|
98
|
+
* Final string post-processing hook.
|
|
99
|
+
*
|
|
100
|
+
* Subclasses may override to apply language-specific whitespace, punctuation or
|
|
101
|
+
* orthographic corrections.
|
|
102
|
+
*
|
|
103
|
+
* @protected
|
|
104
|
+
* @param {string} output Language string produced by the conversion flow.
|
|
105
|
+
* @returns {string} Final formatted string.
|
|
106
|
+
*/
|
|
107
|
+
protected finalizeWords(output: string): string;
|
|
108
|
+
}
|
|
109
|
+
import { AbstractLanguage } from './abstract-language.js';
|
|
@@ -0,0 +1,201 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for Slavic and related languages with complex pluralization.
|
|
3
|
+
*
|
|
4
|
+
* This class provides a reusable implementation for languages that share:
|
|
5
|
+
* - Three-form pluralization (singular/few/many)
|
|
6
|
+
* - Gender-aware number forms (masculine/feminine for 1-9)
|
|
7
|
+
* - Hundreds, tens, ones decomposition pattern
|
|
8
|
+
* - Segment-based large number handling (thousands, millions, etc.)
|
|
9
|
+
* - Inherits decimal handling from AbstractLanguage (supports both grouped and
|
|
10
|
+
* per-digit modes via the `usePerDigitDecimals` class property).
|
|
11
|
+
*
|
|
12
|
+
* Used by: Russian (ru), Czech (cs), Polish (pl), Ukrainian (uk), Serbian (sr-Latn),
|
|
13
|
+
* Croatian (hr), Lithuanian (lt), Latvian (lv), Hebrew (he), and Biblical Hebrew (hbo).
|
|
14
|
+
*
|
|
15
|
+
* Subclasses MUST define these properties with language-specific vocabulary:
|
|
16
|
+
* - `onesWords` - Object mapping 1-9 to masculine forms (or default forms)
|
|
17
|
+
* - `onesFeminineWords` - Object mapping 1-9 to feminine forms (if gender distinction exists)
|
|
18
|
+
* - `teensWords` - Object mapping 0-9 to teen numbers (10-19)
|
|
19
|
+
* - `twentiesWords` - Object mapping 2-9 to tens (20-90)
|
|
20
|
+
* - `hundredsWords` - Object mapping 1-9 to hundreds (100-900) or special hundreds handling
|
|
21
|
+
* - `pluralForms` - Object mapping segment indices to [singular, few, many] plural forms
|
|
22
|
+
*
|
|
23
|
+
* Optional properties:
|
|
24
|
+
* - `scaleGenders` - Object mapping segment indices to boolean (true = feminine scale word)
|
|
25
|
+
* If not defined, defaults to thousands (index 1) being feminine, others masculine.
|
|
26
|
+
*
|
|
27
|
+
* @abstract
|
|
28
|
+
* @extends AbstractLanguage
|
|
29
|
+
*/
|
|
30
|
+
export class SlavicLanguage extends AbstractLanguage {
|
|
31
|
+
/**
|
|
32
|
+
* Constructs a SlavicLanguage instance with optional configuration.
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} [options] Configuration options.
|
|
35
|
+
* @param {('masculine'|'feminine')} [options.gender='masculine'] Grammatical gender for number forms.
|
|
36
|
+
*/
|
|
37
|
+
constructor(options?: {
|
|
38
|
+
gender?: "feminine" | "masculine" | undefined;
|
|
39
|
+
});
|
|
40
|
+
/**
|
|
41
|
+
* Masculine forms for digits 1-9 (or default forms if no gender distinction).
|
|
42
|
+
*
|
|
43
|
+
* @type {Object.<number, string>}
|
|
44
|
+
*/
|
|
45
|
+
onesWords: {
|
|
46
|
+
[x: number]: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Feminine forms for digits 1-9 (if language has gender distinction).
|
|
50
|
+
*
|
|
51
|
+
* @type {Object.<number, string>}
|
|
52
|
+
*/
|
|
53
|
+
onesFeminineWords: {
|
|
54
|
+
[x: number]: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Words for teen numbers (10-19).
|
|
58
|
+
*
|
|
59
|
+
* @type {Object.<number, string>}
|
|
60
|
+
*/
|
|
61
|
+
teensWords: {
|
|
62
|
+
[x: number]: string;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Words for multiples of ten (20, 30, 40, etc.).
|
|
66
|
+
*
|
|
67
|
+
* @type {Object.<number, string>}
|
|
68
|
+
*/
|
|
69
|
+
twentiesWords: {
|
|
70
|
+
[x: number]: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Words for hundreds (100, 200, 300, etc.) or special hundreds handling.
|
|
74
|
+
*
|
|
75
|
+
* @type {Object.<number, string>}
|
|
76
|
+
*/
|
|
77
|
+
hundredsWords: {
|
|
78
|
+
[x: number]: string;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Plural forms for scale words (thousands, millions, billions, etc.).
|
|
82
|
+
* Maps segment indices to [singular, few, many] forms.
|
|
83
|
+
*
|
|
84
|
+
* @type {Object.<number, string[]>}
|
|
85
|
+
*/
|
|
86
|
+
pluralForms: {
|
|
87
|
+
[x: number]: string[];
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Gender of each scale word.
|
|
91
|
+
* Maps segment indices to boolean: true = feminine, false = masculine.
|
|
92
|
+
* Default is empty (all masculine). Languages with feminine thousands
|
|
93
|
+
* (Russian, Ukrainian, Serbian, Croatian) should set `{ 1: true }`.
|
|
94
|
+
*
|
|
95
|
+
* @type {Object.<number, boolean>}
|
|
96
|
+
*/
|
|
97
|
+
scaleGenders: {
|
|
98
|
+
[x: number]: boolean;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Whether to omit "one" before scale words (e.g., "thousand" instead of "one thousand").
|
|
102
|
+
* When true, 1000 becomes "tysiąc" (Polish) instead of "jeden tysiąc".
|
|
103
|
+
* Used by Polish, Czech, and similar languages.
|
|
104
|
+
*
|
|
105
|
+
* @type {boolean}
|
|
106
|
+
*/
|
|
107
|
+
omitOneBeforeScale: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Splits a number string into segments of specified size from right to left.
|
|
110
|
+
*
|
|
111
|
+
* Example: splitToSegments('1234567', 3) => [1n, 234n, 567n]
|
|
112
|
+
* This represents: 1 million + 234 thousand + 567 ones
|
|
113
|
+
*
|
|
114
|
+
* @param {string} numberString The number as a string.
|
|
115
|
+
* @param {number} segmentSize Segment size (typically 3 for thousands grouping).
|
|
116
|
+
* @returns {bigint[]} Array of BigInt segments from highest to lowest scale.
|
|
117
|
+
*/
|
|
118
|
+
splitToSegments(numberString: string, segmentSize: number): bigint[];
|
|
119
|
+
/**
|
|
120
|
+
* Extracts individual digits from a number (units, tens, hundreds).
|
|
121
|
+
*
|
|
122
|
+
* Returns digits in reverse order: [ones, tens, hundreds]
|
|
123
|
+
* Example: 456 => [6n, 5n, 4n]
|
|
124
|
+
*
|
|
125
|
+
* @param {bigint} value The number to extract digits from (0-999).
|
|
126
|
+
* @returns {bigint[]} Array of [ones, tens, hundreds] as BigInts.
|
|
127
|
+
*/
|
|
128
|
+
extractDigits(value: bigint): bigint[];
|
|
129
|
+
/**
|
|
130
|
+
* Selects the correct plural form based on Slavic pluralization rules.
|
|
131
|
+
*
|
|
132
|
+
* Slavic languages typically use three forms:
|
|
133
|
+
* - Form 0 (singular): numbers ending in 1, except 11 (1, 21, 31, 101...)
|
|
134
|
+
* - Form 1 (few): numbers ending in 2-4, except 12-14 (2-4, 22-24, 32-34...)
|
|
135
|
+
* - Form 2 (many): all other numbers (0, 5-20, 25-30, 100, 111-119...)
|
|
136
|
+
*
|
|
137
|
+
* Examples using Russian тысяча (thousand):
|
|
138
|
+
* - 1, 21, 31... ⇒ тысяча (form 0, singular)
|
|
139
|
+
* - 2-4, 22-24, 32-34... ⇒ тысячи (form 1, few)
|
|
140
|
+
* - 0, 5-20, 25-30, 100... ⇒ тысяч (form 2, many)
|
|
141
|
+
*
|
|
142
|
+
* @param {bigint} number The number to check.
|
|
143
|
+
* @param {string[]} pluralForms Array of [singular, few, many] forms.
|
|
144
|
+
* @returns {string} The appropriate form for the number.
|
|
145
|
+
*/
|
|
146
|
+
pluralize(number: bigint, pluralForms: string[]): string;
|
|
147
|
+
}
|
|
148
|
+
import { AbstractLanguage } from './abstract-language.js';
|