Fixed #22404 -- Added a view that exposes i18n catalog as a JSON

Added django.views.i18n.json_catalog() view, which returns a JSON
response containing translations, formats, and a plural expression
for the specified language.
This commit is contained in:
Sergey Kolosov 2015-06-05 15:10:28 +01:00 committed by Tim Graham
parent e8cd65f829
commit 244404227e
5 changed files with 130 additions and 13 deletions

View File

@ -17,6 +17,9 @@ from django.utils.translation import (
LANGUAGE_SESSION_KEY, check_for_language, get_language, to_locale,
)
DEFAULT_PACKAGES = ['django.conf']
LANGUAGE_QUERY_PARAMETER = 'language'
def set_language(request):
"""
@ -36,7 +39,7 @@ def set_language(request):
next = '/'
response = http.HttpResponseRedirect(next)
if request.method == 'POST':
lang_code = request.POST.get('language')
lang_code = request.POST.get(LANGUAGE_QUERY_PARAMETER)
if lang_code and check_for_language(lang_code):
next_trans = translate_url(next, lang_code)
if next_trans != next:
@ -199,7 +202,7 @@ def get_javascript_catalog(locale, domain, packages):
default_locale = to_locale(settings.LANGUAGE_CODE)
app_configs = apps.get_app_configs()
allowable_packages = set(app_config.name for app_config in app_configs)
allowable_packages.add('django.conf')
allowable_packages.update(DEFAULT_PACKAGES)
packages = [p for p in packages if p in allowable_packages]
t = {}
paths = []
@ -284,6 +287,21 @@ def get_javascript_catalog(locale, domain, packages):
return catalog, plural
def _get_locale(request):
language = request.GET.get(LANGUAGE_QUERY_PARAMETER)
if not (language and check_for_language(language)):
language = get_language()
return to_locale(language)
def _parse_packages(packages):
if packages is None:
packages = list(DEFAULT_PACKAGES)
elif isinstance(packages, six.string_types):
packages = packages.split('+')
return packages
def null_javascript_catalog(request, domain=None, packages=None):
"""
Returns "identity" versions of the JavaScript i18n functions -- i.e.,
@ -305,16 +323,35 @@ def javascript_catalog(request, domain='djangojs', packages=None):
go to the djangojs domain. But this might be needed if you
deliver your JavaScript source from Django templates.
"""
locale = to_locale(get_language())
if request.GET and 'language' in request.GET:
if check_for_language(request.GET['language']):
locale = to_locale(request.GET['language'])
if packages is None:
packages = ['django.conf']
if isinstance(packages, six.string_types):
packages = packages.split('+')
locale = _get_locale(request)
packages = _parse_packages(packages)
catalog, plural = get_javascript_catalog(locale, domain, packages)
return render_javascript_catalog(catalog, plural)
def json_catalog(request, domain='djangojs', packages=None):
"""
Return the selected language catalog as a JSON object.
Receives the same parameters as javascript_catalog(), but returns
a response with a JSON object of the following format:
{
"catalog": {
# Translations catalog
},
"formats": {
# Language formats for date, time, etc.
},
"plural": '...' # Expression for plural forms, or null.
}
"""
locale = _get_locale(request)
packages = _parse_packages(packages)
catalog, plural = get_javascript_catalog(locale, domain, packages)
data = {
'catalog': catalog,
'formats': get_formats(),
'plural': plural,
}
return http.JsonResponse(data)

View File

@ -357,6 +357,10 @@ Internationalization
for languages which can be written in different scripts, for example Latin
and Cyrillic (e.g. ``be@latin``).
* Added the :func:`django.views.i18n.json_catalog` view to help build a custom
client-side i18n library upon Django translations. It returns a JSON object
containing a translations catalog, formatting settings, and a plural rule.
* Added the ``name_translated`` attribute to the object returned by the
:ttag:`get_language_info` template tag. Also added a corresponding template
filter: :tfilter:`language_name_translated`.

View File

@ -1213,6 +1213,52 @@ Additionally, if there are complex rules around pluralization, the catalog view
will render a conditional expression. This will evaluate to either a ``true``
(should pluralize) or ``false`` (should **not** pluralize) value.
The ``json_catalog`` view
-------------------------
.. versionadded:: 1.9
.. function:: json_catalog(request, domain='djangojs', packages=None)
In order to use another client-side library to handle translations, you may
want to take advantage of the ``json_catalog()`` view. It's similar to
:meth:`~django.views.i18n.javascript_catalog` but returns a JSON response.
The JSON object contains i18n formatting settings (those available for
`get_format`_), a plural rule (as a ``plural`` part of a GNU gettext
``Plural-Forms`` expression), and translation strings. The translation strings
are taken from applications or Django's own translations, according to what is
specified either via ``urlpatterns`` arguments or as request parameters. Paths
listed in :setting:`LOCALE_PATHS` are also included.
The view is hooked up to your application and configured in the same fashion as
:meth:`~django.views.i18n.javascript_catalog` (namely, the ``domain`` and
``packages`` arguments behave identically)::
from django.views.i18n import json_catalog
js_info_dict = {
'packages': ('your.app.package',),
}
urlpatterns = [
url(r'^jsoni18n/$', json_catalog, js_info_dict),
]
The response format is as follows:
.. code-block:: json
{
"catalog": {
# Translations catalog
},
"formats": {
# Language formats for date, time, etc.
},
"plural": "..." # Expression for plural forms, or null.
}
Note on performance
-------------------

View File

@ -105,6 +105,20 @@ class I18NTests(TestCase):
# Message with context (msgctxt)
self.assertContains(response, '"month name\\u0004May": "mai"', 1)
def test_jsoni18n(self):
"""
The json_catalog returns the language catalog and settings as JSON.
"""
with override('de'):
response = self.client.get('/jsoni18n/')
data = json.loads(response.content.decode('utf-8'))
self.assertIn('catalog', data)
self.assertIn('formats', data)
self.assertIn('plural', data)
self.assertEqual(data['catalog']['month name\x04May'], 'Mai')
self.assertIn('DATETIME_FORMAT', data['formats'])
self.assertEqual(data['plural'], '(n != 1)')
@override_settings(ROOT_URLCONF='view_tests.urls')
class JsI18NTests(SimpleTestCase):
@ -127,6 +141,21 @@ class JsI18NTests(SimpleTestCase):
response = self.client.get('/jsi18n/')
self.assertNotContains(response, 'esto tiene que ser traducido')
def test_jsoni18n_with_missing_en_files(self):
"""
Same as above for the json_catalog view. Here we also check for the
expected JSON format.
"""
with self.settings(LANGUAGE_CODE='es'), override('en-us'):
response = self.client.get('/jsoni18n/')
data = json.loads(response.content.decode('utf-8'))
self.assertIn('catalog', data)
self.assertIn('formats', data)
self.assertIn('plural', data)
self.assertEqual(data['catalog'], {})
self.assertIn('DATETIME_FORMAT', data['formats'])
self.assertIsNone(data['plural'])
def test_jsi18n_fallback_language(self):
"""
Let's make sure that the fallback language is still working properly

View File

@ -84,6 +84,7 @@ urlpatterns = [
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),
url(r'^jsoni18n/$', i18n.json_catalog, js_info_dict),
# Static views
url(r'^site_media/(?P<path>.*)$', static.serve, {'document_root': media_dir}),