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.
Files changed (133) hide show
  1. num2words2/README.md +69 -0
  2. num2words2/__init__.py +196 -0
  3. num2words2/_version.py +3 -0
  4. num2words2/base.py +377 -0
  5. num2words2/compat.py +29 -0
  6. num2words2/currency.py +60 -0
  7. num2words2/lang_AF.py +202 -0
  8. num2words2/lang_AM.py +193 -0
  9. num2words2/lang_AR.py +435 -0
  10. num2words2/lang_AS.py +119 -0
  11. num2words2/lang_AZ.py +208 -0
  12. num2words2/lang_BA.py +119 -0
  13. num2words2/lang_BE.py +356 -0
  14. num2words2/lang_BG.py +393 -0
  15. num2words2/lang_BN.py +242 -0
  16. num2words2/lang_BO.py +122 -0
  17. num2words2/lang_BR.py +135 -0
  18. num2words2/lang_BS.py +189 -0
  19. num2words2/lang_CA.py +476 -0
  20. num2words2/lang_CE.py +546 -0
  21. num2words2/lang_CS.py +260 -0
  22. num2words2/lang_CY.py +574 -0
  23. num2words2/lang_DA.py +170 -0
  24. num2words2/lang_DE.py +180 -0
  25. num2words2/lang_EL.py +338 -0
  26. num2words2/lang_EN.py +118 -0
  27. num2words2/lang_EN_IN.py +26 -0
  28. num2words2/lang_EN_NG.py +45 -0
  29. num2words2/lang_EO.py +130 -0
  30. num2words2/lang_ES.py +399 -0
  31. num2words2/lang_ES_CO.py +37 -0
  32. num2words2/lang_ES_CR.py +38 -0
  33. num2words2/lang_ES_GT.py +37 -0
  34. num2words2/lang_ES_NI.py +35 -0
  35. num2words2/lang_ES_VE.py +43 -0
  36. num2words2/lang_ET.py +393 -0
  37. num2words2/lang_EU.py +158 -0
  38. num2words2/lang_EUR.py +104 -0
  39. num2words2/lang_FA.py +187 -0
  40. num2words2/lang_FI.py +759 -0
  41. num2words2/lang_FO.py +136 -0
  42. num2words2/lang_FR.py +133 -0
  43. num2words2/lang_FR_BE.py +49 -0
  44. num2words2/lang_FR_CH.py +48 -0
  45. num2words2/lang_FR_DZ.py +35 -0
  46. num2words2/lang_GL.py +135 -0
  47. num2words2/lang_GU.py +172 -0
  48. num2words2/lang_HA.py +293 -0
  49. num2words2/lang_HAW.py +144 -0
  50. num2words2/lang_HE.py +314 -0
  51. num2words2/lang_HI.py +243 -0
  52. num2words2/lang_HR.py +304 -0
  53. num2words2/lang_HT.py +136 -0
  54. num2words2/lang_HU.py +189 -0
  55. num2words2/lang_HY.py +364 -0
  56. num2words2/lang_ID.py +242 -0
  57. num2words2/lang_IS.py +208 -0
  58. num2words2/lang_IT.py +265 -0
  59. num2words2/lang_JA.py +615 -0
  60. num2words2/lang_JW.py +136 -0
  61. num2words2/lang_KA.py +169 -0
  62. num2words2/lang_KK.py +136 -0
  63. num2words2/lang_KM.py +173 -0
  64. num2words2/lang_KN.py +182 -0
  65. num2words2/lang_KO.py +175 -0
  66. num2words2/lang_KZ.py +161 -0
  67. num2words2/lang_LA.py +135 -0
  68. num2words2/lang_LB.py +135 -0
  69. num2words2/lang_LN.py +136 -0
  70. num2words2/lang_LO.py +149 -0
  71. num2words2/lang_LT.py +283 -0
  72. num2words2/lang_LV.py +266 -0
  73. num2words2/lang_MG.py +143 -0
  74. num2words2/lang_MI.py +151 -0
  75. num2words2/lang_MK.py +136 -0
  76. num2words2/lang_ML.py +159 -0
  77. num2words2/lang_MN.py +319 -0
  78. num2words2/lang_MR.py +187 -0
  79. num2words2/lang_MS.py +293 -0
  80. num2words2/lang_MT.py +159 -0
  81. num2words2/lang_MY.py +152 -0
  82. num2words2/lang_NE.py +157 -0
  83. num2words2/lang_NL.py +204 -0
  84. num2words2/lang_NN.py +136 -0
  85. num2words2/lang_NO.py +143 -0
  86. num2words2/lang_OC.py +135 -0
  87. num2words2/lang_PA.py +159 -0
  88. num2words2/lang_PL.py +336 -0
  89. num2words2/lang_PS.py +136 -0
  90. num2words2/lang_PT.py +280 -0
  91. num2words2/lang_PT_BR.py +224 -0
  92. num2words2/lang_RO.py +183 -0
  93. num2words2/lang_RU.py +570 -0
  94. num2words2/lang_SA.py +136 -0
  95. num2words2/lang_SD.py +136 -0
  96. num2words2/lang_SI.py +152 -0
  97. num2words2/lang_SK.py +250 -0
  98. num2words2/lang_SL.py +306 -0
  99. num2words2/lang_SN.py +447 -0
  100. num2words2/lang_SO.py +136 -0
  101. num2words2/lang_SQ.py +252 -0
  102. num2words2/lang_SR.py +254 -0
  103. num2words2/lang_SU.py +136 -0
  104. num2words2/lang_SV.py +169 -0
  105. num2words2/lang_SW.py +219 -0
  106. num2words2/lang_TA.py +303 -0
  107. num2words2/lang_TE.py +169 -0
  108. num2words2/lang_TET.py +285 -0
  109. num2words2/lang_TG.py +149 -0
  110. num2words2/lang_TH.py +214 -0
  111. num2words2/lang_TK.py +136 -0
  112. num2words2/lang_TL.py +145 -0
  113. num2words2/lang_TR.py +882 -0
  114. num2words2/lang_TT.py +136 -0
  115. num2words2/lang_UK.py +1101 -0
  116. num2words2/lang_UR.py +168 -0
  117. num2words2/lang_UZ.py +136 -0
  118. num2words2/lang_VI.py +143 -0
  119. num2words2/lang_WO.py +136 -0
  120. num2words2/lang_YI.py +135 -0
  121. num2words2/lang_YO.py +136 -0
  122. num2words2/lang_ZH.py +296 -0
  123. num2words2/lang_ZH_CN.py +85 -0
  124. num2words2/lang_ZH_HK.py +48 -0
  125. num2words2/lang_ZH_TW.py +198 -0
  126. num2words2/utils.py +35 -0
  127. num2words2-0.1.0.dev1.data/scripts/num2words2 +98 -0
  128. num2words2-0.1.0.dev1.dist-info/METADATA +244 -0
  129. num2words2-0.1.0.dev1.dist-info/RECORD +133 -0
  130. num2words2-0.1.0.dev1.dist-info/WHEEL +5 -0
  131. num2words2-0.1.0.dev1.dist-info/entry_points.txt +2 -0
  132. num2words2-0.1.0.dev1.dist-info/licenses/COPYING +145 -0
  133. num2words2-0.1.0.dev1.dist-info/top_level.txt +1 -0
