{% trans "Hello! This is an example home page for django-solid-i18n-urls package usage" %}.
10 |
{% trans "This example site supports two languages: english (default) and russian. As you can see, when accessing url without language prefix, default language is used. It is declared in settings.LANGUAGE_CODE. For more details visit" %} https://github.com/st4lk/django-solid-i18n-urls.
11 |
{% trans "Languages can be switched using buttons in right-up corner" %}.
12 | {% endblock content %}
13 |
--------------------------------------------------------------------------------
/solid_i18n/contrib.py:
--------------------------------------------------------------------------------
1 | """
2 | Contains some code, copied from django, to support different versions of django.
3 | """
4 | from django.utils.encoding import iri_to_uri
5 | from django.utils.encoding import escape_uri_path
6 |
7 |
8 | def get_full_path(request, force_append_slash=False):
9 | """
10 | Copied from django.http.request.get_full_path (django 1.9) to support
11 | older versions of django.
12 | """
13 | # RFC 3986 requires query string arguments to be in the ASCII range.
14 | # Rather than crash if this doesn't happen, we encode defensively.
15 | return '%s%s%s' % (
16 | escape_uri_path(request.path),
17 | '/' if force_append_slash and not request.path.endswith('/') else '',
18 | ('?' + iri_to_uri(request.META.get('QUERY_STRING', ''))) if request.META.get('QUERY_STRING', '') else ''
19 | )
20 |
--------------------------------------------------------------------------------
/example/tests/test_noni18n_urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django.core.urlresolvers import reverse
3 | from django.test.utils import override_settings
4 | from django.core.urlresolvers import clear_url_caches
5 |
6 | from .base import URLTestCaseBase
7 |
8 |
9 | class Noni18nUrlsTestCase(URLTestCaseBase):
10 | urls = 'tests.urls_noni18n'
11 |
12 | def test_noni18n_page(self):
13 | url = reverse('onelang')
14 | self.assertEqual(url, '/onelang/')
15 | response = self.client.get(url)
16 | self.assertContains(response, 'One language content')
17 |
18 |
19 | class SettingsChangeTestCase(URLTestCaseBase):
20 |
21 | def setUp(self):
22 | super(URLTestCaseBase, self).tearDown()
23 | clear_url_caches()
24 | # don't reload urlconf here
25 |
26 | @override_settings(USE_I18N=False)
27 | def test_usei18n_false(self):
28 | response = self.client.get(reverse('home'))
29 | self.assertContains(response, 'Hello!')
30 | response = self.client.get(reverse('about'))
31 | self.assertContains(response, 'Information')
32 | response = self.client.get(reverse('onelang'))
33 | self.assertContains(response, 'One language content')
34 |
--------------------------------------------------------------------------------
/example/example/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for example project.
3 |
4 | This module contains the WSGI application used by Django's development server
5 | and any production WSGI deployments. It should expose a module-level variable
6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
7 | this application via the ``WSGI_APPLICATION`` setting.
8 |
9 | Usually you will have the standard Django WSGI application here, but it also
10 | might make sense to replace the whole Django WSGI application with a custom one
11 | that later delegates to the Django one. For example, you could introduce WSGI
12 | middleware here, or combine a Django application with an application of another
13 | framework.
14 |
15 | """
16 | import os
17 |
18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
19 |
20 | # This application object is used by any WSGI server configured to use this
21 | # file. This includes Django's development server, if the WSGI_APPLICATION
22 | # setting points here.
23 | from django.core.wsgi import get_wsgi_application
24 | application = get_wsgi_application()
25 |
26 | # Apply WSGI middleware here.
27 | # from helloworld.wsgi import HelloWorldApplication
28 | # application = HelloWorldApplication(application)
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, st4lk
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 | 1. Redistributions of source code must retain the above copyright
7 | notice, this list of conditions and the following disclaimer.
8 | 2. Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution..
11 | 3. Neither the name of the st4lk nor the
12 | names of its contributors may be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ''AS IS'' AND ANY
16 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY
19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/solid_i18n/urls.py:
--------------------------------------------------------------------------------
1 | import warnings
2 | from django import VERSION as DJANGO_VERSION
3 | from django.conf import settings
4 | from django.utils import lru_cache, six
5 | from django.core.urlresolvers import get_resolver
6 |
7 | from .urlresolvers import SolidLocaleRegexURLResolver
8 |
9 |
10 | def solid_i18n_patterns(prefix, *args):
11 | """
12 | Modified copy of django i18n_patterns.
13 | Adds the language code prefix to every *non default language* URL pattern
14 | within this function. This may only be used in the root URLconf,
15 | not in an included URLconf.
16 | Do not adds any language code prefix to default language URL pattern.
17 | Default language must be set in settings.LANGUAGE_CODE
18 | """
19 | if DJANGO_VERSION < (1, 10) and isinstance(prefix, six.string_types):
20 | from django.conf.urls import patterns
21 | warnings.warn(
22 | "Calling solid_i18n_patterns() with the `prefix` argument and with "
23 | "tuples instead of django.conf.urls.url() instances is deprecated and "
24 | "will no longer work in Django 2.0. Use a list of "
25 | "django.conf.urls.url() instances instead.",
26 | PendingDeprecationWarning, stacklevel=2
27 | )
28 | pattern_list = patterns(prefix, *args)
29 | else:
30 | pattern_list = [prefix] + list(args)
31 |
32 | if not settings.USE_I18N:
33 | return pattern_list
34 | return [SolidLocaleRegexURLResolver(pattern_list)]
35 |
36 |
37 | @lru_cache.lru_cache(maxsize=None)
38 | def is_language_prefix_patterns_used(urlconf):
39 | """
40 | Returns `True` if the `SolidLocaleRegexURLResolver` is used
41 | at root level of the urlpatterns, else it returns `False`.
42 | """
43 | for url_pattern in get_resolver(urlconf).url_patterns:
44 | if isinstance(url_pattern, SolidLocaleRegexURLResolver):
45 | return True
46 | return False
47 |
--------------------------------------------------------------------------------
/example/tests/base.py:
--------------------------------------------------------------------------------
1 | import sys
2 | try:
3 | from importlib import import_module
4 | except ImportError:
5 | from django.utils.importlib import import_module
6 | try:
7 | from importlib import reload # builtin reload deprecated since version 3.4
8 | except ImportError:
9 | try:
10 | from imp import reload
11 | except ImportError:
12 | pass
13 | from django.conf import settings
14 | from django.test import TestCase
15 | from django.core.urlresolvers import clear_url_caches
16 | from django.utils import translation
17 | try:
18 | from django.test.utils import TransRealMixin
19 | except ImportError:
20 | class TransRealMixin(object):
21 | pass
22 |
23 | from solid_i18n.urls import is_language_prefix_patterns_used
24 |
25 |
26 | def reload_urlconf(urlconf=None, urls_attr='urlpatterns'):
27 | # http://codeinthehole.com/writing/how-to-reload-djangos-url-config/
28 | if settings.ROOT_URLCONF in sys.modules:
29 | reload(sys.modules[settings.ROOT_URLCONF])
30 | return import_module(settings.ROOT_URLCONF)
31 |
32 |
33 | class URLTestCaseBase(TransRealMixin, TestCase):
34 |
35 | def setUp(self):
36 | # Make sure the cache is empty before we are doing our tests.
37 | super(URLTestCaseBase, self).tearDown()
38 | clear_url_caches()
39 | is_language_prefix_patterns_used.cache_clear()
40 | reload_urlconf()
41 |
42 | def tearDown(self):
43 | # Make sure we will leave an empty cache for other testcases.
44 | clear_url_caches()
45 | # Not sure why exactly, but TransRealMixin was removied in django 1.7
46 | # look https://github.com/django/django/commit/b87bc461c89f2006f0b27c7240fb488fac32bed1
47 | # Without it, tests will fail with django 1.7, because language
48 | # will be kept in _active.value between tests.
49 | # have to delete _active.value explicitly by calling deactivate
50 | # TODO: investigate this problem more deeply
51 | translation.deactivate()
52 | super(URLTestCaseBase, self).tearDown()
53 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | from setuptools import setup, find_packages
4 | from solid_i18n import __author__, __version__
5 |
6 |
7 | def __read(fname):
8 | try:
9 | return open(os.path.join(os.path.dirname(__file__), fname)).read()
10 | except IOError:
11 | return ''
12 |
13 | if sys.argv[-1] == 'publish':
14 | os.system('pandoc --from=markdown --to=rst --output=README.rst README.md')
15 | os.system('pandoc --from=markdown --to=rst --output=RELEASE_NOTES.rst RELEASE_NOTES.md')
16 | os.system('python setup.py sdist upload')
17 | os.system('python setup.py bdist_wheel upload')
18 | sys.exit()
19 |
20 | if sys.argv[-1] == 'generate_rst':
21 | os.system('pandoc --from=markdown --to=rst --output=README.rst README.md')
22 | os.system('pandoc --from=markdown --to=rst --output=RELEASE_NOTES.rst RELEASE_NOTES.md')
23 | sys.exit()
24 |
25 | if sys.argv[-1] == 'tag':
26 | print("Tagging the version on github:")
27 | os.system("git tag -a v%s -m 'version %s'" % (__version__, __version__))
28 | os.system("git push --tags")
29 | sys.exit()
30 |
31 | install_requires = __read('requirements.txt').split()
32 |
33 | setup(
34 | name='solid_i18n',
35 | author=__author__,
36 | author_email='alexevseev@gmail.com',
37 | version=__version__,
38 | description='Use default language for urls without language prefix (django)',
39 | long_description=__read('README.rst') + '\n\n' + __read('RELEASE_NOTES.rst'),
40 | platforms=('Any'),
41 | packages=find_packages(),
42 | install_requires=install_requires,
43 | keywords='django i18n urls solid redirects language default'.split(),
44 | include_package_data=True,
45 | license='BSD License',
46 | package_dir={'solid_i18n': 'solid_i18n'},
47 | url='https://github.com/st4lk/django-solid-i18n-urls',
48 | classifiers=[
49 | 'Environment :: Web Environment',
50 | 'Framework :: Django',
51 | 'Intended Audience :: Developers',
52 | 'License :: OSI Approved :: BSD License',
53 | 'Operating System :: OS Independent',
54 | 'Programming Language :: Python',
55 | 'Topic :: Utilities',
56 | ],
57 | )
58 |
--------------------------------------------------------------------------------
/example/locale/ru/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # SOME DESCRIPTIVE TITLE.
2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the PACKAGE package.
4 | # FIRST AUTHOR , YEAR.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2013-07-06 02:07+0400\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: FULL NAME \n"
14 | "Language-Team: LANGUAGE \n"
15 | "Language: \n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
20 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
21 |
22 | #: .\templates\about.html.py:5
23 | msgid "Solid urls about"
24 | msgstr "Информация solid urls"
25 |
26 | #: .\templates\about.html.py:9
27 | msgid "Information"
28 | msgstr "Информация"
29 |
30 | #: .\templates\base.html.py:29
31 | msgid "Django solid i18n urls example"
32 | msgstr "Пример django solid i18n urls"
33 |
34 | #: .\templates\base.html.py:32
35 | msgid "Home"
36 | msgstr "Домой"
37 |
38 | #: .\templates\base.html.py:33
39 | msgid "About"
40 | msgstr "О сайте"
41 |
42 | #: .\templates\base.html.py:34
43 | msgid "Link without i18n"
44 | msgstr "Ссылка без перевода"
45 |
46 | #: .\templates\home.html.py:6
47 | msgid "Solid urls home"
48 | msgstr "Домашняя страница Solid urls"
49 |
50 | #: .\templates\home.html.py:10
51 | msgid ""
52 | "Hello! This is an example home page for django-solid-i18n-urls package usage"
53 | msgstr ""
54 | "Здравствуйте! Это пример домашней страницы для использования пакета django-"
55 | "solid-i18n-urls"
56 |
57 | #: .\templates\home.html.py:11
58 | msgid ""
59 | "This example site supports two languages: english (default) and russian. As "
60 | "you can see, when accessing url without language prefix, default language is "
61 | "used. It is declared in settings.LANGUAGE_CODE. For more details visit"
62 | msgstr ""
63 | "Этот тестовый сайт поддерживает два языка: английский (по умолчанию) и "
64 | "русский. Как вы можете видеть, проходя по ссылке без языкового префикса, "
65 | "используется язык по умолчанию. Он объявлен в settings.LANGUAGE_CODE. Для "
66 | "большей информации пройдите по ссылке"
67 |
68 | #: .\templates\home.html.py:12
69 | msgid "Languages can be switched using buttons in right-up corner"
70 | msgstr "Язык можно переключить используя кнопки в правом верхнем углу"
71 |
72 | #~ msgid "One language link"
73 | #~ msgstr "Ссылка без перевода"
74 |
75 | #~ msgid "Not translatable link"
76 | #~ msgstr "Ссылка без перевода"
77 |
78 | #~ msgid "Package url"
79 | #~ msgstr "Страница пакета"
80 |
81 | #~ msgid "You can visit about page here"
82 | #~ msgstr "Вы можете посетить страницу about здесь"
83 |
84 | #~ msgid "About page link"
85 | #~ msgstr "Ссылка на страницу about"
86 |
--------------------------------------------------------------------------------
/solid_i18n/urlresolvers.py:
--------------------------------------------------------------------------------
1 | import re
2 | from django.utils.translation import get_language
3 | from django.core.urlresolvers import LocaleRegexURLResolver, clear_url_caches
4 | from django.conf import settings
5 | from .memory import get_language_from_path
6 |
7 |
8 | class SolidLocaleRegexURLResolver(LocaleRegexURLResolver):
9 | """
10 | A URL resolver that always matches the active language code as URL prefix,
11 | but for default language non prefix is used.
12 |
13 | Rather than taking a regex argument, we just override the ``regex``
14 | function to always return the active language-code as regex.
15 | """
16 | def __init__(self, urlconf_name, *args, **kwargs):
17 | super(SolidLocaleRegexURLResolver, self).__init__(
18 | urlconf_name, *args, **kwargs)
19 | self.compiled_with_default = False
20 |
21 | @property
22 | def regex(self):
23 | """
24 | For non-default language always returns regex with langauge prefix.
25 | For default language returns either '', eigher '^{lang_code}/',
26 | depending on SOLID_I18N_HANDLE_DEFAULT_PREFIX and request url.
27 |
28 | If SOLID_I18N_HANDLE_DEFAULT_PREFIX == True and default langauge
29 | prefix is present in url, all other urls will be reversed with default
30 | prefix.
31 | Otherwise, all other urls will be reversed without default langauge
32 | prefix.
33 | """
34 | language_code = get_language()
35 | handle_default_prefix = getattr(settings, 'SOLID_I18N_HANDLE_DEFAULT_PREFIX', False)
36 | if language_code not in self._regex_dict:
37 | if language_code != settings.LANGUAGE_CODE:
38 | regex = '^%s/' % language_code
39 | elif handle_default_prefix:
40 | if get_language_from_path() == settings.LANGUAGE_CODE:
41 | self.compiled_with_default = True
42 | regex = '^%s/' % language_code
43 | else:
44 | self.compiled_with_default = False
45 | regex = ''
46 | else:
47 | regex = ''
48 | self._regex_dict[language_code] = re.compile(regex, re.UNICODE)
49 | elif handle_default_prefix and language_code == settings.LANGUAGE_CODE:
50 | language_from_path = get_language_from_path()
51 | regex = None
52 | if self.compiled_with_default and not language_from_path:
53 | # default language is compiled with prefix, but now client
54 | # requests the url without prefix. So compile other urls
55 | # without prefix.
56 | regex = ''
57 | self.compiled_with_default = False
58 | elif not self.compiled_with_default and language_from_path == settings.LANGUAGE_CODE:
59 | # default language is compiled without prefix, but now client
60 | # requests the url with prefix. So compile other urls
61 | # with prefix.
62 | regex = '^%s/' % language_code
63 | self.compiled_with_default = True
64 | if regex is not None:
65 | clear_url_caches()
66 | self._regex_dict[language_code] = re.compile(regex, re.UNICODE)
67 | return self._regex_dict[language_code]
68 |
--------------------------------------------------------------------------------
/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
1 | solid_i18n release notes
2 | ========================
3 |
4 | v1.4.2
5 | ------
6 | - Remove requirement for Django < 1.11 in order to use package on Django 1.11.
7 |
8 | Issues: [#43](https://github.com/st4lk/django-solid-i18n-urls/issues/43)
9 |
10 |
11 | v1.4.1
12 | ------
13 | - Fix minor issue with SolidLocaleRegexURLResolver
14 |
15 | Issues: [#40](https://github.com/st4lk/django-solid-i18n-urls/issues/40)
16 |
17 |
18 | v1.4.0
19 | ------
20 | - Add django 1.10 support
21 | - Add deprecation notice
22 |
23 | Issues: [#35](https://github.com/st4lk/django-solid-i18n-urls/issues/35)
24 |
25 | v1.3.0
26 | ------
27 | - Add SOLID_I18N_PREFIX_STRICT setting to handle urls starting with language code
28 |
29 | Issues: [#34](https://github.com/st4lk/django-solid-i18n-urls/issues/34)
30 |
31 | v1.2.0
32 | ------
33 | - Add django 1.9 support
34 | - Drop django 1.4 support
35 | - Drop python 3.2 support
36 | - Simplify tox settings
37 |
38 | Issues: [#32](https://github.com/st4lk/django-solid-i18n-urls/issues/32), [#23](https://github.com/st4lk/django-solid-i18n-urls/issues/23 ), [#21](https://github.com/st4lk/django-solid-i18n-urls/issues/21)
39 |
40 | v1.1.1
41 | ------
42 | - fix django 1.8 `AppRegistryNotReady("Apps aren't loaded yet.")`
43 |
44 | Issues: [#29](https://github.com/st4lk/django-solid-i18n-urls/issues/29)
45 |
46 | v1.1.0
47 | ------
48 |
49 | - Use 301 redirect in case of `SOLID_I18N_DEFAULT_PREFIX_REDIRECT`
50 | - Upload wheel
51 |
52 | Issues: [#24](https://github.com/st4lk/django-solid-i18n-urls/issues/24), [#20](https://github.com/st4lk/django-solid-i18n-urls/issues/20)
53 |
54 | v1.0.0
55 | ------
56 |
57 | - Add django 1.8 support
58 |
59 | Issues: [#8](https://github.com/st4lk/django-solid-i18n-urls/issues/8), [#19](https://github.com/st4lk/django-solid-i18n-urls/issues/19)
60 |
61 | v0.9.1
62 | ------
63 |
64 | - fix working with [set_language](https://docs.djangoproject.com/en/dev/topics/i18n/translation/#set-language-redirect-view) and `SOLID_I18N_HANDLE_DEFAULT_PREFIX = True`
65 |
66 | Issues: [#17](https://github.com/st4lk/django-solid-i18n-urls/issues/17)
67 |
68 | v0.8.1
69 | ------
70 |
71 | - fix url reverse in case of `SOLID_I18N_HANDLE_DEFAULT_PREFIX = True`
72 | - simplify django version checking
73 |
74 | Issues: [#13](https://github.com/st4lk/django-solid-i18n-urls/issues/13), [#14](https://github.com/st4lk/django-solid-i18n-urls/issues/14)
75 |
76 | v0.7.1
77 | ------
78 |
79 | - add settings `SOLID_I18N_HANDLE_DEFAULT_PREFIX` and `SOLID_I18N_DEFAULT_PREFIX_REDIRECT`
80 |
81 | Issues: [#12](https://github.com/st4lk/django-solid-i18n-urls/issues/12)
82 |
83 | v0.6.1
84 | ------
85 |
86 | - handle urls with default language prefix explicitly set
87 |
88 | Issues: [#10](https://github.com/st4lk/django-solid-i18n-urls/issues/10)
89 |
90 | v0.5.1
91 | ------
92 |
93 | - add django 1.7 support
94 | - add python 3.4 support
95 |
96 | Issues: [#6](https://github.com/st4lk/django-solid-i18n-urls/issues/6)
97 |
98 | v0.4.3
99 | ------
100 |
101 | - fix http header 'Vary Accept-Language'
102 |
103 | Issues: [#4](https://github.com/st4lk/django-solid-i18n-urls/issues/4)
104 |
105 | v0.4.2
106 | ------
107 |
108 | - stop downgrading Django from 1.6.x to 1.6
109 | - include requirements.txt in distribution
110 | - minor docs updates
111 |
112 | Issues: [#3](https://github.com/st4lk/django-solid-i18n-urls/issues/3)
113 |
114 | v0.4.1
115 | ------
116 | Add python 3.2, 3.3 support.
117 |
118 | Issues: [#2](https://github.com/st4lk/django-solid-i18n-urls/issues/2)
119 |
120 | v0.3.1
121 | ------
122 |
123 | Add django 1.6 support
124 |
125 | v0.2.1
126 | ------
127 |
128 | Update README and data for pypi
129 |
130 | v0.2
131 | ----
132 |
133 | First version in pypi
134 |
--------------------------------------------------------------------------------
/example/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
4 | {% get_current_language as LANGUAGE_CODE %}
5 |
6 |
7 |
8 | {% block title %}{% endblock title %}
9 |
10 |
11 |
12 |
32 |
33 |
34 |
35 |
36 |