Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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-2024 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
18from babel import localedata
19from babel.plural import PluralRule
21__all__ = ['UnknownLocaleError', 'Locale', 'default_locale', 'negotiate_locale',
22 'parse_locale']
24if TYPE_CHECKING:
25 from typing_extensions import Literal, TypeAlias
27 _GLOBAL_KEY: TypeAlias = Literal[
28 "all_currencies",
29 "currency_fractions",
30 "language_aliases",
31 "likely_subtags",
32 "meta_zones",
33 "parent_exceptions",
34 "script_aliases",
35 "territory_aliases",
36 "territory_currencies",
37 "territory_languages",
38 "territory_zones",
39 "variant_aliases",
40 "windows_zone_mapping",
41 "zone_aliases",
42 "zone_territories",
43 ]
45 _global_data: Mapping[_GLOBAL_KEY, Mapping[str, Any]] | None
47_global_data = None
48_default_plural_rule = PluralRule({})
51def _raise_no_data_error():
52 raise RuntimeError('The babel data files are not available. '
53 'This usually happens because you are using '
54 'a source checkout from Babel and you did '
55 'not build the data files. Just make sure '
56 'to run "python setup.py import_cldr" before '
57 'installing the library.')
60def get_global(key: _GLOBAL_KEY) -> Mapping[str, Any]:
61 """Return the dictionary for the given key in the global data.
63 The global data is stored in the ``babel/global.dat`` file and contains
64 information independent of individual locales.
66 >>> get_global('zone_aliases')['UTC']
67 u'Etc/UTC'
68 >>> get_global('zone_territories')['Europe/Berlin']
69 u'DE'
71 The keys available are:
73 - ``all_currencies``
74 - ``currency_fractions``
75 - ``language_aliases``
76 - ``likely_subtags``
77 - ``parent_exceptions``
78 - ``script_aliases``
79 - ``territory_aliases``
80 - ``territory_currencies``
81 - ``territory_languages``
82 - ``territory_zones``
83 - ``variant_aliases``
84 - ``windows_zone_mapping``
85 - ``zone_aliases``
86 - ``zone_territories``
88 .. note:: The internal structure of the data may change between versions.
90 .. versionadded:: 0.9
92 :param key: the data key
93 """
94 global _global_data
95 if _global_data is None:
96 dirname = os.path.join(os.path.dirname(__file__))
97 filename = os.path.join(dirname, 'global.dat')
98 if not os.path.isfile(filename):
99 _raise_no_data_error()
100 with open(filename, 'rb') as fileobj:
101 _global_data = pickle.load(fileobj)
102 assert _global_data is not None
103 return _global_data.get(key, {})
106LOCALE_ALIASES = {
107 'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ',
108 'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES',
109 'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES',
110 'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT',
111 'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV',
112 'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL',
113 'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI',
114 'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA',
115}
118class UnknownLocaleError(Exception):
119 """Exception thrown when a locale is requested for which no locale data
120 is available.
121 """
123 def __init__(self, identifier: str) -> None:
124 """Create the exception.
126 :param identifier: the identifier string of the unsupported locale
127 """
128 Exception.__init__(self, f"unknown locale {identifier!r}")
130 #: The identifier of the locale that could not be found.
131 self.identifier = identifier
134class Locale:
135 """Representation of a specific locale.
137 >>> locale = Locale('en', 'US')
138 >>> repr(locale)
139 "Locale('en', territory='US')"
140 >>> locale.display_name
141 u'English (United States)'
143 A `Locale` object can also be instantiated from a raw locale string:
145 >>> locale = Locale.parse('en-US', sep='-')
146 >>> repr(locale)
147 "Locale('en', territory='US')"
149 `Locale` objects provide access to a collection of locale data, such as
150 territory and language names, number and date format patterns, and more:
152 >>> locale.number_symbols['latn']['decimal']
153 u'.'
155 If a locale is requested for which no locale data is available, an
156 `UnknownLocaleError` is raised:
158 >>> Locale.parse('en_XX')
159 Traceback (most recent call last):
160 ...
161 UnknownLocaleError: unknown locale 'en_XX'
163 For more information see :rfc:`3066`.
164 """
166 def __init__(
167 self,
168 language: str,
169 territory: str | None = None,
170 script: str | None = None,
171 variant: str | None = None,
172 modifier: str | None = None,
173 ) -> None:
174 """Initialize the locale object from the given identifier components.
176 >>> locale = Locale('en', 'US')
177 >>> locale.language
178 'en'
179 >>> locale.territory
180 'US'
182 :param language: the language code
183 :param territory: the territory (country or region) code
184 :param script: the script code
185 :param variant: the variant code
186 :param modifier: a modifier (following the '@' symbol, sometimes called '@variant')
187 :raise `UnknownLocaleError`: if no locale data is available for the
188 requested locale
189 """
190 #: the language code
191 self.language = language
192 #: the territory (country or region) code
193 self.territory = territory
194 #: the script code
195 self.script = script
196 #: the variant code
197 self.variant = variant
198 #: the modifier
199 self.modifier = modifier
200 self.__data: localedata.LocaleDataDict | None = None
202 identifier = str(self)
203 identifier_without_modifier = identifier.partition('@')[0]
204 if not localedata.exists(identifier_without_modifier):
205 raise UnknownLocaleError(identifier)
207 @classmethod
208 def default(cls, category: str | None = None, aliases: Mapping[str, str] = LOCALE_ALIASES) -> Locale:
209 """Return the system default locale for the specified category.
211 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
212 ... os.environ[name] = ''
213 >>> os.environ['LANG'] = 'fr_FR.UTF-8'
214 >>> Locale.default('LC_MESSAGES')
215 Locale('fr', territory='FR')
217 The following fallbacks to the variable are always considered:
219 - ``LANGUAGE``
220 - ``LC_ALL``
221 - ``LC_CTYPE``
222 - ``LANG``
224 :param category: one of the ``LC_XXX`` environment variable names
225 :param aliases: a dictionary of aliases for locale identifiers
226 """
227 # XXX: use likely subtag expansion here instead of the
228 # aliases dictionary.
229 locale_string = default_locale(category, aliases=aliases)
230 return cls.parse(locale_string)
232 @classmethod
233 def negotiate(
234 cls,
235 preferred: Iterable[str],
236 available: Iterable[str],
237 sep: str = '_',
238 aliases: Mapping[str, str] = LOCALE_ALIASES,
239 ) -> Locale | None:
240 """Find the best match between available and requested locale strings.
242 >>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
243 Locale('de', territory='DE')
244 >>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de'])
245 Locale('de')
246 >>> Locale.negotiate(['de_DE', 'de'], ['en_US'])
248 You can specify the character used in the locale identifiers to separate
249 the different components. This separator is applied to both lists. Also,
250 case is ignored in the comparison:
252 >>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-')
253 Locale('de', territory='DE')
255 :param preferred: the list of locale identifiers preferred by the user
256 :param available: the list of locale identifiers available
257 :param aliases: a dictionary of aliases for locale identifiers
258 """
259 identifier = negotiate_locale(preferred, available, sep=sep,
260 aliases=aliases)
261 if identifier:
262 return Locale.parse(identifier, sep=sep)
263 return None
265 @classmethod
266 def parse(
267 cls,
268 identifier: str | Locale | None,
269 sep: str = '_',
270 resolve_likely_subtags: bool = True,
271 ) -> Locale:
272 """Create a `Locale` instance for the given locale identifier.
274 >>> l = Locale.parse('de-DE', sep='-')
275 >>> l.display_name
276 u'Deutsch (Deutschland)'
278 If the `identifier` parameter is not a string, but actually a `Locale`
279 object, that object is returned:
281 >>> Locale.parse(l)
282 Locale('de', territory='DE')
284 If the `identifier` parameter is neither of these, such as `None`
285 e.g. because a default locale identifier could not be determined,
286 a `TypeError` is raised:
288 >>> Locale.parse(None)
289 Traceback (most recent call last):
290 ...
291 TypeError: ...
293 This also can perform resolving of likely subtags which it does
294 by default. This is for instance useful to figure out the most
295 likely locale for a territory you can use ``'und'`` as the
296 language tag:
298 >>> Locale.parse('und_AT')
299 Locale('de', territory='AT')
301 Modifiers are optional, and always at the end, separated by "@":
303 >>> Locale.parse('de_AT@euro')
304 Locale('de', territory='AT', modifier='euro')
306 :param identifier: the locale identifier string
307 :param sep: optional component separator
308 :param resolve_likely_subtags: if this is specified then a locale will
309 have its likely subtag resolved if the
310 locale otherwise does not exist. For
311 instance ``zh_TW`` by itself is not a
312 locale that exists but Babel can
313 automatically expand it to the full
314 form of ``zh_hant_TW``. Note that this
315 expansion is only taking place if no
316 locale exists otherwise. For instance
317 there is a locale ``en`` that can exist
318 by itself.
319 :raise `ValueError`: if the string does not appear to be a valid locale
320 identifier
321 :raise `UnknownLocaleError`: if no locale data is available for the
322 requested locale
323 :raise `TypeError`: if the identifier is not a string or a `Locale`
324 """
325 if isinstance(identifier, Locale):
326 return identifier
327 elif not isinstance(identifier, str):
328 raise TypeError(f"Unexpected value for identifier: {identifier!r}")
330 parts = parse_locale(identifier, sep=sep)
331 input_id = get_locale_identifier(parts)
333 def _try_load(parts):
334 try:
335 return cls(*parts)
336 except UnknownLocaleError:
337 return None
339 def _try_load_reducing(parts):
340 # Success on first hit, return it.
341 locale = _try_load(parts)
342 if locale is not None:
343 return locale
345 # Now try without script and variant
346 locale = _try_load(parts[:2])
347 if locale is not None:
348 return locale
350 locale = _try_load(parts)
351 if locale is not None:
352 return locale
353 if not resolve_likely_subtags:
354 raise UnknownLocaleError(input_id)
356 # From here onwards is some very bad likely subtag resolving. This
357 # whole logic is not entirely correct but good enough (tm) for the
358 # time being. This has been added so that zh_TW does not cause
359 # errors for people when they upgrade. Later we should properly
360 # implement ICU like fuzzy locale objects and provide a way to
361 # maximize and minimize locale tags.
363 if len(parts) == 5:
364 language, territory, script, variant, modifier = parts
365 else:
366 language, territory, script, variant = parts
367 modifier = None
368 language = get_global('language_aliases').get(language, language)
369 territory = get_global('territory_aliases').get(territory or '', (territory,))[0]
370 script = get_global('script_aliases').get(script or '', script)
371 variant = get_global('variant_aliases').get(variant or '', variant)
373 if territory == 'ZZ':
374 territory = None
375 if script == 'Zzzz':
376 script = None
378 parts = language, territory, script, variant, modifier
380 # First match: try the whole identifier
381 new_id = get_locale_identifier(parts)
382 likely_subtag = get_global('likely_subtags').get(new_id)
383 if likely_subtag is not None:
384 locale = _try_load_reducing(parse_locale(likely_subtag))
385 if locale is not None:
386 return locale
388 # If we did not find anything so far, try again with a
389 # simplified identifier that is just the language
390 likely_subtag = get_global('likely_subtags').get(language)
391 if likely_subtag is not None:
392 parts2 = parse_locale(likely_subtag)
393 if len(parts2) == 5:
394 language2, _, script2, variant2, modifier2 = parts2
395 else:
396 language2, _, script2, variant2 = parts2
397 modifier2 = None
398 locale = _try_load_reducing((language2, territory, script2, variant2, modifier2))
399 if locale is not None:
400 return locale
402 raise UnknownLocaleError(input_id)
404 def __eq__(self, other: object) -> bool:
405 for key in ('language', 'territory', 'script', 'variant', 'modifier'):
406 if not hasattr(other, key):
407 return False
408 return (
409 self.language == getattr(other, 'language') and # noqa: B009
410 self.territory == getattr(other, 'territory') and # noqa: B009
411 self.script == getattr(other, 'script') and # noqa: B009
412 self.variant == getattr(other, 'variant') and # noqa: B009
413 self.modifier == getattr(other, 'modifier') # noqa: B009
414 )
416 def __ne__(self, other: object) -> bool:
417 return not self.__eq__(other)
419 def __hash__(self) -> int:
420 return hash((self.language, self.territory, self.script,
421 self.variant, self.modifier))
423 def __repr__(self) -> str:
424 parameters = ['']
425 for key in ('territory', 'script', 'variant', 'modifier'):
426 value = getattr(self, key)
427 if value is not None:
428 parameters.append(f"{key}={value!r}")
429 return f"Locale({self.language!r}{', '.join(parameters)})"
431 def __str__(self) -> str:
432 return get_locale_identifier((self.language, self.territory,
433 self.script, self.variant,
434 self.modifier))
436 @property
437 def _data(self) -> localedata.LocaleDataDict:
438 if self.__data is None:
439 self.__data = localedata.LocaleDataDict(localedata.load(str(self)))
440 return self.__data
442 def get_display_name(self, locale: Locale | str | None = None) -> str | None:
443 """Return the display name of the locale using the given locale.
445 The display name will include the language, territory, script, and
446 variant, if those are specified.
448 >>> Locale('zh', 'CN', script='Hans').get_display_name('en')
449 u'Chinese (Simplified, China)'
451 Modifiers are currently passed through verbatim:
453 >>> Locale('it', 'IT', modifier='euro').get_display_name('en')
454 u'Italian (Italy, euro)'
456 :param locale: the locale to use
457 """
458 if locale is None:
459 locale = self
460 locale = Locale.parse(locale)
461 retval = locale.languages.get(self.language)
462 if retval and (self.territory or self.script or self.variant):
463 details = []
464 if self.script:
465 details.append(locale.scripts.get(self.script))
466 if self.territory:
467 details.append(locale.territories.get(self.territory))
468 if self.variant:
469 details.append(locale.variants.get(self.variant))
470 if self.modifier:
471 details.append(self.modifier)
472 detail_string = ', '.join(atom for atom in details if atom)
473 if detail_string:
474 retval += f" ({detail_string})"
475 return retval
477 display_name = property(get_display_name, doc="""\
478 The localized display name of the locale.
480 >>> Locale('en').display_name
481 u'English'
482 >>> Locale('en', 'US').display_name
483 u'English (United States)'
484 >>> Locale('sv').display_name
485 u'svenska'
487 :type: `unicode`
488 """)
490 def get_language_name(self, locale: Locale | str | None = None) -> str | None:
491 """Return the language of this locale in the given locale.
493 >>> Locale('zh', 'CN', script='Hans').get_language_name('de')
494 u'Chinesisch'
496 .. versionadded:: 1.0
498 :param locale: the locale to use
499 """
500 if locale is None:
501 locale = self
502 locale = Locale.parse(locale)
503 return locale.languages.get(self.language)
505 language_name = property(get_language_name, doc="""\
506 The localized language name of the locale.
508 >>> Locale('en', 'US').language_name
509 u'English'
510 """)
512 def get_territory_name(self, locale: Locale | str | None = None) -> str | None:
513 """Return the territory name in the given locale."""
514 if locale is None:
515 locale = self
516 locale = Locale.parse(locale)
517 return locale.territories.get(self.territory or '')
519 territory_name = property(get_territory_name, doc="""\
520 The localized territory name of the locale if available.
522 >>> Locale('de', 'DE').territory_name
523 u'Deutschland'
524 """)
526 def get_script_name(self, locale: Locale | str | None = None) -> str | None:
527 """Return the script name in the given locale."""
528 if locale is None:
529 locale = self
530 locale = Locale.parse(locale)
531 return locale.scripts.get(self.script or '')
533 script_name = property(get_script_name, doc="""\
534 The localized script name of the locale if available.
536 >>> Locale('sr', 'ME', script='Latn').script_name
537 u'latinica'
538 """)
540 @property
541 def english_name(self) -> str | None:
542 """The english display name of the locale.
544 >>> Locale('de').english_name
545 u'German'
546 >>> Locale('de', 'DE').english_name
547 u'German (Germany)'
549 :type: `unicode`"""
550 return self.get_display_name(Locale('en'))
552 # { General Locale Display Names
554 @property
555 def languages(self) -> localedata.LocaleDataDict:
556 """Mapping of language codes to translated language names.
558 >>> Locale('de', 'DE').languages['ja']
559 u'Japanisch'
561 See `ISO 639 <http://www.loc.gov/standards/iso639-2/>`_ for
562 more information.
563 """
564 return self._data['languages']
566 @property
567 def scripts(self) -> localedata.LocaleDataDict:
568 """Mapping of script codes to translated script names.
570 >>> Locale('en', 'US').scripts['Hira']
571 u'Hiragana'
573 See `ISO 15924 <http://www.evertype.com/standards/iso15924/>`_
574 for more information.
575 """
576 return self._data['scripts']
578 @property
579 def territories(self) -> localedata.LocaleDataDict:
580 """Mapping of script codes to translated script names.
582 >>> Locale('es', 'CO').territories['DE']
583 u'Alemania'
585 See `ISO 3166 <http://www.iso.org/iso/en/prods-services/iso3166ma/>`_
586 for more information.
587 """
588 return self._data['territories']
590 @property
591 def variants(self) -> localedata.LocaleDataDict:
592 """Mapping of script codes to translated script names.
594 >>> Locale('de', 'DE').variants['1901']
595 u'Alte deutsche Rechtschreibung'
596 """
597 return self._data['variants']
599 # { Number Formatting
601 @property
602 def currencies(self) -> localedata.LocaleDataDict:
603 """Mapping of currency codes to translated currency names. This
604 only returns the generic form of the currency name, not the count
605 specific one. If an actual number is requested use the
606 :func:`babel.numbers.get_currency_name` function.
608 >>> Locale('en').currencies['COP']
609 u'Colombian Peso'
610 >>> Locale('de', 'DE').currencies['COP']
611 u'Kolumbianischer Peso'
612 """
613 return self._data['currency_names']
615 @property
616 def currency_symbols(self) -> localedata.LocaleDataDict:
617 """Mapping of currency codes to symbols.
619 >>> Locale('en', 'US').currency_symbols['USD']
620 u'$'
621 >>> Locale('es', 'CO').currency_symbols['USD']
622 u'US$'
623 """
624 return self._data['currency_symbols']
626 @property
627 def number_symbols(self) -> localedata.LocaleDataDict:
628 """Symbols used in number formatting by number system.
630 .. note:: The format of the value returned may change between
631 Babel versions.
633 >>> Locale('fr', 'FR').number_symbols["latn"]['decimal']
634 u','
635 >>> Locale('fa', 'IR').number_symbols["arabext"]['decimal']
636 u'٫'
637 >>> Locale('fa', 'IR').number_symbols["latn"]['decimal']
638 u'.'
639 """
640 return self._data['number_symbols']
642 @property
643 def other_numbering_systems(self) -> localedata.LocaleDataDict:
644 """
645 Mapping of other numbering systems available for the locale.
646 See: https://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems
648 >>> Locale('el', 'GR').other_numbering_systems['traditional']
649 u'grek'
651 .. note:: The format of the value returned may change between
652 Babel versions.
653 """
654 return self._data['numbering_systems']
656 @property
657 def default_numbering_system(self) -> str:
658 """The default numbering system used by the locale.
659 >>> Locale('el', 'GR').default_numbering_system
660 u'latn'
661 """
662 return self._data['default_numbering_system']
664 @property
665 def decimal_formats(self) -> localedata.LocaleDataDict:
666 """Locale patterns for decimal number formatting.
668 .. note:: The format of the value returned may change between
669 Babel versions.
671 >>> Locale('en', 'US').decimal_formats[None]
672 <NumberPattern u'#,##0.###'>
673 """
674 return self._data['decimal_formats']
676 @property
677 def compact_decimal_formats(self) -> localedata.LocaleDataDict:
678 """Locale patterns for compact decimal number formatting.
680 .. note:: The format of the value returned may change between
681 Babel versions.
683 >>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"]
684 <NumberPattern u'0K'>
685 """
686 return self._data['compact_decimal_formats']
688 @property
689 def currency_formats(self) -> localedata.LocaleDataDict:
690 """Locale patterns for currency number formatting.
692 .. note:: The format of the value returned may change between
693 Babel versions.
695 >>> Locale('en', 'US').currency_formats['standard']
696 <NumberPattern u'\\xa4#,##0.00'>
697 >>> Locale('en', 'US').currency_formats['accounting']
698 <NumberPattern u'\\xa4#,##0.00;(\\xa4#,##0.00)'>
699 """
700 return self._data['currency_formats']
702 @property
703 def compact_currency_formats(self) -> localedata.LocaleDataDict:
704 """Locale patterns for compact currency number formatting.
706 .. note:: The format of the value returned may change between
707 Babel versions.
709 >>> Locale('en', 'US').compact_currency_formats["short"]["one"]["1000"]
710 <NumberPattern u'¤0K'>
711 """
712 return self._data['compact_currency_formats']
714 @property
715 def percent_formats(self) -> localedata.LocaleDataDict:
716 """Locale patterns for percent number formatting.
718 .. note:: The format of the value returned may change between
719 Babel versions.
721 >>> Locale('en', 'US').percent_formats[None]
722 <NumberPattern u'#,##0%'>
723 """
724 return self._data['percent_formats']
726 @property
727 def scientific_formats(self) -> localedata.LocaleDataDict:
728 """Locale patterns for scientific number formatting.
730 .. note:: The format of the value returned may change between
731 Babel versions.
733 >>> Locale('en', 'US').scientific_formats[None]
734 <NumberPattern u'#E0'>
735 """
736 return self._data['scientific_formats']
738 # { Calendar Information and Date Formatting
740 @property
741 def periods(self) -> localedata.LocaleDataDict:
742 """Locale display names for day periods (AM/PM).
744 >>> Locale('en', 'US').periods['am']
745 u'AM'
746 """
747 try:
748 return self._data['day_periods']['stand-alone']['wide']
749 except KeyError:
750 return localedata.LocaleDataDict({}) # pragma: no cover
752 @property
753 def day_periods(self) -> localedata.LocaleDataDict:
754 """Locale display names for various day periods (not necessarily only AM/PM).
756 These are not meant to be used without the relevant `day_period_rules`.
757 """
758 return self._data['day_periods']
760 @property
761 def day_period_rules(self) -> localedata.LocaleDataDict:
762 """Day period rules for the locale. Used by `get_period_id`.
763 """
764 return self._data.get('day_period_rules', localedata.LocaleDataDict({}))
766 @property
767 def days(self) -> localedata.LocaleDataDict:
768 """Locale display names for weekdays.
770 >>> Locale('de', 'DE').days['format']['wide'][3]
771 u'Donnerstag'
772 """
773 return self._data['days']
775 @property
776 def months(self) -> localedata.LocaleDataDict:
777 """Locale display names for months.
779 >>> Locale('de', 'DE').months['format']['wide'][10]
780 u'Oktober'
781 """
782 return self._data['months']
784 @property
785 def quarters(self) -> localedata.LocaleDataDict:
786 """Locale display names for quarters.
788 >>> Locale('de', 'DE').quarters['format']['wide'][1]
789 u'1. Quartal'
790 """
791 return self._data['quarters']
793 @property
794 def eras(self) -> localedata.LocaleDataDict:
795 """Locale display names for eras.
797 .. note:: The format of the value returned may change between
798 Babel versions.
800 >>> Locale('en', 'US').eras['wide'][1]
801 u'Anno Domini'
802 >>> Locale('en', 'US').eras['abbreviated'][0]
803 u'BC'
804 """
805 return self._data['eras']
807 @property
808 def time_zones(self) -> localedata.LocaleDataDict:
809 """Locale display names for time zones.
811 .. note:: The format of the value returned may change between
812 Babel versions.
814 >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight']
815 u'British Summer Time'
816 >>> Locale('en', 'US').time_zones['America/St_Johns']['city']
817 u'St. John\u2019s'
818 """
819 return self._data['time_zones']
821 @property
822 def meta_zones(self) -> localedata.LocaleDataDict:
823 """Locale display names for meta time zones.
825 Meta time zones are basically groups of different Olson time zones that
826 have the same GMT offset and daylight savings time.
828 .. note:: The format of the value returned may change between
829 Babel versions.
831 >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
832 u'Central European Summer Time'
834 .. versionadded:: 0.9
835 """
836 return self._data['meta_zones']
838 @property
839 def zone_formats(self) -> localedata.LocaleDataDict:
840 """Patterns related to the formatting of time zones.
842 .. note:: The format of the value returned may change between
843 Babel versions.
845 >>> Locale('en', 'US').zone_formats['fallback']
846 u'%(1)s (%(0)s)'
847 >>> Locale('pt', 'BR').zone_formats['region']
848 u'Hor\\xe1rio %s'
850 .. versionadded:: 0.9
851 """
852 return self._data['zone_formats']
854 @property
855 def first_week_day(self) -> int:
856 """The first day of a week, with 0 being Monday.
858 >>> Locale('de', 'DE').first_week_day
859 0
860 >>> Locale('en', 'US').first_week_day
861 6
862 """
863 return self._data['week_data']['first_day']
865 @property
866 def weekend_start(self) -> int:
867 """The day the weekend starts, with 0 being Monday.
869 >>> Locale('de', 'DE').weekend_start
870 5
871 """
872 return self._data['week_data']['weekend_start']
874 @property
875 def weekend_end(self) -> int:
876 """The day the weekend ends, with 0 being Monday.
878 >>> Locale('de', 'DE').weekend_end
879 6
880 """
881 return self._data['week_data']['weekend_end']
883 @property
884 def min_week_days(self) -> int:
885 """The minimum number of days in a week so that the week is counted as
886 the first week of a year or month.
888 >>> Locale('de', 'DE').min_week_days
889 4
890 """
891 return self._data['week_data']['min_days']
893 @property
894 def date_formats(self) -> localedata.LocaleDataDict:
895 """Locale patterns for date formatting.
897 .. note:: The format of the value returned may change between
898 Babel versions.
900 >>> Locale('en', 'US').date_formats['short']
901 <DateTimePattern u'M/d/yy'>
902 >>> Locale('fr', 'FR').date_formats['long']
903 <DateTimePattern u'd MMMM y'>
904 """
905 return self._data['date_formats']
907 @property
908 def time_formats(self) -> localedata.LocaleDataDict:
909 """Locale patterns for time formatting.
911 .. note:: The format of the value returned may change between
912 Babel versions.
914 >>> Locale('en', 'US').time_formats['short']
915 <DateTimePattern u'h:mm\u202fa'>
916 >>> Locale('fr', 'FR').time_formats['long']
917 <DateTimePattern u'HH:mm:ss z'>
918 """
919 return self._data['time_formats']
921 @property
922 def datetime_formats(self) -> localedata.LocaleDataDict:
923 """Locale patterns for datetime formatting.
925 .. note:: The format of the value returned may change between
926 Babel versions.
928 >>> Locale('en').datetime_formats['full']
929 u'{1}, {0}'
930 >>> Locale('th').datetime_formats['medium']
931 u'{1} {0}'
932 """
933 return self._data['datetime_formats']
935 @property
936 def datetime_skeletons(self) -> localedata.LocaleDataDict:
937 """Locale patterns for formatting parts of a datetime.
939 >>> Locale('en').datetime_skeletons['MEd']
940 <DateTimePattern u'E, M/d'>
941 >>> Locale('fr').datetime_skeletons['MEd']
942 <DateTimePattern u'E dd/MM'>
943 >>> Locale('fr').datetime_skeletons['H']
944 <DateTimePattern u"HH 'h'">
945 """
946 return self._data['datetime_skeletons']
948 @property
949 def interval_formats(self) -> localedata.LocaleDataDict:
950 """Locale patterns for interval formatting.
952 .. note:: The format of the value returned may change between
953 Babel versions.
955 How to format date intervals in Finnish when the day is the
956 smallest changing component:
958 >>> Locale('fi_FI').interval_formats['MEd']['d']
959 [u'E d.\u2009\u2013\u2009', u'E d.M.']
961 .. seealso::
963 The primary API to use this data is :py:func:`babel.dates.format_interval`.
966 :rtype: dict[str, dict[str, list[str]]]
967 """
968 return self._data['interval_formats']
970 @property
971 def plural_form(self) -> PluralRule:
972 """Plural rules for the locale.
974 >>> Locale('en').plural_form(1)
975 'one'
976 >>> Locale('en').plural_form(0)
977 'other'
978 >>> Locale('fr').plural_form(0)
979 'one'
980 >>> Locale('ru').plural_form(100)
981 'many'
982 """
983 return self._data.get('plural_form', _default_plural_rule)
985 @property
986 def list_patterns(self) -> localedata.LocaleDataDict:
987 """Patterns for generating lists
989 .. note:: The format of the value returned may change between
990 Babel versions.
992 >>> Locale('en').list_patterns['standard']['start']
993 u'{0}, {1}'
994 >>> Locale('en').list_patterns['standard']['end']
995 u'{0}, and {1}'
996 >>> Locale('en_GB').list_patterns['standard']['end']
997 u'{0} and {1}'
998 """
999 return self._data['list_patterns']
1001 @property
1002 def ordinal_form(self) -> PluralRule:
1003 """Plural rules for the locale.
1005 >>> Locale('en').ordinal_form(1)
1006 'one'
1007 >>> Locale('en').ordinal_form(2)
1008 'two'
1009 >>> Locale('en').ordinal_form(3)
1010 'few'
1011 >>> Locale('fr').ordinal_form(2)
1012 'other'
1013 >>> Locale('ru').ordinal_form(100)
1014 'other'
1015 """
1016 return self._data.get('ordinal_form', _default_plural_rule)
1018 @property
1019 def measurement_systems(self) -> localedata.LocaleDataDict:
1020 """Localized names for various measurement systems.
1022 >>> Locale('fr', 'FR').measurement_systems['US']
1023 u'am\\xe9ricain'
1024 >>> Locale('en', 'US').measurement_systems['US']
1025 u'US'
1027 """
1028 return self._data['measurement_systems']
1030 @property
1031 def character_order(self) -> str:
1032 """The text direction for the language.
1034 >>> Locale('de', 'DE').character_order
1035 'left-to-right'
1036 >>> Locale('ar', 'SA').character_order
1037 'right-to-left'
1038 """
1039 return self._data['character_order']
1041 @property
1042 def text_direction(self) -> str:
1043 """The text direction for the language in CSS short-hand form.
1045 >>> Locale('de', 'DE').text_direction
1046 'ltr'
1047 >>> Locale('ar', 'SA').text_direction
1048 'rtl'
1049 """
1050 return ''.join(word[0] for word in self.character_order.split('-'))
1052 @property
1053 def unit_display_names(self) -> localedata.LocaleDataDict:
1054 """Display names for units of measurement.
1056 .. seealso::
1058 You may want to use :py:func:`babel.units.get_unit_name` instead.
1060 .. note:: The format of the value returned may change between
1061 Babel versions.
1063 """
1064 return self._data['unit_display_names']
1067def default_locale(category: str | None = None, aliases: Mapping[str, str] = LOCALE_ALIASES) -> str | None:
1068 """Returns the system default locale for a given category, based on
1069 environment variables.
1071 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']:
1072 ... os.environ[name] = ''
1073 >>> os.environ['LANG'] = 'fr_FR.UTF-8'
1074 >>> default_locale('LC_MESSAGES')
1075 'fr_FR'
1077 The "C" or "POSIX" pseudo-locales are treated as aliases for the
1078 "en_US_POSIX" locale:
1080 >>> os.environ['LC_MESSAGES'] = 'POSIX'
1081 >>> default_locale('LC_MESSAGES')
1082 'en_US_POSIX'
1084 The following fallbacks to the variable are always considered:
1086 - ``LANGUAGE``
1087 - ``LC_ALL``
1088 - ``LC_CTYPE``
1089 - ``LANG``
1091 :param category: one of the ``LC_XXX`` environment variable names
1092 :param aliases: a dictionary of aliases for locale identifiers
1093 """
1094 varnames = (category, 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
1095 for name in filter(None, varnames):
1096 locale = os.getenv(name)
1097 if locale:
1098 if name == 'LANGUAGE' and ':' in locale:
1099 # the LANGUAGE variable may contain a colon-separated list of
1100 # language codes; we just pick the language on the list
1101 locale = locale.split(':')[0]
1102 if locale.split('.')[0] in ('C', 'POSIX'):
1103 locale = 'en_US_POSIX'
1104 elif aliases and locale in aliases:
1105 locale = aliases[locale]
1106 try:
1107 return get_locale_identifier(parse_locale(locale))
1108 except ValueError:
1109 pass
1110 return None
1113def negotiate_locale(preferred: Iterable[str], available: Iterable[str], sep: str = '_', aliases: Mapping[str, str] = LOCALE_ALIASES) -> str | None:
1114 """Find the best match between available and requested locale strings.
1116 >>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
1117 'de_DE'
1118 >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de'])
1119 'de'
1121 Case is ignored by the algorithm, the result uses the case of the preferred
1122 locale identifier:
1124 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1125 'de_DE'
1127 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1128 'de_DE'
1130 By default, some web browsers unfortunately do not include the territory
1131 in the locale identifier for many locales, and some don't even allow the
1132 user to easily add the territory. So while you may prefer using qualified
1133 locale identifiers in your web-application, they would not normally match
1134 the language-only locale sent by such browsers. To workaround that, this
1135 function uses a default mapping of commonly used language-only locale
1136 identifiers to identifiers including the territory:
1138 >>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US'])
1139 'ja_JP'
1141 Some browsers even use an incorrect or outdated language code, such as "no"
1142 for Norwegian, where the correct locale identifier would actually be "nb_NO"
1143 (Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of
1144 such cases, too:
1146 >>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE'])
1147 'nb_NO'
1149 You can override this default mapping by passing a different `aliases`
1150 dictionary to this function, or you can bypass the behavior althogher by
1151 setting the `aliases` parameter to `None`.
1153 :param preferred: the list of locale strings preferred by the user
1154 :param available: the list of locale strings available
1155 :param sep: character that separates the different parts of the locale
1156 strings
1157 :param aliases: a dictionary of aliases for locale identifiers
1158 """
1159 available = [a.lower() for a in available if a]
1160 for locale in preferred:
1161 ll = locale.lower()
1162 if ll in available:
1163 return locale
1164 if aliases:
1165 alias = aliases.get(ll)
1166 if alias:
1167 alias = alias.replace('_', sep)
1168 if alias.lower() in available:
1169 return alias
1170 parts = locale.split(sep)
1171 if len(parts) > 1 and parts[0].lower() in available:
1172 return parts[0]
1173 return None
1176def parse_locale(
1177 identifier: str,
1178 sep: str = '_',
1179) -> tuple[str, str | None, str | None, str | None] | tuple[str, str | None, str | None, str | None, str | None]:
1180 """Parse a locale identifier into a tuple of the form ``(language,
1181 territory, script, variant, modifier)``.
1183 >>> parse_locale('zh_CN')
1184 ('zh', 'CN', None, None)
1185 >>> parse_locale('zh_Hans_CN')
1186 ('zh', 'CN', 'Hans', None)
1187 >>> parse_locale('ca_es_valencia')
1188 ('ca', 'ES', None, 'VALENCIA')
1189 >>> parse_locale('en_150')
1190 ('en', '150', None, None)
1191 >>> parse_locale('en_us_posix')
1192 ('en', 'US', None, 'POSIX')
1193 >>> parse_locale('it_IT@euro')
1194 ('it', 'IT', None, None, 'euro')
1195 >>> parse_locale('it_IT@custom')
1196 ('it', 'IT', None, None, 'custom')
1197 >>> parse_locale('it_IT@')
1198 ('it', 'IT', None, None)
1200 The default component separator is "_", but a different separator can be
1201 specified using the `sep` parameter.
1203 The optional modifier is always separated with "@" and at the end:
1205 >>> parse_locale('zh-CN', sep='-')
1206 ('zh', 'CN', None, None)
1207 >>> parse_locale('zh-CN@custom', sep='-')
1208 ('zh', 'CN', None, None, 'custom')
1210 If the identifier cannot be parsed into a locale, a `ValueError` exception
1211 is raised:
1213 >>> parse_locale('not_a_LOCALE_String')
1214 Traceback (most recent call last):
1215 ...
1216 ValueError: 'not_a_LOCALE_String' is not a valid locale identifier
1218 Encoding information is removed from the identifier, while modifiers are
1219 kept:
1221 >>> parse_locale('en_US.UTF-8')
1222 ('en', 'US', None, None)
1223 >>> parse_locale('de_DE.iso885915@euro')
1224 ('de', 'DE', None, None, 'euro')
1226 See :rfc:`4646` for more information.
1228 :param identifier: the locale identifier string
1229 :param sep: character that separates the different components of the locale
1230 identifier
1231 :raise `ValueError`: if the string does not appear to be a valid locale
1232 identifier
1233 """
1234 identifier, _, modifier = identifier.partition('@')
1235 if '.' in identifier:
1236 # this is probably the charset/encoding, which we don't care about
1237 identifier = identifier.split('.', 1)[0]
1239 parts = identifier.split(sep)
1240 lang = parts.pop(0).lower()
1241 if not lang.isalpha():
1242 raise ValueError(f"expected only letters, got {lang!r}")
1244 script = territory = variant = None
1245 if parts and len(parts[0]) == 4 and parts[0].isalpha():
1246 script = parts.pop(0).title()
1248 if parts:
1249 if len(parts[0]) == 2 and parts[0].isalpha():
1250 territory = parts.pop(0).upper()
1251 elif len(parts[0]) == 3 and parts[0].isdigit():
1252 territory = parts.pop(0)
1254 if parts and (
1255 len(parts[0]) == 4 and parts[0][0].isdigit() or
1256 len(parts[0]) >= 5 and parts[0][0].isalpha()
1257 ):
1258 variant = parts.pop().upper()
1260 if parts:
1261 raise ValueError(f"{identifier!r} is not a valid locale identifier")
1263 # TODO(3.0): always return a 5-tuple
1264 if modifier:
1265 return lang, territory, script, variant, modifier
1266 else:
1267 return lang, territory, script, variant
1270def get_locale_identifier(
1271 tup: tuple[str]
1272 | tuple[str, str | None]
1273 | tuple[str, str | None, str | None]
1274 | tuple[str, str | None, str | None, str | None]
1275 | tuple[str, str | None, str | None, str | None, str | None],
1276 sep: str = "_",
1277) -> str:
1278 """The reverse of :func:`parse_locale`. It creates a locale identifier out
1279 of a ``(language, territory, script, variant, modifier)`` tuple. Items can be set to
1280 ``None`` and trailing ``None``\\s can also be left out of the tuple.
1282 >>> get_locale_identifier(('de', 'DE', None, '1999', 'custom'))
1283 'de_DE_1999@custom'
1284 >>> get_locale_identifier(('fi', None, None, None, 'custom'))
1285 'fi@custom'
1288 .. versionadded:: 1.0
1290 :param tup: the tuple as returned by :func:`parse_locale`.
1291 :param sep: the separator for the identifier.
1292 """
1293 tup = tuple(tup[:5]) # type: ignore # length should be no more than 5
1294 lang, territory, script, variant, modifier = tup + (None,) * (5 - len(tup))
1295 ret = sep.join(filter(None, (lang, script, territory, variant)))
1296 return f'{ret}@{modifier}' if modifier else ret