Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #14181 -- Added a template tag and filters to allow localizatio…

…n to be disabled in a template. Thanks to Benjamin Wohlwend for the work on the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14395 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit ccc49029b8d84cf3eaaa3593df6370329f7b14e1 1 parent 269e921
Russell Keith-Magee authored October 29, 2010
1  AUTHORS
@@ -501,6 +501,7 @@ answer newbie questions, and generally made Django that much better:
501 501
     Joel Watts <joel@joelwatts.com>
502 502
     Lakin Wecker <lakin@structuredabstraction.com>
503 503
     Chris Wesseling <Chris.Wesseling@cwi.nl>
  504
+    Benjamin Wohlwend <piquadrat@gmail.com>
504 505
     James Wheare <django@sparemint.com>
505 506
     Mike Wiacek <mjwiacek@google.com>
506 507
     Frank Wierzbicki
8  django/contrib/gis/templates/gis/google/google-map.js
... ...
@@ -1,6 +1,8 @@
  1
+{% load l10n %}
1 2
 {% autoescape off %}
2  
-{% block vars %}var geodjango = {};{% for icon in icons %} 
3  
-var {{ icon.varname }} = new GIcon(G_DEFAULT_ICON); 
  3
+{% localize off %}
  4
+{% block vars %}var geodjango = {};{% for icon in icons %}
  5
+var {{ icon.varname }} = new GIcon(G_DEFAULT_ICON);
4 6
 {% if icon.image %}{{ icon.varname }}.image = "{{ icon.image }}";{% endif %}
5 7
 {% if icon.shadow %}{{ icon.varname }}.shadow = "{{ icon.shadow }}";{% endif %} {% if icon.shadowsize %}{{ icon.varname }}.shadowSize = new GSize({{ icon.shadowsize.0 }}, {{ icon.shadowsize.1 }});{% endif %}
6 8
 {% 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);
32 34
     alert("Sorry, the Google Maps API is not compatible with this browser.");
33 35
   }
34 36
 }
35  
-{% endblock load %}{% endblock functions %}{% endautoescape %}
  37
+{% endblock load %}{% endblock functions %}{% endlocalize %}{% endautoescape %}
2  django/template/__init__.py
@@ -825,7 +825,7 @@ def _render_value_in_context(value, context):
825 825
     means escaping, if required, and conversion to a unicode object. If value
826 826
     is a string, it is expected to have already been translated.
827 827
     """
828  
-    value = localize(value)
  828
+    value = localize(value, use_l10n=context.use_l10n)
829 829
     value = force_unicode(value)
830 830
     if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
831 831
         return escape(value)
7  django/template/context.py
@@ -66,8 +66,9 @@ def get(self, key, otherwise=None):
66 66
 
67 67
 class Context(BaseContext):
68 68
     "A stack container for variable context"
69  
-    def __init__(self, dict_=None, autoescape=True, current_app=None):
  69
+    def __init__(self, dict_=None, autoescape=True, current_app=None, use_l10n=None):
70 70
         self.autoescape = autoescape
  71
+        self.use_l10n = use_l10n
71 72
         self.current_app = current_app
72 73
         self.render_context = RenderContext()
73 74
         super(Context, self).__init__(dict_)
@@ -139,8 +140,8 @@ class RequestContext(Context):
139 140
     Additional processors can be specified as a list of callables
140 141
     using the "processors" keyword argument.
141 142
     """
142  
-    def __init__(self, request, dict=None, processors=None, current_app=None):
143  
-        Context.__init__(self, dict, current_app=current_app)
  143
+    def __init__(self, request, dict=None, processors=None, current_app=None, use_l10n=None):
  144
+        Context.__init__(self, dict, current_app=current_app, use_l10n=use_l10n)
144 145
         if processors is None:
145 146
             processors = ()
146 147
         else:
3  django/template/debug.py
... ...
@@ -1,3 +1,4 @@
  1
+from django.conf import settings
1 2
 from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError
2 3
 from django.utils.encoding import force_unicode
3 4
 from django.utils.html import escape
@@ -87,7 +88,7 @@ class DebugVariableNode(VariableNode):
87 88
     def render(self, context):
88 89
         try:
89 90
             output = self.filter_expression.resolve(context)
90  
-            output = localize(output)
  91
+            output = localize(value, use_l10n=use_l10n)
91 92
             output = force_unicode(output)
92 93
         except TemplateSyntaxError, e:
93 94
             if not hasattr(e, 'source'):
67  django/templatetags/l10n.py
... ...
@@ -0,0 +1,67 @@
  1
