11"translation helper functions"
22
3- import os , re , sys
3+ import locale
4+ import os
5+ import re
6+ import sys
47import gettext as gettext_module
58from cStringIO import StringIO
69from django .utils .functional import lazy
@@ -25,15 +28,25 @@ def currentThread():
2528# The default translation is based on the settings file.
2629_default = None
2730
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 .
3033_accepted = {}
3134
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 ):
3343 "Turns a language name (en-us) into a locale name (en_US)."
3444 p = language .find ('-' )
3545 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 ()
3750 else :
3851 return language .lower ()
3952
@@ -297,46 +310,40 @@ def get_language_from_request(request):
297310 if lang_code in supported and lang_code is not None and check_for_language (lang_code ):
298311 return lang_code
299312
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 ):
302315 return lang_code
303316
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
340347
341348 return settings .LANGUAGE_CODE
342349
@@ -457,3 +464,23 @@ def templatize(src):
457464 else :
458465 out .write (blankout (t .contents , 'X' ))
459466 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