diff options
-rw-r--r-- | babel/numbers.py | 62 | ||||
-rwxr-xr-x | scripts/import_cldr.py | 9 | ||||
-rw-r--r-- | tests/test_numbers.py | 38 |
3 files changed, 109 insertions, 0 deletions
diff --git a/babel/numbers.py b/babel/numbers.py index 564d7ce..9df19e9 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -442,6 +442,17 @@ def format_currency( ... UnknownCurrencyFormatError: "'unknown' is not a known currency format type" + You can also pass format_type='name' to use long display names. The order of + the number and currency name, along with the correct localized plural form + of the currency name, is chosen according to locale: + + >>> format_currency(1, 'USD', locale='en_US', format_type='name') + u'1.00 US dollar' + >>> format_currency(1099.98, 'USD', locale='en_US', format_type='name') + u'1,099.98 US dollars' + >>> format_currency(1099.98, 'USD', locale='ee', format_type='name') + u'us ga dollar 1,099.98' + By default the locale is allowed to truncate and round a high-precision number by forcing its format pattern onto the decimal part. You can bypass this behavior with the `decimal_quantization` parameter: @@ -459,7 +470,12 @@ def format_currency( :param format_type: the currency format type to use :param decimal_quantization: Truncate and round high-precision numbers to the format pattern. Defaults to `True`. + """ + if format_type == 'name': + return _format_currency_long_name(number, currency, format=format, + locale=locale, currency_digits=currency_digits, + decimal_quantization=decimal_quantization) locale = Locale.parse(locale) if format: pattern = parse_pattern(format) @@ -475,6 +491,52 @@ def format_currency( decimal_quantization=decimal_quantization) +def _format_currency_long_name( + number, currency, format=None, locale=LC_NUMERIC, currency_digits=True, + format_type='standard', decimal_quantization=True): + # Algorithm described here: + # https://www.unicode.org/reports/tr35/tr35-numbers.html#Currencies + locale = Locale.parse(locale) + # Step 1. + # There are no examples of items with explicit count (0 or 1) in current + # locale data. So there is no point implementing that. + # Step 2. + if isinstance(number, string_types): + plural_category = locale.plural_form(float(number)) + else: + plural_category = locale.plural_form(number) + + # Step 3. + try: + unit_pattern = locale._data['currency_unit_patterns'][plural_category] + except LookupError: + unit_pattern = locale._data['currency_unit_patterns']['other'] + + # Step 4. + try: + display_name = locale._data['currency_names_plural'][currency][plural_category] + except LookupError: + try: + display_name = locale._data['currency_names_plural'][currency]['other'] + except LookupError: + try: + display_name = locale._data['currency_names'][currency] + except LookupError: + display_name = currency + + # Step 5. + if not format: + format = locale.decimal_formats.get(format) + + pattern = parse_pattern(format) + + number_part = pattern.apply( + number, locale, currency=currency, currency_digits=currency_digits, + decimal_quantization=decimal_quantization) + + return unit_pattern.format(number_part, display_name) + + def format_percent( number, format=None, locale=LC_NUMERIC, decimal_quantization=True): """Return formatted percent value for a specific locale. diff --git a/scripts/import_cldr.py b/scripts/import_cldr.py index 60aa6c2..40887f0 100755 --- a/scripts/import_cldr.py +++ b/scripts/import_cldr.py @@ -423,6 +423,7 @@ def _process_local_datas(sup, srcdir, destdir, force=False, dump_json=False): parse_percent_formats(data, tree) parse_currency_formats(data, tree) + parse_currency_unit_patterns(data, tree) parse_currency_names(data, tree) parse_unit_patterns(data, tree) parse_date_fields(data, tree) @@ -903,6 +904,14 @@ def parse_currency_formats(data, tree): currency_formats[type] = numbers.parse_pattern(pattern) +def parse_currency_unit_patterns(data, tree): + currency_unit_patterns = data.setdefault('currency_unit_patterns', {}) + for unit_pattern_elem in tree.findall('.//currencyFormats/unitPattern'): + count = unit_pattern_elem.attrib['count'] + pattern = text_type(unit_pattern_elem.text) + currency_unit_patterns[count] = pattern + + def parse_day_period_rules(tree): """ Parse dayPeriodRule data into a dict. diff --git a/tests/test_numbers.py b/tests/test_numbers.py index 493c1a7..f0239cb 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -415,6 +415,44 @@ def test_format_currency_quantization(): '0.9999999999', 'USD', locale=locale_code, decimal_quantization=False).find('9999999999') > -1 +def test_format_currency_long_display_name(): + assert (numbers.format_currency(1099.98, 'USD', locale='en_US', format_type='name') + == u'1,099.98 US dollars') + assert (numbers.format_currency(1.00, 'USD', locale='en_US', format_type='name') + == u'1.00 US dollar') + assert (numbers.format_currency(1.00, 'EUR', locale='en_US', format_type='name') + == u'1.00 euro') + assert (numbers.format_currency(2, 'EUR', locale='en_US', format_type='name') + == u'2.00 euros') + # This tests that '{1} {0}' unitPatterns are found: + assert (numbers.format_currency(1, 'USD', locale='sw', format_type='name') + == u'dola ya Marekani 1.00') + # This tests unicode chars: + assert (numbers.format_currency(1099.98, 'USD', locale='es_GT', format_type='name') + == u'dólares estadounidenses 1,099.98') + # Test for completely unknown currency, should fallback to currency code + assert (numbers.format_currency(1099.98, 'XAB', locale='en_US', format_type='name') + == u'1,099.98 XAB') + + +def test_format_currency_long_display_name_all(): + for locale_code in localedata.locale_identifiers(): + assert numbers.format_currency( + 1, 'USD', locale=locale_code, format_type='name').find('1') > -1 + assert numbers.format_currency( + '1', 'USD', locale=locale_code, format_type='name').find('1') > -1 + + +def test_format_currency_long_display_name_custom_format(): + assert (numbers.format_currency(1099.98, 'USD', locale='en_US', + format_type='name', format='##0') + == '1099.98 US dollars') + assert (numbers.format_currency(1099.98, 'USD', locale='en_US', + format_type='name', format='##0', + currency_digits=False) + == '1100 US dollars') + + def test_format_percent(): assert numbers.format_percent(0.34, locale='en_US') == u'34%' assert numbers.format_percent(0, locale='en_US') == u'0%' |