From 56ef7c7f578a904917464c187e399abb762bd5e3 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Fri, 22 Dec 2023 00:42:55 +0100 Subject: [PATCH] Prefer LC_MONETARY when formatting currency Co-authored-by: Alexis Hildebrandt --- babel/numbers.py | 29 ++++++++++++++++++----------- tests/test_numbers.py | 41 ++++++++++++++++++++++++++++------------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/babel/numbers.py b/babel/numbers.py index a2b89cc3..8dd4d9ea 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -7,7 +7,8 @@ The default locale for the functions in this module is determined by the following environment variables, in that order: - * ``LC_NUMERIC``, + * ``LC_MONETARY`` for currency related functions, + * ``LC_NUMERIC``, and * ``LC_ALL``, and * ``LANG`` @@ -28,6 +29,7 @@ from typing import Any, Literal, cast, overload from babel.core import Locale, default_locale, get_global from babel.localedata import LocaleDataDict +LC_MONETARY = default_locale(('LC_MONETARY', 'LC_NUMERIC')) LC_NUMERIC = default_locale('LC_NUMERIC') @@ -117,9 +119,10 @@ def get_currency_name( :param currency: the currency code. :param count: the optional count. If provided the currency name will be pluralized to that number if possible. - :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale. + :param locale: the `Locale` object or locale identifier. + Defaults to the system currency locale or numeric locale. """ - loc = Locale.parse(locale or LC_NUMERIC) + loc = Locale.parse(locale or LC_MONETARY) if count is not None: try: plural_form = loc.plural_form(count) @@ -142,9 +145,10 @@ def get_currency_symbol(currency: str, locale: Locale | str | None = None) -> st u'$' :param currency: the currency code. - :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale. + :param locale: the `Locale` object or locale identifier. + Defaults to the system currency locale or numeric locale. """ - return Locale.parse(locale or LC_NUMERIC).currency_symbols.get(currency, currency) + return Locale.parse(locale or LC_MONETARY).currency_symbols.get(currency, currency) def get_currency_precision(currency: str) -> int: @@ -181,9 +185,10 @@ def get_currency_unit_pattern( :param currency: the currency code. :param count: the optional count. If provided the unit pattern for that number will be returned. - :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale. + :param locale: the `Locale` object or locale identifier. + Defaults to the system currency locale or numeric locale. """ - loc = Locale.parse(locale or LC_NUMERIC) + loc = Locale.parse(locale or LC_MONETARY) if count is not None: plural_form = loc.plural_form(count) try: @@ -760,7 +765,8 @@ def format_currency( :param number: the number to format :param currency: the currency code :param format: the format string to use - :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale. + :param locale: the `Locale` object or locale identifier. + Defaults to the system currency locale or numeric locale. :param currency_digits: use the currency's natural number of decimal digits :param format_type: the currency format type to use :param decimal_quantization: Truncate and round high-precision numbers to @@ -771,7 +777,7 @@ def format_currency( The special value "default" will use the default numbering system of the locale. :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale. """ - locale = Locale.parse(locale or LC_NUMERIC) + locale = Locale.parse(locale or LC_MONETARY) if format_type == 'name': return _format_currency_long_name( @@ -860,13 +866,14 @@ def format_compact_currency( :param number: the number to format :param currency: the currency code :param format_type: the compact format type to use. Defaults to "short". - :param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale. + :param locale: the `Locale` object or locale identifier. + Defaults to the system currency locale or numeric locale. :param fraction_digits: Number of digits after the decimal point to use. Defaults to `0`. :param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn". The special value "default" will use the default numbering system of the locale. :raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale. """ - locale = Locale.parse(locale or LC_NUMERIC) + locale = Locale.parse(locale or LC_MONETARY) try: compact_format = locale.compact_currency_formats[format_type] except KeyError as error: diff --git a/tests/test_numbers.py b/tests/test_numbers.py index cf0e8d1b..45892fe2 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -485,19 +485,6 @@ def test_format_currency(): == 'US$0,00') # other -def test_format_currency_with_none_locale_with_default(monkeypatch): - """Test that the default locale is used when locale is None.""" - monkeypatch.setattr(numbers, "LC_NUMERIC", "fi_FI") - assert numbers.format_currency(0, "USD", locale=None) == "0,00\xa0$" - - -def test_format_currency_with_none_locale(monkeypatch): - """Test that the API raises the "Empty locale identifier" error when locale is None, and the default is too.""" - monkeypatch.setattr(numbers, "LC_NUMERIC", None) # Pretend we couldn't find any locale when importing the module - with pytest.raises(TypeError, match="Empty"): - numbers.format_currency(0, "USD", locale=None) - - def test_format_currency_format_type(): assert (numbers.format_currency(1099.98, 'USD', locale='en_US', format_type="standard") @@ -867,3 +854,31 @@ def test_single_quotes_in_pattern(): assert numbers.format_decimal(123, "'$'''0", locale='en') == "$'123" assert numbers.format_decimal(12, "'#'0 o''clock", locale='en') == "#12 o'clock" + + +def test_format_currency_with_none_locale_with_default(monkeypatch): + """Test that the default locale is used when locale is None.""" + monkeypatch.setattr(numbers, "LC_MONETARY", "fi_FI") + monkeypatch.setattr(numbers, "LC_NUMERIC", None) + assert numbers.format_currency(0, "USD", locale=None) == "0,00\xa0$" + + +def test_format_currency_with_none_locale(monkeypatch): + """Test that the API raises the "Empty locale identifier" error when locale is None, and the default is too.""" + monkeypatch.setattr(numbers, "LC_MONETARY", None) # Pretend we couldn't find any locale when importing the module + with pytest.raises(TypeError, match="Empty"): + numbers.format_currency(0, "USD", locale=None) + + +def test_format_decimal_with_none_locale_with_default(monkeypatch): + """Test that the default locale is used when locale is None.""" + monkeypatch.setattr(numbers, "LC_NUMERIC", "fi_FI") + monkeypatch.setattr(numbers, "LC_MONETARY", None) + assert numbers.format_decimal("1.23", locale=None) == "1,23" + + +def test_format_decimal_with_none_locale(monkeypatch): + """Test that the API raises the "Empty locale identifier" error when locale is None, and the default is too.""" + monkeypatch.setattr(numbers, "LC_NUMERIC", None) # Pretend we couldn't find any locale when importing the module + with pytest.raises(TypeError, match="Empty"): + numbers.format_decimal(0, locale=None) -- 2.47.2