| 1 | # -*- coding: utf-8 -*- | 
|---|
| 2 | # | 
|---|
| 3 | # Copyright (C) 2007 Edgewall Software | 
|---|
| 4 | # All rights reserved. | 
|---|
| 5 | # | 
|---|
| 6 | # This software is licensed as described in the file COPYING, which | 
|---|
| 7 | # you should have received as part of this distribution. The terms | 
|---|
| 8 | # are also available at http://babel.edgewall.org/wiki/License. | 
|---|
| 9 | # | 
|---|
| 10 | # This software consists of voluntary contributions made by many | 
|---|
| 11 | # individuals. For the exact contribution history, see the revision | 
|---|
| 12 | # history and logs, available at http://babel.edgewall.org/log/. | 
|---|
| 13 |  | 
|---|
| 14 | """Core locale representation and locale data access.""" | 
|---|
| 15 |  | 
|---|
| 16 | import os | 
|---|
| 17 | import pickle | 
|---|
| 18 |  | 
|---|
| 19 | from babel import localedata | 
|---|
| 20 |  | 
|---|
| 21 | __all__ = ['UnknownLocaleError', 'Locale', 'default_locale', 'negotiate_locale', | 
|---|
| 22 | 'parse_locale'] | 
|---|
| 23 | __docformat__ = 'restructuredtext en' | 
|---|
| 24 |  | 
|---|
| 25 | _global_data = None | 
|---|
| 26 |  | 
|---|
| 27 | def get_global(key): | 
|---|
| 28 | """Return the dictionary for the given key in the global data. | 
|---|
| 29 |  | 
|---|
| 30 | The global data is stored in the ``babel/global.dat`` file and contains | 
|---|
| 31 | information independent of individual locales. | 
|---|
| 32 |  | 
|---|
| 33 | >>> get_global('zone_aliases')['UTC'] | 
|---|
| 34 | 'Etc/GMT' | 
|---|
| 35 | >>> get_global('zone_territories')['Europe/Berlin'] | 
|---|
| 36 | 'DE' | 
|---|
| 37 |  | 
|---|
| 38 | :param key: the data key | 
|---|
| 39 | :return: the dictionary found in the global data under the given key | 
|---|
| 40 | :rtype: `dict` | 
|---|
| 41 | :since: version 0.9 | 
|---|
| 42 | """ | 
|---|
| 43 | global _global_data | 
|---|
| 44 | if _global_data is None: | 
|---|
| 45 | dirname = os.path.join(os.path.dirname(__file__)) | 
|---|
| 46 | filename = os.path.join(dirname, 'global.dat') | 
|---|
| 47 | fileobj = open(filename, 'rb') | 
|---|
| 48 | try: | 
|---|
| 49 | _global_data = pickle.load(fileobj) | 
|---|
| 50 | finally: | 
|---|
| 51 | fileobj.close() | 
|---|
| 52 | return _global_data.get(key, {}) | 
|---|
| 53 |  | 
|---|
| 54 |  | 
|---|
| 55 | LOCALE_ALIASES = { | 
|---|
| 56 | 'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ', | 
|---|
| 57 | 'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES', | 
|---|
| 58 | 'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES', | 
|---|
| 59 | 'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT', | 
|---|
| 60 | 'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV', | 
|---|
| 61 | 'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL', | 
|---|
| 62 | 'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI', | 
|---|
| 63 | 'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA' | 
|---|
| 64 | } | 
|---|
| 65 |  | 
|---|
| 66 |  | 
|---|
| 67 | class UnknownLocaleError(Exception): | 
|---|
| 68 | """Exception thrown when a locale is requested for which no locale data | 
|---|
| 69 | is available. | 
|---|
| 70 | """ | 
|---|
| 71 |  | 
|---|
| 72 | def __init__(self, identifier): | 
|---|
| 73 | """Create the exception. | 
|---|
| 74 |  | 
|---|
| 75 | :param identifier: the identifier string of the unsupported locale | 
|---|
| 76 | """ | 
|---|
| 77 | Exception.__init__(self, 'unknown locale %r' % identifier) | 
|---|
| 78 | self.identifier = identifier | 
|---|
| 79 |  | 
|---|
| 80 |  | 
|---|
| 81 | class Locale(object): | 
|---|
| 82 | """Representation of a specific locale. | 
|---|
| 83 |  | 
|---|
| 84 | >>> locale = Locale('en', 'US') | 
|---|
| 85 | >>> repr(locale) | 
|---|
| 86 | '<Locale "en_US">' | 
|---|
| 87 | >>> locale.display_name | 
|---|
| 88 | u'English (United States)' | 
|---|
| 89 |  | 
|---|
| 90 | A `Locale` object can also be instantiated from a raw locale string: | 
|---|
| 91 |  | 
|---|
| 92 | >>> locale = Locale.parse('en-US', sep='-') | 
|---|
| 93 | >>> repr(locale) | 
|---|
| 94 | '<Locale "en_US">' | 
|---|
| 95 |  | 
|---|
| 96 | `Locale` objects provide access to a collection of locale data, such as | 
|---|
| 97 | territory and language names, number and date format patterns, and more: | 
|---|
| 98 |  | 
|---|
| 99 | >>> locale.number_symbols['decimal'] | 
|---|
| 100 | u'.' | 
|---|
| 101 |  | 
|---|
| 102 | If a locale is requested for which no locale data is available, an | 
|---|
| 103 | `UnknownLocaleError` is raised: | 
|---|
| 104 |  | 
|---|
| 105 | >>> Locale.parse('en_DE') | 
|---|
| 106 | Traceback (most recent call last): | 
|---|
| 107 | ... | 
|---|
| 108 | UnknownLocaleError: unknown locale 'en_DE' | 
|---|
| 109 |  | 
|---|
| 110 | :see: `IETF RFC 3066 <http://www.ietf.org/rfc/rfc3066.txt>`_ | 
|---|
| 111 | """ | 
|---|
| 112 |  | 
|---|
| 113 | def __init__(self, language, territory=None, script=None, variant=None): | 
|---|
| 114 | """Initialize the locale object from the given identifier components. | 
|---|
| 115 |  | 
|---|
| 116 | >>> locale = Locale('en', 'US') | 
|---|
| 117 | >>> locale.language | 
|---|
| 118 | 'en' | 
|---|
| 119 | >>> locale.territory | 
|---|
| 120 | 'US' | 
|---|
| 121 |  | 
|---|
| 122 | :param language: the language code | 
|---|
| 123 | :param territory: the territory (country or region) code | 
|---|
| 124 | :param script: the script code | 
|---|
| 125 | :param variant: the variant code | 
|---|
| 126 | :raise `UnknownLocaleError`: if no locale data is available for the | 
|---|
| 127 | requested locale | 
|---|
| 128 | """ | 
|---|
| 129 | self.language = language | 
|---|
| 130 | self.territory = territory | 
|---|
| 131 | self.script = script | 
|---|
| 132 | self.variant = variant | 
|---|
| 133 | self.__data = None | 
|---|
| 134 |  | 
|---|
| 135 | identifier = str(self) | 
|---|
| 136 | if not localedata.exists(identifier): | 
|---|
| 137 | raise UnknownLocaleError(identifier) | 
|---|
| 138 |  | 
|---|
| 139 | def default(cls, category=None, aliases=LOCALE_ALIASES): | 
|---|
| 140 | """Return the system default locale for the specified category. | 
|---|
| 141 |  | 
|---|
| 142 | >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']: | 
|---|
| 143 | ...     os.environ[name] = '' | 
|---|
| 144 | >>> os.environ['LANG'] = 'fr_FR.UTF-8' | 
|---|
| 145 | >>> Locale.default('LC_MESSAGES') | 
|---|
| 146 | <Locale "fr_FR"> | 
|---|
| 147 |  | 
|---|
| 148 | :param category: one of the ``LC_XXX`` environment variable names | 
|---|
| 149 | :param aliases: a dictionary of aliases for locale identifiers | 
|---|
| 150 | :return: the value of the variable, or any of the fallbacks | 
|---|
| 151 | (``LANGUAGE``, ``LC_ALL``, ``LC_CTYPE``, and ``LANG``) | 
|---|
| 152 | :rtype: `Locale` | 
|---|
| 153 | :see: `default_locale` | 
|---|
| 154 | """ | 
|---|
| 155 | return cls(default_locale(category, aliases=aliases)) | 
|---|
| 156 | default = classmethod(default) | 
|---|
| 157 |  | 
|---|
| 158 | def negotiate(cls, preferred, available, sep='_', aliases=LOCALE_ALIASES): | 
|---|
| 159 | """Find the best match between available and requested locale strings. | 
|---|
| 160 |  | 
|---|
| 161 | >>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT']) | 
|---|
| 162 | <Locale "de_DE"> | 
|---|
| 163 | >>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de']) | 
|---|
| 164 | <Locale "de"> | 
|---|
| 165 | >>> Locale.negotiate(['de_DE', 'de'], ['en_US']) | 
|---|
| 166 |  | 
|---|
| 167 | You can specify the character used in the locale identifiers to separate | 
|---|
| 168 | the differnet components. This separator is applied to both lists. Also, | 
|---|
| 169 | case is ignored in the comparison: | 
|---|
| 170 |  | 
|---|
| 171 | >>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-') | 
|---|
| 172 | <Locale "de_DE"> | 
|---|
| 173 |  | 
|---|
| 174 | :param preferred: the list of locale identifers preferred by the user | 
|---|
| 175 | :param available: the list of locale identifiers available | 
|---|
| 176 | :param aliases: a dictionary of aliases for locale identifiers | 
|---|
| 177 | :return: the `Locale` object for the best match, or `None` if no match | 
|---|
| 178 | was found | 
|---|
| 179 | :rtype: `Locale` | 
|---|
| 180 | :see: `negotiate_locale` | 
|---|
| 181 | """ | 
|---|
| 182 | identifier = negotiate_locale(preferred, available, sep=sep, | 
|---|
| 183 | aliases=aliases) | 
|---|
| 184 | if identifier: | 
|---|
| 185 | return Locale.parse(identifier, sep=sep) | 
|---|
| 186 | negotiate = classmethod(negotiate) | 
|---|
| 187 |  | 
|---|
| 188 | def parse(cls, identifier, sep='_'): | 
|---|
| 189 | """Create a `Locale` instance for the given locale identifier. | 
|---|
| 190 |  | 
|---|
| 191 | >>> l = Locale.parse('de-DE', sep='-') | 
|---|
| 192 | >>> l.display_name | 
|---|
| 193 | u'Deutsch (Deutschland)' | 
|---|
| 194 |  | 
|---|
| 195 | If the `identifier` parameter is not a string, but actually a `Locale` | 
|---|
| 196 | object, that object is returned: | 
|---|
| 197 |  | 
|---|
| 198 | >>> Locale.parse(l) | 
|---|
| 199 | <Locale "de_DE"> | 
|---|
| 200 |  | 
|---|
| 201 | :param identifier: the locale identifier string | 
|---|
| 202 | :param sep: optional component separator | 
|---|
| 203 | :return: a corresponding `Locale` instance | 
|---|
| 204 | :rtype: `Locale` | 
|---|
| 205 | :raise `ValueError`: if the string does not appear to be a valid locale | 
|---|
| 206 | identifier | 
|---|
| 207 | :raise `UnknownLocaleError`: if no locale data is available for the | 
|---|
| 208 | requested locale | 
|---|
| 209 | :see: `parse_locale` | 
|---|
| 210 | """ | 
|---|
| 211 | if isinstance(identifier, basestring): | 
|---|
| 212 | return cls(*parse_locale(identifier, sep=sep)) | 
|---|
| 213 | return identifier | 
|---|
| 214 | parse = classmethod(parse) | 
|---|
| 215 |  | 
|---|
| 216 | def __eq__(self, other): | 
|---|
| 217 | return str(self) == str(other) | 
|---|
| 218 |  | 
|---|
| 219 | def __repr__(self): | 
|---|
| 220 | return '<Locale "%s">' % str(self) | 
|---|
| 221 |  | 
|---|
| 222 | def __str__(self): | 
|---|
| 223 | return '_'.join(filter(None, [self.language, self.script, | 
|---|
| 224 | self.territory, self.variant])) | 
|---|
| 225 |  | 
|---|
| 226 | def _data(self): | 
|---|
| 227 | if self.__data is None: | 
|---|
| 228 | self.__data = localedata.LocaleDataDict(localedata.load(str(self))) | 
|---|
| 229 | return self.__data | 
|---|
| 230 | _data = property(_data) | 
|---|
| 231 |  | 
|---|
| 232 | def get_display_name(self, locale=None): | 
|---|
| 233 | """Return the display name of the locale using the given locale. | 
|---|
| 234 |  | 
|---|
| 235 | The display name will include the language, territory, script, and | 
|---|
| 236 | variant, if those are specified. | 
|---|
| 237 |  | 
|---|
| 238 | >>> Locale('zh', 'CN', script='Hans').get_display_name('en') | 
|---|
| 239 | u'Chinese (Simplified Han, China)' | 
|---|
| 240 |  | 
|---|
| 241 | :param locale: the locale to use | 
|---|
| 242 | :return: the display name | 
|---|
| 243 | """ | 
|---|
| 244 | if locale is None: | 
|---|
| 245 | locale = self | 
|---|
| 246 | locale = Locale.parse(locale) | 
|---|
| 247 | retval = locale.languages.get(self.language) | 
|---|
| 248 | if self.territory or self.script or self.variant: | 
|---|
| 249 | details = [] | 
|---|
| 250 | if self.script: | 
|---|
| 251 | details.append(locale.scripts.get(self.script)) | 
|---|
| 252 | if self.territory: | 
|---|
| 253 | details.append(locale.territories.get(self.territory)) | 
|---|
| 254 | if self.variant: | 
|---|
| 255 | details.append(locale.variants.get(self.variant)) | 
|---|
| 256 | details = filter(None, details) | 
|---|
| 257 | if details: | 
|---|
| 258 | retval += ' (%s)' % u', '.join(details) | 
|---|
| 259 | return retval | 
|---|
| 260 |  | 
|---|
| 261 | display_name = property(get_display_name, doc="""\ | 
|---|
| 262 | The localized display name of the locale. | 
|---|
| 263 |  | 
|---|
| 264 | >>> Locale('en').display_name | 
|---|
| 265 | u'English' | 
|---|
| 266 | >>> Locale('en', 'US').display_name | 
|---|
| 267 | u'English (United States)' | 
|---|
| 268 | >>> Locale('sv').display_name | 
|---|
| 269 | u'svenska' | 
|---|
| 270 |  | 
|---|
| 271 | :type: `unicode` | 
|---|
| 272 | """) | 
|---|
| 273 |  | 
|---|
| 274 | def english_name(self): | 
|---|
| 275 | return self.get_display_name(Locale('en')) | 
|---|
| 276 | english_name = property(english_name, doc="""\ | 
|---|
| 277 | The english display name of the locale. | 
|---|
| 278 |  | 
|---|
| 279 | >>> Locale('de').english_name | 
|---|
| 280 | u'German' | 
|---|
| 281 | >>> Locale('de', 'DE').english_name | 
|---|
| 282 | u'German (Germany)' | 
|---|
| 283 |  | 
|---|
| 284 | :type: `unicode` | 
|---|
| 285 | """) | 
|---|
| 286 |  | 
|---|
| 287 | #{ General Locale Display Names | 
|---|
| 288 |  | 
|---|
| 289 | def languages(self): | 
|---|
| 290 | return self._data['languages'] | 
|---|
| 291 | languages = property(languages, doc="""\ | 
|---|
| 292 | Mapping of language codes to translated language names. | 
|---|
| 293 |  | 
|---|
| 294 | >>> Locale('de', 'DE').languages['ja'] | 
|---|
| 295 | u'Japanisch' | 
|---|
| 296 |  | 
|---|
| 297 | :type: `dict` | 
|---|
| 298 | :see: `ISO 639 <http://www.loc.gov/standards/iso639-2/>`_ | 
|---|
| 299 | """) | 
|---|
| 300 |  | 
|---|
| 301 | def scripts(self): | 
|---|
| 302 | return self._data['scripts'] | 
|---|
| 303 | scripts = property(scripts, doc="""\ | 
|---|
| 304 | Mapping of script codes to translated script names. | 
|---|
| 305 |  | 
|---|
| 306 | >>> Locale('en', 'US').scripts['Hira'] | 
|---|
| 307 | u'Hiragana' | 
|---|
| 308 |  | 
|---|
| 309 | :type: `dict` | 
|---|
| 310 | :see: `ISO 15924 <http://www.evertype.com/standards/iso15924/>`_ | 
|---|
| 311 | """) | 
|---|
| 312 |  | 
|---|
| 313 | def territories(self): | 
|---|
| 314 | return self._data['territories'] | 
|---|
| 315 | territories = property(territories, doc="""\ | 
|---|
| 316 | Mapping of script codes to translated script names. | 
|---|
| 317 |  | 
|---|
| 318 | >>> Locale('es', 'CO').territories['DE'] | 
|---|
| 319 | u'Alemania' | 
|---|
| 320 |  | 
|---|
| 321 | :type: `dict` | 
|---|
| 322 | :see: `ISO 3166 <http://www.iso.org/iso/en/prods-services/iso3166ma/>`_ | 
|---|
| 323 | """) | 
|---|
| 324 |  | 
|---|
| 325 | def variants(self): | 
|---|
| 326 | return self._data['variants'] | 
|---|
| 327 | variants = property(variants, doc="""\ | 
|---|
| 328 | Mapping of script codes to translated script names. | 
|---|
| 329 |  | 
|---|
| 330 | >>> Locale('de', 'DE').variants['1901'] | 
|---|
| 331 | u'Alte deutsche Rechtschreibung' | 
|---|
| 332 |  | 
|---|
| 333 | :type: `dict` | 
|---|
| 334 | """) | 
|---|
| 335 |  | 
|---|
| 336 | #{ Number Formatting | 
|---|
| 337 |  | 
|---|
| 338 | def currencies(self): | 
|---|
| 339 | return self._data['currency_names'] | 
|---|
| 340 | currencies = property(currencies, doc="""\ | 
|---|
| 341 | Mapping of currency codes to translated currency names. | 
|---|
| 342 |  | 
|---|
| 343 | >>> Locale('en').currencies['COP'] | 
|---|
| 344 | u'Colombian Peso' | 
|---|
| 345 | >>> Locale('de', 'DE').currencies['COP'] | 
|---|
| 346 | u'Kolumbianischer Peso' | 
|---|
| 347 |  | 
|---|
| 348 | :type: `dict` | 
|---|
| 349 | """) | 
|---|
| 350 |  | 
|---|
| 351 | def currency_symbols(self): | 
|---|
| 352 | return self._data['currency_symbols'] | 
|---|
| 353 | currency_symbols = property(currency_symbols, doc="""\ | 
|---|
| 354 | Mapping of currency codes to symbols. | 
|---|
| 355 |  | 
|---|
| 356 | >>> Locale('en', 'US').currency_symbols['USD'] | 
|---|
| 357 | u'$' | 
|---|
| 358 | >>> Locale('es', 'CO').currency_symbols['USD'] | 
|---|
| 359 | u'US$' | 
|---|
| 360 |  | 
|---|
| 361 | :type: `dict` | 
|---|
| 362 | """) | 
|---|
| 363 |  | 
|---|
| 364 | def number_symbols(self): | 
|---|
| 365 | return self._data['number_symbols'] | 
|---|
| 366 | number_symbols = property(number_symbols, doc="""\ | 
|---|
| 367 | Symbols used in number formatting. | 
|---|
| 368 |  | 
|---|
| 369 | >>> Locale('fr', 'FR').number_symbols['decimal'] | 
|---|
| 370 | u',' | 
|---|
| 371 |  | 
|---|
| 372 | :type: `dict` | 
|---|
| 373 | """) | 
|---|
| 374 |  | 
|---|
| 375 | def decimal_formats(self): | 
|---|
| 376 | return self._data['decimal_formats'] | 
|---|
| 377 | decimal_formats = property(decimal_formats, doc="""\ | 
|---|
| 378 | Locale patterns for decimal number formatting. | 
|---|
| 379 |  | 
|---|
| 380 | >>> Locale('en', 'US').decimal_formats[None] | 
|---|
| 381 | <NumberPattern u'#,##0.###'> | 
|---|
| 382 |  | 
|---|
| 383 | :type: `dict` | 
|---|
| 384 | """) | 
|---|
| 385 |  | 
|---|
| 386 | def currency_formats(self): | 
|---|
| 387 | return self._data['currency_formats'] | 
|---|
| 388 | currency_formats = property(currency_formats, doc=r"""\ | 
|---|
| 389 | Locale patterns for currency number formatting. | 
|---|
| 390 |  | 
|---|
| 391 | >>> print Locale('en', 'US').currency_formats[None] | 
|---|
| 392 | <NumberPattern u'\xa4#,##0.00'> | 
|---|
| 393 |  | 
|---|
| 394 | :type: `dict` | 
|---|
| 395 | """) | 
|---|
| 396 |  | 
|---|
| 397 | def percent_formats(self): | 
|---|
| 398 | return self._data['percent_formats'] | 
|---|
| 399 | percent_formats = property(percent_formats, doc="""\ | 
|---|
| 400 | Locale patterns for percent number formatting. | 
|---|
| 401 |  | 
|---|
| 402 | >>> Locale('en', 'US').percent_formats[None] | 
|---|
| 403 | <NumberPattern u'#,##0%'> | 
|---|
| 404 |  | 
|---|
| 405 | :type: `dict` | 
|---|
| 406 | """) | 
|---|
| 407 |  | 
|---|
| 408 | def scientific_formats(self): | 
|---|
| 409 | return self._data['scientific_formats'] | 
|---|
| 410 | scientific_formats = property(scientific_formats, doc="""\ | 
|---|
| 411 | Locale patterns for scientific number formatting. | 
|---|
| 412 |  | 
|---|
| 413 | >>> Locale('en', 'US').scientific_formats[None] | 
|---|
| 414 | <NumberPattern u'#E0'> | 
|---|
| 415 |  | 
|---|
| 416 | :type: `dict` | 
|---|
| 417 | """) | 
|---|
| 418 |  | 
|---|
| 419 | #{ Calendar Information and Date Formatting | 
|---|
| 420 |  | 
|---|
| 421 | def periods(self): | 
|---|
| 422 | return self._data['periods'] | 
|---|
| 423 | periods = property(periods, doc="""\ | 
|---|
| 424 | Locale display names for day periods (AM/PM). | 
|---|
| 425 |  | 
|---|
| 426 | >>> Locale('en', 'US').periods['am'] | 
|---|
| 427 | u'AM' | 
|---|
| 428 |  | 
|---|
| 429 | :type: `dict` | 
|---|
| 430 | """) | 
|---|
| 431 |  | 
|---|
| 432 | def days(self): | 
|---|
| 433 | return self._data['days'] | 
|---|
| 434 | days = property(days, doc="""\ | 
|---|
| 435 | Locale display names for weekdays. | 
|---|
| 436 |  | 
|---|
| 437 | >>> Locale('de', 'DE').days['format']['wide'][3] | 
|---|
| 438 | u'Donnerstag' | 
|---|
| 439 |  | 
|---|
| 440 | :type: `dict` | 
|---|
| 441 | """) | 
|---|
| 442 |  | 
|---|
| 443 | def months(self): | 
|---|
| 444 | return self._data['months'] | 
|---|
| 445 | months = property(months, doc="""\ | 
|---|
| 446 | Locale display names for months. | 
|---|
| 447 |  | 
|---|
| 448 | >>> Locale('de', 'DE').months['format']['wide'][10] | 
|---|
| 449 | u'Oktober' | 
|---|
| 450 |  | 
|---|
| 451 | :type: `dict` | 
|---|
| 452 | """) | 
|---|
| 453 |  | 
|---|
| 454 | def quarters(self): | 
|---|
| 455 | return self._data['quarters'] | 
|---|
| 456 | quarters = property(quarters, doc="""\ | 
|---|
| 457 | Locale display names for quarters. | 
|---|
| 458 |  | 
|---|
| 459 | >>> Locale('de', 'DE').quarters['format']['wide'][1] | 
|---|
| 460 | u'1. Quartal' | 
|---|
| 461 |  | 
|---|
| 462 | :type: `dict` | 
|---|
| 463 | """) | 
|---|
| 464 |  | 
|---|
| 465 | def eras(self): | 
|---|
| 466 | return self._data['eras'] | 
|---|
| 467 | eras = property(eras, doc="""\ | 
|---|
| 468 | Locale display names for eras. | 
|---|
| 469 |  | 
|---|
| 470 | >>> Locale('en', 'US').eras['wide'][1] | 
|---|
| 471 | u'Anno Domini' | 
|---|
| 472 | >>> Locale('en', 'US').eras['abbreviated'][0] | 
|---|
| 473 | u'BC' | 
|---|
| 474 |  | 
|---|
| 475 | :type: `dict` | 
|---|
| 476 | """) | 
|---|
| 477 |  | 
|---|
| 478 | def time_zones(self): | 
|---|
| 479 | return self._data['time_zones'] | 
|---|
| 480 | time_zones = property(time_zones, doc="""\ | 
|---|
| 481 | Locale display names for time zones. | 
|---|
| 482 |  | 
|---|
| 483 | >>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight'] | 
|---|
| 484 | u'British Summer Time' | 
|---|
| 485 | >>> Locale('en', 'US').time_zones['America/St_Johns']['city'] | 
|---|
| 486 | u"St. John's" | 
|---|
| 487 |  | 
|---|
| 488 | :type: `dict` | 
|---|
| 489 | """) | 
|---|
| 490 |  | 
|---|
| 491 | def meta_zones(self): | 
|---|
| 492 | return self._data['meta_zones'] | 
|---|
| 493 | meta_zones = property(meta_zones, doc="""\ | 
|---|
| 494 | Locale display names for meta time zones. | 
|---|
| 495 |  | 
|---|
| 496 | Meta time zones are basically groups of different Olson time zones that | 
|---|
| 497 | have the same GMT offset and daylight savings time. | 
|---|
| 498 |  | 
|---|
| 499 | >>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight'] | 
|---|
| 500 | u'Central European Summer Time' | 
|---|
| 501 |  | 
|---|
| 502 | :type: `dict` | 
|---|
| 503 | :since: version 0.9 | 
|---|
| 504 | """) | 
|---|
| 505 |  | 
|---|
| 506 | def zone_formats(self): | 
|---|
| 507 | return self._data['zone_formats'] | 
|---|
| 508 | zone_formats = property(zone_formats, doc=r"""\ | 
|---|
| 509 | Patterns related to the formatting of time zones. | 
|---|
| 510 |  | 
|---|
| 511 | >>> Locale('en', 'US').zone_formats['fallback'] | 
|---|
| 512 | u'%(1)s (%(0)s)' | 
|---|
| 513 | >>> Locale('pt', 'BR').zone_formats['region'] | 
|---|
| 514 | u'Hor\xe1rio %s' | 
|---|
| 515 |  | 
|---|
| 516 | :type: `dict` | 
|---|
| 517 | :since: version 0.9 | 
|---|
| 518 | """) | 
|---|
| 519 |  | 
|---|
| 520 | def first_week_day(self): | 
|---|
| 521 | return self._data['week_data']['first_day'] | 
|---|
| 522 | first_week_day = property(first_week_day, doc="""\ | 
|---|
| 523 | The first day of a week, with 0 being Monday. | 
|---|
| 524 |  | 
|---|
| 525 | >>> Locale('de', 'DE').first_week_day | 
|---|
| 526 | 0 | 
|---|
| 527 | >>> Locale('en', 'US').first_week_day | 
|---|
| 528 | 6 | 
|---|
| 529 |  | 
|---|
| 530 | :type: `int` | 
|---|
| 531 | """) | 
|---|
| 532 |  | 
|---|
| 533 | def weekend_start(self): | 
|---|
| 534 | return self._data['week_data']['weekend_start'] | 
|---|
| 535 | weekend_start = property(weekend_start, doc="""\ | 
|---|
| 536 | The day the weekend starts, with 0 being Monday. | 
|---|
| 537 |  | 
|---|
| 538 | >>> Locale('de', 'DE').weekend_start | 
|---|
| 539 | 5 | 
|---|
| 540 |  | 
|---|
| 541 | :type: `int` | 
|---|
| 542 | """) | 
|---|
| 543 |  | 
|---|
| 544 | def weekend_end(self): | 
|---|
| 545 | return self._data['week_data']['weekend_end'] | 
|---|
| 546 | weekend_end = property(weekend_end, doc="""\ | 
|---|
| 547 | The day the weekend ends, with 0 being Monday. | 
|---|
| 548 |  | 
|---|
| 549 | >>> Locale('de', 'DE').weekend_end | 
|---|
| 550 | 6 | 
|---|
| 551 |  | 
|---|
| 552 | :type: `int` | 
|---|
| 553 | """) | 
|---|
| 554 |  | 
|---|
| 555 | def min_week_days(self): | 
|---|
| 556 | return self._data['week_data']['min_days'] | 
|---|
| 557 | min_week_days = property(min_week_days, doc="""\ | 
|---|
| 558 | The minimum number of days in a week so that the week is counted as the | 
|---|
| 559 | first week of a year or month. | 
|---|
| 560 |  | 
|---|
| 561 | >>> Locale('de', 'DE').min_week_days | 
|---|
| 562 | 4 | 
|---|
| 563 |  | 
|---|
| 564 | :type: `int` | 
|---|
| 565 | """) | 
|---|
| 566 |  | 
|---|
| 567 | def date_formats(self): | 
|---|
| 568 | return self._data['date_formats'] | 
|---|
| 569 | date_formats = property(date_formats, doc="""\ | 
|---|
| 570 | Locale patterns for date formatting. | 
|---|
| 571 |  | 
|---|
| 572 | >>> Locale('en', 'US').date_formats['short'] | 
|---|
| 573 | <DateTimePattern u'M/d/yy'> | 
|---|
| 574 | >>> Locale('fr', 'FR').date_formats['long'] | 
|---|
| 575 | <DateTimePattern u'd MMMM yyyy'> | 
|---|
| 576 |  | 
|---|
| 577 | :type: `dict` | 
|---|
| 578 | """) | 
|---|
| 579 |  | 
|---|
| 580 | def time_formats(self): | 
|---|
| 581 | return self._data['time_formats'] | 
|---|
| 582 | time_formats = property(time_formats, doc="""\ | 
|---|
| 583 | Locale patterns for time formatting. | 
|---|
| 584 |  | 
|---|
| 585 | >>> Locale('en', 'US').time_formats['short'] | 
|---|
| 586 | <DateTimePattern u'h:mm a'> | 
|---|
| 587 | >>> Locale('fr', 'FR').time_formats['long'] | 
|---|
| 588 | <DateTimePattern u'HH:mm:ss z'> | 
|---|
| 589 |  | 
|---|
| 590 | :type: `dict` | 
|---|
| 591 | """) | 
|---|
| 592 |  | 
|---|
| 593 | def datetime_formats(self): | 
|---|
| 594 | return self._data['datetime_formats'] | 
|---|
| 595 | datetime_formats = property(datetime_formats, doc="""\ | 
|---|
| 596 | Locale patterns for datetime formatting. | 
|---|
| 597 |  | 
|---|
| 598 | >>> Locale('en').datetime_formats[None] | 
|---|
| 599 | u'{1} {0}' | 
|---|
| 600 | >>> Locale('th').datetime_formats[None] | 
|---|
| 601 | u'{1}, {0}' | 
|---|
| 602 |  | 
|---|
| 603 | :type: `dict` | 
|---|
| 604 | """) | 
|---|
| 605 |  | 
|---|
| 606 |  | 
|---|
| 607 | def default_locale(category=None, aliases=LOCALE_ALIASES): | 
|---|
| 608 | """Returns the system default locale for a given category, based on | 
|---|
| 609 | environment variables. | 
|---|
| 610 |  | 
|---|
| 611 | >>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']: | 
|---|
| 612 | ...     os.environ[name] = '' | 
|---|
| 613 | >>> os.environ['LANG'] = 'fr_FR.UTF-8' | 
|---|
| 614 | >>> default_locale('LC_MESSAGES') | 
|---|
| 615 | 'fr_FR' | 
|---|
| 616 |  | 
|---|
| 617 | The "C" or "POSIX" pseudo-locales are treated as aliases for the | 
|---|
| 618 | "en_US_POSIX" locale: | 
|---|
| 619 |  | 
|---|
| 620 | >>> os.environ['LC_MESSAGES'] = 'POSIX' | 
|---|
| 621 | >>> default_locale('LC_MESSAGES') | 
|---|
| 622 | 'en_US_POSIX' | 
|---|
| 623 |  | 
|---|
| 624 | :param category: one of the ``LC_XXX`` environment variable names | 
|---|
| 625 | :param aliases: a dictionary of aliases for locale identifiers | 
|---|
| 626 | :return: the value of the variable, or any of the fallbacks (``LANGUAGE``, | 
|---|
| 627 | ``LC_ALL``, ``LC_CTYPE``, and ``LANG``) | 
|---|
| 628 | :rtype: `str` | 
|---|
| 629 | """ | 
|---|
| 630 | varnames = (category, 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG') | 
|---|
| 631 | for name in filter(None, varnames): | 
|---|
| 632 | locale = os.getenv(name) | 
|---|
| 633 | if locale: | 
|---|
| 634 | if name == 'LANGUAGE' and ':' in locale: | 
|---|
| 635 | # the LANGUAGE variable may contain a colon-separated list of | 
|---|
| 636 | # language codes; we just pick the language on the list | 
|---|
| 637 | locale = locale.split(':')[0] | 
|---|
| 638 | if locale in ('C', 'POSIX'): | 
|---|
| 639 | locale = 'en_US_POSIX' | 
|---|
| 640 | elif aliases and locale in aliases: | 
|---|
| 641 | locale = aliases[locale] | 
|---|
| 642 | return '_'.join(filter(None, parse_locale(locale))) | 
|---|
| 643 |  | 
|---|
| 644 | def negotiate_locale(preferred, available, sep='_', aliases=LOCALE_ALIASES): | 
|---|
| 645 | """Find the best match between available and requested locale strings. | 
|---|
| 646 |  | 
|---|
| 647 | >>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT']) | 
|---|
| 648 | 'de_DE' | 
|---|
| 649 | >>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de']) | 
|---|
| 650 | 'de' | 
|---|
| 651 |  | 
|---|
| 652 | Case is ignored by the algorithm, the result uses the case of the preferred | 
|---|
| 653 | locale identifier: | 
|---|
| 654 |  | 
|---|
| 655 | >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at']) | 
|---|
| 656 | 'de_DE' | 
|---|
| 657 |  | 
|---|
| 658 | >>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at']) | 
|---|
| 659 | 'de_DE' | 
|---|
| 660 |  | 
|---|
| 661 | By default, some web browsers unfortunately do not include the territory | 
|---|
| 662 | in the locale identifier for many locales, and some don't even allow the | 
|---|
| 663 | user to easily add the territory. So while you may prefer using qualified | 
|---|
| 664 | locale identifiers in your web-application, they would not normally match | 
|---|
| 665 | the language-only locale sent by such browsers. To workaround that, this | 
|---|
| 666 | function uses a default mapping of commonly used langauge-only locale | 
|---|
| 667 | identifiers to identifiers including the territory: | 
|---|
| 668 |  | 
|---|
| 669 | >>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US']) | 
|---|
| 670 | 'ja_JP' | 
|---|
| 671 |  | 
|---|
| 672 | Some browsers even use an incorrect or outdated language code, such as "no" | 
|---|
| 673 | for Norwegian, where the correct locale identifier would actually be "nb_NO" | 
|---|
| 674 | (Bokmテ・l) or "nn_NO" (Nynorsk). The aliases are intended to take care of | 
|---|
| 675 | such cases, too: | 
|---|
| 676 |  | 
|---|
| 677 | >>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE']) | 
|---|
| 678 | 'nb_NO' | 
|---|
| 679 |  | 
|---|
| 680 | You can override this default mapping by passing a different `aliases` | 
|---|
| 681 | dictionary to this function, or you can bypass the behavior althogher by | 
|---|
| 682 | setting the `aliases` parameter to `None`. | 
|---|
| 683 |  | 
|---|
| 684 | :param preferred: the list of locale strings preferred by the user | 
|---|
| 685 | :param available: the list of locale strings available | 
|---|
| 686 | :param sep: character that separates the different parts of the locale | 
|---|
| 687 | strings | 
|---|
| 688 | :param aliases: a dictionary of aliases for locale identifiers | 
|---|
| 689 | :return: the locale identifier for the best match, or `None` if no match | 
|---|
| 690 | was found | 
|---|
| 691 | :rtype: `str` | 
|---|
| 692 | """ | 
|---|
| 693 | available = [a.lower() for a in available if a] | 
|---|
| 694 | for locale in preferred: | 
|---|
| 695 | ll = locale.lower() | 
|---|
| 696 | if ll in available: | 
|---|
| 697 | return locale | 
|---|
| 698 | if aliases: | 
|---|
| 699 | alias = aliases.get(ll) | 
|---|
| 700 | if alias: | 
|---|
| 701 | alias = alias.replace('_', sep) | 
|---|
| 702 | if alias.lower() in available: | 
|---|
| 703 | return alias | 
|---|
| 704 | parts = locale.split(sep) | 
|---|
| 705 | if len(parts) > 1 and parts[0].lower() in available: | 
|---|
| 706 | return parts[0] | 
|---|
| 707 | return None | 
|---|
| 708 |  | 
|---|
| 709 | def parse_locale(identifier, sep='_'): | 
|---|
| 710 | """Parse a locale identifier into a tuple of the form:: | 
|---|
| 711 |  | 
|---|
| 712 | ``(language, territory, script, variant)`` | 
|---|
| 713 |  | 
|---|
| 714 | >>> parse_locale('zh_CN') | 
|---|
| 715 | ('zh', 'CN', None, None) | 
|---|
| 716 | >>> parse_locale('zh_Hans_CN') | 
|---|
| 717 | ('zh', 'CN', 'Hans', None) | 
|---|
| 718 |  | 
|---|
| 719 | The default component separator is "_", but a different separator can be | 
|---|
| 720 | specified using the `sep` parameter: | 
|---|
| 721 |  | 
|---|
| 722 | >>> parse_locale('zh-CN', sep='-') | 
|---|
| 723 | ('zh', 'CN', None, None) | 
|---|
| 724 |  | 
|---|
| 725 | If the identifier cannot be parsed into a locale, a `ValueError` exception | 
|---|
| 726 | is raised: | 
|---|
| 727 |  | 
|---|
| 728 | >>> parse_locale('not_a_LOCALE_String') | 
|---|
| 729 | Traceback (most recent call last): | 
|---|
| 730 | ... | 
|---|
| 731 | ValueError: 'not_a_LOCALE_String' is not a valid locale identifier | 
|---|
| 732 |  | 
|---|
| 733 | Encoding information and locale modifiers are removed from the identifier: | 
|---|
| 734 |  | 
|---|
| 735 | >>> parse_locale('it_IT@euro') | 
|---|
| 736 | ('it', 'IT', None, None) | 
|---|
| 737 | >>> parse_locale('en_US.UTF-8') | 
|---|
| 738 | ('en', 'US', None, None) | 
|---|
| 739 | >>> parse_locale('de_DE.iso885915@euro') | 
|---|
| 740 | ('de', 'DE', None, None) | 
|---|
| 741 |  | 
|---|
| 742 | :param identifier: the locale identifier string | 
|---|
| 743 | :param sep: character that separates the different components of the locale | 
|---|
| 744 | identifier | 
|---|
| 745 | :return: the ``(language, territory, script, variant)`` tuple | 
|---|
| 746 | :rtype: `tuple` | 
|---|
| 747 | :raise `ValueError`: if the string does not appear to be a valid locale | 
|---|
| 748 | identifier | 
|---|
| 749 |  | 
|---|
| 750 | :see: `IETF RFC 4646 <http://www.ietf.org/rfc/rfc4646.txt>`_ | 
|---|
| 751 | """ | 
|---|
| 752 | if '.' in identifier: | 
|---|
| 753 | # this is probably the charset/encoding, which we don't care about | 
|---|
| 754 | identifier = identifier.split('.', 1)[0] | 
|---|
| 755 | if '@' in identifier: | 
|---|
| 756 | # this is a locale modifier such as @euro, which we don't care about | 
|---|
| 757 | # either | 
|---|
| 758 | identifier = identifier.split('@', 1)[0] | 
|---|
| 759 |  | 
|---|
| 760 | parts = identifier.split(sep) | 
|---|
| 761 | lang = parts.pop(0).lower() | 
|---|
| 762 | if not lang.isalpha(): | 
|---|
| 763 | raise ValueError('expected only letters, got %r' % lang) | 
|---|
| 764 |  | 
|---|
| 765 | script = territory = variant = None | 
|---|
| 766 | if parts: | 
|---|
| 767 | if len(parts[0]) == 4 and parts[0].isalpha(): | 
|---|
| 768 | script = parts.pop(0).title() | 
|---|
| 769 |  | 
|---|
| 770 | if parts: | 
|---|
| 771 | if len(parts[0]) == 2 and parts[0].isalpha(): | 
|---|
| 772 | territory = parts.pop(0).upper() | 
|---|
| 773 | elif len(parts[0]) == 3 and parts[0].isdigit(): | 
|---|
| 774 | territory = parts.pop(0) | 
|---|
| 775 |  | 
|---|
| 776 | if parts: | 
|---|
| 777 | if len(parts[0]) == 4 and parts[0][0].isdigit() or \ | 
|---|
| 778 | len(parts[0]) >= 5 and parts[0][0].isalpha(): | 
|---|
| 779 | variant = parts.pop() | 
|---|
| 780 |  | 
|---|
| 781 | if parts: | 
|---|
| 782 | raise ValueError('%r is not a valid locale identifier' % identifier) | 
|---|
| 783 |  | 
|---|
| 784 | return lang, territory, script, variant | 
|---|