From 0cb3a44dd2362f93c3e45893442e8e67cb7c86d1 Mon Sep 17 00:00:00 2001 From: KhramtsovDR Date: Tue, 7 Mar 2023 14:31:20 +0300 Subject: [PATCH 01/37] Adding genders (m, f, n) --- num2words/lang_RU.py | 219 +++++++++++++++++++++++++------------------ tests/test_ru.py | 40 ++++++++ 2 files changed, 168 insertions(+), 91 deletions(-) diff --git a/num2words/lang_RU.py b/num2words/lang_RU.py index b521cfed..b87f9b9d 100644 --- a/num2words/lang_RU.py +++ b/num2words/lang_RU.py @@ -20,66 +20,78 @@ from .base import Num2Word_Base from .utils import get_digits, splitbyx -ZERO = ('ноль',) - -ONES_FEMININE = { - 1: ('одна',), - 2: ('две',), - 3: ('три',), - 4: ('четыре',), - 5: ('пять',), - 6: ('шесть',), - 7: ('семь',), - 8: ('восемь',), - 9: ('девять',), -} +ZERO = 'ноль' ONES = { - 1: ('один',), - 2: ('два',), - 3: ('три',), - 4: ('четыре',), - 5: ('пять',), - 6: ('шесть',), - 7: ('семь',), - 8: ('восемь',), - 9: ('девять',), + 'f': { + 1: 'одна', + 2: 'две', + 3: 'три', + 4: 'четыре', + 5: 'пять', + 6: 'шесть', + 7: 'семь', + 8: 'восемь', + 9: 'девять', + }, + 'm': { + 1: 'один', + 2: 'два', + 3: 'три', + 4: 'четыре', + 5: 'пять', + 6: 'шесть', + 7: 'семь', + 8: 'восемь', + 9: 'девять', + }, + 'n': { + 1: 'одно', + 2: 'два', + 3: 'три', + 4: 'четыре', + 5: 'пять', + 6: 'шесть', + 7: 'семь', + 8: 'восемь', + 9: 'девять', + } } TENS = { - 0: ('десять',), - 1: ('одиннадцать',), - 2: ('двенадцать',), - 3: ('тринадцать',), - 4: ('четырнадцать',), - 5: ('пятнадцать',), - 6: ('шестнадцать',), - 7: ('семнадцать',), - 8: ('восемнадцать',), - 9: ('девятнадцать',), + 0: 'десять', + 1: 'одиннадцать', + 2: 'двенадцать', + 3: 'тринадцать', + 4: 'четырнадцать', + 5: 'пятнадцать', + 6: 'шестнадцать', + 7: 'семнадцать', + 8: 'восемнадцать', + 9: 'девятнадцать', } TWENTIES = { - 2: ('двадцать',), - 3: ('тридцать',), - 4: ('сорок',), - 5: ('пятьдесят',), - 6: ('шестьдесят',), - 7: ('семьдесят',), - 8: ('восемьдесят',), - 9: ('девяносто',), + 2: 'двадцать', + 3: 'тридцать', + 4: 'сорок', + 5: 'пятьдесят', + 6: 'шестьдесят', + 7: 'семьдесят', + 8: 'восемьдесят', + 9: 'девяносто', } HUNDREDS = { - 1: ('сто',), - 2: ('двести',), - 3: ('триста',), - 4: ('четыреста',), - 5: ('пятьсот',), - 6: ('шестьсот',), - 7: ('семьсот',), - 8: ('восемьсот',), - 9: ('девятьсот',), + 1: 'сто', + 2: 'двести', + 3: 'триста', + 4: 'четыреста', + 5: 'пятьсот', + 6: 'шестьсот', + 7: 'семьсот', + 8: 'восемьсот', + 9: 'девятьсот', } THOUSANDS = { @@ -136,52 +148,49 @@ def setup(self): "восемь": "восьмой", "девять": "девятый", "сто": "сотый"} - self.ords_feminine = {"один": "", - "одна": "", - "две": "двух", - "три": "трёх", - "четыре": "четырёх", - "пять": "пяти", - "шесть": "шести", - "семь": "семи", - "восемь": "восьми", - "девять": "девяти"} - - def to_cardinal(self, number): + self.ords_adjective = {"один": "", + "одна": "", + "две": "двух", + "три": "трёх", + "четыре": "четырёх", + "пять": "пяти", + "шесть": "шести", + "семь": "семи", + "восемь": "восьми", + "девять": "девяти"} + + def to_cardinal(self, number, gender='m'): n = str(number).replace(',', '.') if '.' in n: left, right = n.split('.') leading_zero_count = len(right) - len(right.lstrip('0')) - decimal_part = ((ZERO[0] + ' ') * leading_zero_count + - self._int2word(int(right))) + decimal_part = ((ZERO + ' ') * leading_zero_count + + self._int2word(int(right), gender)) return u'%s %s %s' % ( - self._int2word(int(left)), + self._int2word(int(left), gender), self.pointword, decimal_part ) else: - return self._int2word(int(n)) + return self._int2word(int(n), gender) def pluralize(self, n, forms): - if n % 100 < 10 or n % 100 > 20: - if n % 10 == 1: - form = 0 - elif 5 > n % 10 > 1: - form = 1 - else: - form = 2 - else: - form = 2 - return forms[form] + if n % 100 in (11, 12, 13, 14): + return forms[2] + if n % 10 == 1: + return forms[0] + if n % 10 in (2, 3, 4): + return forms[1] + return forms[2] - def to_ordinal(self, number): + def to_ordinal(self, number, gender='m'): self.verify_ordinal(number) - outwords = self.to_cardinal(number).split(" ") + outwords = self.to_cardinal(number, 'm').split(" ") lastword = outwords[-1].lower() try: if len(outwords) > 1: - if outwords[-2] in self.ords_feminine: - outwords[-2] = self.ords_feminine.get( + if outwords[-2] in self.ords_adjective: + outwords[-2] = self.ords_adjective.get( outwords[-2], outwords[-2]) elif outwords[-2] == 'десять': outwords[-2] = outwords[-2][:-1] + 'и' @@ -190,8 +199,8 @@ def to_ordinal(self, number): outwords[-3] = '' lastword = self.ords[lastword] except KeyError: - if lastword[:-3] in self.ords_feminine: - lastword = self.ords_feminine.get( + if lastword[:-3] in self.ords_adjective: + lastword = self.ords_adjective.get( lastword[:-3], lastword) + "сотый" elif lastword[-1] == "ь" or lastword[-2] == "т": lastword = lastword[:-1] + "ый" @@ -208,21 +217,43 @@ def to_ordinal(self, number): lastword = lastword[:lastword.rfind('н') + 1] + "ный" elif lastword[-1] == "д" or lastword[-2] == "д": lastword = lastword[:lastword.rfind('д') + 1] + "ный" + + if gender == 'f': + if lastword[-2:] == "ий": + lastword = lastword[:-2] + "ья" + else: + lastword = lastword[:-2] + "ая" + if gender == 'n': + if lastword[-2:] == "ий": + lastword = lastword[:-2] + "ье" + else: + lastword = lastword[:-2] + "ое" + outwords[-1] = self.title(lastword) return " ".join(outwords).strip() def _money_verbose(self, number, currency): - return self._int2word(number, currency == 'UAH') + if currency == 'UAH': + gender = 'f' + else: + gender = 'm' + + return self._int2word(number, gender) def _cents_verbose(self, number, currency): - return self._int2word(number, currency in ('UAH', 'RUB', 'BYN')) + if currency in ('UAH', 'RUB', 'BYN'): + gender = 'f' + else: + gender = 'm' + + return self._int2word(number, gender) - def _int2word(self, n, feminine=False): + def _int2word(self, n, gender): if n < 0: - return ' '.join([self.negword, self._int2word(abs(n))]) + return ' '.join([self.negword, self._int2word(abs(n), gender)]) if n == 0: - return ZERO[0] + return ZERO words = [] chunks = list(splitbyx(str(n), 3)) @@ -236,16 +267,22 @@ def _int2word(self, n, feminine=False): n1, n2, n3 = get_digits(x) if n3 > 0: - words.append(HUNDREDS[n3][0]) + words.append(HUNDREDS[n3]) if n2 > 1: - words.append(TWENTIES[n2][0]) + words.append(TWENTIES[n2]) if n2 == 1: - words.append(TENS[n1][0]) + words.append(TENS[n1]) elif n1 > 0: - ones = ONES_FEMININE if i == 1 or feminine and i == 0 else ONES - words.append(ones[n1][0]) + if i == 0: + ones = ONES[gender] + elif i == 1: + ones = ONES['f'] # Thousands is feminine + else: + ones = ONES['m'] + + words.append(ones[n1]) if i > 0: words.append(self.pluralize(x, THOUSANDS[i])) diff --git a/tests/test_ru.py b/tests/test_ru.py index 85d6dc3e..9b827833 100644 --- a/tests/test_ru.py +++ b/tests/test_ru.py @@ -73,6 +73,32 @@ def test_cardinal(self): self.assertEqual(num2words(-15, lang='ru'), "минус пятнадцать") self.assertEqual(num2words(-100, lang='ru'), "минус сто") + def test_feminine(self): + self.assertEqual(num2words(1, lang='ru', gender='f'), 'одна') + self.assertEqual(num2words(2, lang='ru', gender='f'), 'две') + self.assertEqual(num2words(3, lang='ru', gender='f'), 'три') + self.assertEqual(num2words(100, lang='ru', gender='f'), "сто") + self.assertEqual(num2words(101, lang='ru', gender='f'), "сто одна") + self.assertEqual(num2words(110, lang='ru', gender='f'), "сто десять") + self.assertEqual(num2words(115, lang='ru', gender='f'), "сто пятнадцать") + self.assertEqual(num2words(122, lang='ru', gender='f'), "сто двадцать две") + self.assertEqual(num2words(125.1, lang='ru', gender='f'), 'сто двадцать пять запятая одна') + self.assertEqual(num2words(-1, lang='ru', gender='f'), "минус одна") + self.assertEqual(num2words(-100, lang='ru', gender='f'), "минус сто") + + def test_neuter(self): + self.assertEqual(num2words(1, lang='ru', gender='n'), 'одно') + self.assertEqual(num2words(2, lang='ru', gender='n'), 'два') + self.assertEqual(num2words(3, lang='ru', gender='n'), 'три') + self.assertEqual(num2words(100, lang='ru', gender='n'), "сто") + self.assertEqual(num2words(101, lang='ru', gender='n'), "сто одно") + self.assertEqual(num2words(110, lang='ru', gender='n'), "сто десять") + self.assertEqual(num2words(115, lang='ru', gender='n'), "сто пятнадцать") + self.assertEqual(num2words(122, lang='ru', gender='n'), "сто двадцать два") + self.assertEqual(num2words(125.1, lang='ru', gender='n'), 'сто двадцать пять запятая одно') + self.assertEqual(num2words(-1, lang='ru', gender='n'), "минус одно") + self.assertEqual(num2words(-100, lang='ru', gender='n'), "минус сто") + def test_floating_point(self): self.assertEqual(num2words(5.2, lang='ru'), "пять запятая два") self.assertEqual( @@ -159,6 +185,20 @@ def test_to_ordinal(self): 'миллиардный' ) + def test_to_ordinal_feminine(self): + self.assertEqual(num2words(1, lang='ru', to='ordinal', gender='f'), 'первая') + self.assertEqual(num2words(3, lang='ru', to='ordinal', gender='f'), 'третья') + self.assertEqual(num2words(10, lang='ru', to='ordinal', gender='f'), 'десятая') + self.assertEqual(num2words(23, lang='ru', to='ordinal', gender='f'), 'двадцать третья') + self.assertEqual(num2words(1000, lang='ru', to='ordinal', gender='f'), 'тысячная') + + def test_to_ordinal_neuter(self): + self.assertEqual(num2words(1, lang='ru', to='ordinal', gender='n'), 'первое') + self.assertEqual(num2words(3, lang='ru', to='ordinal', gender='n'), 'третье') + self.assertEqual(num2words(10, lang='ru', to='ordinal', gender='n'), 'десятое') + self.assertEqual(num2words(23, lang='ru', to='ordinal', gender='n'), 'двадцать третье') + self.assertEqual(num2words(1000, lang='ru', to='ordinal', gender='n'), 'тысячное') + def test_to_currency(self): self.assertEqual( num2words(1.0, lang='ru', to='currency', currency='EUR'), From 8d0814d6f1efabd41037eb2a50b193e47f554879 Mon Sep 17 00:00:00 2001 From: KhramtsovDR Date: Wed, 8 Mar 2023 00:18:31 +0300 Subject: [PATCH 02/37] Fix E501 - line too long warning --- tests/test_ru.py | 67 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/tests/test_ru.py b/tests/test_ru.py index 9b827833..388cf2a9 100644 --- a/tests/test_ru.py +++ b/tests/test_ru.py @@ -80,9 +80,16 @@ def test_feminine(self): self.assertEqual(num2words(100, lang='ru', gender='f'), "сто") self.assertEqual(num2words(101, lang='ru', gender='f'), "сто одна") self.assertEqual(num2words(110, lang='ru', gender='f'), "сто десять") - self.assertEqual(num2words(115, lang='ru', gender='f'), "сто пятнадцать") - self.assertEqual(num2words(122, lang='ru', gender='f'), "сто двадцать две") - self.assertEqual(num2words(125.1, lang='ru', gender='f'), 'сто двадцать пять запятая одна') + self.assertEqual( + num2words(115, lang='ru', gender='f'), "сто пятнадцать" + ) + self.assertEqual( + num2words(122, lang='ru', gender='f'), "сто двадцать две" + ) + self.assertEqual( + num2words(125.1, lang='ru', gender='f'), + 'сто двадцать пять запятая одна' + ) self.assertEqual(num2words(-1, lang='ru', gender='f'), "минус одна") self.assertEqual(num2words(-100, lang='ru', gender='f'), "минус сто") @@ -93,9 +100,15 @@ def test_neuter(self): self.assertEqual(num2words(100, lang='ru', gender='n'), "сто") self.assertEqual(num2words(101, lang='ru', gender='n'), "сто одно") self.assertEqual(num2words(110, lang='ru', gender='n'), "сто десять") - self.assertEqual(num2words(115, lang='ru', gender='n'), "сто пятнадцать") - self.assertEqual(num2words(122, lang='ru', gender='n'), "сто двадцать два") - self.assertEqual(num2words(125.1, lang='ru', gender='n'), 'сто двадцать пять запятая одно') + self.assertEqual( + num2words(115, lang='ru', gender='n'), "сто пятнадцать" + ) + self.assertEqual( + num2words(122, lang='ru', gender='n'),"сто двадцать два" + ) + self.assertEqual( + num2words(125.1, lang='ru', gender='n'), + 'сто двадцать пять запятая одно') self.assertEqual(num2words(-1, lang='ru', gender='n'), "минус одно") self.assertEqual(num2words(-100, lang='ru', gender='n'), "минус сто") @@ -186,18 +199,40 @@ def test_to_ordinal(self): ) def test_to_ordinal_feminine(self): - self.assertEqual(num2words(1, lang='ru', to='ordinal', gender='f'), 'первая') - self.assertEqual(num2words(3, lang='ru', to='ordinal', gender='f'), 'третья') - self.assertEqual(num2words(10, lang='ru', to='ordinal', gender='f'), 'десятая') - self.assertEqual(num2words(23, lang='ru', to='ordinal', gender='f'), 'двадцать третья') - self.assertEqual(num2words(1000, lang='ru', to='ordinal', gender='f'), 'тысячная') + self.assertEqual( + num2words(1, lang='ru', to='ordinal', gender='f'), 'первая' + ) + self.assertEqual( + num2words(3, lang='ru', to='ordinal', gender='f'), 'третья' + ) + self.assertEqual( + num2words(10, lang='ru', to='ordinal', gender='f'), 'десятая' + ) + self.assertEqual( + num2words(23, lang='ru', to='ordinal', gender='f'), + 'двадцать третья' + ) + self.assertEqual( + num2words(1000, lang='ru', to='ordinal', gender='f'), 'тысячная' + ) def test_to_ordinal_neuter(self): - self.assertEqual(num2words(1, lang='ru', to='ordinal', gender='n'), 'первое') - self.assertEqual(num2words(3, lang='ru', to='ordinal', gender='n'), 'третье') - self.assertEqual(num2words(10, lang='ru', to='ordinal', gender='n'), 'десятое') - self.assertEqual(num2words(23, lang='ru', to='ordinal', gender='n'), 'двадцать третье') - self.assertEqual(num2words(1000, lang='ru', to='ordinal', gender='n'), 'тысячное') + self.assertEqual( + num2words(1, lang='ru', to='ordinal', gender='n'), 'первое' + ) + self.assertEqual( + num2words(3, lang='ru', to='ordinal', gender='n'), 'третье' + ) + self.assertEqual( + num2words(10, lang='ru', to='ordinal', gender='n'), 'десятое' + ) + self.assertEqual( + num2words(23, lang='ru', to='ordinal', gender='n'), + 'двадцать третье' + ) + self.assertEqual( + num2words(1000, lang='ru', to='ordinal', gender='n'), 'тысячное' + ) def test_to_currency(self): self.assertEqual( From 77fbe915b94496e980285935da64ddaa7c101c34 Mon Sep 17 00:00:00 2001 From: "nika.soltani" Date: Wed, 15 Mar 2023 13:05:32 +0330 Subject: [PATCH 03/37] fix 15, 16, 17, 18, 19 issue --- num2words/lang_FA.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/num2words/lang_FA.py b/num2words/lang_FA.py index 96e9e526..f652f255 100644 --- a/num2words/lang_FA.py +++ b/num2words/lang_FA.py @@ -3,6 +3,7 @@ # Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved. # Copyright (c) 2018, Abdullah Alhazmy, Alhazmy13. All Rights Reserved. # Copyright (c) 2020, Hamidreza Kalbasi. All Rights Reserved. +# Copyright (c) 2023, Nika Soltani Tehrani. All Rights Reserved. # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -28,10 +29,10 @@ "دوازده", "سیزده", "چهارده", - "پونزده", - "شونزده", - "هیفده", - "هیجده", + "پانزده", + "شانزده", + "هفده", + "هجده", "نوزده", ] @@ -101,9 +102,9 @@ def float2tuple(self, value): return pre, post, self.precision def cardinal3(self, number): - if (number < 19): + if number <= 19: return farsiOnes[number] - if (number < 100): + if number < 100: x, y = divmod(number, 10) if y == 0: return farsiTens[x] @@ -118,19 +119,19 @@ def cardinalPos(self, number): res = '' for b in farsiBig: x, y = divmod(x, 1000) - if (y == 0): + if y == 0: continue yx = self.cardinal3(y) + b if b == ' هزار' and y == 1: yx = 'هزار' - if (res == ''): + if res == '': res = yx else: res = yx + farsiSeperator + res return res def fractional(self, number, level): - if (number == 5): + if number == 5: return "نیم" x = self.cardinalPos(number) ld3, lm3 = divmod(level, 3) @@ -142,20 +143,21 @@ def to_currency(self, value): def to_ordinal(self, number): r = self.to_cardinal(number) - if (r[-1] == 'ه' and r[-2] == 'س'): + if r[-1] == 'ه' and r[-2] == 'س': return r[:-1] + 'وم' return r + 'م' def to_year(self, value): return self.to_cardinal(value) - def to_ordinal_num(self, value): + @staticmethod + def to_ordinal_num(value): return str(value)+"م" def to_cardinal(self, number): if number < 0: return "منفی " + self.to_cardinal(-number) - if (number == 0): + if number == 0: return "صفر" x, y, level = self.float2tuple(number) if y == 0: From 5708ab304a5cf04621ce96019bc6b1b88ed3a68a Mon Sep 17 00:00:00 2001 From: Nse-Abasi Joseph Etim Date: Tue, 21 Mar 2023 11:16:05 +0100 Subject: [PATCH 04/37] Added support for the Nigerian Naira So all thats required is to set the lang attribute to en_NG Hopefully this would be merged into the main branch, when this is done, the language would be represented as en_NG (English - Nigeria) --- num2words/__init__.py | 3 ++- num2words/lang_EN_NG.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 num2words/lang_EN_NG.py diff --git a/num2words/__init__.py b/num2words/__init__.py index 6aa20d2d..96b08c62 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -24,7 +24,7 @@ lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT, lang_LV, lang_NL, lang_NO, lang_PL, lang_PT, lang_PT_BR, lang_RO, lang_RU, lang_SL, lang_SR, lang_SV, lang_TE, lang_TG, lang_TH, lang_TR, - lang_UK, lang_VI) + lang_UK, lang_VI, lang_EN_NG) CONVERTER_CLASSES = { 'am': lang_AM.Num2Word_AM(), @@ -33,6 +33,7 @@ 'cz': lang_CZ.Num2Word_CZ(), 'en': lang_EN.Num2Word_EN(), 'en_IN': lang_EN_IN.Num2Word_EN_IN(), + 'en_NG': lang_EN_NG.Num2Word_EN_NG(), 'fa': lang_FA.Num2Word_FA(), 'fr': lang_FR.Num2Word_FR(), 'fr_CH': lang_FR_CH.Num2Word_FR_CH(), diff --git a/num2words/lang_EN_NG.py b/num2words/lang_EN_NG.py new file mode 100644 index 00000000..84a2a556 --- /dev/null +++ b/num2words/lang_EN_NG.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2003, Taro Ogawa. All Rights Reserved. +# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved. + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +from __future__ import unicode_literals + +from . import lang_EN + +class Num2Word_EN_NG(lang_EN.Num2Word_EN): + + CURRENCY_FORMS = {'NGN': (('naira', 'naira'), ('kobo', 'kobo'))} + + CURRENCY_ADJECTIVES = {'NGN': 'Nigerian'} + + def to_currency( + self, val, currency='NGN', + cents=True, separator=',', + adjective=False + ): + result = super(Num2Word_EN_NG, self).to_currency( + val, currency=currency, cents=cents, separator=separator, + adjective=adjective) + return result \ No newline at end of file From fc2ec75ab0f0b77ba59cff014b42b3d734459542 Mon Sep 17 00:00:00 2001 From: Nse-Abasi Joseph Etim Date: Tue, 21 Mar 2023 14:40:52 +0100 Subject: [PATCH 05/37] Added tests and some changes to the keyword argument name from cents to kobo --- README.rst | 3 +- num2words/lang_EN_NG.py | 4 +- tests/test_en_ng.py | 91 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 tests/test_en_ng.py diff --git a/README.rst b/README.rst index c24bb099..39b22255 100644 --- a/README.rst +++ b/README.rst @@ -63,7 +63,7 @@ In code there's only one function to use:: >>> num2words(42, lang='fr') quarante-deux -Besides the numerical argument, there are two main optional arguments. +Besides the numerical argument, there are two main optional arguments, ``to:`` and ``lang:`` **to:** The converter to use. Supported values are: @@ -84,6 +84,7 @@ Besides the numerical argument, there are two main optional arguments. * ``dk`` (Danish) * ``en_GB`` (English - Great Britain) * ``en_IN`` (English - India) +* ``en_NG`` (English - Nigeria) * ``es`` (Spanish) * ``es_CO`` (Spanish - Colombia) * ``es_VE`` (Spanish - Venezuela) diff --git a/num2words/lang_EN_NG.py b/num2words/lang_EN_NG.py index 84a2a556..c69574c0 100644 --- a/num2words/lang_EN_NG.py +++ b/num2words/lang_EN_NG.py @@ -27,10 +27,10 @@ class Num2Word_EN_NG(lang_EN.Num2Word_EN): def to_currency( self, val, currency='NGN', - cents=True, separator=',', + kobo=True, separator=',', adjective=False ): result = super(Num2Word_EN_NG, self).to_currency( - val, currency=currency, cents=cents, separator=separator, + val, currency=currency, cents=kobo, separator=separator, adjective=adjective) return result \ No newline at end of file diff --git a/tests/test_en_ng.py b/tests/test_en_ng.py new file mode 100644 index 00000000..aaef0aab --- /dev/null +++ b/tests/test_en_ng.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2003, Taro Ogawa. All Rights Reserved. +# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved. + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +from unittest import TestCase + +from num2words import num2words + + +class Num2WordsENNGTest(TestCase): + + # only the test for currency is writen as other functions in the Class remains the + # same and have been properly tested in the test test_en which tests the parent class + # upon which this class inherits + + def test_to_currency(self): + + language = 'en_NG' + separator = ' and' + + self.assertEqual( + num2words('38.4', lang=language, to='currency', separator=separator, + kobo=False), + "thirty-eight naira and 40 kobo" + ) + self.assertEqual( + num2words('0', lang=language, to='currency', separator=separator, + kobo=False), + "zero naira and 00 kobo" + ) + + self.assertEqual( + num2words('1.01', lang=language, to='currency', separator=separator, + kobo=True), + "one naira and one cent" + ) + + self.assertEqual( + num2words('4778.00', lang=language, to='currency', separator=separator, + kobo=True, adjective=True), + 'four thousand, seven hundred and seventy-eight Nigerian naira' + ' and zero cents') + + self.assertEqual( + num2words('4778.00', lang=language, to='currency', separator=separator, + kobo=True), + 'four thousand, seven hundred and seventy-eight naira and' + ' zero cents') + + self.assertEqual( + num2words('1.1', lang=language, to='currency', separator=separator, + kobo=True), + "one naira and ten kobo" + ) + + self.assertEqual( + num2words('158.3', lang=language, to='currency', separator=separator, + kobo=True), + "one hundred and fifty-eight naira and thirty kobo" + ) + + self.assertEqual( + num2words('2000.00', lang=language, to='currency', separator=separator, + kobo=True), + "two thousand naira and zero kobo" + ) + + self.assertEqual( + num2words('4.01', lang=language, to='currency', separator=separator, + kobo=True), + "four naira and one kobo" + ) + + self.assertEqual( + num2words('2000.00', lang=language, to='currency', separator=separator, + kobo=True), + "two thousand naira and zero kobo" + ) \ No newline at end of file From 291951e249507a076554b292b1aa3e8e834b795b Mon Sep 17 00:00:00 2001 From: Nse-Abasi Joseph Etim Date: Tue, 21 Mar 2023 19:26:58 +0100 Subject: [PATCH 06/37] updated tests and code formatting --- num2words/__init__.py | 14 +++--- num2words/lang_EN_NG.py | 5 +- tests/test_en_ng.py | 104 ++++++++++++++++++++++++++++++---------- 3 files changed, 88 insertions(+), 35 deletions(-) diff --git a/num2words/__init__.py b/num2words/__init__.py index 96b08c62..0338e951 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -18,13 +18,13 @@ from __future__ import unicode_literals from . import (lang_AM, lang_AR, lang_AZ, lang_CZ, lang_DE, lang_DK, lang_EN, - lang_EN_IN, lang_EO, lang_ES, lang_ES_CO, lang_ES_NI, - lang_ES_VE, lang_FA, lang_FI, lang_FR, lang_FR_BE, lang_FR_CH, - lang_FR_DZ, lang_HE, lang_HU, lang_ID, lang_IS, lang_IT, - lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT, lang_LV, lang_NL, - lang_NO, lang_PL, lang_PT, lang_PT_BR, lang_RO, lang_RU, - lang_SL, lang_SR, lang_SV, lang_TE, lang_TG, lang_TH, lang_TR, - lang_UK, lang_VI, lang_EN_NG) + lang_EN_IN, lang_EN_NG, lang_EO, lang_ES, lang_ES_CO, + lang_ES_NI, lang_ES_VE, lang_FA, lang_FI, lang_FR, + lang_FR_BE, lang_FR_CH, lang_FR_DZ, lang_HE, lang_HU, + lang_ID, lang_IS, lang_IT, lang_JA, lang_KN, lang_KO, + lang_KZ, lang_LT, lang_LV, lang_NL, lang_NO, lang_PL, + lang_PT, lang_PT_BR, lang_RO, lang_RU, lang_SL, lang_SR, + lang_SV, lang_TE, lang_TG, lang_TH, lang_TR, lang_UK, lang_VI) CONVERTER_CLASSES = { 'am': lang_AM.Num2Word_AM(), diff --git a/num2words/lang_EN_NG.py b/num2words/lang_EN_NG.py index c69574c0..7de81c84 100644 --- a/num2words/lang_EN_NG.py +++ b/num2words/lang_EN_NG.py @@ -19,6 +19,7 @@ from . import lang_EN + class Num2Word_EN_NG(lang_EN.Num2Word_EN): CURRENCY_FORMS = {'NGN': (('naira', 'naira'), ('kobo', 'kobo'))} @@ -26,11 +27,11 @@ class Num2Word_EN_NG(lang_EN.Num2Word_EN): CURRENCY_ADJECTIVES = {'NGN': 'Nigerian'} def to_currency( - self, val, currency='NGN', + self, val, currency='NGN', kobo=True, separator=',', adjective=False ): result = super(Num2Word_EN_NG, self).to_currency( val, currency=currency, cents=kobo, separator=separator, adjective=adjective) - return result \ No newline at end of file + return result diff --git a/tests/test_en_ng.py b/tests/test_en_ng.py index aaef0aab..6d7e72e5 100644 --- a/tests/test_en_ng.py +++ b/tests/test_en_ng.py @@ -22,8 +22,10 @@ class Num2WordsENNGTest(TestCase): - # only the test for currency is writen as other functions in the Class remains the - # same and have been properly tested in the test test_en which tests the parent class + # only the test for currency is writen as other + # functions in the Class remains the + # same and have been properly tested in the + # test test_en which tests the parent class # upon which this class inherits def test_to_currency(self): @@ -32,60 +34,110 @@ def test_to_currency(self): separator = ' and' self.assertEqual( - num2words('38.4', lang=language, to='currency', separator=separator, - kobo=False), + num2words( + '38.4', + lang=language, + to='currency', + separator=separator, + kobo=False + ), "thirty-eight naira and 40 kobo" ) self.assertEqual( - num2words('0', lang=language, to='currency', separator=separator, - kobo=False), + num2words( + '0', + lang=language, + to='currency', + separator=separator, + kobo=False + ), "zero naira and 00 kobo" ) self.assertEqual( - num2words('1.01', lang=language, to='currency', separator=separator, - kobo=True), - "one naira and one cent" + num2words( + '1.01', + lang=language, + to='currency', + separator=separator, + kobo=True + ), + "one naira and one kobo" ) self.assertEqual( - num2words('4778.00', lang=language, to='currency', separator=separator, - kobo=True, adjective=True), + num2words( + '4778.00', + lang=language, + to='currency', + separator=separator, + kobo=True, adjective=True + ), 'four thousand, seven hundred and seventy-eight Nigerian naira' - ' and zero cents') + ' and zero kobo') self.assertEqual( - num2words('4778.00', lang=language, to='currency', separator=separator, - kobo=True), + num2words( + '4778.00', + lang=language, + to='currency', + separator=separator, + kobo=True + ), 'four thousand, seven hundred and seventy-eight naira and' - ' zero cents') + ' zero kobo') self.assertEqual( - num2words('1.1', lang=language, to='currency', separator=separator, - kobo=True), + num2words( + '1.1', + lang=language, + to='currency', + separator=separator, + kobo=True + ), "one naira and ten kobo" ) self.assertEqual( - num2words('158.3', lang=language, to='currency', separator=separator, - kobo=True), + num2words( + '158.3', + lang=language, + to='currency', + separator=separator, + kobo=True + ), "one hundred and fifty-eight naira and thirty kobo" ) self.assertEqual( - num2words('2000.00', lang=language, to='currency', separator=separator, - kobo=True), + num2words( + '2000.00', + lang=language, + to='currency', + separator=separator, + kobo=True + ), "two thousand naira and zero kobo" ) self.assertEqual( - num2words('4.01', lang=language, to='currency', separator=separator, - kobo=True), + num2words( + '4.01', + lang=language, + to='currency', + separator=separator, + kobo=True + ), "four naira and one kobo" ) self.assertEqual( - num2words('2000.00', lang=language, to='currency', separator=separator, - kobo=True), + num2words( + '2000.00', + lang=language, + to='currency', + separator=separator, + kobo=True + ), "two thousand naira and zero kobo" - ) \ No newline at end of file + ) From 50353485404c292ae71361ce036994d24af6640f Mon Sep 17 00:00:00 2001 From: Nse-Abasi Joseph Etim Date: Fri, 24 Mar 2023 15:38:17 +0100 Subject: [PATCH 07/37] corrected sorting using isort --- num2words/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/num2words/__init__.py b/num2words/__init__.py index 0338e951..3990bd1f 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -19,12 +19,12 @@ from . import (lang_AM, lang_AR, lang_AZ, lang_CZ, lang_DE, lang_DK, lang_EN, lang_EN_IN, lang_EN_NG, lang_EO, lang_ES, lang_ES_CO, - lang_ES_NI, lang_ES_VE, lang_FA, lang_FI, lang_FR, - lang_FR_BE, lang_FR_CH, lang_FR_DZ, lang_HE, lang_HU, - lang_ID, lang_IS, lang_IT, lang_JA, lang_KN, lang_KO, - lang_KZ, lang_LT, lang_LV, lang_NL, lang_NO, lang_PL, - lang_PT, lang_PT_BR, lang_RO, lang_RU, lang_SL, lang_SR, - lang_SV, lang_TE, lang_TG, lang_TH, lang_TR, lang_UK, lang_VI) + lang_ES_NI, lang_ES_VE, lang_FA, lang_FI, lang_FR, lang_FR_BE, + lang_FR_CH, lang_FR_DZ, lang_HE, lang_HU, lang_ID, lang_IS, + lang_IT, lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT, lang_LV, + lang_NL, lang_NO, lang_PL, lang_PT, lang_PT_BR, lang_RO, + lang_RU, lang_SL, lang_SR, lang_SV, lang_TE, lang_TG, lang_TH, + lang_TR, lang_UK, lang_VI) CONVERTER_CLASSES = { 'am': lang_AM.Num2Word_AM(), From 00836e13e00fa5986526fdd7b841dc1ef44f15f8 Mon Sep 17 00:00:00 2001 From: Mario Monroy Date: Tue, 28 Mar 2023 10:57:57 -0600 Subject: [PATCH 08/37] Guatemala currency --- num2words/__init__.py | 15 ++++++----- num2words/lang_ES.py | 1 + num2words/lang_ES_GT.py | 29 ++++++++++++++++++++ tests/test_es_gt.py | 60 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 num2words/lang_ES_GT.py create mode 100644 tests/test_es_gt.py diff --git a/num2words/__init__.py b/num2words/__init__.py index 6aa20d2d..9193ab5a 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -18,13 +18,13 @@ from __future__ import unicode_literals from . import (lang_AM, lang_AR, lang_AZ, lang_CZ, lang_DE, lang_DK, lang_EN, - lang_EN_IN, lang_EO, lang_ES, lang_ES_CO, lang_ES_NI, - lang_ES_VE, lang_FA, lang_FI, lang_FR, lang_FR_BE, lang_FR_CH, - lang_FR_DZ, lang_HE, lang_HU, lang_ID, lang_IS, lang_IT, - lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT, lang_LV, lang_NL, - lang_NO, lang_PL, lang_PT, lang_PT_BR, lang_RO, lang_RU, - lang_SL, lang_SR, lang_SV, lang_TE, lang_TG, lang_TH, lang_TR, - lang_UK, lang_VI) + lang_EN_IN, lang_EO, lang_ES, lang_ES_CO, lang_ES_GT, + lang_ES_NI, lang_ES_VE, lang_FA, lang_FI, lang_FR, lang_FR_BE, + lang_FR_CH, lang_FR_DZ, lang_HE, lang_HU, lang_ID, lang_IS, + lang_IT, lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT, lang_LV, + lang_NL, lang_NO, lang_PL, lang_PT, lang_PT_BR, lang_RO, + lang_RU, lang_SL, lang_SR, lang_SV, lang_TE, lang_TG, lang_TH, + lang_TR, lang_UK, lang_VI) CONVERTER_CLASSES = { 'am': lang_AM.Num2Word_AM(), @@ -45,6 +45,7 @@ 'es_CO': lang_ES_CO.Num2Word_ES_CO(), 'es_NI': lang_ES_NI.Num2Word_ES_NI(), 'es_VE': lang_ES_VE.Num2Word_ES_VE(), + 'es_GT': lang_ES_GT.Num2Word_ES_GT(), 'id': lang_ID.Num2Word_ID(), 'ja': lang_JA.Num2Word_JA(), 'kn': lang_KN.Num2Word_KN(), diff --git a/num2words/lang_ES.py b/num2words/lang_ES.py index de2d1e13..3dc70141 100644 --- a/num2words/lang_ES.py +++ b/num2words/lang_ES.py @@ -206,6 +206,7 @@ class Num2Word_ES(Num2Word_EU): 'ZRZ': (('zaire', 'zaires'), ('likuta', 'makuta')), 'ZWL': (GENERIC_DOLLARS, ('céntimo', 'céntimos')), 'ZWL': (GENERIC_DOLLARS, ('céntimo', 'céntimos')), + 'GTQ': (('quetzal', 'quetzales'), GENERIC_CENTS), } # //CHECK: Is this sufficient?? diff --git a/num2words/lang_ES_GT.py b/num2words/lang_ES_GT.py new file mode 100644 index 00000000..70902fc1 --- /dev/null +++ b/num2words/lang_ES_GT.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2003, Taro Ogawa. All Rights Reserved. +# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved. + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +from __future__ import print_function, unicode_literals + +from .lang_ES import Num2Word_ES + + +class Num2Word_ES_GT(Num2Word_ES): + + def to_currency(self, val, longval=True, old=False): + result = self.to_splitnum(val, hightxt="quetzal/es", lowtxt="centavo/s", + divisor=1, jointxt="y", longval=longval) + # Handle exception, in spanish is "un euro" and not "uno euro" + return result.replace("uno", "un") diff --git a/tests/test_es_gt.py b/tests/test_es_gt.py new file mode 100644 index 00000000..1c9429fa --- /dev/null +++ b/tests/test_es_gt.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2003, Taro Ogawa. All Rights Reserved. +# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved. + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +from __future__ import unicode_literals + +from num2words import num2words + +from . import test_es + +TEST_CASES_TO_CURRENCY = ( + (1, 'un quetzal'), + (2, 'dos quetzales'), + (8, 'ocho quetzales'), + (12, 'doce quetzales'), + (21, 'veintiun quetzales'), + (81.25, 'ochenta y un quetzales y veinticinco centavos'), + (100, 'cien quetzales'), +) + + +class Num2WordsESGTTest(test_es.Num2WordsESTest): + + def test_number(self): + for test in test_es.TEST_CASES_CARDINAL: + self.assertEqual(num2words(test[0], lang='es_GT'), test[1]) + + def test_ordinal(self): + for test in test_es.TEST_CASES_ORDINAL: + self.assertEqual( + num2words(test[0], lang='es_GT', ordinal=True), + test[1] + ) + + def test_ordinal_num(self): + for test in test_es.TEST_CASES_ORDINAL_NUM: + self.assertEqual( + num2words(test[0], lang='es', to='ordinal_num'), + test[1] + ) + + def test_currency(self): + for test in TEST_CASES_TO_CURRENCY: + self.assertEqual( + num2words(test[0], lang='es_GT', to='currency'), + test[1] + ) From 6cfdf779f5f4bcf3da97e6c8c97ade9ed55773aa Mon Sep 17 00:00:00 2001 From: Mario Monroy Date: Tue, 28 Mar 2023 11:21:27 -0600 Subject: [PATCH 09/37] Adding Guatemala in README.rst --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index c24bb099..7c348b2a 100644 --- a/README.rst +++ b/README.rst @@ -87,6 +87,7 @@ Besides the numerical argument, there are two main optional arguments. * ``es`` (Spanish) * ``es_CO`` (Spanish - Colombia) * ``es_VE`` (Spanish - Venezuela) +* ``es_GT`` (Spanish - Guatemala) * ``eu`` (EURO) * ``fa`` (Farsi) * ``fi`` (Finnish) From afac78a01c29b0948bf4ba54d0c546be6364fd15 Mon Sep 17 00:00:00 2001 From: Jeronymous Date: Wed, 29 Mar 2023 12:30:05 +0200 Subject: [PATCH 10/37] Fix missing space/and in numbers like 740 --- num2words/lang_AR.py | 2 +- tests/test_ar.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index a0027a83..5f8415c2 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -192,7 +192,7 @@ def process_arabic_group(self, group_number, group_level, ret_val += " و " ret_val += self.digit_feminine_status(ones, group_level) - if ret_val != "" and ones != 0: + if ret_val != "": ret_val += " و " ret_val += self.arabicTens[int(tens)] diff --git a/tests/test_ar.py b/tests/test_ar.py index 5e1ea410..e511329e 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -97,6 +97,23 @@ def test_cardinal(self): 'أربعة و تسعون ألفاً و مئتان و واحد و ثلاثون') self.assertEqual(num2words(1431, to='cardinal', lang='ar'), 'واحد ألف و أربعمائة و واحد و ثلاثون') + self.assertEqual(num2words(740, to='cardinal', lang='ar'), + 'سبعمائة و أربعون') + self.assertEqual(num2words(741, to='cardinal', lang='ar'), + #'سبعة مائة و واحد و أربعون' + 'سبعمائة و واحد و أربعون' + ) + self.assertEqual(num2words(710, to='cardinal', lang='ar'), + 'سبعمائة و عشرة') + self.assertEqual(num2words(711, to='cardinal', lang='ar'), + # 'سبعة مائة و إحدى عشر' + 'سبعمائة و أحد عشر' + ) + self.assertEqual(num2words(700, to='cardinal', lang='ar'), + 'سبعمائة') + self.assertEqual(num2words(701, to='cardinal', lang='ar'), + 'سبعمائة و واحد') + def test_prefix_and_suffix(self): self.assertEqual(num2words(645, to='currency', From 302052e805a4f407ddc9ab1e466f29ac3d418c84 Mon Sep 17 00:00:00 2001 From: Jeronymous Date: Wed, 29 Mar 2023 12:33:10 +0200 Subject: [PATCH 11/37] fix extra space. Resolves https://github.com/savoirfairelinux/num2words/issues/393 --- num2words/lang_AR.py | 2 +- tests/test_ar.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index 5f8415c2..61c05a1d 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -229,7 +229,7 @@ def convert_to_arabic(self): if group_description != '': if group > 0: if ret_val != "": - ret_val = "{} و {}".format("", ret_val) + ret_val = "{}و {}".format("", ret_val) if number_to_process != 2: if number_to_process % 100 != 1: if 3 <= number_to_process <= 10: diff --git a/tests/test_ar.py b/tests/test_ar.py index e511329e..d9ff7711 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -43,10 +43,10 @@ def test_default_currency(self): 'عشرون ألف ريال و اثنتا عشرة هللة') self.assertEqual(num2words(1000000, to='currency', lang='ar'), 'واحد مليون ريال') - val = 'تسعمائة و ثلاثة و عشرون ألفاً و أربعمائة و أحد عشر ريالاً' + val = 'تسعمائة و ثلاثة و عشرون ألفاً و أربعمائة و أحد عشر ريالاً' self.assertEqual(num2words(923411, to='currency', lang='ar'), val) self.assertEqual(num2words(63411, to='currency', lang='ar'), - 'ثلاثة و ستون ألفاً و أربعمائة و أحد عشر ريالاً') + 'ثلاثة و ستون ألفاً و أربعمائة و أحد عشر ريالاً') self.assertEqual(num2words(1000000.99, to='currency', lang='ar'), 'واحد مليون ريال و تسع و تسعون هللة') @@ -62,7 +62,7 @@ def test_currency_parm(self): 'عشرون ألف جنيه و اثنتا عشرة قرش') self.assertEqual( num2words(923411, to='currency', lang='ar', currency="SR"), - 'تسعمائة و ثلاثة و عشرون ألفاً و أربعمائة و أحد عشر ريالاً') + 'تسعمائة و ثلاثة و عشرون ألفاً و أربعمائة و أحد عشر ريالاً') self.assertEqual( num2words(1000000.99, to='currency', lang='ar', currency="KWD"), 'واحد مليون دينار و تسع و تسعون فلس') @@ -82,21 +82,21 @@ def test_ordinal(self): 'مائة و اثنان') self.assertEqual( num2words(923411, to='ordinal_num', lang='ar'), - 'تسعمائة و ثلاثة و عشرون ألفاً و أربعمائة و أحد عشر') + 'تسعمائة و ثلاثة و عشرون ألفاً و أربعمائة و أحد عشر') def test_cardinal(self): self.assertEqual(num2words(12, to='cardinal', lang='ar'), 'اثنا عشر') self.assertEqual(num2words(-8324, to='cardinal', lang='ar'), - 'سالب ثمانية آلاف و ثلاثمائة و أربعة و عشرون') + 'سالب ثمانية آلاف و ثلاثمائة و أربعة و عشرون') self.assertEqual( num2words(3431.12, to='cardinal', lang='ar'), - 'ثلاثة آلاف و أربعمائة و واحد و ثلاثون , اثنتا عشرة') + 'ثلاثة آلاف و أربعمائة و واحد و ثلاثون , اثنتا عشرة') self.assertEqual(num2words(431, to='cardinal', lang='ar'), 'أربعمائة و واحد و ثلاثون') self.assertEqual(num2words(94231, to='cardinal', lang='ar'), - 'أربعة و تسعون ألفاً و مئتان و واحد و ثلاثون') + 'أربعة و تسعون ألفاً و مئتان و واحد و ثلاثون') self.assertEqual(num2words(1431, to='cardinal', lang='ar'), - 'واحد ألف و أربعمائة و واحد و ثلاثون') + 'واحد ألف و أربعمائة و واحد و ثلاثون') self.assertEqual(num2words(740, to='cardinal', lang='ar'), 'سبعمائة و أربعون') self.assertEqual(num2words(741, to='cardinal', lang='ar'), From 030a76f245d578e74008944626624de3d8df3620 Mon Sep 17 00:00:00 2001 From: Jeronymous Date: Wed, 29 Mar 2023 15:01:38 +0200 Subject: [PATCH 12/37] Resolves https://github.com/savoirfairelinux/num2words/issues/511 : fail with OverflowError on big numbers --- num2words/lang_AR.py | 16 +++++++++++++--- tests/test_ar.py | 9 ++++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index 61c05a1d..71286354 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -18,6 +18,7 @@ # MA 02110-1301 USA import re +import decimal from decimal import Decimal from math import floor @@ -218,9 +219,14 @@ def convert_to_arabic(self): while temp_number > Decimal(0): - number_to_process = int( - Decimal(str(temp_number)) % Decimal(str(1000))) - temp_number = int(Decimal(temp_number) / Decimal(1000)) + temp_number_dec = Decimal(str(temp_number)) + try: + number_to_process = int(temp_number_dec % Decimal(str(1000))) + except decimal.InvalidOperation: # https://stackoverflow.com/questions/42868278/decimal-invalidoperation-divisionimpossible-for-very-large-numbers + decimal.getcontext().prec = len(temp_number_dec.as_tuple().digits) + number_to_process = int(temp_number_dec % Decimal(str(1000))) + + temp_number = int(temp_number_dec / Decimal(1000)) group_description = \ self.process_arabic_group(number_to_process, @@ -237,10 +243,14 @@ def convert_to_arabic(self): self.arabicPluralGroups[group], ret_val) else: if ret_val != "": + if group >= len(self.arabicAppendedGroup): + raise OverflowError(self.errmsg_too_big) ret_val = "{} {}".format( self.arabicAppendedGroup[group], ret_val) else: + if group >= len(self.arabicGroup): + raise OverflowError(self.errmsg_too_big) ret_val = "{} {}".format( self.arabicGroup[group], ret_val) diff --git a/tests/test_ar.py b/tests/test_ar.py index d9ff7711..fda08b48 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -124,7 +124,10 @@ def test_year(self): self.assertEqual(num2words(2000, to='year', lang='ar'), 'ألفا') def test_max_numbers(self): - with self.assertRaises(Exception) as context: - num2words(10 ** 36, to='year', lang='ar') - self.assertTrue('Too large' in str(context.exception)) + for number in 10 ** 36, 123456789123456789123456789, 123456789123456789123456789123456789: + + with self.assertRaises(OverflowError) as context: + num2words(number, lang='ar') + + self.assertTrue('Too large' in str(context.exception)) From fd0516cc67fd21e907a8c3803d60e6a56877d33c Mon Sep 17 00:00:00 2001 From: Jeronymous Date: Wed, 29 Mar 2023 15:14:29 +0200 Subject: [PATCH 13/37] Resolves https://github.com/savoirfairelinux/num2words/issues/509 : fix error messages when numbers are too big --- num2words/lang_EO.py | 2 +- num2words/lang_FR.py | 2 +- num2words/lang_ID.py | 2 +- num2words/lang_RO.py | 2 +- num2words/lang_SL.py | 2 +- tests/test_fr.py | 9 ++++++++- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/num2words/lang_EO.py b/num2words/lang_EO.py index 473e74d1..ff47e69d 100644 --- a/num2words/lang_EO.py +++ b/num2words/lang_EO.py @@ -60,7 +60,7 @@ def setup(self): self.pointword = "komo" self.errmsg_nonnum = u"Sole nombroj povas esti konvertita en vortojn." self.errmsg_toobig = ( - u"Tro granda nombro por esti konvertita en vortojn." + u"Tro granda nombro por esti konvertita en vortojn (abs(%s) > %s)." ) self.exclude_title = ["kaj", "komo", "minus"] self.mid_numwords = [(1000, "mil"), (100, "cent"), (90, "naŭdek"), diff --git a/num2words/lang_FR.py b/num2words/lang_FR.py index 0c440b3f..82942a4e 100644 --- a/num2words/lang_FR.py +++ b/num2words/lang_FR.py @@ -37,7 +37,7 @@ def setup(self): self.errmsg_nonnum = ( u"Seulement des nombres peuvent être convertis en mots." ) - self.errmsg_toobig = u"Nombre trop grand pour être converti en mots." + self.errmsg_toobig = u"Nombre trop grand pour être converti en mots (abs(%s) > %s)." self.exclude_title = ["et", "virgule", "moins"] self.mid_numwords = [(1000, "mille"), (100, "cent"), (80, "quatre-vingts"), (60, "soixante"), diff --git a/num2words/lang_ID.py b/num2words/lang_ID.py index 9a2bbfcb..1973e7e8 100644 --- a/num2words/lang_ID.py +++ b/num2words/lang_ID.py @@ -44,7 +44,7 @@ class Num2Word_ID(): errmsg_floatord = "Cannot treat float number as ordinal" errmsg_negord = "Cannot treat negative number as ordinal" - errmsg_toobig = "Too large" + errmsg_toobig = "Number is too large to convert to words (abs(%s) > %s)." max_num = 10 ** 36 def split_by_koma(self, number): diff --git a/num2words/lang_RO.py b/num2words/lang_RO.py index ec1deda3..b3fcef2a 100644 --- a/num2words/lang_RO.py +++ b/num2words/lang_RO.py @@ -34,7 +34,7 @@ def setup(self): self.pointword = "virgulă" self.exclude_title = ["și", "virgulă", "minus"] self.errmsg_toobig = ( - "Numărul e prea mare pentru a fi convertit în cuvinte." + "Numărul e prea mare pentru a fi convertit în cuvinte (abs(%s) > %s)." ) self.mid_numwords = [(1000, "mie/i"), (100, "sută/e"), (90, "nouăzeci"), (80, "optzeci"), diff --git a/num2words/lang_SL.py b/num2words/lang_SL.py index fb0e2876..cecbbc79 100644 --- a/num2words/lang_SL.py +++ b/num2words/lang_SL.py @@ -31,7 +31,7 @@ def setup(self): self.negword = "minus " self.pointword = "celih" self.errmsg_nonnum = "Only numbers may be converted to words." - self.errmsg_toobig = "Number is too large to convert to words." + self.errmsg_toobig = "Number is too large to convert to words (abs(%s) > %s)." self.exclude_title = [] self.mid_numwords = [(1000, "tisoč"), (900, "devetsto"), diff --git a/tests/test_fr.py b/tests/test_fr.py index 538be341..873473ae 100644 --- a/tests/test_fr.py +++ b/tests/test_fr.py @@ -149,7 +149,7 @@ ) -class Num2WordsENTest(TestCase): +class Num2WordsFRTest(TestCase): def test_ordinal_special_joins(self): # ref https://github.com/savoirfairelinux/num2words/issues/18 self.assertEqual( @@ -203,3 +203,10 @@ def test_currency_usd(self): num2words(test[0], lang='fr', to='currency', currency='USD'), test[1] ) + + def test_max_numbers(self): + + with self.assertRaises(OverflowError) as context: + num2words(10 ** 700, lang='fr') + + self.assertTrue('trop grand' in str(context.exception)) From f55f7c81e020d391984bbaf39cc1df18d35f94d6 Mon Sep 17 00:00:00 2001 From: Jeronymous Date: Wed, 29 Mar 2023 15:54:12 +0200 Subject: [PATCH 14/37] uniform use of errmsg_toobig and MAXVAL --- num2words/lang_AR.py | 23 ++++++++++++++--------- num2words/lang_FA.py | 5 +++-- num2words/lang_ID.py | 6 +++--- tests/test_ar.py | 2 +- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index 71286354..11f51f3b 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -38,11 +38,13 @@ ] -class Num2Word_AR(object): - errmsg_too_big = "Too large" - max_num = 10 ** 36 +class Num2Word_AR(object): # Num2Word_Base ? + errmsg_toobig = "abs(%s) must be less than %s." + MAXVAL = 1000000000000000050331649 # 10 **36 def __init__(self): + super().__init__() + self.number = 0 self.arabicPrefixText = "" self.arabicSuffixText = "" @@ -96,6 +98,9 @@ def __init__(self): "", "آلاف", "ملايين", "مليارات", "تريليونات", "كوادريليونات", "كوينتليونات", "سكستيليونات" ] + assert len(self.arabicAppendedGroup) == len(self.arabicGroup) + assert len(self.arabicPluralGroups) == len(self.arabicGroup) + assert len(self.arabicAppendedTwos) == len(self.arabicTwos) def number_to_arabic(self, arabic_prefix_text, arabic_suffix_text): self.arabicPrefixText = arabic_prefix_text @@ -163,6 +168,8 @@ def process_arabic_group(self, group_number, group_level, if tens > 0: if tens < 20: + if int(group_level) >= len(self.arabicTwos): + raise OverflowError(self.errmsg_toobig % (self.number, self.MAXVAL)) if tens == 2 and int(hundreds) == 0 and group_level > 0: if self.integer_value in [2000, 2000000, 2000000000, 2000000000000, 2000000000000000, @@ -237,20 +244,18 @@ def convert_to_arabic(self): if ret_val != "": ret_val = "{}و {}".format("", ret_val) if number_to_process != 2: + if group >= len(self.arabicGroup): + raise OverflowError(self.errmsg_toobig % (self.number, self.MAXVAL)) if number_to_process % 100 != 1: if 3 <= number_to_process <= 10: ret_val = "{} {}".format( self.arabicPluralGroups[group], ret_val) else: if ret_val != "": - if group >= len(self.arabicAppendedGroup): - raise OverflowError(self.errmsg_too_big) ret_val = "{} {}".format( self.arabicAppendedGroup[group], ret_val) else: - if group >= len(self.arabicGroup): - raise OverflowError(self.errmsg_too_big) ret_val = "{} {}".format( self.arabicGroup[group], ret_val) @@ -304,8 +309,8 @@ def convert_to_arabic(self): return formatted_number def validate_number(self, number): - if number >= self.max_num: - raise OverflowError(self.errmsg_too_big) + if number >= self.MAXVAL: + raise OverflowError(self.errmsg_toobig % (number, self.MAXVAL)) return number def set_currency_prefer(self, currency): diff --git a/num2words/lang_FA.py b/num2words/lang_FA.py index f652f255..3597b08c 100644 --- a/num2words/lang_FA.py +++ b/num2words/lang_FA.py @@ -78,8 +78,9 @@ class Num2Word_FA(object): - errmsg_too_big = "Too large" - max_num = 10 ** 36 + # Those are unused + errmsg_toobig = "Too large" + MAXNUM = 10 ** 36 def __init__(self): self.number = 0 diff --git a/num2words/lang_ID.py b/num2words/lang_ID.py index 1973e7e8..54e636f9 100644 --- a/num2words/lang_ID.py +++ b/num2words/lang_ID.py @@ -45,7 +45,7 @@ class Num2Word_ID(): errmsg_floatord = "Cannot treat float number as ordinal" errmsg_negord = "Cannot treat negative number as ordinal" errmsg_toobig = "Number is too large to convert to words (abs(%s) > %s)." - max_num = 10 ** 36 + MAXVAL = 10 ** 36 def split_by_koma(self, number): return str(number).split('.') @@ -169,8 +169,8 @@ def join(self, word_blocks, float_part): return ' '.join(word_list) + float_part def to_cardinal(self, number): - if number >= self.max_num: - raise OverflowError(self.errmsg_toobig % (number, self.max_num)) + if number >= self.MAXVAL: + raise OverflowError(self.errmsg_toobig % (number, self.MAXVAL)) minus = '' if number < 0: minus = 'min ' diff --git a/tests/test_ar.py b/tests/test_ar.py index fda08b48..f5f56214 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -130,4 +130,4 @@ def test_max_numbers(self): with self.assertRaises(OverflowError) as context: num2words(number, lang='ar') - self.assertTrue('Too large' in str(context.exception)) + self.assertTrue('must be less' in str(context.exception)) From 0f233c5b4c950e2610ecb77a1e42eaeb4f6cb79d Mon Sep 17 00:00:00 2001 From: Jeronymous Date: Wed, 29 Mar 2023 16:26:17 +0200 Subject: [PATCH 15/37] Resolves part of https://github.com/savoirfairelinux/num2words/issues/403 : results after calling to ordinal conversion should not change --- num2words/lang_AR.py | 1 + tests/test_ar.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index 11f51f3b..722fe192 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -354,6 +354,7 @@ def to_ordinal_num(self, value): return self.to_ordinal(value).strip() def to_cardinal(self, number): + self.isCurrencyNameFeminine = False number = self.validate_number(number) minus = '' if number < 0: diff --git a/tests/test_ar.py b/tests/test_ar.py index f5f56214..b98c5906 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -68,6 +68,8 @@ def test_currency_parm(self): 'واحد مليون دينار و تسع و تسعون فلس') def test_ordinal(self): + + self.assertEqual(num2words(1, to='ordinal', lang='ar'), 'اول') self.assertEqual(num2words(2, to='ordinal', lang='ar'), 'ثاني') self.assertEqual(num2words(3, to='ordinal', lang='ar'), 'ثالث') @@ -84,6 +86,11 @@ def test_ordinal(self): num2words(923411, to='ordinal_num', lang='ar'), 'تسعمائة و ثلاثة و عشرون ألفاً و أربعمائة و أحد عشر') + # See https://github.com/savoirfairelinux/num2words/issues/403 + self.assertEqual(num2words(23, lang="ar"), 'ثلاثة و عشرون') + self.assertEqual(num2words(23, to='ordinal', lang="ar"), 'ثلاث و عشرون') + self.assertEqual(num2words(23, lang="ar"), 'ثلاثة و عشرون') + def test_cardinal(self): self.assertEqual(num2words(12, to='cardinal', lang='ar'), 'اثنا عشر') self.assertEqual(num2words(-8324, to='cardinal', lang='ar'), From 8002ed41c3fd2f21b117db4e919469eb1826427f Mon Sep 17 00:00:00 2001 From: Jeronymous Date: Thu, 30 Mar 2023 08:48:58 +0200 Subject: [PATCH 16/37] Resolves https://github.com/savoirfairelinux/num2words/issues/394 : make the CLI work with Arabic --- num2words/lang_AR.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index 722fe192..935b9797 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -37,8 +37,10 @@ "تسعة عشر" ] +from .base import Num2Word_Base -class Num2Word_AR(object): # Num2Word_Base ? + +class Num2Word_AR(Num2Word_Base): errmsg_toobig = "abs(%s) must be less than %s." MAXVAL = 1000000000000000050331649 # 10 **36 From 45a8d503b4f7ebb4dc0f920f45d258650c32c715 Mon Sep 17 00:00:00 2001 From: hedi naouara Date: Thu, 30 Mar 2023 14:04:39 +0100 Subject: [PATCH 17/37] add big numbers until Quintinillion add a test function for a big numbers --- num2words/lang_AR.py | 39 +++++++++++++++++++++++++++++---------- tests/test_ar.py | 15 +++++++++++++-- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index 935b9797..438267ea 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -42,7 +42,7 @@ class Num2Word_AR(Num2Word_Base): errmsg_toobig = "abs(%s) must be less than %s." - MAXVAL = 1000000000000000050331649 # 10 **36 + MAXVAL = 9999999999999999999999999999999999999999999999999 # 1000000000000000050331649 # 10 **36 def __init__(self): super().__init__() @@ -79,26 +79,37 @@ def __init__(self): self.arabicHundreds = [ "", "مائة", "مئتان", "ثلاثمائة", "أربعمائة", "خمسمائة", "ستمائة", "سبعمائة", "ثمانمائة", "تسعمائة" - ] + ] # should be : ["تسعة مائة","ثمانية مائة","سبعة مائة","ستة مائة","خمسة مائة","أربعة مائة","ثلاثة مائة","مئتان","مائة"] + self.arabicAppendedTwos = [ "مئتا", "ألفا", "مليونا", "مليارا", "تريليونا", "كوادريليونا", - "كوينتليونا", "سكستيليونا" + "كوينتليونا", "سكستيليونا","سبتيليونا","أوكتيليونا ","نونيليونا", + "ديسيليونا","أندسيليونا","دوديسيليونا","تريديسيليونا","كوادريسيليونا", + "كوينتينيليونا" ] self.arabicTwos = [ "مئتان", "ألفان", "مليونان", "ملياران", "تريليونان", - "كوادريليونان", "كوينتليونان", "سكستيليونان" + "كوادريليونان", "كوينتليونان", "سكستيليونان","سبتيليونان", + "أوكتيليونان ","نونيليونان ","ديسيليونان","أندسيليونان", + "دوديسيليونان","تريديسيليونان","كوادريسيليونان","كوينتينيليونان" ] self.arabicGroup = [ "مائة", "ألف", "مليون", "مليار", "تريليون", "كوادريليون", - "كوينتليون", "سكستيليون" + "كوينتليون", "سكستيليون","سبتيليون","أوكتيليون","نونيليون", + "ديسيليون","أندسيليون","دوديسيليون","تريديسيليون","كوادريسيليون", + "كوينتينيليون" ] self.arabicAppendedGroup = [ "", "ألفاً", "مليوناً", "ملياراً", "تريليوناً", "كوادريليوناً", - "كوينتليوناً", "سكستيليوناً" + "كوينتليوناً", "سكستيليوناً","سبتيليوناً","أوكتيليوناً","نونيليوناً", + "ديسيليوناً","أندسيليوناً","دوديسيليوناً","تريديسيليوناً","كوادريسيليوناً", + "كوينتينيليوناً" ] self.arabicPluralGroups = [ "", "آلاف", "ملايين", "مليارات", "تريليونات", "كوادريليونات", - "كوينتليونات", "سكستيليونات" + "كوينتليونات", "سكستيليونات","سبتيليونات","أوكتيليونات","نونيليونات", + "ديسيليونات","أندسيليونات","دوديسيليونات","تريديسيليونات","كوادريسيليونات", + "كوينتينيليونات" ] assert len(self.arabicAppendedGroup) == len(self.arabicGroup) assert len(self.arabicPluralGroups) == len(self.arabicGroup) @@ -209,8 +220,16 @@ def process_arabic_group(self, group_number, group_level, return ret_val + def abs(self, number): + return number if number >= 0 else -number + + def to_str(self, number): + integer = int(number) + decimal = round((number - integer) * 10**9) + return str(integer) + "." + "{:09d}".format(decimal) + def convert(self, value): - self.number = "{:.9f}".format(value) + self.number = self.to_str(value) self.number_to_arabic(self.arabicPrefixText, self.arabicSuffixText) return self.convert_to_arabic() @@ -346,7 +365,7 @@ def to_ordinal(self, number, prefix=''): self.currency_unit = ('', '', '', '') self.arabicPrefixText = prefix self.arabicSuffixText = "" - return "{}".format(self.convert(abs(number)).strip()) + return "{}".format(self.convert(self.abs(number)).strip()) def to_year(self, value): value = self.validate_number(value) @@ -367,4 +386,4 @@ def to_cardinal(self, number): self.arabicPrefixText = "" self.arabicSuffixText = "" self.arabicOnes = ARABIC_ONES - return minus + self.convert(value=abs(number)).strip() + return minus + self.convert(value=self.abs(number)).strip() diff --git a/tests/test_ar.py b/tests/test_ar.py index b98c5906..f7645478 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -132,9 +132,20 @@ def test_year(self): def test_max_numbers(self): - for number in 10 ** 36, 123456789123456789123456789, 123456789123456789123456789123456789: - + for number in 9999999999999999999999999999999999999999999999999, 10000000000000000000000000000000000000000000000000: + with self.assertRaises(OverflowError) as context: num2words(number, lang='ar') self.assertTrue('must be less' in str(context.exception)) + + def test_big_numbers(self): + self.assertEqual(num2words(1000000045000000000000003000000002000000300, to='cardinal', lang='ar'), + 'واحد تريديسيليون و خمسة و أربعون ديسيليوناً و ثلاثة كوينتليونات و ملياران و ثلاثمائة' + ) + self.assertEqual(num2words(-1000000000000000000000003000000002000000302, to='cardinal', lang='ar'), + 'سالب واحد تريديسيليون و ثلاثة كوينتليونات و ملياران و ثلاثمائة و اثنان' + ) + self.assertEqual(num2words(9999999999999999999999999999999999999999999999992, to='cardinal', lang='ar'), + 'تسعة كوينتينيليونات و تسعمائةتسعة و تسعون كوادريسيليوناً و تسعمائةتسعة و تسعون تريديسيليوناً و تسعمائةتسعة و تسعون دوديسيليوناً و تسعمائةتسعة و تسعون أندسيليوناً و تسعمائةتسعة و تسعون ديسيليوناً و تسعمائةتسعة و تسعون نونيليوناً و تسعمائةتسعة و تسعون أوكتيليوناً و تسعمائةتسعة و تسعون سبتيليوناً و تسعمائةتسعة و تسعون سكستيليوناً و تسعمائةتسعة و تسعون كوينتليوناً و تسعمائةتسعة و تسعون كوادريليوناً و تسعمائةتسعة و تسعون تريليوناً و تسعمائةتسعة و تسعون ملياراً و تسعمائةتسعة و تسعون مليوناً و تسعمائةتسعة و تسعون ألفاً و تسعمائةاثنان و تسعون' + ) \ No newline at end of file From 55061d66cff7a489eb6634906c7f12a8f164a2fd Mon Sep 17 00:00:00 2001 From: KhramtsovDR Date: Sun, 2 Apr 2023 23:14:56 +0300 Subject: [PATCH 18/37] RU - adding cases, plural and animate --- num2words/lang_RU.py | 554 +++++++++++++++++++++++++++++-------------- tests/test_ru.py | 158 ++++++++++-- 2 files changed, 513 insertions(+), 199 deletions(-) diff --git a/num2words/lang_RU.py b/num2words/lang_RU.py index b87f9b9d..efaca2db 100644 --- a/num2words/lang_RU.py +++ b/num2words/lang_RU.py @@ -20,92 +20,225 @@ from .base import Num2Word_Base from .utils import get_digits, splitbyx -ZERO = 'ноль' +GENDER_PLURAL_INDEXES = { + 'm': 0, 'masculine': 0, 'м': 0, 'мужской': 0, + 'f': 1, 'feminine': 1, 'ж': 0, 'женский': 0, + 'n': 2, 'neuter': 2, 'с': 0, 'средний': 0, + 'p': 3, 'plural': 3 +} +CASE_INDEXES = { + 'n': 0, 'nominative': 0, 'и': 0, 'именительный': 0, + 'g': 1, 'genitive': 1, 'р': 1, 'родительный': 1, + 'd': 2, 'dative': 2, 'д': 2, 'дательный': 2, + 'a': 3, 'accusative': 3, 'в': 3, 'винительный': 3, + 'i': 4, 'instrumental': 4, 'т': 4, 'творительный': 4, + 'p': 5, 'prepositional': 5, 'п': 5, 'предложный': 5 +} +# Default values +D_CASE = 'n' +D_PLURAL = False +D_GENDER = 'm' +D_ANIMATE = True + + +def get_num_element(cases_dict, num, **kwargs): + return case_classifier_element(cases_dict[num], **kwargs) + + +def case_classifier_element(classifier, case=D_CASE, plural=D_PLURAL, + gender=D_GENDER, animate=D_ANIMATE): + case = classifier[CASE_INDEXES[case]] + if isinstance(case, str): + return case + if plural: + gender = case[GENDER_PLURAL_INDEXES['plural']] + else: + gender = case[GENDER_PLURAL_INDEXES[gender]] + if isinstance(gender, str): + return gender + + if animate: + return gender[0] + return gender[1] + + +# format: +# {n : [case_1 .. case_5]} +# case: text or [gender_1 .. gender_3 plural_4] +# gender: text or [animate, inanimate] ONES = { - 'f': { - 1: 'одна', - 2: 'две', - 3: 'три', - 4: 'четыре', - 5: 'пять', - 6: 'шесть', - 7: 'семь', - 8: 'восемь', - 9: 'девять', - }, - 'm': { - 1: 'один', - 2: 'два', - 3: 'три', - 4: 'четыре', - 5: 'пять', - 6: 'шесть', - 7: 'семь', - 8: 'восемь', - 9: 'девять', - }, - 'n': { - 1: 'одно', - 2: 'два', - 3: 'три', - 4: 'четыре', - 5: 'пять', - 6: 'шесть', - 7: 'семь', - 8: 'восемь', - 9: 'девять', - } + 0: ['ноль', 'ноля', 'нолю', 'ноль', 'нолём', 'ноле'], + 1: [['один', 'одна', 'одно', 'одни'], + ['одного', 'одной', 'одного', 'одних'], + ['одному', 'одной', 'одному', 'одним'], + [['одного', 'один'], 'одну', 'одно', ['одних', 'одни']], + ['одним', 'одной', 'одним', 'одними'], + ['одном', 'одной', 'одном', 'одних']], + 2: [['два', 'две', 'два', 'двое'], + ['двух'] * 3 + ['двоих'], + ['двум'] * 3 + ['двоим'], + [['двух', 'два'], ['двух', 'две'], 'два', 'двоих'], + ['двумя'] * 3 + ['двоими'], + ['двух'] * 3 + ['двоих']], + 3: [['три'] * 3 + ['трое'], + ['трёх'] * 3 + ['троих'], + ['трём'] * 3 + ['троим'], + [['трёх', 'три'], ['трёх', 'три'], 'три', 'троих'], + ['тремя'] * 3 + ['троими'], + ['трёх'] * 3 + ['троих']], + 4: [['четыре'] * 3 + ['четверо'], + ['четырёх'] * 3 + ['четверых'], + ['четырём'] * 3 + ['четверым'], + [['четырёх', 'четыре'], ['четырёх', 'четыре'], 'четыре', 'четверых'], + ['четырьмя'] * 3 + ['четверыми'], + ['четырёх'] * 3 + ['четверых']], + 5: ['пять', 'пяти', 'пяти', 'пять', 'пятью', 'пяти'], + 6: ['шесть', 'шести', 'шести', 'шесть', 'шестью', 'шести'], + 7: ['семь', 'семи', 'семи', 'семь', 'семью', 'семи'], + 8: ['восемь', 'восьми', 'восьми', 'восемь', 'восемью', 'восьми'], + 9: ['девять', 'девяти', 'девяти', 'девять', 'девятью', 'девяти'] } -TENS = { - 0: 'десять', - 1: 'одиннадцать', - 2: 'двенадцать', - 3: 'тринадцать', - 4: 'четырнадцать', - 5: 'пятнадцать', - 6: 'шестнадцать', - 7: 'семнадцать', - 8: 'восемнадцать', - 9: 'девятнадцать', +ONES_ORD_PREFIXES = {0: 'нулев', 1: 'перв', 2: 'втор', 4: 'четвёрт', 5: 'пят', + 6: 'шест', 7: 'седьм', 8: 'восьм', 9: 'девят'} +ONES_ORD_POSTFIXES_GROUPS = {0: 0, 1: 1, 2: 0, 4: 1, 5: 1, 6: 0, 7: 0, 8: 0, + 9: 1} +CASE_POSTFIXES = [[{0: 'ой', 1: 'ый'}, 'ая', 'ое', 'ые'], + ['ого', 'ой', 'ого', 'ых'], + ['ому', 'ой', 'ому', 'ым'], + [['ого', {0: 'ой', 1: 'ый'}], 'ую', 'ое', ['ых', 'ые']], + ['ым', 'ой', 'ым', 'ыми'], + ['ом', 'ой', 'ом', 'ых']] + + +def get_cases(prefix, post_group): + return [[ + prefix + postfix if isinstance(postfix, str) else + [prefix + animate if isinstance(animate, str) else + prefix + animate[post_group] + for animate in postfix] if isinstance(postfix, list) else + prefix + postfix[post_group] + for postfix in case] + for case in CASE_POSTFIXES] + + +def get_ord_classifier(prefixes, post_groups): + if isinstance(post_groups, int): + post_groups = {n: post_groups for n, i in prefixes.items()} + return { + num: get_cases(prefix, post_groups[num]) + for num, prefix in prefixes.items() + } + +ONES_ORD = { + 3: [['третий', 'третья', 'третье', 'третьи'], + ['третьего', 'третьей', 'третьего', 'третьих'], + ['третьему', 'третьей', 'третьему', 'третьим'], + [['третьего', 'третий'], 'третью', 'третье', ['третьих', 'третьи']], + ['третьим', 'третьей', 'третьим', 'третьими'], + ['третьем', 'третьей', 'третьем', 'третьих']], } +ONES_ORD.update( + get_ord_classifier(ONES_ORD_PREFIXES, ONES_ORD_POSTFIXES_GROUPS) +) + +TENS_PREFIXES = {1: 'один', 2: 'две', 3: 'три', 4: 'четыр', 5: 'пят', 6: 'шест', + 7: 'сем', 8: 'восем', 9: 'девят'} +TENS_POSTFIXES = ['надцать', 'надцати', 'надцати', 'надцать', 'надцатью', + 'надцати'] +TENS = {0: ['десять', 'десяти', 'десяти', 'десять', 'десятью', 'десяти']} +TENS.update({ + num: [prefix + postfix for postfix in TENS_POSTFIXES] + for num, prefix in TENS_PREFIXES.items() +}) + +TENS_ORD_PREFIXES = {0: "десят"} +TENS_ORD_PREFIXES.update({ + num: prefix + 'надцат' for num, prefix in TENS_PREFIXES.items() +}) +TENS_ORD = get_ord_classifier(TENS_ORD_PREFIXES, 1) TWENTIES = { - 2: 'двадцать', - 3: 'тридцать', - 4: 'сорок', - 5: 'пятьдесят', - 6: 'шестьдесят', - 7: 'семьдесят', - 8: 'восемьдесят', - 9: 'девяносто', + 2: ['двадцать', 'двадцати', 'двадцати', 'двадцать', 'двадцатью', + 'двадцати'], + 3: ['тридцать', 'тридцати', 'тридцати', 'тридцать', 'тридцатью', + 'тридцати'], + 4: ['сорок', 'сорока', 'сорока', 'сорок', 'сорока', 'сорока'], + 5: ['пятьдесят', 'пятидесяти', 'пятидесяти', 'пятьдесят', 'пятьюдесятью', + 'пятидесяти'], + 6: ['шестьдесят', 'шестидесяти', 'шестидесяти', 'шестьдесят', + 'шестьюдесятью', 'шестидесяти'], + 7: ['семьдесят', 'семидесяти', 'семидесяти', 'семьдесят', 'семьюдесятью', + 'семидесяти'], + 8: ['восемьдесят', 'восьмидесяти', 'восьмидесяти', 'восемьдесят', + 'восемьюдесятью', 'восьмидесяти'], + 9: ['девяносто', 'девяноста', 'девяноста', 'девяносто', 'девяноста', + 'девяноста'], } +TWENTIES_ORD_PREFIXES = {2: 'двадцат', 3: 'тридцат', 4: 'сороков', + 5: 'пятидесят',6: 'шестидесят', 7: 'семидесят', + 8: 'восьмидесят', 9: 'девяност'} +TWENTIES_ORD_POSTFIXES_GROUPS = {2: 1, 3: 1, 4: 0, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1} +TWENTIES_ORD = get_ord_classifier(TWENTIES_ORD_PREFIXES, + TWENTIES_ORD_POSTFIXES_GROUPS) + HUNDREDS = { - 1: 'сто', - 2: 'двести', - 3: 'триста', - 4: 'четыреста', - 5: 'пятьсот', - 6: 'шестьсот', - 7: 'семьсот', - 8: 'восемьсот', - 9: 'девятьсот', + 1: ['сто', 'ста', 'ста', 'сто', 'ста', 'ста'], + 2: ['двести', 'двухсот', 'двумстам', 'двести', 'двумястами', 'двухстах'], + 3: ['триста', 'трёхсот', 'трёмстам', 'триста', 'тремястами', 'трёхстах'], + 4: ['четыреста', 'четырёхсот', 'четырёмстам', 'четыреста', 'четырьмястами', + 'четырёхстах'], + 5: ['пятьсот', 'пятисот', 'пятистам', 'пятьсот', 'пятьюстами', 'пятистах'], + 6: ['шестьсот', 'шестисот', 'шестистам', 'шестьсот', 'шестьюстами', + 'шестистах'], + 7: ['семьсот', 'семисот', 'семистам', 'семьсот', 'семьюстами', 'семистах'], + 8: ['восемьсот', 'восьмисот', 'восьмистам', 'восемьсот', 'восемьюстами', + 'восьмистах'], + 9: ['девятьсот', 'девятисот', 'девятистам', 'девятьсот', 'девятьюстами', + 'девятистах'], } +HUNDREDS_ORD_PREFIXES = { + num: case[1] if num != 1 else 'сот' for num, case in HUNDREDS.items() +} +HUNDREDS_ORD = get_ord_classifier(HUNDREDS_ORD_PREFIXES, 1) + + +THOUSANDS_PREFIXES = {2: 'миллион', 3: 'миллиард', 4: 'триллион', + 5: 'квадриллион', 6: 'квинтиллион', 7: 'секстиллион', + 8: 'септиллион', 9: 'октиллион', 10: 'нониллион'} +THOUSANDS_POSTFIXES = [('', 'а', 'ов'), + ('а', 'ов', 'ов'), + ('у', 'ам', 'ам'), + ('', 'а', 'ов'), + ('ом', 'ами', 'ами'), + ('е', 'ах', 'ах')] THOUSANDS = { - 1: ('тысяча', 'тысячи', 'тысяч'), # 10^3 - 2: ('миллион', 'миллиона', 'миллионов'), # 10^6 - 3: ('миллиард', 'миллиарда', 'миллиардов'), # 10^9 - 4: ('триллион', 'триллиона', 'триллионов'), # 10^12 - 5: ('квадриллион', 'квадриллиона', 'квадриллионов'), # 10^15 - 6: ('квинтиллион', 'квинтиллиона', 'квинтиллионов'), # 10^18 - 7: ('секстиллион', 'секстиллиона', 'секстиллионов'), # 10^21 - 8: ('септиллион', 'септиллиона', 'септиллионов'), # 10^24 - 9: ('октиллион', 'октиллиона', 'октиллионов'), # 10^27 - 10: ('нониллион', 'нониллиона', 'нониллионов'), # 10^30 + 1: [['тысяча', 'тысячи', 'тысяч'], + ['тысячи', 'тысяч', 'тысяч'], + ['тысяче', 'тысячам', 'тысячам'], + ['тысячу', 'тысячи', 'тысяч'], + ['тысячей', 'тысячами', 'тысячами'], + ['тысяче', 'тысячах', 'тысячах']] } +THOUSANDS.update({ + num: [ + [prefix + postfix for postfix in case] for case in THOUSANDS_POSTFIXES + ] for num, prefix in THOUSANDS_PREFIXES.items() +}) + + +def get_thousands_elements(num, case): + return THOUSANDS[num][CASE_INDEXES[case]] + + +THOUSANDS_ORD_PREFIXES = {1: 'тысячн'} +THOUSANDS_ORD_PREFIXES.update({ + num: prefix + 'н' for num, prefix in THOUSANDS_PREFIXES.items() +}) +THOUSANDS_ORD = get_ord_classifier(THOUSANDS_ORD_PREFIXES, 1) class Num2Word_RU(Num2Word_Base): @@ -136,43 +269,30 @@ class Num2Word_RU(Num2Word_Base): def setup(self): self.negword = "минус" - self.pointword = "запятая" - self.ords = {"ноль": "нулевой", - "один": "первый", - "два": "второй", - "три": "третий", - "четыре": "четвертый", - "пять": "пятый", - "шесть": "шестой", - "семь": "седьмой", - "восемь": "восьмой", - "девять": "девятый", - "сто": "сотый"} - self.ords_adjective = {"один": "", - "одна": "", - "две": "двух", - "три": "трёх", - "четыре": "четырёх", - "пять": "пяти", - "шесть": "шести", - "семь": "семи", - "восемь": "восьми", - "девять": "девяти"} - - def to_cardinal(self, number, gender='m'): + self.pointword = ('целая', 'целых', 'целых') + self.pointword_ord = get_cases("цел", 1) + + def to_cardinal(self, number, case=D_CASE, plural=D_PLURAL, gender=D_GENDER, + animate=D_ANIMATE): n = str(number).replace(',', '.') if '.' in n: left, right = n.split('.') - leading_zero_count = len(right) - len(right.lstrip('0')) - decimal_part = ((ZERO + ' ') * leading_zero_count + - self._int2word(int(right), gender)) - return u'%s %s %s' % ( - self._int2word(int(left), gender), - self.pointword, - decimal_part + decimal_part = self._int2word(int(right), cardinal=True, gender='f') + return u'%s %s %s %s' % ( + self._int2word(int(left), cardinal=True, gender='f'), + self.pluralize(int(left), self.pointword), + decimal_part, + self.__decimal_bitness(right) ) else: - return self._int2word(int(n), gender) + return self._int2word(int(n), cardinal=True, case=case, + plural=plural, gender=gender, animate=animate) + + def __decimal_bitness(self, n): + l = len(n) + if n[-1] == "1" and n[-2:] != "11": + return self._int2word(10 ** l, cardinal=False, gender='f') + return self._int2word(10 ** l, cardinal=False, case='g', plural='t') def pluralize(self, n, forms): if n % 100 in (11, 12, 13, 14): @@ -183,82 +303,66 @@ def pluralize(self, n, forms): return forms[1] return forms[2] - def to_ordinal(self, number, gender='m'): + def to_ordinal(self, number, case=D_CASE, plural=D_PLURAL, gender=D_GENDER, + animate=D_ANIMATE): self.verify_ordinal(number) - outwords = self.to_cardinal(number, 'm').split(" ") - lastword = outwords[-1].lower() - try: - if len(outwords) > 1: - if outwords[-2] in self.ords_adjective: - outwords[-2] = self.ords_adjective.get( - outwords[-2], outwords[-2]) - elif outwords[-2] == 'десять': - outwords[-2] = outwords[-2][:-1] + 'и' - if len(outwords) == 3: - if outwords[-3] in ['один', 'одна']: - outwords[-3] = '' - lastword = self.ords[lastword] - except KeyError: - if lastword[:-3] in self.ords_adjective: - lastword = self.ords_adjective.get( - lastword[:-3], lastword) + "сотый" - elif lastword[-1] == "ь" or lastword[-2] == "т": - lastword = lastword[:-1] + "ый" - elif lastword[-1] == "к": - lastword = lastword + "овой" - elif lastword[-5:] == "десят": - lastword = lastword.replace('ь', 'и') + 'ый' - elif lastword[-2] == "ч" or lastword[-1] == "ч": - if lastword[-2] == "ч": - lastword = lastword[:-1] + "ный" - if lastword[-1] == "ч": - lastword = lastword + "ный" - elif lastword[-1] == "н" or lastword[-2] == "н": - lastword = lastword[:lastword.rfind('н') + 1] + "ный" - elif lastword[-1] == "д" or lastword[-2] == "д": - lastword = lastword[:lastword.rfind('д') + 1] + "ный" - - if gender == 'f': - if lastword[-2:] == "ий": - lastword = lastword[:-2] + "ья" - else: - lastword = lastword[:-2] + "ая" - if gender == 'n': - if lastword[-2:] == "ий": - lastword = lastword[:-2] + "ье" - else: - lastword = lastword[:-2] + "ое" - - outwords[-1] = self.title(lastword) - return " ".join(outwords).strip() + n = str(number).replace(',', '.') + return self._int2word(int(n), cardinal=False, case=case, plural=plural, + gender=gender, animate=animate) def _money_verbose(self, number, currency): if currency == 'UAH': - gender = 'f' - else: - gender = 'm' - - return self._int2word(number, gender) + return self._int2word(number, gender='f') + return self._int2word(number, gender='m') def _cents_verbose(self, number, currency): if currency in ('UAH', 'RUB', 'BYN'): + return self._int2word(number, gender='f') + return self._int2word(number, gender='m') + + def _int2word(self, n, feminine=False, cardinal=True, case=D_CASE, + plural=D_PLURAL, gender=D_GENDER, animate=D_ANIMATE): + """ + n: number + feminine: not used - for backward compatibility + gender: 'f' - masculine + 'm' - feminine + 'n' - neuter + case: 'n' - nominative + 'g' - genitive + 'd' - dative + 'a' - accusative + 'i' - instrumental + 'p' - prepositional + animate: True - animate + False - inanimate + ordinal: True - ordinal + False - cardinal + """ + # For backward compatibility + if feminine: gender = 'f' - else: - gender = 'm' - return self._int2word(number, gender) + kwargs = {'case': case, 'plural': plural, 'gender': gender, + 'animate': animate} - def _int2word(self, n, gender): if n < 0: - return ' '.join([self.negword, self._int2word(abs(n), gender)]) + return ' '.join([self.negword, self._int2word(abs(n), + cardinal=cardinal, + **kwargs)]) if n == 0: - return ZERO + return get_num_element(ONES, 0, **kwargs) if cardinal else \ + get_num_element(ONES_ORD, 0, **kwargs) words = [] chunks = list(splitbyx(str(n), 3)) + ord_join = chunks[-1] == 0 # join in one word if ending on 'тысячный' i = len(chunks) + rightest_nonzero_chunk_i = i - 1 - max( + [i for i, e in enumerate(chunks) if e != 0]) for x in chunks: + chunk_words = [] i -= 1 if x == 0: @@ -266,25 +370,117 @@ def _int2word(self, n, gender): n1, n2, n3 = get_digits(x) - if n3 > 0: - words.append(HUNDREDS[n3]) + if cardinal: + chunk_words.extend( + self.__chunk_cardianl(n3, n2, n1, i, **kwargs) + ) + if i > 0: + chunk_words.append( + self.pluralize(x, get_thousands_elements(i, case))) + # ordinal, not joined like 'двухтысячный' + elif not (ord_join and rightest_nonzero_chunk_i == i): + chunk_words.extend( + self.__chunk_ordinal(n3, n2, n1, i, **kwargs) + ) + if i > 0: + t_case = case if rightest_nonzero_chunk_i == i else 'n' + chunk_words.append( + self.pluralize(x, get_thousands_elements(i, t_case))) + # ordinal, joined + else: + chunk_words.extend( + self.__chunk_ordinal_join(n3, n2, n1, i, **kwargs) + ) + if i > 0: + chunk_words.append( + get_num_element(THOUSANDS_ORD, i, **kwargs)) - if n2 > 1: - words.append(TWENTIES[n2]) + chunk_words = [''.join(chunk_words)] - if n2 == 1: - words.append(TENS[n1]) - elif n1 > 0: - if i == 0: - ones = ONES[gender] - elif i == 1: - ones = ONES['f'] # Thousands is feminine - else: - ones = ONES['m'] + words.extend(chunk_words) - words.append(ones[n1]) + return ' '.join(words) + + def __chunk_cardianl(self, hundreds, tens, ones, chunk_num, **kwargs): + words = [] + if hundreds > 0: + words.append(get_num_element(HUNDREDS, hundreds, **kwargs)) + + if tens > 1: + words.append(get_num_element(TWENTIES, tens, **kwargs)) + + if tens == 1: + words.append(get_num_element(TENS, ones, **kwargs)) + elif ones > 0: + if chunk_num == 0: + w_ones = get_num_element(ONES, ones, **kwargs) + elif chunk_num == 1: + # Thousands are feminine + f_kwargs = kwargs.copy() + f_kwargs['gender'] = 'f' + w_ones = get_num_element(ONES, ones, **f_kwargs) + else: + w_ones = get_num_element(ONES, ones, **kwargs) - if i > 0: - words.append(self.pluralize(x, THOUSANDS[i])) + words.append(w_ones) + return words - return ' '.join(words) + def __chunk_ordinal(self, hundreds, tens, ones, chunk_num, **kwargs): + words = [] + if hundreds > 0: + if tens == 0 and ones == 0: + words.append(get_num_element(HUNDREDS_ORD, hundreds, **kwargs)) + else: + words.append(get_num_element(HUNDREDS, hundreds)) + + if tens > 1: + if ones == 0: + words.append(get_num_element(TWENTIES_ORD, tens, **kwargs)) + else: + words.append(get_num_element(TWENTIES, tens)) + + if tens == 1: + words.append(get_num_element(TENS_ORD, ones, **kwargs)) + elif ones > 0: + if chunk_num == 0: + w_ones = get_num_element(ONES_ORD, ones, **kwargs) + # тысячный, миллионнный и т.д. + elif chunk_num > 0 and ones == 1 and hundreds == 0 and tens == 0: + w_ones = None + elif chunk_num == 1: + # Thousands are feminine + w_ones = get_num_element(ONES, ones, gender='f') + else: + w_ones = get_num_element(ONES, ones) + + if w_ones: + words.append(w_ones) + + return words + + def __chunk_ordinal_join(self, hundreds, tens, ones, chunk_num, **kwargs): + words = [] + if hundreds > 0: + words.append(get_num_element(HUNDREDS, hundreds)) + + if tens > 1: + words.append(get_num_element(TWENTIES, tens, case='g')) + + if tens == 1: + words.append(get_num_element(TENS, ones, case='g')) + elif ones > 0: + if chunk_num == 0: + w_ones = get_num_element(ONES_ORD, ones, **kwargs) + # тысячный, миллионнный и т.д., двадцатиоднамиллионный + elif chunk_num > 0 and ones == 1 and tens != 1: + if tens == 0 and hundreds == 0: + w_ones = None + else: + w_ones = get_num_element(ONES, 1, gender='f') + else: + w_ones = get_num_element(ONES, ones, case='g') + + if w_ones: + words.append(w_ones) + + return words diff --git a/tests/test_ru.py b/tests/test_ru.py index 388cf2a9..3bbd784c 100644 --- a/tests/test_ru.py +++ b/tests/test_ru.py @@ -35,7 +35,8 @@ def test_cardinal(self): self.assertEqual(num2words(2012, lang='ru'), "две тысячи двенадцать") self.assertEqual( num2words(12519.85, lang='ru'), - "двенадцать тысяч пятьсот девятнадцать запятая восемьдесят пять") + "двенадцать тысяч пятьсот девятнадцать целых восемьдесят пять сотых" + ) self.assertEqual( num2words(1234567890, lang='ru'), "один миллиард двести тридцать четыре миллиона пятьсот " @@ -73,7 +74,7 @@ def test_cardinal(self): self.assertEqual(num2words(-15, lang='ru'), "минус пятнадцать") self.assertEqual(num2words(-100, lang='ru'), "минус сто") - def test_feminine(self): + def test_cardinal_feminine(self): self.assertEqual(num2words(1, lang='ru', gender='f'), 'одна') self.assertEqual(num2words(2, lang='ru', gender='f'), 'две') self.assertEqual(num2words(3, lang='ru', gender='f'), 'три') @@ -88,12 +89,12 @@ def test_feminine(self): ) self.assertEqual( num2words(125.1, lang='ru', gender='f'), - 'сто двадцать пять запятая одна' + 'сто двадцать пять целых одна десятая' ) self.assertEqual(num2words(-1, lang='ru', gender='f'), "минус одна") self.assertEqual(num2words(-100, lang='ru', gender='f'), "минус сто") - def test_neuter(self): + def test_cardinal_neuter(self): self.assertEqual(num2words(1, lang='ru', gender='n'), 'одно') self.assertEqual(num2words(2, lang='ru', gender='n'), 'два') self.assertEqual(num2words(3, lang='ru', gender='n'), 'три') @@ -108,26 +109,30 @@ def test_neuter(self): ) self.assertEqual( num2words(125.1, lang='ru', gender='n'), - 'сто двадцать пять запятая одно') + 'сто двадцать пять целых одна десятая') self.assertEqual(num2words(-1, lang='ru', gender='n'), "минус одно") self.assertEqual(num2words(-100, lang='ru', gender='n'), "минус сто") def test_floating_point(self): - self.assertEqual(num2words(5.2, lang='ru'), "пять запятая два") - self.assertEqual( - num2words(10.02, lang='ru'), - "десять запятая ноль два" - ) - self.assertEqual( - num2words(15.007, lang='ru'), - "пятнадцать запятая ноль ноль семь" - ) - self.assertEqual( - num2words(561.42, lang='ru'), - "пятьсот шестьдесят один запятая сорок два" - ) + self.assertEqual(num2words(5.2, lang='ru'), "пять целых две десятых") + self.assertEqual(num2words(1.001, lang='ru'), + "одна целая одна тысячная") + self.assertEqual(num2words(1.011, lang='ru'), + "одна целая одиннадцать тысячных") + self.assertEqual(num2words(10.02, lang='ru'), + "десять целых две сотых") + self.assertEqual(num2words(15.007, lang='ru'), + "пятнадцать целых семь тысячных") + self.assertEqual(num2words(561.42, lang='ru'), + "пятьсот шестьдесят одна целая сорок две сотых") + self.assertEqual(num2words(561.00001, lang='ru'), + "пятьсот шестьдесят одна целая одна стотысячная") def test_to_ordinal(self): + self.assertEqual( + num2words(0, lang='ru', to='ordinal'), + 'нулевой' + ) self.assertEqual( num2words(1, lang='ru', to='ordinal'), 'первый' @@ -181,18 +186,54 @@ def test_to_ordinal(self): num2words(1001, lang='ru', to='ordinal'), 'тысяча первый' ) + self.assertEqual( + num2words(1060, lang='ru', to='ordinal'), + 'тысяча шестидесятый' + ) self.assertEqual( num2words(2000, lang='ru', to='ordinal'), - 'двух тысячный' + 'двухтысячный' ) self.assertEqual( num2words(10000, lang='ru', to='ordinal'), - 'десяти тысячный' + 'десятитысячный' + ) + self.assertEqual( + num2words(21000, lang='ru', to='ordinal'), + 'двадцатиоднатысячный' + ) + self.assertEqual( + num2words(130000, lang='ru', to='ordinal'), + 'стотридцатитысячный' + ) + self.assertEqual( + num2words(135000, lang='ru', to='ordinal'), + 'стотридцатипятитысячный' + ) + self.assertEqual( + num2words(135100, lang='ru', to='ordinal'), + 'сто тридцать пять тысяч сотый' + ) + self.assertEqual( + num2words(135120, lang='ru', to='ordinal'), + 'сто тридцать пять тысяч сто двадцатый' + ) + self.assertEqual( + num2words(135121, lang='ru', to='ordinal'), + 'сто тридцать пять тысяч сто двадцать первый' ) self.assertEqual( num2words(1000000, lang='ru', to='ordinal'), 'миллионный' ) + self.assertEqual( + num2words(2000000, lang='ru', to='ordinal'), + 'двухмиллионный' + ) + self.assertEqual( + num2words(5135000, lang='ru', to='ordinal'), + 'пять миллионов стотридцатипятитысячный' + ) self.assertEqual( num2words(1000000000, lang='ru', to='ordinal'), 'миллиардный' @@ -215,6 +256,10 @@ def test_to_ordinal_feminine(self): self.assertEqual( num2words(1000, lang='ru', to='ordinal', gender='f'), 'тысячная' ) + self.assertEqual( + num2words(2000000, lang='ru', to='ordinal', gender='f'), + 'двухмиллионная' + ) def test_to_ordinal_neuter(self): self.assertEqual( @@ -233,6 +278,79 @@ def test_to_ordinal_neuter(self): self.assertEqual( num2words(1000, lang='ru', to='ordinal', gender='n'), 'тысячное' ) + self.assertEqual( + num2words(2000000, lang='ru', to='ordinal', gender='n'), + 'двухмиллионное' + ) + + def test_cardinal_cases(self): + self.assertEqual( + num2words(1, lang='ru', case='nominative'), 'один') + self.assertEqual( + num2words(1, lang='ru', case='genitive'), 'одного') + self.assertEqual( + num2words(1, lang='ru', case='a', plural=True, animate=False), + 'одни') + self.assertEqual( + num2words(2, lang='ru', case='a', gender='f', animate=True), + 'двух') + self.assertEqual( + num2words(2, lang='ru', case='a', gender='f', animate=False), + 'две') + self.assertEqual( + num2words(100, lang='ru', case='g'), + 'ста') + self.assertEqual( + num2words(122, lang='ru', case='d'), + 'ста двадцати двум') + self.assertEqual( + num2words(1000, lang='ru', case='p'), + 'одной тысяче') + self.assertEqual( + num2words(1122, lang='ru', case='p'), + 'одной тысяче ста двадцати двух') + self.assertEqual( + num2words(1211, lang='ru', case='i', gender='f'), + 'одной тысячей двумястами одиннадцатью') + self.assertEqual( + num2words(5121000, lang='ru', case='i'), + 'пятью миллионами ста двадцатью одной тысячей') + + def test_ordinal_cases(self): + self.assertEqual( + num2words(1, lang='ru', to='ordinal', case='nominative'), 'первый') + self.assertEqual( + num2words(1, lang='ru', to='ordinal', case='genitive'), 'первого') + self.assertEqual( + num2words(1, lang='ru', to='ordinal', case='a', plural=True, + animate=False), + 'первые') + self.assertEqual( + num2words(2, lang='ru', to='ordinal', case='a', gender='f', + animate=True), + 'вторую') + self.assertEqual( + num2words(2, lang='ru', to='ordinal', case='a', gender='f', + animate=False), + 'вторую') + self.assertEqual( + num2words(100, lang='ru', to='ordinal', case='g'), + 'сотого') + self.assertEqual( + num2words(122, lang='ru', to='ordinal', case='d'), + 'сто двадцать второму') + self.assertEqual( + num2words(1000, lang='ru', to='ordinal', case='p'), + 'тысячном') + self.assertEqual( + num2words(1122, lang='ru', to='ordinal', case='p'), + 'тысяча сто двадцать втором') + self.assertEqual( + num2words(1211, lang='ru', to='ordinal', case='i', gender='f'), + 'тысяча двести одиннадцатой') + self.assertEqual( + num2words(5121000, lang='ru', to='ordinal', case='i'), + 'пять миллионов стодвадцатиоднатысячным') def test_to_currency(self): self.assertEqual( From 283d3ca8fcdedd21ff18cb40edd5bcd406ffae28 Mon Sep 17 00:00:00 2001 From: hedi naouara Date: Mon, 3 Apr 2023 15:25:04 +0100 Subject: [PATCH 19/37] solve ar: -chahnge the max number to 10 ** 51 -1 -solve the et in 262 and others -add some tests --- num2words/lang_AR.py | 18 ++++++++---------- tests/test_ar.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index 438267ea..69c40e66 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -42,7 +42,7 @@ class Num2Word_AR(Num2Word_Base): errmsg_toobig = "abs(%s) must be less than %s." - MAXVAL = 9999999999999999999999999999999999999999999999999 # 1000000000000000050331649 # 10 **36 + MAXVAL = 10**51 - 1 # 9999999999999999999999999999999999999999999999999 # 1000000000000000050331649 # 10 **36 def __init__(self): super().__init__() @@ -172,12 +172,14 @@ def process_arabic_group(self, group_number, group_level, tens = Decimal(group_number) % Decimal(100) hundreds = Decimal(group_number) / Decimal(100) ret_val = "" - + if int(hundreds) > 0: if tens == 0 and int(hundreds) == 2: ret_val = "{}".format(self.arabicAppendedTwos[0]) else: ret_val = "{}".format(self.arabicHundreds[int(hundreds)]) + if ret_val !="" and tens != 0: + ret_val += " و " if tens > 0: if tens < 20: @@ -186,16 +188,14 @@ def process_arabic_group(self, group_number, group_level, if tens == 2 and int(hundreds) == 0 and group_level > 0: if self.integer_value in [2000, 2000000, 2000000000, 2000000000000, 2000000000000000, - 2000000000000000000]: + 2000000000000000000,2000000000000000000000,2000000000000000000000000 + ]: ret_val = "{}".format( self.arabicAppendedTwos[int(group_level)]) else: ret_val = "{}".format( self.arabicTwos[int(group_level)]) else: - if ret_val != "": - ret_val += " و " - if tens == 1 and group_level > 0 and hundreds == 0: ret_val += "" elif (tens == 1 or tens == 2) and ( @@ -209,14 +209,12 @@ def process_arabic_group(self, group_number, group_level, ones = tens % 10 tens = (tens / 10) - 2 if ones > 0: - if ret_val != "" and tens < 4: - ret_val += " و " - ret_val += self.digit_feminine_status(ones, group_level) - if ret_val != "": + if ret_val != "" and ones != 0: ret_val += " و " ret_val += self.arabicTens[int(tens)] + return ret_val diff --git a/tests/test_ar.py b/tests/test_ar.py index f7645478..cf0ca23e 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -110,6 +110,12 @@ def test_cardinal(self): #'سبعة مائة و واحد و أربعون' 'سبعمائة و واحد و أربعون' ) + self.assertEqual(num2words(262, to='cardinal', lang='ar'), + 'مئتان و اثنان و ستون' + ) + self.assertEqual(num2words(798, to='cardinal', lang='ar'), + 'سبعمائة و ثمانية و تسعون' + ) self.assertEqual(num2words(710, to='cardinal', lang='ar'), 'سبعمائة و عشرة') self.assertEqual(num2words(711, to='cardinal', lang='ar'), @@ -132,7 +138,7 @@ def test_year(self): def test_max_numbers(self): - for number in 9999999999999999999999999999999999999999999999999, 10000000000000000000000000000000000000000000000000: + for number in 10**51 - 1 , 10**51: with self.assertRaises(OverflowError) as context: num2words(number, lang='ar') @@ -147,5 +153,5 @@ def test_big_numbers(self): 'سالب واحد تريديسيليون و ثلاثة كوينتليونات و ملياران و ثلاثمائة و اثنان' ) self.assertEqual(num2words(9999999999999999999999999999999999999999999999992, to='cardinal', lang='ar'), - 'تسعة كوينتينيليونات و تسعمائةتسعة و تسعون كوادريسيليوناً و تسعمائةتسعة و تسعون تريديسيليوناً و تسعمائةتسعة و تسعون دوديسيليوناً و تسعمائةتسعة و تسعون أندسيليوناً و تسعمائةتسعة و تسعون ديسيليوناً و تسعمائةتسعة و تسعون نونيليوناً و تسعمائةتسعة و تسعون أوكتيليوناً و تسعمائةتسعة و تسعون سبتيليوناً و تسعمائةتسعة و تسعون سكستيليوناً و تسعمائةتسعة و تسعون كوينتليوناً و تسعمائةتسعة و تسعون كوادريليوناً و تسعمائةتسعة و تسعون تريليوناً و تسعمائةتسعة و تسعون ملياراً و تسعمائةتسعة و تسعون مليوناً و تسعمائةتسعة و تسعون ألفاً و تسعمائةاثنان و تسعون' + 'تسعة كوينتينيليونات و تسعمائة و تسعة و تسعون كوادريسيليوناً و تسعمائة و تسعة و تسعون تريديسيليوناً و تسعمائة و تسعة و تسعون دوديسيليوناً و تسعمائة و تسعة و تسعون أندسيليوناً و تسعمائة و تسعة و تسعون ديسيليوناً و تسعمائة و تسعة و تسعون نونيليوناً و تسعمائة و تسعة و تسعون أوكتيليوناً و تسعمائة و تسعة و تسعون سبتيليوناً و تسعمائة و تسعة و تسعون سكستيليوناً و تسعمائة و تسعة و تسعون كوينتليوناً و تسعمائة و تسعة و تسعون كوادريليوناً و تسعمائة و تسعة و تسعون تريليوناً و تسعمائة و تسعة و تسعون ملياراً و تسعمائة و تسعة و تسعون مليوناً و تسعمائة و تسعة و تسعون ألفاً و تسعمائة و اثنان و تسعون' ) \ No newline at end of file From c5c4c59988499540c55fbac798372591c3f6a0fd Mon Sep 17 00:00:00 2001 From: KhramtsovDR Date: Mon, 3 Apr 2023 17:26:41 +0300 Subject: [PATCH 20/37] Fix error with ordinal 21000 ( https://russian.stackexchange.com/a/4386 ) --- num2words/lang_RU.py | 2 +- tests/test_ru.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/num2words/lang_RU.py b/num2words/lang_RU.py index efaca2db..b34f0e31 100644 --- a/num2words/lang_RU.py +++ b/num2words/lang_RU.py @@ -476,7 +476,7 @@ def __chunk_ordinal_join(self, hundreds, tens, ones, chunk_num, **kwargs): if tens == 0 and hundreds == 0: w_ones = None else: - w_ones = get_num_element(ONES, 1, gender='f') + w_ones = get_num_element(ONES, 1, gender='n') else: w_ones = get_num_element(ONES, ones, case='g') diff --git a/tests/test_ru.py b/tests/test_ru.py index 3bbd784c..ea13bb7f 100644 --- a/tests/test_ru.py +++ b/tests/test_ru.py @@ -115,6 +115,7 @@ def test_cardinal_neuter(self): def test_floating_point(self): self.assertEqual(num2words(5.2, lang='ru'), "пять целых две десятых") + self.assertEqual(num2words(5.0, lang='ru'), "пять целых ноль десятых") self.assertEqual(num2words(1.001, lang='ru'), "одна целая одна тысячная") self.assertEqual(num2words(1.011, lang='ru'), @@ -200,7 +201,7 @@ def test_to_ordinal(self): ) self.assertEqual( num2words(21000, lang='ru', to='ordinal'), - 'двадцатиоднатысячный' + 'двадцатиоднотысячный' ) self.assertEqual( num2words(130000, lang='ru', to='ordinal'), @@ -350,7 +351,7 @@ def test_ordinal_cases(self): 'тысяча двести одиннадцатой') self.assertEqual( num2words(5121000, lang='ru', to='ordinal', case='i'), - 'пять миллионов стодвадцатиоднатысячным') + 'пять миллионов стодвадцатиоднотысячным') def test_to_currency(self): self.assertEqual( From 7883b2936bbe2574d704c515fb2f63246b8f58da Mon Sep 17 00:00:00 2001 From: Mario Monroy Date: Mon, 3 Apr 2023 12:22:28 -0600 Subject: [PATCH 21/37] FIx --- num2words/lang_ES.py | 1 - tests/test_es_gt.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/num2words/lang_ES.py b/num2words/lang_ES.py index 3dc70141..de2d1e13 100644 --- a/num2words/lang_ES.py +++ b/num2words/lang_ES.py @@ -206,7 +206,6 @@ class Num2Word_ES(Num2Word_EU): 'ZRZ': (('zaire', 'zaires'), ('likuta', 'makuta')), 'ZWL': (GENERIC_DOLLARS, ('céntimo', 'céntimos')), 'ZWL': (GENERIC_DOLLARS, ('céntimo', 'céntimos')), - 'GTQ': (('quetzal', 'quetzales'), GENERIC_CENTS), } # //CHECK: Is this sufficient?? diff --git a/tests/test_es_gt.py b/tests/test_es_gt.py index 1c9429fa..b65f8d33 100644 --- a/tests/test_es_gt.py +++ b/tests/test_es_gt.py @@ -48,7 +48,7 @@ def test_ordinal(self): def test_ordinal_num(self): for test in test_es.TEST_CASES_ORDINAL_NUM: self.assertEqual( - num2words(test[0], lang='es', to='ordinal_num'), + num2words(test[0], lang='es_GT', to='ordinal_num'), test[1] ) From 4863fb16b0bcc234b2f66dd1dad17d0a0cbcfe14 Mon Sep 17 00:00:00 2001 From: hedi naouara Date: Tue, 4 Apr 2023 09:54:18 +0100 Subject: [PATCH 22/37] solving ar issues: - change maxval to 10 ** 51. - add comments to abs & to_str functions. - change a list of integer_value by a pow condition --- num2words/lang_AR.py | 13 +++++++------ tests/test_ar.py | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index 69c40e66..da3a1321 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -17,6 +17,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA +import math import re import decimal from decimal import Decimal @@ -42,7 +43,7 @@ class Num2Word_AR(Num2Word_Base): errmsg_toobig = "abs(%s) must be less than %s." - MAXVAL = 10**51 - 1 # 9999999999999999999999999999999999999999999999999 # 1000000000000000050331649 # 10 **36 + MAXVAL = 10**51 # 9999999999999999999999999999999999999999999999999 # 1000000000000000050331649 # 10 **36 def __init__(self): super().__init__() @@ -186,10 +187,8 @@ def process_arabic_group(self, group_number, group_level, if int(group_level) >= len(self.arabicTwos): raise OverflowError(self.errmsg_toobig % (self.number, self.MAXVAL)) if tens == 2 and int(hundreds) == 0 and group_level > 0: - if self.integer_value in [2000, 2000000, 2000000000, - 2000000000000, 2000000000000000, - 2000000000000000000,2000000000000000000000,2000000000000000000000000 - ]: + pow = int(math.log10(self.integer_value)) + if self.integer_value > 10 and pow % 3 == 0 and self.integer_value == 2 * (10 ** pow): ret_val = "{}".format( self.arabicAppendedTwos[int(group_level)]) else: @@ -217,10 +216,12 @@ def process_arabic_group(self, group_number, group_level, return ret_val - + + # We use this instead of built-in `abs` function, because `abs` suffers from loss of precision for big numbers def abs(self, number): return number if number >= 0 else -number + # We use this instead of `"{:09d}".format(number)`, because the string conversion suffers from loss of precision for big numbers def to_str(self, number): integer = int(number) decimal = round((number - integer) * 10**9) diff --git a/tests/test_ar.py b/tests/test_ar.py index cf0ca23e..d9c695fd 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -138,7 +138,7 @@ def test_year(self): def test_max_numbers(self): - for number in 10**51 - 1 , 10**51: + for number in 10**51,10**51 + 2: with self.assertRaises(OverflowError) as context: num2words(number, lang='ar') From 667723aca42e5f9023c70e55d6593dbc0424b73e Mon Sep 17 00:00:00 2001 From: hedi naouara Date: Wed, 5 Apr 2023 08:56:30 +0100 Subject: [PATCH 23/37] solve the one problem --- num2words/lang_AR.py | 12 +++++++----- tests/test_ar.py | 26 ++++++++++++++++++-------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index da3a1321..7e23d2c6 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -164,10 +164,9 @@ def digit_feminine_status(self, digit, group_level): return self.arabicFeminineOnes[int(digit)] else: return self.arabicOnes[int(digit)] - else: return self.arabicOnes[int(digit)] - + def process_arabic_group(self, group_number, group_level, remaining_number): tens = Decimal(group_number) % Decimal(100) @@ -195,12 +194,15 @@ def process_arabic_group(self, group_number, group_level, ret_val = "{}".format( self.arabicTwos[int(group_level)]) else: + if tens == 1 and group_level > 0 and hundreds == 0: ret_val += "" elif (tens == 1 or tens == 2) and ( group_level == 0 or group_level == -1) and \ hundreds == 0 and remaining_number == 0: ret_val += "" + elif tens == 1 and group_level > 0: + ret_val += self.arabicGroup[int(group_level)] else: ret_val += self.digit_feminine_status(int(tens), group_level) @@ -263,11 +265,11 @@ def convert_to_arabic(self): if group > 0: if ret_val != "": ret_val = "{}و {}".format("", ret_val) - if number_to_process != 2: + if number_to_process != 2 and number_to_process != 1: if group >= len(self.arabicGroup): raise OverflowError(self.errmsg_toobig % (self.number, self.MAXVAL)) if number_to_process % 100 != 1: - if 3 <= number_to_process <= 10: + if 3 <= number_to_process <= 9: ret_val = "{} {}".format( self.arabicPluralGroups[group], ret_val) else: @@ -278,7 +280,7 @@ def convert_to_arabic(self): else: ret_val = "{} {}".format( self.arabicGroup[group], ret_val) - + else: ret_val = "{} {}".format(self.arabicGroup[group], ret_val) diff --git a/tests/test_ar.py b/tests/test_ar.py index d9c695fd..3a292209 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -38,17 +38,17 @@ def test_default_currency(self): self.assertEqual(num2words(541, to='currency', lang='ar'), 'خمسمائة و واحد و أربعون ريالاً') self.assertEqual(num2words(10000, to='currency', lang='ar'), - 'عشرة آلاف ريال') + 'عشرة ألف ريال') self.assertEqual(num2words(20000.12, to='currency', lang='ar'), 'عشرون ألف ريال و اثنتا عشرة هللة') self.assertEqual(num2words(1000000, to='currency', lang='ar'), - 'واحد مليون ريال') + 'مليون ريال') val = 'تسعمائة و ثلاثة و عشرون ألفاً و أربعمائة و أحد عشر ريالاً' self.assertEqual(num2words(923411, to='currency', lang='ar'), val) self.assertEqual(num2words(63411, to='currency', lang='ar'), 'ثلاثة و ستون ألفاً و أربعمائة و أحد عشر ريالاً') self.assertEqual(num2words(1000000.99, to='currency', lang='ar'), - 'واحد مليون ريال و تسع و تسعون هللة') + 'مليون ريال و تسع و تسعون هللة') def test_currency_parm(self): self.assertEqual( @@ -65,7 +65,7 @@ def test_currency_parm(self): 'تسعمائة و ثلاثة و عشرون ألفاً و أربعمائة و أحد عشر ريالاً') self.assertEqual( num2words(1000000.99, to='currency', lang='ar', currency="KWD"), - 'واحد مليون دينار و تسع و تسعون فلس') + 'مليون دينار و تسع و تسعون فلس') def test_ordinal(self): @@ -103,7 +103,7 @@ def test_cardinal(self): self.assertEqual(num2words(94231, to='cardinal', lang='ar'), 'أربعة و تسعون ألفاً و مئتان و واحد و ثلاثون') self.assertEqual(num2words(1431, to='cardinal', lang='ar'), - 'واحد ألف و أربعمائة و واحد و ثلاثون') + 'ألف و أربعمائة و واحد و ثلاثون') self.assertEqual(num2words(740, to='cardinal', lang='ar'), 'سبعمائة و أربعون') self.assertEqual(num2words(741, to='cardinal', lang='ar'), @@ -127,6 +127,14 @@ def test_cardinal(self): self.assertEqual(num2words(701, to='cardinal', lang='ar'), 'سبعمائة و واحد') + self.assertEqual(num2words(1258888, to='cardinal', lang='ar'), + 'مليون و مئتان و ثمانية و خمسون ألفاً و ثمانمائة و ثمانية و ثمانون') + + self.assertEqual(num2words(1100, to='cardinal', lang='ar'), + 'ألف و مائة') + + self.assertEqual(num2words(1000000521, to='cardinal', lang='ar'), + 'مليار و خمسمائة و واحد و عشرون') def test_prefix_and_suffix(self): self.assertEqual(num2words(645, to='currency', @@ -147,11 +155,13 @@ def test_max_numbers(self): def test_big_numbers(self): self.assertEqual(num2words(1000000045000000000000003000000002000000300, to='cardinal', lang='ar'), - 'واحد تريديسيليون و خمسة و أربعون ديسيليوناً و ثلاثة كوينتليونات و ملياران و ثلاثمائة' + 'تريديسيليون و خمسة و أربعون ديسيليوناً و ثلاثة كوينتليونات و ملياران و ثلاثمائة' ) self.assertEqual(num2words(-1000000000000000000000003000000002000000302, to='cardinal', lang='ar'), - 'سالب واحد تريديسيليون و ثلاثة كوينتليونات و ملياران و ثلاثمائة و اثنان' + 'سالب تريديسيليون و ثلاثة كوينتليونات و ملياران و ثلاثمائة و اثنان' ) self.assertEqual(num2words(9999999999999999999999999999999999999999999999992, to='cardinal', lang='ar'), 'تسعة كوينتينيليونات و تسعمائة و تسعة و تسعون كوادريسيليوناً و تسعمائة و تسعة و تسعون تريديسيليوناً و تسعمائة و تسعة و تسعون دوديسيليوناً و تسعمائة و تسعة و تسعون أندسيليوناً و تسعمائة و تسعة و تسعون ديسيليوناً و تسعمائة و تسعة و تسعون نونيليوناً و تسعمائة و تسعة و تسعون أوكتيليوناً و تسعمائة و تسعة و تسعون سبتيليوناً و تسعمائة و تسعة و تسعون سكستيليوناً و تسعمائة و تسعة و تسعون كوينتليوناً و تسعمائة و تسعة و تسعون كوادريليوناً و تسعمائة و تسعة و تسعون تريليوناً و تسعمائة و تسعة و تسعون ملياراً و تسعمائة و تسعة و تسعون مليوناً و تسعمائة و تسعة و تسعون ألفاً و تسعمائة و اثنان و تسعون' - ) \ No newline at end of file + ) + + \ No newline at end of file From ff9385fb95a4a4a6d644140782a1005559137e43 Mon Sep 17 00:00:00 2001 From: hedi naouara Date: Wed, 5 Apr 2023 08:58:03 +0100 Subject: [PATCH 24/37] solve the 10000 currency problem --- num2words/lang_AR.py | 2 +- tests/test_ar.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index 7e23d2c6..e40b107c 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -269,7 +269,7 @@ def convert_to_arabic(self): if group >= len(self.arabicGroup): raise OverflowError(self.errmsg_toobig % (self.number, self.MAXVAL)) if number_to_process % 100 != 1: - if 3 <= number_to_process <= 9: + if 3 <= number_to_process <= 10: ret_val = "{} {}".format( self.arabicPluralGroups[group], ret_val) else: diff --git a/tests/test_ar.py b/tests/test_ar.py index 3a292209..20058f05 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -38,7 +38,7 @@ def test_default_currency(self): self.assertEqual(num2words(541, to='currency', lang='ar'), 'خمسمائة و واحد و أربعون ريالاً') self.assertEqual(num2words(10000, to='currency', lang='ar'), - 'عشرة ألف ريال') + 'عشرة آلاف ريال') self.assertEqual(num2words(20000.12, to='currency', lang='ar'), 'عشرون ألف ريال و اثنتا عشرة هللة') self.assertEqual(num2words(1000000, to='currency', lang='ar'), From 02a04c91ac206d1087b334b572043e8b262c1d14 Mon Sep 17 00:00:00 2001 From: Mario Monroy Date: Wed, 5 Apr 2023 18:54:58 -0600 Subject: [PATCH 25/37] fixes flake8 --- num2words/__init__.py | 2 +- num2words/lang_ES_GT.py | 9 ++++++--- tests/test_es.py | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/num2words/__init__.py b/num2words/__init__.py index 9193ab5a..5b975107 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -43,9 +43,9 @@ 'eo': lang_EO.Num2Word_EO(), 'es': lang_ES.Num2Word_ES(), 'es_CO': lang_ES_CO.Num2Word_ES_CO(), + 'es_GT': lang_ES_GT.Num2Word_ES_GT(), 'es_NI': lang_ES_NI.Num2Word_ES_NI(), 'es_VE': lang_ES_VE.Num2Word_ES_VE(), - 'es_GT': lang_ES_GT.Num2Word_ES_GT(), 'id': lang_ID.Num2Word_ID(), 'ja': lang_JA.Num2Word_JA(), 'kn': lang_KN.Num2Word_KN(), diff --git a/num2words/lang_ES_GT.py b/num2words/lang_ES_GT.py index 70902fc1..9bc4c750 100644 --- a/num2words/lang_ES_GT.py +++ b/num2words/lang_ES_GT.py @@ -23,7 +23,10 @@ class Num2Word_ES_GT(Num2Word_ES): def to_currency(self, val, longval=True, old=False): - result = self.to_splitnum(val, hightxt="quetzal/es", lowtxt="centavo/s", - divisor=1, jointxt="y", longval=longval) - # Handle exception, in spanish is "un euro" and not "uno euro" + result = self.to_splitnum(val, hightxt="quetzal/es", + lowtxt="centavo/s", + divisor=1, jointxt="y", + longval=longval) + # Handle exception, in spanish is "un euro" + # and not "uno euro" return result.replace("uno", "un") diff --git a/tests/test_es.py b/tests/test_es.py index 68a14e0f..a2c1242b 100644 --- a/tests/test_es.py +++ b/tests/test_es.py @@ -2742,7 +2742,7 @@ def test_currency_qar(self): def test_currency_qtq(self): for test in TEST_CASES_TO_CURRENCY_GTQ: self.assertEqual( - num2words(test[0], lang='es', to='currency', currency='QTQ'), + num2words(test[0], lang='es', to='currency', currency='GTQ'), test[1] ) From 373f6420f697bbc2acf92b6746d77f4066b04d18 Mon Sep 17 00:00:00 2001 From: KhramtsovDR Date: Thu, 6 Apr 2023 11:29:53 +0300 Subject: [PATCH 26/37] Fix 100000 and 90000 ordinal exceptions --- num2words/lang_RU.py | 13 +++++++++---- tests/test_ru.py | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/num2words/lang_RU.py b/num2words/lang_RU.py index b34f0e31..4a3bbf90 100644 --- a/num2words/lang_RU.py +++ b/num2words/lang_RU.py @@ -460,10 +460,15 @@ def __chunk_ordinal(self, hundreds, tens, ones, chunk_num, **kwargs): def __chunk_ordinal_join(self, hundreds, tens, ones, chunk_num, **kwargs): words = [] - if hundreds > 0: - words.append(get_num_element(HUNDREDS, hundreds)) - if tens > 1: + if hundreds > 1: + words.append(get_num_element(HUNDREDS, hundreds, case='g')) + elif hundreds == 1: + words.append(get_num_element(HUNDREDS, hundreds)) # стО, not стА + + if tens == 9: + words.append(get_num_element(TWENTIES, tens)) # девяностО, not А + elif tens > 1: words.append(get_num_element(TWENTIES, tens, case='g')) if tens == 1: @@ -471,7 +476,7 @@ def __chunk_ordinal_join(self, hundreds, tens, ones, chunk_num, **kwargs): elif ones > 0: if chunk_num == 0: w_ones = get_num_element(ONES_ORD, ones, **kwargs) - # тысячный, миллионнный и т.д., двадцатиоднамиллионный + # тысячный, миллионнный и т.д., двадцатиодномиллионный elif chunk_num > 0 and ones == 1 and tens != 1: if tens == 0 and hundreds == 0: w_ones = None diff --git a/tests/test_ru.py b/tests/test_ru.py index ea13bb7f..6cf85b3e 100644 --- a/tests/test_ru.py +++ b/tests/test_ru.py @@ -199,6 +199,10 @@ def test_to_ordinal(self): num2words(10000, lang='ru', to='ordinal'), 'десятитысячный' ) + self.assertEqual( + num2words(90000, lang='ru', to='ordinal'), + 'девяностотысячный' + ) self.assertEqual( num2words(21000, lang='ru', to='ordinal'), 'двадцатиоднотысячный' @@ -223,6 +227,10 @@ def test_to_ordinal(self): num2words(135121, lang='ru', to='ordinal'), 'сто тридцать пять тысяч сто двадцать первый' ) + self.assertEqual( + num2words(190000, lang='ru', to='ordinal'), + 'стодевяностотысячный' + ) self.assertEqual( num2words(1000000, lang='ru', to='ordinal'), 'миллионный' @@ -235,10 +243,18 @@ def test_to_ordinal(self): num2words(5135000, lang='ru', to='ordinal'), 'пять миллионов стотридцатипятитысячный' ) + self.assertEqual( + num2words(21000000, lang='ru', to='ordinal'), + 'двадцатиодномиллионный' + ) self.assertEqual( num2words(1000000000, lang='ru', to='ordinal'), 'миллиардный' ) + self.assertEqual( + num2words(123456000000, lang='ru', to='ordinal'), + 'сто двадцать три миллиарда четырёхсотпятидесятишестимиллионный' + ) def test_to_ordinal_feminine(self): self.assertEqual( From b9c6c632d4e69982bbd98b6c4801a2a5199841e9 Mon Sep 17 00:00:00 2001 From: Jeronymous Date: Fri, 7 Apr 2023 11:11:22 +0200 Subject: [PATCH 27/37] format code --- num2words/lang_AR.py | 52 +++++++++++++++++++-------------------- tests/test_ar.py | 58 +++++++++++++++++++++----------------------- 2 files changed, 54 insertions(+), 56 deletions(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index e40b107c..036b7d0d 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -17,6 +17,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA +from .base import Num2Word_Base import math import re import decimal @@ -38,12 +39,10 @@ "تسعة عشر" ] -from .base import Num2Word_Base - class Num2Word_AR(Num2Word_Base): errmsg_toobig = "abs(%s) must be less than %s." - MAXVAL = 10**51 # 9999999999999999999999999999999999999999999999999 # 1000000000000000050331649 # 10 **36 + MAXVAL = 10**51 def __init__(self): super().__init__() @@ -80,36 +79,36 @@ def __init__(self): self.arabicHundreds = [ "", "مائة", "مئتان", "ثلاثمائة", "أربعمائة", "خمسمائة", "ستمائة", "سبعمائة", "ثمانمائة", "تسعمائة" - ] # should be : ["تسعة مائة","ثمانية مائة","سبعة مائة","ستة مائة","خمسة مائة","أربعة مائة","ثلاثة مائة","مئتان","مائة"] + ] # should be : ["تسعة مائة","ثمانية مائة","سبعة مائة","ستة مائة","خمسة مائة","أربعة مائة","ثلاثة مائة","مئتان","مائة"] self.arabicAppendedTwos = [ "مئتا", "ألفا", "مليونا", "مليارا", "تريليونا", "كوادريليونا", - "كوينتليونا", "سكستيليونا","سبتيليونا","أوكتيليونا ","نونيليونا", - "ديسيليونا","أندسيليونا","دوديسيليونا","تريديسيليونا","كوادريسيليونا", + "كوينتليونا", "سكستيليونا", "سبتيليونا", "أوكتيليونا ", "نونيليونا", + "ديسيليونا", "أندسيليونا", "دوديسيليونا", "تريديسيليونا", "كوادريسيليونا", "كوينتينيليونا" ] self.arabicTwos = [ "مئتان", "ألفان", "مليونان", "ملياران", "تريليونان", - "كوادريليونان", "كوينتليونان", "سكستيليونان","سبتيليونان", - "أوكتيليونان ","نونيليونان ","ديسيليونان","أندسيليونان", - "دوديسيليونان","تريديسيليونان","كوادريسيليونان","كوينتينيليونان" + "كوادريليونان", "كوينتليونان", "سكستيليونان", "سبتيليونان", + "أوكتيليونان ", "نونيليونان ", "ديسيليونان", "أندسيليونان", + "دوديسيليونان", "تريديسيليونان", "كوادريسيليونان", "كوينتينيليونان" ] self.arabicGroup = [ "مائة", "ألف", "مليون", "مليار", "تريليون", "كوادريليون", - "كوينتليون", "سكستيليون","سبتيليون","أوكتيليون","نونيليون", - "ديسيليون","أندسيليون","دوديسيليون","تريديسيليون","كوادريسيليون", + "كوينتليون", "سكستيليون", "سبتيليون", "أوكتيليون", "نونيليون", + "ديسيليون", "أندسيليون", "دوديسيليون", "تريديسيليون", "كوادريسيليون", "كوينتينيليون" ] self.arabicAppendedGroup = [ "", "ألفاً", "مليوناً", "ملياراً", "تريليوناً", "كوادريليوناً", - "كوينتليوناً", "سكستيليوناً","سبتيليوناً","أوكتيليوناً","نونيليوناً", - "ديسيليوناً","أندسيليوناً","دوديسيليوناً","تريديسيليوناً","كوادريسيليوناً", + "كوينتليوناً", "سكستيليوناً", "سبتيليوناً", "أوكتيليوناً", "نونيليوناً", + "ديسيليوناً", "أندسيليوناً", "دوديسيليوناً", "تريديسيليوناً", "كوادريسيليوناً", "كوينتينيليوناً" ] self.arabicPluralGroups = [ "", "آلاف", "ملايين", "مليارات", "تريليونات", "كوادريليونات", - "كوينتليونات", "سكستيليونات","سبتيليونات","أوكتيليونات","نونيليونات", - "ديسيليونات","أندسيليونات","دوديسيليونات","تريديسيليونات","كوادريسيليونات", + "كوينتليونات", "سكستيليونات", "سبتيليونات", "أوكتيليونات", "نونيليونات", + "ديسيليونات", "أندسيليونات", "دوديسيليونات", "تريديسيليونات", "كوادريسيليونات", "كوينتينيليونات" ] assert len(self.arabicAppendedGroup) == len(self.arabicGroup) @@ -166,25 +165,26 @@ def digit_feminine_status(self, digit, group_level): return self.arabicOnes[int(digit)] else: return self.arabicOnes[int(digit)] - + def process_arabic_group(self, group_number, group_level, remaining_number): tens = Decimal(group_number) % Decimal(100) hundreds = Decimal(group_number) / Decimal(100) ret_val = "" - + if int(hundreds) > 0: if tens == 0 and int(hundreds) == 2: ret_val = "{}".format(self.arabicAppendedTwos[0]) else: ret_val = "{}".format(self.arabicHundreds[int(hundreds)]) - if ret_val !="" and tens != 0: + if ret_val != "" and tens != 0: ret_val += " و " if tens > 0: if tens < 20: if int(group_level) >= len(self.arabicTwos): - raise OverflowError(self.errmsg_toobig % (self.number, self.MAXVAL)) + raise OverflowError(self.errmsg_toobig % + (self.number, self.MAXVAL)) if tens == 2 and int(hundreds) == 0 and group_level > 0: pow = int(math.log10(self.integer_value)) if self.integer_value > 10 and pow % 3 == 0 and self.integer_value == 2 * (10 ** pow): @@ -194,7 +194,7 @@ def process_arabic_group(self, group_number, group_level, ret_val = "{}".format( self.arabicTwos[int(group_level)]) else: - + if tens == 1 and group_level > 0 and hundreds == 0: ret_val += "" elif (tens == 1 or tens == 2) and ( @@ -215,14 +215,13 @@ def process_arabic_group(self, group_number, group_level, ret_val += " و " ret_val += self.arabicTens[int(tens)] - return ret_val - + # We use this instead of built-in `abs` function, because `abs` suffers from loss of precision for big numbers def abs(self, number): return number if number >= 0 else -number - + # We use this instead of `"{:09d}".format(number)`, because the string conversion suffers from loss of precision for big numbers def to_str(self, number): integer = int(number) @@ -251,7 +250,7 @@ def convert_to_arabic(self): temp_number_dec = Decimal(str(temp_number)) try: number_to_process = int(temp_number_dec % Decimal(str(1000))) - except decimal.InvalidOperation: # https://stackoverflow.com/questions/42868278/decimal-invalidoperation-divisionimpossible-for-very-large-numbers + except decimal.InvalidOperation: # https://stackoverflow.com/questions/42868278/decimal-invalidoperation-divisionimpossible-for-very-large-numbers decimal.getcontext().prec = len(temp_number_dec.as_tuple().digits) number_to_process = int(temp_number_dec % Decimal(str(1000))) @@ -267,7 +266,8 @@ def convert_to_arabic(self): ret_val = "{}و {}".format("", ret_val) if number_to_process != 2 and number_to_process != 1: if group >= len(self.arabicGroup): - raise OverflowError(self.errmsg_toobig % (self.number, self.MAXVAL)) + raise OverflowError( + self.errmsg_toobig % (self.number, self.MAXVAL)) if number_to_process % 100 != 1: if 3 <= number_to_process <= 10: ret_val = "{} {}".format( @@ -280,7 +280,7 @@ def convert_to_arabic(self): else: ret_val = "{} {}".format( self.arabicGroup[group], ret_val) - + else: ret_val = "{} {}".format(self.arabicGroup[group], ret_val) diff --git a/tests/test_ar.py b/tests/test_ar.py index 20058f05..1c0c9754 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -69,7 +69,6 @@ def test_currency_parm(self): def test_ordinal(self): - self.assertEqual(num2words(1, to='ordinal', lang='ar'), 'اول') self.assertEqual(num2words(2, to='ordinal', lang='ar'), 'ثاني') self.assertEqual(num2words(3, to='ordinal', lang='ar'), 'ثالث') @@ -87,8 +86,9 @@ def test_ordinal(self): 'تسعمائة و ثلاثة و عشرون ألفاً و أربعمائة و أحد عشر') # See https://github.com/savoirfairelinux/num2words/issues/403 - self.assertEqual(num2words(23, lang="ar"), 'ثلاثة و عشرون') - self.assertEqual(num2words(23, to='ordinal', lang="ar"), 'ثلاث و عشرون') + self.assertEqual(num2words(23, lang="ar"), 'ثلاثة و عشرون') + self.assertEqual(num2words(23, to='ordinal', + lang="ar"), 'ثلاث و عشرون') self.assertEqual(num2words(23, lang="ar"), 'ثلاثة و عشرون') def test_cardinal(self): @@ -107,34 +107,34 @@ def test_cardinal(self): self.assertEqual(num2words(740, to='cardinal', lang='ar'), 'سبعمائة و أربعون') self.assertEqual(num2words(741, to='cardinal', lang='ar'), - #'سبعة مائة و واحد و أربعون' - 'سبعمائة و واحد و أربعون' - ) + # 'سبعة مائة و واحد و أربعون' + 'سبعمائة و واحد و أربعون' + ) self.assertEqual(num2words(262, to='cardinal', lang='ar'), - 'مئتان و اثنان و ستون' - ) + 'مئتان و اثنان و ستون' + ) self.assertEqual(num2words(798, to='cardinal', lang='ar'), - 'سبعمائة و ثمانية و تسعون' - ) + 'سبعمائة و ثمانية و تسعون' + ) self.assertEqual(num2words(710, to='cardinal', lang='ar'), - 'سبعمائة و عشرة') + 'سبعمائة و عشرة') self.assertEqual(num2words(711, to='cardinal', lang='ar'), - # 'سبعة مائة و إحدى عشر' - 'سبعمائة و أحد عشر' - ) + # 'سبعة مائة و إحدى عشر' + 'سبعمائة و أحد عشر' + ) self.assertEqual(num2words(700, to='cardinal', lang='ar'), - 'سبعمائة') + 'سبعمائة') self.assertEqual(num2words(701, to='cardinal', lang='ar'), - 'سبعمائة و واحد') - + 'سبعمائة و واحد') + self.assertEqual(num2words(1258888, to='cardinal', lang='ar'), - 'مليون و مئتان و ثمانية و خمسون ألفاً و ثمانمائة و ثمانية و ثمانون') - + 'مليون و مئتان و ثمانية و خمسون ألفاً و ثمانمائة و ثمانية و ثمانون') + self.assertEqual(num2words(1100, to='cardinal', lang='ar'), - 'ألف و مائة') - + 'ألف و مائة') + self.assertEqual(num2words(1000000521, to='cardinal', lang='ar'), - 'مليار و خمسمائة و واحد و عشرون') + 'مليار و خمسمائة و واحد و عشرون') def test_prefix_and_suffix(self): self.assertEqual(num2words(645, to='currency', @@ -146,22 +146,20 @@ def test_year(self): def test_max_numbers(self): - for number in 10**51,10**51 + 2: - + for number in 10**51, 10**51 + 2: + with self.assertRaises(OverflowError) as context: num2words(number, lang='ar') self.assertTrue('must be less' in str(context.exception)) def test_big_numbers(self): - self.assertEqual(num2words(1000000045000000000000003000000002000000300, to='cardinal', lang='ar'), + self.assertEqual(num2words(1000000045000000000000003000000002000000300, to='cardinal', lang='ar'), 'تريديسيليون و خمسة و أربعون ديسيليوناً و ثلاثة كوينتليونات و ملياران و ثلاثمائة' ) - self.assertEqual(num2words(-1000000000000000000000003000000002000000302, to='cardinal', lang='ar'), + self.assertEqual(num2words(-1000000000000000000000003000000002000000302, to='cardinal', lang='ar'), 'سالب تريديسيليون و ثلاثة كوينتليونات و ملياران و ثلاثمائة و اثنان' ) - self.assertEqual(num2words(9999999999999999999999999999999999999999999999992, to='cardinal', lang='ar'), - 'تسعة كوينتينيليونات و تسعمائة و تسعة و تسعون كوادريسيليوناً و تسعمائة و تسعة و تسعون تريديسيليوناً و تسعمائة و تسعة و تسعون دوديسيليوناً و تسعمائة و تسعة و تسعون أندسيليوناً و تسعمائة و تسعة و تسعون ديسيليوناً و تسعمائة و تسعة و تسعون نونيليوناً و تسعمائة و تسعة و تسعون أوكتيليوناً و تسعمائة و تسعة و تسعون سبتيليوناً و تسعمائة و تسعة و تسعون سكستيليوناً و تسعمائة و تسعة و تسعون كوينتليوناً و تسعمائة و تسعة و تسعون كوادريليوناً و تسعمائة و تسعة و تسعون تريليوناً و تسعمائة و تسعة و تسعون ملياراً و تسعمائة و تسعة و تسعون مليوناً و تسعمائة و تسعة و تسعون ألفاً و تسعمائة و اثنان و تسعون' + self.assertEqual(num2words(9999999999999999999999999999999999999999999999992, to='cardinal', lang='ar'), + 'تسعة كوينتينيليونات و تسعمائة و تسعة و تسعون كوادريسيليوناً و تسعمائة و تسعة و تسعون تريديسيليوناً و تسعمائة و تسعة و تسعون دوديسيليوناً و تسعمائة و تسعة و تسعون أندسيليوناً و تسعمائة و تسعة و تسعون ديسيليوناً و تسعمائة و تسعة و تسعون نونيليوناً و تسعمائة و تسعة و تسعون أوكتيليوناً و تسعمائة و تسعة و تسعون سبتيليوناً و تسعمائة و تسعة و تسعون سكستيليوناً و تسعمائة و تسعة و تسعون كوينتليوناً و تسعمائة و تسعة و تسعون كوادريليوناً و تسعمائة و تسعة و تسعون تريليوناً و تسعمائة و تسعة و تسعون ملياراً و تسعمائة و تسعة و تسعون مليوناً و تسعمائة و تسعة و تسعون ألفاً و تسعمائة و اثنان و تسعون' ) - - \ No newline at end of file From 488eeea4e47293cd658c9166dcf5b4cd0d2da56b Mon Sep 17 00:00:00 2001 From: Jeronymous Date: Fri, 7 Apr 2023 17:52:58 +0200 Subject: [PATCH 28/37] code formatting to pass flake8 --- num2words/lang_AR.py | 48 ++++++++++++++++++++++++++------------------ num2words/lang_FR.py | 4 +++- num2words/lang_RO.py | 3 ++- num2words/lang_SL.py | 4 +++- tests/test_ar.py | 40 ++++++++++++++++++++++++++---------- 5 files changed, 65 insertions(+), 34 deletions(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index 036b7d0d..229606a3 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -17,13 +17,14 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA -from .base import Num2Word_Base +import decimal import math import re -import decimal from decimal import Decimal from math import floor +from .base import Num2Word_Base + CURRENCY_SR = [("ريال", "ريالان", "ريالات", "ريالاً"), ("هللة", "هللتان", "هللات", "هللة")] CURRENCY_EGP = [("جنيه", "جنيهان", "جنيهات", "جنيهاً"), @@ -79,13 +80,13 @@ def __init__(self): self.arabicHundreds = [ "", "مائة", "مئتان", "ثلاثمائة", "أربعمائة", "خمسمائة", "ستمائة", "سبعمائة", "ثمانمائة", "تسعمائة" - ] # should be : ["تسعة مائة","ثمانية مائة","سبعة مائة","ستة مائة","خمسة مائة","أربعة مائة","ثلاثة مائة","مئتان","مائة"] + ] self.arabicAppendedTwos = [ "مئتا", "ألفا", "مليونا", "مليارا", "تريليونا", "كوادريليونا", - "كوينتليونا", "سكستيليونا", "سبتيليونا", "أوكتيليونا ", "نونيليونا", - "ديسيليونا", "أندسيليونا", "دوديسيليونا", "تريديسيليونا", "كوادريسيليونا", - "كوينتينيليونا" + "كوينتليونا", "سكستيليونا", "سبتيليونا", "أوكتيليونا ", + "نونيليونا", "ديسيليونا", "أندسيليونا", "دوديسيليونا", + "تريديسيليونا", "كوادريسيليونا", "كوينتينيليونا" ] self.arabicTwos = [ "مئتان", "ألفان", "مليونان", "ملياران", "تريليونان", @@ -96,20 +97,20 @@ def __init__(self): self.arabicGroup = [ "مائة", "ألف", "مليون", "مليار", "تريليون", "كوادريليون", "كوينتليون", "سكستيليون", "سبتيليون", "أوكتيليون", "نونيليون", - "ديسيليون", "أندسيليون", "دوديسيليون", "تريديسيليون", "كوادريسيليون", - "كوينتينيليون" + "ديسيليون", "أندسيليون", "دوديسيليون", "تريديسيليون", + "كوادريسيليون", "كوينتينيليون" ] self.arabicAppendedGroup = [ "", "ألفاً", "مليوناً", "ملياراً", "تريليوناً", "كوادريليوناً", - "كوينتليوناً", "سكستيليوناً", "سبتيليوناً", "أوكتيليوناً", "نونيليوناً", - "ديسيليوناً", "أندسيليوناً", "دوديسيليوناً", "تريديسيليوناً", "كوادريسيليوناً", - "كوينتينيليوناً" + "كوينتليوناً", "سكستيليوناً", "سبتيليوناً", "أوكتيليوناً", + "نونيليوناً", "ديسيليوناً", "أندسيليوناً", "دوديسيليوناً", + "تريديسيليوناً", "كوادريسيليوناً", "كوينتينيليوناً" ] self.arabicPluralGroups = [ "", "آلاف", "ملايين", "مليارات", "تريليونات", "كوادريليونات", - "كوينتليونات", "سكستيليونات", "سبتيليونات", "أوكتيليونات", "نونيليونات", - "ديسيليونات", "أندسيليونات", "دوديسيليونات", "تريديسيليونات", "كوادريسيليونات", - "كوينتينيليونات" + "كوينتليونات", "سكستيليونات", "سبتيليونات", "أوكتيليونات", + "نونيليونات", "ديسيليونات", "أندسيليونات", "دوديسيليونات", + "تريديسيليونات", "كوادريسيليونات", "كوينتينيليونات" ] assert len(self.arabicAppendedGroup) == len(self.arabicGroup) assert len(self.arabicPluralGroups) == len(self.arabicGroup) @@ -187,7 +188,8 @@ def process_arabic_group(self, group_number, group_level, (self.number, self.MAXVAL)) if tens == 2 and int(hundreds) == 0 and group_level > 0: pow = int(math.log10(self.integer_value)) - if self.integer_value > 10 and pow % 3 == 0 and self.integer_value == 2 * (10 ** pow): + if self.integer_value > 10 and pow % 3 == 0 and \ + self.integer_value == 2 * (10 ** pow): ret_val = "{}".format( self.arabicAppendedTwos[int(group_level)]) else: @@ -218,11 +220,14 @@ def process_arabic_group(self, group_number, group_level, return ret_val - # We use this instead of built-in `abs` function, because `abs` suffers from loss of precision for big numbers + # We use this instead of built-in `abs` function, + # because `abs` suffers from loss of precision for big numbers def abs(self, number): return number if number >= 0 else -number - # We use this instead of `"{:09d}".format(number)`, because the string conversion suffers from loss of precision for big numbers + # We use this instead of `"{:09d}".format(number)`, + # because the string conversion suffers from loss of + # precision for big numbers def to_str(self, number): integer = int(number) decimal = round((number - integer) * 10**9) @@ -250,8 +255,10 @@ def convert_to_arabic(self): temp_number_dec = Decimal(str(temp_number)) try: number_to_process = int(temp_number_dec % Decimal(str(1000))) - except decimal.InvalidOperation: # https://stackoverflow.com/questions/42868278/decimal-invalidoperation-divisionimpossible-for-very-large-numbers - decimal.getcontext().prec = len(temp_number_dec.as_tuple().digits) + except decimal.InvalidOperation: + decimal.getcontext().prec = len( + temp_number_dec.as_tuple().digits + ) number_to_process = int(temp_number_dec % Decimal(str(1000))) temp_number = int(temp_number_dec / Decimal(1000)) @@ -267,7 +274,8 @@ def convert_to_arabic(self): if number_to_process != 2 and number_to_process != 1: if group >= len(self.arabicGroup): raise OverflowError( - self.errmsg_toobig % (self.number, self.MAXVAL)) + self.errmsg_toobig % (self.number, self.MAXVAL) + ) if number_to_process % 100 != 1: if 3 <= number_to_process <= 10: ret_val = "{} {}".format( diff --git a/num2words/lang_FR.py b/num2words/lang_FR.py index 82942a4e..f843205c 100644 --- a/num2words/lang_FR.py +++ b/num2words/lang_FR.py @@ -37,7 +37,9 @@ def setup(self): self.errmsg_nonnum = ( u"Seulement des nombres peuvent être convertis en mots." ) - self.errmsg_toobig = u"Nombre trop grand pour être converti en mots (abs(%s) > %s)." + self.errmsg_toobig = ( + u"Nombre trop grand pour être converti en mots (abs(%s) > %s)." + ) self.exclude_title = ["et", "virgule", "moins"] self.mid_numwords = [(1000, "mille"), (100, "cent"), (80, "quatre-vingts"), (60, "soixante"), diff --git a/num2words/lang_RO.py b/num2words/lang_RO.py index b3fcef2a..feb96105 100644 --- a/num2words/lang_RO.py +++ b/num2words/lang_RO.py @@ -34,7 +34,8 @@ def setup(self): self.pointword = "virgulă" self.exclude_title = ["și", "virgulă", "minus"] self.errmsg_toobig = ( - "Numărul e prea mare pentru a fi convertit în cuvinte (abs(%s) > %s)." + "Numărul e prea mare pentru a \ +fi convertit în cuvinte (abs(%s) > %s)." ) self.mid_numwords = [(1000, "mie/i"), (100, "sută/e"), (90, "nouăzeci"), (80, "optzeci"), diff --git a/num2words/lang_SL.py b/num2words/lang_SL.py index cecbbc79..617b0335 100644 --- a/num2words/lang_SL.py +++ b/num2words/lang_SL.py @@ -31,7 +31,9 @@ def setup(self): self.negword = "minus " self.pointword = "celih" self.errmsg_nonnum = "Only numbers may be converted to words." - self.errmsg_toobig = "Number is too large to convert to words (abs(%s) > %s)." + self.errmsg_toobig = ( + "Number is too large to convert to words (abs(%s) > %s)." + ) self.exclude_title = [] self.mid_numwords = [(1000, "tisoč"), (900, "devetsto"), diff --git a/tests/test_ar.py b/tests/test_ar.py index 1c0c9754..50f9dd74 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -127,8 +127,10 @@ def test_cardinal(self): self.assertEqual(num2words(701, to='cardinal', lang='ar'), 'سبعمائة و واحد') - self.assertEqual(num2words(1258888, to='cardinal', lang='ar'), - 'مليون و مئتان و ثمانية و خمسون ألفاً و ثمانمائة و ثمانية و ثمانون') + self.assertEqual( + num2words(1258888, to='cardinal', lang='ar'), + 'مليون و مئتان و ثمانية و خمسون ألفاً و ثمانمائة و ثمانية و ثمانون' + ) self.assertEqual(num2words(1100, to='cardinal', lang='ar'), 'ألف و مائة') @@ -154,12 +156,28 @@ def test_max_numbers(self): self.assertTrue('must be less' in str(context.exception)) def test_big_numbers(self): - self.assertEqual(num2words(1000000045000000000000003000000002000000300, to='cardinal', lang='ar'), - 'تريديسيليون و خمسة و أربعون ديسيليوناً و ثلاثة كوينتليونات و ملياران و ثلاثمائة' - ) - self.assertEqual(num2words(-1000000000000000000000003000000002000000302, to='cardinal', lang='ar'), - 'سالب تريديسيليون و ثلاثة كوينتليونات و ملياران و ثلاثمائة و اثنان' - ) - self.assertEqual(num2words(9999999999999999999999999999999999999999999999992, to='cardinal', lang='ar'), - 'تسعة كوينتينيليونات و تسعمائة و تسعة و تسعون كوادريسيليوناً و تسعمائة و تسعة و تسعون تريديسيليوناً و تسعمائة و تسعة و تسعون دوديسيليوناً و تسعمائة و تسعة و تسعون أندسيليوناً و تسعمائة و تسعة و تسعون ديسيليوناً و تسعمائة و تسعة و تسعون نونيليوناً و تسعمائة و تسعة و تسعون أوكتيليوناً و تسعمائة و تسعة و تسعون سبتيليوناً و تسعمائة و تسعة و تسعون سكستيليوناً و تسعمائة و تسعة و تسعون كوينتليوناً و تسعمائة و تسعة و تسعون كوادريليوناً و تسعمائة و تسعة و تسعون تريليوناً و تسعمائة و تسعة و تسعون ملياراً و تسعمائة و تسعة و تسعون مليوناً و تسعمائة و تسعة و تسعون ألفاً و تسعمائة و اثنان و تسعون' - ) + self.assertEqual( + num2words(1000000045000000000000003000000002000000300, + to='cardinal', lang='ar'), + 'تريديسيليون و خمسة و أربعون ديسيليوناً\ + و ثلاثة كوينتليونات و ملياران و ثلاثمائة' + ) + self.assertEqual( + num2words(-1000000000000000000000003000000002000000302, + to='cardinal', lang='ar'), + 'سالب تريديسيليون و ثلاثة كوينتليونات \ +و ملياران و ثلاثمائة و اثنان' + ) + self.assertEqual( + num2words(9999999999999999999999999999999999999999999999992, + to='cardinal', lang='ar'), + 'تسعة كوينتينيليونات و تسعمائة و تسعة و تسعون كوادريسيليوناً و تسعمائة و تسعة\ + و تسعون تريديسيليوناً و تسعمائة و تسعة و تسعون دوديسيليوناً و تسعمائة\ + و تسعة و تسعون أندسيليوناً و تسعمائة و تسعة و تسعون ديسيليوناً\ + و تسعمائة و تسعة و تسعون نونيليوناً و تسعمائة و تسعة و تسعون\ + أوكتيليوناً و تسعمائة و تسعة و تسعون سبتيليوناً و تسعمائة و تسعة\ + و تسعون سكستيليوناً و تسعمائة و تسعة و تسعون كوينتليوناً و تسعمائة و\ + تسعة و تسعون كوادريليوناً و تسعمائة و تسعة و تسعون تريليوناً\ + و تسعمائة و تسعة و تسعون ملياراً و تسعمائة و تسعة و تسعون مليوناً\ + و تسعمائة و تسعة و تسعون ألفاً و تسعمائة و اثنان و تسعون' + ) From cc4b2cccbb1662f79a249cbe970dd4041500befc Mon Sep 17 00:00:00 2001 From: Mario Monroy Date: Fri, 7 Apr 2023 11:59:43 -0600 Subject: [PATCH 29/37] fix isort --- num2words/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/num2words/__init__.py b/num2words/__init__.py index 2f7cab56..0942928f 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -18,13 +18,13 @@ from __future__ import unicode_literals from . import (lang_AM, lang_AR, lang_AZ, lang_CZ, lang_DE, lang_DK, lang_EN, - lang_EN_IN, lang_EN_NG, lang_EO, lang_ES, lang_ES_CO, lang_ES_GT, - lang_ES_NI, lang_ES_VE, lang_FA, lang_FI, lang_FR, lang_FR_BE, - lang_FR_CH, lang_FR_DZ, lang_HE, lang_HU, lang_ID, lang_IS, - lang_IT, lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT, lang_LV, - lang_NL, lang_NO, lang_PL, lang_PT, lang_PT_BR, lang_RO, - lang_RU, lang_SL, lang_SR, lang_SV, lang_TE, lang_TG, lang_TH, - lang_TR, lang_UK, lang_VI) + lang_EN_IN, lang_EN_NG, lang_EO, lang_ES, lang_ES_CO, + lang_ES_GT, lang_ES_NI, lang_ES_VE, lang_FA, lang_FI, lang_FR, + lang_FR_BE, lang_FR_CH, lang_FR_DZ, lang_HE, lang_HU, lang_ID, + lang_IS, lang_IT, lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT, + lang_LV, lang_NL, lang_NO, lang_PL, lang_PT, lang_PT_BR, + lang_RO, lang_RU, lang_SL, lang_SR, lang_SV, lang_TE, lang_TG, + lang_TH, lang_TR, lang_UK, lang_VI) CONVERTER_CLASSES = { 'am': lang_AM.Num2Word_AM(), From 66ff4958ce69ce1e1b718c7be7f9d0318f79b9aa Mon Sep 17 00:00:00 2001 From: Jeronymous Date: Tue, 11 Apr 2023 09:14:56 +0200 Subject: [PATCH 30/37] split too long line --- tests/test_ar.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_ar.py b/tests/test_ar.py index 50f9dd74..0d281700 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -171,7 +171,8 @@ def test_big_numbers(self): self.assertEqual( num2words(9999999999999999999999999999999999999999999999992, to='cardinal', lang='ar'), - 'تسعة كوينتينيليونات و تسعمائة و تسعة و تسعون كوادريسيليوناً و تسعمائة و تسعة\ + 'تسعة كوينتينيليونات و تسعمائة و\ + تسعة و تسعون كوادريسيليوناً و تسعمائة و تسعة\ و تسعون تريديسيليوناً و تسعمائة و تسعة و تسعون دوديسيليوناً و تسعمائة\ و تسعة و تسعون أندسيليوناً و تسعمائة و تسعة و تسعون ديسيليوناً\ و تسعمائة و تسعة و تسعون نونيليوناً و تسعمائة و تسعة و تسعون\ From 37dc16eb96580cad2687f837382c173f10cd856e Mon Sep 17 00:00:00 2001 From: Jeronymous Date: Tue, 11 Apr 2023 19:25:23 +0200 Subject: [PATCH 31/37] improve code test coverage --- num2words/lang_AR.py | 30 +++++++++++++++++++----------- tests/test_ar.py | 22 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/num2words/lang_AR.py b/num2words/lang_AR.py index 229606a3..7195dd47 100644 --- a/num2words/lang_AR.py +++ b/num2words/lang_AR.py @@ -122,7 +122,6 @@ def number_to_arabic(self, arabic_prefix_text, arabic_suffix_text): self.extract_integer_and_decimal_parts() def extract_integer_and_decimal_parts(self): - re.split('\\.', str(self.number)) splits = re.split('\\.', str(self.number)) self.integer_value = int(splits[0]) @@ -149,8 +148,9 @@ def decimal_value(self, decimal_part): else: result = decimal_part - for i in range(len(result), self.partPrecision): - result += '0' + # The following is useless (never happens) + # for i in range(len(result), self.partPrecision): + # result += '0' return result def digit_feminine_status(self, digit, group_level): @@ -158,6 +158,7 @@ def digit_feminine_status(self, digit, group_level): if self.isCurrencyPartNameFeminine: return self.arabicFeminineOnes[int(digit)] else: + # Note: this never happens return self.arabicOnes[int(digit)] elif group_level == 0: if self.isCurrencyNameFeminine: @@ -183,9 +184,10 @@ def process_arabic_group(self, group_number, group_level, if tens > 0: if tens < 20: - if int(group_level) >= len(self.arabicTwos): - raise OverflowError(self.errmsg_toobig % - (self.number, self.MAXVAL)) + # if int(group_level) >= len(self.arabicTwos): + # raise OverflowError(self.errmsg_toobig % + # (self.number, self.MAXVAL)) + assert int(group_level) < len(self.arabicTwos) if tens == 2 and int(hundreds) == 0 and group_level > 0: pow = int(math.log10(self.integer_value)) if self.integer_value > 10 and pow % 3 == 0 and \ @@ -198,10 +200,13 @@ def process_arabic_group(self, group_number, group_level, else: if tens == 1 and group_level > 0 and hundreds == 0: + # Note: this never happens + # (hundreds == 0 only if group_number is 0) ret_val += "" elif (tens == 1 or tens == 2) and ( group_level == 0 or group_level == -1) and \ hundreds == 0 and remaining_number == 0: + # Note: this never happens (idem) ret_val += "" elif tens == 1 and group_level > 0: ret_val += self.arabicGroup[int(group_level)] @@ -230,8 +235,10 @@ def abs(self, number): # precision for big numbers def to_str(self, number): integer = int(number) + if integer == number: + return str(integer) decimal = round((number - integer) * 10**9) - return str(integer) + "." + "{:09d}".format(decimal) + return str(integer) + "." + "{:09d}".format(decimal).rstrip("0") def convert(self, value): self.number = self.to_str(value) @@ -272,10 +279,11 @@ def convert_to_arabic(self): if ret_val != "": ret_val = "{}و {}".format("", ret_val) if number_to_process != 2 and number_to_process != 1: - if group >= len(self.arabicGroup): - raise OverflowError( - self.errmsg_toobig % (self.number, self.MAXVAL) - ) + # if group >= len(self.arabicGroup): + # raise OverflowError(self.errmsg_toobig % + # (self.number, self.MAXVAL) + # ) + assert group < len(self.arabicGroup) if number_to_process % 100 != 1: if 3 <= number_to_process <= 10: ret_val = "{} {}".format( diff --git a/tests/test_ar.py b/tests/test_ar.py index 0d281700..91e648a6 100644 --- a/tests/test_ar.py +++ b/tests/test_ar.py @@ -92,9 +92,31 @@ def test_ordinal(self): self.assertEqual(num2words(23, lang="ar"), 'ثلاثة و عشرون') def test_cardinal(self): + self.assertEqual(num2words(0, to='cardinal', lang='ar'), 'صفر') self.assertEqual(num2words(12, to='cardinal', lang='ar'), 'اثنا عشر') + self.assertEqual(num2words(12.3, to='cardinal', lang='ar'), + 'اثنا عشر , ثلاثون') + self.assertEqual(num2words(12.01, to='cardinal', lang='ar'), + 'اثنا عشر , إحدى') + self.assertEqual(num2words(12.02, to='cardinal', lang='ar'), + 'اثنا عشر , اثنتان') + self.assertEqual(num2words(12.03, to='cardinal', lang='ar'), + 'اثنا عشر , ثلاث') + self.assertEqual(num2words(12.34, to='cardinal', lang='ar'), + 'اثنا عشر , أربع و ثلاثون') + # Not implemented + self.assertEqual(num2words(12.345, to='cardinal', lang='ar'), + num2words(12.34, to='cardinal', lang='ar')) self.assertEqual(num2words(-8324, to='cardinal', lang='ar'), 'سالب ثمانية آلاف و ثلاثمائة و أربعة و عشرون') + + self.assertEqual(num2words(200, to='cardinal', lang='ar'), + 'مئتا') + self.assertEqual(num2words(700, to='cardinal', lang='ar'), + 'سبعمائة') + self.assertEqual(num2words(101010, to='cardinal', lang='ar'), + 'مائة و ألف ألف و عشرة') + self.assertEqual( num2words(3431.12, to='cardinal', lang='ar'), 'ثلاثة آلاف و أربعمائة و واحد و ثلاثون , اثنتا عشرة') From 30a8810050c6ed85082c07850ec7b08eb93a04d6 Mon Sep 17 00:00:00 2001 From: KhramtsovDR Date: Tue, 18 Apr 2023 13:01:26 +0300 Subject: [PATCH 32/37] Fixes for PEP8 compatibility --- num2words/lang_RU.py | 38 ++++++++++++++++++++++---------------- tests/test_ru.py | 16 ++++++++++------ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/num2words/lang_RU.py b/num2words/lang_RU.py index 4a3bbf90..aa052aed 100644 --- a/num2words/lang_RU.py +++ b/num2words/lang_RU.py @@ -131,6 +131,7 @@ def get_ord_classifier(prefixes, post_groups): for num, prefix in prefixes.items() } + ONES_ORD = { 3: [['третий', 'третья', 'третье', 'третьи'], ['третьего', 'третьей', 'третьего', 'третьих'], @@ -143,8 +144,8 @@ def get_ord_classifier(prefixes, post_groups): get_ord_classifier(ONES_ORD_PREFIXES, ONES_ORD_POSTFIXES_GROUPS) ) -TENS_PREFIXES = {1: 'один', 2: 'две', 3: 'три', 4: 'четыр', 5: 'пят', 6: 'шест', - 7: 'сем', 8: 'восем', 9: 'девят'} +TENS_PREFIXES = {1: 'один', 2: 'две', 3: 'три', 4: 'четыр', 5: 'пят', + 6: 'шест', 7: 'сем', 8: 'восем', 9: 'девят'} TENS_POSTFIXES = ['надцать', 'надцати', 'надцати', 'надцать', 'надцатью', 'надцати'] TENS = {0: ['десять', 'десяти', 'десяти', 'десять', 'десятью', 'десяти']} @@ -178,9 +179,10 @@ def get_ord_classifier(prefixes, post_groups): } TWENTIES_ORD_PREFIXES = {2: 'двадцат', 3: 'тридцат', 4: 'сороков', - 5: 'пятидесят',6: 'шестидесят', 7: 'семидесят', + 5: 'пятидесят', 6: 'шестидесят', 7: 'семидесят', 8: 'восьмидесят', 9: 'девяност'} -TWENTIES_ORD_POSTFIXES_GROUPS = {2: 1, 3: 1, 4: 0, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1} +TWENTIES_ORD_POSTFIXES_GROUPS = {2: 1, 3: 1, 4: 0, 5: 1, 6: 1, 7: 1, 8: 1, + 9: 1} TWENTIES_ORD = get_ord_classifier(TWENTIES_ORD_PREFIXES, TWENTIES_ORD_POSTFIXES_GROUPS) @@ -272,12 +274,13 @@ def setup(self): self.pointword = ('целая', 'целых', 'целых') self.pointword_ord = get_cases("цел", 1) - def to_cardinal(self, number, case=D_CASE, plural=D_PLURAL, gender=D_GENDER, - animate=D_ANIMATE): + def to_cardinal(self, number, case=D_CASE, plural=D_PLURAL, + gender=D_GENDER, animate=D_ANIMATE): n = str(number).replace(',', '.') if '.' in n: left, right = n.split('.') - decimal_part = self._int2word(int(right), cardinal=True, gender='f') + decimal_part = self._int2word(int(right), cardinal=True, + gender='f') return u'%s %s %s %s' % ( self._int2word(int(left), cardinal=True, gender='f'), self.pluralize(int(left), self.pointword), @@ -286,13 +289,14 @@ def to_cardinal(self, number, case=D_CASE, plural=D_PLURAL, gender=D_GENDER, ) else: return self._int2word(int(n), cardinal=True, case=case, - plural=plural, gender=gender, animate=animate) + plural=plural, gender=gender, + animate=animate) def __decimal_bitness(self, n): - l = len(n) if n[-1] == "1" and n[-2:] != "11": - return self._int2word(10 ** l, cardinal=False, gender='f') - return self._int2word(10 ** l, cardinal=False, case='g', plural='t') + return self._int2word(10 ** len(n), cardinal=False, gender='f') + return self._int2word(10 ** len(n), cardinal=False, case='g', + plural=True) def pluralize(self, n, forms): if n % 100 in (11, 12, 13, 14): @@ -325,19 +329,21 @@ def _int2word(self, n, feminine=False, cardinal=True, case=D_CASE, """ n: number feminine: not used - for backward compatibility - gender: 'f' - masculine - 'm' - feminine - 'n' - neuter + cardinal:True - cardinal + False - ordinal case: 'n' - nominative 'g' - genitive 'd' - dative 'a' - accusative 'i' - instrumental 'p' - prepositional + plural: True - plural + False - singular + gender: 'f' - masculine + 'm' - feminine + 'n' - neuter animate: True - animate False - inanimate - ordinal: True - ordinal - False - cardinal """ # For backward compatibility if feminine: diff --git a/tests/test_ru.py b/tests/test_ru.py index 6cf85b3e..206873c4 100644 --- a/tests/test_ru.py +++ b/tests/test_ru.py @@ -35,7 +35,8 @@ def test_cardinal(self): self.assertEqual(num2words(2012, lang='ru'), "две тысячи двенадцать") self.assertEqual( num2words(12519.85, lang='ru'), - "двенадцать тысяч пятьсот девятнадцать целых восемьдесят пять сотых" + "двенадцать тысяч пятьсот девятнадцать целых восемьдесят пять " + "сотых" ) self.assertEqual( num2words(1234567890, lang='ru'), @@ -105,7 +106,7 @@ def test_cardinal_neuter(self): num2words(115, lang='ru', gender='n'), "сто пятнадцать" ) self.assertEqual( - num2words(122, lang='ru', gender='n'),"сто двадцать два" + num2words(122, lang='ru', gender='n'), "сто двадцать два" ) self.assertEqual( num2words(125.1, lang='ru', gender='n'), @@ -116,18 +117,21 @@ def test_cardinal_neuter(self): def test_floating_point(self): self.assertEqual(num2words(5.2, lang='ru'), "пять целых две десятых") self.assertEqual(num2words(5.0, lang='ru'), "пять целых ноль десятых") + self.assertEqual(num2words(5.10, lang='ru'), "пять целых одна десятая") + self.assertEqual(num2words("5.10", lang='ru'), + "пять целых десять сотых") self.assertEqual(num2words(1.001, lang='ru'), "одна целая одна тысячная") self.assertEqual(num2words(1.011, lang='ru'), "одна целая одиннадцать тысячных") self.assertEqual(num2words(10.02, lang='ru'), - "десять целых две сотых") + "десять целых две сотых") self.assertEqual(num2words(15.007, lang='ru'), - "пятнадцать целых семь тысячных") + "пятнадцать целых семь тысячных") self.assertEqual(num2words(561.42, lang='ru'), - "пятьсот шестьдесят одна целая сорок две сотых") + "пятьсот шестьдесят одна целая сорок две сотых") self.assertEqual(num2words(561.00001, lang='ru'), - "пятьсот шестьдесят одна целая одна стотысячная") + "пятьсот шестьдесят одна целая одна стотысячная") def test_to_ordinal(self): self.assertEqual( From 87def57c4a747ee50009f79ad9fd6ca4a40fa138 Mon Sep 17 00:00:00 2001 From: Arthur O'Dwyer Date: Mon, 1 May 2023 16:36:42 -0400 Subject: [PATCH 33/37] [it] Handle string inputs in `to_ordinal` Fixes #508. --- num2words/lang_IT.py | 12 ++++++------ tests/test_it.py | 25 ++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/num2words/lang_IT.py b/num2words/lang_IT.py index d532f7f4..427e6d36 100644 --- a/num2words/lang_IT.py +++ b/num2words/lang_IT.py @@ -146,13 +146,13 @@ def to_cardinal(self, number): elif isinstance(number, float): string = self.float_to_words(number) elif number < 20: - string = CARDINAL_WORDS[number] + string = CARDINAL_WORDS[int(number)] elif number < 100: - string = self.tens_to_cardinal(number) + string = self.tens_to_cardinal(int(number)) elif number < 1000: - string = self.hundreds_to_cardinal(number) + string = self.hundreds_to_cardinal(int(number)) elif number < 1000000: - string = self.thousands_to_cardinal(number) + string = self.thousands_to_cardinal(int(number)) else: string = self.big_number_to_cardinal(number) return accentuate(string) @@ -167,9 +167,9 @@ def to_ordinal(self, number): elif number % 1 != 0: return self.float_to_words(number, ordinal=True) elif number < 20: - return ORDINAL_WORDS[number] + return ORDINAL_WORDS[int(number)] elif is_outside_teens and tens % 10 == 3: - # Gets ride of the accent ~~~~~~~~~~ + # Gets rid of the accent return self.to_cardinal(number)[:-1] + "eesimo" elif is_outside_teens and tens % 10 == 6: return self.to_cardinal(number) + "esimo" diff --git a/tests/test_it.py b/tests/test_it.py index 9023355f..23c081d1 100644 --- a/tests/test_it.py +++ b/tests/test_it.py @@ -263,7 +263,7 @@ def test_nth_big(self): "cinquecentosessantasettemilaottocentonovantesimo" ) - def test_with_decimals(self): + def test_with_floats(self): self.assertAlmostEqual( num2words(1.0, lang="it"), "uno virgola zero" ) @@ -271,6 +271,29 @@ def test_with_decimals(self): num2words(1.1, lang="it"), "uno virgola uno" ) + def test_with_strings(self): + for i in range(2002): + # Just make sure it doesn't raise an exception + num2words(str(i), lang='it', to='cardinal') + num2words(str(i), lang='it', to='ordinal') + self.assertEqual(num2words('1', lang="it", to='ordinal'), "primo") + self.assertEqual( + num2words('100', lang="it", to='ordinal'), + "centesimo" + ) + self.assertEqual( + num2words('1000', lang="it", to='ordinal'), + "millesimo" + ) + self.assertEqual( + num2words('1234567890123456789012345678', lang="it", to='ordinal'), + "un quadriliardo, duecentotrentaquattro quadrilioni, " + "cinquecentosessantasette triliardi, ottocentonovanta trilioni, " + "centoventitré biliardi, quattrocentocinquantasei bilioni, " + "settecentottantanove miliardi, dodici milioni e " + "trecentoquarantacinquemilaseicentosettantottesimo" + ) + def test_currency_eur(self): for test in TEST_CASES_TO_CURRENCY_EUR: self.assertEqual( From 5635a41c7111393a3a867076b4225cb436c80e2c Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Fri, 28 Jul 2023 10:59:40 +0600 Subject: [PATCH 34/37] Add gender and morphological cases support for Ukrainian Added two parameters: gender and case gender can accept either 'masculine' (default) or 'feminine' case can accept either 'nominative' (default) or 'genitive','dative','accusative','instrumetnal','locative' and 'vocative'. This parameters now working only for to='cardinal' --- num2words/lang_UK.py | 155 ++++++----- tests/test_uk.py | 609 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 698 insertions(+), 66 deletions(-) diff --git a/num2words/lang_UK.py b/num2words/lang_UK.py index a6c0a3bb..0b62d858 100644 --- a/num2words/lang_UK.py +++ b/num2words/lang_UK.py @@ -23,27 +23,27 @@ ZERO = ('нуль',) ONES_FEMININE = { - 1: ('одна',), - 2: ('дві',), - 3: ('три',), - 4: ('чотири',), - 5: ('п\'ять',), - 6: ('шість',), - 7: ('сім',), - 8: ('вісім',), - 9: ('дев\'ять',), + 1: ('одна', "однієї", "одній", "одну", "однією", "одній"), + 2: ('дві', "двох", "двом", "дві", "двома", "двох"), + 3: ('три', "трьох", "трьом", "три", "трьома", "трьох"), + 4: ('чотири', "чотирьох", "чотирьом", "чотири", "чотирма", "чотирьох"), + 5: ('п\'ять', "п'яти", "п'яти", "п'ять", "п'ятьма", "п'яти"), + 6: ('шість', "шести", "шести", "шість", "шістьма", "шести"), + 7: ('сім', "семи", "семи", "сім", "сьома", "семи"), + 8: ('вісім', "восьми", "восьми", "вісім", "вісьма", "восьми"), + 9: ("дев'ять", "дев'яти", "дев'яти", "дев'ять", "дев'ятьма","дев'яти"), } ONES = { - 1: ('один',), - 2: ('два',), - 3: ('три',), - 4: ('чотири',), - 5: ('п\'ять',), - 6: ('шість',), - 7: ('сім',), - 8: ('вісім',), - 9: ('дев\'ять',), + 1: ('один', 'одного', "одному", "один", "одним", "одному"), + 2: ('два', 'двох', "двом", "два", "двома", "двох"), + 3: ('три', 'трьох', "трьом", "три", "трьома", "трьох"), + 4: ('чотири', 'чотирьох', "чотирьом", "чотири", "чотирма", "чотирьох"), + 5: ('п\'ять', "п'яти", "п'яти", "п'ять", "п'ятьма", "п'яти"), + 6: ('шість', 'шести', "шести", "шість", "шістьма", "шести"), + 7: ('сім', 'семи', "семи", "сім", "сьома", "семи"), + 8: ('вісім', 'восьми', "восьми", "вісім", "вісьма", "восьми"), + 9: ('дев\'ять', "дев'яти", "дев'яти", "дев'ять", "дев'ятьма","дев'яти"), } ONES_ORDINALS = { @@ -69,27 +69,27 @@ } TENS = { - 0: ('десять',), - 1: ('одинадцять',), - 2: ('дванадцять',), - 3: ('тринадцять',), - 4: ('чотирнадцять',), - 5: ('п\'ятнадцять',), - 6: ('шістнадцять',), - 7: ('сімнадцять',), - 8: ('вісімнадцять',), - 9: ('дев\'ятнадцять',), + 0: ('десять', 'десяти', "десяти", "десять", "десятьма", "десяти"), + 1: ('одинадцять', 'одинадцяти', "одинадцяти", "одинадцять", "одинадцятьма", "одинадцяти"), + 2: ('дванадцять', 'дванадцяти', "дванадцяти", "дванадцять", "дванадцятьма", "дванадцяти"), + 3: ('тринадцять', 'тринадцяти', "тринадцяти", "тринадцять", "тринадцятьма", "тринадцяти"), + 4: ('чотирнадцять', 'чотирнадцяти', "чотирнадцяти", "чотирнадцять", "чотирнадцятьма", "чотирнадцяти"), + 5: ("п'ятнадцять", "п'ятнадцяти", "п'ятнадцяти", "п'ятнадцять", "п'ятнадцятьма", "п'ятнадцяти"), + 6: ('шістнадцять', 'шістнадцяти', "шістнадцяти", "шістнадцять", "шістнадцятьма", "шістнадцяти"), + 7: ('сімнадцять', 'сімнадцяти', "сімнадцяти", "сімнадцять", "сімнадцятьма", "сімнадцяти"), + 8: ('вісімнадцять', 'вісімнадцяти', "вісімнадцяти", "вісімнадцять", "вісімнадцятьма", "вісімнадцяти"), + 9: ("дев'ятнадцять","дев'ятнадцяти","дев'ятнадцяти","дев'ятнадцять","дев'ятнадцятьма", "дев'ятнадцяти"), } TWENTIES = { - 2: ('двадцять',), - 3: ('тридцять',), - 4: ('сорок',), - 5: ('п\'ятдесят',), - 6: ('шістдесят',), - 7: ('сімдесят',), - 8: ('вісімдесят',), - 9: ('дев\'яносто',), + 2: ('двадцять', "двадцяти", "двадцяти", "двадцять", "двадцятьма", "двадцяти"), + 3: ('тридцять', "тридцяти", "тридцяти", "тридцять", "тридцятьма", "тридцяти"), + 4: ('сорок', "сорока", "сорока", "сорок", "сорока", "сорока"), + 5: ('п\'ятдесят', "п'ятдесяти", "п'ятдесяти", "п'ятдесят", "п'ятдесятьма", "п'ятдесяти"), + 6: ('шістдесят', "шістдесяти", "шістдесяти", "шістдесят", "шістдесятьма", "шістдесяти"), + 7: ('сімдесят', "сімдесяти", "сімдесяти", "сімдесят", "сімдесятьма", "сімдесяти"), + 8: ('вісімдесят', "вісімдесяти", "вісімдесяти", "вісімдесят", "вісімдесятьма","вісімдесяти"), + 9: ('дев\'яносто', "дев'яноста", "дев'яноста", "дев'яносто", "дев'яностами", "дев'яноста"), } TWENTIES_ORDINALS = { @@ -104,15 +104,15 @@ } HUNDREDS = { - 1: ('сто',), - 2: ('двісті',), - 3: ('триста',), - 4: ('чотириста',), - 5: ('п\'ятсот',), - 6: ('шістсот',), - 7: ('сімсот',), - 8: ('вісімсот',), - 9: ('дев\'ятсот',), + 1: ('сто', "ста", "ста", "сто", "стами", "стах"), + 2: ('двісті', "двохста", "двомстам", "двісті", "двомастами", "двохстах"), + 3: ('триста', "трьохста", "трьомстам", "триста", "трьомастами", "трьохстах"), + 4: ('чотириста',"чотирьохста", "чотирьомстам", "чотириста","чотирмастами", "чотирьохстах"), + 5: ('п\'ятсот', "п'ятиста", "п'ятистам", "п'ятсот", "п'ятьмастами", "п'ятистах"), + 6: ('шістсот', "шестиста", "шестистам", "шістсот", "шістьмастами", "шестистах"), + 7: ('сімсот', "семиста", "семистам", "сімсот", "сьомастами", "семистах"), + 8: ('вісімсот', "восьмиста", "восьмистам", "вісімсот", "восьмастами", "восьмистах"), + 9: ("дев'ятсот","дев'ятиста", "дев'ятистам", "дев'ятсот","дев'ятьмастами","дев'ятистах"), } HUNDREDS_ORDINALS = { @@ -128,16 +128,28 @@ } THOUSANDS = { - 1: ('тисяча', 'тисячі', 'тисяч'), # 10^3 - 2: ('мільйон', 'мільйони', 'мільйонів'), # 10^6 - 3: ('мільярд', 'мільярди', 'мільярдів'), # 10^9 - 4: ('трильйон', 'трильйони', 'трильйонів'), # 10^12 - 5: ('квадрильйон', 'квадрильйони', 'квадрильйонів'), # 10^15 - 6: ('квінтильйон', 'квінтильйони', 'квінтильйонів'), # 10^18 - 7: ('секстильйон', 'секстильйони', 'секстильйонів'), # 10^21 - 8: ('септильйон', 'септильйони', 'септильйонів'), # 10^24 - 9: ('октильйон', 'октильйони', 'октильйонів'), # 10^27 - 10: ('нонільйон', 'нонільйони', 'нонільйонів'), # 10^30 + # Nominative Genitive Dative Accusative Instrumental Locative + # ----------------------------------------------------- --------------------------------------------------- --------------------------------------------------- --------------------------------------------------- ------------------------------------------------------- -------------------------------------------------------- + # 10^3 + 1: (('тисяча', 'тисячі', 'тисяч'), ('тисячи', 'тисяч', 'тисяч'), ('тисячі', 'тисячам', 'тисячам'), ('тисячу', 'тисячі', 'тисяч'), ('тисячею', 'тисячами', 'тисячами'), ('тисячі', 'тисячах', 'тисячах'),), + # 10^6 + 2: (('мільйон', 'мільйони', 'мільйонів'), ('мільйона', 'мільйонів', 'мільйонів'), ('мільйону', 'мільйонам', 'мільйонам'), ('мільйон', 'мільйони', 'мільйонів'), ('мільйоном', 'мільйонами', 'мільйонів'), ('мільйоні', 'мільйонах', 'мільйонах'),), + # 10^9 + 3: (('мільярд', 'мільярди', 'мільярдів'), ('мільярда', 'мільярдів', 'мільярдів'), ('мільярду', 'мільярдам', 'мільярдам'), ('мільярд', 'мільярди', 'мільярдів'), ('мільярдом', 'мільярдами', 'мільярдів'), ('мільярді', 'мільярдах', 'мільярдах'),), + # 10^12 + 4: (('трильйон', 'трильйони', 'трильйонів'), ('трильйона', 'трильйонів', 'трильйонів'), ('трильйону', 'трильйонам', 'трильйонам'), ('трильйон', 'трильйони', 'трильйонів'), ('трильйоном', 'трильйонами', 'трильйонів'), ('трильйоні', 'трильйонах', 'трильйонах'),), + # 10^15 + 5: (('квадрильйон', 'квадрильйони', 'квадрильйонів'), ('квадрильйона', 'квадрильйонів', 'квадрильйонів'), ('квадрильйону', 'квадрильйонам', 'квадрильйонам'), ('квадрильйон', 'квадрильйони', 'квадрильйонів'), ('квадрильйоном', 'квадрильйонами', 'квадрильйонів'), ('квадрильйоні', 'квадрильйонах', 'квадрильйонах'),), + # 10^18 + 6: (('квінтильйон', 'квінтильйони', 'квінтильйонів'), ('квінтильйона', 'квінтильйонів', 'квінтильйонів'), ('квінтильйону', 'квінтильйонам', 'квінтильйонам'), ('квінтильйон', 'квінтильйони', 'квінтильйонів'), ('квінтильйоном', 'квінтильйонами', 'квінтильйонів'), ('квінтильйоні', 'квінтильйонах', 'квінтильйонах'),), + # 10^21 + 7: (('секстильйон', 'секстильйони', 'секстильйонів'), ('секстильйона', 'секстильйонів', 'секстильйонів'), ('секстильйону', 'секстильйонам', 'секстильйонам'), ('секстильйон', 'секстильйони', 'секстильйонів'), ('секстильйоном', 'секстильйонами', 'секстильйонів'), ('секстильйоні', 'секстильйонах', 'секстильйонах'),), + # 10^24 + 8: (('септильйон', 'септильйони', 'септильйонів'), ('септильйона', 'септильйонів', 'септильйонів'), ('септильйону', 'септильйонам', 'септильйонам'), ('септильйон', 'септильйони', 'септильйонів'), ('септильйоном', 'септильйонами', 'септильйонів'), ('септильйоні', 'септильйонах', 'септильйонах'),), + # 10^27 + 9: (('октильйон', 'октильйони', 'октильйонів'), ('октильйона', 'октильйонів', 'октильйонів'), ('октильйону', 'октильйонам', 'октильйонам'), ('октильйон', 'октильйони', 'октильйонів'), ('октильйоном', 'октильйонами', 'октильйонів'), ('октильйоні', 'октильйонах', 'октильйонах'),), + # 10^30 + 10: (('нонільйон', 'нонільйони', 'нонільйонів'), ('нонільйона', 'нонільйонів', 'нонільйонів'), ('нонільйону', 'нонільйонам', 'нонільйонам'), ('нонільйон', 'нонільйони', 'нонільйонів'), ('нонільйоном', 'нонільйонами', 'нонільйонів'), ('нонільйоні', 'нонільйонах', 'нонільйонах'),), } prefixes_ordinal = { @@ -711,20 +723,31 @@ def setup(self): self.negword = "мінус" self.pointword = "кома" - def to_cardinal(self, number): + def to_cardinal(self, number, **kwargs): + if 'case' in kwargs: + case = kwargs['case'] + morphological_case = ["nominative", "genitive", "dative", "accusative", "instrumental", "locative"].index(case) + else: + morphological_case = 0 + + if 'gender' in kwargs: + gender = kwargs['gender'] == 'feminine' + else: + gender = False + n = str(number).replace(',', '.') if '.' in n: left, right = n.split('.') leading_zero_count = len(right) - len(right.lstrip('0')) decimal_part = ((ZERO[0] + ' ') * leading_zero_count + - self._int2word(int(right))) + self._int2word(int(right), gender, morphological_case)) return u'%s %s %s' % ( - self._int2word(int(left)), + self._int2word(int(left), gender, morphological_case), self.pointword, decimal_part ) else: - return self._int2word(int(n)) + return self._int2word(int(n), gender, morphological_case) def pluralize(self, n, forms): if n % 100 < 10 or n % 100 > 20: @@ -739,9 +762,9 @@ def pluralize(self, n, forms): return forms[form] - def _int2word(self, n, feminine=False): + def _int2word(self, n, feminine=False, morphological_case = 0): if n < 0: - return ' '.join([self.negword, self._int2word(abs(n))]) + return ' '.join([self.negword, self._int2word(abs(n), feminine, morphological_case)]) if n == 0: return ZERO[0] @@ -758,20 +781,20 @@ def _int2word(self, n, feminine=False): n1, n2, n3 = get_digits(x) if n3 > 0: - words.append(HUNDREDS[n3][0]) + words.append(HUNDREDS[n3][morphological_case]) if n2 > 1: - words.append(TWENTIES[n2][0]) + words.append(TWENTIES[n2][morphological_case]) if n2 == 1: - words.append(TENS[n1][0]) + words.append(TENS[n1][morphological_case]) # elif n1 > 0 and not (i > 0 and x == 1): elif n1 > 0: ones = ONES_FEMININE if i == 1 or feminine and i == 0 else ONES - words.append(ones[n1][0]) + words.append(ones[n1][morphological_case]) if i > 0: - words.append(self.pluralize(x, THOUSANDS[i])) + words.append(self.pluralize(x, THOUSANDS[i][morphological_case])) return ' '.join(words) diff --git a/tests/test_uk.py b/tests/test_uk.py index 84fb770a..3f57eb2d 100644 --- a/tests/test_uk.py +++ b/tests/test_uk.py @@ -117,6 +117,582 @@ "двісті двадцять тисяч двісті дев'яносто один") ) +TEST_CASES_CARDINAL_FEMININE = ( + (1, "одна"), + (2, "дві"), + (3, "три"), + (4, "чотири"), + (5, "п'ять"), + (6, "шість"), + (7, "сім"), + (8, "вісім"), + (9, "дев'ять"), + (10, "десять"), + (10.02, "десять кома нуль дві"), + (11, "одинадцять"), + (12, "дванадцять"), + (12.40, "дванадцять кома чотири"), + (13, "тринадцять"), + (14, "чотирнадцять"), + (14.13, "чотирнадцять кома тринадцять"), + (15, "п'ятнадцять"), + (16, "шістнадцять"), + (17, "сімнадцять"), + (17.31, "сімнадцять кома тридцять одна"), + (18, "вісімнадцять"), + (19, "дев'ятнадцять"), + (20, "двадцять"), + (21, "двадцять одна"), + (21.20, "двадцять одна кома дві"), + (30, "тридцять"), + (32, "тридцять дві"), + (40, "сорок"), + (43, "сорок три"), + (43.007, "сорок три кома нуль нуль сім"), + (50, "п'ятдесят"), + (54, "п'ятдесят чотири"), + (60, "шістдесят"), + (60.059, "шістдесят кома нуль п'ятдесят дев'ять"), + (65, "шістдесят п'ять"), + (70, "сімдесят"), + (76, "сімдесят шість"), + (80, "вісімдесят"), + (87, "вісімдесят сім"), + (90, "дев'яносто"), + (98, "дев'яносто вісім"), + (99, "дев'яносто дев'ять"), + (100, "сто"), + (101, "сто одна"), + (199, "сто дев'яносто дев'ять"), + (200, "двісті"), + (203, "двісті три"), + (300, "триста"), + (356, "триста п'ятдесят шість"), + (400, "чотириста"), + (434, "чотириста тридцять чотири"), + (500, "п'ятсот"), + (578, "п'ятсот сімдесят вісім"), + (600, "шістсот"), + (689, "шістсот вісімдесят дев'ять"), + (700, "сімсот"), + (729, "сімсот двадцять дев'ять"), + (800, "вісімсот"), + (894, "вісімсот дев'яносто чотири"), + (900, "дев'ятсот"), + (999, "дев'ятсот дев'яносто дев'ять"), + (1000, "одна тисяча"), + (1001, "одна тисяча одна"), + (2012, "дві тисячі дванадцять"), + (12519, "дванадцять тисяч п'ятсот дев'ятнадцять"), + (12519.85, "дванадцять тисяч п'ятсот дев'ятнадцять кома вісімдесят п'ять"), + (-260000, "мінус двісті шістдесят тисяч"), + (1000000, "один мільйон"), + (1000000000, "один мільярд"), + (1234567890, "один мільярд двісті тридцять чотири мільйони " + "п'ятсот шістдесят сім тисяч вісімсот дев'яносто"), + (1000000000000, "один трильйон"), + (1000000000000000, "один квадрильйон"), + (1000000000000000000, "один квінтильйон"), + (1000000000000000000000, "один секстильйон"), + (1000000000000000000000000, "один септильйон"), + (1000000000000000000000000000, "один октильйон"), + (1000000000000000000000000000000, "один нонільйон"), + (215461407892039002157189883901676, + "двісті п'ятнадцять нонільйонів чотириста шістдесят один " + "октильйон чотириста сім септильйонів вісімсот дев'яносто " + "два секстильйони тридцять дев'ять квінтильйонів два " + "квадрильйони сто п'ятдесят сім трильйонів сто вісімдесят " + "дев'ять мільярдів вісімсот вісімдесят три мільйони " + "дев'ятсот одна тисяча шістсот сімдесят шість"), + (719094234693663034822824384220291, + "сімсот дев'ятнадцять нонільйонів дев'яносто чотири октильйони " + "двісті тридцять чотири септильйони шістсот дев'яносто три " + "секстильйони шістсот шістдесят три квінтильйони тридцять " + "чотири квадрильйони вісімсот двадцять два трильйони вісімсот " + "двадцять чотири мільярди триста вісімдесят чотири мільйони " + "двісті двадцять тисяч двісті дев'яносто одна") +) + +TEST_CASES_CARDINAL_GENITIVE = ( + (1, "одного"), + (2, "двох"), + (3, "трьох"), + (4, "чотирьох"), + (5, "п'яти"), + (6, "шести"), + (7, "семи"), + (8, "восьми"), + (9, "дев'яти"), + (10, "десяти"), + (10.02, "десяти кома нуль двох"), + (11, "одинадцяти"), + (12, "дванадцяти"), + (12.40, "дванадцяти кома чотирьох"), + (13, "тринадцяти"), + (14, "чотирнадцяти"), + (14.13, "чотирнадцяти кома тринадцяти"), + (15, "п'ятнадцяти"), + (16, "шістнадцяти"), + (17, "сімнадцяти"), + (17.31, "сімнадцяти кома тридцяти одного"), + (18, "вісімнадцяти"), + (19, "дев'ятнадцяти"), + (20, "двадцяти"), + (21, "двадцяти одного"), + (21.20, "двадцяти одного кома двох"), + (30, "тридцяти"), + (32, "тридцяти двох"), + (40, "сорока"), + (43, "сорока трьох"), + (43.007, "сорока трьох кома нуль нуль семи"), + (50, "п'ятдесяти"), + (54, "п'ятдесяти чотирьох"), + (60, "шістдесяти"), + (60.059, "шістдесяти кома нуль п'ятдесяти дев'яти"), + (65, "шістдесяти п'яти"), + (70, "сімдесяти"), + (76, "сімдесяти шести"), + (80, "вісімдесяти"), + (87, "вісімдесяти семи"), + (90, "дев'яноста"), + (98, "дев'яноста восьми"), + (99, "дев'яноста дев'яти"), + (100, "ста"), + (101, "ста одного"), + (199, "ста дев'яноста дев'яти"), + (200, "двохста"), + (203, "двохста трьох"), + (300, "трьохста"), + (356, "трьохста п'ятдесяти шести"), + (400, "чотирьохста"), + (434, "чотирьохста тридцяти чотирьох"), + (500, "п'ятиста"), + (578, "п'ятиста сімдесяти восьми"), + (600, "шестиста"), + (689, "шестиста вісімдесяти дев'яти"), + (700, "семиста"), + (729, "семиста двадцяти дев'яти"), + (800, "восьмиста"), + (894, "восьмиста дев'яноста чотирьох"), + (900, "дев'ятиста"), + (999, "дев'ятиста дев'яноста дев'яти"), + (1000, "однієї тисячи"), + (1001, "однієї тисячи одного"), + (2012, "двох тисяч дванадцяти"), + (12519, "дванадцяти тисяч п'ятиста дев'ятнадцяти"), + (12519.85, "дванадцяти тисяч п'ятиста дев'ятнадцяти кома вісімдесяти п'яти"), + (-260000, "мінус двохста шістдесяти тисяч"), + (1000000, "одного мільйона"), + (1000000000, "одного мільярда"), + (1234567890, "одного мільярда двохста тридцяти чотирьох мільйонів " + "п'ятиста шістдесяти семи тисяч восьмиста дев'яноста"), + (1000000000000, "одного трильйона"), + (1000000000000000, "одного квадрильйона"), + (1000000000000000000, "одного квінтильйона"), + (1000000000000000000000, "одного секстильйона"), + (1000000000000000000000000, "одного септильйона"), + (1000000000000000000000000000, "одного октильйона"), + (1000000000000000000000000000000, "одного нонільйона"), + (215461407892039002157189883901676, + "двохста п'ятнадцяти нонільйонів чотирьохста шістдесяти одного " + "октильйона чотирьохста семи септильйонів восьмиста дев'яноста " + "двох секстильйонів тридцяти дев'яти квінтильйонів двох " + "квадрильйонів ста п'ятдесяти семи трильйонів ста вісімдесяти " + "дев'яти мільярдів восьмиста вісімдесяти трьох мільйонів " + "дев'ятиста однієї тисячи шестиста сімдесяти шести"), + (719094234693663034822824384220291, + "семиста дев'ятнадцяти нонільйонів дев'яноста чотирьох октильйонів " + "двохста тридцяти чотирьох септильйонів шестиста дев'яноста трьох " + "секстильйонів шестиста шістдесяти трьох квінтильйонів тридцяти " + "чотирьох квадрильйонів восьмиста двадцяти двох трильйонів восьмиста " + "двадцяти чотирьох мільярдів трьохста вісімдесяти чотирьох мільйонів " + "двохста двадцяти тисяч двохста дев'яноста одного") +) + +TEST_CASES_CARDINAL_DATIVE = ( + (1, "одному"), + (2, "двом"), + (3, "трьом"), + (4, "чотирьом"), + (5, "п'яти"), + (6, "шести"), + (7, "семи"), + (8, "восьми"), + (9, "дев'яти"), + (10, "десяти"), + (10.02, "десяти кома нуль двом"), + (11, "одинадцяти"), + (12, "дванадцяти"), + (12.40, "дванадцяти кома чотирьом"), + (13, "тринадцяти"), + (14, "чотирнадцяти"), + (14.13, "чотирнадцяти кома тринадцяти"), + (15, "п'ятнадцяти"), + (16, "шістнадцяти"), + (17, "сімнадцяти"), + (17.31, "сімнадцяти кома тридцяти одному"), + (18, "вісімнадцяти"), + (19, "дев'ятнадцяти"), + (20, "двадцяти"), + (21, "двадцяти одному"), + (21.20, "двадцяти одному кома двом"), + (30, "тридцяти"), + (32, "тридцяти двом"), + (40, "сорока"), + (43, "сорока трьом"), + (43.007, "сорока трьом кома нуль нуль семи"), + (50, "п'ятдесяти"), + (54, "п'ятдесяти чотирьом"), + (60, "шістдесяти"), + (60.059, "шістдесяти кома нуль п'ятдесяти дев'яти"), + (65, "шістдесяти п'яти"), + (70, "сімдесяти"), + (76, "сімдесяти шести"), + (80, "вісімдесяти"), + (87, "вісімдесяти семи"), + (90, "дев'яноста"), + (98, "дев'яноста восьми"), + (99, "дев'яноста дев'яти"), + (100, "ста"), + (101, "ста одному"), + (199, "ста дев'яноста дев'яти"), + (200, "двомстам"), + (203, "двомстам трьом"), + (300, "трьомстам"), + (356, "трьомстам п'ятдесяти шести"), + (400, "чотирьомстам"), + (434, "чотирьомстам тридцяти чотирьом"), + (500, "п'ятистам"), + (578, "п'ятистам сімдесяти восьми"), + (600, "шестистам"), + (689, "шестистам вісімдесяти дев'яти"), + (700, "семистам"), + (729, "семистам двадцяти дев'яти"), + (800, "восьмистам"), + (894, "восьмистам дев'яноста чотирьом"), + (900, "дев'ятистам"), + (999, "дев'ятистам дев'яноста дев'яти"), + (1000, "одній тисячі"), + (1001, "одній тисячі одному"), + (2012, "двом тисячам дванадцяти"), + (12519, "дванадцяти тисячам п'ятистам дев'ятнадцяти"), + (12519.85, "дванадцяти тисячам п'ятистам дев'ятнадцяти кома вісімдесяти п'яти"), + (-260000, "мінус двомстам шістдесяти тисячам"), + (1000000, "одному мільйону"), + (1000000000, "одному мільярду"), + (1234567890, "одному мільярду двомстам тридцяти чотирьом мільйонам " + "п'ятистам шістдесяти семи тисячам восьмистам дев'яноста"), + (1000000000000, "одному трильйону"), + (1000000000000000, "одному квадрильйону"), + (1000000000000000000, "одному квінтильйону"), + (1000000000000000000000, "одному секстильйону"), + (1000000000000000000000000, "одному септильйону"), + (1000000000000000000000000000, "одному октильйону"), + (1000000000000000000000000000000, "одному нонільйону"), + (215461407892039002157189883901676, + "двомстам п'ятнадцяти нонільйонам чотирьомстам шістдесяти одному " + "октильйону чотирьомстам семи септильйонам восьмистам дев'яноста " + "двом секстильйонам тридцяти дев'яти квінтильйонам двом " + "квадрильйонам ста п'ятдесяти семи трильйонам ста вісімдесяти " + "дев'яти мільярдам восьмистам вісімдесяти трьом мільйонам " + "дев'ятистам одній тисячі шестистам сімдесяти шести"), + (719094234693663034822824384220291, + "семистам дев'ятнадцяти нонільйонам дев'яноста чотирьом октильйонам " + "двомстам тридцяти чотирьом септильйонам шестистам дев'яноста трьом " + "секстильйонам шестистам шістдесяти трьом квінтильйонам тридцяти " + "чотирьом квадрильйонам восьмистам двадцяти двом трильйонам восьмистам " + "двадцяти чотирьом мільярдам трьомстам вісімдесяти чотирьом мільйонам " + "двомстам двадцяти тисячам двомстам дев'яноста одному") +) + +TEST_CASES_CARDINAL_ACCUSATIVE = ( + (1, "один"), + (2, "два"), + (3, "три"), + (4, "чотири"), + (5, "п'ять"), + (6, "шість"), + (7, "сім"), + (8, "вісім"), + (9, "дев'ять"), + (10, "десять"), + (10.02, "десять кома нуль два"), + (11, "одинадцять"), + (12, "дванадцять"), + (12.40, "дванадцять кома чотири"), + (13, "тринадцять"), + (14, "чотирнадцять"), + (14.13, "чотирнадцять кома тринадцять"), + (15, "п'ятнадцять"), + (16, "шістнадцять"), + (17, "сімнадцять"), + (17.31, "сімнадцять кома тридцять один"), + (18, "вісімнадцять"), + (19, "дев'ятнадцять"), + (20, "двадцять"), + (21, "двадцять один"), + (21.20, "двадцять один кома два"), + (30, "тридцять"), + (32, "тридцять два"), + (40, "сорок"), + (43, "сорок три"), + (43.007, "сорок три кома нуль нуль сім"), + (50, "п'ятдесят"), + (54, "п'ятдесят чотири"), + (60, "шістдесят"), + (60.059, "шістдесят кома нуль п'ятдесят дев'ять"), + (65, "шістдесят п'ять"), + (70, "сімдесят"), + (76, "сімдесят шість"), + (80, "вісімдесят"), + (87, "вісімдесят сім"), + (90, "дев'яносто"), + (98, "дев'яносто вісім"), + (99, "дев'яносто дев'ять"), + (100, "сто"), + (101, "сто один"), + (199, "сто дев'яносто дев'ять"), + (200, "двісті"), + (203, "двісті три"), + (300, "триста"), + (356, "триста п'ятдесят шість"), + (400, "чотириста"), + (434, "чотириста тридцять чотири"), + (500, "п'ятсот"), + (578, "п'ятсот сімдесят вісім"), + (600, "шістсот"), + (689, "шістсот вісімдесят дев'ять"), + (700, "сімсот"), + (729, "сімсот двадцять дев'ять"), + (800, "вісімсот"), + (894, "вісімсот дев'яносто чотири"), + (900, "дев'ятсот"), + (999, "дев'ятсот дев'яносто дев'ять"), + (1000, "одну тисячу"), + (1001, "одну тисячу один"), + (2012, "дві тисячі дванадцять"), + (12519, "дванадцять тисяч п'ятсот дев'ятнадцять"), + (12519.85, "дванадцять тисяч п'ятсот дев'ятнадцять кома вісімдесят п'ять"), + (-260000, "мінус двісті шістдесят тисяч"), + (1000000, "один мільйон"), + (1000000000, "один мільярд"), + (1234567890, "один мільярд двісті тридцять чотири мільйони " + "п'ятсот шістдесят сім тисяч вісімсот дев'яносто"), + (1000000000000, "один трильйон"), + (1000000000000000, "один квадрильйон"), + (1000000000000000000, "один квінтильйон"), + (1000000000000000000000, "один секстильйон"), + (1000000000000000000000000, "один септильйон"), + (1000000000000000000000000000, "один октильйон"), + (1000000000000000000000000000000, "один нонільйон"), + (215461407892039002157189883901676, + "двісті п'ятнадцять нонільйонів чотириста шістдесят один " + "октильйон чотириста сім септильйонів вісімсот дев'яносто " + "два секстильйони тридцять дев'ять квінтильйонів два " + "квадрильйони сто п'ятдесят сім трильйонів сто вісімдесят " + "дев'ять мільярдів вісімсот вісімдесят три мільйони " + "дев'ятсот одну тисячу шістсот сімдесят шість"), + (719094234693663034822824384220291, + "сімсот дев'ятнадцять нонільйонів дев'яносто чотири октильйони " + "двісті тридцять чотири септильйони шістсот дев'яносто три " + "секстильйони шістсот шістдесят три квінтильйони тридцять " + "чотири квадрильйони вісімсот двадцять два трильйони вісімсот " + "двадцять чотири мільярди триста вісімдесят чотири мільйони " + "двісті двадцять тисяч двісті дев'яносто один") +) + +TEST_CASES_CARDINAL_INSTRUMENTAL = ( + (1, "одним"), + (2, "двома"), + (3, "трьома"), + (4, "чотирма"), + (5, "п'ятьма"), + (6, "шістьма"), + (7, "сьома"), + (8, "вісьма"), + (9, "дев'ятьма"), + (10, "десятьма"), + (10.02, "десятьма кома нуль двома"), + (11, "одинадцятьма"), + (12, "дванадцятьма"), + (12.40, "дванадцятьма кома чотирма"), + (13, "тринадцятьма"), + (14, "чотирнадцятьма"), + (14.13, "чотирнадцятьма кома тринадцятьма"), + (15, "п'ятнадцятьма"), + (16, "шістнадцятьма"), + (17, "сімнадцятьма"), + (17.31, "сімнадцятьма кома тридцятьма одним"), + (18, "вісімнадцятьма"), + (19, "дев'ятнадцятьма"), + (20, "двадцятьма"), + (21, "двадцятьма одним"), + (21.20, "двадцятьма одним кома двома"), + (30, "тридцятьма"), + (32, "тридцятьма двома"), + (40, "сорока"), + (43, "сорока трьома"), + (43.007, "сорока трьома кома нуль нуль сьома"), + (50, "п'ятдесятьма"), + (54, "п'ятдесятьма чотирма"), + (60, "шістдесятьма"), + (60.059, "шістдесятьма кома нуль п'ятдесятьма дев'ятьма"), + (65, "шістдесятьма п'ятьма"), + (70, "сімдесятьма"), + (76, "сімдесятьма шістьма"), + (80, "вісімдесятьма"), + (87, "вісімдесятьма сьома"), + (90, "дев'яностами"), + (98, "дев'яностами вісьма"), + (99, "дев'яностами дев'ятьма"), + (100, "стами"), + (101, "стами одним"), + (199, "стами дев'яностами дев'ятьма"), + (200, "двомастами"), + (203, "двомастами трьома"), + (300, "трьомастами"), + (356, "трьомастами п'ятдесятьма шістьма"), + (400, "чотирмастами"), + (434, "чотирмастами тридцятьма чотирма"), + (500, "п'ятьмастами"), + (578, "п'ятьмастами сімдесятьма вісьма"), + (600, "шістьмастами"), + (689, "шістьмастами вісімдесятьма дев'ятьма"), + (700, "сьомастами"), + (729, "сьомастами двадцятьма дев'ятьма"), + (800, "восьмастами"), + (894, "восьмастами дев'яностами чотирма"), + (900, "дев'ятьмастами"), + (999, "дев'ятьмастами дев'яностами дев'ятьма"), + (1000, "однією тисячею"), + (1001, "однією тисячею одним"), + (2012, "двома тисячами дванадцятьма"), + (12519, "дванадцятьма тисячами п'ятьмастами дев'ятнадцятьма"), + (12519.85, "дванадцятьма тисячами п'ятьмастами дев'ятнадцятьма кома вісімдесятьма п'ятьма"), + (-260000, "мінус двомастами шістдесятьма тисячами"), + (1000000, "одним мільйоном"), + (1000000000, "одним мільярдом"), + (1234567890, "одним мільярдом двомастами тридцятьма чотирма мільйонами " + "п'ятьмастами шістдесятьма сьома тисячами восьмастами дев'яностами"), + (1000000000000, "одним трильйоном"), + (1000000000000000, "одним квадрильйоном"), + (1000000000000000000, "одним квінтильйоном"), + (1000000000000000000000, "одним секстильйоном"), + (1000000000000000000000000, "одним септильйоном"), + (1000000000000000000000000000, "одним октильйоном"), + (1000000000000000000000000000000, "одним нонільйоном"), + (215461407892039002157189883901676, + "двомастами п'ятнадцятьма нонільйонів чотирмастами шістдесятьма одним " + "октильйоном чотирмастами сьома септильйонів восьмастами дев'яностами " + "двома секстильйонами тридцятьма дев'ятьма квінтильйонів двома " + "квадрильйонами стами п'ятдесятьма сьома трильйонів стами вісімдесятьма " + "дев'ятьма мільярдів восьмастами вісімдесятьма трьома мільйонами " + "дев'ятьмастами однією тисячею шістьмастами сімдесятьма шістьма"), + (719094234693663034822824384220291, + "сьомастами дев'ятнадцятьма нонільйонів дев'яностами чотирма октильйонами " + "двомастами тридцятьма чотирма септильйонами шістьмастами дев'яностами трьома " + "секстильйонами шістьмастами шістдесятьма трьома квінтильйонами тридцятьма " + "чотирма квадрильйонами восьмастами двадцятьма двома трильйонами восьмастами " + "двадцятьма чотирма мільярдами трьомастами вісімдесятьма чотирма мільйонами " + "двомастами двадцятьма тисячами двомастами дев'яностами одним") +) + +TEST_CASES_CARDINAL_LOCATIVE = ( + (1, "одному"), + (2, "двох"), + (3, "трьох"), + (4, "чотирьох"), + (5, "п'яти"), + (6, "шести"), + (7, "семи"), + (8, "восьми"), + (9, "дев'яти"), + (10, "десяти"), + (10.02, "десяти кома нуль двох"), + (11, "одинадцяти"), + (12, "дванадцяти"), + (12.40, "дванадцяти кома чотирьох"), + (13, "тринадцяти"), + (14, "чотирнадцяти"), + (14.13, "чотирнадцяти кома тринадцяти"), + (15, "п'ятнадцяти"), + (16, "шістнадцяти"), + (17, "сімнадцяти"), + (17.31, "сімнадцяти кома тридцяти одному"), + (18, "вісімнадцяти"), + (19, "дев'ятнадцяти"), + (20, "двадцяти"), + (21, "двадцяти одному"), + (21.20, "двадцяти одному кома двох"), + (30, "тридцяти"), + (32, "тридцяти двох"), + (40, "сорока"), + (43, "сорока трьох"), + (43.007, "сорока трьох кома нуль нуль семи"), + (50, "п'ятдесяти"), + (54, "п'ятдесяти чотирьох"), + (60, "шістдесяти"), + (60.059, "шістдесяти кома нуль п'ятдесяти дев'яти"), + (65, "шістдесяти п'яти"), + (70, "сімдесяти"), + (76, "сімдесяти шести"), + (80, "вісімдесяти"), + (87, "вісімдесяти семи"), + (90, "дев'яноста"), + (98, "дев'яноста восьми"), + (99, "дев'яноста дев'яти"), + (100, "стах"), + (101, "стах одному"), + (199, "стах дев'яноста дев'яти"), + (200, "двохстах"), + (203, "двохстах трьох"), + (300, "трьохстах"), + (356, "трьохстах п'ятдесяти шести"), + (400, "чотирьохстах"), + (434, "чотирьохстах тридцяти чотирьох"), + (500, "п'ятистах"), + (578, "п'ятистах сімдесяти восьми"), + (600, "шестистах"), + (689, "шестистах вісімдесяти дев'яти"), + (700, "семистах"), + (729, "семистах двадцяти дев'яти"), + (800, "восьмистах"), + (894, "восьмистах дев'яноста чотирьох"), + (900, "дев'ятистах"), + (999, "дев'ятистах дев'яноста дев'яти"), + (1000, "одній тисячі"), + (1001, "одній тисячі одному"), + (2012, "двох тисячах дванадцяти"), + (12519, "дванадцяти тисячах п'ятистах дев'ятнадцяти"), + (12519.85, "дванадцяти тисячах п'ятистах дев'ятнадцяти кома вісімдесяти п'яти"), + (-260000, "мінус двохстах шістдесяти тисячах"), + (1000000, "одному мільйоні"), + (1000000000, "одному мільярді"), + (1234567890, "одному мільярді двохстах тридцяти чотирьох мільйонах " + "п'ятистах шістдесяти семи тисячах восьмистах дев'яноста"), + (1000000000000, "одному трильйоні"), + (1000000000000000, "одному квадрильйоні"), + (1000000000000000000, "одному квінтильйоні"), + (1000000000000000000000, "одному секстильйоні"), + (1000000000000000000000000, "одному септильйоні"), + (1000000000000000000000000000, "одному октильйоні"), + (1000000000000000000000000000000, "одному нонільйоні"), + (215461407892039002157189883901676, + "двохстах п'ятнадцяти нонільйонах чотирьохстах шістдесяти одному " + "октильйоні чотирьохстах семи септильйонах восьмистах дев'яноста " + "двох секстильйонах тридцяти дев'яти квінтильйонах двох " + "квадрильйонах стах п'ятдесяти семи трильйонах стах вісімдесяти " + "дев'яти мільярдах восьмистах вісімдесяти трьох мільйонах " + "дев'ятистах одній тисячі шестистах сімдесяти шести"), + (719094234693663034822824384220291, + "семистах дев'ятнадцяти нонільйонах дев'яноста чотирьох октильйонах " + "двохстах тридцяти чотирьох септильйонах шестистах дев'яноста трьох " + "секстильйонах шестистах шістдесяти трьох квінтильйонах тридцяти " + "чотирьох квадрильйонах восьмистах двадцяти двох трильйонах восьмистах " + "двадцяти чотирьох мільярдах трьохстах вісімдесяти чотирьох мільйонах " + "двохстах двадцяти тисячах двохстах дев'яноста одному") +) + TEST_CASES_ORDINAL = ( (1, "перший"), (2, "другий"), @@ -2449,10 +3025,43 @@ class Num2WordsUKTest(TestCase): + def test_to_cardinal(self): for test in TEST_CASES_CARDINAL: self.assertEqual(num2words(test[0], lang='uk'), test[1]) + def test_to_cardinal_feminine(self): + for test in TEST_CASES_CARDINAL_FEMININE: + self.assertEqual(num2words(test[0], lang='uk', gender='feminine'), test[1]) + + def test_to_cardinal_nominative(self): + for test in TEST_CASES_CARDINAL: + self.assertEqual(num2words(test[0], lang='uk', case='nominative'), test[1]) + + def test_to_cardinal_genitive(self): + for test in TEST_CASES_CARDINAL_GENITIVE: + self.assertEqual(num2words(test[0], lang='uk', case='genitive'), test[1]) + + def test_to_cardinal_dative(self): + self.maxDiff = None + for test in TEST_CASES_CARDINAL_DATIVE: + self.assertEqual(num2words(test[0], lang='uk', case='dative'), test[1]) + + def test_to_cardinal_accusative(self): + self.maxDiff = None + for test in TEST_CASES_CARDINAL_ACCUSATIVE: + self.assertEqual(num2words(test[0], lang='uk', case='accusative'), test[1]) + + def test_to_cardinal_instrumental(self): + self.maxDiff = None + for test in TEST_CASES_CARDINAL_INSTRUMENTAL: + self.assertEqual(num2words(test[0], lang='uk', case='instrumental'), test[1]) + + def test_to_cardinal_locative(self): + self.maxDiff = None + for test in TEST_CASES_CARDINAL_LOCATIVE: + self.assertEqual(num2words(test[0], lang='uk', case='locative'), test[1]) + def test_to_ordinal(self): for test in TEST_CASES_ORDINAL: self.assertEqual( From ad42ffdea573b448de8215e2a2316dcaace13d42 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Sat, 12 Aug 2023 15:43:19 +0600 Subject: [PATCH 35/37] Reformat source code --- num2words/lang_UK.py | 318 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 255 insertions(+), 63 deletions(-) diff --git a/num2words/lang_UK.py b/num2words/lang_UK.py index 0b62d858..188d7ced 100644 --- a/num2words/lang_UK.py +++ b/num2words/lang_UK.py @@ -23,27 +23,27 @@ ZERO = ('нуль',) ONES_FEMININE = { - 1: ('одна', "однієї", "одній", "одну", "однією", "одній"), - 2: ('дві', "двох", "двом", "дві", "двома", "двох"), - 3: ('три', "трьох", "трьом", "три", "трьома", "трьох"), - 4: ('чотири', "чотирьох", "чотирьом", "чотири", "чотирма", "чотирьох"), - 5: ('п\'ять', "п'яти", "п'яти", "п'ять", "п'ятьма", "п'яти"), - 6: ('шість', "шести", "шести", "шість", "шістьма", "шести"), - 7: ('сім', "семи", "семи", "сім", "сьома", "семи"), - 8: ('вісім', "восьми", "восьми", "вісім", "вісьма", "восьми"), - 9: ("дев'ять", "дев'яти", "дев'яти", "дев'ять", "дев'ятьма","дев'яти"), + 1: ('одна', "однієї", "одній", "одну", "однією", "одній"), + 2: ('дві', "двох", "двом", "дві", "двома", "двох"), + 3: ('три', "трьох", "трьом", "три", "трьома", "трьох"), + 4: ('чотири', "чотирьох", "чотирьом", "чотири", "чотирма", "чотирьох"), + 5: ('п\'ять', "п'яти", "п'яти", "п'ять", "п'ятьма", "п'яти"), + 6: ('шість', "шести", "шести", "шість", "шістьма", "шести"), + 7: ('сім', "семи", "семи", "сім", "сьома", "семи"), + 8: ('вісім', "восьми", "восьми", "вісім", "вісьма", "восьми"), + 9: ("дев'ять", "дев'яти", "дев'яти", "дев'ять", "дев'ятьма", "дев'яти"), } ONES = { - 1: ('один', 'одного', "одному", "один", "одним", "одному"), - 2: ('два', 'двох', "двом", "два", "двома", "двох"), - 3: ('три', 'трьох', "трьом", "три", "трьома", "трьох"), - 4: ('чотири', 'чотирьох', "чотирьом", "чотири", "чотирма", "чотирьох"), - 5: ('п\'ять', "п'яти", "п'яти", "п'ять", "п'ятьма", "п'яти"), - 6: ('шість', 'шести', "шести", "шість", "шістьма", "шести"), - 7: ('сім', 'семи', "семи", "сім", "сьома", "семи"), - 8: ('вісім', 'восьми', "восьми", "вісім", "вісьма", "восьми"), - 9: ('дев\'ять', "дев'яти", "дев'яти", "дев'ять", "дев'ятьма","дев'яти"), + 1: ('один', 'одного', "одному", "один", "одним", "одному"), + 2: ('два', 'двох', "двом", "два", "двома", "двох"), + 3: ('три', 'трьох', "трьом", "три", "трьома", "трьох"), + 4: ('чотири', 'чотирьох', "чотирьом", "чотири", "чотирма", "чотирьох"), + 5: ('п\'ять', "п'яти", "п'яти", "п'ять", "п'ятьма", "п'яти"), + 6: ('шість', 'шести', "шести", "шість", "шістьма", "шести"), + 7: ('сім', 'семи', "семи", "сім", "сьома", "семи"), + 8: ('вісім', 'восьми', "восьми", "вісім", "вісьма", "восьми"), + 9: ("дев'ять", "дев'яти", "дев'яти", "дев'ять", "дев'ятьма", "дев'яти"), } ONES_ORDINALS = { @@ -67,29 +67,118 @@ 18: ("вісімнадцятий", "вісімнадцяти"), 19: ("дев'ятнадцятий", "дев'ятнадцяти"), } - TENS = { - 0: ('десять', 'десяти', "десяти", "десять", "десятьма", "десяти"), - 1: ('одинадцять', 'одинадцяти', "одинадцяти", "одинадцять", "одинадцятьма", "одинадцяти"), - 2: ('дванадцять', 'дванадцяти', "дванадцяти", "дванадцять", "дванадцятьма", "дванадцяти"), - 3: ('тринадцять', 'тринадцяти', "тринадцяти", "тринадцять", "тринадцятьма", "тринадцяти"), - 4: ('чотирнадцять', 'чотирнадцяти', "чотирнадцяти", "чотирнадцять", "чотирнадцятьма", "чотирнадцяти"), - 5: ("п'ятнадцять", "п'ятнадцяти", "п'ятнадцяти", "п'ятнадцять", "п'ятнадцятьма", "п'ятнадцяти"), - 6: ('шістнадцять', 'шістнадцяти', "шістнадцяти", "шістнадцять", "шістнадцятьма", "шістнадцяти"), - 7: ('сімнадцять', 'сімнадцяти', "сімнадцяти", "сімнадцять", "сімнадцятьма", "сімнадцяти"), - 8: ('вісімнадцять', 'вісімнадцяти', "вісімнадцяти", "вісімнадцять", "вісімнадцятьма", "вісімнадцяти"), - 9: ("дев'ятнадцять","дев'ятнадцяти","дев'ятнадцяти","дев'ятнадцять","дев'ятнадцятьма", "дев'ятнадцяти"), + 0: ('десять', + 'десяти', + "десяти", + "десять", + "десятьма", + "десяти"), + 1: ('одинадцять', + 'одинадцяти', + "одинадцяти", + "одинадцять", + "одинадцятьма", + "одинадцяти"), + 2: ('дванадцять', + 'дванадцяти', + "дванадцяти", + "дванадцять", + "дванадцятьма", + "дванадцяти"), + 3: ('тринадцять', + 'тринадцяти', + "тринадцяти", + "тринадцять", + "тринадцятьма", + "тринадцяти"), + 4: ('чотирнадцять', + 'чотирнадцяти', + "чотирнадцяти", + "чотирнадцять", + "чотирнадцятьма", + "чотирнадцяти"), + 5: ("п'ятнадцять", + "п'ятнадцяти", + "п'ятнадцяти", + "п'ятнадцять", + "п'ятнадцятьма", + "п'ятнадцяти"), + 6: ('шістнадцять', + 'шістнадцяти', + "шістнадцяти", + "шістнадцять", + "шістнадцятьма", + "шістнадцяти"), + 7: ('сімнадцять', + 'сімнадцяти', + "сімнадцяти", + "сімнадцять", + "сімнадцятьма", + "сімнадцяти"), + 8: ('вісімнадцять', + 'вісімнадцяти', + "вісімнадцяти", + "вісімнадцять", + "вісімнадцятьма", + "вісімнадцяти"), + 9: ("дев'ятнадцять", + "дев'ятнадцяти", + "дев'ятнадцяти", + "дев'ятнадцять", + "дев'ятнадцятьма", + "дев'ятнадцяти"), } TWENTIES = { - 2: ('двадцять', "двадцяти", "двадцяти", "двадцять", "двадцятьма", "двадцяти"), - 3: ('тридцять', "тридцяти", "тридцяти", "тридцять", "тридцятьма", "тридцяти"), - 4: ('сорок', "сорока", "сорока", "сорок", "сорока", "сорока"), - 5: ('п\'ятдесят', "п'ятдесяти", "п'ятдесяти", "п'ятдесят", "п'ятдесятьма", "п'ятдесяти"), - 6: ('шістдесят', "шістдесяти", "шістдесяти", "шістдесят", "шістдесятьма", "шістдесяти"), - 7: ('сімдесят', "сімдесяти", "сімдесяти", "сімдесят", "сімдесятьма", "сімдесяти"), - 8: ('вісімдесят', "вісімдесяти", "вісімдесяти", "вісімдесят", "вісімдесятьма","вісімдесяти"), - 9: ('дев\'яносто', "дев'яноста", "дев'яноста", "дев'яносто", "дев'яностами", "дев'яноста"), + 2: ('двадцять', + "двадцяти", + "двадцяти", + "двадцять", + "двадцятьма", + "двадцяти"), + 3: ('тридцять', + "тридцяти", + "тридцяти", + "тридцять", + "тридцятьма", + "тридцяти"), + 4: ('сорок', + "сорока", + "сорока", + "сорок", + "сорока", + "сорока"), + 5: ('п\'ятдесят', + "п'ятдесяти", + "п'ятдесяти", + "п'ятдесят", + "п'ятдесятьма", + "п'ятдесяти"), + 6: ('шістдесят', + "шістдесяти", + "шістдесяти", + "шістдесят", + "шістдесятьма", + "шістдесяти"), + 7: ('сімдесят', + "сімдесяти", + "сімдесяти", + "сімдесят", + "сімдесятьма", + "сімдесяти"), + 8: ('вісімдесят', + "вісімдесяти", + "вісімдесяти", + "вісімдесят", + "вісімдесятьма", + "вісімдесяти"), + 9: ('дев\'яносто', + "дев'яноста", + "дев'яноста", + "дев'яносто", + "дев'яностами", + "дев'яноста"), } TWENTIES_ORDINALS = { @@ -104,15 +193,60 @@ } HUNDREDS = { - 1: ('сто', "ста", "ста", "сто", "стами", "стах"), - 2: ('двісті', "двохста", "двомстам", "двісті", "двомастами", "двохстах"), - 3: ('триста', "трьохста", "трьомстам", "триста", "трьомастами", "трьохстах"), - 4: ('чотириста',"чотирьохста", "чотирьомстам", "чотириста","чотирмастами", "чотирьохстах"), - 5: ('п\'ятсот', "п'ятиста", "п'ятистам", "п'ятсот", "п'ятьмастами", "п'ятистах"), - 6: ('шістсот', "шестиста", "шестистам", "шістсот", "шістьмастами", "шестистах"), - 7: ('сімсот', "семиста", "семистам", "сімсот", "сьомастами", "семистах"), - 8: ('вісімсот', "восьмиста", "восьмистам", "вісімсот", "восьмастами", "восьмистах"), - 9: ("дев'ятсот","дев'ятиста", "дев'ятистам", "дев'ятсот","дев'ятьмастами","дев'ятистах"), + 1: ('сто', + "ста", + "ста", + "сто", + "стами", + "стах"), + 2: ('двісті', + "двохста", + "двомстам", + "двісті", + "двомастами", + "двохстах"), + 3: ('триста', + "трьохста", + "трьомстам", + "триста", + "трьомастами", + "трьохстах"), + 4: ('чотириста', + "чотирьохста", + "чотирьомстам", + "чотириста", + "чотирмастами", + "чотирьохстах"), + 5: ('п\'ятсот', + "п'ятиста", + "п'ятистам", + "п'ятсот", + "п'ятьмастами", + "п'ятистах"), + 6: ('шістсот', + "шестиста", + "шестистам", + "шістсот", + "шістьмастами", + "шестистах"), + 7: ('сімсот', + "семиста", + "семистам", + "сімсот", + "сьомастами", + "семистах"), + 8: ('вісімсот', + "восьмиста", + "восьмистам", + "вісімсот", + "восьмастами", + "восьмистах"), + 9: ("дев'ятсот", + "дев'ятиста", + "дев'ятистам", + "дев'ятсот", + "дев'ятьмастами", + "дев'ятистах"), } HUNDREDS_ORDINALS = { @@ -128,28 +262,76 @@ } THOUSANDS = { - # Nominative Genitive Dative Accusative Instrumental Locative - # ----------------------------------------------------- --------------------------------------------------- --------------------------------------------------- --------------------------------------------------- ------------------------------------------------------- -------------------------------------------------------- # 10^3 - 1: (('тисяча', 'тисячі', 'тисяч'), ('тисячи', 'тисяч', 'тисяч'), ('тисячі', 'тисячам', 'тисячам'), ('тисячу', 'тисячі', 'тисяч'), ('тисячею', 'тисячами', 'тисячами'), ('тисячі', 'тисячах', 'тисячах'),), + 1: (('тисяча', 'тисячі', 'тисяч'), + ('тисячи', 'тисяч', 'тисяч'), + ('тисячі', 'тисячам', 'тисячам'), + ('тисячу', 'тисячі', 'тисяч'), + ('тисячею', 'тисячами', 'тисячами'), + ('тисячі', 'тисячах', 'тисячах'),), # 10^6 - 2: (('мільйон', 'мільйони', 'мільйонів'), ('мільйона', 'мільйонів', 'мільйонів'), ('мільйону', 'мільйонам', 'мільйонам'), ('мільйон', 'мільйони', 'мільйонів'), ('мільйоном', 'мільйонами', 'мільйонів'), ('мільйоні', 'мільйонах', 'мільйонах'),), + 2: (('мільйон', 'мільйони', 'мільйонів'), + ('мільйона', 'мільйонів', 'мільйонів'), + ('мільйону', 'мільйонам', 'мільйонам'), + ('мільйон', 'мільйони', 'мільйонів'), + ('мільйоном', 'мільйонами', 'мільйонів'), + ('мільйоні', 'мільйонах', 'мільйонах'),), # 10^9 - 3: (('мільярд', 'мільярди', 'мільярдів'), ('мільярда', 'мільярдів', 'мільярдів'), ('мільярду', 'мільярдам', 'мільярдам'), ('мільярд', 'мільярди', 'мільярдів'), ('мільярдом', 'мільярдами', 'мільярдів'), ('мільярді', 'мільярдах', 'мільярдах'),), + 3: (('мільярд', 'мільярди', 'мільярдів'), + ('мільярда', 'мільярдів', 'мільярдів'), + ('мільярду', 'мільярдам', 'мільярдам'), + ('мільярд', 'мільярди', 'мільярдів'), + ('мільярдом', 'мільярдами', 'мільярдів'), + ('мільярді', 'мільярдах', 'мільярдах'),), # 10^12 - 4: (('трильйон', 'трильйони', 'трильйонів'), ('трильйона', 'трильйонів', 'трильйонів'), ('трильйону', 'трильйонам', 'трильйонам'), ('трильйон', 'трильйони', 'трильйонів'), ('трильйоном', 'трильйонами', 'трильйонів'), ('трильйоні', 'трильйонах', 'трильйонах'),), + 4: (('трильйон', 'трильйони', 'трильйонів'), + ('трильйона', 'трильйонів', 'трильйонів'), + ('трильйону', 'трильйонам', 'трильйонам'), + ('трильйон', 'трильйони', 'трильйонів'), + ('трильйоном', 'трильйонами', 'трильйонів'), + ('трильйоні', 'трильйонах', 'трильйонах'),), # 10^15 - 5: (('квадрильйон', 'квадрильйони', 'квадрильйонів'), ('квадрильйона', 'квадрильйонів', 'квадрильйонів'), ('квадрильйону', 'квадрильйонам', 'квадрильйонам'), ('квадрильйон', 'квадрильйони', 'квадрильйонів'), ('квадрильйоном', 'квадрильйонами', 'квадрильйонів'), ('квадрильйоні', 'квадрильйонах', 'квадрильйонах'),), + 5: (('квадрильйон', 'квадрильйони', 'квадрильйонів'), + ('квадрильйона', 'квадрильйонів', 'квадрильйонів'), + ('квадрильйону', 'квадрильйонам', 'квадрильйонам'), + ('квадрильйон', 'квадрильйони', 'квадрильйонів'), + ('квадрильйоном', 'квадрильйонами', 'квадрильйонів'), + ('квадрильйоні', 'квадрильйонах', 'квадрильйонах'),), # 10^18 - 6: (('квінтильйон', 'квінтильйони', 'квінтильйонів'), ('квінтильйона', 'квінтильйонів', 'квінтильйонів'), ('квінтильйону', 'квінтильйонам', 'квінтильйонам'), ('квінтильйон', 'квінтильйони', 'квінтильйонів'), ('квінтильйоном', 'квінтильйонами', 'квінтильйонів'), ('квінтильйоні', 'квінтильйонах', 'квінтильйонах'),), + 6: (('квінтильйон', 'квінтильйони', 'квінтильйонів'), + ('квінтильйона', 'квінтильйонів', 'квінтильйонів'), + ('квінтильйону', 'квінтильйонам', 'квінтильйонам'), + ('квінтильйон', 'квінтильйони', 'квінтильйонів'), + ('квінтильйоном', 'квінтильйонами', 'квінтильйонів'), + ('квінтильйоні', 'квінтильйонах', 'квінтильйонах'),), # 10^21 - 7: (('секстильйон', 'секстильйони', 'секстильйонів'), ('секстильйона', 'секстильйонів', 'секстильйонів'), ('секстильйону', 'секстильйонам', 'секстильйонам'), ('секстильйон', 'секстильйони', 'секстильйонів'), ('секстильйоном', 'секстильйонами', 'секстильйонів'), ('секстильйоні', 'секстильйонах', 'секстильйонах'),), + 7: (('секстильйон', 'секстильйони', 'секстильйонів'), + ('секстильйона', 'секстильйонів', 'секстильйонів'), + ('секстильйону', 'секстильйонам', 'секстильйонам'), + ('секстильйон', 'секстильйони', 'секстильйонів'), + ('секстильйоном', 'секстильйонами', 'секстильйонів'), + ('секстильйоні', 'секстильйонах', 'секстильйонах'),), # 10^24 - 8: (('септильйон', 'септильйони', 'септильйонів'), ('септильйона', 'септильйонів', 'септильйонів'), ('септильйону', 'септильйонам', 'септильйонам'), ('септильйон', 'септильйони', 'септильйонів'), ('септильйоном', 'септильйонами', 'септильйонів'), ('септильйоні', 'септильйонах', 'септильйонах'),), + 8: (('септильйон', 'септильйони', 'септильйонів'), + ('септильйона', 'септильйонів', 'септильйонів'), + ('септильйону', 'септильйонам', 'септильйонам'), + ('септильйон', 'септильйони', 'септильйонів'), + ('септильйоном', 'септильйонами', 'септильйонів'), + ('септильйоні', 'септильйонах', 'септильйонах'),), # 10^27 - 9: (('октильйон', 'октильйони', 'октильйонів'), ('октильйона', 'октильйонів', 'октильйонів'), ('октильйону', 'октильйонам', 'октильйонам'), ('октильйон', 'октильйони', 'октильйонів'), ('октильйоном', 'октильйонами', 'октильйонів'), ('октильйоні', 'октильйонах', 'октильйонах'),), + 9: (('октильйон', 'октильйони', 'октильйонів'), + ('октильйона', 'октильйонів', 'октильйонів'), + ('октильйону', 'октильйонам', 'октильйонам'), + ('октильйон', 'октильйони', 'октильйонів'), + ('октильйоном', 'октильйонами', 'октильйонів'), + ('октильйоні', 'октильйонах', 'октильйонах'),), # 10^30 - 10: (('нонільйон', 'нонільйони', 'нонільйонів'), ('нонільйона', 'нонільйонів', 'нонільйонів'), ('нонільйону', 'нонільйонам', 'нонільйонам'), ('нонільйон', 'нонільйони', 'нонільйонів'), ('нонільйоном', 'нонільйонами', 'нонільйонів'), ('нонільйоні', 'нонільйонах', 'нонільйонах'),), + 10: (('нонільйон', 'нонільйони', 'нонільйонів'), + ('нонільйона', 'нонільйонів', 'нонільйонів'), + ('нонільйону', 'нонільйонам', 'нонільйонам'), + ('нонільйон', 'нонільйони', 'нонільйонів'), + ('нонільйоном', 'нонільйонами', 'нонільйонів'), + ('нонільйоні', 'нонільйонах', 'нонільйонах'),), } prefixes_ordinal = { @@ -726,7 +908,13 @@ def setup(self): def to_cardinal(self, number, **kwargs): if 'case' in kwargs: case = kwargs['case'] - morphological_case = ["nominative", "genitive", "dative", "accusative", "instrumental", "locative"].index(case) + morphological_case = [ + "nominative", + "genitive", + "dative", + "accusative", + "instrumental", + "locative"].index(case) else: morphological_case = 0 @@ -739,8 +927,9 @@ def to_cardinal(self, number, **kwargs): if '.' in n: left, right = n.split('.') leading_zero_count = len(right) - len(right.lstrip('0')) + right_side = self._int2word(int(right), gender, morphological_case) decimal_part = ((ZERO[0] + ' ') * leading_zero_count + - self._int2word(int(right), gender, morphological_case)) + right_side) return u'%s %s %s' % ( self._int2word(int(left), gender, morphological_case), self.pointword, @@ -762,9 +951,11 @@ def pluralize(self, n, forms): return forms[form] - def _int2word(self, n, feminine=False, morphological_case = 0): + def _int2word(self, n, feminine=False, morphological_case=0): if n < 0: - return ' '.join([self.negword, self._int2word(abs(n), feminine, morphological_case)]) + n_value = self._int2word(abs(n), feminine, morphological_case) + return ' '.join([self.negword, + n_value]) if n == 0: return ZERO[0] @@ -794,7 +985,8 @@ def _int2word(self, n, feminine=False, morphological_case = 0): words.append(ones[n1][morphological_case]) if i > 0: - words.append(self.pluralize(x, THOUSANDS[i][morphological_case])) + thousands_val = THOUSANDS[i][morphological_case] + words.append(self.pluralize(x, thousands_val)) return ' '.join(words) From 6c8e66bd37f38eac3ef339963b88ebf8140b26d7 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Sat, 19 Aug 2023 00:31:33 +0600 Subject: [PATCH 36/37] Fix style in tests --- tests/test_uk.py | 51 ++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/tests/test_uk.py b/tests/test_uk.py index 3f57eb2d..4dcd7cb1 100644 --- a/tests/test_uk.py +++ b/tests/test_uk.py @@ -280,7 +280,8 @@ (1001, "однієї тисячи одного"), (2012, "двох тисяч дванадцяти"), (12519, "дванадцяти тисяч п'ятиста дев'ятнадцяти"), - (12519.85, "дванадцяти тисяч п'ятиста дев'ятнадцяти кома вісімдесяти п'яти"), + (12519.85, "дванадцяти тисяч п'ятиста дев'ятнадцяти " + "кома вісімдесяти п'яти"), (-260000, "мінус двохста шістдесяти тисяч"), (1000000, "одного мільйона"), (1000000000, "одного мільярда"), @@ -376,7 +377,8 @@ (1001, "одній тисячі одному"), (2012, "двом тисячам дванадцяти"), (12519, "дванадцяти тисячам п'ятистам дев'ятнадцяти"), - (12519.85, "дванадцяти тисячам п'ятистам дев'ятнадцяти кома вісімдесяти п'яти"), + (12519.85, "дванадцяти тисячам п'ятистам дев'ятнадцяти " + "кома вісімдесяти п'яти"), (-260000, "мінус двомстам шістдесяти тисячам"), (1000000, "одному мільйону"), (1000000000, "одному мільярду"), @@ -568,12 +570,14 @@ (1001, "однією тисячею одним"), (2012, "двома тисячами дванадцятьма"), (12519, "дванадцятьма тисячами п'ятьмастами дев'ятнадцятьма"), - (12519.85, "дванадцятьма тисячами п'ятьмастами дев'ятнадцятьма кома вісімдесятьма п'ятьма"), + (12519.85, "дванадцятьма тисячами п'ятьмастами дев'ятнадцятьма " + "кома вісімдесятьма п'ятьма"), (-260000, "мінус двомастами шістдесятьма тисячами"), (1000000, "одним мільйоном"), (1000000000, "одним мільярдом"), (1234567890, "одним мільярдом двомастами тридцятьма чотирма мільйонами " - "п'ятьмастами шістдесятьма сьома тисячами восьмастами дев'яностами"), + "п'ятьмастами шістдесятьма сьома тисячами восьмастами " + "дев'яностами"), (1000000000000, "одним трильйоном"), (1000000000000000, "одним квадрильйоном"), (1000000000000000000, "одним квінтильйоном"), @@ -589,12 +593,13 @@ "дев'ятьма мільярдів восьмастами вісімдесятьма трьома мільйонами " "дев'ятьмастами однією тисячею шістьмастами сімдесятьма шістьма"), (719094234693663034822824384220291, - "сьомастами дев'ятнадцятьма нонільйонів дев'яностами чотирма октильйонами " - "двомастами тридцятьма чотирма септильйонами шістьмастами дев'яностами трьома " - "секстильйонами шістьмастами шістдесятьма трьома квінтильйонами тридцятьма " - "чотирма квадрильйонами восьмастами двадцятьма двома трильйонами восьмастами " - "двадцятьма чотирма мільярдами трьомастами вісімдесятьма чотирма мільйонами " - "двомастами двадцятьма тисячами двомастами дев'яностами одним") + "сьомастами дев'ятнадцятьма нонільйонів дев'яностами чотирма " + "октильйонами двомастами тридцятьма чотирма септильйонами шістьмастами " + "дев'яностами трьома секстильйонами шістьмастами шістдесятьма трьома " + "квінтильйонами тридцятьма чотирма квадрильйонами восьмастами двадцятьма " + "двома трильйонами восьмастами двадцятьма чотирма мільярдами трьомастами " + "вісімдесятьма чотирма мільйонами двомастами двадцятьма тисячами " + "двомастами дев'яностами одним") ) TEST_CASES_CARDINAL_LOCATIVE = ( @@ -664,7 +669,8 @@ (1001, "одній тисячі одному"), (2012, "двох тисячах дванадцяти"), (12519, "дванадцяти тисячах п'ятистах дев'ятнадцяти"), - (12519.85, "дванадцяти тисячах п'ятистах дев'ятнадцяти кома вісімдесяти п'яти"), + (12519.85, "дванадцяти тисячах п'ятистах дев'ятнадцяти " + "кома вісімдесяти п'яти"), (-260000, "мінус двохстах шістдесяти тисячах"), (1000000, "одному мільйоні"), (1000000000, "одному мільярді"), @@ -3025,42 +3031,49 @@ class Num2WordsUKTest(TestCase): - + def test_to_cardinal(self): for test in TEST_CASES_CARDINAL: self.assertEqual(num2words(test[0], lang='uk'), test[1]) def test_to_cardinal_feminine(self): for test in TEST_CASES_CARDINAL_FEMININE: - self.assertEqual(num2words(test[0], lang='uk', gender='feminine'), test[1]) + word = num2words(test[0], lang='uk', gender='feminine') + self.assertEqual(word, test[1]) def test_to_cardinal_nominative(self): for test in TEST_CASES_CARDINAL: - self.assertEqual(num2words(test[0], lang='uk', case='nominative'), test[1]) + word = num2words(test[0], lang='uk', case='nominative') + self.assertEqual(word, test[1]) def test_to_cardinal_genitive(self): for test in TEST_CASES_CARDINAL_GENITIVE: - self.assertEqual(num2words(test[0], lang='uk', case='genitive'), test[1]) + word = num2words(test[0], lang='uk', case='genitive') + self.assertEqual(word, test[1]) def test_to_cardinal_dative(self): self.maxDiff = None for test in TEST_CASES_CARDINAL_DATIVE: - self.assertEqual(num2words(test[0], lang='uk', case='dative'), test[1]) + word = num2words(test[0], lang='uk', case='dative') + self.assertEqual(word, test[1]) def test_to_cardinal_accusative(self): self.maxDiff = None for test in TEST_CASES_CARDINAL_ACCUSATIVE: - self.assertEqual(num2words(test[0], lang='uk', case='accusative'), test[1]) + word = num2words(test[0], lang='uk', case='accusative') + self.assertEqual(word, test[1]) def test_to_cardinal_instrumental(self): self.maxDiff = None for test in TEST_CASES_CARDINAL_INSTRUMENTAL: - self.assertEqual(num2words(test[0], lang='uk', case='instrumental'), test[1]) + word = num2words(test[0], lang='uk', case='instrumental') + self.assertEqual(word, test[1]) def test_to_cardinal_locative(self): self.maxDiff = None for test in TEST_CASES_CARDINAL_LOCATIVE: - self.assertEqual(num2words(test[0], lang='uk', case='locative'), test[1]) + word = num2words(test[0], lang='uk', case='locative') + self.assertEqual(word, test[1]) def test_to_ordinal(self): for test in TEST_CASES_ORDINAL: From 2da1f924dfd49c57a9fa841bd39a6583564badbd Mon Sep 17 00:00:00 2001 From: Michal Juranyi Date: Sun, 3 Sep 2023 20:39:47 +0200 Subject: [PATCH 37/37] Add Slovak language support Signed-off-by: Michal Juranyi --- num2words/__init__.py | 5 +- num2words/lang_SK.py | 160 ++++++++++++++++++++++++++++++++++++++++++ tests/test_sk.py | 98 ++++++++++++++++++++++++++ 3 files changed, 261 insertions(+), 2 deletions(-) create mode 100644 num2words/lang_SK.py create mode 100644 tests/test_sk.py diff --git a/num2words/__init__.py b/num2words/__init__.py index 0942928f..53a28591 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -23,8 +23,8 @@ lang_FR_BE, lang_FR_CH, lang_FR_DZ, lang_HE, lang_HU, lang_ID, lang_IS, lang_IT, lang_JA, lang_KN, lang_KO, lang_KZ, lang_LT, lang_LV, lang_NL, lang_NO, lang_PL, lang_PT, lang_PT_BR, - lang_RO, lang_RU, lang_SL, lang_SR, lang_SV, lang_TE, lang_TG, - lang_TH, lang_TR, lang_UK, lang_VI) + lang_RO, lang_RU, lang_SK, lang_SL, lang_SR, lang_SV, lang_TE, + lang_TG, lang_TH, lang_TR, lang_UK, lang_VI) CONVERTER_CLASSES = { 'am': lang_AM.Num2Word_AM(), @@ -57,6 +57,7 @@ 'pl': lang_PL.Num2Word_PL(), 'ro': lang_RO.Num2Word_RO(), 'ru': lang_RU.Num2Word_RU(), + 'sk': lang_SK.Num2Word_SK(), 'sl': lang_SL.Num2Word_SL(), 'sr': lang_SR.Num2Word_SR(), 'sv': lang_SV.Num2Word_SV(), diff --git a/num2words/lang_SK.py b/num2words/lang_SK.py new file mode 100644 index 00000000..35ece024 --- /dev/null +++ b/num2words/lang_SK.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved. + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +from __future__ import unicode_literals + +from .base import Num2Word_Base +from .utils import get_digits, splitbyx + +ZERO = ('nula',) + +ONES = { + 1: ('jeden', 'jeden', set()), + 2: ('dva', 'dve', {1, 3, 5, 7, 9}), + 3: ('tri', 'tri', set()), + 4: ('štyri', 'štyri', set()), + 5: ('päť', 'päť', set()), + 6: ('šesť', 'šesť', set()), + 7: ('sedem', 'sedem', set()), + 8: ('osem', 'osem', set()), + 9: ('deväť', 'deväť', set()), +} + +TENS = { + 0: ('desať',), + 1: ('jedenásť',), + 2: ('dvanásť',), + 3: ('trinásť',), + 4: ('štrnásť',), + 5: ('pätnásť',), + 6: ('šestnásť',), + 7: ('sedemnásť',), + 8: ('osemnásť',), + 9: ('devätnásť',), +} + +TWENTIES = { + 2: ('dvadsať',), + 3: ('tridsať',), + 4: ('štyridsať',), + 5: ('päťdesiat',), + 6: ('šesťdesiat',), + 7: ('sedemdesiat',), + 8: ('osemdesiat',), + 9: ('deväťdesiat',), +} + +HUNDREDS = { + 1: ('sto',), + 2: ('dvesto',), + 3: ('tristo',), + 4: ('štyristo',), + 5: ('päťsto',), + 6: ('šesťsto',), + 7: ('sedemsto',), + 8: ('osemsto',), + 9: ('deväťsto',), +} + +THOUSANDS = { + 1: ('tisíc', 'tisíc', 'tisíc'), # 10^3 + 2: ('milión', 'milióny', 'miliónov'), # 10^6 + 3: ('miliarda', 'miliardy', 'miliárd'), # 10^9 + 4: ('bilión', 'bilióny', 'biliónov'), # 10^12 + 5: ('biliarda', 'biliardy', 'biliárd'), # 10^15 + 6: ('trilión', 'trilióny', 'triliónov'), # 10^18 + 7: ('triliarda', 'triliardy', 'triliárd'), # 10^21 + 8: ('kvadrilión', 'kvadrilióny', 'kvadriliónov'), # 10^24 + 9: ('kvadriliarda', 'kvadriliardy', 'kvadriliárd'), # 10^27 + 10: ('kvintilión', 'kvintillióny', 'kvintiliónov'), # 10^30 +} + + +class Num2Word_SK(Num2Word_Base): + CURRENCY_FORMS = { + 'EUR': ( + ('euro', 'eurá', 'eur'), ('cent', 'centy', 'centov') + ), + } + + def setup(self): + self.negword = "mínus" + self.pointword = "celých" + + def to_cardinal(self, number): + n = str(number).replace(',', '.') + if '.' in n: + left, right = n.split('.') + leading_zero_count = len(right) - len(right.lstrip('0')) + decimal_part = ((ZERO[0] + ' ') * leading_zero_count + + self._int2word(int(right))) + return u'%s %s %s' % ( + self._int2word(int(left)), + self.pointword, + decimal_part + ) + else: + return self._int2word(int(n)) + + def pluralize(self, n, forms): + if n == 1: + form = 0 + elif 0 < n < 5: + form = 1 + else: + form = 2 + return forms[form] + + def to_ordinal(self, value): + raise NotImplementedError() + + def _int2word(self, n): + if n == 0: + return ZERO[0] + + words = [] + chunks = list(splitbyx(str(n), 3)) + i = len(chunks) + for x in chunks: + i -= 1 + + if x == 0: + continue + + n1, n2, n3 = get_digits(x) + + word_chunk = [] + + if n3 > 0: + word_chunk.append(HUNDREDS[n3][0]) + + if n2 > 1: + word_chunk.append(TWENTIES[n2][0]) + + if n2 == 1: + word_chunk.append(TENS[n1][0]) + elif n1 > 0 and not (i > 0 and x == 1): + if n2 == 0 and n3 == 0 and i in ONES[n1][2]: + word_chunk.append(ONES[n1][1]) + else: + word_chunk.append(ONES[n1][0]) + if i > 1 and word_chunk: + word_chunk.append(' ') + if i > 0: + word_chunk.append(self.pluralize(x, THOUSANDS[i])) + words.append(''.join(word_chunk)) + + return ' '.join(words[:-1]) + ''.join(words[-1:]) diff --git a/tests/test_sk.py b/tests/test_sk.py new file mode 100644 index 00000000..314a7c2b --- /dev/null +++ b/tests/test_sk.py @@ -0,0 +1,98 @@ + +# -*- coding: utf-8 -*- +# Copyright (c) 2003, Taro Ogawa. All Rights Reserved. +# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved. + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +from __future__ import unicode_literals + +from unittest import TestCase + +from num2words import num2words + + +class Num2WordsSKTest(TestCase): + def test_cardinal(self): + self.assertEqual(num2words(100, lang='sk'), "sto") + self.assertEqual(num2words(101, lang='sk'), "stojeden") + self.assertEqual(num2words(110, lang='sk'), "stodesať") + self.assertEqual(num2words(115, lang='sk'), "stopätnásť") + self.assertEqual(num2words(123, lang='sk'), "stodvadsaťtri") + self.assertEqual(num2words(1000, lang='sk'), "tisíc") + self.assertEqual(num2words(1001, lang='sk'), "tisícjeden") + self.assertEqual(num2words(2012, lang='sk'), "dvetisícdvanásť") + self.assertEqual( + num2words(10.02, lang='sk'), + "desať celých nula dva" + ) + self.assertEqual( + num2words(15.007, lang='sk'), + "pätnásť celých nula nula sedem" + ) + self.assertEqual( + num2words(12519.85, lang='sk'), + "dvanásťtisícpäťstodevätnásť celých osemdesiatpäť" + ) + self.assertEqual( + num2words(123.50, lang='sk'), + "stodvadsaťtri celých päť" + ) + self.assertEqual( + num2words(1234567890, lang='sk'), + "miliarda dvestotridsaťštyri miliónov päťstošesťdesiat" + "sedemtisícosemstodeväťdesiat" + ) + self.assertEqual( + num2words(215461407892039002157189883901676, lang='sk'), + "dvestopätnásť kvintiliónov štyristošesťdesiatjeden kvadriliárd " + "štyristosedem kvadriliónov osemstodeväťdesiatdva triliárd " + "tridsaťdeväť triliónov dve biliardy stopäťdesiatsedem biliónov " + "stoosemdesiatdeväť miliárd osemstoosemdesiattri miliónov " + "deväťstojedentisícšesťstosedemdesiatšesť" + ) + self.assertEqual( + num2words(719094234693663034822824384220291, lang='sk'), + "sedemstodevätnásť kvintiliónov deväťdesiatštyri kvadriliárd " + "dvestotridsaťštyri kvadriliónov šesťstodeväťdesiattri triliárd " + "šesťstošesťdesiattri triliónov tridsaťštyri biliárd " + "osemstodvadsaťdva biliónov osemstodvadsaťštyri miliárd " + "tristoosemdesiatštyri miliónov " + "dvestodvadsaťtisícdvestodeväťdesiatjeden" + ) + + def test_to_ordinal(self): + # @TODO: implement to_ordinal + with self.assertRaises(NotImplementedError): + num2words(1, lang='sk', to='ordinal') + + def test_currency(self): + self.assertEqual( + num2words(10.0, lang='sk', to='currency', currency='EUR'), + "desať eur, nula centov") + self.assertEqual( + num2words(1234.56, lang='sk', to='currency', currency='EUR'), + "tisícdvestotridsaťštyri eur, päťdesiatšesť centov") + self.assertEqual( + num2words(101.11, lang='sk', to='currency', currency='EUR', + separator=' a'), + "stojeden eur a jedenásť centov") + self.assertEqual( + num2words(-12519.85, lang='sk', to='currency', cents=False), + "mínus dvanásťtisícpäťstodevätnásť eur, 85 centov" + ) + self.assertEqual( + num2words(19.50, lang='sk', to='currency', cents=False), + "devätnásť eur, 50 centov" + )