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 The constructor preserves the casing of the components. Use lowercase
156 language codes, uppercase territory and variant codes, and title-case
157 script codes. A `Locale` object can also be instantiated from a raw locale
158 string with :meth:`parse`, which normalizes the casing of these codes:
160 >>> locale = Locale.parse('EN-us', sep='-')
161 >>> repr(locale)
162 "Locale('en', territory='US')"
164 `Locale` objects provide access to a collection of locale data, such as
165 territory and language names, number and date format patterns, and more:
167 >>> locale.number_symbols['latn']['decimal']
168 '.'
170 If a locale is requested for which no locale data is available, an
171 `UnknownLocaleError` is raised:
173 >>> Locale.parse('en_XX')
174 Traceback (most recent call last):
175 ...
176 UnknownLocaleError: unknown locale 'en_XX'
178 For more information see :rfc:`3066`.
179 """
181 def __init__(
182 self,
183 language: str,
184 territory: str | None = None,
185 script: str | None = None,
186 variant: str | None = None,
187 modifier: str | None = None,
188 ) -> None:
189 """Initialize the locale object from the given identifier components.
191 >>> locale = Locale('en', 'US')
192 >>> locale.language
193 'en'
194 >>> locale.territory
195 'US'
197 The components are stored as supplied, without case normalization.
198 Use :meth:`parse` for case-insensitive parsing of locale identifier
199 strings.
201 :param language: the lowercase language code
202 :param territory: the uppercase territory (country or region) code,
203 or a numeric region code
204 :param script: the title-case script code
205 :param variant: the uppercase variant code
206 :param modifier: a modifier (following the '@' symbol, sometimes called '@variant')
207 :raise `UnknownLocaleError`: if no locale data is available for the
208 requested locale
209 """
210 #: the language code
211 self.language = language
212 #: the territory (country or region) code
213 self.territory = territory
214 #: the script code
215 self.script = script
216 #: the variant code
217 self.variant = variant
218 #: the modifier
219 self.modifier = modifier
220 self.__data: localedata.LocaleDataDict | None = None
222 identifier = str(self)
223 identifier_without_modifier = identifier.partition('@')[0]
224 if localedata.exists(identifier):
225 self.__data_identifier = identifier
226 elif localedata.exists(identifier_without_modifier):
227 self.__data_identifier = identifier_without_modifier
228 else:
229 raise UnknownLocaleError(identifier)
231 @classmethod
232 def default(
233 cls,
234 category: str | None = None,
235 aliases: Mapping[str, str] = LOCALE_ALIASES,
236 ) -> Locale:
237 """Return the system default locale for the specified category.
239 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
240 ... os.environ[name] = ''
241 >>> os.environ['LANG'] = 'fr_FR.UTF-8'
242 >>> Locale.default('LC_MESSAGES')
243 Locale('fr', territory='FR')
245 The following fallbacks to the variable are always considered:
247 - ``LANGUAGE``
248 - ``LC_ALL``
249 - ``LC_CTYPE``
250 - ``LANG``
252 :param category: one of the ``LC_XXX`` environment variable names
253 :param aliases: a dictionary of aliases for locale identifiers
254 """
255 # XXX: use likely subtag expansion here instead of the
256 # aliases dictionary.
257 locale_string = default_locale(category, aliases=aliases)
258 return cls.parse(locale_string)
260 @classmethod
261 def negotiate(
262 cls,
263 preferred: Iterable[str],
264 available: Iterable[str],
265 sep: str = '_',
266 aliases: Mapping[str, str] = LOCALE_ALIASES,
267 ) -> Locale | None:
268 """Find the best match between available and requested locale strings.
270 >>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
271 Locale('de', territory='DE')
272 >>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de'])
273 Locale('de')
274 >>> Locale.negotiate(['de_DE', 'de'], ['en_US'])
276 You can specify the character used in the locale identifiers to separate
277 the different components. This separator is applied to both lists. Also,
278 case is ignored in the comparison:
280 >>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-')
281 Locale('de', territory='DE')
283 :param preferred: the list of locale identifiers preferred by the user
284 :param available: the list of locale identifiers available
285 :param aliases: a dictionary of aliases for locale identifiers
286 :param sep: separator for parsing; e.g. Windows tends to use '-' instead of '_'.
287 """
288 identifier = negotiate_locale(preferred, available, sep=sep, aliases=aliases)
289 if identifier:
290 return Locale.parse(identifier, sep=sep)
291 return None
293 @classmethod
294 def parse(
295 cls,
296 identifier: Locale | str | None,
297 sep: str = '_',
298 resolve_likely_subtags: bool = True,
299 ) -> Locale:
300 """Create a `Locale` instance for the given locale identifier.
302 >>> l = Locale.parse('de-DE', sep='-')
303 >>> l.display_name
304 'Deutsch (Deutschland)'
306 For string identifiers, language, territory, script, and variant codes
307 are case-insensitive and normalized to their usual casing. Modifiers
308 retain their casing.
310 >>> Locale.parse('ZH_hANT_tw')
311 Locale('zh', territory='TW', script='Hant')
313 If the `identifier` parameter is not a string, but actually a `Locale`
314 object, that object is returned:
316 >>> Locale.parse(l)
317 Locale('de', territory='DE')
319 If the `identifier` parameter is neither of these, such as `None`
320 or an empty string, e.g. because a default locale identifier
321 could not be determined, a `TypeError` is raised:
323 >>> Locale.parse(None)
324 Traceback (most recent call last):
325 ...
326 TypeError: ...
328 This also can perform resolving of likely subtags which it does
329 by default. This is for instance useful to figure out the most
330 likely locale for a territory you can use ``'und'`` as the
331 language tag:
333 >>> Locale.parse('und_AT')
334 Locale('de', territory='AT')
336 Modifiers are optional, and always at the end, separated by "@":
338 >>> Locale.parse('de_AT@euro')
339 Locale('de', territory='AT', modifier='euro')
341 :param identifier: the locale identifier string
342 :param sep: optional component separator
343 :param resolve_likely_subtags: if this is specified then a locale will
344 have its likely subtag resolved if the
345 locale otherwise does not exist. For
346 instance ``zh_TW`` by itself is not a
347 locale that exists but Babel can
348 automatically expand it to the full
349 form of ``zh_hant_TW``. Note that this
350 expansion is only taking place if no
351 locale exists otherwise. For instance
352 there is a locale ``en`` that can exist
353 by itself.
354 :raise `ValueError`: if the string does not appear to be a valid locale
355 identifier
356 :raise `UnknownLocaleError`: if no locale data is available for the
357 requested locale
358 :raise `TypeError`: if the identifier is not a string or a `Locale`
359 :raise `ValueError`: if the identifier is not a valid string
360 """
361 if isinstance(identifier, Locale):
362 return identifier
364 if not identifier:
365 msg = (
366 f"Empty locale identifier value: {identifier!r}\n\n"
367 f"If you didn't explicitly pass an empty value to a Babel function, "
368 f"this could be caused by there being no suitable locale environment "
369 f"variables for the API you tried to use."
370 )
371 if isinstance(identifier, str):
372 # `parse_locale` would raise a ValueError, so let's do that here
373 raise ValueError(msg)
374 raise TypeError(msg)
376 if not isinstance(identifier, str):
377 raise TypeError(f"Unexpected value for identifier: {identifier!r}")
379 # C/POSIX is not a CLDR language. Same mapping default_locale uses.
380 posix_stem = identifier.partition(".")[0]
381 if posix_stem.upper() in {"C", "POSIX"}:
382 identifier = "en_US_POSIX"
384 parts = parse_locale(identifier, sep=sep)
385 input_id = get_locale_identifier(parts)
387 def _try_load(parts):
388 try:
389 return cls(*parts)
390 except UnknownLocaleError:
391 return None
393 def _try_load_reducing(parts):
394 # Success on first hit, return it.
395 locale = _try_load(parts)
396 if locale is not None:
397 return locale
399 # Now try without script and variant
400 locale = _try_load(parts[:2])
401 if locale is not None:
402 return locale
404 locale = _try_load(parts)
405 if locale is not None:
406 return locale
407 if not resolve_likely_subtags:
408 raise UnknownLocaleError(input_id)
410 # From here onwards is some very bad likely subtag resolving. This
411 # whole logic is not entirely correct but good enough (tm) for the
412 # time being. This has been added so that zh_TW does not cause
413 # errors for people when they upgrade. Later we should properly
414 # implement ICU like fuzzy locale objects and provide a way to
415 # maximize and minimize locale tags.
417 if len(parts) == 5:
418 language, territory, script, variant, modifier = parts
419 else:
420 language, territory, script, variant = parts
421 modifier = None
422 language = get_global('language_aliases').get(language, language)
423 territory = get_global('territory_aliases').get(territory or '', (territory,))[0]
424 script = get_global('script_aliases').get(script or '', script)
425 variant = get_global('variant_aliases').get(variant or '', variant)
427 if territory == 'ZZ':
428 territory = None
429 if script == 'Zzzz':
430 script = None
432 parts = language, territory, script, variant, modifier
434 # First match: try the whole identifier
435 new_id = get_locale_identifier(parts)
436 likely_subtag = get_global('likely_subtags').get(new_id)
437 if likely_subtag is not None:
438 locale = _try_load_reducing(parse_locale(likely_subtag))
439 if locale is not None:
440 return locale
442 # If we did not find anything so far, try again with a
443 # simplified identifier that is just the language
444 likely_subtag = get_global('likely_subtags').get(language)
445 if likely_subtag is not None:
446 parts2 = parse_locale(likely_subtag)
447 if len(parts2) == 5:
448 language2, _, script2, variant2, modifier2 = parts2
449 else:
450 language2, _, script2, variant2 = parts2
451 modifier2 = None
452 locale = _try_load_reducing(
453 (language2, territory, script2, variant2, modifier2),
454 )
455 if locale is not None:
456 return locale
458 raise UnknownLocaleError(input_id)
460 def __eq__(self, other: object) -> bool:
461 for key in ('language', 'territory', 'script', 'variant', 'modifier'):
462 if not hasattr(other, key):
463 return False
464 return (
465 self.language == getattr(other, 'language') # noqa: B009
466 and self.territory == getattr(other, 'territory') # noqa: B009
467 and self.script == getattr(other, 'script') # noqa: B009
468 and self.variant == getattr(other, 'variant') # noqa: B009
469 and self.modifier == getattr(other, 'modifier') # noqa: B009
470 )
472 def __ne__(self, other: object) -> bool:
473 return not self.__eq__(other)
475 def __hash__(self) -> int:
476 return hash((self.language, self.territory, self.script, self.variant, self.modifier))
478 def __repr__(self) -> str:
479 parameters = ['']
480 for key in ('territory', 'script', 'variant', 'modifier'):
481 value = getattr(self, key)
482 if value is not None:
483 parameters.append(f"{key}={value!r}")
484 return f"Locale({self.language!r}{', '.join(parameters)})"
486 def __str__(self) -> str:
487 return get_locale_identifier(
488 (self.language, self.territory, self.script, self.variant, self.modifier),
489 )
491 @property
492 def _data(self) -> localedata.LocaleDataDict:
493 if self.__data is None:
494 self.__data = localedata.get_locale_data(self.__data_identifier)
495 return self.__data
497 def get_display_name(self, locale: Locale | str | None = None) -> str | None:
498 """Return the display name of the locale using the given locale.
500 The display name will include the language, territory, script, and
501 variant, if those are specified.
503 >>> Locale('zh', 'CN', script='Hans').get_display_name('en')
504 'Chinese (Simplified, China)'
506 Modifiers are currently passed through verbatim:
508 >>> Locale('it', 'IT', modifier='euro').get_display_name('en')
509 'Italian (Italy, euro)'
511 :param locale: the locale to use
512 """
513 if locale is None:
514 locale = self
515 locale = Locale.parse(locale)
516 retval = locale.languages.get(self.language)
517 if retval and (self.territory or self.script or self.variant):
518 details = []
519 if self.script:
520 details.append(locale.scripts.get(self.script))
521 if self.territory:
522 details.append(locale.territories.get(self.territory))
523 if self.variant:
524 details.append(locale.variants.get(self.variant))
525 if self.modifier:
526 details.append(self.modifier)
527 detail_string = ', '.join(atom for atom in details if atom)
528 if detail_string:
529 retval += f" ({detail_string})"
530 return retval
532 @property
533 def display_name(self) -> str | None:
534 """
535 The localized display name of the locale.
537 >>> Locale('en').display_name
538 'English'
539 >>> Locale('en', 'US').display_name
540 'English (United States)'
541 >>> Locale('sv').display_name
542 'svenska'
543 """
544 return self.get_display_name()
546 def get_language_name(self, locale: Locale | str | None = None) -> str | None:
547 """Return the language of this locale in the given locale.
549 >>> Locale('zh', 'CN', script='Hans').get_language_name('de')
550 'Chinesisch'
552 .. versionadded:: 1.0
554 :param locale: the locale to use
555 """
556 if locale is None:
557 locale = self
558 locale = Locale.parse(locale)
559 return locale.languages.get(self.language)
561 @property
562 def language_name(self) -> str | None:
563 """
564 The localized language name of the locale.
566 >>> Locale('en', 'US').language_name
567 'English'
568 """
569 return self.get_language_name()
571 def get_territory_name(self, locale: Locale | str | None = None) -> str | None:
572 """Return the territory name in the given locale."""
573 if locale is None:
574 locale = self
575 locale = Locale.parse(locale)
576 return locale.territories.get(self.territory or '')
578 @property
579 def territory_name(self) -> str | None:
580 """
581 The localized territory name of the locale if available.
583 >>> Locale('de', 'DE').territory_name
584 'Deutschland'
585 """
586 return self.get_territory_name()
588 def get_script_name(self, locale: Locale | str | None = None) -> str | None:
589 """Return the script name in the given locale."""
590 if locale is None:
591 locale = self
592 locale = Locale.parse(locale)
593 return locale.scripts.get(self.script or '')
595 @property
596 def script_name(self) -> str | None:
597 """
598 The localized script name of the locale if available.
600 >>> Locale('sr', 'ME', script='Latn').script_name
601 'latinica'
602 """
603 return self.get_script_name()
605 @property
606 def english_name(self) -> str | None:
607 """The english display name of the locale.
609 >>> Locale('de').english_name
610 'German'
611 >>> Locale('de', 'DE').english_name
612 'German (Germany)'
613 """
614 return self.get_display_name(Locale('en'))
616 # { General Locale Display Names
618 @property
619 def languages(self) -> localedata.LocaleDataDict:
620 """Mapping of language codes to translated language names.
622 >>> Locale('de', 'DE').languages['ja']
623 'Japanisch'
625 See `ISO 639 <https://www.loc.gov/standards/iso639-2/>`_ for
626 more information.
627 """
628 return self._data['languages']
630 @property
631 def scripts(self) -> localedata.LocaleDataDict:
632 """Mapping of script codes to translated script names.
634 >>> Locale('en', 'US').scripts['Hira']
635 'Hiragana'
637 See `ISO 15924 <https://www.unicode.org/iso15924/>`_
638 for more information.
639 """
640 return self._data['scripts']
642 @property
643 def territories(self) -> localedata.LocaleDataDict:
644 """Mapping of script codes to translated script names.
646 >>> Locale('es', 'CO').territories['DE']
647 'Alemania'
649 See `ISO 3166 <https://en.wikipedia.org/wiki/ISO_3166>`_
650 for more information.
651 """
652 return self._data['territories']
654 @property
655 def variants(self) -> localedata.LocaleDataDict:
656 """Mapping of script codes to translated script names.
658 >>> Locale('de', 'DE').variants['1901']
659 'Alte deutsche Rechtschreibung'
660 """
661 return self._data['variants']
663 # { Number Formatting
665 @property
666 def currencies(self) -> localedata.LocaleDataDict:
667 """Mapping of currency codes to translated currency names. This
668 only returns the generic form of the currency name, not the count
669 specific one. If an actual number is requested use the
670 :func:`babel.numbers.get_currency_name` function.
672 >>> Locale('en').currencies['COP']
673 'Colombian Peso'
674 >>> Locale('de', 'DE').currencies['COP']
675 'Kolumbianischer Peso'
676 """
677 return self._data['currency_names']
679 @property
680 def currency_symbols(self) -> localedata.LocaleDataDict:
681 """Mapping of currency codes to symbols.
683 >>> Locale('en', 'US').currency_symbols['USD']
684 '$'
685 >>> Locale('es', 'CO').currency_symbols['USD']
686 'US$'
687 """
688 return self._data['currency_symbols']
690 @property
691 def number_symbols(self) -> localedata.LocaleDataDict:
692 """Symbols used in number formatting by number system.
694 .. note:: The format of the value returned may change between
695 Babel versions.
697 >>> Locale('fr', 'FR').number_symbols["latn"]['decimal']
698 ','
699 >>> Locale('fa', 'IR').number_symbols["arabext"]['decimal']
700 '٫'
701 >>> Locale('fa', 'IR').number_symbols["latn"]['decimal']
702 '.'
703 """
704 return self._data['number_symbols']
706 @property
707 def other_numbering_systems(self) -> localedata.LocaleDataDict:
708 """
709 Mapping of other numbering systems available for the locale.
710 See: https://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems
712 >>> Locale('el', 'GR').other_numbering_systems['traditional']
713 'grek'
715 .. note:: The format of the value returned may change between
716 Babel versions.
717 """
718 return self._data['numbering_systems']
720 @property
721 def default_numbering_system(self) -> str:
722 """The default numbering system used by the locale.
723 >>> Locale('el', 'GR').default_numbering_system
724 'latn'
725 """
726 return self._data['default_numbering_system']
728 @property
729 def decimal_formats(self) -> localedata.LocaleDataDict:
730 """Locale patterns for decimal number formatting.
732 .. note:: The format of the value returned may change between
733 Babel versions.
735 >>> Locale('en', 'US').decimal_formats[None]
736 <NumberPattern '#,##0.###'>
737 """
738 return self._data['decimal_formats']
740 @property
741 def compact_decimal_formats(self) -> localedata.LocaleDataDict:
742 """Locale patterns for compact decimal number formatting.
744 .. note:: The format of the value returned may change between
745 Babel versions.
747 >>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"]
748 <NumberPattern '0K'>
749 """
750 return self._data['compact_decimal_formats']
752 @property
753 def currency_formats(self) -> localedata.LocaleDataDict:
754 """Locale patterns for currency number formatting.
756 .. note:: The format of the value returned may change between
757 Babel versions.
759 >>> Locale('en', 'US').currency_formats['standard']
760 <NumberPattern '\\xa4#,##0.00'>
761 >>> Locale('en', 'US').currency_formats['accounting']
762 <NumberPattern '\\xa4#,##0.00;(\\xa4#,##0.00)'>
763 """
764 return self._data['currency_formats']
766 @property
767 def compact_currency_formats(self) -> localedata.LocaleDataDict:
768 """Locale patterns for compact currency number formatting.
770 .. note:: The format of the value returned may change between
771 Babel versions.
773 >>> Locale('en', 'US').compact_currency_formats["short"]["one"]["1000"]
774 <NumberPattern '¤0K'>
775 """
776 return self._data['compact_currency_formats']
778 @property
779 def percent_formats(self) -> localedata.LocaleDataDict:
780 """Locale patterns for percent number formatting.
782 .. note:: The format of the value returned may change between
783 Babel versions.
785 >>> Locale('en', 'US').percent_formats[None]
786 <NumberPattern '#,##0%'>
787 """
788 return self._data['percent_formats']
790 @property
791 def scientific_formats(self) -> localedata.LocaleDataDict:
792 """Locale patterns for scientific number formatting.
794 .. note:: The format of the value returned may change between
795 Babel versions.
797 >>> Locale('en', 'US').scientific_formats[None]
798 <NumberPattern '#E0'>
799 """
800 return self._data['scientific_formats']
802 # { Calendar Information and Date Formatting
804 @property
805 def periods(self) -> localedata.LocaleDataDict:
806 """Locale display names for day periods (AM/PM).
808 >>> Locale('en', 'US').periods['am']
809 'AM'
810 """
811 try:
812 return self._data['day_periods']['stand-alone']['wide']
813 except KeyError:
814 return localedata.LocaleDataDict({}) # pragma: no cover
816 @property
817 def day_periods(self) -> localedata.LocaleDataDict:
818 """Locale display names for various day periods (not necessarily only AM/PM).
820 These are not meant to be used without the relevant `day_period_rules`.
821 """
822 return self._data['day_periods']
824 @property
825 def day_period_rules(self) -> localedata.LocaleDataDict:
826 """Day period rules for the locale. Used by `get_period_id`."""
827 return self._data.get('day_period_rules', localedata.LocaleDataDict({}))
829 @property
830 def days(self) -> localedata.LocaleDataDict:
831 """Locale display names for weekdays.
833 >>> Locale('de', 'DE').days['format']['wide'][3]
834 'Donnerstag'
835 """
836 return self._data['days']
838 @property
839 def months(self) -> localedata.LocaleDataDict:
840 """Locale display names for months.
842 >>> Locale('de', 'DE').months['format']['wide'][10]
843 'Oktober'
844 """
845 return self._data['months']
847 @property
848 def quarters(self) -> localedata.LocaleDataDict:
849 """Locale display names for quarters.
851 >>> Locale('de', 'DE').quarters['format']['wide'][1]
852 '1. Quartal'
853 """
854 return self._data['quarters']
856 @property
857 def eras(self) -> localedata.LocaleDataDict:
858 """Locale display names for eras.
860 .. note:: The format of the value returned may change between
861 Babel versions.
863 >>> Locale('en', 'US').eras['wide'][1]
864 'Anno Domini'
865 >>> Locale('en', 'US').eras['abbreviated'][0]
866 'BC'
867 """
868 return self._data['eras']
870 @property
871 def time_zones(self) -> localedata.LocaleDataDict:
872 """Locale display names for time zones.
874 The keys are canonical IANA time zone IDs like ``Asia/Kolkata``,
875 not ``Asia/Calcutta``, like the ``zone_aliases`` global.
877 .. note:: The format of the value returned may change between
878 Babel versions.
880 >>> Locale('en', 'US').time_zones['Etc/UTC']['long']['standard']
881 'Coordinated Universal Time'
882 >>> Locale('en', 'US').time_zones['America/St_Johns']['city']
883 'St. John’s'
884 >>> Locale('de').time_zones['Asia/Kolkata']['city']
885 'Kalkutta'
886 """
887 return self._data['time_zones']
889 @property
890 def meta_zones(self) -> localedata.LocaleDataDict:
891 """Locale display names for meta time zones.
893 Meta time zones are basically groups of different Olson time zones that
894 have the same GMT offset and daylight savings time.
896 .. note:: The format of the value returned may change between
897 Babel versions.
899 >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
900 'Central European Summer Time'
902 .. versionadded:: 0.9
903 """
904 return self._data['meta_zones']
906 @property
907 def zone_formats(self) -> localedata.LocaleDataDict:
908 """Patterns related to the formatting of time zones.
910 .. note:: The format of the value returned may change between
911 Babel versions.
913 >>> Locale('en', 'US').zone_formats['fallback']
914 '%(1)s (%(0)s)'
915 >>> Locale('pt', 'BR').zone_formats['region']
916 'Horário %s'
917 >>> Locale('sl').zone_formats['gmt']
918 'GMT %s'
919 >>> Locale('sl').zone_formats['gmt_utc']
920 'UTC %s'
922 .. versionadded:: 0.9
923 """
924 return self._data['zone_formats']
926 @property
927 def first_week_day(self) -> int:
928 """The first day of a week, with 0 being Monday.
930 >>> Locale('de', 'DE').first_week_day
931 0
932 >>> Locale('en', 'US').first_week_day
933 6
934 """
935 return self._data['week_data']['first_day']
937 @property
938 def weekend_start(self) -> int:
939 """The day the weekend starts, with 0 being Monday.
941 >>> Locale('de', 'DE').weekend_start
942 5
943 """
944 return self._data['week_data']['weekend_start']
946 @property
947 def weekend_end(self) -> int:
948 """The day the weekend ends, with 0 being Monday.
950 >>> Locale('de', 'DE').weekend_end
951 6
952 """
953 return self._data['week_data']['weekend_end']
955 @property
956 def min_week_days(self) -> int:
957 """The minimum number of days in a week so that the week is counted as
958 the first week of a year or month.
960 >>> Locale('de', 'DE').min_week_days
961 4
962 """
963 return self._data['week_data']['min_days']
965 @property
966 def date_formats(self) -> localedata.LocaleDataDict:
967 """Locale patterns for date formatting.
969 .. note:: The format of the value returned may change between
970 Babel versions.
972 >>> Locale('en', 'US').date_formats['short']
973 <DateTimePattern 'M/d/yy'>
974 >>> Locale('fr', 'FR').date_formats['long']
975 <DateTimePattern 'd MMMM y'>
976 """
977 return self._data['date_formats']
979 @property
980 def time_formats(self) -> localedata.LocaleDataDict:
981 """Locale patterns for time formatting.
983 .. note:: The format of the value returned may change between
984 Babel versions.
986 >>> Locale('en', 'US').time_formats['short']
987 <DateTimePattern 'h:mm\\u202fa'>
988 >>> Locale('fr', 'FR').time_formats['long']
989 <DateTimePattern 'HH:mm:ss z'>
990 """
991 return self._data['time_formats']
993 @property
994 def datetime_formats(self) -> localedata.LocaleDataDict:
995 """Locale patterns for datetime formatting.
997 .. note:: The format of the value returned may change between
998 Babel versions.
1000 >>> Locale('en').datetime_formats['full']
1001 '{1}, {0}'
1002 >>> Locale('th').datetime_formats['medium']
1003 '{1} {0}'
1004 """
1005 return self._data['datetime_formats']
1007 @property
1008 def datetime_formats_relative(self) -> localedata.LocaleDataDict:
1009 """Locale patterns for combining a relative date with a time.
1011 Introduced in CLDR 48.
1013 .. note:: The format of the value returned may change between
1014 Babel versions.
1016 >>> Locale('en').datetime_formats_relative['full']
1017 "{1} 'at' {0}"
1019 .. versionadded:: 2.19
1020 """
1021 return self._data['datetime_formats_relative']
1023 @property
1024 def datetime_skeletons(self) -> localedata.LocaleDataDict:
1025 """Locale patterns for formatting parts of a datetime.
1027 >>> Locale('en').datetime_skeletons['MEd']
1028 <DateTimePattern 'E, M/d'>
1029 >>> Locale('fr').datetime_skeletons['MEd']
1030 <DateTimePattern 'E dd/MM'>
1031 >>> Locale('fr').datetime_skeletons['H']
1032 <DateTimePattern 'HH'>
1033 """
1034 return self._data['datetime_skeletons']
1036 @property
1037 def interval_formats(self) -> localedata.LocaleDataDict:
1038 """Locale patterns for interval formatting.
1040 .. note:: The format of the value returned may change between
1041 Babel versions.
1043 How to format date intervals in Finnish when the day is the
1044 smallest changing component:
1046 >>> Locale('fi_FI').interval_formats['MEd']['d']
1047 ['E d.\\u2009–\\u2009', 'E d.M.']
1049 .. seealso::
1051 The primary API to use this data is :py:func:`babel.dates.format_interval`.
1054 :rtype: dict[str, dict[str, list[str]]]
1055 """
1056 return self._data['interval_formats']
1058 @property
1059 def plural_form(self) -> PluralRule:
1060 """Plural rules for the locale.
1062 >>> Locale('en').plural_form(1)
1063 'one'
1064 >>> Locale('en').plural_form(0)
1065 'other'
1066 >>> Locale('fr').plural_form(0)
1067 'one'
1068 >>> Locale('ru').plural_form(100)
1069 'many'
1070 """
1071 return self._data.get('plural_form', _default_plural_rule)
1073 @property
1074 def list_patterns(self) -> localedata.LocaleDataDict:
1075 """Patterns for generating lists
1077 .. note:: The format of the value returned may change between
1078 Babel versions.
1080 >>> Locale('en').list_patterns['standard']['start']
1081 '{0}, {1}'
1082 >>> Locale('en').list_patterns['standard']['end']
1083 '{0}, and {1}'
1084 >>> Locale('en_GB').list_patterns['standard']['end']
1085 '{0} and {1}'
1086 """
1087 return self._data['list_patterns']
1089 @property
1090 def ordinal_form(self) -> PluralRule:
1091 """Plural rules for the locale.
1093 >>> Locale('en').ordinal_form(1)
1094 'one'
1095 >>> Locale('en').ordinal_form(2)
1096 'two'
1097 >>> Locale('en').ordinal_form(3)
1098 'few'
1099 >>> Locale('fr').ordinal_form(2)
1100 'other'
1101 >>> Locale('ru').ordinal_form(100)
1102 'other'
1103 """
1104 return self._data.get('ordinal_form', _default_plural_rule)
1106 @property
1107 def measurement_systems(self) -> localedata.LocaleDataDict:
1108 """Localized names for various measurement systems.
1110 >>> Locale('fr', 'FR').measurement_systems['US']
1111 'américain'
1112 >>> Locale('en', 'US').measurement_systems['US']
1113 'US'
1115 """
1116 return self._data['measurement_systems']
1118 @property
1119 def character_order(self) -> str:
1120 """The text direction for the language.
1122 >>> Locale('de', 'DE').character_order
1123 'left-to-right'
1124 >>> Locale('ar', 'SA').character_order
1125 'right-to-left'
1126 """
1127 return self._data['character_order']
1129 @property
1130 def text_direction(self) -> str:
1131 """The text direction for the language in CSS short-hand form.
1133 >>> Locale('de', 'DE').text_direction
1134 'ltr'
1135 >>> Locale('ar', 'SA').text_direction
1136 'rtl'
1137 """
1138 return ''.join(word[0] for word in self.character_order.split('-'))
1140 @property
1141 def unit_display_names(self) -> localedata.LocaleDataDict:
1142 """Display names for units of measurement.
1144 .. seealso::
1146 You may want to use :py:func:`babel.units.get_unit_name` instead.
1148 .. note:: The format of the value returned may change between
1149 Babel versions.
1151 """
1152 return self._data['unit_display_names']
1155def default_locale(
1156 category: str | tuple[str, ...] | list[str] | None = None,
1157 aliases: Mapping[str, str] = LOCALE_ALIASES,
1158) -> str | None:
1159 """Returns the system default locale for a given category, based on
1160 environment variables.
1162 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']:
1163 ... os.environ[name] = ''
1164 >>> os.environ['LANG'] = 'fr_FR.UTF-8'
1165 >>> default_locale('LC_MESSAGES')
1166 'fr_FR'
1168 The "C" or "POSIX" pseudo-locales are treated as aliases for the
1169 "en_US_POSIX" locale:
1171 >>> os.environ['LC_MESSAGES'] = 'POSIX'
1172 >>> default_locale('LC_MESSAGES')
1173 'en_US_POSIX'
1175 The following fallbacks to the variable are always considered:
1177 - ``LANGUAGE``
1178 - ``LC_ALL``
1179 - ``LC_CTYPE``
1180 - ``LANG``
1182 :param category: one or more of the ``LC_XXX`` environment variable names
1183 :param aliases: a dictionary of aliases for locale identifiers
1184 """
1186 varnames = ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
1187 if category:
1188 if isinstance(category, str):
1189 varnames = (category, *varnames)
1190 elif isinstance(category, (list, tuple)):
1191 varnames = (*category, *varnames)
1192 else:
1193 raise TypeError(f"Invalid type for category: {category!r}")
1195 for name in varnames:
1196 if not name:
1197 continue
1198 locale = os.getenv(name)
1199 if locale:
1200 if name == 'LANGUAGE' and ':' in locale:
1201 # the LANGUAGE variable may contain a colon-separated list of
1202 # language codes; we just pick the language on the list
1203 locale = locale.split(':')[0]
1204 if locale.split('.')[0] in ('C', 'POSIX'):
1205 locale = 'en_US_POSIX'
1206 elif aliases and locale in aliases:
1207 locale = aliases[locale]
1208 try:
1209 return get_locale_identifier(parse_locale(locale))
1210 except ValueError:
1211 pass
1212 return None
1215def negotiate_locale(
1216 preferred: Iterable[str],
1217 available: Iterable[str],
1218 sep: str = '_',
1219 aliases: Mapping[str, str] = LOCALE_ALIASES,
1220) -> str | None:
1221 """Find the best match between available and requested locale strings.
1223 >>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
1224 'de_DE'
1225 >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de'])
1226 'de'
1228 Case is ignored by the algorithm, the result uses the case of the preferred
1229 locale identifier:
1231 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1232 'de_DE'
1234 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1235 'de_DE'
1237 By default, some web browsers unfortunately do not include the territory
1238 in the locale identifier for many locales, and some don't even allow the
1239 user to easily add the territory. So while you may prefer using qualified
1240 locale identifiers in your web-application, they would not normally match
1241 the language-only locale sent by such browsers. To workaround that, this
1242 function uses a default mapping of commonly used language-only locale
1243 identifiers to identifiers including the territory:
1245 >>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US'])
1246 'ja_JP'
1248 Some browsers even use an incorrect or outdated language code, such as "no"
1249 for Norwegian, where the correct locale identifier would actually be "nb_NO"
1250 (Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of
1251 such cases, too:
1253 >>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE'])
1254 'nb_NO'
1256 You can override this default mapping by passing a different `aliases`
1257 dictionary to this function, or you can bypass the behavior althogher by
1258 setting the `aliases` parameter to `None`.
1260 :param preferred: the list of locale strings preferred by the user
1261 :param available: the list of locale strings available
1262 :param sep: character that separates the different parts of the locale
1263 strings
1264 :param aliases: a dictionary of aliases for locale identifiers
1265 """
1266 available = [a.lower() for a in available if a]
1267 for locale in preferred:
1268 ll = locale.lower()
1269 if ll in available:
1270 return locale
1271 if aliases:
1272 alias = aliases.get(ll)
1273 if alias:
1274 alias = alias.replace('_', sep)
1275 if alias.lower() in available:
1276 return alias
1277 parts = locale.split(sep)
1278 if len(parts) > 1 and parts[0].lower() in available:
1279 return parts[0]
1280 return None
1283def parse_locale(
1284 identifier: str,
1285 sep: str = '_',
1286) -> (
1287 tuple[str, str | None, str | None, str | None]
1288 | tuple[str, str | None, str | None, str | None, str | None]
1289):
1290 """Parse a locale identifier into a tuple of the form ``(language,
1291 territory, script, variant, modifier)``.
1293 >>> parse_locale('zh_CN')
1294 ('zh', 'CN', None, None)
1295 >>> parse_locale('zh_Hans_CN')
1296 ('zh', 'CN', 'Hans', None)
1297 >>> parse_locale('ca_es_valencia')
1298 ('ca', 'ES', None, 'VALENCIA')
1299 >>> parse_locale('en_150')
1300 ('en', '150', None, None)
1301 >>> parse_locale('en_us_posix')
1302 ('en', 'US', None, 'POSIX')
1303 >>> parse_locale('it_IT@euro')
1304 ('it', 'IT', None, None, 'euro')
1305 >>> parse_locale('it_IT@custom')
1306 ('it', 'IT', None, None, 'custom')
1307 >>> parse_locale('it_IT@')
1308 ('it', 'IT', None, None)
1310 The default component separator is "_", but a different separator can be
1311 specified using the `sep` parameter.
1313 The optional modifier is always separated with "@" and at the end:
1315 >>> parse_locale('zh-CN', sep='-')
1316 ('zh', 'CN', None, None)
1317 >>> parse_locale('zh-CN@custom', sep='-')
1318 ('zh', 'CN', None, None, 'custom')
1320 If the identifier cannot be parsed into a locale, a `ValueError` exception
1321 is raised:
1323 >>> parse_locale('not_a_LOCALE_String')
1324 Traceback (most recent call last):
1325 ...
1326 ValueError: 'not_a_LOCALE_String' is not a valid locale identifier
1328 Encoding information is removed from the identifier, while modifiers are
1329 kept:
1331 >>> parse_locale('en_US.UTF-8')
1332 ('en', 'US', None, None)
1333 >>> parse_locale('de_DE.iso885915@euro')
1334 ('de', 'DE', None, None, 'euro')
1336 See :rfc:`4646` for more information.
1338 :param identifier: the locale identifier string
1339 :param sep: character that separates the different components of the locale
1340 identifier
1341 :raise `ValueError`: if the string does not appear to be a valid locale
1342 identifier
1343 """
1344 if not identifier:
1345 raise ValueError("empty locale identifier")
1346 identifier, _, modifier = identifier.partition('@')
1347 if '.' in identifier:
1348 # this is probably the charset/encoding, which we don't care about
1349 identifier = identifier.split('.', 1)[0]
1351 parts = identifier.split(sep)
1352 lang = parts.pop(0).lower()
1353 if not lang.isalpha():
1354 raise ValueError(f"expected only letters, got {lang!r}")
1356 script = territory = variant = None
1357 if parts and len(parts[0]) == 4 and parts[0].isalpha():
1358 script = parts.pop(0).title()
1360 if parts:
1361 if len(parts[0]) == 2 and parts[0].isalpha():
1362 territory = parts.pop(0).upper()
1363 elif len(parts[0]) == 3 and parts[0].isdigit():
1364 territory = parts.pop(0)
1366 if parts and (
1367 len(parts[0]) == 4
1368 and parts[0][0].isdigit()
1369 or len(parts[0]) >= 5
1370 and parts[0][0].isalpha()
1371 ):
1372 variant = parts.pop().upper()
1374 if parts:
1375 raise ValueError(f"{identifier!r} is not a valid locale identifier")
1377 # TODO(3.0): always return a 5-tuple
1378 if modifier:
1379 return lang, territory, script, variant, modifier
1380 else:
1381 return lang, territory, script, variant
1384def get_locale_identifier(
1385 tup: tuple[str]
1386 | tuple[str, str | None]
1387 | tuple[str, str | None, str | None]
1388 | tuple[str, str | None, str | None, str | None]
1389 | tuple[str, str | None, str | None, str | None, str | None],
1390 sep: str = "_",
1391) -> str:
1392 """The reverse of :func:`parse_locale`. It creates a locale identifier out
1393 of a ``(language, territory, script, variant, modifier)`` tuple. Items can be set to
1394 ``None`` and trailing ``None``\\s can also be left out of the tuple.
1396 >>> get_locale_identifier(('de', 'DE', None, '1999', 'custom'))
1397 'de_DE_1999@custom'
1398 >>> get_locale_identifier(('fi', None, None, None, 'custom'))
1399 'fi@custom'
1402 .. versionadded:: 1.0
1404 :param tup: the tuple as returned by :func:`parse_locale`.
1405 :param sep: the separator for the identifier.
1406 """
1407 tup = tuple(tup[:5]) # type: ignore # length should be no more than 5
1408 lang, territory, script, variant, modifier = tup + (None,) * (5 - len(tup))
1409 ret = sep.join(filter(None, (lang, script, territory, variant)))
1410 return f'{ret}@{modifier}' if modifier else ret
1413def get_cldr_version() -> str:
1414 """Return the Unicode CLDR version used by this Babel installation.
1416 Generally, you should be able to assume that the return value of this
1417 function is a string representing a version number, e.g. '47'.
1419 >>> get_cldr_version()
1420 '49'
1422 .. versionadded:: 2.18
1424 :rtype: str
1425 """
1426 return str(get_global("cldr")["version"])