Adding more L10n support. Middleware, Language map, and helpers.

This commit is contained in:
Austin King 2011-01-31 16:11:21 -08:00
Родитель 9aefbdced1
Коммит 5e323fdddd
5 изменённых файлов: 269 добавлений и 3 удалений

81
apps/commons/helpers.py Normal file
Просмотреть файл

@ -0,0 +1,81 @@
import cgi
import datetime
import urllib
import urlparse
from django.conf import settings
from django.template import defaultfilters
from django.utils.html import strip_tags
from jingo import register
import jinja2
from .urlresolvers import reverse
# Yanking filters from Django.
register.filter(strip_tags)
register.filter(defaultfilters.timesince)
register.filter(defaultfilters.truncatewords)
@register.function
def page_title(title):
return u'%s | Mozilla Developer Network' % title
@register.function
def thisyear():
"""The current year."""
return jinja2.Markup(datetime.date.today().year)
@register.function
def url(viewname, *args, **kwargs):
"""Helper for Django's ``reverse`` in templates."""
return reverse(viewname, args=args, kwargs=kwargs)
@register.filter
def urlparams(url_, hash=None, **query):
"""
Add a fragment and/or query paramaters to a URL.
New query params will be appended to exising parameters, except duplicate
names, which will be replaced.
"""
url = urlparse.urlparse(url_)
fragment = hash if hash is not None else url.fragment
# Use dict(parse_qsl) so we don't get lists of values.
q = url.query
query_dict = dict(urlparse.parse_qsl(smart_str(q))) if q else {}
query_dict.update((k, v) for k, v in query.items())
query_string = _urlencode([(k, v) for k, v in query_dict.items()
if v is not None])
new = urlparse.ParseResult(url.scheme, url.netloc, url.path, url.params,
query_string, fragment)
return new.geturl()
def _urlencode(items):
"""A Unicode-safe URLencoder."""
try:
return urllib.urlencode(items)
except UnicodeEncodeError:
return urllib.urlencode([(k, smart_str(v)) for k, v in items])
@register.filter
def urlencode(txt):
"""Url encode a path."""
return urllib.quote_plus(txt)
@register.function
def devmo_url(path):
""" Create a URL pointing to devmo.
Currently a no-op from when staging was on several servers.
Might be a useful shim in the future.
"""
return path

Просмотреть файл

@ -0,0 +1,58 @@
"""
Taken from zamboni.amo.middleware.
This is django-localeurl, but with mozilla style capital letters in
the locale codes.
"""
import urllib
from django.http import HttpResponsePermanentRedirect
from django.utils.encoding import smart_str
import tower
from . import urlresolvers
from .helpers import urlparams
class LocaleURLMiddleware(object):
"""
1. Search for the locale.
2. Save it in the request.
3. Strip them from the URL.
"""
def process_request(self, request):
prefixer = urlresolvers.Prefixer(request)
urlresolvers.set_url_prefix(prefixer)
full_path = prefixer.fix(prefixer.shortened_path)
if 'lang' in request.GET:
# Blank out the locale so that we can set a new one. Remove lang
# from the query params so we don't have an infinite loop.
prefixer.locale = ''
new_path = prefixer.fix(prefixer.shortened_path)
query = dict((smart_str(k), request.GET[k]) for k in request.GET)
query.pop('lang')
return HttpResponsePermanentRedirect(urlparams(new_path, **query))
if full_path != request.path:
query_string = request.META.get('QUERY_STRING', '')
full_path = urllib.quote(full_path.encode('utf-8'))
if query_string:
full_path = '%s?%s' % (full_path, query_string)
response = HttpResponsePermanentRedirect(full_path)
# Vary on Accept-Language if we changed the locale
old_locale = prefixer.locale
new_locale, _ = prefixer.split_path(full_path)
if old_locale != new_locale:
response['Vary'] = 'Accept-Language'
return response
request.path_info = '/' + prefixer.shortened_path
request.locale = prefixer.locale
tower.activate(prefixer.locale)

Просмотреть файл