+from django.conf import settings
  2
+from django.template import Node
  3
+from django.template import TemplateSyntaxError, Library
  4
+from django.utils import formats
  5
+from django.utils.encoding import force_unicode
  6
+
  7
+
  8
+register = Library()
  9
+
  10
+def localize(value):
  11
+    """
  12
+    Forces a value to be rendered as a localized value,
  13
+    regardless of the value of ``settings.USE_L10N``.
  14
+    """
  15
+    return force_unicode(formats.localize(value, use_l10n=True))
  16
+localize.is_safe = False
  17
+
  18
+def unlocalize(value):
  19
+    """
  20
+    Forces a value to be rendered as a non-localized value,
  21
+    regardless of the value of ``settings.USE_L10N``.
  22
+    """
  23
+    return force_unicode(value)
  24
+unlocalize.is_safe = False
  25
+
  26
+class LocalizeNode(Node):
  27
+    def __init__(self, nodelist, use_l10n):
  28
+        self.nodelist = nodelist
  29
+        self.use_l10n = use_l10n
  30
+
  31
+    def __repr__(self):
  32
+        return "<LocalizeNode>"
  33
+
  34
+    def render(self, context):
  35
+        old_setting = context.use_l10n
  36
+        context.use_l10n = self.use_l10n
  37
+        output = self.nodelist.render(context)
  38
+        context.use_l10n = old_setting
  39
+        return output
  40
+
  41
+@register.tag('localize')
  42
+def localize_tag(parser, token):
  43
+    """
  44
+    Forces or prevents localization of values, regardless of the value of
  45
+    `settings.USE_L10N`.
  46
+
  47
+    Sample usage::
  48
+
  49
+        {% localize off %}
  50
+            var pi = {{ 3.1415 }};
  51
+        {% endlocalize %}
  52
+
  53
+    """
  54
+    use_l10n = None
  55
+    bits = list(token.split_contents())
  56
+    if len(bits) == 1:
  57
+        use_l10n = True
  58
+    elif len(bits) > 2 or bits[1] not in ('on', 'off'):
  59
+        raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % bits[0])
  60
+    else:
  61
+        use_l10n = bits[1] == 'on'
  62
+    nodelist = parser.parse(('endlocalize',))
  63
+    parser.delete_first_token()
  64
+    return LocalizeNode(nodelist, use_l10n)
  65
+
  66
+register.filter(localize)
  67
+register.filter(unlocalize)
49  django/utils/formats.py
@@ -41,14 +41,17 @@ def get_format_modules(reverse=False):
41 41
         modules.reverse()
42 42
     return modules
43 43
 
44  
-def get_format(format_type, lang=None):
  44
+def get_format(format_type, lang=None, use_l10n=None):
45 45
     """
46 46
     For a specific format type, returns the format for the current
47 47
     language (locale), defaults to the format in the settings.
48 48
     format_type is the name of the format, e.g. 'DATE_FORMAT'
  49
+
  50
+    If use_l10n is provided and is not None, that will force the value to
  51
+    be localized (or not), overriding the value of settings.USE_L10N.
49 52
     """
50 53
     format_type = smart_str(format_type)
51  
-    if settings.USE_L10N:
  54
+    if use_l10n or (use_l10n is None and settings.USE_L10N):
52 55
         if lang is None:
53 56
             lang = get_language()
54 57
         cache_key = (format_type, lang)
@@ -65,48 +68,60 @@ def get_format(format_type, lang=None):
65 68
             _format_cache[cache_key] = None
66 69
     return getattr(settings, format_type)
67 70
 
68  
-def date_format(value, format=None):
  71
+def date_format(value, format=None, use_l10n=None):
69 72
     """
70 73
     Formats a datetime.date or datetime.datetime object using a
71 74
     localizable format
  75
+
  76
+    If use_l10n is provided and is not None, that will force the value to
  77
+    be localized (or not), overriding the value of settings.USE_L10N.
72 78
     """
73  
-    return dateformat.format(value, get_format(format or 'DATE_FORMAT'))
  79
+    return dateformat.format(value, get_format(format or 'DATE_FORMAT', use_l10n=use_l10n))
74 80
 
75  
-def time_format(value, format=None):
  81
+def time_format(value, format=None, use_l10n=None):
76 82
     """
77 83
     Formats a datetime.time object using a localizable format
  84
+
  85
+    If use_l10n is provided and is not None, that will force the value to
  86
+    be localized (or not), overriding the value of settings.USE_L10N.
78 87
     """
79  
-    return dateformat.time_format(value, get_format(format or 'TIME_FORMAT'))
  88
