diff --git a/AUTHORS b/AUTHORS index e1eaded9c3..bc45ce7809 100644 --- a/AUTHORS +++ b/AUTHORS @@ -501,6 +501,7 @@ answer newbie questions, and generally made Django that much better: Joel Watts Lakin Wecker Chris Wesseling + Benjamin Wohlwend James Wheare Mike Wiacek Frank Wierzbicki diff --git a/django/contrib/gis/templates/gis/google/google-map.js b/django/contrib/gis/templates/gis/google/google-map.js index 06f11e35f3..5c1c8132b9 100644 --- a/django/contrib/gis/templates/gis/google/google-map.js +++ b/django/contrib/gis/templates/gis/google/google-map.js @@ -1,6 +1,8 @@ +{% load l10n %} {% autoescape off %} -{% block vars %}var geodjango = {};{% for icon in icons %} -var {{ icon.varname }} = new GIcon(G_DEFAULT_ICON); +{% localize off %} +{% block vars %}var geodjango = {};{% for icon in icons %} +var {{ icon.varname }} = new GIcon(G_DEFAULT_ICON); {% if icon.image %}{{ icon.varname }}.image = "{{ icon.image }}";{% endif %} {% if icon.shadow %}{{ icon.varname }}.shadow = "{{ icon.shadow }}";{% endif %} {% if icon.shadowsize %}{{ icon.varname }}.shadowSize = new GSize({{ icon.shadowsize.0 }}, {{ icon.shadowsize.1 }});{% endif %} {% if icon.iconanchor %}{{ icon.varname }}.iconAnchor = new GPoint({{ icon.iconanchor.0 }}, {{ icon.iconanchor.1 }});{% endif %} {% if icon.iconsize %}{{ icon.varname }}.iconSize = new GSize({{ icon.iconsize.0 }}, {{ icon.iconsize.1 }});{% endif %} @@ -32,4 +34,4 @@ var {{ icon.varname }} = new GIcon(G_DEFAULT_ICON); alert("Sorry, the Google Maps API is not compatible with this browser."); } } -{% endblock load %}{% endblock functions %}{% endautoescape %} +{% endblock load %}{% endblock functions %}{% endlocalize %}{% endautoescape %} diff --git a/django/template/__init__.py b/django/template/__init__.py index c3167861fd..74403584be 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -825,7 +825,7 @@ def _render_value_in_context(value, context): means escaping, if required, and conversion to a unicode object. If value is a string, it is expected to have already been translated. """ - value = localize(value) + value = localize(value, use_l10n=context.use_l10n) value = force_unicode(value) if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData): return escape(value) diff --git a/django/template/context.py b/django/template/context.py index 323c1446b2..a5b417cdd3 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -66,8 +66,9 @@ class BaseContext(object): class Context(BaseContext): "A stack container for variable context" - def __init__(self, dict_=None, autoescape=True, current_app=None): + def __init__(self, dict_=None, autoescape=True, current_app=None, use_l10n=None): self.autoescape = autoescape + self.use_l10n = use_l10n self.current_app = current_app self.render_context = RenderContext() super(Context, self).__init__(dict_) @@ -139,8 +140,8 @@ class RequestContext(Context): Additional processors can be specified as a list of callables using the "processors" keyword argument. """ - def __init__(self, request, dict=None, processors=None, current_app=None): - Context.__init__(self, dict, current_app=current_app) + def __init__(self, request, dict=None, processors=None, current_app=None, use_l10n=None): + Context.__init__(self, dict, current_app=current_app, use_l10n=use_l10n) if processors is None: processors = () else: diff --git a/django/template/debug.py b/django/template/debug.py index c21fb50b3e..aa42b2b5ca 100644 --- a/django/template/debug.py +++ b/django/template/debug.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError from django.utils.encoding import force_unicode from django.utils.html import escape @@ -87,7 +88,7 @@ class DebugVariableNode(VariableNode): def render(self, context): try: output = self.filter_expression.resolve(context) - output = localize(output) + output = localize(value, use_l10n=use_l10n) output = force_unicode(output) except TemplateSyntaxError, e: if not hasattr(e, 'source'): diff --git a/django/templatetags/l10n.py b/django/templatetags/l10n.py new file mode 100644 index 0000000000..5191e6936d --- /dev/null +++ b/django/templatetags/l10n.py @@ -0,0 +1,67 @@ +from django.conf import settings +from django.template import Node +from django.template import TemplateSyntaxError, Library +from django.utils import formats +from django.utils.encoding import force_unicode + + +register = Library() + +def localize(value): + """ + Forces a value to be rendered as a localized value, + regardless of the value of ``settings.USE_L10N``. + """ + return force_unicode(formats.localize(value, use_l10n=True)) +localize.is_safe = False + +def unlocalize(value): + """ + Forces a value to be rendered as a non-localized value, + regardless of the value of ``settings.USE_L10N``. + """ + return force_unicode(value) +unlocalize.is_safe = False + +class LocalizeNode(Node): + def __init__(self, nodelist, use_l10n): + self.nodelist = nodelist + self.use_l10n = use_l10n + + def __repr__(self): + return "" + + def render(self, context): + old_setting = context.use_l10n + context.use_l10n = self.use_l10n + output = self.nodelist.render(context) + context.use_l10n = old_setting + return output + +@register.tag('localize') +def localize_tag(parser, token): + """ + Forces or prevents localization of values, regardless of the value of + `settings.USE_L10N`. + + Sample usage:: + + {% localize off %} + var pi = {{ 3.1415 }}; + {% endlocalize %} + + """ + use_l10n = None + bits = list(token.split_contents()) + if len(bits) == 1: + use_l10n = True + elif len(bits) > 2 or bits[1] not in ('on', 'off'): + raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % bits[0]) + else: + use_l10n = bits[1] == 'on' + nodelist = parser.parse(('endlocalize',)) + parser.delete_first_token() + return LocalizeNode(nodelist, use_l10n) + +register.filter(localize) +register.filter(unlocalize) diff --git a/django/utils/formats.py b/django/utils/formats.py index e64cc4e95a..18fcbb600f 100644 --- a/django/utils/formats.py +++ b/django/utils/formats.py @@ -41,14 +41,17 @@ def get_format_modules(reverse=False): modules.reverse() return modules -def get_format(format_type, lang=None): +def get_format(format_type, lang=None, use_l10n=None): """ For a specific format type, returns the format for the current language (locale), defaults to the format in the settings. format_type is the name of the format, e.g. 'DATE_FORMAT' + + If use_l10n is provided and is not None, that will force the value to + be localized (or not), overriding the value of settings.USE_L10N. """ format_type = smart_str(format_type) - if settings.USE_L10N: + if use_l10n or (use_l10n is None and settings.USE_L10N): if lang is None: lang = get_language() cache_key = (format_type, lang) @@ -65,48 +68,60 @@ def get_format(format_type, lang=None): _format_cache[cache_key] = None return getattr(settings, format_type) -def date_format(value, format=None): +def date_format(value, format=None, use_l10n=None): """ Formats a datetime.date or datetime.datetime object using a localizable format - """ - return dateformat.format(value, get_format(format or 'DATE_FORMAT')) -def time_format(value, format=None): + If use_l10n is provided and is not None, that will force the value to + be localized (or not), overriding the value of settings.USE_L10N. + """ + return dateformat.format(value, get_format(format or 'DATE_FORMAT', use_l10n=use_l10n)) + +def time_format(value, format=None, use_l10n=None): """ Formats a datetime.time object using a localizable format - """ - return dateformat.time_format(value, get_format(format or 'TIME_FORMAT')) -def number_format(value, decimal_pos=None): + If use_l10n is provided and is not None, that will force the value to + be localized (or not), overriding the value of settings.USE_L10N. + """ + return dateformat.time_format(value, get_format(format or 'TIME_FORMAT', use_l10n=use_l10n)) + +def number_format(value, decimal_pos=None, use_l10n=None): """ Formats a numeric value using localization settings + + If use_l10n is provided and is not None, that will force the value to + be localized (or not), overriding the value of settings.USE_L10N. """ - if settings.USE_L10N: + if use_l10n or (use_l10n is None and settings.USE_L10N): lang = get_language() else: lang = None return numberformat.format( value, - get_format('DECIMAL_SEPARATOR', lang), + get_format('DECIMAL_SEPARATOR', lang, use_l10n=use_l10n), decimal_pos, - get_format('NUMBER_GROUPING', lang), - get_format('THOUSAND_SEPARATOR', lang), + get_format('NUMBER_GROUPING', lang, use_l10n=use_l10n), + get_format('THOUSAND_SEPARATOR', lang, use_l10n=use_l10n), ) -def localize(value): +def localize(value, use_l10n=None): """ Checks if value is a localizable type (date, number...) and returns it - formatted as a string using current locale format + formatted as a string using current locale format. + + If use_l10n is provided and is not None, that will force the value to + be localized (or not), overriding the value of settings.USE_L10N. """ if isinstance(value, (decimal.Decimal, float, int, long)): - return number_format(value) + return number_format(value, use_l10n=use_l10n) elif isinstance(value, datetime.datetime): - return date_format(value, 'DATETIME_FORMAT') + return date_format(value, 'DATETIME_FORMAT', use_l10n=use_l10n) elif isinstance(value, datetime.date): - return date_format(value) + return date_format(value, use_l10n=use_l10n) elif isinstance(value, datetime.time): - return time_format(value, 'TIME_FORMAT') + return time_format(value, 'TIME_FORMAT', use_l10n=use_l10n) else: return value diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 8e166d802f..7bb4481904 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -2113,3 +2113,12 @@ Django templates. It is slightly different from the libraries described above because you don't need to add any application to the ``INSTALLED_APPS`` setting but rather set :setting:`USE_I18N` to True, then loading it with ``{% load i18n %}``. See :ref:`specifying-translation-strings-in-template-code`. + +l10n +~~~~ + +Provides a couple of templatetags that allow control over the localization of +values in Django templates. It is slightly different from the libraries described +above because you don't need to add any application to the ``INSTALLED_APPS``; +you only need to load the library using ``{% load l10n %}``. See +:ref:`topic-l10n-templates`. diff --git a/docs/topics/i18n/localization.txt b/docs/topics/i18n/localization.txt index 38d74e68e3..df89851039 100644 --- a/docs/topics/i18n/localization.txt +++ b/docs/topics/i18n/localization.txt @@ -2,11 +2,13 @@ Localization ============ -This document covers two localization-related topics: `Creating language -files`_ and `locale aware date, time and numbers input/output in forms`_ +This document covers three localization-related topics: `Creating language +files`_ , `locale aware date, time and numbers input/output in forms`_, +and `controlling localization in templates`_. .. _`Creating language files`: how-to-create-language-files_ .. _`locale aware date, time and numbers input/output in forms`: format-localization_ +.. _`controlling localization in templates`: topic-l10n-templates .. seealso:: @@ -315,3 +317,94 @@ where :file:`formats.py` contains custom format definitions. For example:: to use a space as a thousand separator, instead of the default for English, a comma. + +.. topic-l10n-templates: + +Controlling localization in templates +===================================== + +When you have enabled localization using :setting:`USE_L10N`, Django +will try to use a locale specific format whenever it outputs a value +in a template. + +However, it may not always be appropriate to use localized values -- +for example, if you're outputting Javascript or XML that is designed +to be machine-readable, you will always want unlocalized values. You +may also want to use localization in selected templates, rather than +using localization everywhere. + +To allow for fine control over the use of localization, Django +provides a the ``l10n`` template library that contains the following +tags and filters. + +Template tags +------------- + +.. templatetag:: localize + +localize +~~~~~~~~ + +.. versionadded:: 1.3 + +Enables or disables localization of template variables in the +contained block. + +This tag allows a more fine grained control of localization than +:setting:`USE_L10N`. + +To activate or deactivate localization for a template block, use:: + + {% localize on %} + {{ value }} + {% endlocalize %} + + {% localize off %} + {{ value }} + {% endlocalize %} + +.. note:: + + The value of :setting:`USE_L10N` is not respected inside of a + `{% localize %}` block. + +See :tfilter:`localized` and :tfilter:`unlocalized` for a template filter that will +do the same job on a per-variable basis. + +Template filters +---------------- + +.. templatefilter:: localize + +localize +~~~~~~~~ + +.. versionadded:: 1.3 + +Forces localization of a single value. + +For example:: + + {{ value|localize }} + +To disable localization on a single value, use :tfilter:`unlocalize`. To control +localization over a large section of a template, use the :ttag:`localize` template +tag. + + +.. templatefilter:: unlocalize + +unlocalize +~~~~~~~~~~ + +.. versionadded:: 1.3 + +Forces a single value to be printed without localization. + +For example:: + + {{ value|unlocalize }} + +To force localization of a single value, use :tfilter:`localize`. To +control localization over a large section of a template, use the +:ttag:`localize` template tag. diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 4aa52b6b55..bac77b0dbc 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -453,6 +453,33 @@ class FormattingTests(TestCase): settings.FORMAT_MODULE_PATH = old_format_module_path deactivate() + def test_localize_templatetag_and_filter(self): + """ + Tests the {% localize %} templatetag + """ + context = Context({'value': 3.14 }) + template1 = Template("{% load l10n %}{% localize %}{{ value }}{% endlocalize %};{% localize on %}{{ value }}{% endlocalize %}") + template2 = Template("{% load l10n %}{{ value }};{% localize off %}{{ value }};{% endlocalize %}{{ value }}") + template3 = Template('{% load l10n %}{{ value }};{{ value|unlocalize }}') + template4 = Template('{% load l10n %}{{ value }};{{ value|localize }}') + output1 = '3,14;3,14' + output2 = '3,14;3.14;3,14' + output3 = '3,14;3.14' + output4 = '3.14;3,14' + old_localize = settings.USE_L10N + try: + activate('de') + settings.USE_L10N = False + self.assertEqual(template1.render(context), output1) + self.assertEqual(template4.render(context), output4) + settings.USE_L10N = True + self.assertEqual(template1.render(context), output1) + self.assertEqual(template2.render(context), output2) + self.assertEqual(template3.render(context), output3) + finally: + deactivate() + settings.USE_L10N = old_localize + class MiscTests(TestCase): def test_parse_spec_http_header(self):