Index: AUTHORS
===================================================================
--- AUTHORS	(revision 16384)
+++ AUTHORS	(working copy)
@@ -94,6 +94,7 @@
     Sean Brant
     Andrew Brehaut <http://brehaut.net/blog>
     David Brenneman <http://davidbrenneman.com>
+    Orne Brocaar <http://brocaar.com/>
     brut.alll@gmail.com
     bthomas
     btoll@bestweb.net
Index: docs/topics/i18n/internationalization.txt
===================================================================
--- docs/topics/i18n/internationalization.txt	(revision 16384)
+++ docs/topics/i18n/internationalization.txt	(working copy)
@@ -769,6 +769,120 @@
 cases where you really need it (for example, in conjunction with ``ngettext``
 to produce proper pluralizations).
 
+.. _url-internationalization:
+
+Specifying translation strings: In URL patterns
+===============================================
+
+..  versionadded:: 1.4
+
+Django provides two meganism to internationalize URL patterns:
+
+* Adding the language-prefix to the root of the URL patterns to make it possible
+  for the ``LocaleMiddleware`` to detect the language to activate from the requested
+  URL.
+
+* Making URL patterns translatable by using the ``ugettext_lazy()`` function.
+
+
+Language prefix in URL patterns
+-------------------------------
+
+By using the ``i18n_patterns()`` function instead of the ``patterns()`` function,
+Django will add the current active language automatically to all the URL patterns
+defined within this function. Example URL patterns::
+
+    from django.conf.urls.defaults import patterns, i18n_patterns, include, url
+    
+    urlpatterns = patterns(''
+        url(r'^sitemap\.xml$', 'sitemap.view', name='sitemap_xml'),
+    )
+    
+    news_patterns = patterns(''
+        url(r'^$', 'news.views.index', name='index'),
+        url(r'^category/(?P<slug>[\w-]+)/$', 'news.views.category', name='category'),
+        url(r'^(?P<slug>[\w-]+)/$', 'news.views.details', name='detail'),
+    )
+    
+    urlpatterns += i18n_patterns('',
+        url(r'^about/$', 'about.view', name='about'),
+        url(r'^news/$', include(news_patterns, namespace='news')),
+    )
+
+
+After defining these URL patterns, Django will automatically add the language-prefix
+to the URL patterns within the ``i18n_patterns`` function. Example::
+
+    from django.core.urlresolvers import reverse
+    from django.utils.translation import activate
+    
+    >>> activate('en')
+    >>> reverse('sitemap_xml')
+    '/sitemap.xml'
+    >>> reverse('news:index')
+    '/en/news/'
+    
+    >>> activate('nl')
+    >>> reverse('news:detail', kwargs={'slug': 'news-slug'})
+    '/nl/news/news-slug/'
+
+.. warning::
+
+    * The ``i18n_patterns()`` function is only allowed at rootlevel of the URL patterns.
+      When including a pattern which contains one or more ``i18n_patterns()`` patterns,
+      an exception will be thrown.
+    
+    * Make sure the automatically added language-prefix does not cause collision
+      with other non-prefixed URL patterns.
+
+
+Translating URL patterns
+------------------------
+
+URL patterns can be marked translatable by using the ``ugettext_lazy()`` function.
+Example::
+
+    from django.conf.urls.defaults import patterns, i18n_patterns, include, url
+    from django.utils.translation import ugettext_lazy
+    
+    urlpatterns = patterns(''
+        url(r'^sitemap\.xml$', 'sitemap.view', name='sitemap_xml'),
+    )
+    
+    news_patterns = patterns(''
+        url(r'^$', 'news.views.index', name='index'),
+        url(_(r'^category/(?P<slug>[\w-]+)/$'), 'news.views.category', name='category'),
+        url(r'^(?P<slug>[\w-]+)/$', 'news.views.details', name='detail'),
+    )
+    
+    urlpatterns += i18n_patterns('',
+        url(_(r'^about/$'), 'about.view', name='about'),
+        url(_(r'^news/$'), include(news_patterns, namespace='news')),
+    )
+
+
+After creating the translations (see :doc:`localization` for more information),
+the ``reverse()`` function will return the URL in the active language. Example::
+
+    from django.core.urlresolvers import reverse
+    from django.utils.translation import activate
+    
+    >>> activate('en')
+    >>> reverse('news:category', kwargs={'slug': 'recent'})
+    '/en/news/news-slug/'
+    
+    >>> activate('nl')
+    >>> reverse('news:category', kwargs={'slug': 'recent'})
+    '/nl/nieuws/categorie/recent/'
+
+.. warning::
+
+    Unless you are using your own middleware to detect the language to activate
+    from the requested URL, always use translatable URL patterns together with
+    the ``i18n_patterns()`` function. Django is not able to resolve a URL when
+    the correct language is not activated.
+
+
 .. _set_language-redirect-view:
 
 The ``set_language`` redirect view
