From aa5ab114e34645823b219f9a866fd82fc26b427b Mon Sep 17 00:00:00 2001
From: Claude Paroz <claude@2xlibre.net>
Date: Thu, 12 Mar 2015 09:32:29 +0100
Subject: [PATCH] Fixed #24122 -- Redirected to translated url after setting
 language

Thanks gbdlin for the initial patch and Tim Graham for the review.
---
 django/core/urlresolvers.py                   |  26 +++++++++++++++++-
 django/views/i18n.py                          |   4 +++
 docs/releases/1.9.txt                         |   3 +-
 tests/i18n/patterns/tests.py                  |  14 +++++++++-
 .../locale/nl/LC_MESSAGES/django.mo           | Bin 515 -> 557 bytes
 .../locale/nl/LC_MESSAGES/django.po           |   4 +++
 tests/view_tests/tests/test_i18n.py           |  17 ++++++++++++
 tests/view_tests/urls.py                      |   6 ++++
 8 files changed, 71 insertions(+), 3 deletions(-)

diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index 59114795bc..cf42768add 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -24,7 +24,8 @@ from django.utils.functional import cached_property, lazy
 from django.utils.http import RFC3986_SUBDELIMS, urlquote
 from django.utils.module_loading import module_has_submodule
 from django.utils.regex_helper import normalize
-from django.utils.translation import get_language
+from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit
+from django.utils.translation import get_language, override
 
 # SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
 # the current thread (which is the only one we ever access), it is assumed to
@@ -652,3 +653,26 @@ def is_valid_path(path, urlconf=None):
         return True
     except Resolver404:
         return False
+
+
+def translate_url(url, lang_code):
+    """
+    Given a URL (absolute or relative), try to get its translated version in
+    the `lang_code` language (either by i18n_patterns or by translated regex).
+    Return the original URL if no translated version is found.
+    """
+    parsed = urlsplit(url)
+    try:
+        match = resolve(parsed.path)
+    except Resolver404:
+        pass
+    else:
+        to_be_reversed = "%s:%s" % (match.namespace, match.url_name) if match.namespace else match.url_name
+        with override(lang_code):
+            try:
+                url = reverse(to_be_reversed, args=match.args, kwargs=match.kwargs)
+            except NoReverseMatch:
+                pass
+            else:
+                url = urlunsplit((parsed.scheme, parsed.netloc, url, parsed.query, parsed.fragment))
+    return url
diff --git a/django/views/i18n.py b/django/views/i18n.py
index 7e21035319..30ecbbeaf3 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -6,6 +6,7 @@ import os
 from django import http
 from django.apps import apps
 from django.conf import settings
+from django.core.urlresolvers import translate_url
 from django.template import Context, Engine
 from django.utils import six
 from django.utils._os import upath
@@ -37,6 +38,9 @@ def set_language(request):
     if request.method == 'POST':
         lang_code = request.POST.get('language', None)
         if lang_code and check_for_language(lang_code):
+            next_trans = translate_url(next, lang_code)
+            if next_trans != next:
+                response = http.HttpResponseRedirect(next_trans)
             if hasattr(request, 'session'):
                 request.session[LANGUAGE_SESSION_KEY] = lang_code
             else:
diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt
index da4984a9be..e8dd955e29 100644
--- a/docs/releases/1.9.txt
+++ b/docs/releases/1.9.txt
@@ -127,7 +127,8 @@ Generic Views
 Internationalization
 ^^^^^^^^^^^^^^^^^^^^
 
-* ...
+* The :func:`django.views.i18n.set_language` view now properly redirects to
+  :ref:`translated URLs <url-internationalization>`, when available.
 
 Management Commands
 ^^^^^^^^^^^^^^^^^^^
diff --git a/tests/i18n/patterns/tests.py b/tests/i18n/patterns/tests.py
index 1ac0e8a4a9..62ca49b67d 100644
--- a/tests/i18n/patterns/tests.py
+++ b/tests/i18n/patterns/tests.py
@@ -4,7 +4,7 @@ import os
 
 from django.core.exceptions import ImproperlyConfigured
 from django.core.urlresolvers import (
-    clear_url_caches, reverse, set_script_prefix,
+    clear_url_caches, reverse, set_script_prefix, translate_url,
 )
 from django.http import HttpResponsePermanentRedirect
 from django.middleware.locale import LocaleMiddleware
@@ -135,6 +135,18 @@ class URLTranslationTests(URLTestCaseBase):
         with translation.override('pt-br'):
             self.assertEqual(reverse('users'), '/pt-br/usuarios/')
 
