django-robot-locale / robot_locale /

import os
import re
import urlparse

from django.conf import settings
from django.utils import translation
from django.utils.cache import patch_vary_headers
from django.utils.importlib import import_module

LOCALES = set([settings.LANGUAGE_CODE])
# look for additional available languages of the project
if settings.SETTINGS_MODULE is not None:    # (as in django.utils.translation.translation())
    parts = settings.SETTINGS_MODULE.split('.')
    # This way works as well:
    # projectpath = os.path.join(imp.find_module(parts[0])[1], 'locale')
    # But is not the preferred one because:
    # 1. Having the parent directory in the Python path is mandatory, which is usually
    #    forgotten in development environment (django has a trick to
    #    compensate for this missing)
    # 2. The filesystem is accessed even though this module is already loaded
    #    at this stage
    # 3. Importing an already imported module is faster
    project = import_module(parts[0])
    projectpath = os.path.join(os.path.dirname(project.__file__), 'locale')
    for file in os.listdir(projectpath):
        if os.path.isdir(os.path.join(projectpath, file)) and os.path.exists(os.path.join(projectpath, file, 'LC_MESSAGES', '')):
LOCALES = tuple(getattr(settings, 'ROBOT_LOCALE_LANGUAGES', LOCALES))
SETLANG_ENDS = getattr(settings, 'ROBOT_LOCALE_SETLANG_ENDS', '/setlang/') # defaults to what is in django.conf.urls.i18n
excludes = getattr(settings, 'ROBOT_LOCALE_EXCLUDES')   # a dict, a string, or an iterable of strings
if isinstance(excludes, dict):
    exact = excludes.get('exact')
    start = excludes.get('start')
    exact = excludes
    start = None
if isinstance(exact, basestring): exact = [exact]
if isinstance(start, basestring): start = [start]
if exact:
    EXCLUDES = [ i+'\B' for i in exact]
if start:

# language prefix detection
PREFIX_RE = re.compile(r'^/(?P<locale>{0})(?P<path>.*)$'.format('|'.join(LOCALES)))
# in redirect headers, to add the prefix, if not already there
LOCATION_ADD_RE = re.compile(r'^(/)(?!({0}))(.*$)'.format('|'.join([l + '/' for l in LOCALES])), re.IGNORECASE)
# in redirect headers, to substract the prefix, if present
LOCATION_SUB_RE = re.compile(r'^(/)({0})(.*$)'.format('|'.join([l + '/' for l in LOCALES])), re.IGNORECASE)
# spot urls to enrich on output
URLS_RE = re.compile(r'(<(?:a[^>]+href|form[^>]+action)=["\'])(?=/)(?!({0}))([^>]*>)'.format('|'.join(['/'+ l + '/' for l in LOCALES]+EXCLUDES)), re.IGNORECASE)

def split_path(path):
    Try to split the path in two parts: an language prefix and a final path.
    Returns them as a tuple.
    '/'        ->  ''  , '/'
    '/foo/'    ->  ''  , '/foo/'
    '/frfoo/'  ->  ''  , '/frfoo/'
    '/fr'      ->  'fr', '/'
    '/fr/foo/' ->  'fr', '/foo/'
    m = PREFIX_RE.match(path)
    if m:
        real_path ='path') or '/'
        if real_path.startswith('/'):
            return'locale'), real_path
    return '', path

class RobotLocaleMiddleware(object):
    Middleware that sets the language if a possible supported language prefix
    is found in the request path. If so, this prefix is stripped of the path
    and it will be added to URLs at the response step.
    For example, the path '/en/foo/' will set request.LANGUAGE_CODE to 'en'
    and request.path_info to '/foo/'. On output a '<a href="/page/">' will be
    changed to '<a href="/en/page/">'.

    def process_request(self, request):
        locale, path = split_path(request.path_info)
        self.locale = locale
        if locale:
            request.path_info = path
            request.LANGUAGE_CODE = translation.get_language()

    def process_response(self, request, response):
        if self.locale:
            patch_vary_headers(response, ('Accept-Language',))
            if 'Content-Language' not in response:
                response['Content-Language'] = translation.get_language()
            if (response.status_code == 301 or response.status_code == 302):
                sr = urlparse.urlsplit(response['Location'])
                path = sr.path
                if request.method == 'POST' and request.path_info.endswith(SETLANG_ENDS):
                    # django.views.i18n.set_language uses HTTP_REFERER, which is an absolute URL with domain name
                    path = LOCATION_SUB_RE.sub(r'\1\3', path)
                    path = LOCATION_ADD_RE.sub(r'\1{0}/\3'.format(self.locale), path)
                response['Location'] = urlparse.urlunsplit((sr.scheme,sr.netloc,path,sr.query,sr.fragment))
                response.content = URLS_RE.sub(r'\1/{0}\3'.format(self.locale), response.content)
        return response