Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/babel/core.py: 36%
351 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:39 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:39 +0000
1"""
2 babel.core
3 ~~~~~~~~~~
5 Core locale representation and locale data access.
7 :copyright: (c) 2013-2023 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['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.
630 .. note:: The format of the value returned may change between
631 Babel versions.
633 >>> Locale('fr', 'FR').number_symbols['decimal']
634 u','
635 """
636 return self._data['number_symbols']
638 @property
639 def decimal_formats(self) -> localedata.LocaleDataDict:
640 """Locale patterns for decimal number formatting.
642 .. note:: The format of the value returned may change between
643 Babel versions.
645 >>> Locale('en', 'US').decimal_formats[None]
646 <NumberPattern u'#,##0.###'>
647 """
648 return self._data['decimal_formats']
650 @property
651 def compact_decimal_formats(self) -> localedata.LocaleDataDict:
652 """Locale patterns for compact decimal number formatting.
654 .. note:: The format of the value returned may change between
655 Babel versions.
657 >>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"]
658 <NumberPattern u'0K'>
659 """
660 return self._data['compact_decimal_formats']
662 @property
663 def currency_formats(self) -> localedata.LocaleDataDict:
664 """Locale patterns for currency number formatting.
666 .. note:: The format of the value returned may change between
667 Babel versions.
669 >>> Locale('en', 'US').currency_formats['standard']
670 <NumberPattern u'\\xa4#,##0.00'>
671 >>> Locale('en', 'US').currency_formats['accounting']
672 <NumberPattern u'\\xa4#,##0.00;(\\xa4#,##0.00)'>
673 """
674 return self._data['currency_formats']
676 @property
677 def compact_currency_formats(self) -> localedata.LocaleDataDict:
678 """Locale patterns for compact currency number formatting.
680 .. note:: The format of the value returned may change between
681 Babel versions.
683 >>> Locale('en', 'US').compact_currency_formats["short"]["one"]["1000"]
684 <NumberPattern u'¤0K'>
685 """
686 return self._data['compact_currency_formats']
688 @property
689 def percent_formats(self) -> localedata.LocaleDataDict:
690 """Locale patterns for percent number formatting.
692 .. note:: The format of the value returned may change between
693 Babel versions.
695 >>> Locale('en', 'US').percent_formats[None]
696 <NumberPattern u'#,##0%'>
697 """
698 return self._data['percent_formats']
700 @property
701 def scientific_formats(self) -> localedata.LocaleDataDict:
702 """Locale patterns for scientific number formatting.
704 .. note:: The format of the value returned may change between
705 Babel versions.
707 >>> Locale('en', 'US').scientific_formats[None]
708 <NumberPattern u'#E0'>
709 """
710 return self._data['scientific_formats']
712 # { Calendar Information and Date Formatting
714 @property
715 def periods(self) -> localedata.LocaleDataDict:
716 """Locale display names for day periods (AM/PM).
718 >>> Locale('en', 'US').periods['am']
719 u'AM'
720 """
721 try:
722 return self._data['day_periods']['stand-alone']['wide']
723 except KeyError:
724 return localedata.LocaleDataDict({}) # pragma: no cover
726 @property
727 def day_periods(self) -> localedata.LocaleDataDict:
728 """Locale display names for various day periods (not necessarily only AM/PM).
730 These are not meant to be used without the relevant `day_period_rules`.
731 """
732 return self._data['day_periods']
734 @property
735 def day_period_rules(self) -> localedata.LocaleDataDict:
736 """Day period rules for the locale. Used by `get_period_id`.
737 """
738 return self._data.get('day_period_rules', localedata.LocaleDataDict({}))
740 @property
741 def days(self) -> localedata.LocaleDataDict:
742 """Locale display names for weekdays.
744 >>> Locale('de', 'DE').days['format']['wide'][3]
745 u'Donnerstag'
746 """
747 return self._data['days']
749 @property
750 def months(self) -> localedata.LocaleDataDict:
751 """Locale display names for months.
753 >>> Locale('de', 'DE').months['format']['wide'][10]
754 u'Oktober'
755 """
756 return self._data['months']
758 @property
759 def quarters(self) -> localedata.LocaleDataDict:
760 """Locale display names for quarters.
762 >>> Locale('de', 'DE').quarters['format']['wide'][1]
763 u'1. Quartal'
764 """
765 return self._data['quarters']
767 @property
768 def eras(self) -> localedata.LocaleDataDict:
769 """Locale display names for eras.
771 .. note:: The format of the value returned may change between
772 Babel versions.
774 >>> Locale('en', 'US').eras['wide'][1]
775 u'Anno Domini'
776 >>> Locale('en', 'US').eras['abbreviated'][0]
777 u'BC'
778 """
779 return self._data['eras']
781 @property
782 def time_zones(self) -> localedata.LocaleDataDict:
783 """Locale display names for time zones.
785 .. note:: The format of the value returned may change between
786 Babel versions.
788 >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight']
789 u'British Summer Time'
790 >>> Locale('en', 'US').time_zones['America/St_Johns']['city']
791 u'St. John\u2019s'
792 """
793 return self._data['time_zones']
795 @property
796 def meta_zones(self) -> localedata.LocaleDataDict:
797 """Locale display names for meta time zones.
799 Meta time zones are basically groups of different Olson time zones that
800 have the same GMT offset and daylight savings time.
802 .. note:: The format of the value returned may change between
803 Babel versions.
805 >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
806 u'Central European Summer Time'
808 .. versionadded:: 0.9
809 """
810 return self._data['meta_zones']
812 @property
813 def zone_formats(self) -> localedata.LocaleDataDict:
814 """Patterns related to the formatting of time zones.
816 .. note:: The format of the value returned may change between
817 Babel versions.
819 >>> Locale('en', 'US').zone_formats['fallback']
820 u'%(1)s (%(0)s)'
821 >>> Locale('pt', 'BR').zone_formats['region']
822 u'Hor\\xe1rio %s'
824 .. versionadded:: 0.9
825 """
826 return self._data['zone_formats']
828 @property
829 def first_week_day(self) -> int:
830 """The first day of a week, with 0 being Monday.
832 >>> Locale('de', 'DE').first_week_day
833 0
834 >>> Locale('en', 'US').first_week_day
835 6
836 """
837 return self._data['week_data']['first_day']
839 @property
840 def weekend_start(self) -> int:
841 """The day the weekend starts, with 0 being Monday.
843 >>> Locale('de', 'DE').weekend_start
844 5
845 """
846 return self._data['week_data']['weekend_start']
848 @property
849 def weekend_end(self) -> int:
850 """The day the weekend ends, with 0 being Monday.
852 >>> Locale('de', 'DE').weekend_end
853 6
854 """
855 return self._data['week_data']['weekend_end']
857 @property
858 def min_week_days(self) -> int:
859 """The minimum number of days in a week so that the week is counted as
860 the first week of a year or month.
862 >>> Locale('de', 'DE').min_week_days
863 4
864 """
865 return self._data['week_data']['min_days']
867 @property
868 def date_formats(self) -> localedata.LocaleDataDict:
869 """Locale patterns for date formatting.
871 .. note:: The format of the value returned may change between
872 Babel versions.
874 >>> Locale('en', 'US').date_formats['short']
875 <DateTimePattern u'M/d/yy'>
876 >>> Locale('fr', 'FR').date_formats['long']
877 <DateTimePattern u'd MMMM y'>
878 """
879 return self._data['date_formats']
881 @property
882 def time_formats(self) -> localedata.LocaleDataDict:
883 """Locale patterns for time formatting.
885 .. note:: The format of the value returned may change between
886 Babel versions.
888 >>> Locale('en', 'US').time_formats['short']
889 <DateTimePattern u'h:mm\u202fa'>
890 >>> Locale('fr', 'FR').time_formats['long']
891 <DateTimePattern u'HH:mm:ss z'>
892 """
893 return self._data['time_formats']
895 @property
896 def datetime_formats(self) -> localedata.LocaleDataDict:
897 """Locale patterns for datetime formatting.
899 .. note:: The format of the value returned may change between
900 Babel versions.
902 >>> Locale('en').datetime_formats['full']
903 u'{1}, {0}'
904 >>> Locale('th').datetime_formats['medium']
905 u'{1} {0}'
906 """
907 return self._data['datetime_formats']
909 @property
910 def datetime_skeletons(self) -> localedata.LocaleDataDict:
911 """Locale patterns for formatting parts of a datetime.
913 >>> Locale('en').datetime_skeletons['MEd']
914 <DateTimePattern u'E, M/d'>
915 >>> Locale('fr').datetime_skeletons['MEd']
916 <DateTimePattern u'E dd/MM'>
917 >>> Locale('fr').datetime_skeletons['H']
918 <DateTimePattern u"HH 'h'">
919 """
920 return self._data['datetime_skeletons']
922 @property
923 def interval_formats(self) -> localedata.LocaleDataDict:
924 """Locale patterns for interval formatting.
926 .. note:: The format of the value returned may change between
927 Babel versions.
929 How to format date intervals in Finnish when the day is the
930 smallest changing component:
932 >>> Locale('fi_FI').interval_formats['MEd']['d']
933 [u'E d. \u2013 ', u'E d.M.']
935 .. seealso::
937 The primary API to use this data is :py:func:`babel.dates.format_interval`.
940 :rtype: dict[str, dict[str, list[str]]]
941 """
942 return self._data['interval_formats']
944 @property
945 def plural_form(self) -> PluralRule:
946 """Plural rules for the locale.
948 >>> Locale('en').plural_form(1)
949 'one'
950 >>> Locale('en').plural_form(0)
951 'other'
952 >>> Locale('fr').plural_form(0)
953 'one'
954 >>> Locale('ru').plural_form(100)
955 'many'
956 """
957 return self._data.get('plural_form', _default_plural_rule)
959 @property
960 def list_patterns(self) -> localedata.LocaleDataDict:
961 """Patterns for generating lists
963 .. note:: The format of the value returned may change between
964 Babel versions.
966 >>> Locale('en').list_patterns['standard']['start']
967 u'{0}, {1}'
968 >>> Locale('en').list_patterns['standard']['end']
969 u'{0}, and {1}'
970 >>> Locale('en_GB').list_patterns['standard']['end']
971 u'{0} and {1}'
972 """
973 return self._data['list_patterns']
975 @property
976 def ordinal_form(self) -> PluralRule:
977 """Plural rules for the locale.
979 >>> Locale('en').ordinal_form(1)
980 'one'
981 >>> Locale('en').ordinal_form(2)
982 'two'
983 >>> Locale('en').ordinal_form(3)
984 'few'
985 >>> Locale('fr').ordinal_form(2)
986 'other'
987 >>> Locale('ru').ordinal_form(100)
988 'other'
989 """
990 return self._data.get('ordinal_form', _default_plural_rule)
992 @property
993 def measurement_systems(self) -> localedata.LocaleDataDict:
994 """Localized names for various measurement systems.
996 >>> Locale('fr', 'FR').measurement_systems['US']
997 u'am\\xe9ricain'
998 >>> Locale('en', 'US').measurement_systems['US']
999 u'US'
1001 """
1002 return self._data['measurement_systems']
1004 @property
1005 def character_order(self) -> str:
1006 """The text direction for the language.
1008 >>> Locale('de', 'DE').character_order
1009 'left-to-right'
1010 >>> Locale('ar', 'SA').character_order
1011 'right-to-left'
1012 """
1013 return self._data['character_order']
1015 @property
1016 def text_direction(self) -> str:
1017 """The text direction for the language in CSS short-hand form.
1019 >>> Locale('de', 'DE').text_direction
1020 'ltr'
1021 >>> Locale('ar', 'SA').text_direction
1022 'rtl'
1023 """
1024 return ''.join(word[0] for word in self.character_order.split('-'))
1026 @property
1027 def unit_display_names(self) -> localedata.LocaleDataDict:
1028 """Display names for units of measurement.
1030 .. seealso::
1032 You may want to use :py:func:`babel.units.get_unit_name` instead.
1034 .. note:: The format of the value returned may change between
1035 Babel versions.
1037 """
1038 return self._data['unit_display_names']
1041def default_locale(category: str | None = None, aliases: Mapping[str, str] = LOCALE_ALIASES) -> str | None:
1042 """Returns the system default locale for a given category, based on
1043 environment variables.
1045 >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']:
1046 ... os.environ[name] = ''
1047 >>> os.environ['LANG'] = 'fr_FR.UTF-8'
1048 >>> default_locale('LC_MESSAGES')
1049 'fr_FR'
1051 The "C" or "POSIX" pseudo-locales are treated as aliases for the
1052 "en_US_POSIX" locale:
1054 >>> os.environ['LC_MESSAGES'] = 'POSIX'
1055 >>> default_locale('LC_MESSAGES')
1056 'en_US_POSIX'
1058 The following fallbacks to the variable are always considered:
1060 - ``LANGUAGE``
1061 - ``LC_ALL``
1062 - ``LC_CTYPE``
1063 - ``LANG``
1065 :param category: one of the ``LC_XXX`` environment variable names
1066 :param aliases: a dictionary of aliases for locale identifiers
1067 """
1068 varnames = (category, 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
1069 for name in filter(None, varnames):
1070 locale = os.getenv(name)
1071 if locale:
1072 if name == 'LANGUAGE' and ':' in locale:
1073 # the LANGUAGE variable may contain a colon-separated list of
1074 # language codes; we just pick the language on the list
1075 locale = locale.split(':')[0]
1076 if locale.split('.')[0] in ('C', 'POSIX'):
1077 locale = 'en_US_POSIX'
1078 elif aliases and locale in aliases:
1079 locale = aliases[locale]
1080 try:
1081 return get_locale_identifier(parse_locale(locale))
1082 except ValueError:
1083 pass
1084 return None
1087def negotiate_locale(preferred: Iterable[str], available: Iterable[str], sep: str = '_', aliases: Mapping[str, str] = LOCALE_ALIASES) -> str | None:
1088 """Find the best match between available and requested locale strings.
1090 >>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
1091 'de_DE'
1092 >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de'])
1093 'de'
1095 Case is ignored by the algorithm, the result uses the case of the preferred
1096 locale identifier:
1098 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1099 'de_DE'
1101 >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
1102 'de_DE'
1104 By default, some web browsers unfortunately do not include the territory
1105 in the locale identifier for many locales, and some don't even allow the
1106 user to easily add the territory. So while you may prefer using qualified
1107 locale identifiers in your web-application, they would not normally match
1108 the language-only locale sent by such browsers. To workaround that, this
1109 function uses a default mapping of commonly used language-only locale
1110 identifiers to identifiers including the territory:
1112 >>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US'])
1113 'ja_JP'
1115 Some browsers even use an incorrect or outdated language code, such as "no"
1116 for Norwegian, where the correct locale identifier would actually be "nb_NO"
1117 (Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of
1118 such cases, too:
1120 >>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE'])
1121 'nb_NO'
1123 You can override this default mapping by passing a different `aliases`
1124 dictionary to this function, or you can bypass the behavior althogher by
1125 setting the `aliases` parameter to `None`.
1127 :param preferred: the list of locale strings preferred by the user
1128 :param available: the list of locale strings available
1129 :param sep: character that separates the different parts of the locale
1130 strings
1131 :param aliases: a dictionary of aliases for locale identifiers
1132 """
1133 available = [a.lower() for a in available if a]
1134 for locale in preferred:
1135 ll = locale.lower()
1136 if ll in available:
1137 return locale
1138 if aliases:
1139 alias = aliases.get(ll)
1140 if alias:
1141 alias = alias.replace('_', sep)
1142 if alias.lower() in available:
1143 return alias
1144 parts = locale.split(sep)
1145 if len(parts) > 1 and parts[0].lower() in available:
1146 return parts[0]
1147 return None
1150def parse_locale(
1151 identifier: str,
1152 sep: str = '_'
1153) -> tuple[str, str | None, str | None, str | None] | tuple[str, str | None, str | None, str | None, str | None]:
1154 """Parse a locale identifier into a tuple of the form ``(language,
1155 territory, script, variant, modifier)``.
1157 >>> parse_locale('zh_CN')
1158 ('zh', 'CN', None, None)
1159 >>> parse_locale('zh_Hans_CN')
1160 ('zh', 'CN', 'Hans', None)
1161 >>> parse_locale('ca_es_valencia')
1162 ('ca', 'ES', None, 'VALENCIA')
1163 >>> parse_locale('en_150')
1164 ('en', '150', None, None)
1165 >>> parse_locale('en_us_posix')
1166 ('en', 'US', None, 'POSIX')
1167 >>> parse_locale('it_IT@euro')
1168 ('it', 'IT', None, None, 'euro')
1169 >>> parse_locale('it_IT@custom')
1170 ('it', 'IT', None, None, 'custom')
1171 >>> parse_locale('it_IT@')
1172 ('it', 'IT', None, None)
1174 The default component separator is "_", but a different separator can be
1175 specified using the `sep` parameter.
1177 The optional modifier is always separated with "@" and at the end:
1179 >>> parse_locale('zh-CN', sep='-')
1180 ('zh', 'CN', None, None)
1181 >>> parse_locale('zh-CN@custom', sep='-')
1182 ('zh', 'CN', None, None, 'custom')
1184 If the identifier cannot be parsed into a locale, a `ValueError` exception
1185 is raised:
1187 >>> parse_locale('not_a_LOCALE_String')
1188 Traceback (most recent call last):
1189 ...
1190 ValueError: 'not_a_LOCALE_String' is not a valid locale identifier
1192 Encoding information is removed from the identifier, while modifiers are
1193 kept:
1195 >>> parse_locale('en_US.UTF-8')
1196 ('en', 'US', None, None)
1197 >>> parse_locale('de_DE.iso885915@euro')
1198 ('de', 'DE', None, None, 'euro')
1200 See :rfc:`4646` for more information.
1202 :param identifier: the locale identifier string
1203 :param sep: character that separates the different components of the locale
1204 identifier
1205 :raise `ValueError`: if the string does not appear to be a valid locale
1206 identifier
1207 """
1208 identifier, _, modifier = identifier.partition('@')
1209 if '.' in identifier:
1210 # this is probably the charset/encoding, which we don't care about
1211 identifier = identifier.split('.', 1)[0]
1213 parts = identifier.split(sep)
1214 lang = parts.pop(0).lower()
1215 if not lang.isalpha():
1216 raise ValueError(f"expected only letters, got {lang!r}")
1218 script = territory = variant = None
1219 if parts and len(parts[0]) == 4 and parts[0].isalpha():
1220 script = parts.pop(0).title()
1222 if parts:
1223 if len(parts[0]) == 2 and parts[0].isalpha():
1224 territory = parts.pop(0).upper()
1225 elif len(parts[0]) == 3 and parts[0].isdigit():
1226 territory = parts.pop(0)
1228 if parts and (
1229 len(parts[0]) == 4 and parts[0][0].isdigit() or
1230 len(parts[0]) >= 5 and parts[0][0].isalpha()
1231 ):
1232 variant = parts.pop().upper()
1234 if parts:
1235 raise ValueError(f"{identifier!r} is not a valid locale identifier")
1237 # TODO(3.0): always return a 5-tuple
1238 if modifier:
1239 return lang, territory, script, variant, modifier
1240 else:
1241 return lang, territory, script, variant
1244def get_locale_identifier(
1245 tup: tuple[str]
1246 | tuple[str, str | None]
1247 | tuple[str, str | None, str | None]
1248 | tuple[str, str | None, str | None, str | None]
1249 | tuple[str, str | None, str | None, str | None, str | None],
1250 sep: str = "_",
1251) -> str:
1252 """The reverse of :func:`parse_locale`. It creates a locale identifier out
1253 of a ``(language, territory, script, variant, modifier)`` tuple. Items can be set to
1254 ``None`` and trailing ``None``\\s can also be left out of the tuple.
1256 >>> get_locale_identifier(('de', 'DE', None, '1999', 'custom'))
1257 'de_DE_1999@custom'
1258 >>> get_locale_identifier(('fi', None, None, None, 'custom'))
1259 'fi@custom'
1262 .. versionadded:: 1.0
1264 :param tup: the tuple as returned by :func:`parse_locale`.
1265 :param sep: the separator for the identifier.
1266 """
1267 tup = tuple(tup[:5]) # type: ignore # length should be no more than 5
1268 lang, territory, script, variant, modifier = tup + (None,) * (5 - len(tup))
1269 ret = sep.join(filter(None, (lang, script, territory, variant)))
1270 return f'{ret}@{modifier}' if modifier else ret