num2words2/lang_PS.py ADDED
@@ -0,0 +1,136 @@
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
+
22
+
23
+ # Pashto language support
24
+ class Num2Word_PS(Num2Word_Base):
25
+ CURRENCY_FORMS = {
26
+ 'AFN': (('افغانۍ', 'افغانۍ'), ('پول', 'پول')),
27
+ 'USD': (('dollar', 'dollars'), ('cent', 'cents')),
28
+ 'EUR': (('euro', 'euros'), ('cent', 'cents'))
29
+ }
30
+
31
+ def setup(self):
32
+ self.negword = "minus "
33
+ self.pointword = "point"
34
+ self.ones = ['', 'یو', 'دوه', 'درې', 'څلور', 'پنځه', 'شپږ', 'اووه', 'اته', 'نهه']
35
+ self.tens = ['', 'لس', 'شل', 'دېرش', 'څلوېښت', 'پنځوس', 'شپېته', 'اویا', 'اتیا', 'نوي']
36
+ self.hundred = "سل"
37
+ self.thousand = "زره"
38
+ self.million = "میلیون"
39
+
40
+ def to_cardinal(self, number):
41
+ """Convert a number to its word representation in Pashto."""
42
+ n = str(number).strip()
43
+
44
+ if n.startswith('-'):
45
+ n = n[1:]
46
+ ret = self.negword
47
+ else:
48
+ ret = ""
49
+
50
+ if '.' in n:
51
+ left, right = n.split('.', 1)
52
+ ret += self._int_to_word(int(left)) + " " + self.pointword + " "
53
+ for digit in right:
54
+ ret += self._int_to_word(int(digit)) + " "
55
+ return ret.strip()
56
+ else:
57
+ return (ret + self._int_to_word(int(n))).strip()
58
+
59
+ def _int_to_word(self, number):
60
+ """Convert an integer to its word representation in Pashto."""
61
+ if number == 0:
62
+ return self.ones[0] if self.ones[0] else "zero"
63
+
64
+ if number < 0:
65
+ return self.negword + self._int_to_word(abs(number))
66
+ elif number < 10:
67
+ return self.ones[number]
68
+ elif number < 100:
69
+ tens_val = number // 10
70
+ ones_val = number % 10
71
+ if ones_val == 0:
72
+ return self.tens[tens_val]
73
+ else:
74
+ return self.tens[tens_val] + " " + self.ones[ones_val]
75
+ elif number < 1000:
76
+ hundreds_val = number // 100
77
+ remainder = number % 100
78
+ result = self.ones[hundreds_val] + " " + self.hundred
79
+ if remainder:
80
+ result += " " + self._int_to_word(remainder)
81
+ return result
82
+ elif number < 1000000:
83
+ thousands_val = number // 1000
84
+ remainder = number % 1000
85
+ result = self._int_to_word(thousands_val) + " " + self.thousand
86
+ if remainder:
87
+ result += " " + self._int_to_word(remainder)
88
+ return result
89
+ elif number < 1000000000:
90
+ millions_val = number // 1000000
91
+ remainder = number % 1000000
92
+ result = self._int_to_word(millions_val) + " " + self.million
93
+ if remainder:
94
+ result += " " + self._int_to_word(remainder)
95
+ return result
96
+ else:
97
+ return str(number) # Fallback for very large numbers
98
+
99
+ def to_ordinal(self, number):
100
+ """Convert to ordinal in Pashto."""
101
+ cardinal = self.to_cardinal(number)
102
+ # Language-specific ordinal suffix
103
+ return cardinal + "-م"
104
+
105
+ def to_ordinal_num(self, number):
106
+ """Convert to abbreviated ordinal."""
107
+ return str(number) + "."
108
+
109
+ def to_year(self, val, longval=True):
110
+ """Convert to year in Pashto."""
111
+ return self.to_cardinal(val)
112
+
113
+ def to_currency(self, val, currency='AFN', cents=True, separator=' ', adjective=False):
114
+ """Convert to currency in Pashto."""
115
+ is_negative = False
116
+ if val < 0:
117
+ is_negative = True
118
+ val = abs(val)
119
+
120
+ parts = str(val).split('.')
121
+ left = int(parts[0]) if parts[0] else 0
122
+ right = int(parts[1][:2].ljust(2, '0')) if len(parts) > 1 and parts[1] else 0
123
+
124
+ cr1, cr2 = self.CURRENCY_FORMS.get(currency, list(self.CURRENCY_FORMS.values())[0])
125
+
126
+ left_str = self._int_to_word(left)
127
+ result = left_str + " " + (cr1[1] if left != 1 else cr1[0])
128
+
129
+ if cents and right:
130
+ cents_str = self._int_to_word(right)
131
+ result += separator + cents_str + " " + (cr2[1] if right != 1 else cr2[0])
132
+
133
+ if is_negative:
134
+ result = self.negword + result
135
+
136
+ return result.strip()
num2words2/lang_PT.py ADDED
@@ -0,0 +1,280 @@
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 division, unicode_literals
19
+
20
+ import re
21
+
22
+ from .lang_EUR import Num2Word_EUR
23
+
24
+ DOLLAR = ('dólar', 'dólares')
25
+ CENTS = ('cêntimo', 'cêntimos')
26
+
27
+
28
+ class Num2Word_PT(Num2Word_EUR):
29
+
30
+ CURRENCY_FORMS = {
31
+ 'AUD': (DOLLAR, CENTS),
32
+ 'BRL': (('real', 'reais'), ('centavo', 'centavos')),
33
+ 'CAD': (DOLLAR, CENTS),
34
+ 'EUR': (('euro', 'euros'), CENTS),
35
+ 'GBP': (('libra', 'libras'), ('péni', 'pence')),
36
+ 'USD': (DOLLAR, CENTS),
37
+ }
38
+
39
+ GIGA_SUFFIX = None
40
+ MEGA_SUFFIX = "ilião"
41
+
42
+ def setup(self):
43
+ super(Num2Word_PT, self).setup()
44
+ lows = ["quatr", "tr", "b", "m"]
45
+ self.high_numwords = self.gen_high_numwords([], [], lows)
46
+ self.negword = "menos "
47
+ self.pointword = "vírgula"
48
+ self.exclude_title = ["e", "vírgula", "menos"]
49
+
50
+ self.mid_numwords = [
51
+ (1000, "mil"), (100, "cem"), (90, "noventa"),
52
+ (80, "oitenta"), (70, "setenta"), (60, "sessenta"),
53
+ (50, "cinquenta"), (40, "quarenta"), (30, "trinta")
54
+ ]
55
+ self.low_numwords = [
56
+ "vinte", "dezanove", "dezoito", "dezassete", "dezasseis",
57
+ "quinze", "catorze", "treze", "doze", "onze", "dez",
58
+ "nove", "oito", "sete", "seis", "cinco", "quatro", "três", "dois",
59
+ "um", "zero"
60
+ ]
61
+ self.ords = [
62
+ {
63
+ 0: "",
64
+ 1: "primeiro",
65
+ 2: "segundo",
66
+ 3: "terceiro",
67
+ 4: "quarto",
68
+ 5: "quinto",
69
+ 6: "sexto",
70
+ 7: "sétimo",
71
+ 8: "oitavo",
72
+ 9: "nono",
73
+ },
74
+ {
75
+ 0: "",
76
+ 1: "décimo",
77
+ 2: "vigésimo",
78
+ 3: "trigésimo",
79
+ 4: "quadragésimo",
80
+ 5: "quinquagésimo",
81
+ 6: "sexagésimo",
82
+ 7: "septuagésimo",
83
+ 8: "octogésimo",
84
+ 9: "nonagésimo",
85
+ },
86
+ {
87
+ 0: "",
88
+ 1: "centésimo",
89
+ 2: "ducentésimo",
90
+ 3: "tricentésimo",
91
+ 4: "quadrigentésimo",
92
+ 5: "quingentésimo",
93
+ 6: "seiscentésimo",
94
+ 7: "septigentésimo",
95
+ 8: "octigentésimo",
96
+ 9: "nongentésimo",
97
+ },
98
+ ]
99
+ self.thousand_separators = {
100
+ 3: "milésimo",
101
+ 6: "milionésimo",
102
+ 9: "milésimo milionésimo",
103
+ 12: "bilionésimo",
104
+ 15: "milésimo bilionésimo"
105
+ }
106
+ self.hundreds = {
107
+ 1: "cento",
108
+ 2: "duzentos",
109
+ 3: "trezentos",
110
+ 4: "quatrocentos",
111
+ 5: "quinhentos",
112
+ 6: "seiscentos",
113
+ 7: "setecentos",
114
+ 8: "oitocentos",
115
+ 9: "novecentos",
116
+ }
117
+
118
+ def merge(self, curr, next):
119
+ ctext, cnum, ntext, nnum = curr + next
120
+
121
+ if cnum == 1:
122
+ if nnum < 1000000:
123
+ return next
124
+ ctext = "um"
125
+ elif cnum == 100 and not nnum % 1000 == 0:
126
+ ctext = "cento"
127
+
128
+ if nnum < cnum:
129
+ if cnum < 100:
130
+ return ("%s e %s" % (ctext, ntext), cnum + nnum)
131
+ return ("%s e %s" % (ctext, ntext), cnum + nnum)
132
+
133
+ elif (not nnum % 1000000000) and cnum > 1:
134
+ ntext = ntext[:-4] + "liões"
135
+ elif (not nnum % 1000000) and cnum > 1:
136
+ ntext = ntext[:-4] + "lhões"
137
+ # correct "milião" to "milhão"
138
+ if ntext == 'milião':
139
+ ntext = 'milhão'
140
+ if nnum == 100:
141
+ ctext = self.hundreds[cnum]
142
+ ntext = ""
143
+
144
+ else:
145
+ ntext = " " + ntext
146
+
147
+ return (ctext + ntext, cnum * nnum)
148
+
149
+ def to_cardinal(self, value):
150
+ result = super(Num2Word_PT, self).to_cardinal(value)
151
+
152
+ # Transforms "mil e cento e catorze" into "mil cento e catorze"
153
+ # Transforms "cem milhões e duzentos mil e duzentos e dez" em "cem
154
+ # milhões duzentos mil duzentos e dez" but "cem milhões e duzentos
155
+ # mil e duzentos" in "cem milhões duzentos mil e duzentos" and not in
156
+ # "cem milhões duzentos mil duzentos"
157
+ for ext in (
158
+ 'mil', 'milhão', 'milhões', 'mil milhões',
159
+ 'bilião', 'biliões', 'mil biliões'):
160
+ if re.match('.*{} e \\w*entos? (?=.*e)'.format(ext), result):
161
+ result = result.replace(
162
+ '{} e'.format(ext), '{}'.format(ext)
163
+ )
164
+
165
+ return result
166
+
167
+ # for the ordinal conversion the code is similar to pt_BR code,
168
+ # although there are other rules that are probably more correct in
169
+ # Portugal. Concerning numbers from 2000th on, saying "dois
170
+ # milésimos" instead of "segundo milésimo" (the first number
171
+ # would be used in the cardinal form instead of the ordinal) is better.
172
+ # This was not implemented.
173
+ # source:
174
+ # https://ciberduvidas.iscte-iul.pt/consultorio/perguntas/a-forma-por-extenso-de-2000-e-de-outros-ordinais/16428
175
+ def to_ordinal(self, value):
176
+ # Before changing this function remember this is used by pt-BR
177
+ # so act accordingly
178
+ self.verify_ordinal(value)
179
+
180
+ result = []
181
+ value = str(value)
182
+ thousand_separator = ''
183
+
184
+ for idx, char in enumerate(value[::-1]):
185
+ if idx and idx % 3 == 0:
186
+ thousand_separator = self.thousand_separators[idx]
187
+
188
+ if char != '0' and thousand_separator:
189
+ # avoiding "segundo milionésimo milésimo" for 6000000,
190
+ # for instance
191
+ result.append(thousand_separator)
192
+ thousand_separator = ''
193
+
194
+ result.append(self.ords[idx % 3][int(char)])
195
+
196
+ result = ' '.join(result[::-1])
197
+ result = result.strip()
198
+ result = re.sub('\\s+', ' ', result)
199
+
200
+ if result.startswith('primeiro') and value != '1':
201
+ # avoiding "primeiro milésimo", "primeiro milionésimo" and so on
202
+ result = result[9:]
203
+
204
+ return result
205
+
206
+ def to_ordinal_num(self, value):
207
+ # Before changing this function remember this is used by pt-BR
208
+ # so act accordingly
209
+ self.verify_ordinal(value)
210
+ return "%sº" % (value)
211
+
212
+ def to_year(self, val, longval=True):
213
+ # Before changing this function remember this is used by pt-BR
214
+ # so act accordingly
215
+ if val < 0:
216
+ return self.to_cardinal(abs(val)) + ' antes de Cristo'
217
+ return self.to_cardinal(val)
218
+
219
+ def to_currency(self, val, currency='EUR', cents=True, separator=' e',
220
+ adjective=False):
221
+ # Track if input was originally an integer (not float)
222
+ is_integer = isinstance(val, int)
223
+
224
+ # Handle integers specially - just add currency name without cents
225
+ if is_integer:
226
+ try:
227
+ cr1, cr2 = self.CURRENCY_FORMS[currency]
228
+ except KeyError:
229
+ return self.to_cardinal(val)
230
+
231
+ # change negword for currency
232
+ backup_negword = self.negword
233
+ self.negword = self.negword[:-1]
234
+
235
+ minus_str = "%s " % self.negword.strip() if val < 0 else ""
236
+ abs_val = abs(int(val) if isinstance(val, float) else val)
237
+ money_str = self.to_cardinal(abs_val)
238
+
239
+ # restore negword
240
+ self.negword = backup_negword
241
+
242
+ # Proper pluralization for currency
243
+ if abs_val == 1:
244
+ currency_str = cr1[0] # singular
245
+ else:
246
+ currency_str = cr1[1] if len(cr1) > 1 else cr1[0] # plural
247
+
248
+ result = "%s%s %s" % (minus_str, money_str, currency_str)
249
+
250
+ # transforms "milhões euros" em "milhões de euros"
251
+ for ext in ('milhão', 'milhões', 'bilião', 'biliões', 'trilião', 'triliões'):
252
+ if re.match('.*{} (?={})'.format(ext, currency_str), result):
253
+ result = result.replace(
254
+ '{}'.format(ext), '{} de'.format(ext), 1
255
+ )
256
+
257
+ return result.strip()
258
+
259
+ # For floats with non-zero decimal part, use the parent class implementation
260
+ backup_negword = self.negword
261
+ self.negword = self.negword[:-1]
262
+ result = super(Num2Word_PT, self).to_currency(
263
+ val, currency=currency, cents=cents, separator=separator,
264
+ adjective=adjective)
265
+ # undo the change on negword
266
+ self.negword = backup_negword
267
+
268
+ # transforms "milhões euros" em "milhões de euros"
269
+ cr1, _ = self.CURRENCY_FORMS[currency]
270
+
271
+ for ext in (
272
+ 'milhão', 'milhões', 'bilião',
273
+ 'biliões', 'trilião', 'triliões'):
274
+ if re.match('.*{} (?={})'.format(ext, cr1[1]), result):
275
+ result = result.replace(
276
+ '{}'.format(ext), '{} de'.format(ext), 1
277
+ )
278
+ # For floats, keep "e zero cêntimos" to show it's a precise amount
279
+ # Only remove for integers (but we handle integers separately now)
280
+ return result
@@ -0,0 +1,224 @@
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 print_function, unicode_literals
19
+
20
+ import re
21
+
22
+ from . import lang_PT
23
+
24
+
25
+ def negativeword(self):
26
+ return self.negword
27
+
28
+
29
+ class Num2Word_PT_BR(lang_PT.Num2Word_PT):
30
+ negword = 'menos '
31
+
32
+ CURRENCY_FORMS = {
33
+ 'BRL': (('real', 'reais'), ('centavo', 'centavos')),
34
+ 'EUR': (('euro', 'euros'), ('cêntimo', 'cêntimos')),
35
+ 'USD': (('dólar', 'dólares'), ('centavo', 'centavos')),
36
+ }
37
+
38
+ def __init__(self):
39
+ super(Num2Word_PT_BR, self).__init__()
40
+ self.negword = 'menos '
41
+
42
+ def setup(self):
43
+ # First call parent setup
44
+ super(Num2Word_PT_BR, self).setup()
45
+
46
+ # Brazilian Portuguese uses different spelling for 16, 17, 19
47
+ self.low_numwords = [
48
+ "vinte", "dezenove", "dezoito", "dezessete", "dezesseis",
49
+ "quinze", "catorze", "treze", "doze", "onze", "dez",
50
+ "nove", "oito", "sete", "seis", "cinco", "quatro", "três", "dois",
51
+ "um", "zero"
52
+ ]
53
+
54
+ # Override thousand separators for Brazilian Portuguese ordinals
55
+ # Brazilian uses short scale: bilhão = 10^9, trilhão = 10^12
56
+ self.thousand_separators = {
57
+ 3: "milésimo",
58
+ 6: "milionésimo",
59
+ 9: "bilionésimo", # Brazilian billion = 10^9
60
+ 12: "trilionésimo", # Brazilian trillion = 10^12
61
+ 15: "quatrilionésimo" # Brazilian quadrillion = 10^15
62
+ }
63
+
64
+ def merge(self, curr, next):
65
+ ctext, cnum, ntext, nnum = curr + next
66
+
67
+ if cnum == 1:
68
+ if nnum < 1000000:
69
+ return (ntext, cnum * nnum)
70
+ ctext = "um"
71
+ elif cnum == 100 and nnum > 0 and nnum < 1000:
72
+ # In Brazilian Portuguese, 100 + any number up to 999 becomes "cento"
73
+ # But 100 * 1000 = 100000 stays as "cem mil"
74
+ ctext = "cento"
75
+
76
+ if nnum > cnum:
77
+ # Multiplicative case (e.g., "dois mil")
78
+ if (not nnum % 1000000) and cnum > 1:
79
+ # Handle Brazilian scale for millions
80
+ if nnum == 1000000:
81
+ ntext = "milhões"
82
+ else:
83
+ ntext = ntext[:-4] + "lhões"
84
+
85
+ if nnum == 100:
86
+ ctext = self.hundreds[cnum]
87
+ ntext = ""
88
+ else:
89
+ ntext = " " + ntext
90
+
91
+ return (ctext + ntext, cnum * nnum)
92
+ else:
93
+ # Additive case (e.g., "vinte e dois")
94
+ return ("%s e %s" % (ctext, ntext), cnum + nnum)
95
+
96
+ def to_cardinal(self, value):
97
+ # Handle negative numbers
98
+ if value < 0:
99
+ return "%s%s" % (self.negword, self.to_cardinal(-value))
100
+
101
+ # Handle billions and trillions specially for Brazilian Portuguese short scale
102
+ # Brazil uses: bilhão = 10^9, trilhão = 10^12 (short scale)
103
+ # Parent PT uses: bilião = 10^12, trilião = 10^18 (European long scale)
104
+
105
+ if value >= 1000000000:
106
+ # Handle trillions (10^12)
107
+ if value >= 1000000000000:
108
+ trillions, remainder = divmod(value, 1000000000000)
109
+ if trillions == 1:
110
+ result = "um trilhão"
111
+ else:
112
+ # Recursively convert the number of trillions
113
+ result = "%s trilhões" % self.to_cardinal(trillions)
114
+
115
+ if remainder:
116
+ if remainder >= 1000000000:
117
+ # Has billions
118
+ billions, rest = divmod(remainder, 1000000000)
119
+ if billions == 1:
120
+ result += ", um bilhão"
121
+ else:
122
+ result += ", %s bilhões" % self.to_cardinal(billions)
123
+ if rest:
124
+ # Call parent for the rest (millions and below)
125
+ rest_str = super(Num2Word_PT_BR, self).to_cardinal(rest)
126
+ # Fix "milião" to "milhão" if it appears
127
+ rest_str = rest_str.replace("milião", "milhão").replace("miliões", "milhões")
128
+ result += " e %s" % rest_str
129
+ else:
130
+ # No billions, just add remainder
131
+ remainder_str = super(Num2Word_PT_BR, self).to_cardinal(remainder)
132
+ # Fix "milião" to "milhão" if it appears
133
+ remainder_str = remainder_str.replace("milião", "milhão").replace("miliões", "milhões")
134
+ result += " e %s" % remainder_str
135
+ return result
136
+
137
+ # Handle billions (10^9)
138
+ else:
139
+ billions, remainder = divmod(value, 1000000000)
140
+ if billions == 1:
141
+ result = "um bilhão"
142
+ else:
143
+ # Recursively convert the number of billions
144
+ result = "%s bilhões" % self.to_cardinal(billions)
145
+
146
+ if remainder:
147
+ remainder_str = super(Num2Word_PT_BR, self).to_cardinal(remainder)
148
+ # Fix "milião" to "milhão" if it appears
149
+ remainder_str = remainder_str.replace("milião", "milhão").replace("miliões", "milhões")
150
+ # Use comma if remainder starts with hundreds (i.e., >= 100000)
151
+ if remainder >= 100000:
152
+ result += ", %s" % remainder_str
153
+ else:
154
+ result += " e %s" % remainder_str
155
+ return result
156
+ else:
157
+ # For values below 1 billion but above million, handle specially
158
+ if value >= 1000000:
159
+ millions, remainder = divmod(value, 1000000)
160
+ if millions == 1:
161
+ result = "um milhão"
162
+ else:
163
+ result = "%s milhões" % super(Num2Word_PT_BR, self).to_cardinal(millions)
164
+
165
+ if remainder:
166
+ # Use our own to_cardinal for remainder to ensure proper formatting
167
+ remainder_str = self.to_cardinal(remainder)
168
+ # Use comma if remainder starts with hundreds (i.e., >= 100)
169
+ if remainder >= 100:
170
+ result += ", %s" % remainder_str
171
+ else:
172
+ result += " e %s" % remainder_str
173
+ return result
174
+ else:
175
+ # For values below 1 million, use parent implementation
176
+ result = super(Num2Word_PT_BR, self).to_cardinal(value)
177
+ # Fix "milião" to "milhão" throughout (shouldn't happen but just in case)
178
+ result = result.replace("milião", "milhão").replace("miliões", "milhões")
179
+
180
+ # Transforms "mil e cento e catorze" into "mil, cento e catorze"
181
+ for ext in (
182
+ 'mil', 'milhão', 'milhões', 'bilhão', 'bilhões',
183
+ 'trilhão', 'trilhões', 'quatrilhão', 'quatrilhões'):
184
+ # Check if pattern is "thousand-word hundreds-word e something"
185
+ if re.search('{} \\w*ento'.format(ext), result):
186
+ result = re.sub(
187
+ '({}) (\\w*entos?)'.format(ext), r'\1, \2', result, count=1
188
+ )
189
+
190
+ return result
191
+
192
+ def to_currency(self, val, currency='BRL', cents=True, separator=' e', adjective=False):
193
+ from decimal import Decimal
194
+
195
+ # Check if this is a whole number (integer or decimal with .00)
196
+ if isinstance(val, Decimal):
197
+ if val % 1 == 0:
198
+ # Convert to integer if it's a whole number
199
+ val = int(val)
200
+ elif isinstance(val, float):
201
+ if val == int(val):
202
+ val = int(val)
203
+
204
+ # Use parent class implementation with our currency forms
205
+ result = super(Num2Word_PT_BR, self).to_currency(
206
+ val, currency=currency, cents=cents, separator=separator, adjective=adjective
207
+ )
208
+
209
+ # For Brazilian Portuguese, we need to add "de" after millions/billions/trillions
210
+ # when they are round numbers (no thousands/hundreds after)
211
+ if isinstance(val, int):
212
+ try:
213
+ cr1, cr2 = self.CURRENCY_FORMS[currency]
214
+ currency_str = cr1[1] if abs(val) != 1 and len(cr1) > 1 else cr1[0]
215
+ # Add "de" for Brazilian terms (milhão/milhões, bilhão/bilhões, trilhão/trilhões)
216
+ for ext in ('milhão', 'milhões', 'bilhão', 'bilhões', 'trilhão', 'trilhões'):
217
+ if re.match('.*{} (?={})'.format(ext, currency_str), result):
218
+ result = result.replace(
219
+ '{}'.format(ext), '{} de'.format(ext), 1
220
+ )
221
+ except KeyError:
222
+ pass # Currency not in our forms, use parent's result as-is
223
+
224
+ return result