Permalink
Browse files

Changed the API so it will match with the upcoming Django 1.4 release.

  • Loading branch information...
1 parent fb3a45f commit 1c829b107c65214009a7dfe277248a0025650f23 @brocaar committed Jul 3, 2011
Showing with 122 additions and 104 deletions.
  1. +51 −24 README.rst
  2. +4 −4 i18nurls/__init__.py
  3. +0 −11 i18nurls/defaults.py
  4. +16 −0 i18nurls/i18n.py
  5. +30 −22 i18nurls/middleware.py
  6. +8 −0 i18nurls/monkeypatch.py
  7. +2 −3 i18nurls/tests/urls.py
  8. +10 −37 i18nurls/urlresolvers.py
  9. +1 −3 setup.py
View
@@ -1,36 +1,63 @@
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).
+This Django app makes it possible to prefix URL patterns with the active
+language and to make URL patterns translatable by using gettext. As well this
+package contains a patch for the ``LocaleMiddleware`` so it is able to activate
+the right language (based on the language-prefix in the requested URL).
+.. note::
-Examples
---------
-
-::
+ During the DjangoCon EU 2011 sprints, I wrote a patch for including this
+ functionality into the Django core. This patch was accepted and will be
+ included in Django 1.4 (thanks to Jannis Leidel and Russell Keith-Magee for
+ their feedback and reviewing the patch).
+
- # urls.py
+ In the 0.6 version of this package, I rewrote the API so that it will match
+ with the upcoming Django 1.4 version. You can read more about this in the
+ `Django documentation (dev) <http://docs.djangoproject.com/en/dev/topics/i18n/internationalization/#specifying-translation-strings-in-url-patterns>`_.
+
+
+Translating URL patterns
+------------------------
+
+After installing this package, URL patterns can also be marked translatable
+using the ``ugettext_lazy()`` function. Example::
+
from django.conf.urls.defaults import patterns, include, url
+ from django.conf.urls.i18n import i18n_patterns
from django.utils.translation import ugettext_lazy as _
- from i18nurls.defaults import language_prefixed_patterns
-
-
- patterns = language_prefixed_patterns('',
- url(_(r'^users/register/$'), 'your.view', name='account-register'),
+ urlpatterns = patterns(''
+ url(r'^sitemap\.xml$', 'sitemap.view', name='sitemap_xml'),
)
-
- # In your shell, after updating your translations (with makemessages / compilemessages)
- >>> activate('nl')
- >>> reverse('account-register')
- '/nl/gebruikers/registeren/'
-
+
+ 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 you've created the translations, 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('account-register')
- '/en/users/register/'
+ >>> reverse('news:category', kwargs={'slug': 'recent'})
+ '/en/news/category/recent/'
+
+ >>> activate('nl')
+ >>> reverse('news:category', kwargs={'slug': 'recent'})
+ '/nl/nieuws/categorie/recent/'
Installation
@@ -40,5 +67,5 @@ 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.
+* Add ``django.middleware.locale.LocaleMiddleware`` to your ``settings.MIDDLEWARE_CLASSES``
+ (if it is not already there, make sure it comes before the ``CommonMiddleware``).
View
@@ -1,4 +1,4 @@
-from i18nurls.urlresolvers import I18NRegexURLPattern, I18NRegexURLResolver
-
-
-__VERSION__ = '0.5.2'
+# Import modules to apply the monkeypatches
+from i18nurls import i18n
+from i18nurls import urlresolvers
+from i18nurls import middleware
View
@@ -1,11 +0,0 @@
-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))]
View
@@ -0,0 +1,16 @@
+from django.conf import settings
+from django.conf.urls.defaults import patterns
+
+
+from i18nurls.urlresolvers import LocaleRegexURLResolver
+
+
+def i18n_patterns(prefix, *args):
+ pattern_list = patterns(prefix, *args)
+ if not settings.USE_I18N:
+ return pattern_list
+ return [LocaleRegexURLResolver(pattern_list)]
+
+
+from django.conf.urls import i18n
+setattr(i18n, 'i18n_patterns', i18n_patterns)
View
@@ -1,21 +1,24 @@
import re
from django.conf import settings
+from django.core.urlresolvers import get_resolver
from django.http import HttpResponseRedirect
+from django.middleware.locale import LocaleMiddleware
from django.utils import translation
+from django.utils.cache import patch_vary_headers
+from i18nurls.monkeypatch import monkeypatch_class
+from i18nurls.urlresolvers import LocaleRegexURLResolver
-language_code_prefix_regex = re.compile(r'^/([\w-]+)/')
-class LocaleMiddleware(object):
+language_code_prefix_re = re.compile(r'^/([\w-]+)/')
+
+class I18NLocaleMiddleware(LocaleMiddleware):
+
+ __metaclass__ = monkeypatch_class
def process_request(self, request):
- """
- If `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)
+ language_code = self.get_language_from_path(request.path_info)
if not language_code:
language_code = translation.get_language_from_request(request)
@@ -24,25 +27,30 @@ def process_request(self, request):
request.LANGUAGE_CODE = translation.get_language()
def process_response(self, request, response):
- """
- Sets the Content-Language header. If `response.status_code` is 404, and
- no language-code prefix is found, returns a `HttpResponseRedirect` to the
- language-code prefixed `request.path`.
- """
language_code = translation.get_language()
translation.deactivate()
+ if (response.status_code == 404 and
+ not self.get_language_from_path(request.path_info)
+ and self.is_language_prefix_patterns_used()):
+ return HttpResponseRedirect(
+ '/%s%s' % (language_code, request.get_full_path()))
+
+ patch_vary_headers(response, ('Accept-Language',))
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
+ return response
+
+ def is_language_prefix_patterns_used(self):
+ for url_pattern in get_resolver(None).url_patterns:
+ if isinstance(url_pattern, LocaleRegexURLResolver):
+ return True
+ return False
- def _language_code_from_path(self, path):
- regex_match = language_code_prefix_regex.match(path)
+ def get_language_from_path(self, path):
+ regex_match = language_code_prefix_re.match(path)
if regex_match:
- if regex_match.group(1) in dict(settings.LANGUAGES):
- return regex_match.group(1)
+ language_code = regex_match.group(1)
+ if language_code in dict(settings.LANGUAGES):
+ return language_code
return None
View
@@ -0,0 +1,8 @@
+def monkeypatch_class(name, bases, namespace):
+ # Source: http://mail.python.org/pipermail/python-dev/2008-January/076194.html
+ assert len(bases) == 1, "Exactly one base class required"
+ base = bases[0]
+ for name, value in namespace.iteritems():
+ if name != "__metaclass__":
+ setattr(base, name, value)
+ return base
View
@@ -1,16 +1,15 @@
+from django.conf.urls.i18n import i18n_patterns
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('',
+urlpatterns += i18n_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')),
View
@@ -2,18 +2,11 @@
from django.core.urlresolvers import RegexURLResolver, RegexURLPattern
from django.utils.datastructures import MultiValueDict
+from django.utils.encoding import force_unicode
from django.utils.regex_helper import normalize
from django.utils.translation import get_language
-
-def monkeypatch_class(name, bases, namespace):
- # Source: http://mail.python.org/pipermail/python-dev/2008-January/076194.html
- assert len(bases) == 1, "Exactly one base class required"
- base = bases[0]
- for name, value in namespace.iteritems():
- if name != "__metaclass__":
- setattr(base, name, value)
- return base
+from i18nurls.monkeypatch import monkeypatch_class
class I18NRegexURLPattern(RegexURLPattern):
@@ -44,15 +37,9 @@ def regex(self):
if isinstance(self._i18n_regex, basestring):
compiled_regex = re.compile(self._i18n_regex, re.UNICODE)
else:
- current_language = get_language()
- if current_language is not language_code:
- activate(language_code)
- regex = unicode(self._i18n_regex)
- if current_language is not language_code:
- activate(current_language)
+ regex = force_unicode(self._i18n_regex)
compiled_regex = re.compile(regex, re.UNICODE)
self._i18n_regex_dict[language_code] = compiled_regex
-
return self._i18n_regex_dict[language_code]
@@ -88,15 +75,9 @@ def regex(self):
if isinstance(self._i18n_regex, basestring):
compiled_regex = re.compile(self._i18n_regex, re.UNICODE)
else:
- current_language = get_language()
- if current_language is not language_code:
- activate(language_code)
- regex = unicode(self._i18n_regex)
- if current_language is not language_code:
- activate(current_language)
+ regex = force_unicode(self._i18n_regex)
compiled_regex = re.compile(regex, re.UNICODE)
self._i18n_regex_dict[language_code] = compiled_regex
-
return self._i18n_regex_dict[language_code]
def _populate(self):
@@ -105,6 +86,7 @@ def _populate(self):
lookups = MultiValueDict()
namespaces = {}
apps = {}
+ language_code = get_language()
for pattern in reversed(self.url_patterns):
p_pattern = pattern.regex.pattern
if p_pattern.startswith('^'):
@@ -132,7 +114,6 @@ def _populate(self):
if pattern.name is not None:
lookups.appendlist(pattern.name, (bits, p_pattern))
- 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
@@ -159,23 +140,15 @@ def _get_app_dict(self):
app_dict = property(_get_app_dict)
-class PrefixedRegexURLResolver(RegexURLResolver):
-
+class LocaleRegexURLResolver(RegexURLResolver):
def __init__(self, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
- regex = None
- super(PrefixedRegexURLResolver, self).__init__(regex, urlconf_name,
- default_kwargs, app_name, namespace)
-
+ super(LocaleRegexURLResolver, 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)
+ regex_compiled = re.compile('^%s/' % language_code, re.UNICODE)
self._i18n_regex_dict[language_code] = regex_compiled
return self._i18n_regex_dict[language_code]
-
-
-class PrefixedURLConf(object):
-
- def __init__(self, pattern_list):
- self.urlpatterns = pattern_list
View
@@ -1,11 +1,9 @@
from distutils.core import setup
-version = __import__('i18nurls').__VERSION__
-
setup(
name='django-i18nurls',
- version=version,
+ version='0.6dev',
author='Orne Brocaar',
author_email='info@brocaar.com',
url='http://bitbucket.org/brocaar/django-i18nurls',

0 comments on commit 1c829b1

Please sign in to comment.