Fixed #22106 -- Allowed using more than one instance of javascript_catalog per project.

This commit is contained in:
Moritz Sichert 2014-02-23 15:10:31 +01:00 committed by Tim Graham
parent 556eb67701
commit 6bb2175ed6
7 changed files with 147 additions and 73 deletions

View File

@ -94,87 +94,87 @@ js_catalog_template = r"""
django.pluralidx = function (count) { return (count == 1) ? 0 : 1; }; django.pluralidx = function (count) { return (count == 1) ? 0 : 1; };
{% endif %} {% endif %}
{% if catalog_str %}
/* gettext library */ /* gettext library */
django.catalog = {{ catalog_str }}; django.catalog = django.catalog || {};
{% if catalog_str %}
django.gettext = function (msgid) { var newcatalog = {{ catalog_str }};
var value = django.catalog[msgid]; for (var key in newcatalog) {
if (typeof(value) == 'undefined') { django.catalog[key] = newcatalog[key];
return msgid; }
} else {
return (typeof(value) == 'string') ? value : value[0];
}
};
django.ngettext = function (singular, plural, count) {
var value = django.catalog[singular];
if (typeof(value) == 'undefined') {
return (count == 1) ? singular : plural;
} else {
return value[django.pluralidx(count)];
}
};
django.gettext_noop = function (msgid) { return msgid; };
django.pgettext = function (context, msgid) {
var value = django.gettext(context + '\x04' + msgid);
if (value.indexOf('\x04') != -1) {
value = msgid;
}
return value;
};
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 */
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 %} {% endif %}
django.interpolate = function (fmt, obj, named) { if (!django.jsi18n_initialized) {
if (named) { django.gettext = function (msgid) {
return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])}); var value = django.catalog[msgid];
} else { if (typeof(value) == 'undefined') {
return fmt.replace(/%s/g, function(match){return String(obj.shift())}); return msgid;
} } else {
}; return (typeof(value) == 'string') ? value : value[0];
}
};
django.ngettext = function (singular, plural, count) {
var value = django.catalog[singular];
if (typeof(value) == 'undefined') {
return (count == 1) ? singular : plural;
} else {
return value[django.pluralidx(count)];
}
};
/* formatting library */ django.gettext_noop = function (msgid) { return msgid; };
django.formats = {{ formats_str }}; django.pgettext = function (context, msgid) {
var value = django.gettext(context + '\x04' + msgid);
django.get_format = function (format_type) { if (value.indexOf('\x04') != -1) {
var value = django.formats[format_type]; value = msgid;
if (typeof(value) == 'undefined') { }
return format_type;
} else {
return value; return value;
} };
};
/* add to global namespace */ django.npgettext = function (context, singular, plural, count) {
globals.pluralidx = django.pluralidx; var value = django.ngettext(context + '\x04' + singular, context + '\x04' + plural, count);
globals.gettext = django.gettext; if (value.indexOf('\x04') != -1) {
globals.ngettext = django.ngettext; value = django.ngettext(singular, plural, count);
globals.gettext_noop = django.gettext_noop; }
globals.pgettext = django.pgettext; return value;
globals.npgettext = django.npgettext; };
globals.interpolate = django.interpolate;
globals.get_format = django.get_format; 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())});
}
};
/* 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;
django.jsi18n_initialized = true;
}
}(this)); }(this));
{% endautoescape %} {% endautoescape %}

View File

@ -143,6 +143,9 @@ Internationalization
* The :func:`django.views.i18n.set_language` view now properly redirects to * The :func:`django.views.i18n.set_language` view now properly redirects to
:ref:`translated URLs <url-internationalization>`, when available. :ref:`translated URLs <url-internationalization>`, when available.
* The :func:`django.views.i18n.javascript_catalog` view now works correctly
if used multiple times with different configurations on the same page.
Management Commands Management Commands
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^

View File

@ -952,6 +952,31 @@ different apps and this changes often and you don't want to pull in one big
catalog file. As a security measure, these values can only be either catalog file. As a security measure, these values can only be either
``django.conf`` or any package from the :setting:`INSTALLED_APPS` setting. ``django.conf`` or any package from the :setting:`INSTALLED_APPS` setting.
You can also split the catalogs in multiple URLs and load them as you need in
your sites::
js_info_dict_app = {
'packages': ('your.app.package',),
}
js_info_dict_other_app = {
'packages': ('your.other.app.package',),
}
urlpatterns = [
url(r'^jsi18n/app/$', javascript_catalog, js_info_dict_app),
url(r'^jsi18n/other_app/$', javascript_catalog, js_info_dict_other_app),
]
If you use more than one ``javascript_catalog`` on a site and some of them
define the same strings, the strings in the catalog that was loaded last take
precedence.
.. versionchanged:: 1.9
Before Django 1.9, the catalogs completely overwrote each other and you
could only use one at a time.
The JavaScript translations found in the paths listed in the The JavaScript translations found in the paths listed in the
:setting:`LOCALE_PATHS` setting are also always included. To keep consistency :setting:`LOCALE_PATHS` setting are also always included. To keep consistency
with the translations lookup order algorithm used for Python and templates, the with the translations lookup order algorithm used for Python and templates, the

View File

@ -0,0 +1,17 @@
<html>
<head>
<script type="text/javascript" src="/jsi18n/app1/"></script>
<script type="text/javascript" src="/jsi18n/app2/"></script>
<body>
<p id="app1string">
<script type="text/javascript">
document.write(gettext('this app1 string is to be translated'))
</script>
</p>
<p id="app2string">
<script type="text/javascript">
document.write(gettext('this app2 string is to be translated'))
</script>
</p>
</body>
</html>

View File

@ -1,4 +1,6 @@
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
from __future__ import unicode_literals
import gettext import gettext
import json import json
import os import os
@ -100,7 +102,7 @@ class I18NTests(TestCase):
self.assertContains(response, json.dumps(trans_txt), 1) self.assertContains(response, json.dumps(trans_txt), 1)
if lang_code == 'fr': if lang_code == 'fr':
# Message with context (msgctxt) # Message with context (msgctxt)
self.assertContains(response, r'"month name\u0004May": "mai"', 1) self.assertContains(response, '"month name\\u0004May": "mai"', 1)
@override_settings(ROOT_URLCONF='view_tests.urls') @override_settings(ROOT_URLCONF='view_tests.urls')
@ -270,6 +272,16 @@ class JavascriptI18nTests(LiveServerTestCase):
elem = self.selenium.find_element_by_id("npgettext_plur") elem = self.selenium.find_element_by_id("npgettext_plur")
self.assertEqual(elem.text, "455 Resultate") self.assertEqual(elem.text, "455 Resultate")
@modify_settings(INSTALLED_APPS={'append': ['view_tests.app1', 'view_tests.app2']})
@override_settings(LANGUAGE_CODE='fr')
def test_multiple_catalogs(self):
self.selenium.get('%s%s' % (self.live_server_url, '/jsi18n_multi_catalogs/'))
elem = self.selenium.find_element_by_id('app1string')
self.assertEqual(elem.text, 'il faut traduire cette chaîne de caractères de app1')
elem = self.selenium.find_element_by_id('app2string')
self.assertEqual(elem.text, 'il faut traduire cette chaîne de caractères de app2')
class JavascriptI18nChromeTests(JavascriptI18nTests): class JavascriptI18nChromeTests(JavascriptI18nTests):
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver' webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'

View File

@ -38,6 +38,16 @@ js_info_dict_admin = {
'packages': ('django.contrib.admin', 'view_tests'), 'packages': ('django.contrib.admin', 'view_tests'),
} }
js_info_dict_app1 = {
'domain': 'djangojs',
'packages': ('view_tests.app1',),
}
js_info_dict_app2 = {
'domain': 'djangojs',
'packages': ('view_tests.app2',),
}
js_info_dict_app5 = { js_info_dict_app5 = {
'domain': 'djangojs', 'domain': 'djangojs',
'packages': ('view_tests.app5',), 'packages': ('view_tests.app5',),
@ -64,12 +74,15 @@ urlpatterns = [
# i18n views # i18n views
url(r'^i18n/', include('django.conf.urls.i18n')), url(r'^i18n/', include('django.conf.urls.i18n')),
url(r'^jsi18n/$', i18n.javascript_catalog, js_info_dict), url(r'^jsi18n/$', i18n.javascript_catalog, js_info_dict),
url(r'^jsi18n/app1/$', i18n.javascript_catalog, js_info_dict_app1),
url(r'^jsi18n/app2/$', i18n.javascript_catalog, js_info_dict_app2),
url(r'^jsi18n/app5/$', i18n.javascript_catalog, js_info_dict_app5), url(r'^jsi18n/app5/$', i18n.javascript_catalog, js_info_dict_app5),
url(r'^jsi18n_english_translation/$', i18n.javascript_catalog, js_info_dict_english_translation), url(r'^jsi18n_english_translation/$', i18n.javascript_catalog, js_info_dict_english_translation),
url(r'^jsi18n_multi_packages1/$', i18n.javascript_catalog, js_info_dict_multi_packages1), url(r'^jsi18n_multi_packages1/$', i18n.javascript_catalog, js_info_dict_multi_packages1),
url(r'^jsi18n_multi_packages2/$', i18n.javascript_catalog, js_info_dict_multi_packages2), url(r'^jsi18n_multi_packages2/$', i18n.javascript_catalog, js_info_dict_multi_packages2),
url(r'^jsi18n_admin/$', i18n.javascript_catalog, js_info_dict_admin), url(r'^jsi18n_admin/$', i18n.javascript_catalog, js_info_dict_admin),
url(r'^jsi18n_template/$', views.jsi18n), url(r'^jsi18n_template/$', views.jsi18n),
url(r'^jsi18n_multi_catalogs/$', views.jsi18n_multi_catalogs),
# Static views # Static views
url(r'^site_media/(?P<path>.*)$', static.serve, {'document_root': media_dir}), url(r'^site_media/(?P<path>.*)$', static.serve, {'document_root': media_dir}),

View File

@ -82,6 +82,10 @@ def jsi18n(request):
return render_to_response('jsi18n.html') return render_to_response('jsi18n.html')
def jsi18n_multi_catalogs(request):
return render_to_response('jsi18n-multi-catalogs.html')
def raises_template_does_not_exist(request, path='i_dont_exist.html'): def raises_template_does_not_exist(request, path='i_dont_exist.html'):
# We need to inspect the HTML generated by the fancy 500 debug view but # We need to inspect the HTML generated by the fancy 500 debug view but
# the test client ignores it, so we send it explicitly. # the test client ignores it, so we send it explicitly.