Skip to content

Commit aa5ab11

Browse files
committed
Fixed #24122 -- Redirected to translated url after setting language
Thanks gbdlin for the initial patch and Tim Graham for the review.
1 parent a8991b9 commit aa5ab11

File tree

8 files changed

+71
-3
lines changed

8 files changed

+71
-3
lines changed

django/core/urlresolvers.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
from django.utils.http import RFC3986_SUBDELIMS, urlquote
2525
from django.utils.module_loading import module_has_submodule
2626
from django.utils.regex_helper import normalize
27-
from django.utils.translation import get_language
27+
from django.utils.six.moves.urllib.parse import urlsplit, urlunsplit
28+
from django.utils.translation import get_language, override
2829

2930
# SCRIPT_NAME prefixes for each thread are stored here. If there's no entry for
3031
# 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):
652653
return True
653654
except Resolver404:
654655
return False
656+
657+
658+
def translate_url(url, lang_code):
659+
"""
660+
Given a URL (absolute or relative), try to get its translated version in
661+
the `lang_code` language (either by i18n_patterns or by translated regex).
662+
Return the original URL if no translated version is found.
663+
"""
664+
parsed = urlsplit(url)
665+
try:
666+
match = resolve(parsed.path)
667+
except Resolver404:
668+
pass
669+
else:
670+
to_be_reversed = "%s:%s" % (match.namespace, match.url_name) if match.namespace else match.url_name
671+
with override(lang_code):
672+
try:
673+
url = reverse(to_be_reversed, args=match.args, kwargs=match.kwargs)
674+
except NoReverseMatch:
675+
pass
676+
else:
677+
url = urlunsplit((parsed.scheme, parsed.netloc, url, parsed.query, parsed.fragment))
678+
return url

django/views/i18n.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from django import http
77
from django.apps import apps
88
from django.conf import settings
9+
from django.core.urlresolvers import translate_url
910
from django.template import Context, Engine
1011
from django.utils import six
1112
from django.utils._os import upath
@@ -37,6 +38,9 @@ def set_language(request):
3738
if request.method == 'POST':
3839
lang_code = request.POST.get('language', None)
3940
if lang_code and check_for_language(lang_code):
41+
next_trans = translate_url(next, lang_code)
42+
if next_trans != next:
43+
response = http.HttpResponseRedirect(next_trans)
4044
if hasattr(request, 'session'):
4145
request.session[LANGUAGE_SESSION_KEY] = lang_code
4246
else:

docs/releases/1.9.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ Generic Views
127127
Internationalization
128128
^^^^^^^^^^^^^^^^^^^^
129129

130-
* ...
130+
* The :func:`django.views.i18n.set_language` view now properly redirects to
131+
:ref:`translated URLs <url-internationalization>`, when available.
131132

132133
Management Commands
133134
^^^^^^^^^^^^^^^^^^^

tests/i18n/patterns/tests.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from django.core.exceptions import ImproperlyConfigured
66
from django.core.urlresolvers import (
7-
clear_url_caches, reverse, set_script_prefix,
7+
clear_url_caches, reverse, set_script_prefix, translate_url,
88
)
99
from django.http import HttpResponsePermanentRedirect
1010
from django.middleware.locale import LocaleMiddleware
@@ -135,6 +135,18 @@ def test_users_url(self):
135135
with translation.override('pt-br'):
136136
self.assertEqual(reverse('users'), '/pt-br/usuarios/')
137137

138+
def test_translate_url_utility(self):
139+
with translation.override('en'):
140+
self.assertEqual(translate_url('/en/non-existent/', 'nl'), '/en/non-existent/')
141+
self.assertEqual(translate_url('/en/users/', 'nl'), '/nl/gebruikers/')
142+
# Namespaced URL
143+
self.assertEqual(translate_url('/en/account/register/', 'nl'), '/nl/profiel/registeren/')
144+
self.assertEqual(translation.get_language(), 'en')
145+
146+
with translation.override('nl'):
147+
self.assertEqual(translate_url('/nl/gebruikers/', 'en'), '/en/users/')
148+
self.assertEqual(translation.get_language(), 'nl')
149+
138150

139151
class URLNamespaceTests(URLTestCaseBase):
140152
"""
42 Bytes
Binary file not shown.

tests/view_tests/locale/nl/LC_MESSAGES/django.po

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ msgstr ""
1616
"Content-Type: text/plain; charset=UTF-8\n"
1717
"Content-Transfer-Encoding: 8bit\n"
1818

19+
#: urls.py:78
20+
msgid "^translated/$"
21+
msgstr "^vertaald/$"
22+
1923
#: views/csrf.py:98
2024
msgid "Forbidden"
2125
msgstr "Verboden"

tests/view_tests/tests/test_i18n.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,23 @@ def test_setlang_cookie(self):
6767
self.assertEqual(language_cookie['path'], '/test/')
6868
self.assertEqual(language_cookie['max-age'], 3600 * 7 * 2)
6969

70+
@modify_settings(MIDDLEWARE_CLASSES={
71+
'append': 'django.middleware.locale.LocaleMiddleware',
72+
})
73+
def test_lang_from_translated_i18n_pattern(self):
74+
response = self.client.post(
75+
'/i18n/setlang/', data={'language': 'nl'},
76+
follow=True, HTTP_REFERER='/en/translated/'
77+
)
78+
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], 'nl')
79+
self.assertRedirects(response, 'http://testserver/nl/vertaald/')
80+
# And reverse
81+
response = self.client.post(
82+
'/i18n/setlang/', data={'language': 'en'},
83+
follow=True, HTTP_REFERER='/nl/vertaald/'
84+
)
85+
self.assertRedirects(response, 'http://testserver/en/translated/')
86+
7087
def test_jsi18n(self):
7188
"""The javascript_catalog can be deployed with language settings"""
7289
for lang_code in ['es', 'fr', 'ru']:

tests/view_tests/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
from os import path
33

44
from django.conf.urls import include, url
5+
from django.conf.urls.i18n import i18n_patterns
56
from django.utils._os import upath
7+
from django.utils.translation import ugettext_lazy as _
68
from django.views import defaults, i18n, static
79

810
from . import views
@@ -73,6 +75,10 @@
7375
url(r'^site_media/(?P<path>.*)$', static.serve, {'document_root': media_dir}),
7476
]
7577

78+
urlpatterns += i18n_patterns(
79+
url(_(r'^translated/$'), views.index_page, name='i18n_prefixed'),
80+
)
81+
7682
urlpatterns += [
7783
url(r'view_exception/(?P<n>[0-9]+)/$', views.view_exception, name='view_exception'),
7884
url(r'template_exception/(?P<n>[0-9]+)/$', views.template_exception, name='template_exception'),

0 commit comments

Comments
 (0)