1
1
"translation helper functions"
2
2
3
- import os , re , sys
3
+ import locale
4
+ import os
5
+ import re
6
+ import sys
4
7
import gettext as gettext_module
5
8
from cStringIO import StringIO
6
9
from django .utils .functional import lazy
@@ -25,15 +28,25 @@ def currentThread():
25
28
# The default translation is based on the settings file.
26
29
_default = None
27
30
28
- # This is a cache for accept-header to translation object mappings to prevent
29
- # the accept parser to run multiple times for one user .
31
+ # This is a cache for normalised accept-header languages to prevent multiple
32
+ # file lookups when checking the same locale on repeated requests .
30
33
_accepted = {}
31
34
32
- def to_locale (language ):
35
+ # Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9.
36
+ accept_language_re = re .compile (r'''
37
+ ([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*) # "en", "en-au", "x-y-z", "*"
38
+ (?:;q=(0(?:\.\d{,3})?|1(?:.0{,3})?))? # Optional "q=1.00", "q=0.8"
39
+ (?:\s*,\s*|$) # Multiple accepts per header.
40
+ ''' , re .VERBOSE )
41
+
42
+ def to_locale (language , to_lower = False ):
33
43
"Turns a language name (en-us) into a locale name (en_US)."
34
44
p = language .find ('-' )
35
45
if p >= 0 :
36
- return language [:p ].lower ()+ '_' + language [p + 1 :].upper ()
46
+ if to_lower :
47
+ return language [:p ].lower ()+ '_' + language [p + 1 :].lower ()
48
+ else :
49
+ return language [:p ].lower ()+ '_' + language [p + 1 :].upper ()
37
50
else :
38
51
return language .lower ()
39
52
@@ -297,46 +310,40 @@ def get_language_from_request(request):
297
310
if lang_code in supported and lang_code is not None and check_for_language (lang_code ):
298
311
return lang_code
299
312
300
- lang_code = request .COOKIES .get ('django_language' , None )
301
- if lang_code in supported and lang_code is not None and check_for_language (lang_code ):
313
+ lang_code = request .COOKIES .get ('django_language' )
314
+ if lang_code and lang_code in supported and check_for_language (lang_code ):
302
315
return lang_code
303
316
304
- accept = request .META .get ('HTTP_ACCEPT_LANGUAGE' , None )
305
- if accept is not None :
306
-
307
- t = _accepted .get (accept , None )
308
- if t is not None :
309
- return t
310
-
311
- def _parsed (el ):
312
- p = el .find (';q=' )
313
- if p >= 0 :
314
- lang = el [:p ].strip ()
315
- order = int (float (el [p + 3 :].strip ())* 100 )
316
- else :
317
- lang = el
318
- order = 100
319
- p = lang .find ('-' )
320
- if p >= 0 :
321
- mainlang = lang [:p ]
322
- else :
323
- mainlang = lang
324
- return (lang , mainlang , order )
325
-
326
- langs = [_parsed (el ) for el in accept .split (',' )]
327
- langs .sort (lambda a ,b : - 1 * cmp (a [2 ], b [2 ]))
328
-
329
- for lang , mainlang , order in langs :
330
- if lang in supported or mainlang in supported :
331
- langfile = gettext_module .find ('django' , globalpath , [to_locale (lang )])
332
- if langfile :
333
- # reconstruct the actual language from the language
334
- # filename, because otherwise we might incorrectly
335
- # report de_DE if we only have de available, but
336
- # did find de_DE because of language normalization
337
- lang = langfile [len (globalpath ):].split (os .path .sep )[1 ]
338
- _accepted [accept ] = lang
339
- return lang
317
+ accept = request .META .get ('HTTP_ACCEPT_LANGUAGE' , '' )
318
+ for lang , unused in parse_accept_lang_header (accept ):
319
+ if lang == '*' :
320
+ break
321
+
322
+ # We have a very restricted form for our language files (no encoding
323
+ # specifier, since they all must be UTF-8 and only one possible
324
+ # language each time. So we avoid the overhead of gettext.find() and
325
+ # look up the MO file manually.
326
+
327
+ normalized = locale .locale_alias .get (to_locale (lang , True ))
328
+ if not normalized :
329
+ continue
330
+
331
+ # Remove the default encoding from locale_alias
332
+ normalized = normalized .split ('.' )[0 ]
333
+
334
+ if normalized in _accepted :
335
+ # We've seen this locale before and have an MO file for it, so no
336
+ # need to check again.
337
+ return _accepted [normalized ]
338
+
339
+ for lang in (normalized , normalized .split ('_' )[0 ]):
340
+ if lang not in supported :
341
+ continue
342
+ langfile = os .path .join (globalpath , lang , 'LC_MESSAGES' ,
343
+ 'django.mo' )
344
+ if os .path .exists (langfile ):
345
+ _accepted [normalized ] = lang
346
+ return lang
340
347
341
348
return settings .LANGUAGE_CODE
342
349
@@ -457,3 +464,23 @@ def templatize(src):
457
464
else :
458
465
out .write (blankout (t .contents , 'X' ))
459
466
return out .getvalue ()
467
+
468
+ def parse_accept_lang_header (lang_string ):
469
+ """
470
+ Parses the lang_string, which is the body of an HTTP Accept-Language
471
+ header, and returns a list of (lang, q-value), ordered by 'q' values.
472
+
473
+ Any format errors in lang_string results in an empty list being returned.
474
+ """
475
+ result = []
476
+ pieces = accept_language_re .split (lang_string )
477
+ if pieces [- 1 ]:
478
+ return []
479
+ for i in range (0 , len (pieces ) - 1 , 3 ):
480
+ first , lang , priority = pieces [i : i + 3 ]
481
+ if first :
482
+ return []
483
+ priority = priority and float (priority ) or 1.0
484
+ result .append ((lang , priority ))
485
+ result .sort (lambda x , y : - cmp (x [1 ], y [1 ]))
486
+ return result
0 commit comments