num2words2 0.1.0.dev1__py3-none-any.whl
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.
- num2words2/README.md +69 -0
- num2words2/__init__.py +196 -0
- num2words2/_version.py +3 -0
- num2words2/base.py +377 -0
- num2words2/compat.py +29 -0
- num2words2/currency.py +60 -0
- num2words2/lang_AF.py +202 -0
- num2words2/lang_AM.py +193 -0
- num2words2/lang_AR.py +435 -0
- num2words2/lang_AS.py +119 -0
- num2words2/lang_AZ.py +208 -0
- num2words2/lang_BA.py +119 -0
- num2words2/lang_BE.py +356 -0
- num2words2/lang_BG.py +393 -0
- num2words2/lang_BN.py +242 -0
- num2words2/lang_BO.py +122 -0
- num2words2/lang_BR.py +135 -0
- num2words2/lang_BS.py +189 -0
- num2words2/lang_CA.py +476 -0
- num2words2/lang_CE.py +546 -0
- num2words2/lang_CS.py +260 -0
- num2words2/lang_CY.py +574 -0
- num2words2/lang_DA.py +170 -0
- num2words2/lang_DE.py +180 -0
- num2words2/lang_EL.py +338 -0
- num2words2/lang_EN.py +118 -0
- num2words2/lang_EN_IN.py +26 -0
- num2words2/lang_EN_NG.py +45 -0
- num2words2/lang_EO.py +130 -0
- num2words2/lang_ES.py +399 -0
- num2words2/lang_ES_CO.py +37 -0
- num2words2/lang_ES_CR.py +38 -0
- num2words2/lang_ES_GT.py +37 -0
- num2words2/lang_ES_NI.py +35 -0
- num2words2/lang_ES_VE.py +43 -0
- num2words2/lang_ET.py +393 -0
- num2words2/lang_EU.py +158 -0
- num2words2/lang_EUR.py +104 -0
- num2words2/lang_FA.py +187 -0
- num2words2/lang_FI.py +759 -0
- num2words2/lang_FO.py +136 -0
- num2words2/lang_FR.py +133 -0
- num2words2/lang_FR_BE.py +49 -0
- num2words2/lang_FR_CH.py +48 -0
- num2words2/lang_FR_DZ.py +35 -0
- num2words2/lang_GL.py +135 -0
- num2words2/lang_GU.py +172 -0
- num2words2/lang_HA.py +293 -0
- num2words2/lang_HAW.py +144 -0
- num2words2/lang_HE.py +314 -0
- num2words2/lang_HI.py +243 -0
- num2words2/lang_HR.py +304 -0
- num2words2/lang_HT.py +136 -0
- num2words2/lang_HU.py +189 -0
- num2words2/lang_HY.py +364 -0
- num2words2/lang_ID.py +242 -0
- num2words2/lang_IS.py +208 -0
- num2words2/lang_IT.py +265 -0
- num2words2/lang_JA.py +615 -0
- num2words2/lang_JW.py +136 -0
- num2words2/lang_KA.py +169 -0
- num2words2/lang_KK.py +136 -0
- num2words2/lang_KM.py +173 -0
- num2words2/lang_KN.py +182 -0
- num2words2/lang_KO.py +175 -0
- num2words2/lang_KZ.py +161 -0
- num2words2/lang_LA.py +135 -0
- num2words2/lang_LB.py +135 -0
- num2words2/lang_LN.py +136 -0
- num2words2/lang_LO.py +149 -0
- num2words2/lang_LT.py +283 -0
- num2words2/lang_LV.py +266 -0
- num2words2/lang_MG.py +143 -0
- num2words2/lang_MI.py +151 -0
- num2words2/lang_MK.py +136 -0
- num2words2/lang_ML.py +159 -0
- num2words2/lang_MN.py +319 -0
- num2words2/lang_MR.py +187 -0
- num2words2/lang_MS.py +293 -0
- num2words2/lang_MT.py +159 -0
- num2words2/lang_MY.py +152 -0
- num2words2/lang_NE.py +157 -0
- num2words2/lang_NL.py +204 -0
- num2words2/lang_NN.py +136 -0
- num2words2/lang_NO.py +143 -0
- num2words2/lang_OC.py +135 -0
- num2words2/lang_PA.py +159 -0
- num2words2/lang_PL.py +336 -0
- num2words2/lang_PS.py +136 -0
- num2words2/lang_PT.py +280 -0
- num2words2/lang_PT_BR.py +224 -0
- num2words2/lang_RO.py +183 -0
- num2words2/lang_RU.py +570 -0
- num2words2/lang_SA.py +136 -0
- num2words2/lang_SD.py +136 -0
- num2words2/lang_SI.py +152 -0
- num2words2/lang_SK.py +250 -0
- num2words2/lang_SL.py +306 -0
- num2words2/lang_SN.py +447 -0
- num2words2/lang_SO.py +136 -0
- num2words2/lang_SQ.py +252 -0
- num2words2/lang_SR.py +254 -0
- num2words2/lang_SU.py +136 -0
- num2words2/lang_SV.py +169 -0
- num2words2/lang_SW.py +219 -0
- num2words2/lang_TA.py +303 -0
- num2words2/lang_TE.py +169 -0
- num2words2/lang_TET.py +285 -0
- num2words2/lang_TG.py +149 -0
- num2words2/lang_TH.py +214 -0
- num2words2/lang_TK.py +136 -0
- num2words2/lang_TL.py +145 -0
- num2words2/lang_TR.py +882 -0
- num2words2/lang_TT.py +136 -0
- num2words2/lang_UK.py +1101 -0
- num2words2/lang_UR.py +168 -0
- num2words2/lang_UZ.py +136 -0
- num2words2/lang_VI.py +143 -0
- num2words2/lang_WO.py +136 -0
- num2words2/lang_YI.py +135 -0
- num2words2/lang_YO.py +136 -0
- num2words2/lang_ZH.py +296 -0
- num2words2/lang_ZH_CN.py +85 -0
- num2words2/lang_ZH_HK.py +48 -0
- num2words2/lang_ZH_TW.py +198 -0
- num2words2/utils.py +35 -0
- num2words2-0.1.0.dev1.data/scripts/num2words2 +98 -0
- num2words2-0.1.0.dev1.dist-info/METADATA +244 -0
- num2words2-0.1.0.dev1.dist-info/RECORD +133 -0
- num2words2-0.1.0.dev1.dist-info/WHEEL +5 -0
- num2words2-0.1.0.dev1.dist-info/entry_points.txt +2 -0
- num2words2-0.1.0.dev1.dist-info/licenses/COPYING +145 -0
- num2words2-0.1.0.dev1.dist-info/top_level.txt +1 -0
num2words2/lang_RU.py
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
|
|
3
|
+
# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved.
|
|
4
|
+
|
|
5
|
+
# This library is free software; you can redistribute it and/or
|
|
6
|
+
# modify it under the terms of the GNU Lesser General Public
|
|
7
|
+
# License as published by the Free Software Foundation; either
|
|
8
|
+
# version 2.1 of the License, or (at your option) any later version.
|
|
9
|
+
# This library is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
12
|
+
# Lesser General Public License for more details.
|
|
13
|
+
# You should have received a copy of the GNU Lesser General Public
|
|
14
|
+
# License along with this library; if not, write to the Free Software
|
|
15
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
16
|
+
# MA 02110-1301 USA
|
|
17
|
+
|
|
18
|
+
from __future__ import unicode_literals
|
|
19
|
+
|
|
20
|
+
from .base import Num2Word_Base
|
|
21
|
+
from .utils import get_digits, splitbyx
|
|
22
|
+
|
|
23
|
+
GENDER_PLURAL_INDEXES = {
|
|
24
|
+
'm': 0, 'masculine': 0, 'м': 0, 'мужской': 0,
|
|
25
|
+
'f': 1, 'feminine': 1, 'ж': 0, 'женский': 0,
|
|
26
|
+
'n': 2, 'neuter': 2, 'с': 0, 'средний': 0,
|
|
27
|
+
'p': 3, 'plural': 3
|
|
28
|
+
}
|
|
29
|
+
CASE_INDEXES = {
|
|
30
|
+
'n': 0, 'nominative': 0, 'и': 0, 'именительный': 0,
|
|
31
|
+
'g': 1, 'genitive': 1, 'р': 1, 'родительный': 1,
|
|
32
|
+
'd': 2, 'dative': 2, 'д': 2, 'дательный': 2,
|
|
33
|
+
'a': 3, 'accusative': 3, 'в': 3, 'винительный': 3,
|
|
34
|
+
'i': 4, 'instrumental': 4, 'т': 4, 'творительный': 4,
|
|
35
|
+
'p': 5, 'prepositional': 5, 'п': 5, 'предложный': 5
|
|
36
|
+
}
|
|
37
|
+
# Default values
|
|
38
|
+
D_CASE = 'n'
|
|
39
|
+
D_PLURAL = False
|
|
40
|
+
D_GENDER = 'm'
|
|
41
|
+
D_ANIMATE = True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_num_element(cases_dict, num, **kwargs):
|
|
45
|
+
return case_classifier_element(cases_dict[num], **kwargs)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def case_classifier_element(classifier, case=D_CASE, plural=D_PLURAL,
|
|
49
|
+
gender=D_GENDER, animate=D_ANIMATE):
|
|
50
|
+
case = classifier[CASE_INDEXES[case]]
|
|
51
|
+
if isinstance(case, str):
|
|
52
|
+
return case
|
|
53
|
+
|
|
54
|
+
if plural:
|
|
55
|
+
gender = case[GENDER_PLURAL_INDEXES['plural']]
|
|
56
|
+
else:
|
|
57
|
+
gender = case[GENDER_PLURAL_INDEXES[gender]]
|
|
58
|
+
if isinstance(gender, str):
|
|
59
|
+
return gender
|
|
60
|
+
|
|
61
|
+
if animate:
|
|
62
|
+
return gender[0]
|
|
63
|
+
return gender[1]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# format:
|
|
67
|
+
# {n : [case_1 .. case_5]}
|
|
68
|
+
# case: text or [gender_1 .. gender_3 plural_4]
|
|
69
|
+
# gender: text or [animate, inanimate]
|
|
70
|
+
ONES = {
|
|
71
|
+
0: ['ноль', 'ноля', 'нолю', 'ноль', 'нолём', 'ноле'],
|
|
72
|
+
1: [['один', 'одна', 'одно', 'одни'],
|
|
73
|
+
['одного', 'одной', 'одного', 'одних'],
|
|
74
|
+
['одному', 'одной', 'одному', 'одним'],
|
|
75
|
+
[['одного', 'один'], 'одну', 'одно', ['одних', 'одни']],
|
|
76
|
+
['одним', 'одной', 'одним', 'одними'],
|
|
77
|
+
['одном', 'одной', 'одном', 'одних']],
|
|
78
|
+
2: [['два', 'две', 'два', 'двое'],
|
|
79
|
+
['двух'] * 3 + ['двоих'],
|
|
80
|
+
['двум'] * 3 + ['двоим'],
|
|
81
|
+
[['двух', 'два'], ['двух', 'две'], 'два', 'двоих'],
|
|
82
|
+
['двумя'] * 3 + ['двоими'],
|
|
83
|
+
['двух'] * 3 + ['двоих']],
|
|
84
|
+
3: [['три'] * 3 + ['трое'],
|
|
85
|
+
['трёх'] * 3 + ['троих'],
|
|
86
|
+
['трём'] * 3 + ['троим'],
|
|
87
|
+
[['трёх', 'три'], ['трёх', 'три'], 'три', 'троих'],
|
|
88
|
+
['тремя'] * 3 + ['троими'],
|
|
89
|
+
['трёх'] * 3 + ['троих']],
|
|
90
|
+
4: [['четыре'] * 3 + ['четверо'],
|
|
91
|
+
['четырёх'] * 3 + ['четверых'],
|
|
92
|
+
['четырём'] * 3 + ['четверым'],
|
|
93
|
+
[['четырёх', 'четыре'], ['четырёх', 'четыре'], 'четыре', 'четверых'],
|
|
94
|
+
['четырьмя'] * 3 + ['четверыми'],
|
|
95
|
+
['четырёх'] * 3 + ['четверых']],
|
|
96
|
+
5: ['пять', 'пяти', 'пяти', 'пять', 'пятью', 'пяти'],
|
|
97
|
+
6: ['шесть', 'шести', 'шести', 'шесть', 'шестью', 'шести'],
|
|
98
|
+
7: ['семь', 'семи', 'семи', 'семь', 'семью', 'семи'],
|
|
99
|
+
8: ['восемь', 'восьми', 'восьми', 'восемь', 'восемью', 'восьми'],
|
|
100
|
+
9: ['девять', 'девяти', 'девяти', 'девять', 'девятью', 'девяти']
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
ONES_ORD_PREFIXES = {0: 'нулев', 1: 'перв', 2: 'втор', 4: 'четвёрт', 5: 'пят',
|
|
104
|
+
6: 'шест', 7: 'седьм', 8: 'восьм', 9: 'девят'}
|
|
105
|
+
ONES_ORD_POSTFIXES_GROUPS = {0: 0, 1: 1, 2: 0, 4: 1, 5: 1, 6: 0, 7: 0, 8: 0,
|
|
106
|
+
9: 1}
|
|
107
|
+
CASE_POSTFIXES = [[{0: 'ой', 1: 'ый'}, 'ая', 'ое', 'ые'],
|
|
108
|
+
['ого', 'ой', 'ого', 'ых'],
|
|
109
|
+
['ому', 'ой', 'ому', 'ым'],
|
|
110
|
+
[['ого', {0: 'ой', 1: 'ый'}], 'ую', 'ое', ['ых', 'ые']],
|
|
111
|
+
['ым', 'ой', 'ым', 'ыми'],
|
|
112
|
+
['ом', 'ой', 'ом', 'ых']]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def get_cases(prefix, post_group):
|
|
116
|
+
return [[
|
|
117
|
+
prefix + postfix if isinstance(postfix, str) else
|
|
118
|
+
[prefix + animate if isinstance(animate, str) else
|
|
119
|
+
prefix + animate[post_group]
|
|
120
|
+
for animate in postfix] if isinstance(postfix, list) else
|
|
121
|
+
prefix + postfix[post_group]
|
|
122
|
+
for postfix in case]
|
|
123
|
+
for case in CASE_POSTFIXES]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_ord_classifier(prefixes, post_groups):
|
|
127
|
+
if isinstance(post_groups, int):
|
|
128
|
+
post_groups = {n: post_groups for n, i in prefixes.items()}
|
|
129
|
+
return {
|
|
130
|
+
num: get_cases(prefix, post_groups[num])
|
|
131
|
+
for num, prefix in prefixes.items()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
ONES_ORD = {
|
|
136
|
+
3: [['третий', 'третья', 'третье', 'третьи'],
|
|
137
|
+
['третьего', 'третьей', 'третьего', 'третьих'],
|
|
138
|
+
['третьему', 'третьей', 'третьему', 'третьим'],
|
|
139
|
+
[['третьего', 'третий'], 'третью', 'третье', ['третьих', 'третьи']],
|
|
140
|
+
['третьим', 'третьей', 'третьим', 'третьими'],
|
|
141
|
+
['третьем', 'третьей', 'третьем', 'третьих']],
|
|
142
|
+
}
|
|
143
|
+
ONES_ORD.update(
|
|
144
|
+
get_ord_classifier(ONES_ORD_PREFIXES, ONES_ORD_POSTFIXES_GROUPS)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
TENS_PREFIXES = {1: 'один', 2: 'две', 3: 'три', 4: 'четыр', 5: 'пят',
|
|
148
|
+
6: 'шест', 7: 'сем', 8: 'восем', 9: 'девят'}
|
|
149
|
+
TENS_POSTFIXES = ['надцать', 'надцати', 'надцати', 'надцать', 'надцатью',
|
|
150
|
+
'надцати']
|
|
151
|
+
TENS = {0: ['десять', 'десяти', 'десяти', 'десять', 'десятью', 'десяти']}
|
|
152
|
+
TENS.update({
|
|
153
|
+
num: [prefix + postfix for postfix in TENS_POSTFIXES]
|
|
154
|
+
for num, prefix in TENS_PREFIXES.items()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
TENS_ORD_PREFIXES = {0: "десят"}
|
|
158
|
+
TENS_ORD_PREFIXES.update({
|
|
159
|
+
num: prefix + 'надцат' for num, prefix in TENS_PREFIXES.items()
|
|
160
|
+
})
|
|
161
|
+
TENS_ORD = get_ord_classifier(TENS_ORD_PREFIXES, 1)
|
|
162
|
+
|
|
163
|
+
TWENTIES = {
|
|
164
|
+
2: ['двадцать', 'двадцати', 'двадцати', 'двадцать', 'двадцатью',
|
|
165
|
+
'двадцати'],
|
|
166
|
+
3: ['тридцать', 'тридцати', 'тридцати', 'тридцать', 'тридцатью',
|
|
167
|
+
'тридцати'],
|
|
168
|
+
4: ['сорок', 'сорока', 'сорока', 'сорок', 'сорока', 'сорока'],
|
|
169
|
+
5: ['пятьдесят', 'пятидесяти', 'пятидесяти', 'пятьдесят', 'пятьюдесятью',
|
|
170
|
+
'пятидесяти'],
|
|
171
|
+
6: ['шестьдесят', 'шестидесяти', 'шестидесяти', 'шестьдесят',
|
|
172
|
+
'шестьюдесятью', 'шестидесяти'],
|
|
173
|
+
7: ['семьдесят', 'семидесяти', 'семидесяти', 'семьдесят', 'семьюдесятью',
|
|
174
|
+
'семидесяти'],
|
|
175
|
+
8: ['восемьдесят', 'восьмидесяти', 'восьмидесяти', 'восемьдесят',
|
|
176
|
+
'восемьюдесятью', 'восьмидесяти'],
|
|
177
|
+
9: ['девяносто', 'девяноста', 'девяноста', 'девяносто', 'девяноста',
|
|
178
|
+
'девяноста'],
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
TWENTIES_ORD_PREFIXES = {2: 'двадцат', 3: 'тридцат', 4: 'сороков',
|
|
182
|
+
5: 'пятидесят', 6: 'шестидесят', 7: 'семидесят',
|
|
183
|
+
8: 'восьмидесят', 9: 'девяност'}
|
|
184
|
+
TWENTIES_ORD_POSTFIXES_GROUPS = {2: 1, 3: 1, 4: 0, 5: 1, 6: 1, 7: 1, 8: 1,
|
|
185
|
+
9: 1}
|
|
186
|
+
TWENTIES_ORD = get_ord_classifier(TWENTIES_ORD_PREFIXES,
|
|
187
|
+
TWENTIES_ORD_POSTFIXES_GROUPS)
|
|
188
|
+
|
|
189
|
+
HUNDREDS = {
|
|
190
|
+
1: ['сто', 'ста', 'ста', 'сто', 'ста', 'ста'],
|
|
191
|
+
2: ['двести', 'двухсот', 'двумстам', 'двести', 'двумястами', 'двухстах'],
|
|
192
|
+
3: ['триста', 'трёхсот', 'трёмстам', 'триста', 'тремястами', 'трёхстах'],
|
|
193
|
+
4: ['четыреста', 'четырёхсот', 'четырёмстам', 'четыреста', 'четырьмястами',
|
|
194
|
+
'четырёхстах'],
|
|
195
|
+
5: ['пятьсот', 'пятисот', 'пятистам', 'пятьсот', 'пятьюстами', 'пятистах'],
|
|
196
|
+
6: ['шестьсот', 'шестисот', 'шестистам', 'шестьсот', 'шестьюстами',
|
|
197
|
+
'шестистах'],
|
|
198
|
+
7: ['семьсот', 'семисот', 'семистам', 'семьсот', 'семьюстами', 'семистах'],
|
|
199
|
+
8: ['восемьсот', 'восьмисот', 'восьмистам', 'восемьсот', 'восемьюстами',
|
|
200
|
+
'восьмистах'],
|
|
201
|
+
9: ['девятьсот', 'девятисот', 'девятистам', 'девятьсот', 'девятьюстами',
|
|
202
|
+
'девятистах'],
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
HUNDREDS_ORD_PREFIXES = {
|
|
206
|
+
num: case[1] if num != 1 else 'сот' for num, case in HUNDREDS.items()
|
|
207
|
+
}
|
|
208
|
+
HUNDREDS_ORD = get_ord_classifier(HUNDREDS_ORD_PREFIXES, 1)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
THOUSANDS_PREFIXES = {2: 'миллион', 3: 'миллиард', 4: 'триллион',
|
|
212
|
+
5: 'квадриллион', 6: 'квинтиллион', 7: 'секстиллион',
|
|
213
|
+
8: 'септиллион', 9: 'октиллион', 10: 'нониллион'}
|
|
214
|
+
THOUSANDS_POSTFIXES = [('', 'а', 'ов'),
|
|
215
|
+
('а', 'ов', 'ов'),
|
|
216
|
+
('у', 'ам', 'ам'),
|
|
217
|
+
('', 'а', 'ов'),
|
|
218
|
+
('ом', 'ами', 'ами'),
|
|
219
|
+
('е', 'ах', 'ах')]
|
|
220
|
+
THOUSANDS = {
|
|
221
|
+
1: [['тысяча', 'тысячи', 'тысяч'],
|
|
222
|
+
['тысячи', 'тысяч', 'тысяч'],
|
|
223
|
+
['тысяче', 'тысячам', 'тысячам'],
|
|
224
|
+
['тысячу', 'тысячи', 'тысяч'],
|
|
225
|
+
['тысячей', 'тысячами', 'тысячами'],
|
|
226
|
+
['тысяче', 'тысячах', 'тысячах']]
|
|
227
|
+
}
|
|
228
|
+
THOUSANDS.update({
|
|
229
|
+
num: [
|
|
230
|
+
[prefix + postfix for postfix in case] for case in THOUSANDS_POSTFIXES
|
|
231
|
+
] for num, prefix in THOUSANDS_PREFIXES.items()
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def get_thousands_elements(num, case):
|
|
236
|
+
return THOUSANDS[num][CASE_INDEXES[case]]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
THOUSANDS_ORD_PREFIXES = {1: 'тысячн'}
|
|
240
|
+
THOUSANDS_ORD_PREFIXES.update({
|
|
241
|
+
num: prefix + 'н' for num, prefix in THOUSANDS_PREFIXES.items()
|
|
242
|
+
})
|
|
243
|
+
THOUSANDS_ORD = get_ord_classifier(THOUSANDS_ORD_PREFIXES, 1)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class Num2Word_RU(Num2Word_Base):
|
|
247
|
+
CURRENCY_FORMS = {
|
|
248
|
+
'RUB': (
|
|
249
|
+
('рубль', 'рубля', 'рублей'), ('копейка', 'копейки', 'копеек')
|
|
250
|
+
),
|
|
251
|
+
'EUR': (
|
|
252
|
+
('евро', 'евро', 'евро'), ('цент', 'цента', 'центов')
|
|
253
|
+
),
|
|
254
|
+
'USD': (
|
|
255
|
+
('доллар', 'доллара', 'долларов'), ('цент', 'цента', 'центов')
|
|
256
|
+
),
|
|
257
|
+
'UAH': (
|
|
258
|
+
('гривна', 'гривны', 'гривен'), ('копейка', 'копейки', 'копеек')
|
|
259
|
+
),
|
|
260
|
+
'KZT': (
|
|
261
|
+
('тенге', 'тенге', 'тенге'), ('тиын', 'тиына', 'тиынов')
|
|
262
|
+
),
|
|
263
|
+
'BYN': (
|
|
264
|
+
('белорусский рубль', 'белорусских рубля', 'белорусских рублей'),
|
|
265
|
+
('копейка', 'копейки', 'копеек')
|
|
266
|
+
),
|
|
267
|
+
'UZS': (
|
|
268
|
+
('сум', 'сума', 'сумов'), ('тийин', 'тийина', 'тийинов')
|
|
269
|
+
),
|
|
270
|
+
'PLN': (
|
|
271
|
+
('польский злотый', 'польских слотых', 'польских злотых'),
|
|
272
|
+
('грош', 'гроша', 'грошей')
|
|
273
|
+
),
|
|
274
|
+
'GBP': (
|
|
275
|
+
('фунт стерлингов', 'фунта стерлингов', 'фунтов стерлингов'),
|
|
276
|
+
('пенни', 'пенни', 'пенни')
|
|
277
|
+
),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
def setup(self):
|
|
281
|
+
self.negword = "минус"
|
|
282
|
+
self.pointword = ('целая', 'целых', 'целых')
|
|
283
|
+
self.pointword_ord = get_cases("цел", 1)
|
|
284
|
+
|
|
285
|
+
def to_cardinal(self, number, case=D_CASE, plural=D_PLURAL,
|
|
286
|
+
gender=D_GENDER, animate=D_ANIMATE):
|
|
287
|
+
# Handle scientific notation
|
|
288
|
+
if isinstance(number, str) and ('e' in number.lower() or 'E' in number):
|
|
289
|
+
number = float(number)
|
|
290
|
+
|
|
291
|
+
# Convert float to proper format if needed
|
|
292
|
+
if isinstance(number, float):
|
|
293
|
+
# For very small numbers close to zero, just return zero
|
|
294
|
+
if abs(number) < 0.01:
|
|
295
|
+
return self._int2word(0, cardinal=True, case=case,
|
|
296
|
+
plural=plural, gender=gender,
|
|
297
|
+
animate=animate)
|
|
298
|
+
# Convert to string with proper precision
|
|
299
|
+
n = str(number)
|
|
300
|
+
else:
|
|
301
|
+
n = str(number)
|
|
302
|
+
|
|
303
|
+
n = n.replace(',', '.')
|
|
304
|
+
if '.' in n:
|
|
305
|
+
is_negative = n.startswith('-')
|
|
306
|
+
abs_n = n[1:] if is_negative else n
|
|
307
|
+
left, right = abs_n.split('.')
|
|
308
|
+
decimal_part = self._int2word(int(right), cardinal=True,
|
|
309
|
+
gender='f')
|
|
310
|
+
result = u'%s %s %s %s' % (
|
|
311
|
+
self._int2word(int(left), cardinal=True, gender='f'),
|
|
312
|
+
self.pluralize(int(left), self.pointword),
|
|
313
|
+
decimal_part,
|
|
314
|
+
self.__decimal_bitness(right)
|
|
315
|
+
)
|
|
316
|
+
if is_negative:
|
|
317
|
+
result = self.negword + ' ' + result
|
|
318
|
+
return result
|
|
319
|
+
else:
|
|
320
|
+
# Handle scientific notation in integer strings
|
|
321
|
+
if 'e' in n.lower() or 'E' in n:
|
|
322
|
+
n = str(int(float(n)))
|
|
323
|
+
return self._int2word(int(n), cardinal=True, case=case,
|
|
324
|
+
plural=plural, gender=gender,
|
|
325
|
+
animate=animate)
|
|
326
|
+
|
|
327
|
+
def __decimal_bitness(self, n):
|
|
328
|
+
if n[-1] == "1" and n[-2:] != "11":
|
|
329
|
+
return self._int2word(10 ** len(n), cardinal=False, gender='f')
|
|
330
|
+
return self._int2word(10 ** len(n), cardinal=False, case='g',
|
|
331
|
+
plural=True)
|
|
332
|
+
|
|
333
|
+
def pluralize(self, n, forms):
|
|
334
|
+
if n % 100 in (11, 12, 13, 14):
|
|
335
|
+
return forms[2]
|
|
336
|
+
if n % 10 == 1:
|
|
337
|
+
return forms[0]
|
|
338
|
+
if n % 10 in (2, 3, 4):
|
|
339
|
+
return forms[1]
|
|
340
|
+
return forms[2]
|
|
341
|
+
|
|
342
|
+
def to_currency(self, val, currency='EUR', cents=True, separator=',',
|
|
343
|
+
adjective=False):
|
|
344
|
+
# Handle scientific notation that results in whole numbers
|
|
345
|
+
# (e.g., 1e+18 becomes 1000000000000000000.0)
|
|
346
|
+
if isinstance(val, float) and val == int(val):
|
|
347
|
+
val = int(val)
|
|
348
|
+
|
|
349
|
+
# Handle integers specially - just add currency name without cents
|
|
350
|
+
if isinstance(val, int):
|
|
351
|
+
try:
|
|
352
|
+
cr1, cr2 = self.CURRENCY_FORMS[currency]
|
|
353
|
+
except (KeyError, AttributeError):
|
|
354
|
+
# Fallback to base implementation for unknown currency
|
|
355
|
+
return super(Num2Word_RU, self).to_currency(
|
|
356
|
+
val, currency=currency, cents=cents, separator=separator,
|
|
357
|
+
adjective=adjective)
|
|
358
|
+
|
|
359
|
+
minus_str = self.negword if val < 0 else ""
|
|
360
|
+
abs_val = abs(val)
|
|
361
|
+
money_str = self.to_cardinal(abs_val)
|
|
362
|
+
|
|
363
|
+
# Use the pluralize method for proper currency form selection
|
|
364
|
+
currency_str = self.pluralize(abs_val, cr1)
|
|
365
|
+
|
|
366
|
+
return (u'%s %s %s' % (minus_str, money_str, currency_str)).strip()
|
|
367
|
+
|
|
368
|
+
# For floats, use the parent class implementation
|
|
369
|
+
return super(Num2Word_RU, self).to_currency(
|
|
370
|
+
val, currency=currency, cents=cents, separator=separator,
|
|
371
|
+
adjective=adjective)
|
|
372
|
+
|
|
373
|
+
def to_ordinal(self, number, case=D_CASE, plural=D_PLURAL, gender=D_GENDER,
|
|
374
|
+
animate=D_ANIMATE):
|
|
375
|
+
self.verify_ordinal(number)
|
|
376
|
+
n = str(number).replace(',', '.')
|
|
377
|
+
return self._int2word(int(n), cardinal=False, case=case, plural=plural,
|
|
378
|
+
gender=gender, animate=animate)
|
|
379
|
+
|
|
380
|
+
def _money_verbose(self, number, currency):
|
|
381
|
+
if currency == 'UAH':
|
|
382
|
+
return self._int2word(number, gender='f')
|
|
383
|
+
return self._int2word(number, gender='m')
|
|
384
|
+
|
|
385
|
+
def _cents_verbose(self, number, currency):
|
|
386
|
+
# Handle scientific notation or very small decimals
|
|
387
|
+
if isinstance(number, str) and ('e' in number.lower() or 'E' in number):
|
|
388
|
+
number = float(number)
|
|
389
|
+
# Convert float to int if it represents a whole number
|
|
390
|
+
if isinstance(number, float):
|
|
391
|
+
# For very small numbers, round to avoid scientific notation issues
|
|
392
|
+
number = round(number * 100) / 100 # Round to 2 decimal places
|
|
393
|
+
if number == int(number):
|
|
394
|
+
number = int(number)
|
|
395
|
+
|
|
396
|
+
if currency in ('UAH', 'RUB', 'BYN'):
|
|
397
|
+
return self._int2word(int(number), gender='f')
|
|
398
|
+
return self._int2word(int(number), gender='m')
|
|
399
|
+
|
|
400
|
+
def _int2word(self, n, feminine=False, cardinal=True, case=D_CASE,
|
|
401
|
+
plural=D_PLURAL, gender=D_GENDER, animate=D_ANIMATE):
|
|
402
|
+
"""
|
|
403
|
+
n: number
|
|
404
|
+
feminine: not used - for backward compatibility
|
|
405
|
+
cardinal:True - cardinal
|
|
406
|
+
False - ordinal
|
|
407
|
+
case: 'n' - nominative
|
|
408
|
+
'g' - genitive
|
|
409
|
+
'd' - dative
|
|
410
|
+
'a' - accusative
|
|
411
|
+
'i' - instrumental
|
|
412
|
+
'p' - prepositional
|
|
413
|
+
plural: True - plural
|
|
414
|
+
False - singular
|
|
415
|
+
gender: 'f' - masculine
|
|
416
|
+
'm' - feminine
|
|
417
|
+
'n' - neuter
|
|
418
|
+
animate: True - animate
|
|
419
|
+
False - inanimate
|
|
420
|
+
"""
|
|
421
|
+
# For backward compatibility
|
|
422
|
+
if feminine:
|
|
423
|
+
gender = 'f'
|
|
424
|
+
|
|
425
|
+
kwargs = {'case': case, 'plural': plural, 'gender': gender,
|
|
426
|
+
'animate': animate}
|
|
427
|
+
|
|
428
|
+
if n < 0:
|
|
429
|
+
return ' '.join([self.negword, self._int2word(abs(n),
|
|
430
|
+
cardinal=cardinal,
|
|
431
|
+
**kwargs)])
|
|
432
|
+
|
|
433
|
+
if n == 0:
|
|
434
|
+
return get_num_element(ONES, 0, **kwargs) if cardinal else \
|
|
435
|
+
get_num_element(ONES_ORD, 0, **kwargs)
|
|
436
|
+
|
|
437
|
+
words = []
|
|
438
|
+
chunks = list(splitbyx(str(n), 3))
|
|
439
|
+
ord_join = chunks[-1] == 0 # join in one word if ending on 'тысячный'
|
|
440
|
+
i = len(chunks)
|
|
441
|
+
rightest_nonzero_chunk_i = i - 1 - max(
|
|
442
|
+
[i for i, e in enumerate(chunks) if e != 0])
|
|
443
|
+
for x in chunks:
|
|
444
|
+
chunk_words = []
|
|
445
|
+
i -= 1
|
|
446
|
+
|
|
447
|
+
if x == 0:
|
|
448
|
+
continue
|
|
449
|
+
|
|
450
|
+
n1, n2, n3 = get_digits(x)
|
|
451
|
+
|
|
452
|
+
if cardinal:
|
|
453
|
+
chunk_words.extend(
|
|
454
|
+
self.__chunk_cardianl(n3, n2, n1, i, **kwargs)
|
|
455
|
+
)
|
|
456
|
+
if i > 0:
|
|
457
|
+
chunk_words.append(
|
|
458
|
+
self.pluralize(x, get_thousands_elements(i, case)))
|
|
459
|
+
# ordinal, not joined like 'двухтысячный'
|
|
460
|
+
elif not (ord_join and rightest_nonzero_chunk_i == i):
|
|
461
|
+
chunk_words.extend(
|
|
462
|
+
self.__chunk_ordinal(n3, n2, n1, i, **kwargs)
|
|
463
|
+
)
|
|
464
|
+
if i > 0:
|
|
465
|
+
t_case = case if rightest_nonzero_chunk_i == i else 'n'
|
|
466
|
+
chunk_words.append(
|
|
467
|
+
self.pluralize(x, get_thousands_elements(i, t_case)))
|
|
468
|
+
# ordinal, joined
|
|
469
|
+
else:
|
|
470
|
+
chunk_words.extend(
|
|
471
|
+
self.__chunk_ordinal_join(n3, n2, n1, i, **kwargs)
|
|
472
|
+
)
|
|
473
|
+
if i > 0:
|
|
474
|
+
chunk_words.append(
|
|
475
|
+
get_num_element(THOUSANDS_ORD, i, **kwargs))
|
|
476
|
+
|
|
477
|
+
chunk_words = [''.join(chunk_words)]
|
|
478
|
+
|
|
479
|
+
words.extend(chunk_words)
|
|
480
|
+
|
|
481
|
+
return ' '.join(words)
|
|
482
|
+
|
|
483
|
+
def __chunk_cardianl(self, hundreds, tens, ones, chunk_num, **kwargs):
|
|
484
|
+
words = []
|
|
485
|
+
if hundreds > 0:
|
|
486
|
+
words.append(get_num_element(HUNDREDS, hundreds, **kwargs))
|
|
487
|
+
|
|
488
|
+
if tens > 1:
|
|
489
|
+
words.append(get_num_element(TWENTIES, tens, **kwargs))
|
|
490
|
+
|
|
491
|
+
if tens == 1:
|
|
492
|
+
words.append(get_num_element(TENS, ones, **kwargs))
|
|
493
|
+
elif ones > 0:
|
|
494
|
+
if chunk_num == 0:
|
|
495
|
+
w_ones = get_num_element(ONES, ones, **kwargs)
|
|
496
|
+
elif chunk_num == 1:
|
|
497
|
+
# Thousands are feminine
|
|
498
|
+
f_kwargs = kwargs.copy()
|
|
499
|
+
f_kwargs['gender'] = 'f'
|
|
500
|
+
w_ones = get_num_element(ONES, ones, **f_kwargs)
|
|
501
|
+
else:
|
|
502
|
+
w_ones = get_num_element(ONES, ones, **kwargs)
|
|
503
|
+
|
|
504
|
+
words.append(w_ones)
|
|
505
|
+
return words
|
|
506
|
+
|
|
507
|
+
def __chunk_ordinal(self, hundreds, tens, ones, chunk_num, **kwargs):
|
|
508
|
+
words = []
|
|
509
|
+
if hundreds > 0:
|
|
510
|
+
if tens == 0 and ones == 0:
|
|
511
|
+
words.append(get_num_element(HUNDREDS_ORD, hundreds, **kwargs))
|
|
512
|
+
else:
|
|
513
|
+
words.append(get_num_element(HUNDREDS, hundreds))
|
|
514
|
+
|
|
515
|
+
if tens > 1:
|
|
516
|
+
if ones == 0:
|
|
517
|
+
words.append(get_num_element(TWENTIES_ORD, tens, **kwargs))
|
|
518
|
+
else:
|
|
519
|
+
words.append(get_num_element(TWENTIES, tens))
|
|
520
|
+
|
|
521
|
+
if tens == 1:
|
|
522
|
+
words.append(get_num_element(TENS_ORD, ones, **kwargs))
|
|
523
|
+
elif ones > 0:
|
|
524
|
+
if chunk_num == 0:
|
|
525
|
+
w_ones = get_num_element(ONES_ORD, ones, **kwargs)
|
|
526
|
+
# тысячный, миллионнный и т.д.
|
|
527
|
+
elif chunk_num > 0 and ones == 1 and hundreds == 0 and tens == 0:
|
|
528
|
+
w_ones = None
|
|
529
|
+
elif chunk_num == 1:
|
|
530
|
+
# Thousands are feminine
|
|
531
|
+
w_ones = get_num_element(ONES, ones, gender='f')
|
|
532
|
+
else:
|
|
533
|
+
w_ones = get_num_element(ONES, ones)
|
|
534
|
+
|
|
535
|
+
if w_ones:
|
|
536
|
+
words.append(w_ones)
|
|
537
|
+
|
|
538
|
+
return words
|
|
539
|
+
|
|
540
|
+
def __chunk_ordinal_join(self, hundreds, tens, ones, chunk_num, **kwargs):
|
|
541
|
+
words = []
|
|
542
|
+
|
|
543
|
+
if hundreds > 1:
|
|
544
|
+
words.append(get_num_element(HUNDREDS, hundreds, case='g'))
|
|
545
|
+
elif hundreds == 1:
|
|
546
|
+
words.append(get_num_element(HUNDREDS, hundreds)) # стО, not стА
|
|
547
|
+
|
|
548
|
+
if tens == 9:
|
|
549
|
+
words.append(get_num_element(TWENTIES, tens)) # девяностО, not А
|
|
550
|
+
elif tens > 1:
|
|
551
|
+
words.append(get_num_element(TWENTIES, tens, case='g'))
|
|
552
|
+
|
|
553
|
+
if tens == 1:
|
|
554
|
+
words.append(get_num_element(TENS, ones, case='g'))
|
|
555
|
+
elif ones > 0:
|
|
556
|
+
if chunk_num == 0:
|
|
557
|
+
w_ones = get_num_element(ONES_ORD, ones, **kwargs)
|
|
558
|
+
# тысячный, миллионнный и т.д., двадцатиодномиллионный
|
|
559
|
+
elif chunk_num > 0 and ones == 1 and tens != 1:
|
|
560
|
+
if tens == 0 and hundreds == 0:
|
|
561
|
+
w_ones = None
|
|
562
|
+
else:
|
|
563
|
+
w_ones = get_num_element(ONES, 1, gender='n')
|
|
564
|
+
else:
|
|
565
|
+
w_ones = get_num_element(ONES, ones, case='g')
|
|
566
|
+
|
|
567
|
+
if w_ones:
|
|
568
|
+
words.append(w_ones)
|
|
569
|
+
|
|
570
|
+
return words
|