[3] | 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 |
---|