Skip to content

Commit e9c3ef8

Browse files
authored
Merge pull request #1173 from python-babel/lc-monetary-2
Prefer LC_MONETARY when formatting currencies
2 parents 2d8a808 + 56ef7c7 commit e9c3ef8

File tree

4 files changed

+82
-29
lines changed

4 files changed

+82
-29
lines changed

babel/core.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,7 +1089,10 @@ def unit_display_names(self) -> localedata.LocaleDataDict:
10891089
return self._data['unit_display_names']
10901090

10911091

1092-
def default_locale(category: str | None = None, aliases: Mapping[str, str] = LOCALE_ALIASES) -> str | None:
1092+
def default_locale(
1093+
category: str | tuple[str, ...] | list[str] | None = None,
1094+
aliases: Mapping[str, str] = LOCALE_ALIASES,
1095+
) -> str | None:
10931096
"""Returns the system default locale for a given category, based on
10941097
environment variables.
10951098
@@ -1113,11 +1116,22 @@ def default_locale(category: str | None = None, aliases: Mapping[str, str] = LOC
11131116
- ``LC_CTYPE``
11141117
- ``LANG``
11151118
1116-
:param category: one of the ``LC_XXX`` environment variable names
1119+
:param category: one or more of the ``LC_XXX`` environment variable names
11171120
:param aliases: a dictionary of aliases for locale identifiers
11181121
"""
1119-
varnames = (category, 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
1120-
for name in filter(None, varnames):
1122+
1123+
varnames = ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
1124+
if category:
1125+
if isinstance(category, str):
1126+
varnames = (category, *varnames)
1127+
elif isinstance(category, (list, tuple)):
1128+
varnames = (*category, *varnames)
1129+
else:
1130+
raise TypeError(f"Invalid type for category: {category!r}")
1131+
1132+
for name in varnames:
1133+
if not name:
1134+
continue
11211135
locale = os.getenv(name)
11221136
if locale:
11231137
if name == 'LANGUAGE' and ':' in locale:

babel/numbers.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
The default locale for the functions in this module is determined by the
88
following environment variables, in that order:
99
10-
* ``LC_NUMERIC``,
10+
* ``LC_MONETARY`` for currency related functions,
11+
* ``LC_NUMERIC``, and
1112
* ``LC_ALL``, and
1213
* ``LANG``
1314
@@ -28,6 +29,7 @@
2829
from babel.core import Locale, default_locale, get_global
2930
from babel.localedata import LocaleDataDict
3031

32+
LC_MONETARY = default_locale(('LC_MONETARY', 'LC_NUMERIC'))
3133
LC_NUMERIC = default_locale('LC_NUMERIC')
3234

3335

@@ -117,9 +119,10 @@ def get_currency_name(
117119
:param currency: the currency code.
118120
:param count: the optional count. If provided the currency name
119121
will be pluralized to that number if possible.
120-
:param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
122+
:param locale: the `Locale` object or locale identifier.
123+
Defaults to the system currency locale or numeric locale.
121124
"""
122-
loc = Locale.parse(locale or LC_NUMERIC)
125+
loc = Locale.parse(locale or LC_MONETARY)
123126
if count is not None:
124127
try:
125128
plural_form = loc.plural_form(count)
@@ -142,9 +145,10 @@ def get_currency_symbol(currency: str, locale: Locale | str | None = None) -> st
142145
u'$'
143146
144147
:param currency: the currency code.
145-
:param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
148+
:param locale: the `Locale` object or locale identifier.
149+
Defaults to the system currency locale or numeric locale.
146150
"""
147-
return Locale.parse(locale or LC_NUMERIC).currency_symbols.get(currency, currency)
151+
return Locale.parse(locale or LC_MONETARY).currency_symbols.get(currency, currency)
148152

149153

150154
def get_currency_precision(currency: str) -> int:
@@ -181,9 +185,10 @@ def get_currency_unit_pattern(
181185
:param currency: the currency code.
182186
:param count: the optional count. If provided the unit
183187
pattern for that number will be returned.
184-
:param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
188+
:param locale: the `Locale` object or locale identifier.
189+
Defaults to the system currency locale or numeric locale.
185190
"""
186-
loc = Locale.parse(locale or LC_NUMERIC)
191+
loc = Locale.parse(locale or LC_MONETARY)
187192
if count is not None:
188193
plural_form = loc.plural_form(count)
189194
try:
@@ -760,7 +765,8 @@ def format_currency(
760765
:param number: the number to format
761766
:param currency: the currency code
762767
:param format: the format string to use
763-
:param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
768+
:param locale: the `Locale` object or locale identifier.
769+
Defaults to the system currency locale or numeric locale.
764770
:param currency_digits: use the currency's natural number of decimal digits
765771
:param format_type: the currency format type to use
766772
:param decimal_quantization: Truncate and round high-precision numbers to
@@ -771,7 +777,7 @@ def format_currency(
771777
The special value "default" will use the default numbering system of the locale.
772778
:raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
773779
"""
774-
locale = Locale.parse(locale or LC_NUMERIC)
780+
locale = Locale.parse(locale or LC_MONETARY)
775781

776782
if format_type == 'name':
777783
return _format_currency_long_name(
@@ -860,13 +866,14 @@ def format_compact_currency(
860866
:param number: the number to format
861867
:param currency: the currency code
862868
:param format_type: the compact format type to use. Defaults to "short".
863-
:param locale: the `Locale` object or locale identifier. Defaults to the system numeric locale.
869+
:param locale: the `Locale` object or locale identifier.
870+
Defaults to the system currency locale or numeric locale.
864871
:param fraction_digits: Number of digits after the decimal point to use. Defaults to `0`.
865872
:param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
866873
The special value "default" will use the default numbering system of the locale.
867874
:raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
868875
"""
869-
locale = Locale.parse(locale or LC_NUMERIC)
876+
locale = Locale.parse(locale or LC_MONETARY)
870877
try:
871878
compact_format = locale.compact_currency_formats[format_type]
872879
except KeyError as error:

tests/test_core.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ def test_plural_form_property(self):
265265

266266

267267
def test_default_locale(monkeypatch):
268-
for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
268+
for name in ['LANGUAGE', 'LANG', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
269269
monkeypatch.setenv(name, '')
270270
monkeypatch.setenv('LANG', 'fr_FR.UTF-8')
271271
assert default_locale('LC_MESSAGES') == 'fr_FR'
@@ -277,6 +277,23 @@ def test_default_locale(monkeypatch):
277277
assert default_locale() == 'en_US_POSIX'
278278

279279

280+
def test_default_locale_multiple_args(monkeypatch):
281+
for name in ['LANGUAGE', 'LANG', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY', 'LC_NUMERIC']:
282+
monkeypatch.setenv(name, '')
283+
assert default_locale(["", 0, None]) is None
284+
monkeypatch.setenv('LANG', 'en_US')
285+
assert default_locale(('LC_MONETARY', 'LC_NUMERIC')) == 'en_US' # No LC_MONETARY or LC_NUMERIC set
286+
monkeypatch.setenv('LC_NUMERIC', 'fr_FR.UTF-8')
287+
assert default_locale(('LC_MONETARY', 'LC_NUMERIC')) == 'fr_FR' # LC_NUMERIC set
288+
monkeypatch.setenv('LC_MONETARY', 'fi_FI.UTF-8')
289+
assert default_locale(('LC_MONETARY', 'LC_NUMERIC')) == 'fi_FI' # LC_MONETARY set, it takes precedence
290+
291+
292+
def test_default_locale_bad_arg():
293+
with pytest.raises(TypeError):
294+
default_locale(42)
295+
296+
280297
def test_negotiate_locale():
281298
assert (core.negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT']) ==
282299
'de_DE')

tests/test_numbers.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -485,19 +485,6 @@ def test_format_currency():
485485
== 'US$0,00') # other
486486

487487

488-
def test_format_currency_with_none_locale_with_default(monkeypatch):
489-
"""Test that the default locale is used when locale is None."""
490-
monkeypatch.setattr(numbers, "LC_NUMERIC", "fi_FI")
491-
assert numbers.format_currency(0, "USD", locale=None) == "0,00\xa0$"
492-
493-
494-
def test_format_currency_with_none_locale(monkeypatch):
495-
"""Test that the API raises the "Empty locale identifier" error when locale is None, and the default is too."""
496-
monkeypatch.setattr(numbers, "LC_NUMERIC", None) # Pretend we couldn't find any locale when importing the module
497-
with pytest.raises(TypeError, match="Empty"):
498-
numbers.format_currency(0, "USD", locale=None)
499-
500-
501488
def test_format_currency_format_type():
502489
assert (numbers.format_currency(1099.98, 'USD', locale='en_US',
503490
format_type="standard")
@@ -867,3 +854,31 @@ def test_single_quotes_in_pattern():
867854
assert numbers.format_decimal(123, "'$'''0", locale='en') == "$'123"
868855

869856
assert numbers.format_decimal(12, "'#'0 o''clock", locale='en') == "#12 o'clock"
857+
858+
859+
def test_format_currency_with_none_locale_with_default(monkeypatch):
860+
"""Test that the default locale is used when locale is None."""
861+
monkeypatch.setattr(numbers, "LC_MONETARY", "fi_FI")
862+
monkeypatch.setattr(numbers, "LC_NUMERIC", None)
863+
assert numbers.format_currency(0, "USD", locale=None) == "0,00\xa0$"
864+
865+
866+
def test_format_currency_with_none_locale(monkeypatch):
867+
"""Test that the API raises the "Empty locale identifier" error when locale is None, and the default is too."""
868+
monkeypatch.setattr(numbers, "LC_MONETARY", None) # Pretend we couldn't find any locale when importing the module
869+
with pytest.raises(TypeError, match="Empty"):
870+
numbers.format_currency(0, "USD", locale=None)
871+
872+
873+
def test_format_decimal_with_none_locale_with_default(monkeypatch):
874+
"""Test that the default locale is used when locale is None."""
875+
monkeypatch.setattr(numbers, "LC_NUMERIC", "fi_FI")
876+
monkeypatch.setattr(numbers, "LC_MONETARY", None)
877+
assert numbers.format_decimal("1.23", locale=None) == "1,23"
878+
879+
880+
def test_format_decimal_with_none_locale(monkeypatch):
881+
"""Test that the API raises the "Empty locale identifier" error when locale is None, and the default is too."""
882+
monkeypatch.setattr(numbers, "LC_NUMERIC", None) # Pretend we couldn't find any locale when importing the module
883+
with pytest.raises(TypeError, match="Empty"):
884+
numbers.format_decimal(0, locale=None)

0 commit comments

Comments
 (0)