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-2025 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.LocaleDataDict(localedata.load(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 display_name = property(
513 get_display_name,
514 doc="""\
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'
524 :type: `unicode`
525 """,
526 )
528 def get_language_name(self, locale: Locale | str | None = None) -> str | None:
529 """Return the language of this locale in the given locale.
531 >>> Locale('zh', 'CN', script='Hans').get_language_name('de')
532 'Chinesisch'
534 .. versionadded:: 1.0
536 :param locale: the locale to use
537 """
538 if locale is None:
539 locale = self
540 locale = Locale.parse(locale)
541 return locale.languages.get(self.language)
543 language_name = property(
544 get_language_name,
545 doc="""\
546 The localized language name of the locale.
548 >>> Locale('en', 'US').language_name
549 'English'
550 """,
551 )
553 def get_territory_name(self, locale: Locale | str | None = None) -> str | None:
554 """Return the territory name in the given locale."""
555 if locale is None:
556 locale = self
557 locale = Locale.parse(locale)
558 return locale.territories.get(self.territory or '')
560 territory_name = property(
561 get_territory_name,
562 doc="""\
563 The localized territory name of the locale if available.
565 >>> Locale('de', 'DE').territory_name
566 'Deutschland'
567 """,
568 )
570 def get_script_name(self, locale: Locale | str | None = None) -> str | None:
571 """Return the script name in the given locale."""
572 if locale is None:
573 locale = self
574 locale = Locale.parse(locale)
575 return locale.scripts.get(self.script or '')
577 script_name = property(
578 get_script_name,
579 doc="""\
580 The localized script name of the locale if available.
582 >>> Locale('sr', 'ME', script='Latn').script_name
583 'latinica'
584 """,
585 )
587 @property
588 def english_name(self) -> str | None:
589 """The english display name of the locale.
591 >>> Locale('de').english_name
592 'German'
593 >>> Locale('de', 'DE').english_name
594 'German (Germany)'
596 :type: `unicode`"""
597 return self.get_display_name(Locale('en'))
599 # { General Locale Display Names
601 @property
602 def languages(self) -> localedata.LocaleDataDict:
603 """Mapping of language codes to translated language names.
605 >>> Locale('de', 'DE').languages['ja']
606 'Japanisch'
608 See `ISO 639 <https://www.loc.gov/standards/iso639-2/>`_ for
609 more information.
610 """
611 return self._data['languages']
613 @property
614 def scripts(self) -> localedata.LocaleDataDict:
615 """Mapping of script codes to translated script names.
617 >>> Locale('en', 'US').scripts['Hira']
618 'Hiragana'
620 See `ISO 15924 <https://www.unicode.org/iso15924/>`_
621 for more information.
622 """
623 return self._data['scripts']
625 @property
626 def territories(self) -> localedata.LocaleDataDict:
627 """Mapping of script codes to translated script names.
629 >>> Locale('es', 'CO').territories['DE']
630 'Alemania'
632 See `ISO 3166 <https://en.wikipedia.org/wiki/ISO_3166>`_
633 for more information.
634 """
635 return self._data['territories']
637 @property
638 def variants(self) -> localedata.LocaleDataDict:
639 """Mapping of script codes to translated script names.
641 >>> Locale('de', 'DE').variants['1901']
642 'Alte deutsche Rechtschreibung'
643 """
644 return self._data['variants']
646 # { Number Formatting
648 @property
649 def currencies(self) -> localedata.LocaleDataDict:
650 """Mapping of currency codes to translated currency names. This
651 only returns the generic form of the currency name, not the count
652 specific one. If an actual number is requested use the
653 :func:`babel.numbers.get_currency_name` function.
655 >>> Locale('en').currencies['COP']
656 'Colombian Peso'
657 >>> Locale('de', 'DE').currencies['COP']
658 'Kolumbianischer Peso'
659 """
660 return self._data['currency_names']
662 @property
663 def currency_symbols(self) -> localedata.LocaleDataDict:
664 """Mapping of currency codes to symbols.
666 >>> Locale('en', 'US').currency_symbols['USD']
667 '$'
668 >>> Locale('es', 'CO').currency_symbols['USD']
669 'US$'
670 """
671 return self._data['currency_symbols']
673 @property
674 def number_symbols(self) -> localedata.LocaleDataDict:
675 """Symbols used in number formatting by number system.
677 .. note:: The format of the value returned may change between
678 Babel versions.
680 >>> Locale('fr', 'FR').number_symbols["latn"]['decimal']
681 ','
682 >>> Locale('fa', 'IR').number_symbols["arabext"]['decimal']
683 '٫'
684 >>> Locale('fa', 'IR').number_symbols["latn"]['decimal']
685 '.'
686 """
687 return self._data['number_symbols']
689 @property
690 def other_numbering_systems(self) -> localedata.LocaleDataDict:
691 """
692 Mapping of other numbering systems available for the locale.
693 See: https://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems
695 >>> Locale('el', 'GR').other_numbering_systems['traditional']
696 'grek'
698 .. note:: The format of the value returned may change between
699 Babel versions.
700 """
701 return self._data['numbering_systems']
703 @property
704 def default_numbering_system(self) -> str:
705 """The default numbering system used by the locale.
706 >>> Locale('el', 'GR').default_numbering_system
707 'latn'
708 """
709 return self._data['default_numbering_system']
711 @property
712 def decimal_formats(self) -> localedata.LocaleDataDict:
713 """Locale patterns for decimal number formatting.
715 .. note:: The format of the value returned may change between
716 Babel versions.
718 >>> Locale('en', 'US').decimal_formats[None]
719 <NumberPattern '#,##0.###'>
720 """
721 return self._data['decimal_formats']
723 @property
724 def compact_decimal_formats(self) -> localedata.LocaleDataDict:
725 """Locale patterns for compact decimal number formatting.
727 .. note:: The format of the value returned may change between
728 Babel versions.
730 >>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"]
731 <NumberPattern '0K'>
732 """
733 return self._data['compact_decimal_formats']
735 @property
736 def currency_formats(self) -> localedata.LocaleDataDict:
737 """Locale patterns for currency number formatting.
739 .. note:: The format of the value returned may change between
740 Babel versions.
742 >>> Locale('en', 'US').currency_formats['standard']
743 <NumberPattern '\\xa4#,##0.00'>
744 >>> Locale('en', 'US').currency_formats['accounting']
745 <NumberPattern '\\xa4#,##0.00;(\\xa4#,##0.00)'>
746 """
747 return self._data['currency_formats']
749 @property
750 def compact_currency_formats(self) -> localedata.LocaleDataDict:
751 """Locale patterns for compact currency number formatting.
753 .. note:: The format of the value returned may change between
754 Babel versions.
756 >>> Locale('en', 'US').compact_currency_formats["short"]["one"]["1000"]
757 <NumberPattern '¤0K'>
758 """
759 return self._data['compact_currency_formats']
761 @property
762 def percent_formats(self) -> localedata.LocaleDataDict:
763 """Locale patterns for percent number formatting.
765 .. note:: The format of the value returned may change between
766 Babel versions.
768 >>> Locale('en', 'US').percent_formats[None]
769 <NumberPattern '#,##0%'>
770 """
771 return self._data['percent_formats']
773 @property
774 def scientific_formats(self) -> localedata.LocaleDataDict:
775 """Locale patterns for scientific number formatting.
777 .. note:: The format of the value returned may change between
778 Babel versions.
780 >>> Locale('en', 'US').scientific_formats[None]
781 <NumberPattern '#E0'>
782 """
783 return self._data['scientific_formats']
785 # { Calendar Information and Date Formatting
787 @property
788 def periods(self) -> localedata.LocaleDataDict:
789 """Locale display names for day periods (AM/PM).
791 >>> Locale('en', 'US').periods['am']
792 'AM'
793 """
794 try:
795 return self._data['day_periods']['stand-alone']['wide']
796 except KeyError:
797 return localedata.LocaleDataDict({}) # pragma: no cover
799 @property
800 def day_periods(self) -> localedata.LocaleDataDict:
801 """Locale display names for various day periods (not necessarily only AM/PM).
803 These are not meant to be used without the relevant `day_period_rules`.
804 """
805 return self._data['day_periods']
807 @property
808 def day_period_rules(self) -> localedata.LocaleDataDict:
809 """Day period rules for the locale. Used by `get_period_id`."""
810 return self._data.get('day_period_rules', localedata.LocaleDataDict({}))
812 @property
813 def days(self) -> localedata.LocaleDataDict:
814 """Locale display names for weekdays.
816 >>> Locale('de', 'DE').days['format']['wide'][3]
817 'Donnerstag'
818 """
819 return self._data['days']
821 @property
822 def months(self) -> localedata.LocaleDataDict:
823 """Locale display names for months.
825 >>> Locale('de', 'DE').months['format']['wide'][10]
826 'Oktober'
827 """
828 return self._data['months']
830 @property
831 def quarters(self) -> localedata.LocaleDataDict:
832 """Locale display names for quarters.
834 >>> Locale('de', 'DE').quarters['format']['wide'][1]
835 '1. Quartal'
836 """
837 return self._data['quarters']
839 @property
840 def eras(self) -> localedata.LocaleDataDict:
841 """Locale display names for eras.
843 .. note:: The format of the value returned may change between
844 Babel versions.
846 >>> Locale('en', 'US').eras['wide'][1]
847 'Anno Domini'
848 >>> Locale('en', 'US').eras['abbreviated'][0]
849 'BC'
850 """
851 return self._data['eras']
853 @property
854 def time_zones(self) -> localedata.LocaleDataDict:
855 """Locale display names for time zones.
857 .. note:: The format of the value returned may change between
858 Babel versions.
860 >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight']
861 'British Summer Time'
862 >>> Locale('en', 'US').time_zones['America/St_Johns']['city']
863 'St. John’s'
864 """
865 return self._data['time_zones']
867 @property
868 def meta_zones(self) -> localedata.LocaleDataDict:
869 """Locale display names for meta time zones.
871 Meta time zones are basically groups of different Olson time zones that
872 have the same GMT offset and daylight savings time.
874 .. note:: The format of the value returned may change between
875 Babel versions.
877 >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
878 'Central European Summer Time'
880 .. versionadded:: 0.9
881 """
882 return self._data['meta_zones']
884 @property
885 def zone_formats(self) -> localedata.LocaleDataDict:
886 """Patterns related to the formatting of time zones.
888 .. note:: The format of the value returned may change between
889 Babel versions.
891 >>> Locale('en', 'US').zone_formats['fallback']
892 '%(1)s (%(0)s)'
893 >>> Locale('pt', 'BR').zone_formats['region']
894 'Horário %s'
896 .. versionadded:: 0.9
897 """
898 return self._data['zone_formats']
900 @property
901 def first_week_day(self) -> int:
902 """The first day of a week, with 0 being Monday.
904 >>> Locale('de', 'DE').first_week_day
905 0
906 >>> Locale('en', 'US').first_week_day
907 6
908 """
909 return self._data['week_data']['first_day']
911 @property
912 def weekend_start(self) -> int:
913 """The day the weekend starts, with 0 being Monday.
915 >>> Locale('de', 'DE').weekend_start
916 5
917 """
918 return self._data['week_data']['weekend_start']
920 @property
921 def weekend_end(self) -> int:
922 """The day the weekend ends, with 0 being Monday.
924 >>> Locale('de', 'DE').weekend_end
925 6
926 """
927 return self._data['week_data']['weekend_end']
929 @property
930 def min_week_days(self) -> int:
931 """The minimum number of days in a week so that the week is counted as
932 the first week of a year or month.
934 >>> Locale('de', 'DE').min_week_days
935 4
936 """
937 return self._data['week_data']['min_days']
939 @property
940 def date_formats(self) -> localedata.LocaleDataDict:
941 """Locale patterns for date formatting.
943 .. note:: The format of the value returned may change between
944 Babel versions.
946 >>> Locale('en', 'US').date_formats['short']
947 <DateTimePattern 'M/d/yy'>
948 >>> Locale('fr', 'FR').date_formats['long']
949 <DateTimePattern 'd MMMM y'>
950 """
951 return self._data['date_formats']
953 @property
954 def time_formats(self) -> localedata.LocaleDataDict:
955 """Locale patterns for time formatting.
957 .. note:: The format of the value returned may change between
958 Babel versions.
960 >>> Locale('en', 'US').time_formats['short']
961 <DateTimePattern 'h:mm\\u202fa'>
962 >>> Locale('fr', 'FR').time_formats['long']
963 <DateTimePattern 'HH:mm:ss z'>
964 """
965 return self._data['time_formats']
967 @property
968 def datetime_formats(self) -> localedata.LocaleDataDict:
969 """Locale patterns for datetime formatting.
971 .. note:: The format of the value returned may change between
972 Babel versions.
974 >>> Locale('en').datetime_formats['full']
975 '{1}, {0}'
976 >>> Locale('th').datetime_formats['medium']
977 '{1} {0}'
978 """
979 return self._data['datetime_formats']
981 @property
982 def datetime_skeletons(self) -> localedata.LocaleDataDict:
983 """Locale patterns for formatting parts of a datetime.
985 >>> Locale('en').datetime_skeletons['MEd']
986 <DateTimePattern 'E, M/d'>
987 >>> Locale('fr').datetime_skeletons['MEd']
988 <DateTimePattern 'E dd/MM'>
989 >>> Locale('fr').datetime_skeletons['H']
990 <DateTimePattern "HH 'h'">
991 """
992 return self._data['datetime_skeletons']
994 @property
995 def interval_formats(self) -> localedata.LocaleDataDict:
996 """Locale patterns for interval formatting.
998 .. note:: The format of the value returned may change between
999 Babel versions.
1001 How to format date intervals in Finnish when the day is the
1002 smallest changing component:
1004 >>> Locale('fi_FI').interval_formats['MEd']['d']
1005 ['E d.\\u2009–\\u2009', 'E d.M.']
1007 .. seealso::
1009 The primary API to use this data is :py:func:`babel.dates.format_interval`.
1012 :rtype: dict[str, dict[str, list[str]]]
1013 """
1014 return self._data['interval_formats']
1016 @property
1017 def plural_form(self) -> PluralRule:
1018 """Plural rules for the locale.
1020 >>> Locale('en').plural_form(1)
1021 'one'
1022 >>> Locale('en').plural_form(0)
1023 'other'
1024 >>> Locale('fr').plural_form(0)
1025 'one'
1026 >>> Locale('ru').plural_form(100)
1027 'many'
1028 """
1029 return self._data.get('plural_form', _default_plural_rule)
1031 @property
1032 def list_patterns(self) -> localedata.LocaleDataDict:
1033 """Patterns for generating lists
1035 .. note:: The format of the value returned may change between
1036 Babel versions.
1038 >>> Locale('en').list_patterns['standard']['start']
1039 '{0}, {1}'
1040 >>> Locale('en').list_patterns['standard']['end']
1041 '{0}, and {1}'
1042 >>> Locale('en_GB').list_patterns['standard']['end']
1043 '{0} and {1}'
1044 """
1045 return self._data['list_patterns']
1047 @property
1048 def ordinal_form(self) -> PluralRule:
1049 """Plural rules for the locale.
1051 >>> Locale('en').ordinal_form(1)
1052 'one'
1053 >>> Locale('en').ordinal_form(2)
1054 'two'
1055 >>> Locale('en').ordinal_form(3)
1056 'few'
1057 >>> Locale('fr').ordinal_form(2)
1058 'other'
1059 >>> Locale('ru').ordinal_form(100)
1060 'other'
1061 """
1062 return self._data.get('ordinal_form', _default_plural_rule)
1064 @property
1065 def measurement_systems(self) -> localedata.LocaleDataDict:
1066 """Localized names for various measurement systems.
1068 >>> Locale('fr', 'FR').measurement_systems['US']
1069 'américain'
1070 >>> Locale('en', 'US').measurement_systems['US']
1071 'US'
1073 """
1074 return self._data['measurement_systems']
1076 @property
1077 def character_order(self) -> str:
1078 """The text direction for the language.
1080 >>> Locale('de', 'DE').character_order
1081 'left-to-right'
1082 >>> Locale('ar', 'SA').character_order
1083 'right-to-left'
1084 """
1085 return self._data['character_order']
1087 @property
1088 def text_direction(self) -> str:
1089 """The text direction for the language in CSS short-hand form.
1091 >>> Locale('de', 'DE').text_direction
1092 'ltr'
1093 >>> Locale('ar', 'SA').text_direction
1094 'rtl'
1095 """
1096 return ''.join(word[0] for word in self.character_order.split('-'))
1098 @property
1099 def unit_display_names(self) -> localedata.LocaleDataDict:
1100 """Display names for units of measurement.
1102 .. seealso::
1104 You may want to use :py:func:`babel.units.get_unit_name` instead.
1106 .. note:: The format of the value returned may change between
1107 Babel versions.
1109 """
1110 return self._data['unit_display_names']
1113def default_locale(
1114 category: str | tuple[str, ...] | list[str] | None = None,
1115 aliases: Mapping[str, str] = LOCALE_ALIASES,
1116) -> str | None:
1117 """Returns the system default locale for a given category, based on
1118 environment variables.
1120 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']:
1121 ... os.environ[name] = ''
1122 >>> os.environ['LANG'] = 'fr_FR.UTF-8'
1123 >>> default_locale('LC_MESSAGES')
1124 'fr_FR'
1126 The "C" or "POSIX" pseudo-locales are treated as aliases for the
1127 "en_US_POSIX" locale:
1129 >>> os.environ['LC_MESSAGES'] = 'POSIX'
1130 >>> default_locale('LC_MESSAGES')
1131 'en_US_POSIX'
1133 The following fallbacks to the variable are always considered:
1135 - ``LANGUAGE``
1136 - ``LC_ALL``
1137 - ``LC_CTYPE``
1138 - ``LANG``
1140 :param category: one or more of the ``LC_XXX`` environment variable names
1141 :param aliases: a dictionary of aliases for locale identifiers
1142 """
1144 varnames = ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
1145 if category:
1146 if isinstance(category, str):
1147 varnames = (category, *varnames)
1148 elif isinstance(category, (list, tuple)):
1149 varnames = (*category, *varnames)
1150 else:
1151 raise TypeError(f"Invalid type for category: {category!r}")
1153 for name in varnames:
1154 if not name:
1155 continue
1156 locale = os.getenv(name)
1157 if locale:
1158 if name == 'LANGUAGE' and ':' in locale:
1159 # the LANGUAGE variable may contain a colon-separated list of
1160 # language codes; we just pick the language on the list
1161 locale = locale.split(':')[0]
1162 if locale.split('.')[0] in ('C', 'POSIX'):
1163 locale = 'en_US_POSIX'
1164 elif aliases and locale in aliases:
1165 locale = aliases[locale]
1166 try:
1167 return get_locale_identifier(parse_locale(locale))
1168 except ValueError:
1169 pass
1170 return None
1173def negotiate_locale(
1174 preferred: Iterable[str],
1175 available: Iterable[str],
1176 sep: str = '_',
1177 aliases: Mapping[str, str] = LOCALE_ALIASES,
1178) -> str | None:
1179 """Find the best match between available and requested locale strings.
1181 >>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
1182 'de_DE'
1183 >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de'])
1184 'de'
1186 Case is ignored by the algorithm, the result uses the case of the preferred
1187 locale identifier:
1189 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1190 'de_DE'
1192 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1193 'de_DE'
1195 By default, some web browsers unfortunately do not include the territory
1196 in the locale identifier for many locales, and some don't even allow the
1197 user to easily add the territory. So while you may prefer using qualified
1198 locale identifiers in your web-application, they would not normally match
1199 the language-only locale sent by such browsers. To workaround that, this
1200 function uses a default mapping of commonly used language-only locale
1201 identifiers to identifiers including the territory:
1203 >>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US'])
1204 'ja_JP'
1206 Some browsers even use an incorrect or outdated language code, such as "no"
1207 for Norwegian, where the correct locale identifier would actually be "nb_NO"
1208 (Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of
1209 such cases, too:
1211 >>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE'])
1212 'nb_NO'
1214 You can override this default mapping by passing a different `aliases`
1215 dictionary to this function, or you can bypass the behavior althogher by
1216 setting the `aliases` parameter to `None`.
1218 :param preferred: the list of locale strings preferred by the user
1219 :param available: the list of locale strings available
1220 :param sep: character that separates the different parts of the locale
1221 strings
1222 :param aliases: a dictionary of aliases for locale identifiers
1223 """
1224 available = [a.lower() for a in available if a]
1225 for locale in preferred:
1226 ll = locale.lower()
1227 if ll in available:
1228 return locale
1229 if aliases:
1230 alias = aliases.get(ll)
1231 if alias:
1232 alias = alias.replace('_', sep)
1233 if alias.lower() in available:
1234 return alias
1235 parts = locale.split(sep)
1236 if len(parts) > 1 and parts[0].lower() in available:
1237 return parts[0]
1238 return None
1241def parse_locale(
1242 identifier: str,
1243 sep: str = '_',
1244) -> (
1245 tuple[str, str | None, str | None, str | None]
1246 | tuple[str, str | None, str | None, str | None, str | None]
1247):
1248 """Parse a locale identifier into a tuple of the form ``(language,
1249 territory, script, variant, modifier)``.
1251 >>> parse_locale('zh_CN')
1252 ('zh', 'CN', None, None)
1253 >>> parse_locale('zh_Hans_CN')
1254 ('zh', 'CN', 'Hans', None)
1255 >>> parse_locale('ca_es_valencia')
1256 ('ca', 'ES', None, 'VALENCIA')
1257 >>> parse_locale('en_150')
1258 ('en', '150', None, None)
1259 >>> parse_locale('en_us_posix')
1260 ('en', 'US', None, 'POSIX')
1261 >>> parse_locale('it_IT@euro')
1262 ('it', 'IT', None, None, 'euro')
1263 >>> parse_locale('it_IT@custom')
1264 ('it', 'IT', None, None, 'custom')
1265 >>> parse_locale('it_IT@')
1266 ('it', 'IT', None, None)
1268 The default component separator is "_", but a different separator can be
1269 specified using the `sep` parameter.
1271 The optional modifier is always separated with "@" and at the end:
1273 >>> parse_locale('zh-CN', sep='-')
1274 ('zh', 'CN', None, None)
1275 >>> parse_locale('zh-CN@custom', sep='-')
1276 ('zh', 'CN', None, None, 'custom')
1278 If the identifier cannot be parsed into a locale, a `ValueError` exception
1279 is raised:
1281 >>> parse_locale('not_a_LOCALE_String')
1282 Traceback (most recent call last):
1283 ...
1284 ValueError: 'not_a_LOCALE_String' is not a valid locale identifier
1286 Encoding information is removed from the identifier, while modifiers are
1287 kept:
1289 >>> parse_locale('en_US.UTF-8')
1290 ('en', 'US', None, None)
1291 >>> parse_locale('de_DE.iso885915@euro')
1292 ('de', 'DE', None, None, 'euro')
1294 See :rfc:`4646` for more information.
1296 :param identifier: the locale identifier string
1297 :param sep: character that separates the different components of the locale
1298 identifier
1299 :raise `ValueError`: if the string does not appear to be a valid locale
1300 identifier
1301 """
1302 if not identifier:
1303 raise ValueError("empty locale identifier")
1304 identifier, _, modifier = identifier.partition('@')
1305 if '.' in identifier:
1306 # this is probably the charset/encoding, which we don't care about
1307 identifier = identifier.split('.', 1)[0]
1309 parts = identifier.split(sep)
1310 lang = parts.pop(0).lower()
1311 if not lang.isalpha():
1312 raise ValueError(f"expected only letters, got {lang!r}")
1314 script = territory = variant = None
1315 if parts and len(parts[0]) == 4 and parts[0].isalpha():
1316 script = parts.pop(0).title()
1318 if parts:
1319 if len(parts[0]) == 2 and parts[0].isalpha():
1320 territory = parts.pop(0).upper()
1321 elif len(parts[0]) == 3 and parts[0].isdigit():
1322 territory = parts.pop(0)
1324 if parts and (
1325 len(parts[0]) == 4
1326 and parts[0][0].isdigit()
1327 or len(parts[0]) >= 5
1328 and parts[0][0].isalpha()
1329 ):
1330 variant = parts.pop().upper()
1332 if parts:
1333 raise ValueError(f"{identifier!r} is not a valid locale identifier")
1335 # TODO(3.0): always return a 5-tuple
1336 if modifier:
1337 return lang, territory, script, variant, modifier
1338 else:
1339 return lang, territory, script, variant
1342def get_locale_identifier(
1343 tup: tuple[str]
1344 | tuple[str, str | None]
1345 | tuple[str, str | None, str | None]
1346 | tuple[str, str | None, str | None, str | None]
1347 | tuple[str, str | None, str | None, str | None, str | None],
1348 sep: str = "_",
1349) -> str:
1350 """The reverse of :func:`parse_locale`. It creates a locale identifier out
1351 of a ``(language, territory, script, variant, modifier)`` tuple. Items can be set to
1352 ``None`` and trailing ``None``\\s can also be left out of the tuple.
1354 >>> get_locale_identifier(('de', 'DE', None, '1999', 'custom'))
1355 'de_DE_1999@custom'
1356 >>> get_locale_identifier(('fi', None, None, None, 'custom'))
1357 'fi@custom'
1360 .. versionadded:: 1.0
1362 :param tup: the tuple as returned by :func:`parse_locale`.
1363 :param sep: the separator for the identifier.
1364 """
1365 tup = tuple(tup[:5]) # type: ignore # length should be no more than 5
1366 lang, territory, script, variant, modifier = tup + (None,) * (5 - len(tup))
1367 ret = sep.join(filter(None, (lang, script, territory, variant)))
1368 return f'{ret}@{modifier}' if modifier else ret
1371def get_cldr_version() -> str:
1372 """Return the Unicode CLDR version used by this Babel installation.
1374 Generally, you should be able to assume that the return value of this
1375 function is a string representing a version number, e.g. '47'.
1377 >>> get_cldr_version()
1378 '47'
1380 .. versionadded:: 2.18
1382 :rtype: str
1383 """
1384 return str(get_global("cldr")["version"])