+    return dateformat.time_format(value, get_format(format or 'TIME_FORMAT', use_l10n=use_l10n))
80 89
 
81  
-def number_format(value, decimal_pos=None):
  90
+def number_format(value, decimal_pos=None, use_l10n=None):
82 91
     """
83 92
     Formats a numeric value using localization settings
  93
+
  94
+    If use_l10n is provided and is not None, that will force the value to
  95
+    be localized (or not), overriding the value of settings.USE_L10N.
84 96
     """
85  
-    if settings.USE_L10N:
  97
+    if use_l10n or (use_l10n is None and settings.USE_L10N):
86 98
         lang = get_language()
87 99
     else:
88 100
         lang = None
89 101
     return numberformat.format(
90 102
         value,
91  
-        get_format('DECIMAL_SEPARATOR', lang),
  103
+        get_format('DECIMAL_SEPARATOR', lang, use_l10n=use_l10n),
92 104
         decimal_pos,
93  
-        get_format('NUMBER_GROUPING', lang),
94  
-        get_format('THOUSAND_SEPARATOR', lang),
  105
+        get_format('NUMBER_GROUPING', lang, use_l10n=use_l10n),
  106
+        get_format('THOUSAND_SEPARATOR', lang, use_l10n=use_l10n),
95 107
     )
96 108
 
97  
-def localize(value):
  109
+def localize(value, use_l10n=None):
98 110
     """
99 111
     Checks if value is a localizable type (date, number...) and returns it
100  
-    formatted as a string using current locale format
  112
+    formatted as a string using current locale format.
  113
+
  114
+    If use_l10n is provided and is not None, that will force the value to
  115
+    be localized (or not), overriding the value of settings.USE_L10N.
101 116
     """
102 117
     if isinstance(value, (decimal.Decimal, float, int, long)):
103  
-        return number_format(value)
  118
+        return number_format(value, use_l10n=use_l10n)
104 119
     elif isinstance(value, datetime.datetime):
105  
-        return date_format(value, 'DATETIME_FORMAT')
  120
+        return date_format(value, 'DATETIME_FORMAT', use_l10n=use_l10n)
106 121
     elif isinstance(value, datetime.date):
107  
-        return date_format(value)
  122
+        return date_format(value, use_l10n=use_l10n)
108 123
     elif isinstance(value, datetime.time):
109  
-        return time_format(value, 'TIME_FORMAT')
  124
+        return time_format(value, 'TIME_FORMAT', use_l10n=use_l10n)
110 125
     else:
111 126
         return value
112 127
 
9  docs/ref/templates/builtins.txt
@@ -2113,3 +2113,12 @@ Django templates. It is slightly different from the libraries described
2113 2113
 above because you don't need to add any application to the ``INSTALLED_APPS``
2114 2114
 setting but rather set :setting:`USE_I18N` to True, then loading it with
2115 2115
 ``{% load i18n %}``. See :ref:`specifying-translation-strings-in-template-code`.
  2116
+
  2117
+l10n
  2118
+~~~~
  2119
+
  2120
+Provides a couple of templatetags that allow control over the localization of
  2121
+values in Django templates. It is slightly different from the libraries described
  2122
+above because you don't need to add any application to the ``INSTALLED_APPS``;
  2123
+you only need to load the library using ``{% load l10n %}``. See
  2124
+:ref:`topic-l10n-templates`.
97  docs/topics/i18n/localization.txt
@@ -2,11 +2,13 @@
2 2
 Localization
3 3
 ============
4 4
 
5  
-This document covers two localization-related topics: `Creating language
6  
-files`_ and `locale aware date, time and numbers input/output in forms`_
  5
+This document covers three localization-related topics: `Creating language
  6
+files`_ , `locale aware date, time and numbers input/output in forms`_,
  7
+and `controlling localization in templates`_.
7 8
 
8 9
 .. _`Creating language files`: how-to-create-language-files_
9 10
 .. _`locale aware date, time and numbers input/output in forms`: format-localization_
  11
+.. _`controlling localization in templates`: topic-l10n-templates
10 12
 
11 13
 .. seealso::
12 14
 
@@ -315,3 +317,94 @@ where :file:`formats.py` contains custom format definitions. For example::
315 317
 
316 318
 to use a space as a thousand separator, instead of the default for English,
317 319
 a comma.
  320
+
  321
+.. topic-l10n-templates:
  322
+
  323
+Controlling localization in templates
  324
+=====================================
  325
+
  326
+When you have enabled localization using :setting:`USE_L10N`, Django
  327
