12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133 |
- # -*- coding: utf-8 -*-
- """
- babel.core
- ~~~~~~~~~~
- Core locale representation and locale data access.
- :copyright: (c) 2013-2021 by the Babel Team.
- :license: BSD, see LICENSE for more details.
- """
- import os
- from babel import localedata
- from babel._compat import pickle, string_types
- from babel.plural import PluralRule
- __all__ = ['UnknownLocaleError', 'Locale', 'default_locale', 'negotiate_locale',
- 'parse_locale']
- _global_data = None
- _default_plural_rule = PluralRule({})
- def _raise_no_data_error():
- raise RuntimeError('The babel data files are not available. '
- 'This usually happens because you are using '
- 'a source checkout from Babel and you did '
- 'not build the data files. Just make sure '
- 'to run "python setup.py import_cldr" before '
- 'installing the library.')
- def get_global(key):
- """Return the dictionary for the given key in the global data.
- The global data is stored in the ``babel/global.dat`` file and contains
- information independent of individual locales.
- >>> get_global('zone_aliases')['UTC']
- u'Etc/UTC'
- >>> get_global('zone_territories')['Europe/Berlin']
- u'DE'
- The keys available are:
- - ``all_currencies``
- - ``currency_fractions``
- - ``language_aliases``
- - ``likely_subtags``
- - ``parent_exceptions``
- - ``script_aliases``
- - ``territory_aliases``
- - ``territory_currencies``
- - ``territory_languages``
- - ``territory_zones``
- - ``variant_aliases``
- - ``windows_zone_mapping``
- - ``zone_aliases``
- - ``zone_territories``
- .. note:: The internal structure of the data may change between versions.
- .. versionadded:: 0.9
- :param key: the data key
- """
- global _global_data
- if _global_data is None:
- dirname = os.path.join(os.path.dirname(__file__))
- filename = os.path.join(dirname, 'global.dat')
- if not os.path.isfile(filename):
- _raise_no_data_error()
- with open(filename, 'rb') as fileobj:
- _global_data = pickle.load(fileobj)
- return _global_data.get(key, {})
- LOCALE_ALIASES = {
- 'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ',
- 'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES',
- 'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES',
- 'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT',
- 'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV',
- 'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL',
- 'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI',
- 'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA'
- }
- class UnknownLocaleError(Exception):
- """Exception thrown when a locale is requested for which no locale data
- is available.
- """
- def __init__(self, identifier):
- """Create the exception.
- :param identifier: the identifier string of the unsupported locale
- """
- Exception.__init__(self, 'unknown locale %r' % identifier)
- #: The identifier of the locale that could not be found.
- self.identifier = identifier
- class Locale(object):
- """Representation of a specific locale.
- >>> locale = Locale('en', 'US')
- >>> repr(locale)
- "Locale('en', territory='US')"
- >>> locale.display_name
- u'English (United States)'
- A `Locale` object can also be instantiated from a raw locale string:
- >>> locale = Locale.parse('en-US', sep='-')
- >>> repr(locale)
- "Locale('en', territory='US')"
- `Locale` objects provide access to a collection of locale data, such as
- territory and language names, number and date format patterns, and more:
- >>> locale.number_symbols['decimal']
- u'.'
- If a locale is requested for which no locale data is available, an
- `UnknownLocaleError` is raised:
- >>> Locale.parse('en_XX')
- Traceback (most recent call last):
- ...
- UnknownLocaleError: unknown locale 'en_XX'
- For more information see :rfc:`3066`.
- """
- def __init__(self, language, territory=None, script=None, variant=None):
- """Initialize the locale object from the given identifier components.
- >>> locale = Locale('en', 'US')
- >>> locale.language
- 'en'
- >>> locale.territory
- 'US'
- :param language: the language code
- :param territory: the territory (country or region) code
- :param script: the script code
- :param variant: the variant code
- :raise `UnknownLocaleError`: if no locale data is available for the
- requested locale
- """
- #: the language code
- self.language = language
- #: the territory (country or region) code
- self.territory = territory
- #: the script code
- self.script = script
- #: the variant code
- self.variant = variant
- self.__data = None
- identifier = str(self)
- if not localedata.exists(identifier):
- raise UnknownLocaleError(identifier)
- @classmethod
- def default(cls, category=None, aliases=LOCALE_ALIASES):
- """Return the system default locale for the specified category.
- >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
- ... os.environ[name] = ''
- >>> os.environ['LANG'] = 'fr_FR.UTF-8'
- >>> Locale.default('LC_MESSAGES')
- Locale('fr', territory='FR')
- The following fallbacks to the variable are always considered:
- - ``LANGUAGE``
- - ``LC_ALL``
- - ``LC_CTYPE``
- - ``LANG``
- :param category: one of the ``LC_XXX`` environment variable names
- :param aliases: a dictionary of aliases for locale identifiers
- """
- # XXX: use likely subtag expansion here instead of the
- # aliases dictionary.
- locale_string = default_locale(category, aliases=aliases)
- return cls.parse(locale_string)
- @classmethod
- def negotiate(cls, preferred, available, sep='_', aliases=LOCALE_ALIASES):
- """Find the best match between available and requested locale strings.
- >>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
- Locale('de', territory='DE')
- >>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de'])
- Locale('de')
- >>> Locale.negotiate(['de_DE', 'de'], ['en_US'])
- You can specify the character used in the locale identifiers to separate
- the differnet components. This separator is applied to both lists. Also,
- case is ignored in the comparison:
- >>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-')
- Locale('de', territory='DE')
- :param preferred: the list of locale identifers preferred by the user
- :param available: the list of locale identifiers available
- :param aliases: a dictionary of aliases for locale identifiers
- """
- identifier = negotiate_locale(preferred, available, sep=sep,
- aliases=aliases)
- if identifier:
- return Locale.parse(identifier, sep=sep)
- @classmethod
- def parse(cls, identifier, sep='_', resolve_likely_subtags=True):
- """Create a `Locale` instance for the given locale identifier.
- >>> l = Locale.parse('de-DE', sep='-')
- >>> l.display_name
- u'Deutsch (Deutschland)'
- If the `identifier` parameter is not a string, but actually a `Locale`
- object, that object is returned:
- >>> Locale.parse(l)
- Locale('de', territory='DE')
- This also can perform resolving of likely subtags which it does
- by default. This is for instance useful to figure out the most
- likely locale for a territory you can use ``'und'`` as the
- language tag:
- >>> Locale.parse('und_AT')
- Locale('de', territory='AT')
- :param identifier: the locale identifier string
- :param sep: optional component separator
- :param resolve_likely_subtags: if this is specified then a locale will
- have its likely subtag resolved if the
- locale otherwise does not exist. For
- instance ``zh_TW`` by itself is not a
- locale that exists but Babel can
- automatically expand it to the full
- form of ``zh_hant_TW``. Note that this
- expansion is only taking place if no
- locale exists otherwise. For instance
- there is a locale ``en`` that can exist
- by itself.
- :raise `ValueError`: if the string does not appear to be a valid locale
- identifier
- :raise `UnknownLocaleError`: if no locale data is available for the
- requested locale
- """
- if identifier is None:
- return None
- elif isinstance(identifier, Locale):
- return identifier
- elif not isinstance(identifier, string_types):
- raise TypeError('Unexpected value for identifier: %r' % (identifier,))
- parts = parse_locale(identifier, sep=sep)
- input_id = get_locale_identifier(parts)
- def _try_load(parts):
- try:
- return cls(*parts)
- except UnknownLocaleError:
- return None
- def _try_load_reducing(parts):
- # Success on first hit, return it.
- locale = _try_load(parts)
- if locale is not None:
- return locale
- # Now try without script and variant
- locale = _try_load(parts[:2])
- if locale is not None:
- return locale
- locale = _try_load(parts)
- if locale is not None:
- return locale
- if not resolve_likely_subtags:
- raise UnknownLocaleError(input_id)
- # From here onwards is some very bad likely subtag resolving. This
- # whole logic is not entirely correct but good enough (tm) for the
- # time being. This has been added so that zh_TW does not cause
- # errors for people when they upgrade. Later we should properly
- # implement ICU like fuzzy locale objects and provide a way to
- # maximize and minimize locale tags.
- language, territory, script, variant = parts
- language = get_global('language_aliases').get(language, language)
- territory = get_global('territory_aliases').get(territory, (territory,))[0]
- script = get_global('script_aliases').get(script, script)
- variant = get_global('variant_aliases').get(variant, variant)
- if territory == 'ZZ':
- territory = None
- if script == 'Zzzz':
- script = None
- parts = language, territory, script, variant
- # First match: try the whole identifier
- new_id = get_locale_identifier(parts)
- likely_subtag = get_global('likely_subtags').get(new_id)
- if likely_subtag is not None:
- locale = _try_load_reducing(parse_locale(likely_subtag))
- if locale is not None:
- return locale
- # If we did not find anything so far, try again with a
- # simplified identifier that is just the language
- likely_subtag = get_global('likely_subtags').get(language)
- if likely_subtag is not None:
- language2, _, script2, variant2 = parse_locale(likely_subtag)
- locale = _try_load_reducing((language2, territory, script2, variant2))
- if locale is not None:
- return locale
- raise UnknownLocaleError(input_id)
- def __eq__(self, other):
- for key in ('language', 'territory', 'script', 'variant'):
- if not hasattr(other, key):
- return False
- return (self.language == other.language) and \
- (self.territory == other.territory) and \
- (self.script == other.script) and \
- (self.variant == other.variant)
- def __ne__(self, other):
- return not self.__eq__(other)
- def __hash__(self):
- return hash((self.language, self.territory, self.script, self.variant))
- def __repr__(self):
- parameters = ['']
- for key in ('territory', 'script', 'variant'):
- value = getattr(self, key)
- if value is not None:
- parameters.append('%s=%r' % (key, value))
- parameter_string = '%r' % self.language + ', '.join(parameters)
- return 'Locale(%s)' % parameter_string
- def __str__(self):
- return get_locale_identifier((self.language, self.territory,
- self.script, self.variant))
- @property
- def _data(self):
- if self.__data is None:
- self.__data = localedata.LocaleDataDict(localedata.load(str(self)))
- return self.__data
- def get_display_name(self, locale=None):
- """Return the display name of the locale using the given locale.
- The display name will include the language, territory, script, and
- variant, if those are specified.
- >>> Locale('zh', 'CN', script='Hans').get_display_name('en')
- u'Chinese (Simplified, China)'
- :param locale: the locale to use
- """
- if locale is None:
- locale = self
- locale = Locale.parse(locale)
- retval = locale.languages.get(self.language)
- if retval and (self.territory or self.script or self.variant):
- details = []
- if self.script:
- details.append(locale.scripts.get(self.script))
- if self.territory:
- details.append(locale.territories.get(self.territory))
- if self.variant:
- details.append(locale.variants.get(self.variant))
- details = filter(None, details)
- if details:
- retval += ' (%s)' % u', '.join(details)
- return retval
- display_name = property(get_display_name, doc="""\
- The localized display name of the locale.
- >>> Locale('en').display_name
- u'English'
- >>> Locale('en', 'US').display_name
- u'English (United States)'
- >>> Locale('sv').display_name
- u'svenska'
- :type: `unicode`
- """)
- def get_language_name(self, locale=None):
- """Return the language of this locale in the given locale.
- >>> Locale('zh', 'CN', script='Hans').get_language_name('de')
- u'Chinesisch'
- .. versionadded:: 1.0
- :param locale: the locale to use
- """
- if locale is None:
- locale = self
- locale = Locale.parse(locale)
- return locale.languages.get(self.language)
- language_name = property(get_language_name, doc="""\
- The localized language name of the locale.
- >>> Locale('en', 'US').language_name
- u'English'
- """)
- def get_territory_name(self, locale=None):
- """Return the territory name in the given locale."""
- if locale is None:
- locale = self
- locale = Locale.parse(locale)
- return locale.territories.get(self.territory)
- territory_name = property(get_territory_name, doc="""\
- The localized territory name of the locale if available.
- >>> Locale('de', 'DE').territory_name
- u'Deutschland'
- """)
- def get_script_name(self, locale=None):
- """Return the script name in the given locale."""
- if locale is None:
- locale = self
- locale = Locale.parse(locale)
- return locale.scripts.get(self.script)
- script_name = property(get_script_name, doc="""\
- The localized script name of the locale if available.
- >>> Locale('sr', 'ME', script='Latn').script_name
- u'latinica'
- """)
- @property
- def english_name(self):
- """The english display name of the locale.
- >>> Locale('de').english_name
- u'German'
- >>> Locale('de', 'DE').english_name
- u'German (Germany)'
- :type: `unicode`"""
- return self.get_display_name(Locale('en'))
- # { General Locale Display Names
- @property
- def languages(self):
- """Mapping of language codes to translated language names.
- >>> Locale('de', 'DE').languages['ja']
- u'Japanisch'
- See `ISO 639 <http://www.loc.gov/standards/iso639-2/>`_ for
- more information.
- """
- return self._data['languages']
- @property
- def scripts(self):
- """Mapping of script codes to translated script names.
- >>> Locale('en', 'US').scripts['Hira']
- u'Hiragana'
- See `ISO 15924 <http://www.evertype.com/standards/iso15924/>`_
- for more information.
- """
- return self._data['scripts']
- @property
- def territories(self):
- """Mapping of script codes to translated script names.
- >>> Locale('es', 'CO').territories['DE']
- u'Alemania'
- See `ISO 3166 <http://www.iso.org/iso/en/prods-services/iso3166ma/>`_
- for more information.
- """
- return self._data['territories']
- @property
- def variants(self):
- """Mapping of script codes to translated script names.
- >>> Locale('de', 'DE').variants['1901']
- u'Alte deutsche Rechtschreibung'
- """
- return self._data['variants']
- # { Number Formatting
- @property
- def currencies(self):
- """Mapping of currency codes to translated currency names. This
- only returns the generic form of the currency name, not the count
- specific one. If an actual number is requested use the
- :func:`babel.numbers.get_currency_name` function.
- >>> Locale('en').currencies['COP']
- u'Colombian Peso'
- >>> Locale('de', 'DE').currencies['COP']
- u'Kolumbianischer Peso'
- """
- return self._data['currency_names']
- @property
- def currency_symbols(self):
- """Mapping of currency codes to symbols.
- >>> Locale('en', 'US').currency_symbols['USD']
- u'$'
- >>> Locale('es', 'CO').currency_symbols['USD']
- u'US$'
- """
- return self._data['currency_symbols']
- @property
- def number_symbols(self):
- """Symbols used in number formatting.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('fr', 'FR').number_symbols['decimal']
- u','
- """
- return self._data['number_symbols']
- @property
- def decimal_formats(self):
- """Locale patterns for decimal number formatting.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en', 'US').decimal_formats[None]
- <NumberPattern u'#,##0.###'>
- """
- return self._data['decimal_formats']
- @property
- def currency_formats(self):
- """Locale patterns for currency number formatting.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en', 'US').currency_formats['standard']
- <NumberPattern u'\\xa4#,##0.00'>
- >>> Locale('en', 'US').currency_formats['accounting']
- <NumberPattern u'\\xa4#,##0.00;(\\xa4#,##0.00)'>
- """
- return self._data['currency_formats']
- @property
- def percent_formats(self):
- """Locale patterns for percent number formatting.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en', 'US').percent_formats[None]
- <NumberPattern u'#,##0%'>
- """
- return self._data['percent_formats']
- @property
- def scientific_formats(self):
- """Locale patterns for scientific number formatting.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en', 'US').scientific_formats[None]
- <NumberPattern u'#E0'>
- """
- return self._data['scientific_formats']
- # { Calendar Information and Date Formatting
- @property
- def periods(self):
- """Locale display names for day periods (AM/PM).
- >>> Locale('en', 'US').periods['am']
- u'AM'
- """
- try:
- return self._data['day_periods']['stand-alone']['wide']
- except KeyError:
- return {}
- @property
- def day_periods(self):
- """Locale display names for various day periods (not necessarily only AM/PM).
- These are not meant to be used without the relevant `day_period_rules`.
- """
- return self._data['day_periods']
- @property
- def day_period_rules(self):
- """Day period rules for the locale. Used by `get_period_id`.
- """
- return self._data.get('day_period_rules', {})
- @property
- def days(self):
- """Locale display names for weekdays.
- >>> Locale('de', 'DE').days['format']['wide'][3]
- u'Donnerstag'
- """
- return self._data['days']
- @property
- def months(self):
- """Locale display names for months.
- >>> Locale('de', 'DE').months['format']['wide'][10]
- u'Oktober'
- """
- return self._data['months']
- @property
- def quarters(self):
- """Locale display names for quarters.
- >>> Locale('de', 'DE').quarters['format']['wide'][1]
- u'1. Quartal'
- """
- return self._data['quarters']
- @property
- def eras(self):
- """Locale display names for eras.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en', 'US').eras['wide'][1]
- u'Anno Domini'
- >>> Locale('en', 'US').eras['abbreviated'][0]
- u'BC'
- """
- return self._data['eras']
- @property
- def time_zones(self):
- """Locale display names for time zones.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight']
- u'British Summer Time'
- >>> Locale('en', 'US').time_zones['America/St_Johns']['city']
- u'St. John\u2019s'
- """
- return self._data['time_zones']
- @property
- def meta_zones(self):
- """Locale display names for meta time zones.
- Meta time zones are basically groups of different Olson time zones that
- have the same GMT offset and daylight savings time.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
- u'Central European Summer Time'
- .. versionadded:: 0.9
- """
- return self._data['meta_zones']
- @property
- def zone_formats(self):
- """Patterns related to the formatting of time zones.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en', 'US').zone_formats['fallback']
- u'%(1)s (%(0)s)'
- >>> Locale('pt', 'BR').zone_formats['region']
- u'Hor\\xe1rio %s'
- .. versionadded:: 0.9
- """
- return self._data['zone_formats']
- @property
- def first_week_day(self):
- """The first day of a week, with 0 being Monday.
- >>> Locale('de', 'DE').first_week_day
- 0
- >>> Locale('en', 'US').first_week_day
- 6
- """
- return self._data['week_data']['first_day']
- @property
- def weekend_start(self):
- """The day the weekend starts, with 0 being Monday.
- >>> Locale('de', 'DE').weekend_start
- 5
- """
- return self._data['week_data']['weekend_start']
- @property
- def weekend_end(self):
- """The day the weekend ends, with 0 being Monday.
- >>> Locale('de', 'DE').weekend_end
- 6
- """
- return self._data['week_data']['weekend_end']
- @property
- def min_week_days(self):
- """The minimum number of days in a week so that the week is counted as
- the first week of a year or month.
- >>> Locale('de', 'DE').min_week_days
- 4
- """
- return self._data['week_data']['min_days']
- @property
- def date_formats(self):
- """Locale patterns for date formatting.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en', 'US').date_formats['short']
- <DateTimePattern u'M/d/yy'>
- >>> Locale('fr', 'FR').date_formats['long']
- <DateTimePattern u'd MMMM y'>
- """
- return self._data['date_formats']
- @property
- def time_formats(self):
- """Locale patterns for time formatting.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en', 'US').time_formats['short']
- <DateTimePattern u'h:mm a'>
- >>> Locale('fr', 'FR').time_formats['long']
- <DateTimePattern u'HH:mm:ss z'>
- """
- return self._data['time_formats']
- @property
- def datetime_formats(self):
- """Locale patterns for datetime formatting.
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en').datetime_formats['full']
- u"{1} 'at' {0}"
- >>> Locale('th').datetime_formats['medium']
- u'{1} {0}'
- """
- return self._data['datetime_formats']
- @property
- def datetime_skeletons(self):
- """Locale patterns for formatting parts of a datetime.
- >>> Locale('en').datetime_skeletons['MEd']
- <DateTimePattern u'E, M/d'>
- >>> Locale('fr').datetime_skeletons['MEd']
- <DateTimePattern u'E dd/MM'>
- >>> Locale('fr').datetime_skeletons['H']
- <DateTimePattern u"HH 'h'">
- """
- return self._data['datetime_skeletons']
- @property
- def interval_formats(self):
- """Locale patterns for interval formatting.
- .. note:: The format of the value returned may change between
- Babel versions.
- How to format date intervals in Finnish when the day is the
- smallest changing component:
- >>> Locale('fi_FI').interval_formats['MEd']['d']
- [u'E d. \u2013 ', u'E d.M.']
- .. seealso::
- The primary API to use this data is :py:func:`babel.dates.format_interval`.
- :rtype: dict[str, dict[str, list[str]]]
- """
- return self._data['interval_formats']
- @property
- def plural_form(self):
- """Plural rules for the locale.
- >>> Locale('en').plural_form(1)
- 'one'
- >>> Locale('en').plural_form(0)
- 'other'
- >>> Locale('fr').plural_form(0)
- 'one'
- >>> Locale('ru').plural_form(100)
- 'many'
- """
- return self._data.get('plural_form', _default_plural_rule)
- @property
- def list_patterns(self):
- """Patterns for generating lists
- .. note:: The format of the value returned may change between
- Babel versions.
- >>> Locale('en').list_patterns['standard']['start']
- u'{0}, {1}'
- >>> Locale('en').list_patterns['standard']['end']
- u'{0}, and {1}'
- >>> Locale('en_GB').list_patterns['standard']['end']
- u'{0} and {1}'
- """
- return self._data['list_patterns']
- @property
- def ordinal_form(self):
- """Plural rules for the locale.
- >>> Locale('en').ordinal_form(1)
- 'one'
- >>> Locale('en').ordinal_form(2)
- 'two'
- >>> Locale('en').ordinal_form(3)
- 'few'
- >>> Locale('fr').ordinal_form(2)
- 'other'
- >>> Locale('ru').ordinal_form(100)
- 'other'
- """
- return self._data.get('ordinal_form', _default_plural_rule)
- @property
- def measurement_systems(self):
- """Localized names for various measurement systems.
- >>> Locale('fr', 'FR').measurement_systems['US']
- u'am\\xe9ricain'
- >>> Locale('en', 'US').measurement_systems['US']
- u'US'
- """
- return self._data['measurement_systems']
- @property
- def character_order(self):
- """The text direction for the language.
- >>> Locale('de', 'DE').character_order
- 'left-to-right'
- >>> Locale('ar', 'SA').character_order
- 'right-to-left'
- """
- return self._data['character_order']
- @property
- def text_direction(self):
- """The text direction for the language in CSS short-hand form.
- >>> Locale('de', 'DE').text_direction
- 'ltr'
- >>> Locale('ar', 'SA').text_direction
- 'rtl'
- """
- return ''.join(word[0] for word in self.character_order.split('-'))
- @property
- def unit_display_names(self):
- """Display names for units of measurement.
- .. seealso::
- You may want to use :py:func:`babel.units.get_unit_name` instead.
- .. note:: The format of the value returned may change between
- Babel versions.
- """
- return self._data['unit_display_names']
- def default_locale(category=None, aliases=LOCALE_ALIASES):
- """Returns the system default locale for a given category, based on
- environment variables.
- >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']:
- ... os.environ[name] = ''
- >>> os.environ['LANG'] = 'fr_FR.UTF-8'
- >>> default_locale('LC_MESSAGES')
- 'fr_FR'
- The "C" or "POSIX" pseudo-locales are treated as aliases for the
- "en_US_POSIX" locale:
- >>> os.environ['LC_MESSAGES'] = 'POSIX'
- >>> default_locale('LC_MESSAGES')
- 'en_US_POSIX'
- The following fallbacks to the variable are always considered:
- - ``LANGUAGE``
- - ``LC_ALL``
- - ``LC_CTYPE``
- - ``LANG``
- :param category: one of the ``LC_XXX`` environment variable names
- :param aliases: a dictionary of aliases for locale identifiers
- """
- varnames = (category, 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
- for name in filter(None, varnames):
- locale = os.getenv(name)
- if locale:
- if name == 'LANGUAGE' and ':' in locale:
- # the LANGUAGE variable may contain a colon-separated list of
- # language codes; we just pick the language on the list
- locale = locale.split(':')[0]
- if locale.split('.')[0] in ('C', 'POSIX'):
- locale = 'en_US_POSIX'
- elif aliases and locale in aliases:
- locale = aliases[locale]
- try:
- return get_locale_identifier(parse_locale(locale))
- except ValueError:
- pass
- def negotiate_locale(preferred, available, sep='_', aliases=LOCALE_ALIASES):
- """Find the best match between available and requested locale strings.
- >>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
- 'de_DE'
- >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de'])
- 'de'
- Case is ignored by the algorithm, the result uses the case of the preferred
- locale identifier:
- >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
- 'de_DE'
- >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
- 'de_DE'
- By default, some web browsers unfortunately do not include the territory
- in the locale identifier for many locales, and some don't even allow the
- user to easily add the territory. So while you may prefer using qualified
- locale identifiers in your web-application, they would not normally match
- the language-only locale sent by such browsers. To workaround that, this
- function uses a default mapping of commonly used langauge-only locale
- identifiers to identifiers including the territory:
- >>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US'])
- 'ja_JP'
- Some browsers even use an incorrect or outdated language code, such as "no"
- for Norwegian, where the correct locale identifier would actually be "nb_NO"
- (Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of
- such cases, too:
- >>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE'])
- 'nb_NO'
- You can override this default mapping by passing a different `aliases`
- dictionary to this function, or you can bypass the behavior althogher by
- setting the `aliases` parameter to `None`.
- :param preferred: the list of locale strings preferred by the user
- :param available: the list of locale strings available
- :param sep: character that separates the different parts of the locale
- strings
- :param aliases: a dictionary of aliases for locale identifiers
- """
- available = [a.lower() for a in available if a]
- for locale in preferred:
- ll = locale.lower()
- if ll in available:
- return locale
- if aliases:
- alias = aliases.get(ll)
- if alias:
- alias = alias.replace('_', sep)
- if alias.lower() in available:
- return alias
- parts = locale.split(sep)
- if len(parts) > 1 and parts[0].lower() in available:
- return parts[0]
- return None
- def parse_locale(identifier, sep='_'):
- """Parse a locale identifier into a tuple of the form ``(language,
- territory, script, variant)``.
- >>> parse_locale('zh_CN')
- ('zh', 'CN', None, None)
- >>> parse_locale('zh_Hans_CN')
- ('zh', 'CN', 'Hans', None)
- The default component separator is "_", but a different separator can be
- specified using the `sep` parameter:
- >>> parse_locale('zh-CN', sep='-')
- ('zh', 'CN', None, None)
- If the identifier cannot be parsed into a locale, a `ValueError` exception
- is raised:
- >>> parse_locale('not_a_LOCALE_String')
- Traceback (most recent call last):
- ...
- ValueError: 'not_a_LOCALE_String' is not a valid locale identifier
- Encoding information and locale modifiers are removed from the identifier:
- >>> parse_locale('it_IT@euro')
- ('it', 'IT', None, None)
- >>> parse_locale('en_US.UTF-8')
- ('en', 'US', None, None)
- >>> parse_locale('de_DE.iso885915@euro')
- ('de', 'DE', None, None)
- See :rfc:`4646` for more information.
- :param identifier: the locale identifier string
- :param sep: character that separates the different components of the locale
- identifier
- :raise `ValueError`: if the string does not appear to be a valid locale
- identifier
- """
- if '.' in identifier:
- # this is probably the charset/encoding, which we don't care about
- identifier = identifier.split('.', 1)[0]
- if '@' in identifier:
- # this is a locale modifier such as @euro, which we don't care about
- # either
- identifier = identifier.split('@', 1)[0]
- parts = identifier.split(sep)
- lang = parts.pop(0).lower()
- if not lang.isalpha():
- raise ValueError('expected only letters, got %r' % lang)
- script = territory = variant = None
- if parts:
- if len(parts[0]) == 4 and parts[0].isalpha():
- script = parts.pop(0).title()
- if parts:
- if len(parts[0]) == 2 and parts[0].isalpha():
- territory = parts.pop(0).upper()
- elif len(parts[0]) == 3 and parts[0].isdigit():
- territory = parts.pop(0)
- if parts:
- if len(parts[0]) == 4 and parts[0][0].isdigit() or \
- len(parts[0]) >= 5 and parts[0][0].isalpha():
- variant = parts.pop()
- if parts:
- raise ValueError('%r is not a valid locale identifier' % identifier)
- return lang, territory, script, variant
- def get_locale_identifier(tup, sep='_'):
- """The reverse of :func:`parse_locale`. It creates a locale identifier out
- of a ``(language, territory, script, variant)`` tuple. Items can be set to
- ``None`` and trailing ``None``\\s can also be left out of the tuple.
- >>> get_locale_identifier(('de', 'DE', None, '1999'))
- 'de_DE_1999'
- .. versionadded:: 1.0
- :param tup: the tuple as returned by :func:`parse_locale`.
- :param sep: the separator for the identifier.
- """
- tup = tuple(tup[:4])
- lang, territory, script, variant = tup + (None,) * (4 - len(tup))
- return sep.join(filter(None, (lang, script, territory, variant)))
|