cmlenz@3: # -*- coding: utf-8 -*- cmlenz@3: # cmlenz@14: # Copyright (C) 2007 Edgewall Software cmlenz@3: # All rights reserved. cmlenz@3: # cmlenz@3: # This software is licensed as described in the file COPYING, which cmlenz@3: # you should have received as part of this distribution. The terms cmlenz@3: # are also available at http://babel.edgewall.org/wiki/License. cmlenz@3: # cmlenz@3: # This software consists of voluntary contributions made by many cmlenz@3: # individuals. For the exact contribution history, see the revision cmlenz@3: # history and logs, available at http://babel.edgewall.org/log/. cmlenz@3: cmlenz@3: """Core locale representation and locale data access gateway.""" cmlenz@3: cmlenz@28: from babel import localedata cmlenz@3: cmlenz@33: __all__ = ['UnknownLocaleError', 'Locale', 'negotiate', 'parse'] cmlenz@3: __docformat__ = 'restructuredtext en' cmlenz@3: cmlenz@3: cmlenz@33: class UnknownLocaleError(Exception): cmlenz@33: """Exception thrown when a locale is requested for which no locale data cmlenz@33: is available. cmlenz@33: """ cmlenz@33: cmlenz@33: def __init__(self, identifier): cmlenz@33: """Create the exception. cmlenz@33: cmlenz@33: :param identifier: the identifier string of the unsupported locale cmlenz@33: """ cmlenz@33: Exception.__init__(self, 'unknown locale %r' % identifier) cmlenz@33: self.identifier = identifier cmlenz@33: cmlenz@33: cmlenz@3: class Locale(object): cmlenz@3: """Representation of a specific locale. cmlenz@3: cmlenz@3: >>> locale = Locale('en', territory='US') cmlenz@3: >>> repr(locale) cmlenz@3: '' cmlenz@3: >>> locale.display_name cmlenz@3: u'English (United States)' cmlenz@3: cmlenz@3: A `Locale` object can also be instantiated from a raw locale string: cmlenz@3: cmlenz@3: >>> locale = Locale.parse('en-US', sep='-') cmlenz@3: >>> repr(locale) cmlenz@3: '' cmlenz@3: cmlenz@3: `Locale` objects provide access to a collection of locale data, such as cmlenz@3: territory and language names, number and date format patterns, and more: cmlenz@3: cmlenz@3: >>> locale.number_symbols['decimal'] cmlenz@3: u'.' cmlenz@33: cmlenz@33: If a locale is requested for which no locale data is available, an cmlenz@33: `UnknownLocaleError` is raised: cmlenz@33: cmlenz@33: >>> Locale.parse('en_DE') cmlenz@33: Traceback (most recent call last): cmlenz@33: ... cmlenz@33: UnknownLocaleError: unknown locale 'en_DE' cmlenz@3: cmlenz@3: :see: `IETF RFC 3066 `_ cmlenz@3: """ cmlenz@3: cmlenz@3: def __init__(self, language, territory=None, variant=None): cmlenz@3: """Initialize the locale object from the given identifier components. cmlenz@3: cmlenz@3: >>> locale = Locale('en', 'US') cmlenz@3: >>> locale.language cmlenz@3: 'en' cmlenz@3: >>> locale.territory cmlenz@3: 'US' cmlenz@3: cmlenz@3: :param language: the language code cmlenz@3: :param territory: the territory (country or region) code cmlenz@3: :param variant: the variant code cmlenz@33: :raise `UnknownLocaleError`: if no locale data is available for the cmlenz@33: requested locale cmlenz@3: """ cmlenz@3: self.language = language cmlenz@3: self.territory = territory cmlenz@3: self.variant = variant cmlenz@33: identifier = str(self) cmlenz@33: try: cmlenz@33: self._data = localedata.load(identifier) cmlenz@33: except IOError: cmlenz@33: raise UnknownLocaleError(identifier) cmlenz@3: cmlenz@3: def parse(cls, identifier, sep='_'): cmlenz@3: """Create a `Locale` instance for the given locale identifier. cmlenz@3: cmlenz@3: >>> l = Locale.parse('de-DE', sep='-') cmlenz@3: >>> l.display_name cmlenz@3: u'Deutsch (Deutschland)' cmlenz@3: cmlenz@3: If the `identifier` parameter is not a string, but actually a `Locale` cmlenz@3: object, that object is returned: cmlenz@3: cmlenz@3: >>> Locale.parse(l) cmlenz@3: cmlenz@3: cmlenz@3: :param identifier: the locale identifier string cmlenz@3: :param sep: optional component separator cmlenz@3: :return: a corresponding `Locale` instance cmlenz@3: :rtype: `Locale` cmlenz@3: :raise `ValueError`: if the string does not appear to be a valid locale cmlenz@3: identifier cmlenz@33: :raise `UnknownLocaleError`: if no locale data is available for the cmlenz@33: requested locale cmlenz@3: """ cmlenz@3: if type(identifier) is cls: cmlenz@3: return identifier cmlenz@3: return cls(*parse(identifier, sep=sep)) cmlenz@3: parse = classmethod(parse) cmlenz@3: cmlenz@3: def __repr__(self): cmlenz@3: return '' % str(self) cmlenz@3: cmlenz@3: def __str__(self): cmlenz@3: return '_'.join(filter(None, [self.language, self.territory, cmlenz@3: self.variant])) cmlenz@3: cmlenz@3: def display_name(self): cmlenz@3: retval = self.languages.get(self.language) cmlenz@3: if self.territory: cmlenz@3: variant = '' cmlenz@3: if self.variant: cmlenz@3: variant = ', %s' % self.variants.get(self.variant) cmlenz@3: retval += ' (%s%s)' % (self.territories.get(self.territory), variant) cmlenz@3: return retval cmlenz@3: display_name = property(display_name, doc="""\ cmlenz@3: The localized display name of the locale. cmlenz@3: cmlenz@3: >>> Locale('en').display_name cmlenz@3: u'English' cmlenz@3: >>> Locale('en', 'US').display_name cmlenz@3: u'English (United States)' cmlenz@3: cmlenz@3: :type: `unicode` cmlenz@3: """) cmlenz@3: cmlenz@10: #{ General Locale Display Names cmlenz@10: cmlenz@3: def languages(self): cmlenz@3: return self._data['languages'] cmlenz@3: languages = property(languages, doc="""\ cmlenz@3: Mapping of language codes to translated language names. cmlenz@3: cmlenz@3: >>> Locale('de', 'DE').languages['ja'] cmlenz@3: u'Japanisch' cmlenz@3: cmlenz@3: :type: `dict` cmlenz@3: :see: `ISO 639 `_ cmlenz@3: """) cmlenz@3: cmlenz@3: def scripts(self): cmlenz@3: return self._data['scripts'] cmlenz@3: scripts = property(scripts, doc="""\ cmlenz@3: Mapping of script codes to translated script names. cmlenz@3: cmlenz@3: >>> Locale('en', 'US').scripts['Hira'] cmlenz@3: u'Hiragana' cmlenz@3: cmlenz@3: :type: `dict` cmlenz@3: :see: `ISO 15924 `_ cmlenz@3: """) cmlenz@3: cmlenz@3: def territories(self): cmlenz@3: return self._data['territories'] cmlenz@3: territories = property(territories, doc="""\ cmlenz@3: Mapping of script codes to translated script names. cmlenz@3: cmlenz@3: >>> Locale('es', 'CO').territories['DE'] cmlenz@3: u'Alemania' cmlenz@3: cmlenz@3: :type: `dict` cmlenz@3: :see: `ISO 3166 `_ cmlenz@3: """) cmlenz@3: cmlenz@3: def variants(self): cmlenz@3: return self._data['variants'] cmlenz@3: variants = property(variants, doc="""\ cmlenz@3: Mapping of script codes to translated script names. cmlenz@3: cmlenz@3: >>> Locale('de', 'DE').variants['1901'] cmlenz@3: u'alte deutsche Rechtschreibung' cmlenz@3: cmlenz@3: :type: `dict` cmlenz@3: """) cmlenz@3: cmlenz@10: #{ Number Formatting cmlenz@10: cmlenz@28: def currencies(self): cmlenz@28: return self._data['currency_names'] cmlenz@28: currencies = property(currencies, doc="""\ cmlenz@28: Mapping of currency codes to translated currency names. cmlenz@28: cmlenz@28: >>> Locale('en').currencies['COP'] cmlenz@28: u'Colombian Peso' cmlenz@28: >>> Locale('de', 'DE').currencies['COP'] cmlenz@28: u'Kolumbianischer Peso' cmlenz@28: cmlenz@28: :type: `dict` cmlenz@28: """) cmlenz@28: cmlenz@28: def currency_symbols(self): cmlenz@28: return self._data['currency_symbols'] cmlenz@28: currency_symbols = property(currency_symbols, doc="""\ cmlenz@28: Mapping of currency codes to symbols. cmlenz@28: cmlenz@28: >>> Locale('en').currency_symbols['USD'] cmlenz@28: u'US$' cmlenz@28: >>> Locale('en', 'US').currency_symbols['USD'] cmlenz@28: u'$' cmlenz@28: cmlenz@28: :type: `dict` cmlenz@28: """) cmlenz@28: cmlenz@3: def number_symbols(self): cmlenz@3: return self._data['number_symbols'] cmlenz@3: number_symbols = property(number_symbols, doc="""\ cmlenz@3: Symbols used in number formatting. cmlenz@3: cmlenz@3: >>> Locale('fr', 'FR').number_symbols['decimal'] cmlenz@3: u',' cmlenz@3: cmlenz@3: :type: `dict` cmlenz@3: """) cmlenz@3: cmlenz@14: def decimal_formats(self): cmlenz@14: return self._data['decimal_formats'] cmlenz@14: decimal_formats = property(decimal_formats, doc="""\ cmlenz@14: Locale patterns for decimal number formatting. cmlenz@14: cmlenz@14: >>> Locale('en', 'US').decimal_formats[None] cmlenz@14: cmlenz@14: cmlenz@14: :type: `dict` cmlenz@14: """) cmlenz@14: jonas@24: def percent_formats(self): jonas@24: return self._data['percent_formats'] jonas@24: percent_formats = property(percent_formats, doc="""\ jonas@24: Locale patterns for percent number formatting. jonas@24: jonas@24: >>> Locale('en', 'US').percent_formats[None] jonas@24: jonas@24: jonas@24: :type: `dict` jonas@24: """) jonas@24: cmlenz@10: #{ Calendar Information and Date Formatting cmlenz@10: cmlenz@3: def periods(self): cmlenz@3: return self._data['periods'] cmlenz@3: periods = property(periods, doc="""\ cmlenz@3: Locale display names for day periods (AM/PM). cmlenz@3: cmlenz@3: >>> Locale('en', 'US').periods['am'] cmlenz@3: u'AM' cmlenz@3: cmlenz@3: :type: `dict` cmlenz@3: """) cmlenz@3: cmlenz@3: def days(self): cmlenz@3: return self._data['days'] cmlenz@3: days = property(days, doc="""\ cmlenz@3: Locale display names for weekdays. cmlenz@3: cmlenz@17: >>> Locale('de', 'DE').days['format']['wide'][3] cmlenz@3: u'Donnerstag' cmlenz@3: cmlenz@3: :type: `dict` cmlenz@3: """) cmlenz@3: cmlenz@3: def months(self): cmlenz@3: return self._data['months'] cmlenz@3: months = property(months, doc="""\ cmlenz@3: Locale display names for months. cmlenz@3: cmlenz@3: >>> Locale('de', 'DE').months['format']['wide'][10] cmlenz@3: u'Oktober' cmlenz@3: cmlenz@3: :type: `dict` cmlenz@3: """) cmlenz@3: cmlenz@3: def quarters(self): cmlenz@3: return self._data['quarters'] cmlenz@3: quarters = property(quarters, doc="""\ cmlenz@3: Locale display names for quarters. cmlenz@3: cmlenz@3: >>> Locale('de', 'DE').quarters['format']['wide'][1] cmlenz@3: u'1. Quartal' cmlenz@3: cmlenz@3: :type: `dict` cmlenz@3: """) cmlenz@3: cmlenz@3: def eras(self): cmlenz@3: return self._data['eras'] cmlenz@3: eras = property(eras, doc="""\ cmlenz@3: Locale display names for eras. cmlenz@3: cmlenz@3: >>> Locale('en', 'US').eras['wide'][1] cmlenz@3: u'Anno Domini' cmlenz@3: >>> Locale('en', 'US').eras['abbreviated'][0] cmlenz@3: u'BC' cmlenz@3: cmlenz@3: :type: `dict` cmlenz@3: """) cmlenz@3: cmlenz@30: def time_zones(self): cmlenz@30: return self._data['time_zones'] cmlenz@30: time_zones = property(time_zones, doc="""\ cmlenz@30: Locale display names for time zones. cmlenz@30: cmlenz@30: >>> Locale('en', 'US').time_zones['America/Los_Angeles']['long']['standard'] cmlenz@30: u'Pacific Standard Time' cmlenz@30: >>> Locale('en', 'US').time_zones['Europe/Dublin']['city'] cmlenz@30: u'Dublin' cmlenz@30: cmlenz@30: :type: `dict` cmlenz@30: """) cmlenz@30: cmlenz@10: def first_week_day(self): cmlenz@10: return self._data['week_data']['first_day'] cmlenz@10: first_week_day = property(first_week_day, doc="""\ cmlenz@10: The first day of a week. cmlenz@10: cmlenz@10: >>> Locale('de', 'DE').first_week_day cmlenz@17: 0 cmlenz@10: >>> Locale('en', 'US').first_week_day cmlenz@17: 6 cmlenz@10: cmlenz@10: :type: `int` cmlenz@10: """) cmlenz@10: cmlenz@10: def weekend_start(self): cmlenz@10: return self._data['week_data']['weekend_start'] cmlenz@10: weekend_start = property(weekend_start, doc="""\ cmlenz@10: The day the weekend starts. cmlenz@10: cmlenz@10: >>> Locale('de', 'DE').weekend_start cmlenz@17: 5 cmlenz@10: cmlenz@10: :type: `int` cmlenz@10: """) cmlenz@10: cmlenz@10: def weekend_end(self): cmlenz@10: return self._data['week_data']['weekend_end'] cmlenz@10: weekend_end = property(weekend_end, doc="""\ cmlenz@10: The day the weekend ends. cmlenz@10: cmlenz@10: >>> Locale('de', 'DE').weekend_end cmlenz@17: 6 cmlenz@10: cmlenz@10: :type: `int` cmlenz@10: """) cmlenz@10: cmlenz@10: def min_week_days(self): cmlenz@10: return self._data['week_data']['min_days'] cmlenz@10: min_week_days = property(min_week_days, doc="""\ cmlenz@10: The minimum number of days in a week so that the week is counted as the cmlenz@10: first week of a year or month. cmlenz@10: cmlenz@10: >>> Locale('de', 'DE').min_week_days cmlenz@10: 4 cmlenz@10: cmlenz@10: :type: `int` cmlenz@10: """) cmlenz@10: cmlenz@3: def date_formats(self): cmlenz@3: return self._data['date_formats'] cmlenz@3: date_formats = property(date_formats, doc="""\ cmlenz@3: Locale patterns for date formatting. cmlenz@3: cmlenz@3: >>> Locale('en', 'US').date_formats['short'] cmlenz@14: cmlenz@3: >>> Locale('fr', 'FR').date_formats['long'] cmlenz@14: cmlenz@3: cmlenz@3: :type: `dict` cmlenz@3: """) cmlenz@3: cmlenz@3: def time_formats(self): cmlenz@3: return self._data['time_formats'] cmlenz@3: time_formats = property(time_formats, doc="""\ cmlenz@3: Locale patterns for time formatting. cmlenz@3: cmlenz@3: >>> Locale('en', 'US').time_formats['short'] cmlenz@14: cmlenz@3: >>> Locale('fr', 'FR').time_formats['long'] cmlenz@14: jonas@11: jonas@11: :type: `dict` jonas@11: """) jonas@11: cmlenz@35: def datetime_formats(self): cmlenz@35: return self._data['datetime_formats'] cmlenz@35: datetime_formats = property(datetime_formats, doc="""\ cmlenz@35: Locale patterns for datetime formatting. cmlenz@35: cmlenz@35: >>> Locale('en').datetime_formats[None] cmlenz@35: u'{1} {0}' cmlenz@35: >>> Locale('th').datetime_formats[None] cmlenz@35: u'{1}, {0}' cmlenz@35: cmlenz@35: :type: `dict` cmlenz@35: """) cmlenz@35: cmlenz@3: cmlenz@3: def negotiate(preferred, available): cmlenz@3: """Find the best match between available and requested locale strings. cmlenz@3: cmlenz@3: >>> negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT']) cmlenz@3: 'de_DE' cmlenz@3: >>> negotiate(['de_DE', 'en_US'], ['en', 'de']) cmlenz@3: 'de' cmlenz@3: cmlenz@3: :param preferred: the list of locale strings preferred by the user cmlenz@3: :param available: the list of locale strings available cmlenz@3: :return: the locale identifier for the best match, or `None` if no match cmlenz@3: was found cmlenz@3: :rtype: `str` cmlenz@3: """ cmlenz@3: for locale in preferred: cmlenz@3: if locale in available: cmlenz@3: return locale cmlenz@3: parts = locale.split('_') cmlenz@3: if len(parts) > 1 and parts[0] in available: cmlenz@3: return parts[0] cmlenz@3: return None cmlenz@3: cmlenz@3: def parse(identifier, sep='_'): cmlenz@3: """Parse a locale identifier into a ``(language, territory, variant)`` cmlenz@3: tuple. cmlenz@3: cmlenz@3: >>> parse('zh_CN') cmlenz@3: ('zh', 'CN', None) cmlenz@3: cmlenz@3: The default component separator is "_", but a different separator can be cmlenz@3: specified using the `sep` parameter: cmlenz@3: cmlenz@3: >>> parse('zh-CN', sep='-') cmlenz@3: ('zh', 'CN', None) cmlenz@3: cmlenz@3: :param identifier: the locale identifier string cmlenz@3: :param sep: character that separates the different parts of the locale cmlenz@3: string cmlenz@3: :return: the ``(language, territory, variant)`` tuple cmlenz@3: :rtype: `tuple` cmlenz@3: :raise `ValueError`: if the string does not appear to be a valid locale cmlenz@3: identifier cmlenz@3: cmlenz@3: :see: `IETF RFC 3066 `_ cmlenz@3: """ cmlenz@3: parts = identifier.split(sep) cmlenz@3: lang, territory, variant = parts[0].lower(), None, None cmlenz@3: if not lang.isalpha(): cmlenz@3: raise ValueError('expected only letters, got %r' % lang) cmlenz@3: if len(parts) > 1: cmlenz@3: territory = parts[1].upper().split('.', 1)[0] cmlenz@3: if not territory.isalpha(): cmlenz@3: raise ValueError('expected only letters, got %r' % territory) cmlenz@3: if len(parts) > 2: cmlenz@3: variant = parts[2].upper().split('.', 1)[0] cmlenz@3: return lang, territory, variant