Mercurial > babel > old > mirror
annotate babel/core.py @ 11:11f64b232b04
Add basic support for number format patterns.
author | jonas |
---|---|
date | Wed, 30 May 2007 21:30:14 +0000 |
parents | 0ca5dd65594f |
children | 29ef15a6fd75 |
rev | line source |
---|---|
3 | 1 # -*- coding: utf-8 -*- |
2 # | |
3 # Copyright (C) 2006 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 gateway.""" | |
15 | |
16 import pickle | |
17 from pkg_resources import resource_filename | |
18 try: | |
19 import threading | |
20 except ImportError: | |
21 import dummy_threading as threading | |
22 | |
23 __all__ = ['Locale', 'negotiate', 'parse'] | |
24 __docformat__ = 'restructuredtext en' | |
25 | |
26 | |
27 class Locale(object): | |
28 """Representation of a specific locale. | |
29 | |
30 >>> locale = Locale('en', territory='US') | |
31 >>> repr(locale) | |
32 '<Locale "en_US">' | |
33 >>> locale.display_name | |
34 u'English (United States)' | |
35 | |
36 A `Locale` object can also be instantiated from a raw locale string: | |
37 | |
38 >>> locale = Locale.parse('en-US', sep='-') | |
39 >>> repr(locale) | |
40 '<Locale "en_US">' | |
41 | |
42 `Locale` objects provide access to a collection of locale data, such as | |
43 territory and language names, number and date format patterns, and more: | |
44 | |
45 >>> locale.number_symbols['decimal'] | |
46 u'.' | |
47 | |
48 :see: `IETF RFC 3066 <http://www.ietf.org/rfc/rfc3066.txt>`_ | |
49 """ | |
50 _cache = {} | |
51 _cache_lock = threading.Lock() | |
52 | |
53 def __new__(cls, language, territory=None, variant=None): | |
54 """Create new locale object, or load it from the cache if it had already | |
55 been instantiated. | |
56 | |
57 >>> l1 = Locale('en') | |
58 >>> l2 = Locale('en') | |
59 >>> l1 is l2 | |
60 True | |
61 | |
62 :param language: the language code | |
63 :param territory: the territory (country or region) code | |
64 :param variant: the variant code | |
65 :return: new or existing `Locale` instance | |
66 :rtype: `Locale` | |
67 """ | |
68 key = (language, territory, variant) | |
69 cls._cache_lock.acquire() | |
70 try: | |
71 self = cls._cache.get(key) | |
72 if self is None: | |
73 self = super(Locale, cls).__new__(cls, language, territory, | |
74 variant) | |
75 cls._cache[key] = self | |
76 return self | |
77 finally: | |
78 self._cache_lock.release() | |
79 | |
80 def __init__(self, language, territory=None, variant=None): | |
81 """Initialize the locale object from the given identifier components. | |
82 | |
83 >>> locale = Locale('en', 'US') | |
84 >>> locale.language | |
85 'en' | |
86 >>> locale.territory | |
87 'US' | |
88 | |
89 :param language: the language code | |
90 :param territory: the territory (country or region) code | |
91 :param variant: the variant code | |
92 """ | |
93 self.language = language | |
94 self.territory = territory | |
95 self.variant = variant | |
96 self.__data = None | |
97 | |
98 def parse(cls, identifier, sep='_'): | |
99 """Create a `Locale` instance for the given locale identifier. | |
100 | |
101 >>> l = Locale.parse('de-DE', sep='-') | |
102 >>> l.display_name | |
103 u'Deutsch (Deutschland)' | |
104 | |
105 If the `identifier` parameter is not a string, but actually a `Locale` | |
106 object, that object is returned: | |
107 | |
108 >>> Locale.parse(l) | |
109 <Locale "de_DE"> | |
110 | |
111 :param identifier: the locale identifier string | |
112 :param sep: optional component separator | |
113 :return: a corresponding `Locale` instance | |
114 :rtype: `Locale` | |
115 :raise `ValueError`: if the string does not appear to be a valid locale | |
116 identifier | |
117 """ | |
118 if type(identifier) is cls: | |
119 return identifier | |
120 return cls(*parse(identifier, sep=sep)) | |
121 parse = classmethod(parse) | |
122 | |
123 def __repr__(self): | |
124 return '<Locale "%s">' % str(self) | |
125 | |
126 def __str__(self): | |
127 return '_'.join(filter(None, [self.language, self.territory, | |
128 self.variant])) | |
129 | |
130 def _data(self): | |
131 if self.__data is None: | |
132 filename = resource_filename(__name__, 'localedata/%s.dat' % self) | |
133 fileobj = open(filename, 'rb') | |
134 try: | |
135 self.__data = pickle.load(fileobj) | |
136 finally: | |
137 fileobj.close() | |
138 return self.__data | |
139 _data = property(_data) | |
140 | |
141 def display_name(self): | |
142 retval = self.languages.get(self.language) | |
143 if self.territory: | |
144 variant = '' | |
145 if self.variant: | |
146 variant = ', %s' % self.variants.get(self.variant) | |
147 retval += ' (%s%s)' % (self.territories.get(self.territory), variant) | |
148 return retval | |
149 display_name = property(display_name, doc="""\ | |
150 The localized display name of the locale. | |
151 | |
152 >>> Locale('en').display_name | |
153 u'English' | |
154 >>> Locale('en', 'US').display_name | |
155 u'English (United States)' | |
156 | |
157 :type: `unicode` | |
158 """) | |
159 | |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
160 #{ General Locale Display Names |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
161 |
3 | 162 def languages(self): |
163 return self._data['languages'] | |
164 languages = property(languages, doc="""\ | |
165 Mapping of language codes to translated language names. | |
166 | |
167 >>> Locale('de', 'DE').languages['ja'] | |
168 u'Japanisch' | |
169 | |
170 :type: `dict` | |
171 :see: `ISO 639 <http://www.loc.gov/standards/iso639-2/>`_ | |
172 """) | |
173 | |
174 def scripts(self): | |
175 return self._data['scripts'] | |
176 scripts = property(scripts, doc="""\ | |
177 Mapping of script codes to translated script names. | |
178 | |
179 >>> Locale('en', 'US').scripts['Hira'] | |
180 u'Hiragana' | |
181 | |
182 :type: `dict` | |
183 :see: `ISO 15924 <http://www.evertype.com/standards/iso15924/>`_ | |
184 """) | |
185 | |
186 def territories(self): | |
187 return self._data['territories'] | |
188 territories = property(territories, doc="""\ | |
189 Mapping of script codes to translated script names. | |
190 | |
191 >>> Locale('es', 'CO').territories['DE'] | |
192 u'Alemania' | |
193 | |
194 :type: `dict` | |
195 :see: `ISO 3166 <http://www.iso.org/iso/en/prods-services/iso3166ma/>`_ | |
196 """) | |
197 | |
198 def variants(self): | |
199 return self._data['variants'] | |
200 variants = property(variants, doc="""\ | |
201 Mapping of script codes to translated script names. | |
202 | |
203 >>> Locale('de', 'DE').variants['1901'] | |
204 u'alte deutsche Rechtschreibung' | |
205 | |
206 :type: `dict` | |
207 """) | |
208 | |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
209 #{ Number Formatting |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
210 |
3 | 211 def number_symbols(self): |
212 return self._data['number_symbols'] | |
213 number_symbols = property(number_symbols, doc="""\ | |
214 Symbols used in number formatting. | |
215 | |
216 >>> Locale('fr', 'FR').number_symbols['decimal'] | |
217 u',' | |
218 | |
219 :type: `dict` | |
220 """) | |
221 | |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
222 #{ Calendar Information and Date Formatting |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
223 |
3 | 224 def periods(self): |
225 return self._data['periods'] | |
226 periods = property(periods, doc="""\ | |
227 Locale display names for day periods (AM/PM). | |
228 | |
229 >>> Locale('en', 'US').periods['am'] | |
230 u'AM' | |
231 | |
232 :type: `dict` | |
233 """) | |
234 | |
235 def days(self): | |
236 return self._data['days'] | |
237 days = property(days, doc="""\ | |
238 Locale display names for weekdays. | |
239 | |
240 >>> Locale('de', 'DE').days['format']['wide'][4] | |
241 u'Donnerstag' | |
242 | |
243 :type: `dict` | |
244 """) | |
245 | |
246 def months(self): | |
247 return self._data['months'] | |
248 months = property(months, doc="""\ | |
249 Locale display names for months. | |
250 | |
251 >>> Locale('de', 'DE').months['format']['wide'][10] | |
252 u'Oktober' | |
253 | |
254 :type: `dict` | |
255 """) | |
256 | |
257 def quarters(self): | |
258 return self._data['quarters'] | |
259 quarters = property(quarters, doc="""\ | |
260 Locale display names for quarters. | |
261 | |
262 >>> Locale('de', 'DE').quarters['format']['wide'][1] | |
263 u'1. Quartal' | |
264 | |
265 :type: `dict` | |
266 """) | |
267 | |
268 def eras(self): | |
269 return self._data['eras'] | |
270 eras = property(eras, doc="""\ | |
271 Locale display names for eras. | |
272 | |
273 >>> Locale('en', 'US').eras['wide'][1] | |
274 u'Anno Domini' | |
275 >>> Locale('en', 'US').eras['abbreviated'][0] | |
276 u'BC' | |
277 | |
278 :type: `dict` | |
279 """) | |
280 | |
10
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
281 def first_week_day(self): |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
282 return self._data['week_data']['first_day'] |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
283 first_week_day = property(first_week_day, doc="""\ |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
284 The first day of a week. |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
285 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
286 >>> Locale('de', 'DE').first_week_day |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
287 1 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
288 >>> Locale('en', 'US').first_week_day |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
289 7 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
290 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
291 :type: `int` |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
292 """) |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
293 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
294 def weekend_start(self): |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
295 return self._data['week_data']['weekend_start'] |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
296 weekend_start = property(weekend_start, doc="""\ |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
297 The day the weekend starts. |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
298 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
299 >>> Locale('de', 'DE').weekend_start |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
300 6 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
301 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
302 :type: `int` |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
303 """) |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
304 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
305 def weekend_end(self): |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
306 return self._data['week_data']['weekend_end'] |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
307 weekend_end = property(weekend_end, doc="""\ |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
308 The day the weekend ends. |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
309 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
310 >>> Locale('de', 'DE').weekend_end |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
311 7 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
312 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
313 :type: `int` |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
314 """) |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
315 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
316 def min_week_days(self): |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
317 return self._data['week_data']['min_days'] |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
318 min_week_days = property(min_week_days, doc="""\ |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
319 The minimum number of days in a week so that the week is counted as the |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
320 first week of a year or month. |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
321 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
322 >>> Locale('de', 'DE').min_week_days |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
323 4 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
324 |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
325 :type: `int` |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
326 """) |
0ca5dd65594f
Pull in some supplemental data from the CLDR, for things like the first day of the week.
cmlenz
parents:
3
diff
changeset
|
327 |
3 | 328 def date_formats(self): |
329 return self._data['date_formats'] | |
330 date_formats = property(date_formats, doc="""\ | |
331 Locale patterns for date formatting. | |
332 | |
333 >>> Locale('en', 'US').date_formats['short'] | |
334 <DateTimeFormatPattern u'M/d/yy'> | |
335 >>> Locale('fr', 'FR').date_formats['long'] | |
336 <DateTimeFormatPattern u'd MMMM yyyy'> | |
337 | |
338 :type: `dict` | |
339 """) | |
340 | |
341 def time_formats(self): | |
342 return self._data['time_formats'] | |
343 time_formats = property(time_formats, doc="""\ | |
344 Locale patterns for time formatting. | |
345 | |
346 >>> Locale('en', 'US').time_formats['short'] | |
347 <DateTimeFormatPattern u'h:mm a'> | |
348 >>> Locale('fr', 'FR').time_formats['long'] | |
349 <DateTimeFormatPattern u'HH:mm:ss z'> | |
350 | |
351 :type: `dict` | |
352 """) | |
353 | |
11 | 354 def decimal_formats(self): |
355 return self._data['decimal_formats'] | |
356 decimal_formats = property(decimal_formats, doc="""\ | |
357 Locale patterns for decimal number formatting. | |
358 | |
359 > Locale('en', 'US').decimal_formats[None] | |
360 <NumberFormatPattern u'#,##0.###'> | |
361 | |
362 :type: `dict` | |
363 """) | |
364 | |
3 | 365 |
366 def negotiate(preferred, available): | |
367 """Find the best match between available and requested locale strings. | |
368 | |
369 >>> negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT']) | |
370 'de_DE' | |
371 >>> negotiate(['de_DE', 'en_US'], ['en', 'de']) | |
372 'de' | |
373 | |
374 :param preferred: the list of locale strings preferred by the user | |
375 :param available: the list of locale strings available | |
376 :return: the locale identifier for the best match, or `None` if no match | |
377 was found | |
378 :rtype: `str` | |
379 """ | |
380 for locale in preferred: | |
381 if locale in available: | |
382 return locale | |
383 parts = locale.split('_') | |
384 if len(parts) > 1 and parts[0] in available: | |
385 return parts[0] | |
386 return None | |
387 | |
388 def parse(identifier, sep='_'): | |
389 """Parse a locale identifier into a ``(language, territory, variant)`` | |
390 tuple. | |
391 | |
392 >>> parse('zh_CN') | |
393 ('zh', 'CN', None) | |
394 | |
395 The default component separator is "_", but a different separator can be | |
396 specified using the `sep` parameter: | |
397 | |
398 >>> parse('zh-CN', sep='-') | |
399 ('zh', 'CN', None) | |
400 | |
401 :param identifier: the locale identifier string | |
402 :param sep: character that separates the different parts of the locale | |
403 string | |
404 :return: the ``(language, territory, variant)`` tuple | |
405 :rtype: `tuple` | |
406 :raise `ValueError`: if the string does not appear to be a valid locale | |
407 identifier | |
408 | |
409 :see: `IETF RFC 3066 <http://www.ietf.org/rfc/rfc3066.txt>`_ | |
410 """ | |
411 parts = identifier.split(sep) | |
412 lang, territory, variant = parts[0].lower(), None, None | |
413 if not lang.isalpha(): | |
414 raise ValueError('expected only letters, got %r' % lang) | |
415 if len(parts) > 1: | |
416 territory = parts[1].upper().split('.', 1)[0] | |
417 if not territory.isalpha(): | |
418 raise ValueError('expected only letters, got %r' % territory) | |
419 if len(parts) > 2: | |
420 variant = parts[2].upper().split('.', 1)[0] | |
421 return lang, territory, variant |