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
+
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4