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"""
2 babel.core
3 ~~~~~~~~~~
5 Core 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_global',
26 'get_locale_identifier',
27 'negotiate_locale',
28 'parse_locale',
29]
31if TYPE_CHECKING:
32 from typing_extensions import TypeAlias
34 _GLOBAL_KEY: TypeAlias = Literal[
35 "all_currencies",
36 "currency_fractions",
37 "language_aliases",
38 "likely_subtags",
39 "meta_zones",
40 "parent_exceptions",
41 "script_aliases",
42 "territory_aliases",
43 "territory_currencies",
44 "territory_languages",
45 "territory_zones",
46 "variant_aliases",
47 "windows_zone_mapping",
48 "zone_aliases",
49 "zone_territories",
50 ]
52 _global_data: Mapping[_GLOBAL_KEY, Mapping[str, Any]] | None
54_global_data = None
55_default_plural_rule = PluralRule({})
58def _raise_no_data_error():
59 raise RuntimeError('The babel data files are not available. '
60 'This usually happens because you are using '
61 'a source checkout from Babel and you did '
62 'not build the data files. Just make sure '
63 'to run "python setup.py import_cldr" before '
64 'installing the library.')
67def get_global(key: _GLOBAL_KEY) -> Mapping[str, Any]:
68 """Return the dictionary for the given key in the global data.
70 The global data is stored in the ``babel/global.dat`` file and contains
71 information independent of individual locales.
73 >>> get_global('zone_aliases')['UTC']
74 u'Etc/UTC'
75 >>> get_global('zone_territories')['Europe/Berlin']
76 u'DE'
78 The keys available are:
80 - ``all_currencies``
81 - ``currency_fractions``
82 - ``language_aliases``
83 - ``likely_subtags``
84 - ``parent_exceptions``
85 - ``script_aliases``
86 - ``territory_aliases``
87 - ``territory_currencies``
88 - ``territory_languages``
89 - ``territory_zones``
90 - ``variant_aliases``
91 - ``windows_zone_mapping``
92 - ``zone_aliases``
93 - ``zone_territories``
95 .. note:: The internal structure of the data may change between versions.
97 .. versionadded:: 0.9
99 :param key: the data key
100 """
101 global _global_data
102 if _global_data is None:
103 dirname = os.path.join(os.path.dirname(__file__))
104 filename = os.path.join(dirname, 'global.dat')
105 if not os.path.isfile(filename):
106 _raise_no_data_error()
107 with open(filename, 'rb') as fileobj:
108 _global_data = pickle.load(fileobj)
109 assert _global_data is not None
110 return _global_data.get(key, {})
113LOCALE_ALIASES = {
114 'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ',
115 'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES',
116 'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES',
117 'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT',
118 'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV',
119 'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL',
120 'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI',
121 'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA',
122}
125class UnknownLocaleError(Exception):
126 """Exception thrown when a locale is requested for which no locale data
127 is available.
128 """
130 def __init__(self, identifier: str) -> None:
131 """Create the exception.
133 :param identifier: the identifier string of the unsupported locale
134 """
135 Exception.__init__(self, f"unknown locale {identifier!r}")
137 #: The identifier of the locale that could not be found.
138 self.identifier = identifier
141class Locale:
142 """Representation of a specific locale.
144 >>> locale = Locale('en', 'US')
145 >>> repr(locale)
146 "Locale('en', territory='US')"
147 >>> locale.display_name
148 u'English (United States)'
150 A `Locale` object can also be instantiated from a raw locale string:
152 >>> locale = Locale.parse('en-US', sep='-')
153 >>> repr(locale)
154 "Locale('en', territory='US')"
156 `Locale` objects provide access to a collection of locale data, such as
157 territory and language names, number and date format patterns, and more:
159 >>> locale.number_symbols['latn']['decimal']
160 u'.'
162 If a locale is requested for which no locale data is available, an
163 `UnknownLocaleError` is raised:
165 >>> Locale.parse('en_XX')
166 Traceback (most recent call last):
167 ...
168 UnknownLocaleError: unknown locale 'en_XX'
170 For more information see :rfc:`3066`.
171 """
173 def __init__(
174 self,
175 language: str,
176 territory: str | None = None,
177 script: str | None = None,
178 variant: str | None = None,
179 modifier: str | None = None,
180 ) -> None:
181 """Initialize the locale object from the given identifier components.
183 >>> locale = Locale('en', 'US')
184 >>> locale.language
185 'en'
186 >>> locale.territory
187 'US'
189 :param language: the language code
190 :param territory: the territory (country or region) code
191 :param script: the script code
192 :param variant: the variant code
193 :param modifier: a modifier (following the '@' symbol, sometimes called '@variant')
194 :raise `UnknownLocaleError`: if no locale data is available for the
195 requested locale
196 """
197 #: the language code
198 self.language = language
199 #: the territory (country or region) code
200 self.territory = territory
201 #: the script code
202 self.script = script
203 #: the variant code
204 self.variant = variant
205 #: the modifier
206 self.modifier = modifier
207 self.__data: localedata.LocaleDataDict | None = None
209 identifier = str(self)
210 identifier_without_modifier = identifier.partition('@')[0]
211 if localedata.exists(identifier):
212 self.__data_identifier = identifier
213 elif localedata.exists(identifier_without_modifier):
214 self.__data_identifier = identifier_without_modifier
215 else:
216 raise UnknownLocaleError(identifier)
218 @classmethod
219 def default(cls, category: str | None = None, aliases: Mapping[str, str] = LOCALE_ALIASES) -> Locale:
220 """Return the system default locale for the specified category.
222 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
223 ... os.environ[name] = ''
224 >>> os.environ['LANG'] = 'fr_FR.UTF-8'
225 >>> Locale.default('LC_MESSAGES')
226 Locale('fr', territory='FR')
228 The following fallbacks to the variable are always considered:
230 - ``LANGUAGE``
231 - ``LC_ALL``
232 - ``LC_CTYPE``
233 - ``LANG``
235 :param category: one of the ``LC_XXX`` environment variable names
236 :param aliases: a dictionary of aliases for locale identifiers
237 """
238 # XXX: use likely subtag expansion here instead of the
239 # aliases dictionary.
240 locale_string = default_locale(category, aliases=aliases)
241 return cls.parse(locale_string)
243 @classmethod
244 def negotiate(
245 cls,
246 preferred: Iterable[str],
247 available: Iterable[str],
248 sep: str = '_',
249 aliases: Mapping[str, str] = LOCALE_ALIASES,
250 ) -> Locale | None:
251 """Find the best match between available and requested locale strings.
253 >>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
254 Locale('de', territory='DE')
255 >>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de'])
256 Locale('de')
257 >>> Locale.negotiate(['de_DE', 'de'], ['en_US'])
259 You can specify the character used in the locale identifiers to separate
260 the different components. This separator is applied to both lists. Also,
261 case is ignored in the comparison:
263 >>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-')
264 Locale('de', territory='DE')
266 :param preferred: the list of locale identifiers preferred by the user
267 :param available: the list of locale identifiers available
268 :param aliases: a dictionary of aliases for locale identifiers
269 :param sep: separator for parsing; e.g. Windows tends to use '-' instead of '_'.
270 """
271 identifier = negotiate_locale(preferred, available, sep=sep,
272 aliases=aliases)
273 if identifier:
274 return Locale.parse(identifier, sep=sep)
275 return None
277 @classmethod
278 def parse(
279 cls,
280 identifier: Locale | str | None,
281 sep: str = '_',
282 resolve_likely_subtags: bool = True,
283 ) -> Locale:
284 """Create a `Locale` instance for the given locale identifier.
286 >>> l = Locale.parse('de-DE', sep='-')
287 >>> l.display_name
288 u'Deutsch (Deutschland)'
290 If the `identifier` parameter is not a string, but actually a `Locale`
291 object, that object is returned:
293 >>> Locale.parse(l)
294 Locale('de', territory='DE')
296 If the `identifier` parameter is neither of these, such as `None`
297 or an empty string, e.g. because a default locale identifier
298 could not be determined, a `TypeError` is raised:
300 >>> Locale.parse(None)
301 Traceback (most recent call last):
302 ...
303 TypeError: ...
305 This also can perform resolving of likely subtags which it does
306 by default. This is for instance useful to figure out the most
307 likely locale for a territory you can use ``'und'`` as the
308 language tag:
310 >>> Locale.parse('und_AT')
311 Locale('de', territory='AT')
313 Modifiers are optional, and always at the end, separated by "@":
315 >>> Locale.parse('de_AT@euro')
316 Locale('de', territory='AT', modifier='euro')
318 :param identifier: the locale identifier string
319 :param sep: optional component separator
320 :param resolve_likely_subtags: if this is specified then a locale will
321 have its likely subtag resolved if the
322 locale otherwise does not exist. For
323 instance ``zh_TW`` by itself is not a
324 locale that exists but Babel can
325 automatically expand it to the full
326 form of ``zh_hant_TW``. Note that this
327 expansion is only taking place if no
328 locale exists otherwise. For instance
329 there is a locale ``en`` that can exist
330 by itself.
331 :raise `ValueError`: if the string does not appear to be a valid locale
332 identifier
333 :raise `UnknownLocaleError`: if no locale data is available for the
334 requested locale
335 :raise `TypeError`: if the identifier is not a string or a `Locale`
336 :raise `ValueError`: if the identifier is not a valid string
337 """
338 if isinstance(identifier, Locale):
339 return identifier
341 if not identifier:
342 msg = (
343 f"Empty locale identifier value: {identifier!r}\n\n"
344 f"If you didn't explicitly pass an empty value to a Babel function, "
345 f"this could be caused by there being no suitable locale environment "
346 f"variables for the API you tried to use.",
347 )
348 if isinstance(identifier, str):
349 raise ValueError(msg) # `parse_locale` would raise a ValueError, so let's do that here
350 raise TypeError(msg)
352 if not isinstance(identifier, str):
353 raise TypeError(f"Unexpected value for identifier: {identifier!r}")
355 parts = parse_locale(identifier, sep=sep)
356 input_id = get_locale_identifier(parts)
358 def _try_load(parts):
359 try:
360 return cls(*parts)
361 except UnknownLocaleError:
362 return None
364 def _try_load_reducing(parts):
365 # Success on first hit, return it.
366 locale = _try_load(parts)
367 if locale is not None:
368 return locale
370 # Now try without script and variant
371 locale = _try_load(parts[:2])
372 if locale is not None:
373 return locale
375 locale = _try_load(parts)
376 if locale is not None:
377 return locale
378 if not resolve_likely_subtags:
379 raise UnknownLocaleError(input_id)
381 # From here onwards is some very bad likely subtag resolving. This
382 # whole logic is not entirely correct but good enough (tm) for the
383 # time being. This has been added so that zh_TW does not cause
384 # errors for people when they upgrade. Later we should properly
385 # implement ICU like fuzzy locale objects and provide a way to
386 # maximize and minimize locale tags.
388 if len(parts) == 5:
389 language, territory, script, variant, modifier = parts
390 else:
391 language, territory, script, variant = parts
392 modifier = None
393 language = get_global('language_aliases').get(language, language)
394 territory = get_global('territory_aliases').get(territory or '', (territory,))[0]
395 script = get_global('script_aliases').get(script or '', script)
396 variant = get_global('variant_aliases').get(variant or '', variant)
398 if territory == 'ZZ':
399 territory = None
400 if script == 'Zzzz':
401 script = None
403 parts = language, territory, script, variant, modifier
405 # First match: try the whole identifier
406 new_id = get_locale_identifier(parts)
407 likely_subtag = get_global('likely_subtags').get(new_id)
408 if likely_subtag is not None:
409 locale = _try_load_reducing(parse_locale(likely_subtag))
410 if locale is not None:
411 return locale
413 # If we did not find anything so far, try again with a
414 # simplified identifier that is just the language
415 likely_subtag = get_global('likely_subtags').get(language)
416 if likely_subtag is not None:
417 parts2 = parse_locale(likely_subtag)
418 if len(parts2) == 5:
419 language2, _, script2, variant2, modifier2 = parts2
420 else:
421 language2, _, script2, variant2 = parts2
422 modifier2 = None
423 locale = _try_load_reducing((language2, territory, script2, variant2, modifier2))
424 if locale is not None:
425 return locale
427 raise UnknownLocaleError(input_id)
429 def __eq__(self, other: object) -> bool:
430 for key in ('language', 'territory', 'script', 'variant', 'modifier'):
431 if not hasattr(other, key):
432 return False
433 return (
434 self.language == getattr(other, 'language') and # noqa: B009
435 self.territory == getattr(other, 'territory') and # noqa: B009
436 self.script == getattr(other, 'script') and # noqa: B009
437 self.variant == getattr(other, 'variant') and # noqa: B009
438 self.modifier == getattr(other, 'modifier') # noqa: B009
439 )
441 def __ne__(self, other: object) -> bool:
442 return not self.__eq__(other)
444 def __hash__(self) -> int:
445 return hash((self.language, self.territory, self.script,
446 self.variant, self.modifier))
448 def __repr__(self) -> str:
449 parameters = ['']
450 for key in ('territory', 'script', 'variant', 'modifier'):
451 value = getattr(self, key)
452 if value is not None:
453 parameters.append(f"{key}={value!r}")
454 return f"Locale({self.language!r}{', '.join(parameters)})"
456 def __str__(self) -> str:
457 return get_locale_identifier((self.language, self.territory,
458 self.script, self.variant,
459 self.modifier))
461 @property
462 def _data(self) -> localedata.LocaleDataDict:
463 if self.__data is None:
464 self.__data = localedata.LocaleDataDict(localedata.load(self.__data_identifier))
465 return self.__data
467 def get_display_name(self, locale: Locale | str | None = None) -> str | None:
468 """Return the display name of the locale using the given locale.
470 The display name will include the language, territory, script, and
471 variant, if those are specified.
473 >>> Locale('zh', 'CN', script='Hans').get_display_name('en')
474 u'Chinese (Simplified, China)'
476 Modifiers are currently passed through verbatim:
478 >>> Locale('it', 'IT', modifier='euro').get_display_name('en')
479 u'Italian (Italy, euro)'
481 :param locale: the locale to use
482 """
483 if locale is None:
484 locale = self
485 locale = Locale.parse(locale)
486 retval = locale.languages.get(self.language)
487 if retval and (self.territory or self.script or self.variant):
488 details = []
489 if self.script:
490 details.append(locale.scripts.get(self.script))
491 if self.territory:
492 details.append(locale.territories.get(self.territory))
493 if self.variant:
494 details.append(locale.variants.get(self.variant))
495 if self.modifier:
496 details.append(self.modifier)
497 detail_string = ', '.join(atom for atom in details if atom)
498 if detail_string:
499 retval += f" ({detail_string})"
500 return retval
502 display_name = property(get_display_name, doc="""\
503 The localized display name of the locale.
505 >>> Locale('en').display_name
506 u'English'
507 >>> Locale('en', 'US').display_name
508 u'English (United States)'
509 >>> Locale('sv').display_name
510 u'svenska'
512 :type: `unicode`
513 """)
515 def get_language_name(self, locale: Locale | str | None = None) -> str | None:
516 """Return the language of this locale in the given locale.
518 >>> Locale('zh', 'CN', script='Hans').get_language_name('de')
519 u'Chinesisch'
521 .. versionadded:: 1.0
523 :param locale: the locale to use
524 """
525 if locale is None:
526 locale = self
527 locale = Locale.parse(locale)
528 return locale.languages.get(self.language)
530 language_name = property(get_language_name, doc="""\
531 The localized language name of the locale.
533 >>> Locale('en', 'US').language_name
534 u'English'
535 """)
537 def get_territory_name(self, locale: Locale | str | None = None) -> str | None:
538 """Return the territory name in the given locale."""
539 if locale is None:
540 locale = self
541 locale = Locale.parse(locale)
542 return locale.territories.get(self.territory or '')
544 territory_name = property(get_territory_name, doc="""\
545 The localized territory name of the locale if available.
547 >>> Locale('de', 'DE').territory_name
548 u'Deutschland'
549 """)
551 def get_script_name(self, locale: Locale | str | None = None) -> str | None:
552 """Return the script name in the given locale."""
553 if locale is None:
554 locale = self
555 locale = Locale.parse(locale)
556 return locale.scripts.get(self.script or '')
558 script_name = property(get_script_name, doc="""\
559 The localized script name of the locale if available.
561 >>> Locale('sr', 'ME', script='Latn').script_name
562 u'latinica'
563 """)
565 @property
566 def english_name(self) -> str | None:
567 """The english display name of the locale.
569 >>> Locale('de').english_name
570 u'German'
571 >>> Locale('de', 'DE').english_name
572 u'German (Germany)'
574 :type: `unicode`"""
575 return self.get_display_name(Locale('en'))
577 # { General Locale Display Names
579 @property
580 def languages(self) -> localedata.LocaleDataDict:
581 """Mapping of language codes to translated language names.
583 >>> Locale('de', 'DE').languages['ja']
584 u'Japanisch'
586 See `ISO 639 <https://www.loc.gov/standards/iso639-2/>`_ for
587 more information.
588 """
589 return self._data['languages']
591 @property
592 def scripts(self) -> localedata.LocaleDataDict:
593 """Mapping of script codes to translated script names.
595 >>> Locale('en', 'US').scripts['Hira']
596 u'Hiragana'
598 See `ISO 15924 <https://www.unicode.org/iso15924/>`_
599 for more information.
600 """
601 return self._data['scripts']
603 @property
604 def territories(self) -> localedata.LocaleDataDict:
605 """Mapping of script codes to translated script names.
607 >>> Locale('es', 'CO').territories['DE']
608 u'Alemania'
610 See `ISO 3166 <https://en.wikipedia.org/wiki/ISO_3166>`_
611 for more information.
612 """
613 return self._data['territories']
615 @property
616 def variants(self) -> localedata.LocaleDataDict:
617 """Mapping of script codes to translated script names.
619 >>> Locale('de', 'DE').variants['1901']
620 u'Alte deutsche Rechtschreibung'
621 """
622 return self._data['variants']
624 # { Number Formatting
626 @property
627 def currencies(self) -> localedata.LocaleDataDict:
628 """Mapping of currency codes to translated currency names. This
629 only returns the generic form of the currency name, not the count
630 specific one. If an actual number is requested use the
631 :func:`babel.numbers.get_currency_name` function.
633 >>> Locale('en').currencies['COP']
634 u'Colombian Peso'
635 >>> Locale('de', 'DE').currencies['COP']
636 u'Kolumbianischer Peso'
637 """
638 return self._data['currency_names']
640 @property
641 def currency_symbols(self) -> localedata.LocaleDataDict:
642 """Mapping of currency codes to symbols.
644 >>> Locale('en', 'US').currency_symbols['USD']
645 u'$'
646 >>> Locale('es', 'CO').currency_symbols['USD']
647 u'US$'
648 """
649 return self._data['currency_symbols']
651 @property
652 def number_symbols(self) -> localedata.LocaleDataDict:
653 """Symbols used in number formatting by number system.
655 .. note:: The format of the value returned may change between
656 Babel versions.
658 >>> Locale('fr', 'FR').number_symbols["latn"]['decimal']
659 u','
660 >>> Locale('fa', 'IR').number_symbols["arabext"]['decimal']
661 u'٫'
662 >>> Locale('fa', 'IR').number_symbols["latn"]['decimal']
663 u'.'
664 """
665 return self._data['number_symbols']
667 @property
668 def other_numbering_systems(self) -> localedata.LocaleDataDict:
669 """
670 Mapping of other numbering systems available for the locale.
671 See: https://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems
673 >>> Locale('el', 'GR').other_numbering_systems['traditional']
674 u'grek'
676 .. note:: The format of the value returned may change between
677 Babel versions.
678 """
679 return self._data['numbering_systems']
681 @property
682 def default_numbering_system(self) -> str:
683 """The default numbering system used by the locale.
684 >>> Locale('el', 'GR').default_numbering_system
685 u'latn'
686 """
687 return self._data['default_numbering_system']
689 @property
690 def decimal_formats(self) -> localedata.LocaleDataDict:
691 """Locale patterns for decimal number formatting.
693 .. note:: The format of the value returned may change between
694 Babel versions.
696 >>> Locale('en', 'US').decimal_formats[None]
697 <NumberPattern u'#,##0.###'>
698 """
699 return self._data['decimal_formats']
701 @property
702 def compact_decimal_formats(self) -> localedata.LocaleDataDict:
703 """Locale patterns for compact decimal number formatting.
705 .. note:: The format of the value returned may change between
706 Babel versions.
708 >>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"]
709 <NumberPattern u'0K'>
710 """
711 return self._data['compact_decimal_formats']
713 @property
714 def currency_formats(self) -> localedata.LocaleDataDict:
715 """Locale patterns for currency number formatting.
717 .. note:: The format of the value returned may change between
718 Babel versions.
720 >>> Locale('en', 'US').currency_formats['standard']
721 <NumberPattern u'\\xa4#,##0.00'>
722 >>> Locale('en', 'US').currency_formats['accounting']
723 <NumberPattern u'\\xa4#,##0.00;(\\xa4#,##0.00)'>
724 """
725 return self._data['currency_formats']
727 @property
728 def compact_currency_formats(self) -> localedata.LocaleDataDict:
729 """Locale patterns for compact currency number formatting.
731 .. note:: The format of the value returned may change between
732 Babel versions.
734 >>> Locale('en', 'US').compact_currency_formats["short"]["one"]["1000"]
735 <NumberPattern u'¤0K'>
736 """
737 return self._data['compact_currency_formats']
739 @property
740 def percent_formats(self) -> localedata.LocaleDataDict:
741 """Locale patterns for percent number formatting.
743 .. note:: The format of the value returned may change between
744 Babel versions.
746 >>> Locale('en', 'US').percent_formats[None]
747 <NumberPattern u'#,##0%'>
748 """
749 return self._data['percent_formats']
751 @property
752 def scientific_formats(self) -> localedata.LocaleDataDict:
753 """Locale patterns for scientific number formatting.
755 .. note:: The format of the value returned may change between
756 Babel versions.
758 >>> Locale('en', 'US').scientific_formats[None]
759 <NumberPattern u'#E0'>
760 """
761 return self._data['scientific_formats']
763 # { Calendar Information and Date Formatting
765 @property
766 def periods(self) -> localedata.LocaleDataDict:
767 """Locale display names for day periods (AM/PM).
769 >>> Locale('en', 'US').periods['am']
770 u'AM'
771 """
772 try:
773 return self._data['day_periods']['stand-alone']['wide']
774 except KeyError:
775 return localedata.LocaleDataDict({}) # pragma: no cover
777 @property
778 def day_periods(self) -> localedata.LocaleDataDict:
779 """Locale display names for various day periods (not necessarily only AM/PM).
781 These are not meant to be used without the relevant `day_period_rules`.
782 """
783 return self._data['day_periods']
785 @property
786 def day_period_rules(self) -> localedata.LocaleDataDict:
787 """Day period rules for the locale. Used by `get_period_id`.
788 """
789 return self._data.get('day_period_rules', localedata.LocaleDataDict({}))
791 @property
792 def days(self) -> localedata.LocaleDataDict:
793 """Locale display names for weekdays.
795 >>> Locale('de', 'DE').days['format']['wide'][3]
796 u'Donnerstag'
797 """
798 return self._data['days']
800 @property
801 def months(self) -> localedata.LocaleDataDict:
802 """Locale display names for months.
804 >>> Locale('de', 'DE').months['format']['wide'][10]
805 u'Oktober'
806 """
807 return self._data['months']
809 @property
810 def quarters(self) -> localedata.LocaleDataDict:
811 """Locale display names for quarters.
813 >>> Locale('de', 'DE').quarters['format']['wide'][1]
814 u'1. Quartal'
815 """
816 return self._data['quarters']
818 @property
819 def eras(self) -> localedata.LocaleDataDict:
820 """Locale display names for eras.
822 .. note:: The format of the value returned may change between
823 Babel versions.
825 >>> Locale('en', 'US').eras['wide'][1]
826 u'Anno Domini'
827 >>> Locale('en', 'US').eras['abbreviated'][0]
828 u'BC'
829 """
830 return self._data['eras']
832 @property
833 def time_zones(self) -> localedata.LocaleDataDict:
834 """Locale display names for time zones.
836 .. note:: The format of the value returned may change between
837 Babel versions.
839 >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight']
840 u'British Summer Time'
841 >>> Locale('en', 'US').time_zones['America/St_Johns']['city']
842 u'St. John\u2019s'
843 """
844 return self._data['time_zones']
846 @property
847 def meta_zones(self) -> localedata.LocaleDataDict:
848 """Locale display names for meta time zones.
850 Meta time zones are basically groups of different Olson time zones that
851 have the same GMT offset and daylight savings time.
853 .. note:: The format of the value returned may change between
854 Babel versions.
856 >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
857 u'Central European Summer Time'
859 .. versionadded:: 0.9
860 """
861 return self._data['meta_zones']
863 @property
864 def zone_formats(self) -> localedata.LocaleDataDict:
865 """Patterns related to the formatting of time zones.
867 .. note:: The format of the value returned may change between
868 Babel versions.
870 >>> Locale('en', 'US').zone_formats['fallback']
871 u'%(1)s (%(0)s)'
872 >>> Locale('pt', 'BR').zone_formats['region']
873 u'Hor\\xe1rio %s'
875 .. versionadded:: 0.9
876 """
877 return self._data['zone_formats']
879 @property
880 def first_week_day(self) -> int:
881 """The first day of a week, with 0 being Monday.
883 >>> Locale('de', 'DE').first_week_day
884 0
885 >>> Locale('en', 'US').first_week_day
886 6
887 """
888 return self._data['week_data']['first_day']
890 @property
891 def weekend_start(self) -> int:
892 """The day the weekend starts, with 0 being Monday.
894 >>> Locale('de', 'DE').weekend_start
895 5
896 """
897 return self._data['week_data']['weekend_start']
899 @property
900 def weekend_end(self) -> int:
901 """The day the weekend ends, with 0 being Monday.
903 >>> Locale('de', 'DE').weekend_end
904 6
905 """
906 return self._data['week_data']['weekend_end']
908 @property
909 def min_week_days(self) -> int:
910 """The minimum number of days in a week so that the week is counted as
911 the first week of a year or month.
913 >>> Locale('de', 'DE').min_week_days
914 4
915 """
916 return self._data['week_data']['min_days']
918 @property
919 def date_formats(self) -> localedata.LocaleDataDict:
920 """Locale patterns for date formatting.
922 .. note:: The format of the value returned may change between
923 Babel versions.
925 >>> Locale('en', 'US').date_formats['short']
926 <DateTimePattern u'M/d/yy'>
927 >>> Locale('fr', 'FR').date_formats['long']
928 <DateTimePattern u'd MMMM y'>
929 """
930 return self._data['date_formats']
932 @property
933 def time_formats(self) -> localedata.LocaleDataDict:
934 """Locale patterns for time formatting.
936 .. note:: The format of the value returned may change between
937 Babel versions.
939 >>> Locale('en', 'US').time_formats['short']
940 <DateTimePattern u'h:mm\u202fa'>
941 >>> Locale('fr', 'FR').time_formats['long']
942 <DateTimePattern u'HH:mm:ss z'>
943 """
944 return self._data['time_formats']
946 @property
947 def datetime_formats(self) -> localedata.LocaleDataDict:
948 """Locale patterns for datetime formatting.
950 .. note:: The format of the value returned may change between
951 Babel versions.
953 >>> Locale('en').datetime_formats['full']
954 u'{1}, {0}'
955 >>> Locale('th').datetime_formats['medium']
956 u'{1} {0}'
957 """
958 return self._data['datetime_formats']
960 @property
961 def datetime_skeletons(self) -> localedata.LocaleDataDict:
962 """Locale patterns for formatting parts of a datetime.
964 >>> Locale('en').datetime_skeletons['MEd']
965 <DateTimePattern u'E, M/d'>
966 >>> Locale('fr').datetime_skeletons['MEd']
967 <DateTimePattern u'E dd/MM'>
968 >>> Locale('fr').datetime_skeletons['H']
969 <DateTimePattern u"HH 'h'">
970 """
971 return self._data['datetime_skeletons']
973 @property
974 def interval_formats(self) -> localedata.LocaleDataDict:
975 """Locale patterns for interval formatting.
977 .. note:: The format of the value returned may change between
978 Babel versions.
980 How to format date intervals in Finnish when the day is the
981 smallest changing component:
983 >>> Locale('fi_FI').interval_formats['MEd']['d']
984 [u'E d.\u2009\u2013\u2009', u'E d.M.']
986 .. seealso::
988 The primary API to use this data is :py:func:`babel.dates.format_interval`.
991 :rtype: dict[str, dict[str, list[str]]]
992 """
993 return self._data['interval_formats']
995 @property
996 def plural_form(self) -> PluralRule:
997 """Plural rules for the locale.
999 >>> Locale('en').plural_form(1)
1000 'one'
1001 >>> Locale('en').plural_form(0)
1002 'other'
1003 >>> Locale('fr').plural_form(0)
1004 'one'
1005 >>> Locale('ru').plural_form(100)
1006 'many'
1007 """
1008 return self._data.get('plural_form', _default_plural_rule)
1010 @property
1011 def list_patterns(self) -> localedata.LocaleDataDict:
1012 """Patterns for generating lists
1014 .. note:: The format of the value returned may change between
1015 Babel versions.
1017 >>> Locale('en').list_patterns['standard']['start']
1018 u'{0}, {1}'
1019 >>> Locale('en').list_patterns['standard']['end']
1020 u'{0}, and {1}'
1021 >>> Locale('en_GB').list_patterns['standard']['end']
1022 u'{0} and {1}'
1023 """
1024 return self._data['list_patterns']
1026 @property
1027 def ordinal_form(self) -> PluralRule:
1028 """Plural rules for the locale.
1030 >>> Locale('en').ordinal_form(1)
1031 'one'
1032 >>> Locale('en').ordinal_form(2)
1033 'two'
1034 >>> Locale('en').ordinal_form(3)
1035 'few'
1036 >>> Locale('fr').ordinal_form(2)
1037 'other'
1038 >>> Locale('ru').ordinal_form(100)
1039 'other'
1040 """
1041 return self._data.get('ordinal_form', _default_plural_rule)
1043 @property
1044 def measurement_systems(self) -> localedata.LocaleDataDict:
1045 """Localized names for various measurement systems.
1047 >>> Locale('fr', 'FR').measurement_systems['US']
1048 u'am\\xe9ricain'
1049 >>> Locale('en', 'US').measurement_systems['US']
1050 u'US'
1052 """
1053 return self._data['measurement_systems']
1055 @property
1056 def character_order(self) -> str:
1057 """The text direction for the language.
1059 >>> Locale('de', 'DE').character_order
1060 'left-to-right'
1061 >>> Locale('ar', 'SA').character_order
1062 'right-to-left'
1063 """
1064 return self._data['character_order']
1066 @property
1067 def text_direction(self) -> str:
1068 """The text direction for the language in CSS short-hand form.
1070 >>> Locale('de', 'DE').text_direction
1071 'ltr'
1072 >>> Locale('ar', 'SA').text_direction
1073 'rtl'
1074 """
1075 return ''.join(word[0] for word in self.character_order.split('-'))
1077 @property
1078 def unit_display_names(self) -> localedata.LocaleDataDict:
1079 """Display names for units of measurement.
1081 .. seealso::
1083 You may want to use :py:func:`babel.units.get_unit_name` instead.
1085 .. note:: The format of the value returned may change between
1086 Babel versions.
1088 """
1089 return self._data['unit_display_names']
1092def default_locale(
1093 category: str | tuple[str, ...] | list[str] | None = None,
1094 aliases: Mapping[str, str] = LOCALE_ALIASES,
1095) -> str | None:
1096 """Returns the system default locale for a given category, based on
1097 environment variables.
1099 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']:
1100 ... os.environ[name] = ''
1101 >>> os.environ['LANG'] = 'fr_FR.UTF-8'
1102 >>> default_locale('LC_MESSAGES')
1103 'fr_FR'
1105 The "C" or "POSIX" pseudo-locales are treated as aliases for the
1106 "en_US_POSIX" locale:
1108 >>> os.environ['LC_MESSAGES'] = 'POSIX'
1109 >>> default_locale('LC_MESSAGES')
1110 'en_US_POSIX'
1112 The following fallbacks to the variable are always considered:
1114 - ``LANGUAGE``
1115 - ``LC_ALL``
1116 - ``LC_CTYPE``
1117 - ``LANG``
1119 :param category: one or more of the ``LC_XXX`` environment variable names
1120 :param aliases: a dictionary of aliases for locale identifiers
1121 """
1123 varnames = ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
1124 if category:
1125 if isinstance(category, str):
1126 varnames = (category, *varnames)
1127 elif isinstance(category, (list, tuple)):
1128 varnames = (*category, *varnames)
1129 else:
1130 raise TypeError(f"Invalid type for category: {category!r}")
1132 for name in varnames:
1133 if not name:
1134 continue
1135 locale = os.getenv(name)
1136 if locale:
1137 if name == 'LANGUAGE' and ':' in locale:
1138 # the LANGUAGE variable may contain a colon-separated list of
1139 # language codes; we just pick the language on the list
1140 locale = locale.split(':')[0]
1141 if locale.split('.')[0] in ('C', 'POSIX'):
1142 locale = 'en_US_POSIX'
1143 elif aliases and locale in aliases:
1144 locale = aliases[locale]
1145 try:
1146 return get_locale_identifier(parse_locale(locale))
1147 except ValueError:
1148 pass
1149 return None
1152def negotiate_locale(preferred: Iterable[str], available: Iterable[str], sep: str = '_', aliases: Mapping[str, str] = LOCALE_ALIASES) -> str | None:
1153 """Find the best match between available and requested locale strings.
1155 >>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
1156 'de_DE'
1157 >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de'])
1158 'de'
1160 Case is ignored by the algorithm, the result uses the case of the preferred
1161 locale identifier:
1163 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1164 'de_DE'
1166 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1167 'de_DE'
1169 By default, some web browsers unfortunately do not include the territory
1170 in the locale identifier for many locales, and some don't even allow the
1171 user to easily add the territory. So while you may prefer using qualified
1172 locale identifiers in your web-application, they would not normally match
1173 the language-only locale sent by such browsers. To workaround that, this
1174 function uses a default mapping of commonly used language-only locale
1175 identifiers to identifiers including the territory:
1177 >>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US'])
1178 'ja_JP'
1180 Some browsers even use an incorrect or outdated language code, such as "no"
1181 for Norwegian, where the correct locale identifier would actually be "nb_NO"
1182 (Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of
1183 such cases, too:
1185 >>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE'])
1186 'nb_NO'
1188 You can override this default mapping by passing a different `aliases`
1189 dictionary to this function, or you can bypass the behavior althogher by
1190 setting the `aliases` parameter to `None`.
1192 :param preferred: the list of locale strings preferred by the user
1193 :param available: the list of locale strings available
1194 :param sep: character that separates the different parts of the locale
1195 strings
1196 :param aliases: a dictionary of aliases for locale identifiers
1197 """
1198 available = [a.lower() for a in available if a]
1199 for locale in preferred:
1200 ll = locale.lower()
1201 if ll in available:
1202 return locale
1203 if aliases:
1204 alias = aliases.get(ll)
1205 if alias:
1206 alias = alias.replace('_', sep)
1207 if alias.lower() in available:
1208 return alias
1209 parts = locale.split(sep)
1210 if len(parts) > 1 and parts[0].lower() in available:
1211 return parts[0]
1212 return None
1215def parse_locale(
1216 identifier: str,
1217 sep: str = '_',
1218) -> tuple[str, str | None, str | None, str | None] | tuple[str, str | None, str | None, str | None, str | None]:
1219 """Parse a locale identifier into a tuple of the form ``(language,
1220 territory, script, variant, modifier)``.
1222 >>> parse_locale('zh_CN')
1223 ('zh', 'CN', None, None)
1224 >>> parse_locale('zh_Hans_CN')
1225 ('zh', 'CN', 'Hans', None)
1226 >>> parse_locale('ca_es_valencia')
1227 ('ca', 'ES', None, 'VALENCIA')
1228 >>> parse_locale('en_150')
1229 ('en', '150', None, None)
1230 >>> parse_locale('en_us_posix')
1231 ('en', 'US', None, 'POSIX')
1232 >>> parse_locale('it_IT@euro')
1233 ('it', 'IT', None, None, 'euro')
1234 >>> parse_locale('it_IT@custom')
1235 ('it', 'IT', None, None, 'custom')
1236 >>> parse_locale('it_IT@')
1237 ('it', 'IT', None, None)
1239 The default component separator is "_", but a different separator can be
1240 specified using the `sep` parameter.
1242 The optional modifier is always separated with "@" and at the end:
1244 >>> parse_locale('zh-CN', sep='-')
1245 ('zh', 'CN', None, None)
1246 >>> parse_locale('zh-CN@custom', sep='-')
1247 ('zh', 'CN', None, None, 'custom')
1249 If the identifier cannot be parsed into a locale, a `ValueError` exception
1250 is raised:
1252 >>> parse_locale('not_a_LOCALE_String')
1253 Traceback (most recent call last):
1254 ...
1255 ValueError: 'not_a_LOCALE_String' is not a valid locale identifier
1257 Encoding information is removed from the identifier, while modifiers are
1258 kept:
1260 >>> parse_locale('en_US.UTF-8')
1261 ('en', 'US', None, None)
1262 >>> parse_locale('de_DE.iso885915@euro')
1263 ('de', 'DE', None, None, 'euro')
1265 See :rfc:`4646` for more information.
1267 :param identifier: the locale identifier string
1268 :param sep: character that separates the different components of the locale
1269 identifier
1270 :raise `ValueError`: if the string does not appear to be a valid locale
1271 identifier
1272 """
1273 if not identifier:
1274 raise ValueError("empty locale identifier")
1275 identifier, _, modifier = identifier.partition('@')
1276 if '.' in identifier:
1277 # this is probably the charset/encoding, which we don't care about
1278 identifier = identifier.split('.', 1)[0]
1280 parts = identifier.split(sep)
1281 lang = parts.pop(0).lower()
1282 if not lang.isalpha():
1283 raise ValueError(f"expected only letters, got {lang!r}")
1285 script = territory = variant = None
1286 if parts and len(parts[0]) == 4 and parts[0].isalpha():
1287 script = parts.pop(0).title()
1289 if parts:
1290 if len(parts[0]) == 2 and parts[0].isalpha():
1291 territory = parts.pop(0).upper()
1292 elif len(parts[0]) == 3 and parts[0].isdigit():
1293 territory = parts.pop(0)
1295 if parts and (
1296 len(parts[0]) == 4 and parts[0][0].isdigit() or
1297 len(parts[0]) >= 5 and parts[0][0].isalpha()
1298 ):
1299 variant = parts.pop().upper()
1301 if parts:
1302 raise ValueError(f"{identifier!r} is not a valid locale identifier")
1304 # TODO(3.0): always return a 5-tuple
1305 if modifier:
1306 return lang, territory, script, variant, modifier
1307 else:
1308 return lang, territory, script, variant
1311def get_locale_identifier(
1312 tup: tuple[str]
1313 | tuple[str, str | None]
1314 | tuple[str, str | None, str | None]
1315 | tuple[str, str | None, str | None, str | None]
1316 | tuple[str, str | None, str | None, str | None, str | None],
1317 sep: str = "_",
1318) -> str:
1319 """The reverse of :func:`parse_locale`. It creates a locale identifier out
1320 of a ``(language, territory, script, variant, modifier)`` tuple. Items can be set to
1321 ``None`` and trailing ``None``\\s can also be left out of the tuple.
1323 >>> get_locale_identifier(('de', 'DE', None, '1999', 'custom'))
1324 'de_DE_1999@custom'
1325 >>> get_locale_identifier(('fi', None, None, None, 'custom'))
1326 'fi@custom'
1329 .. versionadded:: 1.0
1331 :param tup: the tuple as returned by :func:`parse_locale`.
1332 :param sep: the separator for the identifier.
1333 """
1334 tup = tuple(tup[:5]) # type: ignore # length should be no more than 5
1335 lang, territory, script, variant, modifier = tup + (None,) * (5 - len(tup))
1336 ret = sep.join(filter(None, (lang, script, territory, variant)))
1337 return f'{ret}@{modifier}' if modifier else ret