summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Plant <L.Plant.98@cantab.net>2018-06-04 20:53:28 +0300
committerLuke Plant <L.Plant.98@cantab.net>2018-06-04 20:53:28 +0300
commit772b8402a77dab1ee683e0f715eb9db52fc66936 (patch)
tree347a7f453a9c8662c4985ffc3934f3154396f588
parenta5ecaa321817d3705cbda1476f6e9f06daa1e847 (diff)
downloadbabel-772b8402a77dab1ee683e0f715eb9db52fc66936.tar.gz
numbers: implement currency formatting with long display names.
Fixes #578
-rw-r--r--babel/numbers.py62
-rwxr-xr-xscripts/import_cldr.py9
-rw-r--r--tests/test_numbers.py38
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%'