diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index 17821367a6..edb297404a 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -1,15 +1,16 @@ +from __future__ import with_statement import re -from django.template import Node, Variable, VariableNode -from django.template import TemplateSyntaxError, TokenParser, Library -from django.template import TOKEN_TEXT, TOKEN_VAR +from django.template import (Node, Variable, TemplateSyntaxError, + TokenParser, Library, TOKEN_TEXT, TOKEN_VAR) from django.template.base import _render_value_in_context -from django.utils import translation -from django.utils.encoding import force_unicode from django.template.defaulttags import token_kwargs +from django.utils import translation + register = Library() + class GetAvailableLanguagesNode(Node): def __init__(self, variable): self.variable = variable @@ -19,6 +20,7 @@ class GetAvailableLanguagesNode(Node): context[self.variable] = [(k, translation.ugettext(v)) for k, v in settings.LANGUAGES] return '' + class GetLanguageInfoNode(Node): def __init__(self, lang_code, variable): self.lang_code = Variable(lang_code) @@ -29,6 +31,7 @@ class GetLanguageInfoNode(Node): context[self.variable] = translation.get_language_info(lang_code) return '' + class GetLanguageInfoListNode(Node): def __init__(self, languages, variable): self.languages = Variable(languages) @@ -47,6 +50,7 @@ class GetLanguageInfoListNode(Node): context[self.variable] = [self.get_language_info(lang) for lang in langs] return '' + class GetCurrentLanguageNode(Node): def __init__(self, variable): self.variable = variable @@ -55,6 +59,7 @@ class GetCurrentLanguageNode(Node): context[self.variable] = translation.get_language() return '' + class GetCurrentLanguageBidiNode(Node): def __init__(self, variable): self.variable = variable @@ -63,6 +68,7 @@ class GetCurrentLanguageBidiNode(Node): context[self.variable] = translation.get_language_bidi() return '' + class TranslateNode(Node): def __init__(self, filter_expression, noop): self.noop = noop @@ -75,6 +81,7 @@ class TranslateNode(Node): output = self.filter_expression.resolve(context) return _render_value_in_context(output, context) + class BlockTranslateNode(Node): def __init__(self, extra_context, singular, plural=None, countervar=None, counter=None): @@ -117,6 +124,18 @@ class BlockTranslateNode(Node): context.pop() return result % data + +class LanguageNode(Node): + def __init__(self, nodelist, language): + self.nodelist = nodelist + self.language = language + + def render(self, context): + with translation.override(self.language.resolve(context)): + output = self.nodelist.render(context) + return output + + @register.tag("get_available_languages") def do_get_available_languages(parser, token): """ @@ -271,9 +290,9 @@ def do_translate(parser, token): # where single quote use is supported. if value[0] == "'": pos = None - m = re.match("^'([^']+)'(\|.*$)",value) + m = re.match("^'([^']+)'(\|.*$)", value) if m: - value = '"%s"%s' % (m.group(1).replace('"','\\"'),m.group(2)) + value = '"%s"%s' % (m.group(1).replace('"','\\"'), m.group(2)) elif value[-1] == "'": value = '"%s"' % value[1:-1].replace('"','\\"') @@ -366,3 +385,23 @@ def do_block_translate(parser, token): return BlockTranslateNode(extra_context, singular, plural, countervar, counter) + +@register.tag +def language(parser, token): + """ + This will enable the given language just for this block. + + Usage:: + + {% language "de" %} + This is {{ bar }} and {{ boo }}. + {% endlanguage %} + + """ + bits = token.split_contents() + if len(bits) < 2: + raise TemplateSyntaxError("'%s' takes one argument (language)" % bits[0]) + language = parser.compile_filter(bits[1]) + nodelist = parser.parse(('endlanguage',)) + parser.delete_first_token() + return LanguageNode(nodelist, language) diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 1f880262fa..f06dbe1506 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -28,6 +28,12 @@ This mapping can be as short or as long as needed. It can reference other mappings. And, because it's pure Python code, it can be constructed dynamically. +.. versionadded:: 1.4 + + Django also allows to translate URLs according to the active language. + This process is described in the + :ref:`internationalization docs `. + .. _how-django-processes-a-request: How Django processes a request diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index 4850f7e7bc..5e9c1cd3b0 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -887,6 +887,32 @@ return the URL in the active language. Example:: that a carelessly translated URL causes a collision with a non-translated URL pattern. +.. _reversing_in_templates: + +.. templatetag:: language + +Reversing in templates +---------------------- + +If localized URLs get reversed in templates they always use the current +language. To link to a URL in another language use the ``language`` +template tag. It enables the given language in the enclosed template section: + +.. code-block:: html+django + + {% load i18n %} + + {% get_available_languages as languages %} + + {% trans "View this category in:" %} + {% for lang_code, lang_name in languages %} + {% language lang_code %} + {{ lang_name }} + {% endlanguage %} + {% endfor %} + +The :ttag:`language` tag expects the language code as the only argument. + .. _set_language-redirect-view: The ``set_language`` redirect view diff --git a/tests/regressiontests/i18n/patterns/tests.py b/tests/regressiontests/i18n/patterns/tests.py index 091eb0f233..724eb03e27 100644 --- a/tests/regressiontests/i18n/patterns/tests.py +++ b/tests/regressiontests/i18n/patterns/tests.py @@ -6,6 +6,7 @@ from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse, clear_url_caches from django.test import TestCase from django.test.utils import override_settings +from django.template import Template, Context from django.utils import translation @@ -241,3 +242,44 @@ class URLResponseTests(URLTestCaseBase): self.assertEqual(response.status_code, 200) self.assertEqual(response['content-language'], 'pt-br') self.assertEqual(response.context['LANGUAGE_CODE'], 'pt-br') + + +class URLTagTests(URLTestCaseBase): + """ + Test if the language tag works. + """ + def test_strings_only(self): + t = Template("""{% load i18n %} + {% language 'nl' %}{% url no-prefix-translated %}{% endlanguage %} + {% language 'pt-br' %}{% url no-prefix-translated %}{% endlanguage %}""") + self.assertEqual(t.render(Context({})).strip().split(), + [u'/vertaald/', u'/traduzidos/']) + + def test_context(self): + ctx = Context({'lang1':'nl', 'lang2':'pt-br'}) + tpl = Template("""{% load i18n %} + {% language lang1 %}{% url no-prefix-translated %}{% endlanguage %} + {% language lang2 %}{% url no-prefix-translated %}{% endlanguage %}""") + self.assertEqual(tpl.render(ctx).strip().split(), + [u'/vertaald/', u'/traduzidos/']) + + def test_args(self): + tpl = Template("""{% load i18n %} + {% language 'nl' %}{% url no-prefix-translated-slug 'apo' %}{% endlanguage %} + {% language 'pt-br' %}{% url no-prefix-translated-slug 'apo' %}{% endlanguage %}""") + self.assertEqual(tpl.render(Context({})).strip().split(), + [u'/vertaald/apo/', u'/traduzidos/apo/']) + + def test_kwargs(self): + tpl = Template("""{% load i18n %} + {% language 'nl' %}{% url no-prefix-translated-slug slug='apo' %}{% endlanguage %} + {% language 'pt-br' %}{% url no-prefix-translated-slug slug='apo' %}{% endlanguage %}""") + self.assertEqual(tpl.render(Context({})).strip().split(), + [u'/vertaald/apo/', u'/traduzidos/apo/']) + + def test_future_kwargs(self): + tpl = Template("""{% load i18n %}{% load url from future %} + {% language 'nl' %}{% url 'no-prefix-translated-slug' slug='apo' %}{% endlanguage %} + {% language 'pt-br' %}{% url 'no-prefix-translated-slug' slug='apo' %}{% endlanguage %}""") + self.assertEqual(tpl.render(Context({})).strip().split(), + [u'/vertaald/apo/', u'/traduzidos/apo/'])