Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/babel/core.py: 37%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2babel.core
3~~~~~~~~~~
5Core locale representation and locale data access.
7:copyright: (c) 2013-2026 by the Babel Team.
8:license: BSD, see LICENSE for more details.
9"""
11from __future__ import annotations
13import os
14import pickle
15from collections.abc import Iterable, Mapping
16from typing import TYPE_CHECKING, Any, Literal
18from babel import localedata
19from babel.plural import PluralRule
21__all__ = [
22 'Locale',
23 'UnknownLocaleError',
24 'default_locale',
25 'get_cldr_version',
26 'get_global',
27 'get_locale_identifier',
28 'negotiate_locale',
29 'parse_locale',
30]
32if TYPE_CHECKING:
33 from typing_extensions import TypeAlias
35 _GLOBAL_KEY: TypeAlias = Literal[
36 "all_currencies",
37 "cldr",
38 "currency_fractions",
39 "language_aliases",
40 "likely_subtags",
41 "meta_zones",
42 "parent_exceptions",
43 "script_aliases",
44 "territory_aliases",
45 "territory_currencies",
46 "territory_languages",
47 "territory_zones",
48 "variant_aliases",
49 "windows_zone_mapping",
50 "zone_aliases",
51 "zone_territories",
52 ]
54 _global_data: Mapping[_GLOBAL_KEY, Mapping[str, Any]] | None
56_global_data = None
57_default_plural_rule = PluralRule({})
60def _raise_no_data_error():
61 raise RuntimeError(
62 'The babel data files are not available. '
63 'This usually happens because you are using '
64 'a source checkout from Babel and you did '
65 'not build the data files. Just make sure '
66 'to run "python setup.py import_cldr" before '
67 'installing the library.',
68 )
71def get_global(key: _GLOBAL_KEY) -> Mapping[str, Any]:
72 """Return the dictionary for the given key in the global data.
74 The global data is stored in the ``babel/global.dat`` file and contains
75 information independent of individual locales.
77 >>> get_global('zone_aliases')['UTC']
78 'Etc/UTC'
79 >>> get_global('zone_territories')['Europe/Berlin']
80 'DE'
82 The keys available are:
84 - ``all_currencies``
85 - ``cldr`` (metadata)
86 - ``currency_fractions``
87 - ``language_aliases``
88 - ``likely_subtags``
89 - ``parent_exceptions``
90 - ``script_aliases``
91 - ``territory_aliases``
92 - ``territory_currencies``
93 - ``territory_languages``
94 - ``territory_zones``
95 - ``variant_aliases``
96 - ``windows_zone_mapping``
97 - ``zone_aliases``
98 - ``zone_territories``
100 .. note:: The internal structure of the data may change between versions.
102 .. versionadded:: 0.9
104 :param key: the data key
105 """
106 global _global_data
107 if _global_data is None:
108 dirname = os.path.join(os.path.dirname(__file__))
109 filename = os.path.join(dirname, 'global.dat')
110 if not os.path.isfile(filename):
111 _raise_no_data_error()
112 with open(filename, 'rb') as fileobj:
113 _global_data = pickle.load(fileobj)
114 assert _global_data is not None
115 return _global_data.get(key, {})
118LOCALE_ALIASES = {
119 'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ',
120 'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES',
121 'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES',
122 'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT',
123 'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV',
124 'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL',
125 'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI',
126 'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA',
127} # fmt: skip
130class UnknownLocaleError(Exception):
131 """Exception thrown when a locale is requested for which no locale data
132 is available.
133 """
135 def __init__(self, identifier: str) -> None:
136 """Create the exception.
138 :param identifier: the identifier string of the unsupported locale
139 """
140 Exception.__init__(self, f"unknown locale {identifier!r}")
142 #: The identifier of the locale that could not be found.
143 self.identifier = identifier
146class Locale:
147 """Representation of a specific locale.
149 >>> locale = Locale('en', 'US')
150 >>> repr(locale)
151 "Locale('en', territory='US')"
152 >>> locale.display_name
153 'English (United States)'
155 A `Locale` object can also be instantiated from a raw locale string:
157 >>> locale = Locale.parse('en-US', sep='-')
158 >>> repr(locale)
159 "Locale('en', territory='US')"
161 `Locale` objects provide access to a collection of locale data, such as
162 territory and language names, number and date format patterns, and more:
164 >>> locale.number_symbols['latn']['decimal']
165 '.'
167 If a locale is requested for which no locale data is available, an
168 `UnknownLocaleError` is raised:
170 >>> Locale.parse('en_XX')
171 Traceback (most recent call last):
172 ...
173 UnknownLocaleError: unknown locale 'en_XX'
175 For more information see :rfc:`3066`.
176 """
178 def __init__(
179 self,
180 language: str,
181 territory: str | None = None,
182 script: str | None = None,
183 variant: str | None = None,
184 modifier: str | None = None,
185 ) -> None:
186 """Initialize the locale object from the given identifier components.
188 >>> locale = Locale('en', 'US')
189 >>> locale.language
190 'en'
191 >>> locale.territory
192 'US'
194 :param language: the language code
195 :param territory: the territory (country or region) code
196 :param script: the script code
197 :param variant: the variant code
198 :param modifier: a modifier (following the '@' symbol, sometimes called '@variant')
199 :raise `UnknownLocaleError`: if no locale data is available for the
200 requested locale
201 """
202 #: the language code
203 self.language = language
204 #: the territory (country or region) code
205 self.territory = territory
206 #: the script code
207 self.script = script
208 #: the variant code
209 self.variant = variant
210 #: the modifier
211 self.modifier = modifier
212 self.__data: localedata.LocaleDataDict | None = None
214 identifier = str(self)
215 identifier_without_modifier = identifier.partition('@')[0]
216 if localedata.exists(identifier):
217 self.__data_identifier = identifier
218 elif localedata.exists(identifier_without_modifier):
219 self.__data_identifier = identifier_without_modifier
220 else:
221 raise UnknownLocaleError(identifier)
223 @classmethod
224 def default(
225 cls,
226 category: str | None = None,
227 aliases: Mapping[str, str] = LOCALE_ALIASES,
228 ) -> Locale:
229 """Return the system default locale for the specified category.
231 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
232 ... os.environ[name] = ''
233 >>> os.environ['LANG'] = 'fr_FR.UTF-8'
234 >>> Locale.default('LC_MESSAGES')
235 Locale('fr', territory='FR')
237 The following fallbacks to the variable are always considered:
239 - ``LANGUAGE``
240 - ``LC_ALL``
241 - ``LC_CTYPE``
242 - ``LANG``
244 :param category: one of the ``LC_XXX`` environment variable names
245 :param aliases: a dictionary of aliases for locale identifiers
246 """
247 # XXX: use likely subtag expansion here instead of the
248 # aliases dictionary.
249 locale_string = default_locale(category, aliases=aliases)
250 return cls.parse(locale_string)
252 @classmethod
253 def negotiate(
254 cls,
255 preferred: Iterable[str],
256 available: Iterable[str],
257 sep: str = '_',
258 aliases: Mapping[str, str] = LOCALE_ALIASES,
259 ) -> Locale | None:
260 """Find the best match between available and requested locale strings.
262 >>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
263 Locale('de', territory='DE')
264 >>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de'])
265 Locale('de')
266 >>> Locale.negotiate(['de_DE', 'de'], ['en_US'])
268 You can specify the character used in the locale identifiers to separate
269 the different components. This separator is applied to both lists. Also,
270 case is ignored in the comparison:
272 >>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-')
273 Locale('de', territory='DE')
275 :param preferred: the list of locale identifiers preferred by the user
276 :param available: the list of locale identifiers available
277 :param aliases: a dictionary of aliases for locale identifiers
278 :param sep: separator for parsing; e.g. Windows tends to use '-' instead of '_'.
279 """
280 identifier = negotiate_locale(preferred, available, sep=sep, aliases=aliases)
281 if identifier:
282 return Locale.parse(identifier, sep=sep)
283 return None
285 @classmethod
286 def parse(
287 cls,
288 identifier: Locale | str | None,
289 sep: str = '_',
290 resolve_likely_subtags: bool = True,
291 ) -> Locale:
292 """Create a `Locale` instance for the given locale identifier.
294 >>> l = Locale.parse('de-DE', sep='-')
295 >>> l.display_name
296 'Deutsch (Deutschland)'
298 If the `identifier` parameter is not a string, but actually a `Locale`
299 object, that object is returned:
301 >>> Locale.parse(l)
302 Locale('de', territory='DE')
304 If the `identifier` parameter is neither of these, such as `None`
305 or an empty string, e.g. because a default locale identifier
306 could not be determined, a `TypeError` is raised:
308 >>> Locale.parse(None)
309 Traceback (most recent call last):
310 ...
311 TypeError: ...
313 This also can perform resolving of likely subtags which it does
314 by default. This is for instance useful to figure out the most
315 likely locale for a territory you can use ``'und'`` as the
316 language tag:
318 >>> Locale.parse('und_AT')
319 Locale('de', territory='AT')
321 Modifiers are optional, and always at the end, separated by "@":
323 >>> Locale.parse('de_AT@euro')
324 Locale('de', territory='AT', modifier='euro')
326 :param identifier: the locale identifier string
327 :param sep: optional component separator
328 :param resolve_likely_subtags: if this is specified then a locale will
329 have its likely subtag resolved if the
330 locale otherwise does not exist. For
331 instance ``zh_TW`` by itself is not a
332 locale that exists but Babel can
333 automatically expand it to the full
334 form of ``zh_hant_TW``. Note that this
335 expansion is only taking place if no
336 locale exists otherwise. For instance
337 there is a locale ``en`` that can exist
338 by itself.
339 :raise `ValueError`: if the string does not appear to be a valid locale
340 identifier
341 :raise `UnknownLocaleError`: if no locale data is available for the
342 requested locale
343 :raise `TypeError`: if the identifier is not a string or a `Locale`
344 :raise `ValueError`: if the identifier is not a valid string
345 """
346 if isinstance(identifier, Locale):
347 return identifier
349 if not identifier:
350 msg = (
351 f"Empty locale identifier value: {identifier!r}\n\n"
352 f"If you didn't explicitly pass an empty value to a Babel function, "
353 f"this could be caused by there being no suitable locale environment "
354 f"variables for the API you tried to use."
355 )
356 if isinstance(identifier, str):
357 # `parse_locale` would raise a ValueError, so let's do that here
358 raise ValueError(msg)
359 raise TypeError(msg)
361 if not isinstance(identifier, str):
362 raise TypeError(f"Unexpected value for identifier: {identifier!r}")
364 parts = parse_locale(identifier, sep=sep)
365 input_id = get_locale_identifier(parts)
367 def _try_load(parts):
368 try:
369 return cls(*parts)
370 except UnknownLocaleError:
371 return None
373 def _try_load_reducing(parts):
374 # Success on first hit, return it.
375 locale = _try_load(parts)
376 if locale is not None:
377 return locale
379 # Now try without script and variant
380 locale = _try_load(parts[:2])
381 if locale is not None:
382 return locale
384 locale = _try_load(parts)
385 if locale is not None:
386 return locale
387 if not resolve_likely_subtags:
388 raise UnknownLocaleError(input_id)
390 # From here onwards is some very bad likely subtag resolving. This
391 # whole logic is not entirely correct but good enough (tm) for the
392 # time being. This has been added so that zh_TW does not cause
393 # errors for people when they upgrade. Later we should properly
394 # implement ICU like fuzzy locale objects and provide a way to
395 # maximize and minimize locale tags.
397 if len(parts) == 5:
398 language, territory, script, variant, modifier = parts
399 else:
400 language, territory, script, variant = parts
401 modifier = None
402 language = get_global('language_aliases').get(language, language)
403 territory = get_global('territory_aliases').get(territory or '', (territory,))[0]
404 script = get_global('script_aliases').get(script or '', script)
405 variant = get_global('variant_aliases').get(variant or '', variant)
407 if territory == 'ZZ':
408 territory = None
409 if script == 'Zzzz':
410 script = None
412 parts = language, territory, script, variant, modifier
414 # First match: try the whole identifier
415 new_id = get_locale_identifier(parts)
416 likely_subtag = get_global('likely_subtags').get(new_id)
417 if likely_subtag is not None:
418 locale = _try_load_reducing(parse_locale(likely_subtag))
419 if locale is not None:
420 return locale
422 # If we did not find anything so far, try again with a
423 # simplified identifier that is just the language
424 likely_subtag = get_global('likely_subtags').get(language)
425 if likely_subtag is not None:
426 parts2 = parse_locale(likely_subtag)
427 if len(parts2) == 5:
428 language2, _, script2, variant2, modifier2 = parts2
429 else:
430 language2, _, script2, variant2 = parts2
431 modifier2 = None
432 locale = _try_load_reducing(
433 (language2, territory, script2, variant2, modifier2),
434 )
435 if locale is not None:
436 return locale
438 raise UnknownLocaleError(input_id)
440 def __eq__(self, other: object) -> bool:
441 for key in ('language', 'territory', 'script', 'variant', 'modifier'):
442 if not hasattr(other, key):
443 return False
444 return (
445 self.language == getattr(other, 'language') # noqa: B009
446 and self.territory == getattr(other, 'territory') # noqa: B009
447 and self.script == getattr(other, 'script') # noqa: B009
448 and self.variant == getattr(other, 'variant') # noqa: B009
449 and self.modifier == getattr(other, 'modifier') # noqa: B009
450 )
452 def __ne__(self, other: object) -> bool:
453 return not self.__eq__(other)
455 def __hash__(self) -> int:
456 return hash((self.language, self.territory, self.script, self.variant, self.modifier))
458 def __repr__(self) -> str:
459 parameters = ['']
460 for key in ('territory', 'script', 'variant', 'modifier'):
461 value = getattr(self, key)
462 if value is not None:
463 parameters.append(f"{key}={value!r}")
464 return f"Locale({self.language!r}{', '.join(parameters)})"
466 def __str__(self) -> str:
467 return get_locale_identifier(
468 (self.language, self.territory, self.script, self.variant, self.modifier),
469 )
471 @property
472 def _data(self) -> localedata.LocaleDataDict:
473 if self.__data is None:
474 self.__data = localedata.get_locale_data(self.__data_identifier)
475 return self.__data
477 def get_display_name(self, locale: Locale | str | None = None) -> str | None:
478 """Return the display name of the locale using the given locale.
480 The display name will include the language, territory, script, and
481 variant, if those are specified.
483 >>> Locale('zh', 'CN', script='Hans').get_display_name('en')
484 'Chinese (Simplified, China)'
486 Modifiers are currently passed through verbatim:
488 >>> Locale('it', 'IT', modifier='euro').get_display_name('en')
489 'Italian (Italy, euro)'
491 :param locale: the locale to use
492 """
493 if locale is None:
494 locale = self
495 locale = Locale.parse(locale)
496 retval = locale.languages.get(self.language)
497 if retval and (self.territory or self.script or self.variant):
498 details = []
499 if self.script:
500 details.append(locale.scripts.get(self.script))
501 if self.territory:
502 details.append(locale.territories.get(self.territory))
503 if self.variant:
504 details.append(locale.variants.get(self.variant))
505 if self.modifier:
506 details.append(self.modifier)
507 detail_string = ', '.join(atom for atom in details if atom)
508 if detail_string:
509 retval += f" ({detail_string})"
510 return retval
512 @property
513 def display_name(self) -> str | None:
514 """
515 The localized display name of the locale.
517 >>> Locale('en').display_name
518 'English'
519 >>> Locale('en', 'US').display_name
520 'English (United States)'
521 >>> Locale('sv').display_name
522 'svenska'
523 """
524 return self.get_display_name()
526 def get_language_name(self, locale: Locale | str | None = None) -> str | None:
527 """Return the language of this locale in the given locale.
529 >>> Locale('zh', 'CN', script='Hans').get_language_name('de')
530 'Chinesisch'
532 .. versionadded:: 1.0
534 :param locale: the locale to use
535 """
536 if locale is None:
537 locale = self
538 locale = Locale.parse(locale)
539 return locale.languages.get(self.language)
541 @property
542 def language_name(self) -> str | None:
543 """
544 The localized language name of the locale.
546 >>> Locale('en', 'US').language_name
547 'English'
548 """
549 return self.get_language_name()
551 def get_territory_name(self, locale: Locale | str | None = None) -> str | None:
552 """Return the territory name in the given locale."""
553 if locale is None:
554 locale = self
555 locale = Locale.parse(locale)
556 return locale.territories.get(self.territory or '')
558 @property
559 def territory_name(self) -> str | None:
560 """
561 The localized territory name of the locale if available.
563 >>> Locale('de', 'DE').territory_name
564 'Deutschland'
565 """
566 return self.get_territory_name()
568 def get_script_name(self, locale: Locale | str | None = None) -> str | None:
569 """Return the script name in the given locale."""
570 if locale is None:
571 locale = self
572 locale = Locale.parse(locale)
573 return locale.scripts.get(self.script or '')
575 @property
576 def script_name(self) -> str | None:
577 """
578 The localized script name of the locale if available.
580 >>> Locale('sr', 'ME', script='Latn').script_name
581 'latinica'
582 """
583 return self.get_script_name()
585 @property
586 def english_name(self) -> str | None:
587 """The english display name of the locale.
589 >>> Locale('de').english_name
590 'German'
591 >>> Locale('de', 'DE').english_name
592 'German (Germany)'
593 """
594 return self.get_display_name(Locale('en'))
596 # { General Locale Display Names
598 @property
599 def languages(self) -> localedata.LocaleDataDict:
600 """Mapping of language codes to translated language names.
602 >>> Locale('de', 'DE').languages['ja']
603 'Japanisch'
605 See `ISO 639 <https://www.loc.gov/standards/iso639-2/>`_ for
606 more information.
607 """
608 return self._data['languages']
610 @property
611 def scripts(self) -> localedata.LocaleDataDict:
612 """Mapping of script codes to translated script names.
614 >>> Locale('en', 'US').scripts['Hira']
615 'Hiragana'
617 See `ISO 15924 <https://www.unicode.org/iso15924/>`_
618 for more information.
619 """
620 return self._data['scripts']
622 @property
623 def territories(self) -> localedata.LocaleDataDict:
624 """Mapping of script codes to translated script names.
626 >>> Locale('es', 'CO').territories['DE']
627 'Alemania'
629 See `ISO 3166 <https://en.wikipedia.org/wiki/ISO_3166>`_
630 for more information.
631 """
632 return self._data['territories']
634 @property
635 def variants(self) -> localedata.LocaleDataDict:
636 """Mapping of script codes to translated script names.
638 >>> Locale('de', 'DE').variants['1901']
639 'Alte deutsche Rechtschreibung'
640 """
641 return self._data['variants']
643 # { Number Formatting
645 @property
646 def currencies(self) -> localedata.LocaleDataDict:
647 """Mapping of currency codes to translated currency names. This
648 only returns the generic form of the currency name, not the count
649 specific one. If an actual number is requested use the
650 :func:`babel.numbers.get_currency_name` function.
652 >>> Locale('en').currencies['COP']
653 'Colombian Peso'
654 >>> Locale('de', 'DE').currencies['COP']
655 'Kolumbianischer Peso'
656 """
657 return self._data['currency_names']
659 @property
660 def currency_symbols(self) -> localedata.LocaleDataDict:
661 """Mapping of currency codes to symbols.
663 >>> Locale('en', 'US').currency_symbols['USD']
664 '$'
665 >>> Locale('es', 'CO').currency_symbols['USD']
666 'US$'
667 """
668 return self._data['currency_symbols']
670 @property
671 def number_symbols(self) -> localedata.LocaleDataDict:
672 """Symbols used in number formatting by number system.
674 .. note:: The format of the value returned may change between
675 Babel versions.
677 >>> Locale('fr', 'FR').number_symbols["latn"]['decimal']
678 ','
679 >>> Locale('fa', 'IR').number_symbols["arabext"]['decimal']
680 '٫'
681 >>> Locale('fa', 'IR').number_symbols["latn"]['decimal']
682 '.'
683 """
684 return self._data['number_symbols']
686 @property
687 def other_numbering_systems(self) -> localedata.LocaleDataDict:
688 """
689 Mapping of other numbering systems available for the locale.
690 See: https://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems
692 >>> Locale('el', 'GR').other_numbering_systems['traditional']
693 'grek'
695 .. note:: The format of the value returned may change between
696 Babel versions.
697 """
698 return self._data['numbering_systems']
700 @property
701 def default_numbering_system(self) -> str:
702 """The default numbering system used by the locale.
703 >>> Locale('el', 'GR').default_numbering_system
704 'latn'
705 """
706 return self._data['default_numbering_system']
708 @property
709 def decimal_formats(self) -> localedata.LocaleDataDict:
710 """Locale patterns for decimal number formatting.
712 .. note:: The format of the value returned may change between
713 Babel versions.
715 >>> Locale('en', 'US').decimal_formats[None]
716 <NumberPattern '#,##0.###'>
717 """
718 return self._data['decimal_formats']
720 @property
721 def compact_decimal_formats(self) -> localedata.LocaleDataDict:
722 """Locale patterns for compact decimal number formatting.
724 .. note:: The format of the value returned may change between
725 Babel versions.
727 >>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"]
728 <NumberPattern '0K'>
729 """
730 return self._data['compact_decimal_formats']
732 @property
733 def currency_formats(self) -> localedata.LocaleDataDict:
734 """Locale patterns for currency number formatting.
736 .. note:: The format of the value returned may change between
737 Babel versions.
739 >>> Locale('en', 'US').currency_formats['standard']
740 <NumberPattern '\\xa4#,##0.00'>
741 >>> Locale('en', 'US').currency_formats['accounting']
742 <NumberPattern '\\xa4#,##0.00;(\\xa4#,##0.00)'>
743 """
744 return self._data['currency_formats']
746 @property
747 def compact_currency_formats(self) -> localedata.LocaleDataDict:
748 """Locale patterns for compact currency number formatting.
750 .. note:: The format of the value returned may change between
751 Babel versions.
753 >>> Locale('en', 'US').compact_currency_formats["short"]["one"]["1000"]
754 <NumberPattern '¤0K'>
755 """
756 return self._data['compact_currency_formats']
758 @property
759 def percent_formats(self) -> localedata.LocaleDataDict:
760 """Locale patterns for percent number formatting.
762 .. note:: The format of the value returned may change between
763 Babel versions.
765 >>> Locale('en', 'US').percent_formats[None]
766 <NumberPattern '#,##0%'>
767 """
768 return self._data['percent_formats']
770 @property
771 def scientific_formats(self) -> localedata.LocaleDataDict:
772 """Locale patterns for scientific number formatting.
774 .. note:: The format of the value returned may change between
775 Babel versions.
777 >>> Locale('en', 'US').scientific_formats[None]
778 <NumberPattern '#E0'>
779 """
780 return self._data['scientific_formats']
782 # { Calendar Information and Date Formatting
784 @property
785 def periods(self) -> localedata.LocaleDataDict:
786 """Locale display names for day periods (AM/PM).
788 >>> Locale('en', 'US').periods['am']
789 'AM'
790 """
791 try:
792 return self._data['day_periods']['stand-alone']['wide']
793 except KeyError:
794 return localedata.LocaleDataDict({}) # pragma: no cover
796 @property
797 def day_periods(self) -> localedata.LocaleDataDict:
798 """Locale display names for various day periods (not necessarily only AM/PM).
800 These are not meant to be used without the relevant `day_period_rules`.
801 """
802 return self._data['day_periods']
804 @property
805 def day_period_rules(self) -> localedata.LocaleDataDict:
806 """Day period rules for the locale. Used by `get_period_id`."""
807 return self._data.get('day_period_rules', localedata.LocaleDataDict({}))
809 @property
810 def days(self) -> localedata.LocaleDataDict:
811 """Locale display names for weekdays.
813 >>> Locale('de', 'DE').days['format']['wide'][3]
814 'Donnerstag'
815 """
816 return self._data['days']
818 @property
819 def months(self) -> localedata.LocaleDataDict:
820 """Locale display names for months.
822 >>> Locale('de', 'DE').months['format']['wide'][10]
823 'Oktober'
824 """
825 return self._data['months']
827 @property
828 def quarters(self) -> localedata.LocaleDataDict:
829 """Locale display names for quarters.
831 >>> Locale('de', 'DE').quarters['format']['wide'][1]
832 '1. Quartal'
833 """
834 return self._data['quarters']
836 @property
837 def eras(self) -> localedata.LocaleDataDict:
838 """Locale display names for eras.
840 .. note:: The format of the value returned may change between
841 Babel versions.
843 >>> Locale('en', 'US').eras['wide'][1]
844 'Anno Domini'
845 >>> Locale('en', 'US').eras['abbreviated'][0]
846 'BC'
847 """
848 return self._data['eras']
850 @property
851 def time_zones(self) -> localedata.LocaleDataDict:
852 """Locale display names for time zones.
854 .. note:: The format of the value returned may change between
855 Babel versions.
857 >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight']
858 'British Summer Time'
859 >>> Locale('en', 'US').time_zones['America/St_Johns']['city']
860 'St. John’s'
861 """
862 return self._data['time_zones']
864 @property
865 def meta_zones(self) -> localedata.LocaleDataDict:
866 """Locale display names for meta time zones.
868 Meta time zones are basically groups of different Olson time zones that
869 have the same GMT offset and daylight savings time.
871 .. note:: The format of the value returned may change between
872 Babel versions.
874 >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
875 'Central European Summer Time'
877 .. versionadded:: 0.9
878 """
879 return self._data['meta_zones']
881 @property
882 def zone_formats(self) -> localedata.LocaleDataDict:
883 """Patterns related to the formatting of time zones.
885 .. note:: The format of the value returned may change between
886 Babel versions.
888 >>> Locale('en', 'US').zone_formats['fallback']
889 '%(1)s (%(0)s)'
890 >>> Locale('pt', 'BR').zone_formats['region']
891 'Horário %s'
893 .. versionadded:: 0.9
894 """
895 return self._data['zone_formats']
897 @property
898 def first_week_day(self) -> int:
899 """The first day of a week, with 0 being Monday.
901 >>> Locale('de', 'DE').first_week_day
902 0
903 >>> Locale('en', 'US').first_week_day
904 6
905 """
906 return self._data['week_data']['first_day']
908 @property
909 def weekend_start(self) -> int:
910 """The day the weekend starts, with 0 being Monday.
912 >>> Locale('de', 'DE').weekend_start
913 5
914 """
915 return self._data['week_data']['weekend_start']
917 @property
918 def weekend_end(self) -> int:
919 """The day the weekend ends, with 0 being Monday.
921 >>> Locale('de', 'DE').weekend_end
922 6
923 """
924 return self._data['week_data']['weekend_end']
926 @property
927 def min_week_days(self) -> int:
928 """The minimum number of days in a week so that the week is counted as
929 the first week of a year or month.
931 >>> Locale('de', 'DE').min_week_days
932 4
933 """
934 return self._data['week_data']['min_days']
936 @property
937 def date_formats(self) -> localedata.LocaleDataDict:
938 """Locale patterns for date formatting.
940 .. note:: The format of the value returned may change between
941 Babel versions.
943 >>> Locale('en', 'US').date_formats['short']
944 <DateTimePattern 'M/d/yy'>
945 >>> Locale('fr', 'FR').date_formats['long']
946 <DateTimePattern 'd MMMM y'>
947 """
948 return self._data['date_formats']
950 @property
951 def time_formats(self) -> localedata.LocaleDataDict:
952 """Locale patterns for time formatting.
954 .. note:: The format of the value returned may change between
955 Babel versions.
957 >>> Locale('en', 'US').time_formats['short']
958 <DateTimePattern 'h:mm\\u202fa'>
959 >>> Locale('fr', 'FR').time_formats['long']
960 <DateTimePattern 'HH:mm:ss z'>
961 """
962 return self._data['time_formats']
964 @property
965 def datetime_formats(self) -> localedata.LocaleDataDict:
966 """Locale patterns for datetime formatting.
968 .. note:: The format of the value returned may change between
969 Babel versions.
971 >>> Locale('en').datetime_formats['full']
972 '{1}, {0}'
973 >>> Locale('th').datetime_formats['medium']
974 '{1} {0}'
975 """
976 return self._data['datetime_formats']
978 @property
979 def datetime_skeletons(self) -> localedata.LocaleDataDict:
980 """Locale patterns for formatting parts of a datetime.
982 >>> Locale('en').datetime_skeletons['MEd']
983 <DateTimePattern 'E, M/d'>
984 >>> Locale('fr').datetime_skeletons['MEd']
985 <DateTimePattern 'E dd/MM'>
986 >>> Locale('fr').datetime_skeletons['H']
987 <DateTimePattern "HH 'h'">
988 """
989 return self._data['datetime_skeletons']
991 @property
992 def interval_formats(self) -> localedata.LocaleDataDict:
993 """Locale patterns for interval formatting.
995 .. note:: The format of the value returned may change between
996 Babel versions.
998 How to format date intervals in Finnish when the day is the
999 smallest changing component:
1001 >>> Locale('fi_FI').interval_formats['MEd']['d']
1002 ['E d.\\u2009–\\u2009', 'E d.M.']
1004 .. seealso::
1006 The primary API to use this data is :py:func:`babel.dates.format_interval`.
1009 :rtype: dict[str, dict[str, list[str]]]
1010 """
1011 return self._data['interval_formats']
1013 @property
1014 def plural_form(self) -> PluralRule:
1015 """Plural rules for the locale.
1017 >>> Locale('en').plural_form(1)
1018 'one'
1019 >>> Locale('en').plural_form(0)
1020 'other'
1021 >>> Locale('fr').plural_form(0)
1022 'one'
1023 >>> Locale('ru').plural_form(100)
1024 'many'
1025 """
1026 return self._data.get('plural_form', _default_plural_rule)
1028 @property
1029 def list_patterns(self) -> localedata.LocaleDataDict:
1030 """Patterns for generating lists
1032 .. note:: The format of the value returned may change between
1033 Babel versions.
1035 >>> Locale('en').list_patterns['standard']['start']
1036 '{0}, {1}'
1037 >>> Locale('en').list_patterns['standard']['end']
1038 '{0}, and {1}'
1039 >>> Locale('en_GB').list_patterns['standard']['end']
1040 '{0} and {1}'
1041 """
1042 return self._data['list_patterns']
1044 @property
1045 def ordinal_form(self) -> PluralRule:
1046 """Plural rules for the locale.
1048 >>> Locale('en').ordinal_form(1)
1049 'one'
1050 >>> Locale('en').ordinal_form(2)
1051 'two'
1052 >>> Locale('en').ordinal_form(3)
1053 'few'
1054 >>> Locale('fr').ordinal_form(2)
1055 'other'
1056 >>> Locale('ru').ordinal_form(100)
1057 'other'
1058 """
1059 return self._data.get('ordinal_form', _default_plural_rule)
1061 @property
1062 def measurement_systems(self) -> localedata.LocaleDataDict:
1063 """Localized names for various measurement systems.
1065 >>> Locale('fr', 'FR').measurement_systems['US']
1066 'américain'
1067 >>> Locale('en', 'US').measurement_systems['US']
1068 'US'
1070 """
1071 return self._data['measurement_systems']
1073 @property
1074 def character_order(self) -> str:
1075 """The text direction for the language.
1077 >>> Locale('de', 'DE').character_order
1078 'left-to-right'
1079 >>> Locale('ar', 'SA').character_order
1080 'right-to-left'
1081 """
1082 return self._data['character_order']
1084 @property
1085 def text_direction(self) -> str:
1086 """The text direction for the language in CSS short-hand form.
1088 >>> Locale('de', 'DE').text_direction
1089 'ltr'
1090 >>> Locale('ar', 'SA').text_direction
1091 'rtl'
1092 """
1093 return ''.join(word[0] for word in self.character_order.split('-'))
1095 @property
1096 def unit_display_names(self) -> localedata.LocaleDataDict:
1097 """Display names for units of measurement.
1099 .. seealso::
1101 You may want to use :py:func:`babel.units.get_unit_name` instead.
1103 .. note:: The format of the value returned may change between
1104 Babel versions.
1106 """
1107 return self._data['unit_display_names']
1110def default_locale(
1111 category: str | tuple[str, ...] | list[str] | None = None,
1112 aliases: Mapping[str, str] = LOCALE_ALIASES,
1113) -> str | None:
1114 """Returns the system default locale for a given category, based on
1115 environment variables.
1117 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']:
1118 ... os.environ[name] = ''
1119 >>> os.environ['LANG'] = 'fr_FR.UTF-8'
1120 >>> default_locale('LC_MESSAGES')
1121 'fr_FR'
1123 The "C" or "POSIX" pseudo-locales are treated as aliases for the
1124 "en_US_POSIX" locale:
1126 >>> os.environ['LC_MESSAGES'] = 'POSIX'
1127 >>> default_locale('LC_MESSAGES')
1128 'en_US_POSIX'
1130 The following fallbacks to the variable are always considered:
1132 - ``LANGUAGE``
1133 - ``LC_ALL``
1134 - ``LC_CTYPE``
1135 - ``LANG``
1137 :param category: one or more of the ``LC_XXX`` environment variable names
1138 :param aliases: a dictionary of aliases for locale identifiers
1139 """
1141 varnames = ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
1142 if category:
1143 if isinstance(category, str):
1144 varnames = (category, *varnames)
1145 elif isinstance(category, (list, tuple)):
1146 varnames = (*category, *varnames)
1147 else:
1148 raise TypeError(f"Invalid type for category: {category!r}")
1150 for name in varnames:
1151 if not name:
1152 continue
1153 locale = os.getenv(name)
1154 if locale:
1155 if name == 'LANGUAGE' and ':' in locale:
1156 # the LANGUAGE variable may contain a colon-separated list of
1157 # language codes; we just pick the language on the list
1158 locale = locale.split(':')[0]
1159 if locale.split('.')[0] in ('C', 'POSIX'):
1160 locale = 'en_US_POSIX'
1161 elif aliases and locale in aliases:
1162 locale = aliases[locale]
1163 try:
1164 return get_locale_identifier(parse_locale(locale))
1165 except ValueError:
1166 pass
1167 return None
1170def negotiate_locale(
1171 preferred: Iterable[str],
1172 available: Iterable[str],
1173 sep: str = '_',
1174 aliases: Mapping[str, str] = LOCALE_ALIASES,
1175) -> str | None:
1176 """Find the best match between available and requested locale strings.
1178 >>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
1179 'de_DE'
1180 >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de'])
1181 'de'
1183 Case is ignored by the algorithm, the result uses the case of the preferred
1184 locale identifier:
1186 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1187 'de_DE'
1189 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1190 'de_DE'
1192 By default, some web browsers unfortunately do not include the territory
1193 in the locale identifier for many locales, and some don't even allow the
1194 user to easily add the territory. So while you may prefer using qualified
1195 locale identifiers in your web-application, they would not normally match
1196 the language-only locale sent by such browsers. To workaround that, this
1197 function uses a default mapping of commonly used language-only locale
1198 identifiers to identifiers including the territory:
1200 >>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US'])
1201 'ja_JP'
1203 Some browsers even use an incorrect or outdated language code, such as "no"
1204 for Norwegian, where the correct locale identifier would actually be "nb_NO"
1205 (Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of
1206 such cases, too:
1208 >>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE'])
1209 'nb_NO'
1211 You can override this default mapping by passing a different `aliases`
1212 dictionary to this function, or you can bypass the behavior althogher by
1213 setting the `aliases` parameter to `None`.
1215 :param preferred: the list of locale strings preferred by the user
1216 :param available: the list of locale strings available
1217 :param sep: character that separates the different parts of the locale
1218 strings
1219 :param aliases: a dictionary of aliases for locale identifiers
1220 """
1221 available = [a.lower() for a in available if a]
1222 for locale in preferred:
1223 ll = locale.lower()
1224 if ll in available:
1225 return locale
1226 if aliases:
1227 alias = aliases.get(ll)
1228 if alias:
1229 alias = alias.replace('_', sep)
1230 if alias.lower() in available:
1231 return alias
1232 parts = locale.split(sep)
1233 if len(parts) > 1 and parts[0].lower() in available:
1234 return parts[0]
1235 return None
1238def parse_locale(
1239 identifier: str,
1240 sep: str = '_',
1241) -> (
1242 tuple[str, str | None, str | None, str | None]
1243 | tuple[str, str | None, str | None, str | None, str | None]
1244):
1245 """Parse a locale identifier into a tuple of the form ``(language,
1246 territory, script, variant, modifier)``.
1248 >>> parse_locale('zh_CN')
1249 ('zh', 'CN', None, None)
1250 >>> parse_locale('zh_Hans_CN')
1251 ('zh', 'CN', 'Hans', None)
1252 >>> parse_locale('ca_es_valencia')
1253 ('ca', 'ES', None, 'VALENCIA')
1254 >>> parse_locale('en_150')
1255 ('en', '150', None, None)
1256 >>> parse_locale('en_us_posix')
1257 ('en', 'US', None, 'POSIX')
1258 >>> parse_locale('it_IT@euro')
1259 ('it', 'IT', None, None, 'euro')
1260 >>> parse_locale('it_IT@custom')
1261 ('it', 'IT', None, None, 'custom')
1262 >>> parse_locale('it_IT@')
1263 ('it', 'IT', None, None)
1265 The default component separator is "_", but a different separator can be
1266 specified using the `sep` parameter.
1268 The optional modifier is always separated with "@" and at the end:
1270 >>> parse_locale('zh-CN', sep='-')
1271 ('zh', 'CN', None, None)
1272 >>> parse_locale('zh-CN@custom', sep='-')
1273 ('zh', 'CN', None, None, 'custom')
1275 If the identifier cannot be parsed into a locale, a `ValueError` exception
1276 is raised:
1278 >>> parse_locale('not_a_LOCALE_String')
1279 Traceback (most recent call last):
1280 ...
1281 ValueError: 'not_a_LOCALE_String' is not a valid locale identifier
1283 Encoding information is removed from the identifier, while modifiers are
1284 kept:
1286 >>> parse_locale('en_US.UTF-8')
1287 ('en', 'US', None, None)
1288 >>> parse_locale('de_DE.iso885915@euro')
1289 ('de', 'DE', None, None, 'euro')
1291 See :rfc:`4646` for more information.
1293 :param identifier: the locale identifier string
1294 :param sep: character that separates the different components of the locale
1295 identifier
1296 :raise `ValueError`: if the string does not appear to be a valid locale
1297 identifier
1298 """
1299 if not identifier:
1300 raise ValueError("empty locale identifier")
1301 identifier, _, modifier = identifier.partition('@')
1302 if '.' in identifier:
1303 # this is probably the charset/encoding, which we don't care about
1304 identifier = identifier.split('.', 1)[0]
1306 parts = identifier.split(sep)
1307 lang = parts.pop(0).lower()
1308 if not lang.isalpha():
1309 raise ValueError(f"expected only letters, got {lang!r}")
1311 script = territory = variant = None
1312 if parts and len(parts[0]) == 4 and parts[0].isalpha():
1313 script = parts.pop(0).title()
1315 if parts:
1316 if len(parts[0]) == 2 and parts[0].isalpha():
1317 territory = parts.pop(0).upper()
1318 elif len(parts[0]) == 3 and parts[0].isdigit():
1319 territory = parts.pop(0)
1321 if parts and (
1322 len(parts[0]) == 4
1323 and parts[0][0].isdigit()
1324 or len(parts[0]) >= 5
1325 and parts[0][0].isalpha()
1326 ):
1327 variant = parts.pop().upper()
1329 if parts:
1330 raise ValueError(f"{identifier!r} is not a valid locale identifier")
1332 # TODO(3.0): always return a 5-tuple
1333 if modifier:
1334 return lang, territory, script, variant, modifier
1335 else:
1336 return lang, territory, script, variant
1339def get_locale_identifier(
1340 tup: tuple[str]
1341 | tuple[str, str | None]
1342 | tuple[str, str | None, str | None]
1343 | tuple[str, str | None, str | None, str | None]
1344 | tuple[str, str | None, str | None, str | None, str | None],
1345 sep: str = "_",
1346) -> str:
1347 """The reverse of :func:`parse_locale`. It creates a locale identifier out
1348 of a ``(language, territory, script, variant, modifier)`` tuple. Items can be set to
1349 ``None`` and trailing ``None``\\s can also be left out of the tuple.
1351 >>> get_locale_identifier(('de', 'DE', None, '1999', 'custom'))
1352 'de_DE_1999@custom'
1353 >>> get_locale_identifier(('fi', None, None, None, 'custom'))
1354 'fi@custom'
1357 .. versionadded:: 1.0
1359 :param tup: the tuple as returned by :func:`parse_locale`.
1360 :param sep: the separator for the identifier.
1361 """
1362 tup = tuple(tup[:5]) # type: ignore # length should be no more than 5
1363 lang, territory, script, variant, modifier = tup + (None,) * (5 - len(tup))
1364 ret = sep.join(filter(None, (lang, script, territory, variant)))
1365 return f'{ret}@{modifier}' if modifier else ret
1368def get_cldr_version() -> str:
1369 """Return the Unicode CLDR version used by this Babel installation.
1371 Generally, you should be able to assume that the return value of this
1372 function is a string representing a version number, e.g. '47'.
1374 >>> get_cldr_version()
1375 '48'
1377 .. versionadded:: 2.18
1379 :rtype: str
1380 """
1381 return str(get_global("cldr")["version"])