From 11c60b529816c5d29c3b4145055bf59b2e389de5 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 6 Mar 2016 01:01:39 +0100 Subject: [PATCH] Reused the DjangoTranslation class for the javascript_catalog view Thanks Tim Graham and Cristiano Coelho for the reviews. Refs #26328, #26319. --- django/utils/translation/trans_real.py | 41 +++++++--- django/views/i18n.py | 80 +++++-------------- .../locale/fr/LC_MESSAGES/djangojs.po | 4 +- tests/view_tests/tests/test_i18n.py | 1 + 4 files changed, 56 insertions(+), 70 deletions(-) diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index a51d761145..baa39b445d 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -100,9 +100,13 @@ class DjangoTranslation(gettext_module.GNUTranslations): requested language and add a fallback to the default language, if it's different from the requested language. """ - def __init__(self, language): + domain = 'django' + + def __init__(self, language, domain=None, localedirs=None): """Create a GNUTranslations() using many locale directories""" gettext_module.GNUTranslations.__init__(self) + if domain is not None: + self.domain = domain self.set_output_charset('utf-8') # For Python 2 gettext() (#25720) self.__language = language @@ -110,13 +114,26 @@ class DjangoTranslation(gettext_module.GNUTranslations): self.__locale = to_locale(language) self._catalog = None - self._init_translation_catalog() - self._add_installed_apps_translations() + if self.domain == 'django': + if localedirs is not None: + # A module-level cache is used for caching 'django' translations + warnings.warn("localedirs is ignored when domain is 'django'.", RuntimeWarning) + localedirs = None + self._init_translation_catalog() + + if localedirs: + for localedir in localedirs: + translation = self._new_gnu_trans(localedir) + self.merge(translation) + else: + self._add_installed_apps_translations() + self._add_local_translations() - if self.__language == settings.LANGUAGE_CODE and self._catalog is None: + if (self.__language == settings.LANGUAGE_CODE and self.domain == 'django' + and self._catalog is None): # default lang should have at least one translation file available. raise IOError("No translation files found for default language %s." % settings.LANGUAGE_CODE) - self._add_fallback() + self._add_fallback(localedirs) if self._catalog is None: # No catalogs found for this language, set an empty catalog. self._catalog = {} @@ -133,7 +150,7 @@ class DjangoTranslation(gettext_module.GNUTranslations): references to 'fallback'. """ return gettext_module.translation( - domain='django', + domain=self.domain, localedir=localedir, languages=[self.__locale], codeset='utf-8', @@ -166,13 +183,19 @@ class DjangoTranslation(gettext_module.GNUTranslations): translation = self._new_gnu_trans(localedir) self.merge(translation) - def _add_fallback(self): + def _add_fallback(self, localedirs=None): """Sets the GNUTranslations() fallback with the default language.""" # Don't set a fallback for the default language or any English variant # (as it's empty, so it'll ALWAYS fall back to the default language) if self.__language == settings.LANGUAGE_CODE or self.__language.startswith('en'): return - default_translation = translation(settings.LANGUAGE_CODE) + if self.domain == 'django': + # Get from cache + default_translation = translation(settings.LANGUAGE_CODE) + else: + default_translation = DjangoTranslation( + settings.LANGUAGE_CODE, domain=self.domain, localedirs=localedirs + ) self.add_fallback(default_translation) def merge(self, other): @@ -198,7 +221,7 @@ class DjangoTranslation(gettext_module.GNUTranslations): def translation(language): """ - Returns a translation object. + Returns a translation object in the default 'django' domain. """ global _translations if language not in _translations: diff --git a/django/views/i18n.py b/django/views/i18n.py index 974b63e194..92d75b259d 100644 --- a/django/views/i18n.py +++ b/django/views/i18n.py @@ -1,5 +1,5 @@ -import gettext as gettext_module import importlib +import itertools import json import os @@ -16,6 +16,7 @@ from django.utils.http import is_safe_url from django.utils.translation import ( LANGUAGE_SESSION_KEY, check_for_language, get_language, to_locale, ) +from django.utils.translation.trans_real import DjangoTranslation DEFAULT_PACKAGES = ['django.conf'] LANGUAGE_QUERY_PARAMETER = 'language' @@ -202,67 +203,25 @@ def render_javascript_catalog(catalog=None, plural=None): 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.update(DEFAULT_PACKAGES) packages = [p for p in packages if p in allowable_packages] - t = {} paths = [] - en_selected = locale.startswith('en') - en_catalog_missing = True # paths of requested packages for package in packages: p = importlib.import_module(package) path = os.path.join(os.path.dirname(upath(p.__file__)), 'locale') paths.append(path) - # add the filesystem paths listed in the LOCALE_PATHS setting - paths.extend(reversed(settings.LOCALE_PATHS)) - # first load all english languages files for defaults - for path in paths: - try: - catalog = gettext_module.translation(domain, path, ['en']) - t.update(catalog._catalog) - except IOError: - pass - else: - # 'en' is the selected language and at least one of the packages - # listed in `packages` has an 'en' catalog - if en_selected: - en_catalog_missing = False - # next load the settings.LANGUAGE_CODE translations if it isn't english - if default_locale != 'en': - for path in paths: - try: - catalog = gettext_module.translation(domain, path, [default_locale]) - except IOError: - catalog = None - if catalog is not None: - t.update(catalog._catalog) - # last load the currently selected language, if it isn't identical to the default. - if locale != default_locale: - # If the currently selected language is English but it doesn't have a - # translation catalog (presumably due to being the language translated - # from) then a wrong language catalog might have been loaded in the - # previous step. It needs to be discarded. - if en_selected and en_catalog_missing: - t = {} - else: - locale_t = {} - for path in paths: - try: - catalog = gettext_module.translation(domain, path, [locale]) - except IOError: - catalog = None - if catalog is not None: - locale_t.update(catalog._catalog) - if locale_t: - t = locale_t + + trans = DjangoTranslation(locale, domain=domain, localedirs=paths) + trans_cat = trans._catalog + plural = None - if '' in t: - for l in t[''].split('\n'): - if l.startswith('Plural-Forms:'): - plural = l.split(':', 1)[1].strip() + if '' in trans_cat: + for line in trans_cat[''].split('\n'): + if line.startswith('Plural-Forms:'): + plural = line.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 : @@ -272,18 +231,19 @@ def get_javascript_catalog(locale, domain, packages): pdict = {} maxcnts = {} catalog = {} - for k, v in t.items(): - if k == '': + trans_fallback_cat = trans._fallback._catalog if trans._fallback else {} + for key, value in itertools.chain(six.iteritems(trans_cat), six.iteritems(trans_fallback_cat)): + if key == '' or key in catalog: continue - if isinstance(k, six.string_types): - catalog[k] = v - elif isinstance(k, tuple): - msgid = k[0] - cnt = k[1] + if isinstance(key, six.string_types): + catalog[key] = value + elif isinstance(key, tuple): + msgid = key[0] + cnt = key[1] maxcnts[msgid] = max(cnt, maxcnts.get(msgid, 0)) - pdict.setdefault(msgid, {})[cnt] = v + pdict.setdefault(msgid, {})[cnt] = value else: - raise TypeError(k) + raise TypeError(key) for k, v in pdict.items(): catalog[k] = [v.get(i, '') for i in range(maxcnts[msgid] + 1)] diff --git a/tests/view_tests/locale/fr/LC_MESSAGES/djangojs.po b/tests/view_tests/locale/fr/LC_MESSAGES/djangojs.po index 9259aab91b..d4efb88b71 100644 --- a/tests/view_tests/locale/fr/LC_MESSAGES/djangojs.po +++ b/tests/view_tests/locale/fr/LC_MESSAGES/djangojs.po @@ -19,10 +19,12 @@ msgstr "" msgid "this is to be translated" msgstr "il faut le traduire" - msgid "Choose a time" msgstr "Choisir une heure" msgctxt "month name" msgid "May" msgstr "mai" + +msgid "Untranslated string" +msgstr "" diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py index c342c85803..0e87856228 100644 --- a/tests/view_tests/tests/test_i18n.py +++ b/tests/view_tests/tests/test_i18n.py @@ -164,6 +164,7 @@ class JsI18NTests(SimpleTestCase): with self.settings(LANGUAGE_CODE='fr'), override('fi'): response = self.client.get('/jsi18n/') self.assertContains(response, 'il faut le traduire') + self.assertNotContains(response, "Untranslated string") def test_i18n_language_non_english_default(self): """