Index: docs/topics/i18n/deployment.txt
===================================================================
--- docs/topics/i18n/deployment.txt	(revision 16384)
+++ docs/topics/i18n/deployment.txt	(working copy)
@@ -59,7 +59,9 @@
 
     * Make sure it's one of the first middlewares installed.
     * It should come after ``SessionMiddleware``, because ``LocaleMiddleware``
-      makes use of session data.
+      makes use of session data. As well it should come before ``CommonMiddleware``
+      because ``CommonMiddleware`` depends on a activated language when it needs
+      to resolve the requested URL.
     * If you use ``CacheMiddleware``, put ``LocaleMiddleware`` after it.
 
 For example, your :setting:`MIDDLEWARE_CLASSES` might look like this::
@@ -76,7 +78,12 @@
 ``LocaleMiddleware`` tries to determine the user's language preference by
 following this algorithm:
 
-    * First, it looks for a ``django_language`` key in the current user's
+    * First, it looks for the language-prefix in the requested URL.
+      This is only performed when you are using the ``i18n_patterns`` function.
+      See :ref:`url-internationalization` for more information about the
+      language-prefix and how to internationalize URL patterns.
+
+    * Failing that, it looks for a ``django_language`` key in the current user's
       session.
 
     * Failing that, it looks for a cookie.
Index: django/conf/urls/defaults.py
===================================================================
--- django/conf/urls/defaults.py	(revision 16384)
+++ django/conf/urls/defaults.py	(working copy)
@@ -1,6 +1,9 @@
-from django.core.urlresolvers import RegexURLPattern, RegexURLResolver
+from django.core.urlresolvers import (RegexURLPattern, RegexURLResolver,
+    LanguagePrefixedRegexURLResolver, get_resolver)
 from django.core.exceptions import ImproperlyConfigured
+from django.utils.importlib import import_module
 
+
 __all__ = ['handler404', 'handler500', 'include', 'patterns', 'url']
 
 handler404 = 'django.views.defaults.page_not_found'
@@ -15,6 +18,20 @@
     else:
         # No namespace hint - use manually provided namespace
         urlconf_module = arg
+    
+    # Test if the LanguagePrefixedRegexURLResolver is used within the include,
+    # this should throw an error since this is not allowed!
+    if isinstance(urlconf_module, basestring):
+        urlconf_module = import_module(urlconf_module)
+    patterns = getattr(urlconf_module, 'urlpatterns', urlconf_module)
+    
+    # Make sure we can iterate through the patterns (without this, some testcases
+    # will break).
+    if isinstance(patterns, (list, tuple)):
+        for url_pattern in patterns:
+            if isinstance(url_pattern, LanguagePrefixedRegexURLResolver):
+                raise ImproperlyConfigured('Using i18n_patterns inside an include is not allowed')
+    
     return (urlconf_module, app_name, namespace)
 
 def patterns(prefix, *args):
@@ -27,6 +44,14 @@
         pattern_list.append(t)
     return pattern_list
 