@ -0,0 +1,109 @@
from django.conf import settings
from django.core.urlresolvers import reverse as django_reverse
from django.utils.thread_support import currentThread
from django.utils.translation.trans_real import parse_accept_lang_header
# Thread-local storage for URL prefixes. Access with (get|set)_url_prefix.
_prefixes = {}
def set_url_prefix(prefix):
"""Set the ``prefix`` for the current thread."""
_prefixes[currentThread()] = prefix
def get_url_prefix():
"""Get the prefix for the current thread, or None."""
return _prefixes.get(currentThread())
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None):
"""Wraps Django's reverse to prepend the correct locale."""
prefixer = get_url_prefix()
if prefixer:
prefix = prefix or '/'
url = django_reverse(viewname, urlconf, args, kwargs, prefix)
if prefixer:
return prefixer.fix(url)
else:
return url
def find_supported(test):
return [settings.LANGUAGE_URL_MAP[x] for
x in settings.LANGUAGE_URL_MAP if
x.split('-', 1)[0] == test.lower().split('-', 1)[0]]
class Prefixer(object):
def __init__(self, request):
self.request = request
split = self.split_path(request.path_info)
self.locale, self.shortened_path = split
def split_path(self, path_):
"""
Split the requested path into (locale, path).
locale will be empty if it isn't found.
"""
path = path_.lstrip('/')
# Use partitition instead of split since it always returns 3 parts
first, _, rest = path.partition('/')
lang = first.lower()
if lang in settings.LANGUAGE_URL_MAP:
return settings.LANGUAGE_URL_MAP[lang], rest
else:
supported = find_supported(first)
if len(supported):
return supported[0], rest
else:
return '', path
def get_language(self):
"""
Return a locale code we support on the site using the
user's Accept-Language header to determine which is best. This
mostly follows the RFCs but read bug 439568 for details.
"""
if 'lang' in self.request.GET:
lang = self.request.GET['lang'].lower()
if lang in settings.LANGUAGE_URL_MAP:
return settings.LANGUAGE_URL_MAP[lang]
if self.request.META.get('HTTP_ACCEPT_LANGUAGE'):
ranked_languages = parse_accept_lang_header(
self.request.META['HTTP_ACCEPT_LANGUAGE'])
# Do we support or remap their locale?
supported = [lang[0] for lang in ranked_languages if lang[0]
in settings.LANGUAGE_URL_MAP]
# Do we support a less specific locale? (xx-YY -> xx)
if not len(supported):
for lang in ranked_languages:
supported = find_supported(lang[0])
if supported:
break
if len(supported):
return settings.LANGUAGE_URL_MAP[supported[0].lower()]
return settings.LANGUAGE_CODE
def fix(self, path):
path = path.lstrip('/')
url_parts = [self.request.META['SCRIPT_NAME']]
if path.partition('/')[0] not in settings.SUPPORTED_NONLOCALES:
locale = self.locale if self.locale else self.get_language()
url_parts.append(locale)
url_parts.append(path)
return '/'.join(url_parts)

Просмотреть файл

@ -19,3 +19,4 @@ django-celery
# L10n
Babel>=0.9.4
-e git://github.com/clouserw/tower.git#egg=tower
-e git://github.com/fwenzel/django-mozilla-product-details#egg=django-mozilla-product-details

Просмотреть файл

@ -2,6 +2,8 @@
import os
from django.utils.functional import lazy
# Make file paths relative to settings.
ROOT = os.path.dirname(os.path.abspath(__file__))
path = lambda *a: os.path.join(ROOT, *a)
@ -47,14 +49,25 @@ TEXT_DOMAIN = 'messages'
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-US'
# List of locales known to this project.
LANGUAGES = ('en-US',)
# Accepted locales
KNOWN_LANGUAGES = ('en-US',)
# List of RTL locales known to this project. Subset of LANGUAGES.
RTL_LANGUAGES = () # ('ar', 'fa', 'fa-IR', 'he')
LANGUAGE_URL_MAP = dict([(i.lower(), i) for i in LANGUAGES])
LANGUAGE_URL_MAP = dict([(i.lower(), i) for i in KNOWN_LANGUAGES])
# Override Django's built-in with our native names
class LazyLangs(dict):
def __new__(self):
from product_details import product_details
return dict([(lang.lower(), product_details.languages[lang]['native'])
for lang in KNOWN_LANGUAGES])
# Where to store product details etc.
PROD_DETAILS_DIR = path('lib/product_details_json')
LANGUAGES = lazy(LazyLangs, dict)()
## Media and templates.
@ -135,6 +148,7 @@ MINIFY_BUNDLES = {
## Middlewares, apps, URL configs.
MIDDLEWARE_CLASSES = (
'commons.middleware.LocaleURLMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@ -174,6 +188,9 @@ INSTALLED_APPS = (
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',
# L10n
'product_details',
)
# Tells the extract script what files to look for L10n in and what function