summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/views/i18n.py134
-rw-r--r--docs/releases/1.9.txt3
-rw-r--r--docs/topics/i18n/translation.txt25
-rw-r--r--tests/view_tests/templates/jsi18n-multi-catalogs.html17
-rw-r--r--tests/view_tests/tests/test_i18n.py14
-rw-r--r--tests/view_tests/urls.py13
-rw-r--r--tests/view_tests/views.py4
7 files changed, 142 insertions, 68 deletions
diff --git a/django/views/i18n.py b/django/views/i18n.py
index 30ecbbeaf3..3df4f47c88 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -94,87 +94,87 @@ js_catalog_template = r"""
django.pluralidx = function (count) { return (count == 1) ? 0 : 1; };
{% endif %}
- {% if catalog_str %}
/* gettext library */
- django.catalog = {{ catalog_str }};
+ django.catalog = django.catalog || {};
+ {% if catalog_str %}
+ var newcatalog = {{ catalog_str }};
+ for (var key in newcatalog) {
+ django.catalog[key] = newcatalog[key];
+ }
+ {% endif %}
- django.gettext = function (msgid) {
- var value = django.catalog[msgid];
- if (typeof(value) == 'undefined') {
- return msgid;
- } else {
- return (typeof(value) == 'string') ? value : value[0];
- }
- };
+ if (!django.jsi18n_initialized) {
+ django.gettext = function (msgid) {
+ var value = django.catalog[msgid];
+ if (typeof(value) == 'undefined') {
+ 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.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.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.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.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;
+ };
- 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())});
+ }
+ };
- 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 */
- /* formatting library */
+ django.formats = {{ formats_str }};
- 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;
+ }
+ };
- 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;
- /* 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));
{% endautoescape %}
diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt
index bce55085ef..6ec8a3f455 100644
--- a/docs/releases/1.9.txt
+++ b/docs/releases/1.9.txt
@@ -143,6 +143,9 @@ Internationalization
* The :func:`django.views.i18n.set_language` view now properly redirects to
: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
^^^^^^^^^^^^^^^^^^^
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index 0f7e76756b..ea295ae8ca 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -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
``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
:setting:`LOCALE_PATHS` setting are also always included. To keep consistency
with the translations lookup order algorithm used for Python and templates, the
diff --git a/tests/view_tests/templates/jsi18n-multi-catalogs.html b/tests/view_tests/templates/jsi18n-multi-catalogs.html
new file mode 100644
index 0000000000..8ec902e1c7
--- /dev/null
+++ b/tests/view_tests/templates/jsi18n-multi-catalogs.html
@@ -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>
diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py
index ae3025b5f1..04643d0508 100644
--- a/tests/view_tests/tests/test_i18n.py
+++ b/tests/view_tests/tests/test_i18n.py
@@ -1,4 +1,6 @@
# -*- coding:utf-8 -*-
+from __future__ import unicode_literals
+
import gettext
import json
import os
@@ -100,7 +102,7 @@ class I18NTests(TestCase):
self.assertContains(response, json.dumps(trans_txt), 1)
if lang_code == 'fr':
# 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')
@@ -270,6 +272,16 @@ class JavascriptI18nTests(LiveServerTestCase):
elem = self.selenium.find_element_by_id("npgettext_plur")
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):
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
diff --git a/tests/view_tests/urls.py b/tests/view_tests/urls.py
index 061dd19751..1d34b1f934 100644
--- a/tests/view_tests/urls.py
+++ b/tests/view_tests/urls.py
@@ -38,6 +38,16 @@ js_info_dict_admin = {
'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 = {
'domain': 'djangojs',
'packages': ('view_tests.app5',),
@@ -64,12 +74,15 @@ urlpatterns = [
# i18n views
url(r'^i18n/', include('django.conf.urls.i18n')),
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_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_packages2/$', i18n.javascript_catalog, js_info_dict_multi_packages2),
url(r'^jsi18n_admin/$', i18n.javascript_catalog, js_info_dict_admin),
url(r'^jsi18n_template/$', views.jsi18n),
+ url(r'^jsi18n_multi_catalogs/$', views.jsi18n_multi_catalogs),
# Static views
url(r'^site_media/(?P<path>.*)$', static.serve, {'document_root': media_dir}),
diff --git a/tests/view_tests/views.py b/tests/view_tests/views.py
index ad688573df..debc7bd7df 100644
--- a/tests/view_tests/views.py
+++ b/tests/view_tests/views.py
@@ -82,6 +82,10 @@ def jsi18n(request):
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'):
# We need to inspect the HTML generated by the fancy 500 debug view but
# the test client ignores it, so we send it explicitly.