+def i18n_patterns(prefix, *args):
+    """
+    This will add the language-code prefix to every URLPattern within this
+    function. It is only allowed to use this at rootlevel of your URLConf.
+    """
+    pattern_list = patterns(prefix, *args)
+    return [LanguagePrefixedRegexURLResolver(pattern_list)]
+
 def url(regex, view, kwargs=None, name=None, prefix=''):
     if isinstance(view, (list,tuple)):
         # For include(...) processing.
@@ -39,4 +64,3 @@
             if prefix:
                 view = prefix + '.' + view
         return RegexURLPattern(regex, view, kwargs, name)
-
Index: django/core/urlresolvers.py
===================================================================
--- django/core/urlresolvers.py	(revision 16384)
+++ django/core/urlresolvers.py	(working copy)
@@ -18,7 +18,9 @@
 from django.utils.functional import memoize, lazy
 from django.utils.importlib import import_module
 from django.utils.regex_helper import normalize
+from django.utils.translation import get_language
 
+
 _resolver_cache = {} # Maps URLconf modules to RegexURLResolver instances.
 _callable_cache = {} # Maps view and url pattern names to their view functions.
 
@@ -117,11 +119,14 @@
 
 class RegexURLPattern(object):
     def __init__(self, regex, callback, default_args=None, name=None):
-        # regex is a string representing a regular expression.
+        # regex is eighter a string representing a regular expression, or a
+        # translatable string (using ugettext_lazy) representing a regular
+        # expression.
         # callback is either a string like 'foo.views.news.stories.story_detail'
         # which represents the path to a module and a view function name, or a
         # callable object (view).
-        self.regex = re.compile(regex, re.UNICODE)
+        self._regex = regex
+        self._i18n_regex_dict = {}
         if callable(callback):
             self._callback = callback
         else:
@@ -129,6 +134,24 @@
             self._callback_str = callback
         self.default_args = default_args or {}
         self.name = name
+    
+    @property
+    def regex(self):
+        """
+        Returns a compiled regular expression, depending upon the activated
+        language-code.
+        """
+        language_code = get_language()
+        
+        if language_code not in self._i18n_regex_dict:
+            if isinstance(self._regex, basestring):
+                compiled_regex = re.compile(self._regex, re.UNICODE)
+            else:
+                regex = unicode(self._regex)
+                compiled_regex = re.compile(regex, re.UNICODE)
+            self._i18n_regex_dict[language_code] = compiled_regex
+        
+        return self._i18n_regex_dict[language_code]
 
     def __repr__(self):
         return smart_str(u'<%s %s %s>' % (self.__class__.__name__, self.name, self.regex.pattern))
@@ -175,7 +198,8 @@
     def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
         # regex is a string representing a regular expression.
         # urlconf_name is a string representing the module containing URLconfs.
-        self.regex = re.compile(regex, re.UNICODE)
+        self._regex = regex
+        self._i18n_regex_dict = {}
         self.urlconf_name = urlconf_name
         if not isinstance(urlconf_name, basestring):
             self._urlconf_module = self.urlconf_name
@@ -183,10 +207,28 @@
         self.default_kwargs = default_kwargs or {}
         self.namespace = namespace
         self.app_name = app_name
-        self._reverse_dict = None
-        self._namespace_dict = None
-        self._app_dict = None
+        self._i18n_reverse_dict = {}
+        self._i18n_namespace_dict = {}
+        self._i18n_app_dict = {}
 
