44
55 Core locale representation and locale data access.
66
7- :copyright: (c) 2013-2024 by the Babel Team.
7+ :copyright: (c) 2013-2025 by the Babel Team.
88 :license: BSD, see LICENSE for more details.
99"""
1010
1313import os
1414import pickle
1515from collections .abc import Iterable , Mapping
16- from typing import TYPE_CHECKING , Any
16+ from typing import TYPE_CHECKING , Any , Literal
1717
1818from babel import localedata
1919from babel .plural import PluralRule
2020
21- __all__ = ['UnknownLocaleError' , 'Locale' , 'default_locale' , 'negotiate_locale' ,
22- 'parse_locale' ]
21+ __all__ = [
22+ 'Locale' ,
23+ 'UnknownLocaleError' ,
24+ 'default_locale' ,
25+ 'get_global' ,
26+ 'get_locale_identifier' ,
27+ 'negotiate_locale' ,
28+ 'parse_locale' ,
29+ ]
2330
2431if TYPE_CHECKING :
25- from typing_extensions import Literal , TypeAlias
32+ from typing_extensions import TypeAlias
2633
2734 _GLOBAL_KEY : TypeAlias = Literal [
2835 "all_currencies" ,
@@ -259,6 +266,7 @@ def negotiate(
259266 :param preferred: the list of locale identifiers preferred by the user
260267 :param available: the list of locale identifiers available
261268 :param aliases: a dictionary of aliases for locale identifiers
269+ :param sep: separator for parsing; e.g. Windows tends to use '-' instead of '_'.
262270 """
263271 identifier = negotiate_locale (preferred , available , sep = sep ,
264272 aliases = aliases )
@@ -269,7 +277,7 @@ def negotiate(
269277 @classmethod
270278 def parse (
271279 cls ,
272- identifier : str | Locale | None ,
280+ identifier : Locale | str | None ,
273281 sep : str = '_' ,
274282 resolve_likely_subtags : bool = True ,
275283 ) -> Locale :
@@ -286,8 +294,8 @@ def parse(
286294 Locale('de', territory='DE')
287295
288296 If the `identifier` parameter is neither of these, such as `None`
289- e.g. because a default locale identifier could not be determined,
290- a `TypeError` is raised:
297+ or an empty string, e.g. because a default locale identifier
298+ could not be determined, a `TypeError` is raised:
291299
292300 >>> Locale.parse(None)
293301 Traceback (most recent call last):
@@ -325,10 +333,23 @@ def parse(
325333 :raise `UnknownLocaleError`: if no locale data is available for the
326334 requested locale
327335 :raise `TypeError`: if the identifier is not a string or a `Locale`
336+ :raise `ValueError`: if the identifier is not a valid string
328337 """
329338 if isinstance (identifier , Locale ):
330339 return identifier
331- elif not isinstance (identifier , str ):
340+
341+ if not identifier :
342+ msg = (
343+ f"Empty locale identifier value: { identifier !r} \n \n "
344+ f"If you didn't explicitly pass an empty value to a Babel function, "
345+ f"this could be caused by there being no suitable locale environment "
346+ f"variables for the API you tried to use."
347+ )
348+ if isinstance (identifier , str ):
349+ raise ValueError (msg ) # `parse_locale` would raise a ValueError, so let's do that here
350+ raise TypeError (msg )
351+
352+ if not isinstance (identifier , str ):
332353 raise TypeError (f"Unexpected value for identifier: { identifier !r} " )
333354
334355 parts = parse_locale (identifier , sep = sep )
@@ -562,7 +583,7 @@ def languages(self) -> localedata.LocaleDataDict:
562583 >>> Locale('de', 'DE').languages['ja']
563584 u'Japanisch'
564585
565- See `ISO 639 <http ://www.loc.gov/standards/iso639-2/>`_ for
586+ See `ISO 639 <https ://www.loc.gov/standards/iso639-2/>`_ for
566587 more information.
567588 """
568589 return self ._data ['languages' ]
@@ -574,7 +595,7 @@ def scripts(self) -> localedata.LocaleDataDict:
574595 >>> Locale('en', 'US').scripts['Hira']
575596 u'Hiragana'
576597
577- See `ISO 15924 <http ://www.evertype.com/standards /iso15924/>`_
598+ See `ISO 15924 <https ://www.unicode.org /iso15924/>`_
578599 for more information.
579600 """
580601 return self ._data ['scripts' ]
@@ -586,7 +607,7 @@ def territories(self) -> localedata.LocaleDataDict:
586607 >>> Locale('es', 'CO').territories['DE']
587608 u'Alemania'
588609
589- See `ISO 3166 <http ://www.iso .org/iso/en/prods-services/iso3166ma/ >`_
610+ See `ISO 3166 <https ://en.wikipedia .org/wiki/ISO_3166 >`_
590611 for more information.
591612 """
592613 return self ._data ['territories' ]
@@ -1068,7 +1089,10 @@ def unit_display_names(self) -> localedata.LocaleDataDict:
10681089 return self ._data ['unit_display_names' ]
10691090
10701091
1071- 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 :
10721096 """Returns the system default locale for a given category, based on
10731097 environment variables.
10741098
@@ -1092,11 +1116,22 @@ def default_locale(category: str | None = None, aliases: Mapping[str, str] = LOC
10921116 - ``LC_CTYPE``
10931117 - ``LANG``
10941118
1095- :param category: one of the ``LC_XXX`` environment variable names
1119+ :param category: one or more of the ``LC_XXX`` environment variable names
10961120 :param aliases: a dictionary of aliases for locale identifiers
10971121 """
1098- varnames = (category , 'LANGUAGE' , 'LC_ALL' , 'LC_CTYPE' , 'LANG' )
1099- 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
11001135 locale = os .getenv (name )
11011136 if locale :
11021137 if name == 'LANGUAGE' and ':' in locale :
@@ -1235,6 +1270,8 @@ def parse_locale(
12351270 :raise `ValueError`: if the string does not appear to be a valid locale
12361271 identifier
12371272 """
1273+ if not identifier :
1274+ raise ValueError ("empty locale identifier" )
12381275 identifier , _ , modifier = identifier .partition ('@' )
12391276 if '.' in identifier :
12401277 # this is probably the charset/encoding, which we don't care about
0 commit comments