Permalink
Browse files

Initial code commit.

  • Loading branch information...
0 parents commit 30b10c43fa521538ebe3dbb5b241618f0a9e174a @brocaar committed May 18, 2011
@@ -0,0 +1,8 @@
+syntax: glob
+
+*.pyc
+.*
+
+dist
+MANIFEST
+
27 LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) Orne Brocaar, www.brocaar.com.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of Brocaar nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,5 @@
+include LICENSE
+include README.rst
+
+recursive-include i18nurls/locale *
+recursive-include i18nurls/templates *
@@ -0,0 +1,42 @@
+Django URL internationalization
+===============================
+
+This Django pluggable makes it possible to translate URL patterns by using gettext.
+As well it contains a custom patterns function for prefixing URLs with the active
+language-code (eg: ``/en/news/``, ``/nl/nieuws/``) and a middleware to activate
+the language code in the prefix (for incoming request).
+
+
+Examples
+--------
+
+::
+
+ # urls.py
+ from django.conf.urls.defaults import patterns, include, url
+ from django.utils.translation import ugettext_lazy as _
+
+ from i18nurls.defaults import locale_prefixed_patterns
+
+
+ patterns = locale_prefixed_patterns('',
+ url(_(r'^users/register/$', 'your.view', name='account-register')),
+ )
+
+ # In your shell, after updating your translations (with makemessages / compilemessages)
+ >>> activate('nl')
+ >>> reverse('account-register')
+ '/nl/gebruikers/registeren/'
+
+ >>> activate('nl')
+ >>> reverse('account-register')
+ '/en/users/register/'
+
+
+Installation
+------------
+
+* Add ``i18nurls`` to your ``settings.INSTALLED_APPS``
+
+* Add ``i18nurls.middleware.LocaleMiddleware`` to your ``settings.MIDDLEWARE_CLASSES``.
+ Note: This middleware replaces the default Django LocaleMiddleware.
@@ -0,0 +1,4 @@
+from i18nurls.urlresolvers import I18NRegexURLPattern, I18NRegexURLResolver
+
+
+__VERSION__ = '0.5dev'
@@ -0,0 +1,11 @@
+from django.conf.urls.defaults import patterns
+
+from i18nurls.urlresolvers import PrefixedRegexURLResolver, PrefixedURLConf
+
+
+def language_prefixed_patterns(prefix, *args):
+ """
+ Prefix all URLs with the current active language code.
+ """
+ pattern_list = patterns(prefix, *args)
+ return [PrefixedRegexURLResolver(PrefixedURLConf(pattern_list))]
Binary file not shown.
@@ -0,0 +1,30 @@
+# 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-05-18 10:24+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"
+
+#: tests/urls.py:15
+msgid "^news/$"
+msgstr "^news/$"
+
+#: tests/urls.py:16
+msgid "^users/"
+msgstr "^users/"
+
+#: tests/user_urls.py:7
+msgid "register/$"
+msgstr "register/$"
Binary file not shown.
@@ -0,0 +1,31 @@
+# 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-05-18 10:24+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"
+
+#: tests/urls.py:15
+msgid "^news/$"
+msgstr "^nieuws/$"
+
+#: tests/urls.py:16
+msgid "^users/"
+msgstr "^gebruikers/"
+
+#: tests/user_urls.py:7
+msgid "register/$"
+msgstr "registeren/$"
@@ -0,0 +1,54 @@
+from django.conf import settings
+from django.http import HttpResponseRedirect
+from django.utils import translation
+from django.utils.cache import patch_vary_headers
+
+
+class LocaleMiddleware(object):
+
+ def process_request(self, request):
+ """
+ If the `request.path` starts with a (valid) language-code prefix, this
+ language-code will be activated. Else `get_language_from_request` is
+ used as a fallback.
+ """
+ language_code = self._language_code_from_path(request.path)
+
+ if not language_code:
+ language_code = translation.get_language_from_request(request)
+
+ translation.activate(language_code)
+ request.LANGUAGE_CODE = translation.get_language()
+
+ def process_response(self, request, response):
+ """
+ Set the Content-Language header. If `response.status_code` is 404, and
+ no language-code prefix is found, return a `HttpResponseRedirect` to the
+ language-code prefixed `request.path`.
+ """
+ patch_vary_headers(response, ('Accept-Language',))
+ language_code = translation.get_language()
+ translation.deactivate()
+
+ if 'Content-Language' not in response:
+ response['Content-Language'] = language_code
+
+ if response.status_code == 404 and not self._language_code_from_path(request.path):
+ return HttpResponseRedirect('/%s%s' % (language_code, request.get_full_path()))
+ else:
+ return response
+
+ def _language_code_from_path(self, path):
+ """
+ Return the language-code found in the requested path. For example
+ `/en/my/path/` will return `en`. Return `None` if no language-code
+ could be found.
+ """
+ language_code = None
+ path_parts = path.split('/')
+ if len(path_parts) > 2:
+ possible_language_code = path_parts[1]
+ for language in settings.LANGUAGES:
+ if language[0] == possible_language_code:
+ language_code = possible_language_code
+ return language_code
@@ -0,0 +1 @@
+# Without a models.py file, Django doesn't find our tests.
No changes.
@@ -0,0 +1 @@
+from i18nurls.tests.url_tests import *
@@ -0,0 +1,44 @@
+import os
+
+from django.core.urlresolvers import clear_url_caches
+from django.conf import settings
+from django.test import TestCase
+from django.utils.translation import activate
+
+
+class BaseTestCase(TestCase):
+
+ def _update_setting(self, key, value):
+ if not key in self.old_settings:
+ self.old_settings[key] = getattr(settings, key)
+ setattr(settings, key, value)
+
+ def setUp(self):
+ clear_url_caches()
+
+ self.old_settings = {}
+
+ self._update_setting('DEBUG', True)
+ self._update_setting('TEMPLATE_DEBUG', True)
+
+ self._update_setting('ROOT_URLCONF',
+ 'i18nurls.tests.urls'
+ )
+
+ self._update_setting('MIDDLEWARE_CLASSES', (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'i18nurls.middleware.LocaleMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ ))
+
+ self._update_setting('LANGUAGES', (
+ ('nl', 'Dutch'),
+ ('en', 'English'),
+ ))
+
+ def tearDown(self):
+ for key in self.old_settings:
+ setattr(settings, key, self.old_settings[key])
@@ -0,0 +1,99 @@
+from django.core.urlresolvers import reverse
+from django.utils.translation import activate
+
+from i18nurls.tests.base import BaseTestCase
+
+
+class PrefixTest(BaseTestCase):
+
+ def test_not_prefixed_url(self):
+ activate('en')
+ self.assertEqual(reverse('not-prefixed'), '/not-prefixed/')
+ activate('nl')
+ self.assertEqual(reverse('not-prefixed'), '/not-prefixed/')
+
+ def test_prefixed_url(self):
+ activate('en')
+ self.assertEqual(reverse('prefixed'), '/en/prefixed/')
+ activate('nl')
+ self.assertEqual(reverse('prefixed'), '/nl/prefixed/')
+
+
+class TranslationTest(BaseTestCase):
+
+ def test_url(self):
+ activate('en')
+ self.assertEqual(reverse('news'), '/en/news/')
+ activate('nl')
+ self.assertEqual(reverse('news'), '/nl/nieuws/')
+
+
+class NamespaceTest(BaseTestCase):
+
+ def test_namespace(self):
+ activate('en')
+ self.assertEqual(reverse('users:register'), '/en/users/register/')
+ activate('nl')
+ self.assertEqual(reverse('users:register'), '/nl/gebruikers/registeren/')
+
+
+class RedirectTest(BaseTestCase):
+
+ def test_en_redirect(self):
+ response = self.client.get('/users/register/', HTTP_ACCEPT_LANGUAGE='en')
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(response.has_header('location'), True)
+ self.assertEqual(response['location'], 'http://testserver/en/users/register/')
+
+ response = self.client.get(response['location'])
+ self.assertEqual(response.status_code, 200)
+
+ def test_en_redirect_wrong_url(self):
+ response = self.client.get('/gebruikers/registeren/', HTTP_ACCEPT_LANGUAGE='en')
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(response.has_header('location'), True)
+ self.assertEqual(response['location'], 'http://testserver/en/gebruikers/registeren/')
+
+ response = self.client.get(response['location'])
+ self.assertEqual(response.status_code, 404)
+
+ def test_nl_redirects(self):
+ response = self.client.get('/gebruikers/registeren/', HTTP_ACCEPT_LANGUAGE='nl')
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(response.has_header('location'), True)
+ self.assertEqual(response['location'], 'http://testserver/nl/gebruikers/registeren/')
+
+ response = self.client.get(response['location'])
+ self.assertEqual(response.status_code, 200)
+
+ def test_nl_redirects_wrong_url(self):
+ response = self.client.get('/users/register/', HTTP_ACCEPT_LANGUAGE='nl')
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(response.has_header('location'), True)
+ self.assertEqual(response['location'], 'http://testserver/nl/users/register/')
+
+ response = self.client.get(response['location'])
+ self.assertEqual(response.status_code, 404)
+
+
+class ResponseTest(BaseTestCase):
+
+ def test_en_url(self):
+ response = self.client.get('/en/users/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/gebruikers/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/gebruikers/registeren/')
+ self.assertEqual(response.status_code, 404)
+
+ def test_wrong_nl_prefix(self):
+ response = self.client.get('/nl/users/register/')
+ self.assertEqual(response.status_code, 404)
@@ -0,0 +1,17 @@
+from django.conf.urls.defaults import patterns, include, url
+from django.utils.translation import ugettext_lazy as _
+from django.views.generic import TemplateView
+
+from i18nurls.defaults import language_prefixed_patterns
+
+
+urlpatterns = patterns('',
+ url(r'^not-prefixed/$', TemplateView.as_view(template_name='i18nurls/dummy.html'), name='not-prefixed'),
+ url(r'^news/$', TemplateView.as_view(template_name='i18nurls/dummy.html'), name='news-no-i18n'),
+)
+
+urlpatterns += language_prefixed_patterns('',
+ url(r'^prefixed/$', TemplateView.as_view(template_name='i18nurls/dummy.html'), name='prefixed'),
+ url(_(r'^news/$'), TemplateView.as_view(template_name='i18nurls/dummy.html'), name='news'),
+ url(_(r'^users/'), include('i18nurls.tests.user_urls', namespace='users')),
+)
@@ -0,0 +1,8 @@
+from django.conf.urls.defaults import patterns, url
+from django.utils.translation import ugettext_lazy as _
+from django.views.generic import TemplateView
+
+
+urlpatterns = patterns('',
+ url(_(r'register/$'), TemplateView.as_view(template_name='i18nurls/dummy.html'), name='register'),
+)
Oops, something went wrong.

0 comments on commit 30b10c4

Please sign in to comment.