+    @property
+    def regex(self):
+        """
+        Returns a compiled regular expression, depending upon the activated
+        language-code.
+        """
+        language_code = get_language()
+        
+        if language_code not in self._i18n_regex_dict:
+            if isinstance(self._regex, basestring):
+                compiled_regex = re.compile(self._regex, re.UNICODE)
+            else:
+                regex = unicode(self._regex)
+                compiled_regex = re.compile(regex, re.UNICODE)
+            self._i18n_regex_dict[language_code] = compiled_regex
+        
+        return self._i18n_regex_dict[language_code]
+
     def __repr__(self):
         return smart_str(u'<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern))
 
@@ -220,27 +262,32 @@
                 lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
                 if pattern.name is not None:
                     lookups.appendlist(pattern.name, (bits, p_pattern, pattern.default_args))
-        self._reverse_dict = lookups
-        self._namespace_dict = namespaces
-        self._app_dict = apps
+        
+        language_code = get_language()
+        self._i18n_reverse_dict[language_code] = lookups
+        self._i18n_namespace_dict[language_code] = namespaces
+        self._i18n_app_dict[language_code] = apps
 
-    def _get_reverse_dict(self):
-        if self._reverse_dict is None:
+    @property
+    def reverse_dict(self):
+        language_code = get_language()
+        if language_code not in self._i18n_reverse_dict:
             self._populate()
-        return self._reverse_dict
-    reverse_dict = property(_get_reverse_dict)
+        return self._i18n_reverse_dict[language_code]
 
-    def _get_namespace_dict(self):
-        if self._namespace_dict is None:
+    @property
+    def namespace_dict(self):
+        language_code = get_language()
+        if language_code not in self._i18n_namespace_dict:
             self._populate()
-        return self._namespace_dict
-    namespace_dict = property(_get_namespace_dict)
+        return self._i18n_namespace_dict[language_code]
 
-    def _get_app_dict(self):
-        if self._app_dict is None:
+    @property
+    def app_dict(self):
+        language_code = get_language()
+        if language_code not in self._i18n_app_dict:
             self._populate()
-        return self._app_dict
-    app_dict = property(_get_app_dict)
+        return self._i18n_app_dict[language_code]
 
     def resolve(self, path):
         tried = []
@@ -343,6 +390,25 @@
         raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
                 "arguments '%s' not found." % (lookup_view_s, args, kwargs))
 