+will try to use a locale specific format whenever it outputs a value
  328
+in a template.
  329
+
  330
+However, it may not always be appropriate to use localized values --
  331
+for example, if you're outputting Javascript or XML that is designed
  332
+to be machine-readable, you will always want unlocalized values. You
  333
+may also want to use localization in selected templates, rather than
  334
+using localization everywhere.
  335
+
  336
+To allow for fine control over the use of localization, Django
  337
+provides a the ``l10n`` template library that contains the following
  338
+tags and filters.
  339
+
  340
+Template tags
  341
+-------------
  342
+
  343
+.. templatetag:: localize
  344
+
  345
+localize
  346
+~~~~~~~~
  347
+
  348
+.. versionadded:: 1.3
  349
+
  350
+Enables or disables localization of template variables in the
  351
+contained block.
  352
+
  353
+This tag allows a more fine grained control of localization than
  354
+:setting:`USE_L10N`.
  355
+
  356
+To activate or deactivate localization for a template block, use::
  357
+
  358
+    {% localize on %}
  359
+        {{ value }}
  360
+    {% endlocalize %}
  361
+
  362
+    {% localize off %}
  363
+        {{ value }}
  364
+    {% endlocalize %}
  365
+
  366
+.. note::
  367
+
  368
+    The value of :setting:`USE_L10N` is not respected inside of a
  369
+    `{% localize %}` block.
  370
+
  371
+See :tfilter:`localized` and :tfilter:`unlocalized` for a template filter that will
  372
+do the same job on a per-variable basis.
  373
+
  374
+Template filters
  375
+----------------
  376
+
  377
+.. templatefilter:: localize
  378
+
  379
+localize
  380
+~~~~~~~~
  381
+
  382
+.. versionadded:: 1.3
  383
+
  384
+Forces localization of a single value.
  385
+
  386
+For example::
  387
+
  388
+    {{ value|localize }}
  389
+
  390
+To disable localization on a single value, use :tfilter:`unlocalize`. To control
  391
+localization over a large section of a template, use the :ttag:`localize` template
  392
+tag.
  393
+
  394
+
  395
+.. templatefilter:: unlocalize
  396
+
  397
+unlocalize
  398
+~~~~~~~~~~
  399
+
  400
+.. versionadded:: 1.3
  401
+
  402
+Forces a single value to be printed without localization.
  403
+
  404
+For example::
  405
+
  406
+    {{ value|unlocalize }}
  407
+
  408
+To force localization of a single value, use :tfilter:`localize`. To
  409
+control localization over a large section of a template, use the
  410
+:ttag:`localize` template tag.
27  tests/regressiontests/i18n/tests.py
@@ -453,6 +453,33 @@ def test_iter_format_modules(self):
453 453
             settings.FORMAT_MODULE_PATH = old_format_module_path
454 454
             deactivate()
455 455
 
  456
+    def test_localize_templatetag_and_filter(self):
  457
+        """
  458
+        Tests the {% localize %} templatetag
  459
+        """
  460
+        context = Context({'value': 3.14 })
  461
+        template1 = Template("{% load l10n %}{% localize %}{{ value }}{% endlocalize %};{% localize on %}{{ value }}{% endlocalize %}")
  462
+        template2 = Template("{% load l10n %}{{ value }};{% localize off %}{{ value }};{% endlocalize %}{{ value }}")
  463
+        template3 = Template('{% load l10n %}{{ value }};{{ value|unlocalize }}')
  464
+        template4 = Template('{% load l10n %}{{ value }};{{ value|localize }}')
  465
+        output1 = '3,14;3,14'
  466
+        output2 = '3,14;3.14;3,14'
  467
+        output3 = '3,14;3.14'
  468
+        output4 = '3.14;3,14'
  469
+        old_localize = settings.USE_L10N
  470
+        try:
  471
+            activate('de')
  472
+            settings.USE_L10N = False
  473
+            self.assertEqual(template1.render(context), output1)
  474
+            self.assertEqual(template4.render(context), output4)
  475
+            settings.USE_L10N = True
  476
+            self.assertEqual(template1.render(context), output1)
  477
+            self.assertEqual(template2.render(context), output2)
  478
+            self.assertEqual(template3.render(context), output3)
  479
+        finally:
  480
+            deactivate()
  481
+            settings.USE_L10N = old_localize
  482
+
456 483
 class MiscTests(TestCase):
457 484
 
458 485
     def test_parse_spec_http_header(self):

0 notes on commit ccc4902

Please sign in to comment.
Something went wrong with that request. Please try again.