Commit 24440422 authored by Sergey Kolosov's avatar Sergey Kolosov Committed by Tim Graham
Browse files

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.
parent e8cd65f8
Loading
Loading
Loading
Loading
+49 −12
Original line number Diff line number Diff line
@@ -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'])
    locale = _get_locale(request)
    packages = _parse_packages(packages)
    catalog, plural = get_javascript_catalog(locale, domain, packages)
    return render_javascript_catalog(catalog, plural)

    if packages is None:
        packages = ['django.conf']
    if isinstance(packages, six.string_types):
        packages = packages.split('+')

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)
    return render_javascript_catalog(catalog, plural)
    data = {
        'catalog': catalog,
        'formats': get_formats(),
        'plural': plural,
    }
    return http.JsonResponse(data)
+4 −0
Original line number Diff line number Diff line
@@ -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`.
+46 −0
Original line number Diff line number Diff line
@@ -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
-------------------

+29 −0
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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}),