+class LanguagePrefixedRegexURLResolver(RegexURLResolver):
+    """
+    The ``__init__`` function does not take an regex argument. Instead, we are
+    overriding the ``regex`` function to always return the active language-code
+    as regex.
+    """
+    
+    def __init__(self, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
+        super(LanguagePrefixedRegexURLResolver, self).__init__(None, urlconf_name,
+            default_kwargs, app_name, namespace)
+
+    @property
+    def regex(self):
+        language_code = get_language()
+        if language_code not in self._i18n_regex_dict:
+            regex_compiled = re.compile('^%s/' % language_code, re.UNICODE)
+            self._i18n_regex_dict[language_code] = regex_compiled
+        return self._i18n_regex_dict[language_code]
+
 def resolve(path, urlconf=None):
     if urlconf is None:
         urlconf = get_urlconf()
Index: django/middleware/locale.py
===================================================================
--- django/middleware/locale.py	(revision 16384)
+++ django/middleware/locale.py	(working copy)
@@ -1,8 +1,16 @@
 "this is the locale selecting middleware that will look at accept headers"
 
+import re
+
+from django.conf import settings
+from django.core import urlresolvers
+from django.http import HttpResponseRedirect
 from django.utils.cache import patch_vary_headers
 from django.utils import translation
 
+
+language_code_prefix_regex = re.compile(r'^/([\w-]+)/')
+
 class LocaleMiddleware(object):
     """
     This is a very simple middleware that parses a request
@@ -13,13 +21,46 @@
     """
 
     def process_request(self, request):
-        language = translation.get_language_from_request(request)
+        language = _language_code_from_path(request.path_info)
+        
+        if not language:
+            language = translation.get_language_from_request(request)
+        
         translation.activate(language)
         request.LANGUAGE_CODE = translation.get_language()
 
     def process_response(self, request, response):
+        language = translation.get_language()
+        translation.deactivate()
+        
+        if (response.status_code == 404 and not _language_code_from_path(request.path_info)
+            and _is_language_prefix_patterns_used()):
+            prefixed_request_path = '/%s%s' % (language, request.get_full_path())
+            return HttpResponseRedirect(prefixed_request_path)
+        
         patch_vary_headers(response, ('Accept-Language',))
         if 'Content-Language' not in response:
-            response['Content-Language'] = translation.get_language()
-        translation.deactivate()
+            response['Content-Language'] = language
         return response
+
+def _language_code_from_path(path):
+    """
+    Returns the language-code if there is a valid language-code found in the
+    `path`.
+    """
+    regex_match = language_code_prefix_regex.match(path)
+    if regex_match:
+        if regex_match.group(1) in dict(settings.LANGUAGES):
+            return regex_match.group(1)
+    return None
+
+def _is_language_prefix_patterns_used():
+    """
+    Returns `True` if the `LanguagePrefixedRegexURLResolver` is used at rootlevel
+    of the urlpatterns, else it returns `False`.
+    """
+    resolver = urlresolvers.get_resolver(None)
+    for url_pattern in resolver.url_patterns:
+        if isinstance(url_pattern, urlresolvers.LanguagePrefixedRegexURLResolver):
+            return True
+    return False
Index: tests/regressiontests/i18n/wrong_urls_namespace.py
===================================================================
--- tests/regressiontests/i18n/wrong_urls_namespace.py	(revision 0)
+++ tests/regressiontests/i18n/wrong_urls_namespace.py	(revision 0)
@@ -0,0 +1,10 @@
+from django.conf.urls.defaults import i18n_patterns, include, url
+from django.utils.translation import ugettext_lazy as _
+from django.views.generic import TemplateView
+
+
+view = TemplateView.as_view(template_name='dummy.html')
+
+urlpatterns = i18n_patterns('',
+    url(_(r'^register/$'), view, name='register'),
+)
Index: tests/regressiontests/i18n/urls_namespace.py
===================================================================
--- tests/regressiontests/i18n/urls_namespace.py	(revision 0)
+++ tests/regressiontests/i18n/urls_namespace.py	(revision 0)
@@ -0,0 +1,10 @@
+from django.conf.urls.defaults import patterns, include, url
+from django.utils.translation import ugettext_lazy as _
+from django.views.generic import TemplateView
+
+
+view = TemplateView.as_view(template_name='dummy.html')
+
+urlpatterns = patterns('',
+    url(_(r'^register/$'), view, name='register'),
+)
Index: tests/regressiontests/i18n/wrong_urls.py
===================================================================
--- tests/regressiontests/i18n/wrong_urls.py	(revision 0)
+++ tests/regressiontests/i18n/wrong_urls.py	(revision 0)
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import patterns, i18n_patterns, include, url
+from django.utils.translation import ugettext_lazy as _
+
+
+urlpatterns = i18n_patterns('',
+    url(_(r'^account/'), include('regressiontests.i18n.wrong_urls_namespace', namespace='account')),
+)
Index: tests/regressiontests/i18n/tests.py
===================================================================
--- tests/regressiontests/i18n/tests.py	(revision 16384)
+++ tests/regressiontests/i18n/tests.py	(working copy)
@@ -8,8 +8,11 @@
 from threading import local
 
 from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.urlresolvers import reverse, get_resolver, clear_url_caches
 from django.template import Template, Context
 from django.test import TestCase
+from django.test.utils import override_settings
 from django.utils.formats import (get_format, date_format, time_format,
     localize, localize_input, iter_format_modules, get_format_modules)
 from django.utils.importlib import import_module
@@ -828,3 +831,217 @@
         with translation.override('nl'):
             self.assertEqual(t.render(Context({})), 'Nee')
 
+
+class URLTestCaseBase(TestCase):
+    """
+    TestCase base-class for the URL tests.
+    """
+    def setUp(self):
+        # Make sure the cache is empty before we are doing our tests.
+        clear_url_caches()
+    
+    def tearDown(self):
+        # Make sure we will leave an empty cache for other testcases.
+        clear_url_caches()
+
+
+URLTestCaseBase = override_settings(
+    ROOT_URLCONF='regressiontests.i18n.urls',
+    LOCALE_PATHS=(os.path.join(os.path.dirname(__file__), 'test_locale'), ),
+    TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'test_templates'), ),
+    LANGUAGE_CODE='en',
+    LANGUAGES=(
+        ('nl', 'Dutch'),
+        ('en', 'English'),
+        ('pt-br', 'Brazilian Portuguese'),
+    ),
+    MIDDLEWARE_CLASSES=(
+        'django.middleware.locale.LocaleMiddleware',
+        'django.middleware.common.CommonMiddleware',
+    ),
+)(URLTestCaseBase)
+
+
+class URLPrefixTests(URLTestCaseBase):
+    """
+    Tests if the `i18n_patterns` is adding the prefix correctly.
+    """
+    def test_not_prefixed(self):
+        with translation.override('en'):
+            self.assertEqual(reverse('not-prefixed'), '/not-prefixed/')
+        with translation.override('nl'):
+            self.assertEqual(reverse('not-prefixed'), '/not-prefixed/')
+
+    def test_prefixed(self):
+        with translation.override('en'):
+            self.assertEqual(reverse('prefixed'), '/en/prefixed/')
+        with translation.override('nl'):
+            self.assertEqual(reverse('prefixed'), '/nl/prefixed/')
+    
+    @override_settings(ROOT_URLCONF='regressiontests.i18n.wrong_urls')
+    def test_invalid_prefix_use(self):
+        self.assertRaises(ImproperlyConfigured, lambda: reverse('account:register'))
+
+
+class URLTranslationTests(URLTestCaseBase):
+    """
+    Tests if the pattern-strings are translated correctly (within the
+    `i18n_patterns` and the normal `patterns` function).
+    """
+    def test_no_prefix_translated(self):
+        with translation.override('en'):
+            self.assertEqual(reverse('no-prefix-translated'), '/translated/')
+
+        with translation.override('nl'):
+            self.assertEqual(reverse('no-prefix-translated'), '/vertaald/')
+
+        with translation.override('pt-br'):
+            self.assertEqual(reverse('no-prefix-translated'), '/traduzidos/')
+
+    def test_users_url(self):
+        with translation.override('en'):
+            self.assertEqual(reverse('users'), '/en/users/')
+
+        with translation.override('nl'):
+            self.assertEqual(reverse('users'), '/nl/gebruikers/')
+
+        with translation.override('pt-br'):
+            self.assertEqual(reverse('users'), '/pt-br/usuarios/')
+
+
+class URLNamespaceTests(URLTestCaseBase):
+    """
+    Tests if the translations are still working within namespaces.
+    """
+    def test_account_register(self):
+        with translation.override('en'):
+            self.assertEqual(reverse('account:register'), '/en/account/register/')
+
+        with translation.override('nl'):
+            self.assertEqual(reverse('account:register'), '/nl/profiel/registeren/')
+
+
+class URLRedirectTests(URLTestCaseBase):
+    """
+    Tests if the user gets redirected to the right URL when there is no
+    language-prefix in the request URL.
+    """
+    def test_no_prefix_response(self):
+        response = self.client.get('/not-prefixed/')
+        self.assertEqual(response.status_code, 200)
+
+    def test_en_redirect(self):
+        response = self.client.get('/account/register/', HTTP_ACCEPT_LANGUAGE='en')
+        self.assertRedirects(response, 'http://testserver/en/account/register/')
+
+        response = self.client.get(response['location'])
+        self.assertEqual(response.status_code, 200)
+
+    def test_en_redirect_wrong_url(self):
+        response = self.client.get('/profiel/registeren/', HTTP_ACCEPT_LANGUAGE='en')
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response['location'], 'http://testserver/en/profiel/registeren/')
+
+        response = self.client.get(response['location'])
+        self.assertEqual(response.status_code, 404)
+
+    def test_nl_redirect(self):
+        response = self.client.get('/profiel/registeren/', HTTP_ACCEPT_LANGUAGE='nl')
+        self.assertRedirects(response, 'http://testserver/nl/profiel/registeren/')
+
+        response = self.client.get(response['location'])
+        self.assertEqual(response.status_code, 200)
+
+    def test_nl_redirect_wrong_url(self):
+        response = self.client.get('/account/register/', HTTP_ACCEPT_LANGUAGE='nl')
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response['location'], 'http://testserver/nl/account/register/')
+
+        response = self.client.get(response['location'])
+        self.assertEqual(response.status_code, 404)
+
+    def test_pt_br_redirect(self):
+        response = self.client.get('/conta/registre-se/', HTTP_ACCEPT_LANGUAGE='pt-br')
+        self.assertRedirects(response, 'http://testserver/pt-br/conta/registre-se/')
+
+        response = self.client.get(response['location'])
+        self.assertEqual(response.status_code, 200)
+
+
+class URLRedirectWithoutTrailingSlashTests(URLTestCaseBase):
+    """
+    Tests the redirect when the requested URL doesn't end with a slash
+    (`settings.APPEND_SLASH=True`).
+    """
+    def test_not_prefixed_redirect(self):
+        response = self.client.get('/not-prefixed', HTTP_ACCEPT_LANGUAGE='en')
+        self.assertEqual(response.status_code, 301)
+        self.assertEqual(response['location'], 'http://testserver/not-prefixed/')
+
+    def test_en_redirect(self):
+        response = self.client.get('/account/register', HTTP_ACCEPT_LANGUAGE='en')
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response['location'], 'http://testserver/en/account/register')
+
+        response = self.client.get(response['location'])
+        self.assertEqual(response.status_code, 301)
+        self.assertEqual(response['location'], 'http://testserver/en/account/register/')
+
+
+class URLRedirectWithoutTrailingSlashSettingTests(URLTestCaseBase):
+    """
+    Tests the redirect when the requested URL doesn't end with a slash
+    (`settings.APPEND_SLASH=False`).
+    """
+    @override_settings(APPEND_SLASH=False)
+    def test_not_prefixed_redirect(self):
+        response = self.client.get('/not-prefixed', HTTP_ACCEPT_LANGUAGE='en')
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response['location'], 'http://testserver/en/not-prefixed')
+
+        response = self.client.get(response['location'])
+        self.assertEqual(response.status_code, 404)
+
+    @override_settings(APPEND_SLASH=False)
+    def test_en_redirect(self):
+        response = self.client.get('/account/register', HTTP_ACCEPT_LANGUAGE='en')
+        self.assertEqual(response.status_code, 302)
+        self.assertEqual(response['location'], 'http://testserver/en/account/register')
+
+        response = self.client.get(response['location'])
+        self.assertEqual(response.status_code, 404)
+
+
+class URLResponseTests(URLTestCaseBase):
+    """
+    Tests if the response has the right language-code.
+    """
+    def test_not_prefixed_with_prefix(self):
+        response = self.client.get('/en/not-prefixed/')
+        self.assertEqual(response.status_code, 404)
+
+    def test_en_url(self):
+        response = self.client.get('/en/account/register/')
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response['content-language'], 'en')
+        self.assertEqual(response.context['LANGUAGE_CODE'], 'en')
+
+    def test_nl_url(self):
+        response = self.client.get('/nl/profiel/registeren/')
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response['content-language'], 'nl')
+        self.assertEqual(response.context['LANGUAGE_CODE'], 'nl')
+
+    def test_wrong_en_prefix(self):
+        response = self.client.get('/en/profiel/registeren/')
+        self.assertEqual(response.status_code, 404)
+
+    def test_wrong_nl_prefix(self):
+        response = self.client.get('/nl/account/register/')
+        self.assertEqual(response.status_code, 404)
+
+    def test_pt_br_url(self):
+        response = self.client.get('/pt-br/conta/registre-se/')
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response['content-language'], 'pt-br')
+        self.assertEqual(response.context['LANGUAGE_CODE'], 'pt-br')
Index: tests/regressiontests/i18n/urls.py
===================================================================
--- tests/regressiontests/i18n/urls.py	(revision 0)
+++ tests/regressiontests/i18n/urls.py	(revision 0)
@@ -0,0 +1,17 @@
+from django.conf.urls.defaults import patterns, i18n_patterns, include, url
+from django.utils.translation import ugettext_lazy as _
+from django.views.generic import TemplateView
+
+
+view = TemplateView.as_view(template_name='dummy.html')
+
+urlpatterns = patterns('',
+    url(r'^not-prefixed/$', view, name='not-prefixed'),
+    url(_(r'^translated/$'), view, name='no-prefix-translated'),
+)
+
+urlpatterns += i18n_patterns('',
+    url(r'^prefixed/$', view, name='prefixed'),
+    url(_(r'^users/$'), view, name='users'),
+    url(_(r'^account/'), include('regressiontests.i18n.urls_namespace', namespace='account')),
+)
Index: tests/regressiontests/i18n/test_templates/404.html
===================================================================
Index: tests/regressiontests/i18n/test_templates/dummy.html
===================================================================
Index: tests/regressiontests/i18n/test_locale/en/LC_MESSAGES/django.po
===================================================================
--- tests/regressiontests/i18n/test_locale/en/LC_MESSAGES/django.po	(revision 0)
+++ tests/regressiontests/i18n/test_locale/en/LC_MESSAGES/django.po	(revision 0)
@@ -0,0 +1,34 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-06-10 11:53+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: urls.py:10
+msgid "^translated/$"
+msgstr "^translated/$"
+
+#: urls.py:15
+msgid "^users/$"
+msgstr "^users/$"
+
+#: urls.py:16
+msgid "^account/"
+msgstr "^account/"
+
+#: urls_namespace.py:9
+msgid "^register/$"
+msgstr "^register/$"
Index: tests/regressiontests/i18n/test_locale/en/LC_MESSAGES/django.mo
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: tests/regressiontests/i18n/test_locale/en/LC_MESSAGES/django.mo
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: tests/regressiontests/i18n/test_locale/pt_BR/LC_MESSAGES/django.po
===================================================================
--- tests/regressiontests/i18n/test_locale/pt_BR/LC_MESSAGES/django.po	(revision 0)
+++ tests/regressiontests/i18n/test_locale/pt_BR/LC_MESSAGES/django.po	(revision 0)
@@ -0,0 +1,35 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-06-10 13:56+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+
+#: urls.py:10
+msgid "^translated/$"
+msgstr "^traduzidos/$"
+
+#: urls.py:15
+msgid "^users/$"
+msgstr "^usuarios/$"
+
+#: urls.py:16 wrong_urls.py:6
+msgid "^account/"
+msgstr "^conta/"
+
+#: urls_namespace.py:9 wrong_urls_namespace.py:9
+msgid "^register/$"
+msgstr "^registre-se/$"
Index: tests/regressiontests/i18n/test_locale/pt_BR/LC_MESSAGES/django.mo
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: tests/regressiontests/i18n/test_locale/pt_BR/LC_MESSAGES/django.mo
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Index: tests/regressiontests/i18n/test_locale/nl/LC_MESSAGES/django.po
===================================================================
--- tests/regressiontests/i18n/test_locale/nl/LC_MESSAGES/django.po	(revision 0)
+++ tests/regressiontests/i18n/test_locale/nl/LC_MESSAGES/django.po	(revision 0)
@@ -0,0 +1,35 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-06-10 11:53+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+#: urls.py:10
+msgid "^translated/$"
+msgstr "^vertaald/$"
+
+#: urls.py:15
+msgid "^users/$"
+msgstr "^gebruikers/$"
+
+#: urls.py:16
+msgid "^account/"
+msgstr "^profiel/"
+
+#: urls_namespace.py:9
+msgid "^register/$"
+msgstr "^registeren/$"
Index: tests/regressiontests/i18n/test_locale/nl/LC_MESSAGES/django.mo
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: tests/regressiontests/i18n/test_locale/nl/LC_MESSAGES/django.mo
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