+    def test_translate_url_utility(self):
+        with translation.override('en'):
+            self.assertEqual(translate_url('/en/non-existent/', 'nl'), '/en/non-existent/')
+            self.assertEqual(translate_url('/en/users/', 'nl'), '/nl/gebruikers/')
+            # Namespaced URL
+            self.assertEqual(translate_url('/en/account/register/', 'nl'), '/nl/profiel/registeren/')
+            self.assertEqual(translation.get_language(), 'en')
+
+        with translation.override('nl'):
+            self.assertEqual(translate_url('/nl/gebruikers/', 'en'), '/en/users/')
+            self.assertEqual(translation.get_language(), 'nl')
+
 
 class URLNamespaceTests(URLTestCaseBase):
     """
diff --git a/tests/view_tests/locale/nl/LC_MESSAGES/django.mo b/tests/view_tests/locale/nl/LC_MESSAGES/django.mo
index 90066d70ecfea30c963789239a707ad531d60059..ef12f2ade80a2f044b8ea96b73bdeb77cc9772fb 100644
GIT binary patch
delta 166
zcmZo>S<6y?Pl#nI0}!wPu?!H~05K~N#{e-16acXn5ElY5ClJp8VqPF#2E?X}3=CU<
zv@Q_80<t-Pv;-3a12>RX1kyku1|S7C8%QxQg9zu~AUB1w)S}F^%;dz9iTUchaV15G
cdBr)2C8;U;DjTQfG4jL#<x3J1bAWsX01R#!qyPW_

delta 97
zcmZ3>(##TiPl#nI0}wC+u?!HK05K~N`v5TrBml7%5GMjLClL1lu_+@1!!#hR3&dxD
YYz`p(3rGVI0}~K4OiXp$xFwen0QV>h5dZ)H

diff --git a/tests/view_tests/locale/nl/LC_MESSAGES/django.po b/tests/view_tests/locale/nl/LC_MESSAGES/django.po
index e85f08ee80..4e5f7e2fb5 100644
--- a/tests/view_tests/locale/nl/LC_MESSAGES/django.po
+++ b/tests/view_tests/locale/nl/LC_MESSAGES/django.po
@@ -16,6 +16,10 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
+#: urls.py:78
+msgid "^translated/$"
+msgstr "^vertaald/$"
+
 #: views/csrf.py:98
 msgid "Forbidden"
 msgstr "Verboden"
diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py
index 2f47007b04..ef11d7d82a 100644
--- a/tests/view_tests/tests/test_i18n.py
+++ b/tests/view_tests/tests/test_i18n.py
@@ -67,6 +67,23 @@ class I18NTests(TestCase):
             self.assertEqual(language_cookie['path'], '/test/')
             self.assertEqual(language_cookie['max-age'], 3600 * 7 * 2)
 
+    @modify_settings(MIDDLEWARE_CLASSES={
+        'append': 'django.middleware.locale.LocaleMiddleware',
+    })
+    def test_lang_from_translated_i18n_pattern(self):
+        response = self.client.post(
+            '/i18n/setlang/', data={'language': 'nl'},
+            follow=True, HTTP_REFERER='/en/translated/'
+        )
+        self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], 'nl')
+        self.assertRedirects(response, 'http://testserver/nl/vertaald/')
+        # And reverse
+        response = self.client.post(
+            '/i18n/setlang/', data={'language': 'en'},
+            follow=True, HTTP_REFERER='/nl/vertaald/'
+        )
+        self.assertRedirects(response, 'http://testserver/en/translated/')
+
     def test_jsi18n(self):
         """The javascript_catalog can be deployed with language settings"""
         for lang_code in ['es', 'fr', 'ru']:
diff --git a/tests/view_tests/urls.py b/tests/view_tests/urls.py
index 0bfcd587c2..061dd19751 100644
--- a/tests/view_tests/urls.py
+++ b/tests/view_tests/urls.py
@@ -2,7 +2,9 @@
 from os import path
 
 from django.conf.urls import include, url
+from django.conf.urls.i18n import i18n_patterns
 from django.utils._os import upath
+from django.utils.translation import ugettext_lazy as _
 from django.views import defaults, i18n, static
 
 from . import views
@@ -73,6 +75,10 @@ urlpatterns = [
     url(r'^site_media/(?P<path>.*)$', static.serve, {'document_root': media_dir}),
 ]
 
+urlpatterns += i18n_patterns(
+    url(_(r'^translated/$'), views.index_page, name='i18n_prefixed'),
+)
+
 urlpatterns += [
     url(r'view_exception/(?P<n>[0-9]+)/$', views.view_exception, name='view_exception'),
     url(r'template_exception/(?P<n>[0-9]+)/$', views.template_exception, name='template_exception'),