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
@@ -309,46 +322,40 @@ def get_language_from_request(request):
309
322
if lang_code in supported and lang_code is not None and check_for_language (lang_code ):
310
323
return lang_code
311
324
312
- lang_code = request .COOKIES .get ('django_language' , None )
313
- if lang_code in supported and lang_code is not None and check_for_language (lang_code ):
325
+ lang_code = request .COOKIES .get ('django_language' )
326
+ if lang_code and lang_code in supported and check_for_language (lang_code ):
314
327
return lang_code
315
328
316
- accept = request .META .get ('HTTP_ACCEPT_LANGUAGE' , None )
317
- if accept is not None :
318
-
319
- t = _accepted .get (accept , None )
320
- if t is not None :
321
- return t
322
-
323
- def _parsed (el ):
324
- p = el .find (';q=' )
325
- if p >= 0 :
326
- lang = el [:p ].strip ()
327
- order = int (float (el [p + 3 :].strip ())* 100 )
328
- else :
329
- lang = el
330
- order = 100
331
- p = lang .find ('-' )
332
- if p >= 0 :
333
- mainlang = lang [:p ]
334
- else :
335
- mainlang = lang
336
- return (lang , mainlang , order )
337
-
338
- langs = [_parsed (el ) for el in accept .split (',' )]
339
- langs .sort (lambda a ,b : - 1 * cmp (a [2 ], b [2 ]))
340
-
341
- for lang , mainlang , order in langs :
342
- if lang in supported or mainlang in supported :
343
- langfile = gettext_module .find ('django' , globalpath , [to_locale (lang )])
344
- if langfile :
345
- # reconstruct the actual language from the language
346
- # filename, because otherwise we might incorrectly
347
- # report de_DE if we only have de available, but
348
- # did find de_DE because of language normalization
349
- lang = langfile [len (globalpath ):].split (os .path .sep )[1 ]
350
- _accepted [accept ] = lang
351
- return lang
329
+ accept = request .META .get ('HTTP_ACCEPT_LANGUAGE' , '' )
330
+ for lang , unused in parse_accept_lang_header (accept ):
331
+ if lang == '*' :
332
+ break
333
+
334
+ # We have a very restricted form for our language files (no encoding
335
+ # specifier, since they all must be UTF-8 and only one possible
336
+ # language each time. So we avoid the overhead of gettext.find() and
337
+ # look up the MO file manually.
338
+
339
+ normalized = locale .locale_alias .get (to_locale (lang , True ))
340
+ if not normalized :
341
+ continue
342
+
343
+ # Remove the default encoding from locale_alias
344
+ normalized = normalized .split ('.' )[0 ]
345
+
346
+ if normalized in _accepted :
347
+ # We've seen this locale before and have an MO file for it, so no
348
+ # need to check again.
349
+ return _accepted [normalized ]
350
+
351
+ for lang in (normalized , normalized .split ('_' )[0 ]):
352
+ if lang not in supported :
353
+ continue
354
+ langfile = os .path .join (globalpath , lang , 'LC_MESSAGES' ,
355
+ 'django.mo' )
356
+ if os .path .exists (langfile ):
357
+ _accepted [normalized ] = lang
358
+ return lang
352
359
353
360
return settings .LANGUAGE_CODE
354
361
@@ -494,3 +501,24 @@ def string_concat(*strings):
494
501
return '' .join ([str (el ) for el in strings ])
495
502
496
503
string_concat = lazy (string_concat , str )
504
+
505
+ def parse_accept_lang_header (lang_string ):
506
+ """
507
+ Parses the lang_string, which is the body of an HTTP Accept-Language
508
+ header, and returns a list of (lang, q-value), ordered by 'q' values.
509
+
510
+ Any format errors in lang_string results in an empty list being returned.
511
+ """
512
+ result = []
513
+ pieces = accept_language_re .split (lang_string )
514
+ if pieces [- 1 ]:
515
+ return []
516
+ for i in range (0 , len (pieces ) - 1 , 3 ):
517
+ first , lang , priority = pieces [i : i + 3 ]
518
+ if first :
519
+ return []
520
+ priority = priority and float (priority ) or 1.0
521
+ result .append ((lang , priority ))
522
+ result .sort (lambda x , y : - cmp (x [1 ], y [1 ]))
523
+ return result
524
+
0 commit comments