diff --git a/CHANGES.rst b/CHANGES.rst index 9f3a7b2..4a1d1e1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,21 @@ Changelog ========= +Version 0.5.12 -- 2022/08/19 +---------------------------- + +* Support Japanese Reiwa (令和/れいわ) era. (#412) +* Add basic farsi support (#354) +* Added Tajik language support (#406) +* Fix Amharic language support (#465) +* Fix Hebrew pluralize and implement to_currency (#330) +* Add support to translate some currencies in italian language (#434) +* Fix Polish twenties (#345) +* Add uzs for ru and en (#422) +* Added support for Esperanto numbers. (#387) +* [ADD] to ordinal number for Turkish (#468) +* Fix zeroth in Dutch to nulde fixing (#326) + Version 0.5.11 -- 2022/08/03 ---------------------------- @@ -77,9 +92,9 @@ Version 0.5.7 -- 2018/06/27 * Add Finnish localization. (#170) * Add Japanese localization. (#171) * Add belgian-french localization. (#151) -* Add Czech localization. (#154) +* Add Czech localization. (#154) * Add Thai localization. (#139) -* Improve English localization. (#144) +* Improve English localization. (#144) * Improve Spanish localization. (#167) * Improve Italian localization. (#143) * Improve documentation. (#155, #145, #174) diff --git a/README.rst b/README.rst index 9978a20..2a8f99f 100644 --- a/README.rst +++ b/README.rst @@ -76,6 +76,7 @@ Besides the numerical argument, there are two main optional arguments. **lang:** The language in which to convert the number. Supported values are: * ``en`` (English, default) +* ``am`` (Amharic) * ``ar`` (Arabic) * ``cz`` (Czech) * ``de`` (German) @@ -86,6 +87,7 @@ Besides the numerical argument, there are two main optional arguments. * ``es_CO`` (Spanish - Colombia) * ``es_VE`` (Spanish - Venezuela) * ``eu`` (EURO) +* ``fa`` (Farsi) * ``fi`` (Finnish) * ``fr`` (French) * ``fr_CH`` (French - Switzerland) @@ -111,6 +113,7 @@ Besides the numerical argument, there are two main optional arguments. * ``ro`` (Romanian) * ``ru`` (Russian) * ``te`` (Telugu) +* ``tg`` (Tajik) * ``tr`` (Turkish) * ``th`` (Thai) * ``vi`` (Vietnamese) @@ -148,4 +151,4 @@ added Lithuanian support, but didn't take over maintenance of the project. I am thus basing myself on Marius Grigaitis' improvements and re-publishing ``pynum2word`` as ``num2words``. -Virgil Dupras, Savoir-faire Linux +Virgil Dupras, Savoir-faire Linux \ No newline at end of file diff --git a/bin/num2words b/bin/num2words index 2a20b22..26990c0 100755 --- a/bin/num2words +++ b/bin/num2words @@ -34,7 +34,7 @@ Options: -t --to= Output converter [default: cardinal]. -h --help Show this message. -v --version Show version. - + Examples: $ num2words 10001 ten thousand and one @@ -55,7 +55,7 @@ import sys from docopt import docopt import num2words -__version__ = "0.5.11" +__version__ = "0.5.12" __license__ = "LGPL" diff --git a/num2words/__init__.py b/num2words/__init__.py index a8c4737..fb9f7e9 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -17,25 +17,29 @@ from __future__ import unicode_literals -from . import (lang_AR, lang_CZ, lang_DE, lang_DK, lang_EN, lang_EN_IN, - lang_ES, lang_ES_CO, lang_ES_NI, lang_ES_VE, lang_FI, lang_FR, - lang_FR_BE, lang_FR_CH, lang_FR_DZ, lang_HE, lang_HU, lang_ID, - 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_TH, lang_TR, - lang_UK, lang_VI) +from . import (lang_AM, lang_AR, 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_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(), 'ar': lang_AR.Num2Word_AR(), 'cz': lang_CZ.Num2Word_CZ(), 'en': lang_EN.Num2Word_EN(), 'en_IN': lang_EN_IN.Num2Word_EN_IN(), + 'fa': lang_FA.Num2Word_FA(), 'fr': lang_FR.Num2Word_FR(), 'fr_CH': lang_FR_CH.Num2Word_FR_CH(), 'fr_BE': lang_FR_BE.Num2Word_FR_BE(), 'fr_DZ': lang_FR_DZ.Num2Word_FR_DZ(), 'de': lang_DE.Num2Word_DE(), 'fi': lang_FI.Num2Word_FI(), + 'eo': lang_EO.Num2Word_EO(), 'es': lang_ES.Num2Word_ES(), 'es_CO': lang_ES_CO.Num2Word_ES_CO(), 'es_NI': lang_ES_NI.Num2Word_ES_NI(), @@ -60,6 +64,7 @@ CONVERTER_CLASSES = { 'he': lang_HE.Num2Word_HE(), 'it': lang_IT.Num2Word_IT(), 'vi': lang_VI.Num2Word_VI(), + 'tg': lang_TG.Num2Word_TG(), 'th': lang_TH.Num2Word_TH(), 'tr': lang_TR.Num2Word_TR(), 'nl': lang_NL.Num2Word_NL(), diff --git a/num2words/lang_AM.py b/num2words/lang_AM.py new file mode 100644 index 0000000..77922db --- /dev/null +++ b/num2words/lang_AM.py @@ -0,0 +1,129 @@ +# -*- 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 division, print_function, unicode_literals + +from . import lang_EU + + +class Num2Word_AM(lang_EU.Num2Word_EU): + CURRENCY_FORMS = {'ETB': (('ብር', 'ብር'), ('ሳንቲም', 'ሳንቲም'))} + + GIGA_SUFFIX = 'ቢሊዮን' + MEGA_SUFFIX = 'ሚሊዮን' + + def set_high_numwords(self, high): + cap = 3 * (len(high) + 1) + + for word, n in zip(high, range(cap, 5, -3)): + if n == 9: + self.cards[10 ** n] = word + self.GIGA_SUFFIX + else: + self.cards[10 ** n] = word + self.MEGA_SUFFIX + + def setup(self): + super(Num2Word_AM, self).setup() + + self.negword = 'አሉታዊ ' + self.pointword = 'ነጥብ' + self.exclude_title = ['እና', 'ነጥብ', 'አሉታዊ'] + + self.mid_numwords = [(1000, 'ሺህ'), (100, 'መቶ'), (90, 'ዘጠና'), + (80, 'ሰማኒያ'), (70, 'ሰባ'), (60, 'ስድሳ'), + (50, 'አምሳ'), (40, 'አርባ'), (30, 'ሠላሳ')] + self.low_numwords = ['ሃያ', 'አሥራ ዘጠኝ', 'አሥራ ስምንት', 'አሥራ ሰባት', + 'አስራ ስድስት', 'አሥራ አምስት', 'አሥራ አራት', 'አሥራ ሦስት', + 'አሥራ ሁለት', 'አሥራ አንድ', 'አሥር', 'ዘጠኝ', 'ስምንት', + 'ሰባት', 'ስድስት', 'አምስት', 'አራት', 'ሦስት', 'ሁለት', + 'አንድ', 'ዜሮ'] + self.ords = {'አንድ': 'አንደኛ', + 'ሁለት': 'ሁለተኛ', + 'ሦስት': 'ሦስተኛ', + 'አራት': 'አራተኛ', + 'አምስት': 'አምስተኛ', + 'ስድስት': 'ስድስተኛ', + 'ሰባት': 'ሰባተኛ', + 'ስምንት': 'ስምንተኛ', + 'ዘጠኝ': 'ዘጠነኛ', + 'አሥር': 'አሥረኛ', + 'አሥራ አንድ': 'አሥራ አንደኛ', + 'አሥራ ሁለት': 'አሥራ ሁለተኛ', + 'አሥራ ሦስት': 'አሥራ ሦስተኛ', + 'አሥራ አራት': 'አሥራ አራተኛ', + 'አሥራ አምስት': 'አሥራ አምስተኛ', + 'አሥራ ስድስት': 'አሥራ ስድስተኛ', + 'አሥራ ሰባት': 'አሥራ ሰባተኛ', + 'አሥራ ስምንት': 'አሥራ ስምንተኛ', + 'አሥራ ዘጠኝ': 'አሥራ ዘጠነኛ'} + + def to_cardinal(self, value): + try: + assert int(value) == value + except (ValueError, TypeError, AssertionError): + return self.to_cardinal_float(value) + + out = '' + if value >= self.MAXVAL: + raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL)) + + if value == 100: + return self.title(out + 'መቶ') + else: + val = self.splitnum(value) + words, num = self.clean(val) + return self.title(out + words) + + def merge(self, lpair, rpair): + ltext, lnum = lpair + rtext, rnum = rpair + if lnum == 1 and rnum < 100: + return rtext, rnum + elif 100 > lnum > rnum: + return '%s %s' % (ltext, rtext), lnum + rnum + elif lnum >= 100 > rnum: + return '%s %s' % (ltext, rtext), lnum + rnum + elif rnum > lnum: + return '%s %s' % (ltext, rtext), lnum * rnum + + def to_ordinal(self, value): + self.verify_ordinal(value) + outwords = self.to_cardinal(value).split(' ') + lastwords = outwords[-1].split('-') + lastword = lastwords[-1].lower() + try: + lastword = self.ords[lastword] + except KeyError: + lastword += 'ኛ' + lastwords[-1] = self.title(lastword) + outwords[-1] = ' '.join(lastwords) + return ' '.join(outwords) + + def to_ordinal_num(self, value): + self.verify_ordinal(value) + return '%s%s' % (value, self.to_ordinal(value)[-1:]) + + def to_currency(self, val, currency='ብር', cents=True, separator=' ከ', + adjective=True): + result = super(Num2Word_AM, self).to_currency( + val, currency=currency, cents=cents, separator=separator, + adjective=adjective) + return result + + def to_year(self, val, longval=True): + if not (val // 100) % 10: + return self.to_cardinal(val) + return self.to_splitnum(val, hightxt='መቶ', longval=longval) diff --git a/num2words/lang_EO.py b/num2words/lang_EO.py new file mode 100644 index 0000000..473e74d --- /dev/null +++ b/num2words/lang_EO.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 .base import Num2Word_Base + + +class Num2Word_EO(Num2Word_Base): + CURRENCY_FORMS = { + "EUR": (("eŭro", "eŭroj"), ("centimo", "centimoj")), + "USD": (("dolaro", "dolaroj"), ("cendo", "cendoj")), + "FRF": (("franko", "frankoj"), ("centimo", "centimoj")), + "GBP": (("pundo", "pundoj"), ("penco", "pencoj")), + "CNY": (("juano", "juanoj"), ("feno", "fenoj")), + } + + GIGA_SUFFIX = "iliardo" + MEGA_SUFFIX = "iliono" + + def set_high_numwords(self, high): + cap = 3 + 6 * len(high) + + for word, n in zip(high, range(cap, 3, -6)): + if self.GIGA_SUFFIX: + self.cards[10 ** n] = word + self.GIGA_SUFFIX + + if self.MEGA_SUFFIX: + self.cards[10 ** (n - 3)] = word + self.MEGA_SUFFIX + + def gen_high_numwords(self, units, tens, lows): + out = [u + t for t in tens for u in units] + out.reverse() + return out + lows + + def setup(self): + lows = ["naŭ", "ok", "sep", "ses", "kvin", "kvar", "tr", "b", "m"] + units = ["", "un", "duo", "tre", "kvatuor", + "kvin", "seks", "septen", "okto", "novem"] + tens = ["dek", "vigint", "trigint", "kvadragint", "kvinkvagint", + "seksagint", "septuagint", "oktogint", "nonagint"] + + self.high_numwords = ["cent"] + self.gen_high_numwords(units, tens, + lows) + + self.negword = "minus " + 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." + ) + self.exclude_title = ["kaj", "komo", "minus"] + self.mid_numwords = [(1000, "mil"), (100, "cent"), (90, "naŭdek"), + (80, "okdek"), (70, "sepdek"), (60, "sesdek"), + (50, "kvindek"), (40, "kvardek"), (30, "tridek")] + self.low_numwords = ["dudek", "dek naŭ", "dek ok", "dek sep", + "dek ses", "dek kvin", "dek kvar", "dek tri", + "dek du", "dek unu", "dek", "naŭ", "ok", "sep", + "ses", "kvin", "kvar", "tri", "du", "unu", "nul"] + self.ords = { + "unu": "unua", + "du": "dua", + "tri": "tria", + "kvar": "kvara", + "kvin": "kvina", + "ses": "sesa", + "sep": "sepa", + "ok": "oka", + "naŭ": "naŭa", + "dek": "deka" + } + + def merge(self, curr, next): + ctext, cnum, ntext, nnum = curr + next + if cnum == 1 and nnum < 1000000: + return next + + if nnum >= 10**6 and cnum > 1: + return ("%s %sj" % (ctext, ntext), cnum + nnum) + + if nnum == 100: + return ("%s%s" % (ctext, ntext), cnum + nnum) + + return ("%s %s" % (ctext, ntext), cnum + nnum) + + def to_ordinal(self, value): + self.verify_ordinal(value) + word = self.to_cardinal(value) + for src, repl in self.ords.items(): + if word.endswith(src): + word = word[:-len(src)] + repl + return word + + if word.endswith("o"): + word = word[:-1] + "a" + elif word.endswith("oj"): + word = word[:-2] + "a" + else: + word = word + "a" + return word + + def to_ordinal_num(self, value): + self.verify_ordinal(value) + out = str(value) + out += "a" + return out + + def to_currency(self, val, currency="EUR", cents=True, separator=" kaj", + adjective=False): + result = super(Num2Word_EO, self).to_currency( + val, currency=currency, cents=cents, separator=separator, + adjective=adjective) + return result + + def pluralize(self, n, forms): + form = 0 if n <= 1 else 1 + return forms[form] diff --git a/num2words/lang_EU.py b/num2words/lang_EU.py index 09d39c1..529547a 100644 --- a/num2words/lang_EU.py +++ b/num2words/lang_EU.py @@ -43,7 +43,8 @@ class Num2Word_EU(Num2Word_Base): 'MXN': (('peso', 'pesos'), GENERIC_CENTS), 'RON': (('leu', 'lei', 'de lei'), ('ban', 'bani', 'de bani')), 'INR': (('rupee', 'rupees'), ('paisa', 'paise')), - 'HUF': (('forint', 'forint'), ('fillér', 'fillér')) + 'HUF': (('forint', 'forint'), ('fillér', 'fillér')), + 'UZS': (('sum', 'sums'), ('tiyin', 'tiyins')) } CURRENCY_ADJECTIVES = { @@ -56,7 +57,8 @@ class Num2Word_EU(Num2Word_Base): 'MXN': 'Mexican', 'RON': 'Romanian', 'INR': 'Indian', - 'HUF': 'Hungarian' + 'HUF': 'Hungarian', + 'UZS': 'Uzbekistan' } GIGA_SUFFIX = "illiard" diff --git a/num2words/lang_FA.py b/num2words/lang_FA.py new file mode 100644 index 0000000..96e9e52 --- /dev/null +++ b/num2words/lang_FA.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2003, Taro Ogawa. All Rights Reserved. +# 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. + +# 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 decimal import Decimal +from math import floor + +farsiOnes = [ + "", "یک", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت", + "نه", + "ده", + "یازده", + "دوازده", + "سیزده", + "چهارده", + "پونزده", + "شونزده", + "هیفده", + "هیجده", + "نوزده", +] + +farsiTens = [ + "", + "ده", + "بیست", + "سی", + "چهل", + "پنجاه", + "شصت", + "هفتاد", + "هشتاد", + "نود", +] + +farsiHundreds = [ + "", + "صد", + "دویست", + "سیصد", + "چهارصد", + "پانصد", + "ششصد", + "هفتصد", + "هشتصد", + "نهصد", +] + +farsiBig = [ + '', + ' هزار', + ' میلیون', + " میلیارد", + ' تریلیون', + " تریلیارد", +] + +farsiFrac = ["", "دهم", "صدم"] +farsiFracBig = ["", "هزارم", "میلیونیم", "میلیاردیم"] + +farsiSeperator = ' و ' + + +class Num2Word_FA(object): + errmsg_too_big = "Too large" + max_num = 10 ** 36 + + def __init__(self): + self.number = 0 + + def float2tuple(self, value): + pre = int(value) + + # Simple way of finding decimal places to update the precision + self.precision = abs(Decimal(str(value)).as_tuple().exponent) + + post = abs(value - pre) * 10**self.precision + if abs(round(post) - post) < 0.01: + # We generally floor all values beyond our precision (rather than + # rounding), but in cases where we have something like 1.239999999, + # which is probably due to python's handling of floats, we actually + # want to consider it as 1.24 instead of 1.23 + post = int(round(post)) + else: + post = int(floor(post)) + return pre, post, self.precision + + def cardinal3(self, number): + if (number < 19): + return farsiOnes[number] + if (number < 100): + x, y = divmod(number, 10) + if y == 0: + return farsiTens[x] + return farsiTens[x] + farsiSeperator + farsiOnes[y] + x, y = divmod(number, 100) + if y == 0: + return farsiHundreds[x] + return farsiHundreds[x] + farsiSeperator + self.cardinal3(y) + + def cardinalPos(self, number): + x = number + res = '' + for b in farsiBig: + x, y = divmod(x, 1000) + if (y == 0): + continue + yx = self.cardinal3(y) + b + if b == ' هزار' and y == 1: + yx = 'هزار' + if (res == ''): + res = yx + else: + res = yx + farsiSeperator + res + return res + + def fractional(self, number, level): + if (number == 5): + return "نیم" + x = self.cardinalPos(number) + ld3, lm3 = divmod(level, 3) + ltext = (farsiFrac[lm3] + " " + farsiFracBig[ld3]).strip() + return x + " " + ltext + + def to_currency(self, value): + return self.to_cardinal(value) + " تومان" + + def to_ordinal(self, number): + r = self.to_cardinal(number) + 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): + return str(value)+"م" + + def to_cardinal(self, number): + if number < 0: + return "منفی " + self.to_cardinal(-number) + if (number == 0): + return "صفر" + x, y, level = self.float2tuple(number) + if y == 0: + return self.cardinalPos(x) + if x == 0: + return self.fractional(y, level) + return self.cardinalPos(x) + farsiSeperator + self.fractional(y, level) diff --git a/num2words/lang_HE.py b/num2words/lang_HE.py index 34b2ec8..f987192 100644 --- a/num2words/lang_HE.py +++ b/num2words/lang_HE.py @@ -81,11 +81,7 @@ AND = u'ו' def pluralize(n, forms): - # gettext implementation: - # (n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2) - - form = 0 if (n % 10 == 1 and n % 100 != 11) else 1 if n != 0 else 2 - + form = 1 if n == 0 else 0 if n == 1 else 1 return forms[form] @@ -140,17 +136,30 @@ def n2w(n): return int2word(int(n)) -def to_currency(n, currency='EUR', cents=True, separator=','): - raise NotImplementedError() - - class Num2Word_HE(Num2Word_Base): + CURRENCY_FORMS = { + 'NIS': (('שקל', 'שקלים'), ('אגורה', 'אגורות')), + 'EUR': (('אירו', 'אירו'), ('סנט', 'סנט')), + 'USD': (('דולר', 'דולרים'), ('סנט', 'סנט')), + } + def to_cardinal(self, number): return n2w(number) def to_ordinal(self, number): raise NotImplementedError() + def pluralize(self, n, forms): + return pluralize(n, forms) + + def to_currency(self, val, currency='NIS', cents=True, separator=' ו', + adjective=False): + result = super(Num2Word_HE, self).to_currency( + val, currency=currency, cents=cents, separator=separator, + adjective=adjective) + # In Hebrew the separator is along with the following word + return result.replace(" ו ", " ו") + if __name__ == '__main__': yo = Num2Word_HE() diff --git a/num2words/lang_IT.py b/num2words/lang_IT.py index 6966d73..d532f7f 100644 --- a/num2words/lang_IT.py +++ b/num2words/lang_IT.py @@ -16,6 +16,8 @@ from __future__ import unicode_literals +from .lang_EU import Num2Word_EU + # Globals # ------- @@ -43,57 +45,27 @@ EXPONENT_PREFIXES = [ ] -# Utils -# ===== - -def phonetic_contraction(string): - return (string - .replace("oo", "o") # ex. "centootto" - .replace("ao", "o") # ex. "settantaotto" - .replace("io", "o") # ex. "ventiotto" - .replace("au", "u") # ex. "trentauno" - .replace("iu", "u") # ex. "ventiunesimo" - ) - - -def exponent_length_to_string(exponent_length): - # We always assume `exponent` to be a multiple of 3. If it's not true, then - # Num2Word_IT.big_number_to_cardinal did something wrong. - prefix = EXPONENT_PREFIXES[exponent_length // 6] - if exponent_length % 6 == 0: - return prefix + "ilione" - else: - return prefix + "iliardo" - - -def accentuate(string): - # This is inefficient: it may do several rewritings when deleting - # half-sentence accents. However, it is the easiest method and speed is - # not crucial (duh), so... - return " ".join( - # Deletes half-sentence accents and accentuates the last "tre" - [w.replace("tré", "tre")[:-3] + "tré" - # We shouldn't accentuate a single "tre": is has to be a composite - # word. ~~~~~~~~~~ - if w[-3:] == "tre" and len(w) > 3 - # Deletes half-sentence accents anyway - # ~~~~~~~~~~~~~~~~~~~~~~ - else w.replace("tré", "tre") - for w in string.split() - ]) - - -def omitt_if_zero(number_to_string): - return "" if number_to_string == ZERO else number_to_string +GENERIC_DOLLARS = ('dollaro', 'dollari') +GENERIC_CENTS = ('centesimo', 'centesimi') +CURRENCIES_UNA = ('GBP') # Main class # ========== -class Num2Word_IT: +class Num2Word_IT(Num2Word_EU): + CURRENCY_FORMS = { + 'EUR': (('euro', 'euro'), GENERIC_CENTS), + 'USD': (GENERIC_DOLLARS, GENERIC_CENTS), + 'GBP': (('sterlina', 'sterline'), ('penny', 'penny')), + 'CNY': (('yuan', 'yuan'), ('fen', 'fen')), + } MINUS_PREFIX_WORD = "meno " FLOAT_INFIX_WORD = " virgola " + def setup(self): + Num2Word_EU.setup(self) + def __init__(self): pass @@ -206,3 +178,64 @@ class Num2Word_IT: if string[-3:] == "mil": string += "l" return string + "esimo" + + def to_currency(self, val, currency='EUR', cents=True, separator=' e', + adjective=False): + result = super(Num2Word_IT, self).to_currency( + val, currency=currency, cents=cents, separator=separator, + adjective=adjective) + # Handle exception. In italian language is "un euro", + # "un dollaro" etc. (not "uno euro", "uno dollaro"). + # There is an exception, some currencies need "una": + # e.g. "una sterlina" + if currency in CURRENCIES_UNA: + list_result = result.split(" ") + if list_result[0] == "uno": + list_result[0] = list_result[0].replace("uno", "una") + result = " ".join(list_result) + result = result.replace("uno", "un") + return result + +# Utils +# ===== + + +def phonetic_contraction(string): + return (string + .replace("oo", "o") # ex. "centootto" + .replace("ao", "o") # ex. "settantaotto" + .replace("io", "o") # ex. "ventiotto" + .replace("au", "u") # ex. "trentauno" + .replace("iu", "u") # ex. "ventiunesimo" + ) + + +def exponent_length_to_string(exponent_length): + # We always assume `exponent` to be a multiple of 3. If it's not true, then + # Num2Word_IT.big_number_to_cardinal did something wrong. + prefix = EXPONENT_PREFIXES[exponent_length // 6] + if exponent_length % 6 == 0: + return prefix + "ilione" + else: + return prefix + "iliardo" + + +def accentuate(string): + # This is inefficient: it may do several rewritings when deleting + # half-sentence accents. However, it is the easiest method and speed is + # not crucial (duh), so... + return " ".join( + # Deletes half-sentence accents and accentuates the last "tre" + [w.replace("tré", "tre")[:-3] + "tré" + # We shouldn't accentuate a single "tre": is has to be a composite + # word. ~~~~~~~~~~ + if w[-3:] == "tre" and len(w) > 3 + # Deletes half-sentence accents anyway + # ~~~~~~~~~~~~~~~~~~~~~~ + else w.replace("tré", "tre") + for w in string.split() + ]) + + +def omitt_if_zero(number_to_string): + return "" if number_to_string == ZERO else number_to_string diff --git a/num2words/lang_JA.py b/num2words/lang_JA.py index 58a2946..1e0e622 100644 --- a/num2words/lang_JA.py +++ b/num2words/lang_JA.py @@ -335,6 +335,7 @@ ERA_START = [ (1912, ("大正", "たいしょう")), (1926, ("昭和", "しょうわ")), (1989, ("平成", "へいせい")), + (2019, ("令和", "れいわ")), ] diff --git a/num2words/lang_NL.py b/num2words/lang_NL.py index 00d6c5b..7251e2a 100644 --- a/num2words/lang_NL.py +++ b/num2words/lang_NL.py @@ -76,7 +76,10 @@ class Num2Word_NL(Num2Word_EU): "zes", "vijf", "vier", "drie", "twee", "één", "nul"] - self.ords = {"één": "eerst", + # Wiktionary says it is "nulde", not "nulte" or "nule" + # https://en.wiktionary.org/wiki/nulde + self.ords = {"nul": "nuld", + "één": "eerst", "twee": "tweed", "drie": "derd", "vier": "vierd", diff --git a/num2words/lang_PL.py b/num2words/lang_PL.py index 1fb1fdc..ec7e6a2 100644 --- a/num2words/lang_PL.py +++ b/num2words/lang_PL.py @@ -80,7 +80,7 @@ TWENTIES = { 6: ('sześćdziesiąt',), 7: ('siedemdziesiąt',), 8: ('osiemdziesiąt',), - 9: ('dziewięćdzisiąt',), + 9: ('dziewięćdziesiąt',), } TWENTIES_ORDINALS = { diff --git a/num2words/lang_RU.py b/num2words/lang_RU.py index 8e6c875..166169c 100644 --- a/num2words/lang_RU.py +++ b/num2words/lang_RU.py @@ -113,6 +113,9 @@ class Num2Word_RU(Num2Word_Base): 'KZT': ( ('тенге', 'тенге', 'тенге'), ('тиын', 'тиына', 'тиынов') ), + 'UZS': ( + ('сум', 'сума', 'сумов'), ('тийин', 'тийина', 'тийинов') + ), } def setup(self): diff --git a/num2words/lang_TG.py b/num2words/lang_TG.py new file mode 100644 index 0000000..48291a2 --- /dev/null +++ b/num2words/lang_TG.py @@ -0,0 +1,149 @@ +# -*- 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 division, print_function, unicode_literals + +from . import lang_EU + +GENERIC_DOLLARS = ("доллар", "доллар") +GENERIC_CENTS = ("сент", "сент") + + +class Num2Word_TG(lang_EU.Num2Word_EU): + CURRENCY_FORMS = { + # repalced by EUR + "EUR": (("евро", "евро"), GENERIC_CENTS), + # replaced by EUR + "USD": (GENERIC_DOLLARS, GENERIC_CENTS), + "RUB": (("рубл", "рубл"), ("копейк", "копейк")), + "TJS": (("сомонӣ", "сомонӣ"), ("дирам", "дирам")), + } + + GIGA_SUFFIX = "иллиард" + MEGA_SUFFIX = "иллион" + + def set_high_numwords(self, high): + cap = 3 * (len(high) + 1) + + for word, n in zip(high, range(cap, 5, -3)): + if n == 9: + self.cards[10 ** n] = word + self.GIGA_SUFFIX + + else: + self.cards[10 ** n] = word + self.MEGA_SUFFIX + + def setup(self): + super(Num2Word_TG, self).setup() + + lows = ["квинт", "квадр", "тр", "м", "м"] + self.high_numwords = self.gen_high_numwords([], [], lows) + self.negword = "минус " + self.pointword = "нуқта" + self.exclude_title = ["ва", "минус", "нуқта"] + + self.mid_numwords = [ + (1000, "ҳазор"), + (100, "сад"), + (90, "навад"), + (80, "ҳаштод"), + (70, "ҳафтод"), + (60, "шаст"), + (50, "панҷоҳ"), + (40, "чил"), + (30, "си"), + ] + self.low_numwords = [ + "бист", + "нуздаҳ", + "ҳаждаҳ", + "ҳабдаҳ", + "шонздаҳ", + "понздаҳ", + "чордаҳ", + "сенздаҳ", + "дувоздаҳ", + "ёздаҳ", + "даҳ", + "нӯҳ", + "ҳашт", + "ҳафт", + "шаш", + "панҷ", + "чор", + "се", + "ду", + "як", + "сифр", + ] + + def to_cardinal(self, value): + try: + assert int(value) == value + except (ValueError, TypeError, AssertionError): + return self.to_cardinal_float(value) + + out = "" + if value < 0: + value = abs(value) + out = self.negword + + if value >= self.MAXVAL: + raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL)) + + if value == 100: + return self.title(out + "сад") + else: + val = self.splitnum(value) + words, num = self.clean(val) + return self.title(out + words) + + def merge(self, lpair, rpair): + ltext, lnum = lpair + rtext, rnum = rpair + if lnum == 1 and rnum < 100: + return (rtext, rnum) + elif 100 > lnum > rnum: + if ltext == "си": + return ("%sю %s" % (ltext, rtext), lnum + rnum) + elif ltext == "панҷоҳ": + return ("панҷову %s" % (rtext), lnum + rnum) + else: + return ("%sу %s" % (ltext, rtext), lnum + rnum) + elif lnum >= 100 > rnum: + return ("%sу %s" % (ltext, rtext), lnum + rnum) + elif rnum > lnum: + if ltext == "яксад" and rtext not in self.low_numwords: + return ("сад %s" % (rtext), lnum * rnum) + if rtext == "сад": + return ("%s%s" % (ltext, rtext), lnum * rnum) + else: + return ("%s %s" % (ltext, rtext), lnum * rnum) + return ("%sу %s" % (ltext, rtext), lnum + rnum) + + def to_ordinal(self, value): + self.verify_ordinal(value) + cardinal = self.to_cardinal(value) + outwords = cardinal.split(" ") + lastword = outwords[-1] + if lastword in ["ду", "се", "си"]: + return "%sюм" % (cardinal) + else: + return "%sум" % (cardinal) + + def to_ordinal_num(self, value): + self.verify_ordinal(value) + return "%s%s" % (value, self.to_ordinal(value)[-2:]) diff --git a/num2words/lang_TR.py b/num2words/lang_TR.py index 7fba871..ee1d6d4 100644 --- a/num2words/lang_TR.py +++ b/num2words/lang_TR.py @@ -806,6 +806,10 @@ class Num2Word_TR(Num2Word_Base): return wrd + def to_ordinal_num(self, value): + self.verify_ordinal(value) + return "%s%s" % (value, self.to_ordinal(value)[-4:]) + def to_splitnum(self, val): float_digits = str(int(val * 10 ** self.precision)) if not int(val) == 0: diff --git a/tests/test_am.py b/tests/test_am.py new file mode 100644 index 0000000..58709c3 --- /dev/null +++ b/tests/test_am.py @@ -0,0 +1,94 @@ +# -*- 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 Num2WordsAMTest(TestCase): + def test_cardinal(self): + self.assertEqual(num2words(100, lang='am'), 'መቶ') + self.assertEqual(num2words(100000, lang='am'), 'አንድ መቶ ሺህ') + self.assertEqual(num2words(101, lang='am'), 'አንድ መቶ አንድ') + + def test_and_join_199(self): + self.assertEqual(num2words(199, lang='am'), 'አንድ መቶ ዘጠና ዘጠኝ') + + def test_to_ordinal(self): + self.assertEqual( + num2words(1, lang='am', to='ordinal'), + 'አንደኛ' + ) + self.assertEqual( + num2words(13, lang='am', to='ordinal'), + 'አሥራ ሦስተኛ' + ) + self.assertEqual( + num2words(22, lang='am', to='ordinal'), + 'ሃያ ሁለተኛ' + ) + self.assertEqual( + num2words(10000, lang='am', to='ordinal'), + 'አሥር ሺህኛ' + ) + + def test_to_ordinal_num(self): + self.assertEqual(num2words(10, lang='am', to='ordinal_num'), '10ኛ') + self.assertEqual(num2words(21, lang='am', to='ordinal_num'), '21ኛ') + self.assertEqual(num2words(102, lang='am', to='ordinal_num'), '102ኛ') + + def test_cardinal_for_float_number(self): + self.assertEqual(num2words(12.5, lang='am'), 'አሥራ ሁለት ነጥብ አምስት') + self.assertEqual(num2words(12.51, lang='am'), 'አሥራ ሁለት ነጥብ አምስት አንድ') + self.assertEqual(num2words(12.53, lang='am'), 'አሥራ ሁለት ነጥብ አምስት ሦስት') + + def test_to_overflow(self): + with self.assertRaises(OverflowError): + num2words('1000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000' + '00000000000000000000000000000000', lang='am') + + def test_to_currency(self): + self.assertEqual( + num2words('38.4', lang='am', to='currency', cents=False, + currency='ETB'), 'ሠላሳ ስምንት ብር ከ 40 ሳንቲም' + ) + self.assertEqual( + num2words('0', lang='am', to='currency', separator=' እና', + cents=True, currency='ETB'), 'ዜሮ ብር እና ዜሮ ሳንቲም' + ) + + self.assertEqual( + num2words('1.50', lang='am', to='currency', cents=True, + currency='ETB'), 'አንድ ብር ከ አምሳ ሳንቲም' + ) + + def test_to_year(self): + self.assertEqual(num2words(1990, lang='am', to='year'), + 'አሥራ ዘጠኝ መቶ ዘጠና') + self.assertEqual(num2words(5555, lang='am', to='year'), + 'አምሳ አምስት መቶ አምሳ አምስት') + self.assertEqual(num2words(2017, lang='am', to='year'), + 'ሁለት ሺህ አሥራ ሰባት') + self.assertEqual(num2words(1066, lang='am', to='year'), + 'አንድ ሺህ ስድሳ ስድስት') + self.assertEqual(num2words(1865, lang='am', to='year'), + 'አሥራ ስምንት መቶ ስድሳ አምስት') diff --git a/tests/test_en.py b/tests/test_en.py index 6234ac9..e763841 100644 --- a/tests/test_en.py +++ b/tests/test_en.py @@ -131,6 +131,12 @@ class Num2WordsENTest(TestCase): "four pesos and one cent" ) + self.assertEqual( + num2words('2000.00', lang='en', to='currency', separator=' and', + cents=True, currency='UZS'), + "two thousand sums and zero tiyins" + ) + def test_to_year(self): # issue 141 # "e2 e2" diff --git a/tests/test_eo.py b/tests/test_eo.py new file mode 100644 index 0000000..64076ce --- /dev/null +++ b/tests/test_eo.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 + +TEST_CASES_CARDINAL = ( + (1, "unu"), + (2, "du"), + (3, "tri"), + (5.5, "kvin komo kvin"), + (11, "dek unu"), + (12, "dek du"), + (16, "dek ses"), + (17.42, "dek sep komo kvar du"), + (19, "dek naŭ"), + (20, "dudek"), + (21, "dudek unu"), + (26, "dudek ses"), + (27.312, "dudek sep komo tri unu du"), + (28, "dudek ok"), + (30, "tridek"), + (31, "tridek unu"), + (40, "kvardek"), + (44, "kvardek kvar"), + (50, "kvindek"), + (53.486, "kvindek tri komo kvar ok ses"), + (55, "kvindek kvin"), + (60, "sesdek"), + (67, "sesdek sep"), + (70, "sepdek"), + (79, "sepdek naŭ"), + (89, "okdek naŭ"), + (95, "naŭdek kvin"), + (100, "cent"), + (101, "cent unu"), + (199, "cent naŭdek naŭ"), + (203, "ducent tri"), + (287, "ducent okdek sep"), + (300.42, "tricent komo kvar du"), + (356, "tricent kvindek ses"), + (400, "kvarcent"), + (434, "kvarcent tridek kvar"), + (578, "kvincent sepdek ok"), + (689, "sescent okdek naŭ"), + (729, "sepcent dudek naŭ"), + (894, "okcent naŭdek kvar"), + (999, "naŭcent naŭdek naŭ"), + (1000, "mil"), + (1001, "mil unu"), + (1097, "mil naŭdek sep"), + (1104, "mil cent kvar"), + (1243, "mil ducent kvardek tri"), + (2385, "du mil tricent okdek kvin"), + (3766, "tri mil sepcent sesdek ses"), + (4196, "kvar mil cent naŭdek ses"), + (4196.42, "kvar mil cent naŭdek ses komo kvar du"), + (5846, "kvin mil okcent kvardek ses"), + (6459, "ses mil kvarcent kvindek naŭ"), + (7232, "sep mil ducent tridek du"), + (8569, "ok mil kvincent sesdek naŭ"), + (9539, "naŭ mil kvincent tridek naŭ"), + (1000000, "unu miliono"), + (1000001, "unu miliono unu"), + (4000000, "kvar milionoj"), + (4000004, "kvar milionoj kvar"), + (4300000, "kvar milionoj tricent mil"), + (80000000, "okdek milionoj"), + (300000000, "tricent milionoj"), + (10000000000000, "dek bilionoj"), + (10000000000010, "dek bilionoj dek"), + (100000000000000, "cent bilionoj"), + (1000000000000000000, "unu triliono"), + (1000000000000000000000, "unu triliardo"), + (10000000000000000000000000, "dek kvarilionoj") +) + +TEST_CASES_ORDINAL = ( + (1, "unua"), + (8, "oka"), + (12, "dek dua"), + (14, "dek kvara"), + (28, "dudek oka"), + (100, "centa"), + (1000, "mila"), + (1000000, "unu miliona"), + (1000000000000000, "unu biliarda"), + (1000000000000000000, "unu triliona") +) + +TEST_CASES_ORDINAL_NUM = ( + (1, "1a"), + (8, "8a"), + (11, "11a"), + (12, "12a"), + (14, "14a"), + (21, "21a"), + (28, "28a"), + (100, "100a"), + (101, "101a"), + (1000, "1000a"), + (1000000, "1000000a") +) + +TEST_CASES_TO_CURRENCY_EUR = ( + (1.00, "unu eŭro kaj nul centimo"), + (2.01, "du eŭroj kaj unu centimo"), + (8.10, "ok eŭroj kaj dek centimoj"), + (12.26, "dek du eŭroj kaj dudek ses centimoj"), + (21.29, "dudek unu eŭroj kaj dudek naŭ centimoj"), + (81.25, "okdek unu eŭroj kaj dudek kvin centimoj"), + (100.00, "cent eŭroj kaj nul centimo"), +) + +TEST_CASES_TO_CURRENCY_FRF = ( + (1.00, "unu franko kaj nul centimo"), + (2.01, "du frankoj kaj unu centimo"), + (8.10, "ok frankoj kaj dek centimoj"), + (12.27, "dek du frankoj kaj dudek sep centimoj"), + (21.29, "dudek unu frankoj kaj dudek naŭ centimoj"), + (81.25, "okdek unu frankoj kaj dudek kvin centimoj"), + (100.00, "cent frankoj kaj nul centimo"), +) + +TEST_CASES_TO_CURRENCY_USD = ( + (1.00, "unu dolaro kaj nul cendo"), + (2.01, "du dolaroj kaj unu cendo"), + (8.10, "ok dolaroj kaj dek cendoj"), + (12.26, "dek du dolaroj kaj dudek ses cendoj"), + (21.29, "dudek unu dolaroj kaj dudek naŭ cendoj"), + (81.25, "okdek unu dolaroj kaj dudek kvin cendoj"), + (100.00, "cent dolaroj kaj nul cendo"), +) + + +class Num2WordsEOTest(TestCase): + def test_number(self): + for test in TEST_CASES_CARDINAL: + self.assertEqual(num2words(test[0], lang="eo"), test[1]) + + def test_ordinal(self): + for test in TEST_CASES_ORDINAL: + self.assertEqual( + num2words(test[0], lang="eo", ordinal=True), + test[1] + ) + + def test_ordinal_num(self): + for test in TEST_CASES_ORDINAL_NUM: + self.assertEqual( + num2words(test[0], lang="eo", to="ordinal_num"), + test[1] + ) + + def test_currency_eur(self): + for test in TEST_CASES_TO_CURRENCY_EUR: + self.assertEqual( + num2words(test[0], lang="eo", to="currency", currency="EUR"), + test[1] + ) + + def test_currency_frf(self): + for test in TEST_CASES_TO_CURRENCY_FRF: + self.assertEqual( + num2words(test[0], lang="eo", to="currency", currency="FRF"), + test[1] + ) + + def test_currency_usd(self): + for test in TEST_CASES_TO_CURRENCY_USD: + self.assertEqual( + num2words(test[0], lang="eo", to="currency", currency="USD"), + test[1] + ) diff --git a/tests/test_fa.py b/tests/test_fa.py new file mode 100644 index 0000000..9a9c4ea --- /dev/null +++ b/tests/test_fa.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2003, Taro Ogawa. All Rights Reserved. +# Copyright (c) 2013, Savoir-faire Linux inc. All Rights Reserved. +# Copyright (c) 2020, Hamidreza Kalbasi. 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 Num2WordsFATest(TestCase): + def test_and_join_199(self): + self.assertEqual(num2words(199, lang='fa'), "صد و نود و نه") + + def test_ordinal(self): + self.assertEqual( + num2words(0, lang='fa', to='ordinal'), + 'صفرم' + ) + self.assertEqual( + num2words(1, lang='fa', to='ordinal'), + 'یکم' + ) + self.assertEqual( + num2words(13, lang='fa', to='ordinal'), + 'سیزدهم' + ) + self.assertEqual( + num2words(23, lang='fa', to='ordinal'), + 'بیست و سوم' + ) + self.assertEqual( + num2words(12, lang='fa', to='ordinal'), + 'دوازدهم' + ) + self.assertEqual( + num2words(113, lang='fa', to='ordinal'), + 'صد و سیزدهم' + ) + self.assertEqual( + num2words(103, lang='fa', to='ordinal'), + 'صد و سوم' + ) + + def test_cardinal(self): + self.assertEqual(num2words(130000, lang='fa'), "صد و سی هزار") + self.assertEqual(num2words(242, lang='fa'), "دویست و چهل و دو") + self.assertEqual(num2words(800, lang='fa'), "هشتصد") + self.assertEqual(num2words(-203, lang='fa'), "منفی دویست و سه") + self.assertEqual( + num2words(1234567890, lang='fa'), + "یک میلیارد و دویست و سی و چهار میلیون و" + " پانصد و شصت و هفت هزار و هشتصد و نود" + ) + + def test_year(self): + self.assertEqual(num2words(1398, lang='fa', to='year'), + "هزار و سیصد و نود و هشت") + self.assertEqual(num2words(1399, lang='fa', to='year'), + "هزار و سیصد و نود و نه") + self.assertEqual( + num2words(1400, lang='fa', to='year'), "هزار و چهارصد") + + def test_currency(self): + self.assertEqual( + num2words(1000, lang='fa', to='currency'), 'هزار تومان') + self.assertEqual( + num2words(1500000, lang='fa', to='currency'), + 'یک میلیون و پانصد هزار تومان' + ) + + def test_ordinal_num(self): + self.assertEqual(num2words(10, lang='fa', to='ordinal_num'), '10م') + self.assertEqual(num2words(21, lang='fa', to='ordinal_num'), '21م') + self.assertEqual(num2words(102, lang='fa', to='ordinal_num'), '102م') + self.assertEqual(num2words(73, lang='fa', to='ordinal_num'), '73م') + + def test_cardinal_for_float_number(self): + self.assertEqual(num2words(12.5, lang='fa'), "دوازده و نیم") + self.assertEqual(num2words(0.75, lang='fa'), "هفتاد و پنج صدم") + self.assertEqual(num2words(12.51, lang='fa'), + "دوازده و پنجاه و یک صدم") + self.assertEqual(num2words(12.53, lang='fa'), + "دوازده و پنجاه و سه صدم") + self.assertEqual(num2words(12.59, lang='fa'), + "دوازده و پنجاه و نه صدم") + self.assertEqual(num2words(0.000001, lang='fa'), "یک میلیونیم") + + def test_overflow(self): + with self.assertRaises(OverflowError): + num2words("1000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000") diff --git a/tests/test_he.py b/tests/test_he.py index 206d7cc..e50a6a2 100644 --- a/tests/test_he.py +++ b/tests/test_he.py @@ -20,6 +20,7 @@ from __future__ import unicode_literals from unittest import TestCase from num2words import num2words +from num2words.lang_HE import Num2Word_HE class Num2WordsHETest(TestCase): @@ -71,3 +72,45 @@ class Num2WordsHETest(TestCase): self.assertEqual( num2words(6870, lang="he"), u'ששת אלפים שמונה מאות ושבעים' ) + + def test_pluralize(self): + n = Num2Word_HE() + cr1, cr2 = n.CURRENCY_FORMS['NIS'] + self.assertEqual(n.pluralize(1, cr1), 'שקל') + self.assertEqual(n.pluralize(2, cr1), 'שקלים') + self.assertEqual(n.pluralize(1, cr2), 'אגורה') + self.assertEqual(n.pluralize(2, cr2), 'אגורות') + + cr1, cr2 = n.CURRENCY_FORMS['USD'] + self.assertEqual(n.pluralize(1, cr1), 'דולר') + self.assertEqual(n.pluralize(2, cr1), 'דולרים') + self.assertEqual(n.pluralize(1, cr2), 'סנט') + self.assertEqual(n.pluralize(2, cr2), 'סנט') + + def test_to_currency(self): + n = Num2Word_HE() + self.assertEqual( + n.to_currency(20.0, currency='NIS'), 'עשרים שקלים ואפס אגורות' + ) + self.assertEqual( + n.to_currency(100.0, currency='NIS'), 'מאה שקלים ואפס אגורות' + ) + self.assertEqual( + n.to_currency(100.50, currency='NIS'), 'מאה שקלים וחמישים אגורות' + ) + + def test_to_cardinal(self): + n = Num2Word_HE() + self.assertEqual(n.to_cardinal(1500), u'אלף וחמש מאות') + + +class Num2WordsHETestNotImplementedMethofs(TestCase): + n = Num2Word_HE() + + def test_to_ordinal(self): + with self.assertRaises(NotImplementedError): + self.n.to_ordinal('1') + + def test_large_number(self): + with self.assertRaises(NotImplementedError): + num2words(2000000, lang="he") diff --git a/tests/test_it.py b/tests/test_it.py index 348f4ed..9023355 100644 --- a/tests/test_it.py +++ b/tests/test_it.py @@ -21,6 +21,36 @@ from unittest import TestCase from num2words import num2words +TEST_CASES_TO_CURRENCY_EUR = ( + (1.00, 'un euro e zero centesimi'), + (2.01, 'due euro e un centesimo'), + (8.10, 'otto euro e dieci centesimi'), + (12.26, 'dodici euro e ventisei centesimi'), + (21.29, 'ventun euro e ventinove centesimi'), + (81.25, 'ottantun euro e venticinque centesimi'), + (100.00, 'cento euro e zero centesimi'), +) + +TEST_CASES_TO_CURRENCY_USD = ( + (1.00, 'un dollaro e zero centesimi'), + (2.01, 'due dollari e un centesimo'), + (8.10, 'otto dollari e dieci centesimi'), + (12.26, 'dodici dollari e ventisei centesimi'), + (21.29, 'ventun dollari e ventinove centesimi'), + (81.25, 'ottantun dollari e venticinque centesimi'), + (100.00, 'cento dollari e zero centesimi'), +) + +TEST_CASES_TO_CURRENCY_GBP = ( + (1.00, 'una sterlina e zero penny'), + (2.01, 'due sterline e un penny'), + (8.10, 'otto sterline e dieci penny'), + (12.26, 'dodici sterline e ventisei penny'), + (21.29, 'ventun sterline e ventinove penny'), + (81.25, 'ottantun sterline e venticinque penny'), + (100.00, 'cento sterline e zero penny'), +) + class Num2WordsITTest(TestCase): maxDiff = None @@ -240,3 +270,24 @@ class Num2WordsITTest(TestCase): self.assertAlmostEqual( num2words(1.1, lang="it"), "uno virgola uno" ) + + def test_currency_eur(self): + for test in TEST_CASES_TO_CURRENCY_EUR: + self.assertEqual( + num2words(test[0], lang='it', to='currency', currency='EUR'), + test[1] + ) + + def test_currency_usd(self): + for test in TEST_CASES_TO_CURRENCY_USD: + self.assertEqual( + num2words(test[0], lang='it', to='currency', currency='USD'), + test[1] + ) + + def test_currency_gbp(self): + for test in TEST_CASES_TO_CURRENCY_GBP: + self.assertEqual( + num2words(test[0], lang='it', to='currency', currency='GBP'), + test[1] + ) diff --git a/tests/test_ja.py b/tests/test_ja.py index eaac20b..42da144 100644 --- a/tests/test_ja.py +++ b/tests/test_ja.py @@ -163,6 +163,21 @@ class Num2WordsJATest(TestCase): "はちじゅうきゅうえん") def test_year(self): + self.assertEqual(n2j(2021, to="year"), "令和三年") + self.assertEqual(n2j(2021, to="year", reading=True), + "れいわさんねん") + self.assertEqual(n2j(2021, to="year", reading="arabic"), + "令和3年") + self.assertEqual(n2j(2019, to="year"), "令和元年") + self.assertEqual(n2j(2019, to="year", reading=True), + "れいわがんねん") + self.assertEqual(n2j(2019, to="year", reading="arabic"), + "令和1年") + self.assertEqual(n2j(2018, to="year"), "平成三十年") + self.assertEqual(n2j(2018, to="year", reading=True), + "へいせいさんじゅうねん") + self.assertEqual(n2j(2018, to="year", reading="arabic"), + "平成30年") self.assertEqual(n2j(2017, to="year"), "平成二十九年") self.assertEqual(n2j(2017, to="year", reading=True), "へいせいにじゅうくねん") @@ -176,8 +191,6 @@ class Num2WordsJATest(TestCase): "にせんねん") self.assertEqual(n2j(645, to="year"), "大化元年") self.assertEqual(n2j(645, to="year", reading=True), "たいかがんねん") - self.assertEqual(n2j(645, to="year"), "大化元年") - self.assertEqual(n2j(645, to="year", reading=True), "たいかがんねん") self.assertEqual(n2j(-99, to="year", era=False), "紀元前九十九年") self.assertEqual(n2j(-99, to="year", era=False, reading=True), "きげんぜんきゅうじゅうくねん") diff --git a/tests/test_nl.py b/tests/test_nl.py index e72b43b..95c690f 100644 --- a/tests/test_nl.py +++ b/tests/test_nl.py @@ -36,6 +36,7 @@ class Num2WordsNLTest(TestCase): ) def test_ordinal_at_crucial_number(self): + self.assertEqual(num2words(0, ordinal=True, lang='nl'), "nulde") self.assertEqual(num2words(100, ordinal=True, lang='nl'), "honderdste") self.assertEqual( num2words(1000, ordinal=True, lang='nl'), "duizendste" diff --git a/tests/test_pl.py b/tests/test_pl.py index 147a747..08ef039 100644 --- a/tests/test_pl.py +++ b/tests/test_pl.py @@ -24,11 +24,13 @@ from num2words import num2words class Num2WordsPLTest(TestCase): def test_cardinal(self): + self.assertEqual(num2words(90, lang='pl'), "dziewięćdziesiąt") self.assertEqual(num2words(100, lang='pl'), "sto") self.assertEqual(num2words(101, lang='pl'), "sto jeden") self.assertEqual(num2words(110, lang='pl'), "sto dziesięć") self.assertEqual(num2words(115, lang='pl'), "sto piętnaście") self.assertEqual(num2words(123, lang='pl'), "sto dwadzieścia trzy") + self.assertEqual(num2words(400, lang='pl'), "czterysta") self.assertEqual(num2words(1000, lang='pl'), "tysiąc") self.assertEqual(num2words(1001, lang='pl'), "tysiąc jeden") self.assertEqual(num2words(2012, lang='pl'), "dwa tysiące dwanaście") @@ -52,7 +54,7 @@ class Num2WordsPLTest(TestCase): self.assertEqual( num2words(1234567890, lang='pl'), "miliard dwieście trzydzieści cztery miliony pięćset " - "sześćdziesiąt siedem tysięcy osiemset dziewięćdzisiąt" + "sześćdziesiąt siedem tysięcy osiemset dziewięćdziesiąt" ) self.assertEqual( num2words(10000000001000000100000, lang='pl'), @@ -62,20 +64,20 @@ class Num2WordsPLTest(TestCase): num2words(215461407892039002157189883901676, lang='pl'), "dwieście piętnaście kwintylionów czterysta sześćdziesiąt jeden " "kwadryliardów czterysta siedem kwadrylionów osiemset " - "dziewięćdzisiąt dwa tryliardy trzydzieści dziewięć trylionów " + "dziewięćdziesiąt dwa tryliardy trzydzieści dziewięć trylionów " "dwa biliardy sto pięćdziesiąt siedem bilionów sto osiemdziesiąt " "dziewięć miliardów osiemset osiemdziesiąt trzy miliony " "dziewięćset jeden tysięcy sześćset siedemdziesiąt sześć" ) self.assertEqual( num2words(719094234693663034822824384220291, lang='pl'), - "siedemset dziewiętnaście kwintylionów dziewięćdzisiąt cztery " + "siedemset dziewiętnaście kwintylionów dziewięćdziesiąt cztery " "kwadryliardy dwieście trzydzieści cztery kwadryliony sześćset " - "dziewięćdzisiąt trzy tryliardy sześćset sześćdziesiąt trzy " + "dziewięćdziesiąt trzy tryliardy sześćset sześćdziesiąt trzy " "tryliony trzydzieści cztery biliardy osiemset dwadzieścia dwa " "biliony osiemset dwadzieścia cztery miliardy trzysta " "osiemdziesiąt cztery miliony dwieście dwadzieścia " - "tysięcy dwieście dziewięćdzisiąt jeden" + "tysięcy dwieście dziewięćdziesiąt jeden" ) self.assertEqual( num2words( @@ -94,6 +96,9 @@ class Num2WordsPLTest(TestCase): self.assertEqual(num2words(100, lang='pl', to='ordinal'), "setny") self.assertEqual( num2words(101, lang='pl', to='ordinal'), "sto pierwszy") + self.assertEqual(num2words(120, lang='pl', to='ordinal'), + "sto dwudziesty") + self.assertEqual(num2words(20, lang='pl', to='ordinal'), "dwudziesty") self.assertEqual(num2words(121, lang='pl', to='ordinal'), "sto dwudziesty pierwszy") self.assertEqual( @@ -118,6 +123,10 @@ class Num2WordsPLTest(TestCase): self.assertEqual(num2words(1000000, lang='pl', to='ordinal'), "milionowy") + def test_to_ordinal_error(self): + with self.assertRaises(NotImplementedError): + num2words(1.5, lang='pl', to='ordinal') + def test_currency(self): self.assertEqual( num2words(1.0, lang='pl', to='currency', currency='EUR'), diff --git a/tests/test_ru.py b/tests/test_ru.py index dc47975..85d6dc3 100644 --- a/tests/test_ru.py +++ b/tests/test_ru.py @@ -272,3 +272,8 @@ class Num2WordsRUTest(TestCase): 'одна тысяча двести тридцать четыре доллара, пятьдесят шесть ' 'центов' ) + self.assertEqual( + num2words(10122, lang='ru', to='currency', currency='UZS', + separator=' и'), + 'сто один сум и двадцать два тийина' + ) diff --git a/tests/test_tg.py b/tests/test_tg.py new file mode 100644 index 0000000..53a49e4 --- /dev/null +++ b/tests/test_tg.py @@ -0,0 +1,118 @@ +# -*- 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 Num2WordsTGTest(TestCase): + def test_cardinal(self): + with self.assertRaises(OverflowError): + num2words(1000000000000000000000000, lang="tg") + self.assertEqual(num2words(100, lang="tg"), "сад") + self.assertEqual(num2words(100000, lang="tg"), "сад ҳазор") + self.assertEqual(num2words(101, lang="tg"), "яксаду як") + self.assertEqual(num2words(110, lang="tg"), "яксаду даҳ") + self.assertEqual(num2words(115, lang="tg"), "яксаду понздаҳ") + self.assertEqual(num2words(123, lang="tg"), "яксаду бисту се") + self.assertEqual(num2words(1000, lang="tg"), "як ҳазор") + self.assertEqual(num2words(1001, lang="tg"), "як ҳазору як") + self.assertEqual(num2words(2012, lang="tg"), "ду ҳазору дувоздаҳ") + self.assertEqual( + num2words(12519.85, lang="tg"), + "дувоздаҳ ҳазору панҷсаду нуздаҳ нуқта ҳашт панҷ", + ) + + self.assertEqual( + num2words(1234567890, lang="tg"), + "як миллиарду дусаду сию чор миллиону панҷсаду шасту ҳафт ҳазору " + "ҳаштсаду навад", + ) + self.assertEqual(num2words(1000000, lang="tg"), "як миллион") + self.assertEqual(num2words(1000000000, lang="tg"), "як миллиард") + self.assertEqual(num2words(1000000000000, lang="tg"), "як триллион") + self.assertEqual(num2words(5, lang="tg"), "панҷ") + self.assertEqual(num2words(-1, lang="tg"), "минус як") + self.assertEqual(num2words(-15, lang="tg"), "минус понздаҳ") + self.assertEqual(num2words(-100, lang="tg"), "минус сад") + + def test_to_ordinal(self): + self.assertEqual(num2words(1, lang="tg", to="ordinal"), "якум") + self.assertEqual(num2words(2, lang="tg", to="ordinal"), "дуюм") + self.assertEqual(num2words(3, lang="tg", to="ordinal"), "сеюм") + self.assertEqual(num2words(30, lang="tg", to="ordinal"), "сиюм") + + self.assertEqual(num2words(13, lang="tg", to="ordinal"), "сенздаҳум") + self.assertEqual(num2words(20, lang="tg", to="ordinal"), "бистум") + self.assertEqual(num2words(23, lang="tg", to="ordinal"), "бисту сеюм") + self.assertEqual(num2words(100, lang="tg", to="ordinal"), "садум") + self.assertEqual( + num2words(136, lang="tg", to="ordinal"), "яксаду сию шашум" + ) + self.assertEqual(num2words(500, lang="tg", to="ordinal"), "панҷсадум") + self.assertEqual( + num2words(1000, lang="tg", to="ordinal"), "як ҳазорум" + ) + self.assertEqual( + num2words(1001, lang="tg", to="ordinal"), "як ҳазору якум" + ) + self.assertEqual( + num2words(2000, lang="tg", to="ordinal"), "ду ҳазорум" + ) + self.assertEqual( + num2words(1000000, lang="tg", to="ordinal"), "як миллионум" + ) + self.assertEqual( + num2words(1000000000, lang="tg", to="ordinal"), "як миллиардум" + ) + + def test_to_currency(self): + self.assertEqual( + num2words(1.0, lang="tg", to="currency", currency="EUR"), + "як евро, сифр сент", + ) + self.assertEqual( + num2words(1.0, lang="tg", to="currency", currency="TJS"), + "як сомонӣ, сифр дирам", + ) + self.assertEqual( + num2words(1234.56, lang="tg", to="currency", currency="TJS"), + "як ҳазору дусаду сию чор сомонӣ, панҷову шаш дирам", + ) + self.assertEqual( + num2words(1234.56, lang="tg", to="currency", currency="RUB"), + "як ҳазору дусаду сию чор рубл, панҷову шаш копейк", + ) + self.assertEqual( + num2words( + 12519.85, lang="tg", to="currency", currency="TJS", cents=False + ), + "дувоздаҳ ҳазору панҷсаду нуздаҳ сомонӣ, 85 дирам", + ) + self.assertEqual( + num2words("1230.56", lang="tg", to="currency", currency="USD"), + "як ҳазору дусаду си доллар, панҷову шаш сент", + ) + + def test_to_ordinal_num(self): + self.assertEqual( + num2words("100", lang="tg", to="ordinal_num"), + "100ум", + ) diff --git a/tests/test_tr.py b/tests/test_tr.py index 8425262..e1aefba 100644 --- a/tests/test_tr.py +++ b/tests/test_tr.py @@ -182,7 +182,11 @@ class Num2WordsTRTest(TestCase): {"test": 101101011010.02, "to": "cardinal", "expected": u"yüzbirmilyaryüzbirmilyononbirbinonvirgüliki"}, {"test": 101101011010.2, "to": "cardinal", - "expected": u"yüzbirmilyaryüzbirmilyononbirbinonvirgülyirmi"} + "expected": u"yüzbirmilyaryüzbirmilyononbirbinonvirgülyirmi"}, + {"test": 10, "to": "ordinal_num", "expected": u"10uncu"}, + {"test": 1, "to": "ordinal_num", "expected": u"1inci"}, + {"test": 3, "to": "ordinal_num", "expected": u"3üncü"}, + {"test": 6, "to": "ordinal_num", "expected": u"6ıncı"} ] for casedata in testcases: