diff --git a/AUTHORS b/AUTHORS
index 084aefd647..8cd86d38dc 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -558,6 +558,7 @@ answer newbie questions, and generally made Django that much better:
Tom Tobin
Joe Topjian
torne-django@wolfpuppy.org.uk
+ Matthew Tretter
Jeff Triplett
tstromberg@google.com
Makoto Tsuyuki
diff --git a/django/contrib/admin/static/admin/js/jquery.init.js b/django/contrib/admin/static/admin/js/jquery.init.js
index dd4605c368..22a4c8bfd3 100644
--- a/django/contrib/admin/static/admin/js/jquery.init.js
+++ b/django/contrib/admin/static/admin/js/jquery.init.js
@@ -3,6 +3,5 @@
* namespace (i.e. this preserves pre-existing values for both window.$ and
* window.jQuery).
*/
-var django = {
- "jQuery": jQuery.noConflict(true)
-};
+var django = django || {};
+django.jQuery = jQuery.noConflict(true);
diff --git a/django/views/i18n.py b/django/views/i18n.py
index c87e3a82db..c7ee1b33bd 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -1,11 +1,12 @@
+import json
import os
import gettext as gettext_module
from django import http
from django.conf import settings
+from django.template import Context, Template
from django.utils import importlib
from django.utils.translation import check_for_language, activate, to_locale, get_language
-from django.utils.text import javascript_quote
from django.utils.encoding import smart_text
from django.utils.formats import get_format_modules, get_format
from django.utils._os import upath
@@ -38,6 +39,7 @@ def set_language(request):
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code)
return response
+
def get_formats():
"""
Returns all formats strings required for i18n to work
@@ -53,120 +55,142 @@ def get_formats():
for module in [settings] + get_format_modules(reverse=True):
for attr in FORMAT_SETTINGS:
result[attr] = get_format(attr)
- src = []
+ formats = {}
for k, v in result.items():
if isinstance(v, (six.string_types, int)):
- src.append("formats['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(smart_text(v))))
+ formats[k] = smart_text(v)
elif isinstance(v, (tuple, list)):
- v = [javascript_quote(smart_text(value)) for value in v]
- src.append("formats['%s'] = ['%s'];\n" % (javascript_quote(k), "', '".join(v)))
- return ''.join(src)
+ formats[k] = [smart_text(value) for value in v]
+ return formats
-NullSource = """
-/* gettext identity library */
-function gettext(msgid) { return msgid; }
-function ngettext(singular, plural, count) { return (count == 1) ? singular : plural; }
-function gettext_noop(msgid) { return msgid; }
-function pgettext(context, msgid) { return msgid; }
-function npgettext(context, singular, plural, count) { return (count == 1) ? singular : plural; }
-"""
+js_catalog_template = r"""
+{% autoescape off %}
+(function (globals) {
-LibHead = """
-/* gettext library */
+ var django = globals.django || (globals.django = {});
-var catalog = new Array();
-"""
+ {% if plural %}
+ django.pluralidx = function (n) {
+ var v={{ plural }};
+ if (typeof(v) == 'boolean') {
+ return v ? 1 : 0;
+ } else {
+ return v;
+ }
+ };
+ {% else %}
+ django.pluralidx = function (count) { return (count == 1) ? 0 : 1; };
+ {% endif %}
-LibFoot = """
+ {% if catalog_str %}
+ /* gettext library */
-function gettext(msgid) {
- var value = catalog[msgid];
- if (typeof(value) == 'undefined') {
- return msgid;
- } else {
- return (typeof(value) == 'string') ? value : value[0];
- }
-}
+ django.catalog = {{ catalog_str }};
-function ngettext(singular, plural, count) {
- value = catalog[singular];
- if (typeof(value) == 'undefined') {
- return (count == 1) ? singular : plural;
- } else {
- return value[pluralidx(count)];
- }
-}
+ django.gettext = function (msgid) {
+ var value = django.catalog[msgid];
+ if (typeof(value) == 'undefined') {
+ return msgid;
+ } else {
+ return (typeof(value) == 'string') ? value : value[0];
+ }
+ };
-function gettext_noop(msgid) { return msgid; }
+ django.ngettext = function (singular, plural, count) {
+ value = django.catalog[singular];
+ if (typeof(value) == 'undefined') {
+ return (count == 1) ? singular : plural;
+ } else {
+ return value[django.pluralidx(count)];
+ }
+ };
-function pgettext(context, msgid) {
- var value = gettext(context + '\\x04' + msgid);
- if (value.indexOf('\\x04') != -1) {
- value = msgid;
- }
- return value;
-}
+ django.gettext_noop = function (msgid) { return msgid; };
-function npgettext(context, singular, plural, count) {
- var value = ngettext(context + '\\x04' + singular, context + '\\x04' + plural, count);
- if (value.indexOf('\\x04') != -1) {
- value = ngettext(singular, plural, count);
- }
- return value;
-}
-"""
+ django.pgettext = function (context, msgid) {
+ var value = django.gettext(context + '\x04' + msgid);
+ if (value.indexOf('\x04') != -1) {
+ value = msgid;
+ }
+ return value;
+ };
-LibFormatHead = """
-/* formatting library */
+ django.npgettext = function (context, singular, plural, count) {
+ var value = django.ngettext(context + '\x04' + singular, context + '\x04' + plural, count);
+ if (value.indexOf('\x04') != -1) {
+ value = django.ngettext(singular, plural, count);
+ }
+ return value;
+ };
+ {% else %}
+ /* gettext identity library */
-var formats = new Array();
+ django.gettext = function (msgid) { return msgid; };
+ django.ngettext = function (singular, plural, count) { return (count == 1) ? singular : plural; };
+ django.gettext_noop = function (msgid) { return msgid; };
+ django.pgettext = function (context, msgid) { return msgid; };
+ django.npgettext = function (context, singular, plural, count) { return (count == 1) ? singular : plural; };
+ {% endif %}
-"""
+ django.interpolate = function (fmt, obj, named) {
+ if (named) {
+ return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
+ } else {
+ return fmt.replace(/%s/g, function(match){return String(obj.shift())});
+ }
+ };
-LibFormatFoot = """
-function get_format(format_type) {
- var value = formats[format_type];
+
+ /* formatting library */
+
+ django.formats = {{ formats_str }};
+
+ django.get_format = function (format_type) {
+ var value = django.formats[format_type];
if (typeof(value) == 'undefined') {
return format_type;
} else {
return value;
}
-}
+ };
+
+ /* add to global namespace */
+ globals.pluralidx = django.pluralidx;
+ globals.gettext = django.gettext;
+ globals.ngettext = django.ngettext;
+ globals.gettext_noop = django.gettext_noop;
+ globals.pgettext = django.pgettext;
+ globals.npgettext = django.npgettext;
+ globals.interpolate = django.interpolate;
+ globals.get_format = django.get_format;
+
+}(this));
+{% endautoescape %}
"""
-SimplePlural = """
-function pluralidx(count) { return (count == 1) ? 0 : 1; }
-"""
-InterPolate = r"""
-function interpolate(fmt, obj, named) {
- if (named) {
- return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
- } else {
- return fmt.replace(/%s/g, function(match){return String(obj.shift())});
- }
-}
-"""
+def render_javascript_catalog(catalog=None, plural=None):
+ template = Template(js_catalog_template)
+ indent = lambda s: s.replace('\n', '\n ')
+ context = Context({
+ 'catalog_str': indent(json.dumps(
+ catalog, sort_keys=True, indent=2)) if catalog else None,
+ 'formats_str': indent(json.dumps(
+ get_formats(), sort_keys=True, indent=2)),
+ 'plural': plural,
+ })
+
+ return http.HttpResponse(template.render(context), 'text/javascript')
-PluralIdx = r"""
-function pluralidx(n) {
- var v=%s;
- if (typeof(v) == 'boolean') {
- return v ? 1 : 0;
- } else {
- return v;
- }
-}
-"""
def null_javascript_catalog(request, domain=None, packages=None):
"""
Returns "identity" versions of the JavaScript i18n functions -- i.e.,
versions that don't actually do anything.
"""
- src = [NullSource, InterPolate, LibFormatHead, get_formats(), LibFormatFoot]
- return http.HttpResponse(''.join(src), 'text/javascript')
+ return render_javascript_catalog()
+
def javascript_catalog(request, domain='djangojs', packages=None):
"""
@@ -243,42 +267,32 @@ def javascript_catalog(request, domain='djangojs', packages=None):
locale_t.update(catalog._catalog)
if locale_t:
t = locale_t
- src = [LibHead]
plural = None
if '' in t:
for l in t[''].split('\n'):
if l.startswith('Plural-Forms:'):
- plural = l.split(':',1)[1].strip()
+ plural = l.split(':', 1)[1].strip()
if plural is not None:
# this should actually be a compiled function of a typical plural-form:
# Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
- plural = [el.strip() for el in plural.split(';') if el.strip().startswith('plural=')][0].split('=',1)[1]
- src.append(PluralIdx % plural)
- else:
- src.append(SimplePlural)
- csrc = []
+ plural = [el.strip() for el in plural.split(';') if el.strip().startswith('plural=')][0].split('=', 1)[1]
+
pdict = {}
+ maxcnts = {}
+ catalog = {}
for k, v in t.items():
if k == '':
continue
if isinstance(k, six.string_types):
- csrc.append("catalog['%s'] = '%s';\n" % (javascript_quote(k), javascript_quote(v)))
+ catalog[k] = v
elif isinstance(k, tuple):
- if k[0] not in pdict:
- pdict[k[0]] = k[1]
- else:
- pdict[k[0]] = max(k[1], pdict[k[0]])
- csrc.append("catalog['%s'][%d] = '%s';\n" % (javascript_quote(k[0]), k[1], javascript_quote(v)))
+ msgid = k[0]
+ cnt = k[1]
+ maxcnts[msgid] = max(cnt, maxcnts.get(msgid, 0))
+ pdict.setdefault(msgid, {})[cnt] = v
else:
raise TypeError(k)
- csrc.sort()
for k, v in pdict.items():
- src.append("catalog['%s'] = [%s];\n" % (javascript_quote(k), ','.join(["''"]*(v+1))))
- src.extend(csrc)
- src.append(LibFoot)
- src.append(InterPolate)
- src.append(LibFormatHead)
- src.append(get_formats())
- src.append(LibFormatFoot)
- src = ''.join(src)
- return http.HttpResponse(src, 'text/javascript')
+ catalog[k] = [v.get(i, '') for i in range(maxcnts[msgid] + 1)]
+
+ return render_javascript_catalog(catalog, plural)
diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py
index d186fab335..5a3bdd1062 100644
--- a/tests/view_tests/tests/test_i18n.py
+++ b/tests/view_tests/tests/test_i18n.py
@@ -61,13 +61,13 @@ class I18NTests(TestCase):
else:
trans_txt = catalog.ugettext('this is to be translated')
response = self.client.get('/views/jsi18n/')
- # in response content must to be a line like that:
- # catalog['this is to be translated'] = 'same_that_trans_txt'
+ # response content must include a line like:
+ # "this is to be translated":
# javascript_quote is used to be able to check unicode strings
self.assertContains(response, javascript_quote(trans_txt), 1)
if lang_code == 'fr':
# Message with context (msgctxt)
- self.assertContains(response, "['month name\x04May'] = 'mai';", 1)
+ self.assertContains(response, r'"month name\u0004May": "mai"', 1)
class JsI18NTests(TestCase):