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 # C/POSIX is not a CLDR language. Same mapping default_locale uses.
365 posix_stem = identifier.partition(".")[0]
366 if posix_stem.upper() in {"C", "POSIX"}:
367 identifier = "en_US_POSIX"
369 parts = parse_locale(identifier, sep=sep)
370 input_id = get_locale_identifier(parts)
372 def _try_load(parts):
373 try:
374 return cls(*parts)
375 except UnknownLocaleError:
376 return None
378 def _try_load_reducing(parts):
379 # Success on first hit, return it.
380 locale = _try_load(parts)
381 if locale is not None:
382 return locale
384 # Now try without script and variant
385 locale = _try_load(parts[:2])
386 if locale is not None:
387 return locale
389 locale = _try_load(parts)
390 if locale is not None:
391 return locale
392 if not resolve_likely_subtags:
393 raise UnknownLocaleError(input_id)
395 # From here onwards is some very bad likely subtag resolving. This
396 # whole logic is not entirely correct but good enough (tm) for the
397 # time being. This has been added so that zh_TW does not cause
398 # errors for people when they upgrade. Later we should properly
399 # implement ICU like fuzzy locale objects and provide a way to
400 # maximize and minimize locale tags.
402 if len(parts) == 5:
403 language, territory, script, variant, modifier = parts
404 else:
405 language, territory, script, variant = parts
406 modifier = None
407 language = get_global('language_aliases').get(language, language)
408 territory = get_global('territory_aliases').get(territory or '', (territory,))[0]
409 script = get_global('script_aliases').get(script or '', script)
410 variant = get_global('variant_aliases').get(variant or '', variant)
412 if territory == 'ZZ':
413 territory = None
414 if script == 'Zzzz':
415 script = None
417 parts = language, territory, script, variant, modifier
419 # First match: try the whole identifier
420 new_id = get_locale_identifier(parts)
421 likely_subtag = get_global('likely_subtags').get(new_id)
422 if likely_subtag is not None:
423 locale = _try_load_reducing(parse_locale(likely_subtag))
424 if locale is not None:
425 return locale
427 # If we did not find anything so far, try again with a
428 # simplified identifier that is just the language
429 likely_subtag = get_global('likely_subtags').get(language)
430 if likely_subtag is not None:
431 parts2 = parse_locale(likely_subtag)
432 if len(parts2) == 5:
433 language2, _, script2, variant2, modifier2 = parts2
434 else:
435 language2, _, script2, variant2 = parts2
436 modifier2 = None
437 locale = _try_load_reducing(
438 (language2, territory, script2, variant2, modifier2),
439 )
440 if locale is not None:
441 return locale
443 raise UnknownLocaleError(input_id)
445 def __eq__(self, other: object) -> bool:
446 for key in ('language', 'territory', 'script', 'variant', 'modifier'):
447 if not hasattr(other, key):
448 return False
449 return (
450 self.language == getattr(other, 'language') # noqa: B009
451 and self.territory == getattr(other, 'territory') # noqa: B009
452 and self.script == getattr(other, 'script') # noqa: B009
453 and self.variant == getattr(other, 'variant') # noqa: B009
454 and self.modifier == getattr(other, 'modifier') # noqa: B009
455 )
457 def __ne__(self, other: object) -> bool:
458 return not self.__eq__(other)
460 def __hash__(self) -> int:
461 return hash((self.language, self.territory, self.script, self.variant, self.modifier))
463 def __repr__(self) -> str:
464 parameters = ['']
465 for key in ('territory', 'script', 'variant', 'modifier'):
466 value = getattr(self, key)
467 if value is not None:
468 parameters.append(f"{key}={value!r}")
469 return f"Locale({self.language!r}{', '.join(parameters)})"
471 def __str__(self) -> str:
472 return get_locale_identifier(
473 (self.language, self.territory, self.script, self.variant, self.modifier),
474 )
476 @property
477 def _data(self) -> localedata.LocaleDataDict:
478 if self.__data is None:
479 self.__data = localedata.get_locale_data(self.__data_identifier)
480 return self.__data
482 def get_display_name(self, locale: Locale | str | None = None) -> str | None:
483 """Return the display name of the locale using the given locale.
485 The display name will include the language, territory, script, and
486 variant, if those are specified.
488 >>> Locale('zh', 'CN', script='Hans').get_display_name('en')
489 'Chinese (Simplified, China)'
491 Modifiers are currently passed through verbatim:
493 >>> Locale('it', 'IT', modifier='euro').get_display_name('en')
494 'Italian (Italy, euro)'
496 :param locale: the locale to use
497 """
498 if locale is None:
499 locale = self
500 locale = Locale.parse(locale)
501 retval = locale.languages.get(self.language)
502 if retval and (self.territory or self.script or self.variant):
503 details = []
504 if self.script:
505 details.append(locale.scripts.get(self.script))
506 if self.territory:
507 details.append(locale.territories.get(self.territory))
508 if self.variant:
509 details.append(locale.variants.get(self.variant))
510 if self.modifier:
511 details.append(self.modifier)
512 detail_string = ', '.join(atom for atom in details if atom)
513 if detail_string:
514 retval += f" ({detail_string})"
515 return retval
517 @property
518 def display_name(self) -> str | None:
519 """
520 The localized display name of the locale.
522 >>> Locale('en').display_name
523 'English'
524 >>> Locale('en', 'US').display_name
525 'English (United States)'
526 >>> Locale('sv').display_name
527 'svenska'
528 """
529 return self.get_display_name()
531 def get_language_name(self, locale: Locale | str | None = None) -> str | None:
532 """Return the language of this locale in the given locale.
534 >>> Locale('zh', 'CN', script='Hans').get_language_name('de')
535 'Chinesisch'
537 .. versionadded:: 1.0
539 :param locale: the locale to use
540 """
541 if locale is None:
542 locale = self
543 locale = Locale.parse(locale)
544 return locale.languages.get(self.language)
546 @property
547 def language_name(self) -> str | None:
548 """
549 The localized language name of the locale.
551 >>> Locale('en', 'US').language_name
552 'English'
553 """
554 return self.get_language_name()
556 def get_territory_name(self, locale: Locale | str | None = None) -> str | None:
557 """Return the territory name in the given locale."""
558 if locale is None:
559 locale = self
560 locale = Locale.parse(locale)
561 return locale.territories.get(self.territory or '')
563 @property
564 def territory_name(self) -> str | None:
565 """
566 The localized territory name of the locale if available.
568 >>> Locale('de', 'DE').territory_name
569 'Deutschland'
570 """
571 return self.get_territory_name()
573 def get_script_name(self, locale: Locale | str | None = None) -> str | None:
574 """Return the script name in the given locale."""
575 if locale is None:
576 locale = self
577 locale = Locale.parse(locale)
578 return locale.scripts.get(self.script or '')
580 @property
581 def script_name(self) -> str | None:
582 """
583 The localized script name of the locale if available.
585 >>> Locale('sr', 'ME', script='Latn').script_name
586 'latinica'
587 """
588 return self.get_script_name()
590 @property
591 def english_name(self) -> str | None:
592 """The english display name of the locale.
594 >>> Locale('de').english_name
595 'German'
596 >>> Locale('de', 'DE').english_name
597 'German (Germany)'
598 """
599 return self.get_display_name(Locale('en'))
601 # { General Locale Display Names
603 @property
604 def languages(self) -> localedata.LocaleDataDict:
605 """Mapping of language codes to translated language names.
607 >>> Locale('de', 'DE').languages['ja']
608 'Japanisch'
610 See `ISO 639 <https://www.loc.gov/standards/iso639-2/>`_ for
611 more information.
612 """
613 return self._data['languages']
615 @property
616 def scripts(self) -> localedata.LocaleDataDict:
617 """Mapping of script codes to translated script names.
619 >>> Locale('en', 'US').scripts['Hira']
620 'Hiragana'
622 See `ISO 15924 <https://www.unicode.org/iso15924/>`_
623 for more information.
624 """
625 return self._data['scripts']
627 @property
628 def territories(self) -> localedata.LocaleDataDict:
629 """Mapping of script codes to translated script names.
631 >>> Locale('es', 'CO').territories['DE']
632 'Alemania'
634 See `ISO 3166 <https://en.wikipedia.org/wiki/ISO_3166>`_
635 for more information.
636 """
637 return self._data['territories']
639 @property
640 def variants(self) -> localedata.LocaleDataDict:
641 """Mapping of script codes to translated script names.
643 >>> Locale('de', 'DE').variants['1901']
644 'Alte deutsche Rechtschreibung'
645 """
646 return self._data['variants']
648 # { Number Formatting
650 @property
651 def currencies(self) -> localedata.LocaleDataDict:
652 """Mapping of currency codes to translated currency names. This
653 only returns the generic form of the currency name, not the count
654 specific one. If an actual number is requested use the
655 :func:`babel.numbers.get_currency_name` function.
657 >>> Locale('en').currencies['COP']
658 'Colombian Peso'
659 >>> Locale('de', 'DE').currencies['COP']
660 'Kolumbianischer Peso'
661 """
662 return self._data['currency_names']
664 @property
665 def currency_symbols(self) -> localedata.LocaleDataDict:
666 """Mapping of currency codes to symbols.
668 >>> Locale('en', 'US').currency_symbols['USD']
669 '$'
670 >>> Locale('es', 'CO').currency_symbols['USD']
671 'US$'
672 """
673 return self._data['currency_symbols']
675 @property
676 def number_symbols(self) -> localedata.LocaleDataDict:
677 """Symbols used in number formatting by number system.
679 .. note:: The format of the value returned may change between
680 Babel versions.
682 >>> Locale('fr', 'FR').number_symbols["latn"]['decimal']
683 ','
684 >>> Locale('fa', 'IR').number_symbols["arabext"]['decimal']
685 '٫'
686 >>> Locale('fa', 'IR').number_symbols["latn"]['decimal']
687 '.'
688 """
689 return self._data['number_symbols']
691 @property
692 def other_numbering_systems(self) -> localedata.LocaleDataDict:
693 """
694 Mapping of other numbering systems available for the locale.
695 See: https://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems
697 >>> Locale('el', 'GR').other_numbering_systems['traditional']
698 'grek'
700 .. note:: The format of the value returned may change between
701 Babel versions.
702 """
703 return self._data['numbering_systems']
705 @property
706 def default_numbering_system(self) -> str:
707 """The default numbering system used by the locale.
708 >>> Locale('el', 'GR').default_numbering_system
709 'latn'
710 """
711 return self._data['default_numbering_system']
713 @property
714 def decimal_formats(self) -> localedata.LocaleDataDict:
715 """Locale patterns for decimal number formatting.
717 .. note:: The format of the value returned may change between
718 Babel versions.
720 >>> Locale('en', 'US').decimal_formats[None]
721 <NumberPattern '#,##0.###'>
722 """
723 return self._data['decimal_formats']
725 @property
726 def compact_decimal_formats(self) -> localedata.LocaleDataDict:
727 """Locale patterns for compact decimal number formatting.
729 .. note:: The format of the value returned may change between
730 Babel versions.
732 >>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"]
733 <NumberPattern '0K'>
734 """
735 return self._data['compact_decimal_formats']
737 @property
738 def currency_formats(self) -> localedata.LocaleDataDict:
739 """Locale patterns for currency number formatting.
741 .. note:: The format of the value returned may change between
742 Babel versions.
744 >>> Locale('en', 'US').currency_formats['standard']
745 <NumberPattern '\\xa4#,##0.00'>
746 >>> Locale('en', 'US').currency_formats['accounting']
747 <NumberPattern '\\xa4#,##0.00;(\\xa4#,##0.00)'>
748 """
749 return self._data['currency_formats']
751 @property
752 def compact_currency_formats(self) -> localedata.LocaleDataDict:
753 """Locale patterns for compact currency number formatting.
755 .. note:: The format of the value returned may change between
756 Babel versions.
758 >>> Locale('en', 'US').compact_currency_formats["short"]["one"]["1000"]
759 <NumberPattern '¤0K'>
760 """
761 return self._data['compact_currency_formats']
763 @property
764 def percent_formats(self) -> localedata.LocaleDataDict:
765 """Locale patterns for percent number formatting.
767 .. note:: The format of the value returned may change between
768 Babel versions.
770 >>> Locale('en', 'US').percent_formats[None]
771 <NumberPattern '#,##0%'>
772 """
773 return self._data['percent_formats']
775 @property
776 def scientific_formats(self) -> localedata.LocaleDataDict:
777 """Locale patterns for scientific number formatting.
779 .. note:: The format of the value returned may change between
780 Babel versions.
782 >>> Locale('en', 'US').scientific_formats[None]
783 <NumberPattern '#E0'>
784 """
785 return self._data['scientific_formats']
787 # { Calendar Information and Date Formatting
789 @property
790 def periods(self) -> localedata.LocaleDataDict:
791 """Locale display names for day periods (AM/PM).
793 >>> Locale('en', 'US').periods['am']
794 'AM'
795 """
796 try:
797 return self._data['day_periods']['stand-alone']['wide']
798 except KeyError:
799 return localedata.LocaleDataDict({}) # pragma: no cover
801 @property
802 def day_periods(self) -> localedata.LocaleDataDict:
803 """Locale display names for various day periods (not necessarily only AM/PM).
805 These are not meant to be used without the relevant `day_period_rules`.
806 """
807 return self._data['day_periods']
809 @property
810 def day_period_rules(self) -> localedata.LocaleDataDict:
811 """Day period rules for the locale. Used by `get_period_id`."""
812 return self._data.get('day_period_rules', localedata.LocaleDataDict({}))
814 @property
815 def days(self) -> localedata.LocaleDataDict:
816 """Locale display names for weekdays.
818 >>> Locale('de', 'DE').days['format']['wide'][3]
819 'Donnerstag'
820 """
821 return self._data['days']
823 @property
824 def months(self) -> localedata.LocaleDataDict:
825 """Locale display names for months.
827 >>> Locale('de', 'DE').months['format']['wide'][10]
828 'Oktober'
829 """
830 return self._data['months']
832 @property
833 def quarters(self) -> localedata.LocaleDataDict:
834 """Locale display names for quarters.
836 >>> Locale('de', 'DE').quarters['format']['wide'][1]
837 '1. Quartal'
838 """
839 return self._data['quarters']
841 @property
842 def eras(self) -> localedata.LocaleDataDict:
843 """Locale display names for eras.
845 .. note:: The format of the value returned may change between
846 Babel versions.
848 >>> Locale('en', 'US').eras['wide'][1]
849 'Anno Domini'
850 >>> Locale('en', 'US').eras['abbreviated'][0]
851 'BC'
852 """
853 return self._data['eras']
855 @property
856 def time_zones(self) -> localedata.LocaleDataDict:
857 """Locale display names for time zones.
859 .. note:: The format of the value returned may change between
860 Babel versions.
862 >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight']
863 'British Summer Time'
864 >>> Locale('en', 'US').time_zones['America/St_Johns']['city']
865 'St. John’s'
866 """
867 return self._data['time_zones']
869 @property
870 def meta_zones(self) -> localedata.LocaleDataDict:
871 """Locale display names for meta time zones.
873 Meta time zones are basically groups of different Olson time zones that
874 have the same GMT offset and daylight savings time.
876 .. note:: The format of the value returned may change between
877 Babel versions.
879 >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
880 'Central European Summer Time'
882 .. versionadded:: 0.9
883 """
884 return self._data['meta_zones']
886 @property
887 def zone_formats(self) -> localedata.LocaleDataDict:
888 """Patterns related to the formatting of time zones.
890 .. note:: The format of the value returned may change between
891 Babel versions.
893 >>> Locale('en', 'US').zone_formats['fallback']
894 '%(1)s (%(0)s)'
895 >>> Locale('pt', 'BR').zone_formats['region']
896 'Horário %s'
898 .. versionadded:: 0.9
899 """
900 return self._data['zone_formats']
902 @property
903 def first_week_day(self) -> int:
904 """The first day of a week, with 0 being Monday.
906 >>> Locale('de', 'DE').first_week_day
907 0
908 >>> Locale('en', 'US').first_week_day
909 6
910 """
911 return self._data['week_data']['first_day']
913 @property
914 def weekend_start(self) -> int:
915 """The day the weekend starts, with 0 being Monday.
917 >>> Locale('de', 'DE').weekend_start
918 5
919 """
920 return self._data['week_data']['weekend_start']
922 @property
923 def weekend_end(self) -> int:
924 """The day the weekend ends, with 0 being Monday.
926 >>> Locale('de', 'DE').weekend_end
927 6
928 """
929 return self._data['week_data']['weekend_end']
931 @property
932 def min_week_days(self) -> int:
933 """The minimum number of days in a week so that the week is counted as
934 the first week of a year or month.
936 >>> Locale('de', 'DE').min_week_days
937 4
938 """
939 return self._data['week_data']['min_days']
941 @property
942 def date_formats(self) -> localedata.LocaleDataDict:
943 """Locale patterns for date formatting.
945 .. note:: The format of the value returned may change between
946 Babel versions.
948 >>> Locale('en', 'US').date_formats['short']
949 <DateTimePattern 'M/d/yy'>
950 >>> Locale('fr', 'FR').date_formats['long']
951 <DateTimePattern 'd MMMM y'>
952 """
953 return self._data['date_formats']
955 @property
956 def time_formats(self) -> localedata.LocaleDataDict:
957 """Locale patterns for time formatting.
959 .. note:: The format of the value returned may change between
960 Babel versions.
962 >>> Locale('en', 'US').time_formats['short']
963 <DateTimePattern 'h:mm\\u202fa'>
964 >>> Locale('fr', 'FR').time_formats['long']
965 <DateTimePattern 'HH:mm:ss z'>
966 """
967 return self._data['time_formats']
969 @property
970 def datetime_formats(self) -> localedata.LocaleDataDict:
971 """Locale patterns for datetime formatting.
973 .. note:: The format of the value returned may change between
974 Babel versions.
976 >>> Locale('en').datetime_formats['full']
977 '{1}, {0}'
978 >>> Locale('th').datetime_formats['medium']
979 '{1} {0}'
980 """
981 return self._data['datetime_formats']
983 @property
984 def datetime_skeletons(self) -> localedata.LocaleDataDict:
985 """Locale patterns for formatting parts of a datetime.
987 >>> Locale('en').datetime_skeletons['MEd']
988 <DateTimePattern 'E, M/d'>
989 >>> Locale('fr').datetime_skeletons['MEd']
990 <DateTimePattern 'E dd/MM'>
991 >>> Locale('fr').datetime_skeletons['H']
992 <DateTimePattern "HH 'h'">
993 """
994 return self._data['datetime_skeletons']
996 @property
997 def interval_formats(self) -> localedata.LocaleDataDict:
998 """Locale patterns for interval formatting.
1000 .. note:: The format of the value returned may change between
1001 Babel versions.
1003 How to format date intervals in Finnish when the day is the
1004 smallest changing component:
1006 >>> Locale('fi_FI').interval_formats['MEd']['d']
1007 ['E d.\\u2009–\\u2009', 'E d.M.']
1009 .. seealso::
1011 The primary API to use this data is :py:func:`babel.dates.format_interval`.
1014 :rtype: dict[str, dict[str, list[str]]]
1015 """
1016 return self._data['interval_formats']
1018 @property
1019 def plural_form(self) -> PluralRule:
1020 """Plural rules for the locale.
1022 >>> Locale('en').plural_form(1)
1023 'one'
1024 >>> Locale('en').plural_form(0)
1025 'other'
1026 >>> Locale('fr').plural_form(0)
1027 'one'
1028 >>> Locale('ru').plural_form(100)
1029 'many'
1030 """
1031 return self._data.get('plural_form', _default_plural_rule)
1033 @property
1034 def list_patterns(self) -> localedata.LocaleDataDict:
1035 """Patterns for generating lists
1037 .. note:: The format of the value returned may change between
1038 Babel versions.
1040 >>> Locale('en').list_patterns['standard']['start']
1041 '{0}, {1}'
1042 >>> Locale('en').list_patterns['standard']['end']
1043 '{0}, and {1}'
1044 >>> Locale('en_GB').list_patterns['standard']['end']
1045 '{0} and {1}'
1046 """
1047 return self._data['list_patterns']
1049 @property
1050 def ordinal_form(self) -> PluralRule:
1051 """Plural rules for the locale.
1053 >>> Locale('en').ordinal_form(1)
1054 'one'
1055 >>> Locale('en').ordinal_form(2)
1056 'two'
1057 >>> Locale('en').ordinal_form(3)
1058 'few'
1059 >>> Locale('fr').ordinal_form(2)
1060 'other'
1061 >>> Locale('ru').ordinal_form(100)
1062 'other'
1063 """
1064 return self._data.get('ordinal_form', _default_plural_rule)
1066 @property
1067 def measurement_systems(self) -> localedata.LocaleDataDict:
1068 """Localized names for various measurement systems.
1070 >>> Locale('fr', 'FR').measurement_systems['US']
1071 'américain'
1072 >>> Locale('en', 'US').measurement_systems['US']
1073 'US'
1075 """
1076 return self._data['measurement_systems']
1078 @property
1079 def character_order(self) -> str:
1080 """The text direction for the language.
1082 >>> Locale('de', 'DE').character_order
1083 'left-to-right'
1084 >>> Locale('ar', 'SA').character_order
1085 'right-to-left'
1086 """
1087 return self._data['character_order']
1089 @property
1090 def text_direction(self) -> str:
1091 """The text direction for the language in CSS short-hand form.
1093 >>> Locale('de', 'DE').text_direction
1094 'ltr'
1095 >>> Locale('ar', 'SA').text_direction
1096 'rtl'
1097 """
1098 return ''.join(word[0] for word in self.character_order.split('-'))
1100 @property
1101 def unit_display_names(self) -> localedata.LocaleDataDict:
1102 """Display names for units of measurement.
1104 .. seealso::
1106 You may want to use :py:func:`babel.units.get_unit_name` instead.
1108 .. note:: The format of the value returned may change between
1109 Babel versions.
1111 """
1112 return self._data['unit_display_names']
1115def default_locale(
1116 category: str | tuple[str, ...] | list[str] | None = None,
1117 aliases: Mapping[str, str] = LOCALE_ALIASES,
1118) -> str | None:
1119 """Returns the system default locale for a given category, based on
1120 environment variables.
1122 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']:
1123 ... os.environ[name] = ''
1124 >>> os.environ['LANG'] = 'fr_FR.UTF-8'
1125 >>> default_locale('LC_MESSAGES')
1126 'fr_FR'
1128 The "C" or "POSIX" pseudo-locales are treated as aliases for the
1129 "en_US_POSIX" locale:
1131 >>> os.environ['LC_MESSAGES'] = 'POSIX'
1132 >>> default_locale('LC_MESSAGES')
1133 'en_US_POSIX'
1135 The following fallbacks to the variable are always considered:
1137 - ``LANGUAGE``
1138 - ``LC_ALL``
1139 - ``LC_CTYPE``
1140 - ``LANG``
1142 :param category: one or more of the ``LC_XXX`` environment variable names
1143 :param aliases: a dictionary of aliases for locale identifiers
1144 """
1146 varnames = ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
1147 if category:
1148 if isinstance(category, str):
1149 varnames = (category, *varnames)
1150 elif isinstance(category, (list, tuple)):
1151 varnames = (*category, *varnames)
1152 else:
1153 raise TypeError(f"Invalid type for category: {category!r}")
1155 for name in varnames:
1156 if not name:
1157 continue
1158 locale = os.getenv(name)
1159 if locale:
1160 if name == 'LANGUAGE' and ':' in locale:
1161 # the LANGUAGE variable may contain a colon-separated list of
1162 # language codes; we just pick the language on the list
1163 locale = locale.split(':')[0]
1164 if locale.split('.')[0] in ('C', 'POSIX'):
1165 locale = 'en_US_POSIX'
1166 elif aliases and locale in aliases:
1167 locale = aliases[locale]
1168 try:
1169 return get_locale_identifier(parse_locale(locale))
1170 except ValueError:
1171 pass
1172 return None
1175def negotiate_locale(
1176 preferred: Iterable[str],
1177 available: Iterable[str],
1178 sep: str = '_',
1179 aliases: Mapping[str, str] = LOCALE_ALIASES,
1180) -> str | None:
1181 """Find the best match between available and requested locale strings.
1183 >>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
1184 'de_DE'
1185 >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de'])
1186 'de'
1188 Case is ignored by the algorithm, the result uses the case of the preferred
1189 locale identifier:
1191 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1192 'de_DE'
1194 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1195 'de_DE'
1197 By default, some web browsers unfortunately do not include the territory
1198 in the locale identifier for many locales, and some don't even allow the
1199 user to easily add the territory. So while you may prefer using qualified
1200 locale identifiers in your web-application, they would not normally match
1201 the language-only locale sent by such browsers. To workaround that, this
1202 function uses a default mapping of commonly used language-only locale
1203 identifiers to identifiers including the territory:
1205 >>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US'])
1206 'ja_JP'
1208 Some browsers even use an incorrect or outdated language code, such as "no"
1209 for Norwegian, where the correct locale identifier would actually be "nb_NO"
1210 (Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of
1211 such cases, too:
1213 >>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE'])
1214 'nb_NO'
1216 You can override this default mapping by passing a different `aliases`
1217 dictionary to this function, or you can bypass the behavior althogher by
1218 setting the `aliases` parameter to `None`.
1220 :param preferred: the list of locale strings preferred by the user
1221 :param available: the list of locale strings available
1222 :param sep: character that separates the different parts of the locale
1223 strings
1224 :param aliases: a dictionary of aliases for locale identifiers
1225 """
1226 available = [a.lower() for a in available if a]
1227 for locale in preferred:
1228 ll = locale.lower()
1229 if ll in available:
1230 return locale
1231 if aliases:
1232 alias = aliases.get(ll)
1233 if alias:
1234 alias = alias.replace('_', sep)
1235 if alias.lower() in available:
1236 return alias
1237 parts = locale.split(sep)
1238 if len(parts) > 1 and parts[0].lower() in available:
1239 return parts[0]
1240 return None
1243def parse_locale(
1244 identifier: str,
1245 sep: str = '_',
1246) -> (
1247 tuple[str, str | None, str | None, str | None]
1248 | tuple[str, str | None, str | None, str | None, str | None]
1249):
1250 """Parse a locale identifier into a tuple of the form ``(language,
1251 territory, script, variant, modifier)``.
1253 >>> parse_locale('zh_CN')
1254 ('zh', 'CN', None, None)
1255 >>> parse_locale('zh_Hans_CN')
1256 ('zh', 'CN', 'Hans', None)
1257 >>> parse_locale('ca_es_valencia')
1258 ('ca', 'ES', None, 'VALENCIA')
1259 >>> parse_locale('en_150')
1260 ('en', '150', None, None)
1261 >>> parse_locale('en_us_posix')
1262 ('en', 'US', None, 'POSIX')
1263 >>> parse_locale('it_IT@euro')
1264 ('it', 'IT', None, None, 'euro')
1265 >>> parse_locale('it_IT@custom')
1266 ('it', 'IT', None, None, 'custom')
1267 >>> parse_locale('it_IT@')
1268 ('it', 'IT', None, None)
1270 The default component separator is "_", but a different separator can be
1271 specified using the `sep` parameter.
1273 The optional modifier is always separated with "@" and at the end:
1275 >>> parse_locale('zh-CN', sep='-')
1276 ('zh', 'CN', None, None)
1277 >>> parse_locale('zh-CN@custom', sep='-')
1278 ('zh', 'CN', None, None, 'custom')
1280 If the identifier cannot be parsed into a locale, a `ValueError` exception
1281 is raised:
1283 >>> parse_locale('not_a_LOCALE_String')
1284 Traceback (most recent call last):
1285 ...
1286 ValueError: 'not_a_LOCALE_String' is not a valid locale identifier
1288 Encoding information is removed from the identifier, while modifiers are
1289 kept:
1291 >>> parse_locale('en_US.UTF-8')
1292 ('en', 'US', None, None)
1293 >>> parse_locale('de_DE.iso885915@euro')
1294 ('de', 'DE', None, None, 'euro')
1296 See :rfc:`4646` for more information.
1298 :param identifier: the locale identifier string
1299 :param sep: character that separates the different components of the locale
1300 identifier
1301 :raise `ValueError`: if the string does not appear to be a valid locale
1302 identifier
1303 """
1304 if not identifier:
1305 raise ValueError("empty locale identifier")
1306 identifier, _, modifier = identifier.partition('@')
1307 if '.' in identifier:
1308 # this is probably the charset/encoding, which we don't care about
1309 identifier = identifier.split('.', 1)[0]
1311 parts = identifier.split(sep)
1312 lang = parts.pop(0).lower()
1313 if not lang.isalpha():
1314 raise ValueError(f"expected only letters, got {lang!r}")
1316 script = territory = variant = None
1317 if parts and len(parts[0]) == 4 and parts[0].isalpha():
1318 script = parts.pop(0).title()
1320 if parts:
1321 if len(parts[0]) == 2 and parts[0].isalpha():
1322 territory = parts.pop(0).upper()
1323 elif len(parts[0]) == 3 and parts[0].isdigit():
1324 territory = parts.pop(0)
1326 if parts and (
1327 len(parts[0]) == 4
1328 and parts[0][0].isdigit()
1329 or len(parts[0]) >= 5
1330 and parts[0][0].isalpha()
1331 ):
1332 variant = parts.pop().upper()
1334 if parts:
1335 raise ValueError(f"{identifier!r} is not a valid locale identifier")
1337 # TODO(3.0): always return a 5-tuple
1338 if modifier:
1339 return lang, territory, script, variant, modifier
1340 else:
1341 return lang, territory, script, variant
1344def get_locale_identifier(
1345 tup: tuple[str]
1346 | tuple[str, str | None]
1347 | tuple[str, str | None, str | None]
1348 | tuple[str, str | None, str | None, str | None]
1349 | tuple[str, str | None, str | None, str | None, str | None],
1350 sep: str = "_",
1351) -> str:
1352 """The reverse of :func:`parse_locale`. It creates a locale identifier out
1353 of a ``(language, territory, script, variant, modifier)`` tuple. Items can be set to
1354 ``None`` and trailing ``None``\\s can also be left out of the tuple.
1356 >>> get_locale_identifier(('de', 'DE', None, '1999', 'custom'))
1357 'de_DE_1999@custom'
1358 >>> get_locale_identifier(('fi', None, None, None, 'custom'))
1359 'fi@custom'
1362 .. versionadded:: 1.0
1364 :param tup: the tuple as returned by :func:`parse_locale`.
1365 :param sep: the separator for the identifier.
1366 """
1367 tup = tuple(tup[:5]) # type: ignore # length should be no more than 5
1368 lang, territory, script, variant, modifier = tup + (None,) * (5 - len(tup))
1369 ret = sep.join(filter(None, (lang, script, territory, variant)))
1370 return f'{ret}@{modifier}' if modifier else ret
1373def get_cldr_version() -> str:
1374 """Return the Unicode CLDR version used by this Babel installation.
1376 Generally, you should be able to assume that the return value of this
1377 function is a string representing a version number, e.g. '47'.
1379 >>> get_cldr_version()
1380 '48'
1382 .. versionadded:: 2.18
1384 :rtype: str
1385 """
1386 return str(get_global("cldr")["version"])