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 |
---|