Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Refactored DjangoTranslation class

Also fixes #18192 and #21055.
  • Loading branch information...
commit a5f6cbce07b5f3ab48d931e3fd1883c757fb9b45 1 parent 7c54f8c
Doug Beck beck authored claudep committed
1  AUTHORS
View
@@ -104,6 +104,7 @@ answer newbie questions, and generally made Django that much better:
Batman
Oliver Beattie <oliver@obeattie.com>
Brian Beck <http://blog.brianbeck.com/>
+ Doug Beck <doug@douglasbeck.com>
Shannon -jj Behrens <http://jjinux.blogspot.com/>
Esdras Beleza <linux@esdrasbeleza.com>
Božidar Benko <bbenko@gmail.com>
175 django/utils/translation/trans_real.py
View
@@ -10,6 +10,7 @@
import warnings
from django.apps import apps
+from django.conf import settings
from django.dispatch import receiver
from django.test.signals import setting_changed
from django.utils.deprecation import RemovedInDjango19Warning
@@ -101,107 +102,103 @@ class DjangoTranslation(gettext_module.GNUTranslations):
"""
This class sets up the GNUTranslations context with regard to output
charset.
- """
- def __init__(self, *args, **kw):
- gettext_module.GNUTranslations.__init__(self, *args, **kw)
- self.set_output_charset('utf-8')
- self.__language = '??'
- def merge(self, other):
- self._catalog.update(other._catalog)
+ This translation object will be constructed out of multiple GNUTranslations
+ objects by merging their catalogs. It will construct an object for the
+ requested language and add a fallback to the default language, if it's
+ different from the requested language.
+ """
+ def __init__(self, language):
+ """Create a GNUTranslations() using many locale directories"""
+ gettext_module.GNUTranslations.__init__(self)
- def set_language(self, language):
self.__language = language
self.__to_language = to_language(language)
+ self.__locale = to_locale(language)
+ self.plural = lambda n: int(n != 1)
+
+ self._init_translation_catalog()
+ self._add_installed_apps_translations()
+ self._add_local_translations()
+ self._add_fallback()
+
+ def __repr__(self):
+ return "<DjangoTranslation lang:%s>" % self.__language
+
+ def _new_gnu_trans(self, localedir, use_null_fallback=True):
+ """
+ Returns a mergeable gettext.GNUTranslations instance.
+
+ A convenience wrapper. By default gettext uses 'fallback=False'.
+ Using param `use_null_fallback` to avoid confusion with any other
+ references to 'fallback'.
+ """
+ translation = gettext_module.translation(
+ domain='django',
+ localedir=localedir,
+ languages=[self.__locale],
+ codeset='utf-8',
+ fallback=use_null_fallback)
+ if not hasattr(translation, '_catalog'):
+ # provides merge support for NullTranslations()
+ translation._catalog = {}
+ translation._info = {}
+ return translation
+
+ def _init_translation_catalog(self):
+ """Creates a base catalog using global django translations."""
+ settingsfile = upath(sys.modules[settings.__module__].__file__)
+ localedir = os.path.join(os.path.dirname(settingsfile), 'locale')
+ use_null_fallback = True
+ if self.__language == settings.LANGUAGE_CODE:
+ # default lang should be present and parseable, if not
+ # gettext will raise an IOError (refs #18192).
+ use_null_fallback = False
+ translation = self._new_gnu_trans(localedir, use_null_fallback)
+ self._info = translation._info.copy()
+ self._catalog = translation._catalog.copy()
+
+ def _add_installed_apps_translations(self):
+ """Merges translations from each installed app."""
+ for app_config in reversed(list(apps.get_app_configs())):
+ localedir = os.path.join(app_config.path, 'locale')
+ translation = self._new_gnu_trans(localedir)
+ self.merge(translation)
+
+ def _add_local_translations(self):
+ """Merges translations defined in LOCALE_PATHS."""
+ for localedir in reversed(settings.LOCALE_PATHS):
+ translation = self._new_gnu_trans(localedir)
+ self.merge(translation)
+
+ def _add_fallback(self):
+ """Sets the GNUTranslations() fallback with the default language."""
+ if self.__language == settings.LANGUAGE_CODE:
+ return
+ default_translation = translation(settings.LANGUAGE_CODE)
+ self.add_fallback(default_translation)
+
+ def merge(self, other):
+ """Merge another translation into this catalog."""
+ self._catalog.update(other._catalog)
def language(self):
+ """Returns the translation language."""
return self.__language
def to_language(self):
+ """Returns the translation language name."""
return self.__to_language
- def __repr__(self):
- return "<DjangoTranslation lang:%s>" % self.__language
-
def translation(language):
"""
Returns a translation object.
-
- This translation object will be constructed out of multiple GNUTranslations
- objects by merging their catalogs. It will construct a object for the
- requested language and add a fallback to the default language, if it's
- different from the requested language.
"""
global _translations
-
- t = _translations.get(language, None)
- if t is not None:
- return t
-
- from django.conf import settings
-
- globalpath = os.path.join(os.path.dirname(upath(sys.modules[settings.__module__].__file__)), 'locale')
-
- def _fetch(lang, fallback=None):
-
- global _translations
-
- res = _translations.get(lang, None)
- if res is not None:
- return res
-
- loc = to_locale(lang)
-
- def _translation(path):
- try:
- t = gettext_module.translation('django', path, [loc], DjangoTranslation)
- t.set_language(lang)
- return t
- except IOError:
- return None
-
- res = _translation(globalpath)
-
- # We want to ensure that, for example, "en-gb" and "en-us" don't share
- # the same translation object (thus, merging en-us with a local update
- # doesn't affect en-gb), even though they will both use the core "en"
- # translation. So we have to subvert Python's internal gettext caching.
- base_lang = lambda x: x.split('-', 1)[0]
- if any(base_lang(lang) == base_lang(trans) for trans in _translations):
- res._info = res._info.copy()
- res._catalog = res._catalog.copy()
-
- def _merge(path):
- t = _translation(path)
- if t is not None:
- if res is None:
- return t
- else:
- res.merge(t)
- return res
-
- for app_config in reversed(list(apps.get_app_configs())):
- apppath = os.path.join(app_config.path, 'locale')
- if os.path.isdir(apppath):
- res = _merge(apppath)
-
- for localepath in reversed(settings.LOCALE_PATHS):
- if os.path.isdir(localepath):
- res = _merge(localepath)
-
- if res is None:
- if fallback is not None:
- res = fallback
- else:
- return gettext_module.NullTranslations()
- _translations[lang] = res
- return res
-
- default_translation = _fetch(settings.LANGUAGE_CODE)
- current_translation = _fetch(language, fallback=default_translation)
-
- return current_translation
+ if not language in _translations:
+ _translations[language] = DjangoTranslation(language)
+ return _translations[language]
def activate(language):
@@ -244,7 +241,6 @@ def get_language():
except AttributeError:
pass
# If we don't have a real translation object, assume it's the default language.
- from django.conf import settings
return settings.LANGUAGE_CODE
@@ -255,8 +251,6 @@ def get_language_bidi():
* False = left-to-right layout
* True = right-to-left layout
"""
- from django.conf import settings
-
base_lang = get_language().split('-')[0]
return base_lang in settings.LANGUAGES_BIDI
@@ -273,7 +267,6 @@ def catalog():
if t is not None:
return t
if _default is None:
- from django.conf import settings
_default = translation(settings.LANGUAGE_CODE)
return _default
@@ -294,7 +287,6 @@ def do_translate(message, translation_function):
result = getattr(t, translation_function)(eol_message)
else:
if _default is None:
- from django.conf import settings
_default = translation(settings.LANGUAGE_CODE)
result = getattr(_default, translation_function)(eol_message)
if isinstance(message, SafeData):
@@ -343,7 +335,6 @@ def do_ntranslate(singular, plural, number, translation_function):
if t is not None:
return getattr(t, translation_function)(singular, plural, number)
if _default is None:
- from django.conf import settings
_default = translation(settings.LANGUAGE_CODE)
return getattr(_default, translation_function)(singular, plural, number)
@@ -383,7 +374,6 @@ def all_locale_paths():
"""
Returns a list of paths to user-provides languages files.
"""
- from django.conf import settings
globalpath = os.path.join(
os.path.dirname(upath(sys.modules[settings.__module__].__file__)), 'locale')
return [globalpath] + list(settings.LOCALE_PATHS)
@@ -424,7 +414,6 @@ def get_supported_language_variant(lang_code, strict=False):
"""
global _supported
if _supported is None:
- from django.conf import settings
_supported = OrderedDict(settings.LANGUAGES)
if lang_code:
# some browsers use deprecated language codes -- #18419
@@ -472,7 +461,6 @@ def get_language_from_request(request, check_path=False):
If check_path is True, the URL path prefix will be checked for a language
code, otherwise this is skipped for backwards compatibility.
"""
- from django.conf import settings
global _supported
if _supported is None:
_supported = OrderedDict(settings.LANGUAGES)
@@ -538,7 +526,6 @@ def templatize(src, origin=None):
does so by translating the Django translation tags into standard gettext
function invocations.
"""
- from django.conf import settings
from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK,
TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK)
src = force_text(src, settings.FILE_CHARSET)
24 tests/i18n/tests.py
View
@@ -4,6 +4,7 @@
from contextlib import contextmanager
import datetime
import decimal
+import gettext as gettext_module
from importlib import import_module
import os
import pickle
@@ -1338,3 +1339,26 @@ def test_specific_language_codes(self):
r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt-pt,en-US;q=0.8,en;q=0.6,ru;q=0.4'}
lang = get_language_from_request(r)
self.assertEqual('pt-br', lang)
+
+
+class TranslationFilesMissing(TestCase):
+
+ def setUp(self):
+ super(TranslationFilesMissing, self).setUp()
+ self.gettext_find_builtin = gettext_module.find
+
+ def tearDown(self):
+ gettext_module.find = self.gettext_find_builtin
+ super(TranslationFilesMissing, self).tearDown()
+
+ def patchGettextFind(self):
+ gettext_module.find = lambda *args, **kw: None
+
+ def test_failure_finding_default_mo_files(self):
+ '''
+ Ensure IOError is raised if the default language is unparseable.
+ Refs: #18192
+ '''
+ self.patchGettextFind()
+ trans_real._translations = {}
+ self.assertRaises(IOError, activate, 'en')
Please sign in to comment.
Something went wrong with that request. Please try again.