n2words 1.23.1 → 1.24.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 +181 -52
- package/dist/languages/ar.js +2 -0
- package/dist/languages/ar.js.map +1 -0
- package/dist/languages/az.js +2 -0
- package/dist/languages/az.js.map +1 -0
- package/dist/languages/bn.js +2 -0
- package/dist/languages/bn.js.map +1 -0
- package/dist/languages/cs.js +2 -0
- package/dist/languages/cs.js.map +1 -0
- package/dist/languages/da.js +2 -0
- package/dist/languages/da.js.map +1 -0
- package/dist/languages/de.js +2 -0
- package/dist/languages/de.js.map +1 -0
- package/dist/languages/el.js +2 -0
- package/dist/languages/el.js.map +1 -0
- package/dist/languages/en.js +2 -0
- package/dist/languages/en.js.map +1 -0
- package/dist/languages/es.js +2 -0
- package/dist/languages/es.js.map +1 -0
- package/dist/languages/fa.js +2 -0
- package/dist/languages/fa.js.map +1 -0
- package/dist/languages/fil.js +2 -0
- package/dist/languages/fil.js.map +1 -0
- package/dist/languages/fr-BE.js +2 -0
- package/dist/languages/fr-BE.js.map +1 -0
- package/dist/languages/fr.js +2 -0
- package/dist/languages/fr.js.map +1 -0
- package/dist/languages/gu.js +2 -0
- package/dist/languages/gu.js.map +1 -0
- package/dist/languages/he.js +2 -0
- package/dist/languages/he.js.map +1 -0
- package/dist/languages/hi.js +2 -0
- package/dist/languages/hi.js.map +1 -0
- package/dist/languages/hr.js +2 -0
- package/dist/languages/hr.js.map +1 -0
- package/dist/languages/hu.js +2 -0
- package/dist/languages/hu.js.map +1 -0
- package/dist/languages/id.js +2 -0
- package/dist/languages/id.js.map +1 -0
- package/dist/languages/it.js +2 -0
- package/dist/languages/it.js.map +1 -0
- package/dist/languages/ja.js +2 -0
- package/dist/languages/ja.js.map +1 -0
- package/dist/languages/kn.js +2 -0
- package/dist/languages/kn.js.map +1 -0
- package/dist/languages/ko.js +2 -0
- package/dist/languages/ko.js.map +1 -0
- package/dist/languages/lt.js +2 -0
- package/dist/languages/lt.js.map +1 -0
- package/dist/languages/lv.js +2 -0
- package/dist/languages/lv.js.map +1 -0
- package/dist/languages/mr.js +2 -0
- package/dist/languages/mr.js.map +1 -0
- package/dist/languages/ms.js +2 -0
- package/dist/languages/ms.js.map +1 -0
- package/dist/languages/nb.js +2 -0
- package/dist/languages/nb.js.map +1 -0
- package/dist/languages/nl.js +2 -0
- package/dist/languages/nl.js.map +1 -0
- package/dist/languages/pa-Guru.js +2 -0
- package/dist/languages/pa-Guru.js.map +1 -0
- package/dist/languages/pl.js +2 -0
- package/dist/languages/pl.js.map +1 -0
- package/dist/languages/pt.js +2 -0
- package/dist/languages/pt.js.map +1 -0
- package/dist/languages/ro.js +2 -0
- package/dist/languages/ro.js.map +1 -0
- package/dist/languages/ru.js +2 -0
- package/dist/languages/ru.js.map +1 -0
- package/dist/languages/sr-Latn.js +2 -0
- package/dist/languages/sr-Latn.js.map +1 -0
- package/dist/languages/sv.js +2 -0
- package/dist/languages/sv.js.map +1 -0
- package/dist/languages/sw.js +2 -0
- package/dist/languages/sw.js.map +1 -0
- package/dist/languages/ta.js +2 -0
- package/dist/languages/ta.js.map +1 -0
- package/dist/languages/te.js +2 -0
- package/dist/languages/te.js.map +1 -0
- package/dist/languages/th.js +2 -0
- package/dist/languages/th.js.map +1 -0
- package/dist/languages/tr.js +2 -0
- package/dist/languages/tr.js.map +1 -0
- package/dist/languages/uk.js +2 -0
- package/dist/languages/uk.js.map +1 -0
- package/dist/languages/ur.js +2 -0
- package/dist/languages/ur.js.map +1 -0
- package/dist/languages/vi.js +2 -0
- package/dist/languages/vi.js.map +1 -0
- package/dist/languages/zh-Hans.js +2 -0
- package/dist/languages/zh-Hans.js.map +1 -0
- package/dist/n2words.js +1 -1
- package/dist/n2words.js.map +1 -1
- package/lib/classes/abstract-language.js +211 -110
- package/lib/classes/greedy-scale-language.js +195 -0
- package/lib/classes/slavic-language.js +251 -0
- package/lib/classes/south-asian-language.js +161 -0
- package/lib/classes/turkic-language.js +63 -0
- package/lib/languages/ar.js +243 -0
- package/lib/languages/az.js +58 -0
- package/lib/languages/bn.js +126 -0
- package/lib/languages/cs.js +212 -0
- package/lib/languages/da.js +167 -0
- package/lib/languages/de.js +135 -0
- package/lib/languages/el.js +116 -0
- package/lib/languages/en.js +123 -0
- package/lib/languages/es.js +153 -0
- package/lib/languages/fa.js +127 -0
- package/lib/languages/fil.js +162 -0
- package/lib/languages/fr-BE.js +61 -0
- package/lib/languages/fr.js +145 -0
- package/lib/languages/gu.js +156 -0
- package/lib/languages/he.js +329 -0
- package/lib/languages/hi.js +126 -0
- package/lib/languages/hr.js +157 -0
- package/lib/languages/hu.js +155 -0
- package/lib/languages/id.js +174 -0
- package/lib/languages/it.js +148 -0
- package/lib/languages/ja.js +190 -0
- package/lib/languages/kn.js +71 -0
- package/lib/languages/ko.js +83 -0
- package/lib/languages/lt.js +171 -0
- package/lib/languages/lv.js +153 -0
- package/lib/languages/mr.js +156 -0
- package/lib/languages/ms.js +146 -0
- package/lib/languages/nb.js +120 -0
- package/lib/languages/nl.js +206 -0
- package/lib/languages/pa-Guru.js +126 -0
- package/lib/languages/pl.js +189 -0
- package/lib/languages/pt.js +147 -0
- package/lib/languages/ro.js +380 -0
- package/lib/languages/ru.js +116 -0
- package/lib/languages/sr-Latn.js +157 -0
- package/lib/languages/sv.js +127 -0
- package/lib/languages/sw.js +121 -0
- package/lib/languages/ta.js +226 -0
- package/lib/languages/te.js +229 -0
- package/lib/languages/th.js +123 -0
- package/lib/languages/tr.js +83 -0
- package/lib/{i18n → languages}/uk.js +50 -23
- package/lib/languages/ur.js +126 -0
- package/lib/languages/vi.js +193 -0
- package/lib/languages/zh-Hans.js +165 -0
- package/lib/n2words.js +246 -75
- package/package.json +80 -72
- package/typings/classes/abstract-language.d.ts +144 -0
- package/typings/classes/greedy-scale-language.d.ts +148 -0
- package/typings/classes/slavic-language.d.ts +145 -0
- package/typings/classes/south-asian-language.d.ts +101 -0
- package/typings/classes/turkic-language.d.ts +42 -0
- package/typings/languages/ar.d.ts +93 -0
- package/typings/languages/az.d.ts +25 -0
- package/typings/languages/bn.d.ts +1 -0
- package/typings/languages/cs.d.ts +120 -0
- package/typings/languages/da.d.ts +53 -0
- package/typings/languages/de.d.ts +26 -0
- package/typings/languages/el.d.ts +11 -0
- package/typings/languages/en.d.ts +30 -0
- package/typings/languages/es.d.ts +43 -0
- package/typings/languages/fa.d.ts +81 -0
- package/typings/languages/fil.d.ts +12 -0
- package/typings/languages/fr-BE.d.ts +41 -0
- package/typings/languages/fr.d.ts +43 -0
- package/typings/languages/gu.d.ts +12 -0
- package/typings/languages/he.d.ts +197 -0
- package/typings/languages/hi.d.ts +1 -0
- package/typings/languages/hr.d.ts +110 -0
- package/typings/languages/hu.d.ts +37 -0
- package/typings/languages/id.d.ts +69 -0
- package/typings/languages/it.d.ts +51 -0
- package/typings/languages/ja.d.ts +58 -0
- package/typings/languages/kn.d.ts +11 -0
- package/typings/languages/ko.d.ts +25 -0
- package/typings/languages/lt.d.ts +110 -0
- package/typings/languages/lv.d.ts +99 -0
- package/typings/languages/mr.d.ts +12 -0
- package/typings/languages/ms.d.ts +37 -0
- package/typings/languages/nb.d.ts +27 -0
- package/typings/languages/nl.d.ts +65 -0
- package/typings/languages/pa-Guru.d.ts +1 -0
- package/typings/languages/pl.d.ts +116 -0
- package/typings/languages/pt.d.ts +39 -0
- package/typings/languages/ro.d.ts +229 -0
- package/typings/languages/ru.d.ts +108 -0
- package/typings/languages/sr-Latn.d.ts +98 -0
- package/typings/languages/sv.d.ts +30 -0
- package/typings/languages/sw.d.ts +1 -0
- package/typings/languages/ta.d.ts +1 -0
- package/typings/languages/te.d.ts +1 -0
- package/typings/languages/th.d.ts +1 -0
- package/typings/languages/tr.d.ts +46 -0
- package/typings/languages/uk.d.ts +117 -0
- package/typings/languages/ur.d.ts +1 -0
- package/typings/languages/vi.d.ts +116 -0
- package/typings/languages/zh-Hans.d.ts +57 -0
- package/typings/n2words.d.ts +177 -0
- 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/abstract-language.d.ts +0 -54
- 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.d.ts +0 -68
- 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.d.ts +0 -68
- 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.d.ts +0 -67
- 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/uk.d.ts +0 -78
- package/lib/i18n/vi.d.ts +0 -70
- package/lib/i18n/vi.js +0 -151
- package/lib/i18n/zh.d.ts +0 -18
- package/lib/i18n/zh.js +0 -78
- package/lib/n2words.d.ts +0 -9
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import AbstractLanguage from './abstract-language.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {string[]} SlavicPluralForms
|
|
5
|
+
* Array of three plural forms for Slavic languages:
|
|
6
|
+
* - [0]: Singular form (for numbers ending in 1, except 11)
|
|
7
|
+
* - [1]: Few form (for numbers ending in 2-4, except 12-14)
|
|
8
|
+
* - [2]: Many form (for all other numbers: 0, 5-20, and numbers ending in 0, 5-9, 11-19)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object.<string, SlavicPluralForms>} SlavicThousandsMap
|
|
13
|
+
* Mapping from power indices to their plural forms.
|
|
14
|
+
* Example: { '0': ['тысяча', 'тысячи', 'тысяч'], '1': ['миллион', 'миллиона', 'миллионов'] }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Base class for Slavic and related languages with complex pluralization.
|
|
19
|
+
*
|
|
20
|
+
* This class provides a reusable implementation for languages that share:
|
|
21
|
+
* - Three-form pluralization (singular/few/many)
|
|
22
|
+
* - Gender-aware number forms (masculine/feminine for 1, 2)
|
|
23
|
+
* - Hundreds, tens, ones decomposition
|
|
24
|
+
* - Chunk-based large number handling (thousands, millions, etc.)
|
|
25
|
+
* - Inherits decimal handling from AbstractLanguage (supports both grouped and
|
|
26
|
+
* per-digit modes via the `convertDecimalsPerDigit` class property).
|
|
27
|
+
*
|
|
28
|
+
* Used by: Russian, Czech, Polish, Ukrainian, Serbian, Croatian,
|
|
29
|
+
* as well as Baltic (Lithuanian, Latvian) and Hebrew languages.
|
|
30
|
+
*
|
|
31
|
+
* Subclasses MUST define these properties with language-specific vocabulary:
|
|
32
|
+
* - `ones` - Object mapping 1-9 to masculine forms
|
|
33
|
+
* - `onesFeminine` - Object mapping 1-9 to feminine forms
|
|
34
|
+
* - `tens` - Object mapping 0-9 to teen numbers (10-19)
|
|
35
|
+
* - `twenties` - Object mapping 2-9 to tens (20-90)
|
|
36
|
+
* - `hundreds` - Object mapping 1-9 to hundreds (100-900)
|
|
37
|
+
* - `thousands` - Object mapping power indices to [singular, few, many] forms
|
|
38
|
+
* - `feminine` - Boolean indicating if feminine forms should be used (optional)
|
|
39
|
+
*
|
|
40
|
+
* @abstract
|
|
41
|
+
* @extends AbstractLanguage
|
|
42
|
+
*/
|
|
43
|
+
class SlavicLanguage extends AbstractLanguage {
|
|
44
|
+
/**
|
|
45
|
+
* Masculine forms for digits 1-9.
|
|
46
|
+
*
|
|
47
|
+
* @type {object}
|
|
48
|
+
*/
|
|
49
|
+
ones = {}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Feminine forms for digits 1-9.
|
|
53
|
+
*
|
|
54
|
+
* @type {object}
|
|
55
|
+
*/
|
|
56
|
+
onesFeminine = {}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Words for tens (10, 20, 30, etc.).
|
|
60
|
+
*
|
|
61
|
+
* @type {object}
|
|
62
|
+
*/
|
|
63
|
+
tens = {}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Special forms for 21-29 in some languages.
|
|
67
|
+
*
|
|
68
|
+
* @type {object}
|
|
69
|
+
*/
|
|
70
|
+
twenties = {}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Words for hundreds (100, 200, 300, etc.).
|
|
74
|
+
*
|
|
75
|
+
* @type {object}
|
|
76
|
+
*/
|
|
77
|
+
hundreds = {}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Scale words for thousands, millions, etc.
|
|
81
|
+
*
|
|
82
|
+
* @type {object}
|
|
83
|
+
*/
|
|
84
|
+
thousands = {}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Use feminine forms for numbers (affects 1-9).
|
|
88
|
+
*
|
|
89
|
+
* @type {boolean}
|
|
90
|
+
*/
|
|
91
|
+
feminine
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Initializes the Slavic language converter with language-specific options.
|
|
95
|
+
*
|
|
96
|
+
* @param {Object} [options={}] Configuration options.
|
|
97
|
+
* @param {boolean} [options.feminine=false] Use feminine forms for numbers (affects gender agreement).
|
|
98
|
+
*/
|
|
99
|
+
constructor ({ feminine = false } = {}) {
|
|
100
|
+
super()
|
|
101
|
+
|
|
102
|
+
this.feminine = feminine
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Converts a whole number to its word representation.
|
|
107
|
+
*
|
|
108
|
+
* This method implements the Slavic number construction algorithm:
|
|
109
|
+
* 1. Split number into 3-digit chunks (right to left)
|
|
110
|
+
* 2. For each chunk: convert hundreds, tens, ones
|
|
111
|
+
* 3. Apply gender rules for ones (feminine for thousands, or when feminine=true)
|
|
112
|
+
* 4. Add pluralized power word (thousand/million/billion/etc.)
|
|
113
|
+
* 5. Join all parts with spaces
|
|
114
|
+
*
|
|
115
|
+
* @param {bigint} number The whole number to convert (non-negative).
|
|
116
|
+
* @returns {string} The number in words.
|
|
117
|
+
*/
|
|
118
|
+
convertWholePart (number) {
|
|
119
|
+
if (number === 0n) {
|
|
120
|
+
return this.zeroWord
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const words = []
|
|
124
|
+
const chunks = this.splitByX(number.toString(), 3)
|
|
125
|
+
let chunkIndex = chunks.length
|
|
126
|
+
|
|
127
|
+
for (const chunkValue of chunks) {
|
|
128
|
+
chunkIndex = chunkIndex - 1
|
|
129
|
+
|
|
130
|
+
if (chunkValue === 0n) {
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const [onesDigit, tensDigit, hundredsDigit] = this.getDigits(chunkValue)
|
|
135
|
+
|
|
136
|
+
if (hundredsDigit > 0n) {
|
|
137
|
+
words.push(this.hundreds[hundredsDigit])
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (tensDigit > 1n) {
|
|
141
|
+
words.push(this.twenties[tensDigit])
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle teens (10-19) or ones (1-9)
|
|
145
|
+
if (tensDigit === 1n) {
|
|
146
|
+
// Teens: use tens array directly
|
|
147
|
+
words.push(this.tens[onesDigit])
|
|
148
|
+
} else if (onesDigit > 0n) {
|
|
149
|
+
// Ones: use feminine form for thousands (chunkIndex 1) or when feminine=true (chunkIndex 0)
|
|
150
|
+
const onesArray =
|
|
151
|
+
chunkIndex === 1 || (this.feminine && chunkIndex === 0)
|
|
152
|
+
? this.onesFeminine
|
|
153
|
+
: this.ones
|
|
154
|
+
words.push(onesArray[onesDigit])
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Add power word (thousand, million, etc.) with proper pluralization
|
|
158
|
+
if (chunkIndex > 0) {
|
|
159
|
+
words.push(this.pluralize(chunkValue, this.thousands[chunkIndex]))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return words.join(' ')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Splits a number string into chunks of X digits.
|
|
168
|
+
*
|
|
169
|
+
* Example: splitByX('1234567', 3) => [1n, 234n, 567n]
|
|
170
|
+
*
|
|
171
|
+
* @param {string} numberString The number as a string.
|
|
172
|
+
* @param {number} chunkSize Chunk size (typically 3 for thousands grouping).
|
|
173
|
+
* @returns {bigint[]} Array of BigInt chunks.
|
|
174
|
+
*/
|
|
175
|
+
splitByX (numberString, chunkSize) {
|
|
176
|
+
const chunks = []
|
|
177
|
+
const stringLength = numberString.length
|
|
178
|
+
|
|
179
|
+
if (stringLength > chunkSize) {
|
|
180
|
+
const remainderLength = stringLength % chunkSize
|
|
181
|
+
|
|
182
|
+
if (remainderLength > 0) {
|
|
183
|
+
chunks.push(BigInt(numberString.slice(0, remainderLength)))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (let i = remainderLength; i < stringLength; i += chunkSize) {
|
|
187
|
+
chunks.push(BigInt(numberString.slice(i, i + chunkSize)))
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
chunks.push(BigInt(numberString))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return chunks
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Extracts individual digits from a number (units, tens, hundreds).
|
|
198
|
+
*
|
|
199
|
+
* Returns digits in reverse order: [ones, tens, hundreds]
|
|
200
|
+
* Example: 456 => [6n, 5n, 4n]
|
|
201
|
+
*
|
|
202
|
+
* @param {bigint} value The number to extract digits from (0-999).
|
|
203
|
+
* @returns {bigint[]} Array of [ones, tens, hundreds] as BigInts.
|
|
204
|
+
*/
|
|
205
|
+
getDigits (value) {
|
|
206
|
+
// Direct BigInt arithmetic is faster than string manipulation
|
|
207
|
+
const onesPlace = value % 10n
|
|
208
|
+
const tensPlace = (value / 10n) % 10n
|
|
209
|
+
const hundredsPlace = value / 100n
|
|
210
|
+
return [onesPlace, tensPlace, hundredsPlace]
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Selects the correct plural form based on Slavic pluralization rules.
|
|
215
|
+
*
|
|
216
|
+
* Slavic languages use three forms:
|
|
217
|
+
* - Form 0 (singular): numbers ending in 1 (but not 11)
|
|
218
|
+
* - Form 1 (few): numbers ending in 2-4 (but not 12-14)
|
|
219
|
+
* - Form 2 (many): all other numbers (0, 5-20, 25-30, etc.)
|
|
220
|
+
*
|
|
221
|
+
* Examples (Russian):
|
|
222
|
+
* - 1, 21, 31... => тысяча (form 0)
|
|
223
|
+
* - 2-4, 22-24, 32-34... => тысячи (form 1)
|
|
224
|
+
* - 0, 5-20, 25-30... => тысяч (form 2)
|
|
225
|
+
*
|
|
226
|
+
* @param {bigint} n The number to check.
|
|
227
|
+
* @param {string[]} forms Array of [singular, few, many] forms.
|
|
228
|
+
* @returns {string} The appropriate form for the number.
|
|
229
|
+
*/
|
|
230
|
+
pluralize (number, pluralForms) {
|
|
231
|
+
const remainder100 = number % 100n
|
|
232
|
+
const remainder10 = number % 10n
|
|
233
|
+
|
|
234
|
+
// Check if in 11-19 range (special case)
|
|
235
|
+
if (remainder100 >= 10n && remainder100 <= 20n) {
|
|
236
|
+
return pluralForms[2] // Always use "many" form for 11-20
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (remainder10 === 1n) {
|
|
240
|
+
return pluralForms[0] // Singular
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (remainder10 >= 2n && remainder10 <= 4n) {
|
|
244
|
+
return pluralForms[1] // Few (2-4)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return pluralForms[2] // Many
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export default SlavicLanguage
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import AbstractLanguage from './abstract-language.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Array<string>} SouthAsianScaleWords
|
|
5
|
+
* Array of scale words for the Indian numbering system, in ascending order.
|
|
6
|
+
* - Index 0: Usually empty/unused (ones place)
|
|
7
|
+
* - Index 1: Thousands (hazaar/হাজার/हजार)
|
|
8
|
+
* - Index 2: Lakhs (lakh/লাখ/लाख)
|
|
9
|
+
* - Index 3: Crores (crore/কোটি/करोड़)
|
|
10
|
+
* - Index 4: Arabs (arab/আরব/अरब)
|
|
11
|
+
* Each index i represents the scale word for groups at position i in the Indian system.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Array<string>} SouthAsianBelowHundred
|
|
16
|
+
* Array of words for numbers 0-99, indexed directly.
|
|
17
|
+
* belowHundred[0] = word for 0, belowHundred[42] = word for 42, etc.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Base class for South Asian languages with shared grouping patterns.
|
|
22
|
+
*
|
|
23
|
+
* This class provides a reusable implementation for South Asian languages that share:
|
|
24
|
+
* - Indian-style number grouping: last 3 digits, then 2-2 (1,23,45,67,89)
|
|
25
|
+
* - Lakh (100,000), Crore (10,000,000), Arab (1,000,000,000) scale words
|
|
26
|
+
* - Standard negative and decimal handling (inherits AbstractLanguage decimal logic,
|
|
27
|
+
* including `convertDecimalsPerDigit` support when set by subclasses)
|
|
28
|
+
*
|
|
29
|
+
* Used by: Hindi (hi), Bengali (bn), Urdu (ur), Punjabi (pa), Marathi (mr), Gujarati (gu), Kannada (kn)
|
|
30
|
+
*
|
|
31
|
+
* Subclasses MUST define language-specific vocabulary via class properties:
|
|
32
|
+
* - `belowHundred` array with digit and teen words (0-99)
|
|
33
|
+
* - `hundredWord` string used inside `convertBelowThousand`
|
|
34
|
+
* - `scaleWords` array with grouping words (hazaar, lakh, crore, etc.) indexed by grouping level
|
|
35
|
+
* - `negativeWord`, `decimalSeparatorWord`, `zeroWord`, `wordSeparator`
|
|
36
|
+
*
|
|
37
|
+
* @abstract
|
|
38
|
+
* @extends AbstractLanguage
|
|
39
|
+
*/
|
|
40
|
+
class SouthAsianLanguage extends AbstractLanguage {
|
|
41
|
+
/**
|
|
42
|
+
* Array of words for numbers 0-99 (digits and teens).
|
|
43
|
+
* Index directly: belowHundred[0] through belowHundred[99].
|
|
44
|
+
* @type {Array<string>}
|
|
45
|
+
*/
|
|
46
|
+
belowHundred
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Word for "hundred" in the language (e.g., 'सौ' in Hindi, 'শত' in Bengali).
|
|
50
|
+
* Used to construct hundreds (e.g., "1 hundred", "2 hundred").
|
|
51
|
+
* @type {string}
|
|
52
|
+
*/
|
|
53
|
+
hundredWord
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Array of scale words for Indian-style grouping (hazaar, lakh, crore, arab, etc.).
|
|
57
|
+
* Index 0 is typically unused (ones place, no scale word).
|
|
58
|
+
* Index 1 is for thousands, Index 2 for lakhs, Index 3 for crores, etc.
|
|
59
|
+
* @type {Array<string>}
|
|
60
|
+
*/
|
|
61
|
+
scaleWords
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Split a number into Indian numbering system groups.
|
|
65
|
+
*
|
|
66
|
+
* The Indian system groups differently than Western (3-3-3) systems:
|
|
67
|
+
* - First group (rightmost): Up to 3 digits (ones, tens, hundreds)
|
|
68
|
+
* - Subsequent groups: Exactly 2 digits each (thousands, lakhs, crores, etc.)
|
|
69
|
+
*
|
|
70
|
+
* This creates the familiar Indian comma pattern: 1,23,45,67,890
|
|
71
|
+
*
|
|
72
|
+
* @protected
|
|
73
|
+
* @param {bigint} number The number to split into groups
|
|
74
|
+
* @returns {Array<number>} Array of groups from most significant to least significant
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // splitToGroups(1234567n) → [12, 34, 567]
|
|
78
|
+
* // Reads as: 12 lakhs, 34 thousands, 567 units
|
|
79
|
+
* // splitToGroups(98765432n) → [9, 87, 65, 432]
|
|
80
|
+
* // Reads as: 9 crores, 87 lakhs, 65 thousands, 432 units
|
|
81
|
+
*/
|
|
82
|
+
splitToGroups (number) {
|
|
83
|
+
const numStr = number.toString()
|
|
84
|
+
|
|
85
|
+
if (numStr.length <= 3) {
|
|
86
|
+
return [Number(numStr)]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const groups = []
|
|
90
|
+
const last3 = numStr.slice(-3)
|
|
91
|
+
groups.unshift(Number(last3))
|
|
92
|
+
|
|
93
|
+
let remaining = numStr.slice(0, -3)
|
|
94
|
+
while (remaining.length > 0) {
|
|
95
|
+
const group = remaining.slice(-2)
|
|
96
|
+
groups.unshift(Number(group))
|
|
97
|
+
remaining = remaining.slice(0, -2)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return groups
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Convert a number below 1000 to words (0-999).
|
|
105
|
+
*
|
|
106
|
+
* @protected
|
|
107
|
+
* @param {number} number Value between 0 and 999
|
|
108
|
+
* @returns {string} Language-specific word representation
|
|
109
|
+
*/
|
|
110
|
+
convertBelowThousand (number) {
|
|
111
|
+
if (number === 0) return ''
|
|
112
|
+
if (number < 100) return this.belowHundred[number]
|
|
113
|
+
|
|
114
|
+
const hundreds = Math.trunc(number / 100)
|
|
115
|
+
const remainder = number % 100
|
|
116
|
+
const parts = []
|
|
117
|
+
|
|
118
|
+
if (hundreds === 1) {
|
|
119
|
+
parts.push(this.belowHundred[1] + ' ' + this.hundredWord)
|
|
120
|
+
} else {
|
|
121
|
+
parts.push(this.belowHundred[hundreds] + ' ' + this.hundredWord)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (remainder > 0) {
|
|
125
|
+
parts.push(this.belowHundred[remainder])
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return parts.join(' ')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Convert whole number to cardinal words using South Asian grouping.
|
|
133
|
+
*
|
|
134
|
+
* @param {bigint} number Number to convert
|
|
135
|
+
* @returns {string} Cardinal representation
|
|
136
|
+
*/
|
|
137
|
+
convertWholePart (number) {
|
|
138
|
+
if (number === 0n) {
|
|
139
|
+
return this.zeroWord
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const groups = this.splitToGroups(number)
|
|
143
|
+
const groupCount = groups.length
|
|
144
|
+
const words = []
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < groupCount; i++) {
|
|
147
|
+
const groupValue = groups[i]
|
|
148
|
+
if (groupValue === 0) continue
|
|
149
|
+
|
|
150
|
+
const scaleIndex = groupCount - i - 1
|
|
151
|
+
words.push(this.convertBelowThousand(groupValue))
|
|
152
|
+
if (scaleIndex > 0 && this.scaleWords[scaleIndex]) {
|
|
153
|
+
words.push(this.scaleWords[scaleIndex])
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return words.join(' ').trim()
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export default SouthAsianLanguage
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import GreedyScaleLanguage from './greedy-scale-language.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} TurkicWordPair
|
|
5
|
+
* @property {string} word - The Turkic word or phrase
|
|
6
|
+
* @property {bigint} value - The numeric value represented by the word
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base class for Turkic languages with shared grammar patterns.
|
|
11
|
+
*
|
|
12
|
+
* This class provides a reusable implementation for Turkic languages that share:
|
|
13
|
+
* - Space-separated number combinations
|
|
14
|
+
* - Implicit 'bir' (one) before hundreds and thousands
|
|
15
|
+
* - Simple multiplication/addition logic
|
|
16
|
+
* - Consistent magnitude handling
|
|
17
|
+
* - Inherits decimal handling from AbstractLanguage via GreedyScaleLanguage
|
|
18
|
+
* (supports both grouped and per-digit modes via the `convertDecimalsPerDigit` class property).
|
|
19
|
+
*
|
|
20
|
+
* Used by: Turkish (TR), Azerbaijani (AZ)
|
|
21
|
+
*
|
|
22
|
+
* Subclasses MUST define (from GreedyScaleLanguage requirements):
|
|
23
|
+
* - `scaleWordPairs` array of [value, word] pairs as a class property (ordered descending by value).
|
|
24
|
+
* Optionally, language-specific class properties (e.g., `negativeWord`, `zeroWord`, `decimalSeparatorWord`, `wordSeparator`).
|
|
25
|
+
*
|
|
26
|
+
* TurkicLanguage provides a default `mergeScales()` implementation; subclasses may override
|
|
27
|
+
* if specialized merge logic is needed (unlikely for Turkic languages).
|
|
28
|
+
*
|
|
29
|
+
* @abstract
|
|
30
|
+
* @extends GreedyScaleLanguage
|
|
31
|
+
*/
|
|
32
|
+
class TurkicLanguage extends GreedyScaleLanguage {
|
|
33
|
+
/**
|
|
34
|
+
* Merges two adjacent word-number pairs according to Turkic grammar rules.
|
|
35
|
+
*
|
|
36
|
+
* Shared Turkic patterns:
|
|
37
|
+
* - Implicit 'bir' (one) before hundreds and thousands
|
|
38
|
+
* - Space separator (wordSeparator property) for all combinations
|
|
39
|
+
* - Multiplies when right > left (crossing magnitude boundary)
|
|
40
|
+
* - Adds otherwise (combining same-magnitude components)
|
|
41
|
+
*
|
|
42
|
+
* @param {Object} leftPair The left operand as `{ word: bigint }`.
|
|
43
|
+
* @param {Object} rightPair The right operand as `{ word: bigint }`.
|
|
44
|
+
* @returns {Object} Merged pair with combined word and resulting number (bigint).
|
|
45
|
+
*/
|
|
46
|
+
mergeScales (leftPair, rightPair) {
|
|
47
|
+
const [[leftWord, leftNumber]] = Object.entries(leftPair)
|
|
48
|
+
const [[rightWord, rightNumber]] = Object.entries(rightPair)
|
|
49
|
+
|
|
50
|
+
// Implicit 'bir' (one) before certain magnitudes:
|
|
51
|
+
// Omit '1' before hundreds (100n) and thousands (1000n) to form natural combinations
|
|
52
|
+
if (leftNumber === 1n && (rightNumber <= 100n || rightNumber === 1000n)) {
|
|
53
|
+
return rightPair // Return just the magnitude word (e.g., "yüz", not "bir yüz")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Combine numbers with space separator (wordSeparator from GreedyScaleLanguage):
|
|
57
|
+
// Multiply when crossing magnitude boundary, add otherwise
|
|
58
|
+
const mergedNumber = rightNumber > leftNumber ? leftNumber * rightNumber : leftNumber + rightNumber
|
|
59
|
+
return { [`${leftWord}${this.wordSeparator}${rightWord}`]: mergedNumber }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default TurkicLanguage
|