parent
7c54f8cced
commit
a5f6cbce07
1
AUTHORS
1
AUTHORS
|
@ -104,6 +104,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Batman
|
Batman
|
||||||
Oliver Beattie <oliver@obeattie.com>
|
Oliver Beattie <oliver@obeattie.com>
|
||||||
Brian Beck <http://blog.brianbeck.com/>
|
Brian Beck <http://blog.brianbeck.com/>
|
||||||
|
Doug Beck <doug@douglasbeck.com>
|
||||||
Shannon -jj Behrens <http://jjinux.blogspot.com/>
|
Shannon -jj Behrens <http://jjinux.blogspot.com/>
|
||||||
Esdras Beleza <linux@esdrasbeleza.com>
|
Esdras Beleza <linux@esdrasbeleza.com>
|
||||||
Božidar Benko <bbenko@gmail.com>
|
Božidar Benko <bbenko@gmail.com>
|
||||||
|
|
|
@ -10,6 +10,7 @@ from threading import local
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from django.conf import settings
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.test.signals import setting_changed
|
from django.test.signals import setting_changed
|
||||||
from django.utils.deprecation import RemovedInDjango19Warning
|
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
|
This class sets up the GNUTranslations context with regard to output
|
||||||
charset.
|
charset.
|
||||||
|
|
||||||
|
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, *args, **kw):
|
def __init__(self, language):
|
||||||
gettext_module.GNUTranslations.__init__(self, *args, **kw)
|
"""Create a GNUTranslations() using many locale directories"""
|
||||||
self.set_output_charset('utf-8')
|
gettext_module.GNUTranslations.__init__(self)
|
||||||
self.__language = '??'
|
|
||||||
|
|
||||||
def merge(self, other):
|
|
||||||
self._catalog.update(other._catalog)
|
|
||||||
|
|
||||||
def set_language(self, language):
|
|
||||||
self.__language = language
|
self.__language = language
|
||||||
self.__to_language = to_language(language)
|
self.__to_language = to_language(language)
|
||||||
|
self.__locale = to_locale(language)
|
||||||
|
self.plural = lambda n: int(n != 1)
|
||||||
|
|
||||||
def language(self):
|
self._init_translation_catalog()
|
||||||
return self.__language
|
self._add_installed_apps_translations()
|
||||||
|
self._add_local_translations()
|
||||||
def to_language(self):
|
self._add_fallback()
|
||||||
return self.__to_language
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<DjangoTranslation lang:%s>" % self.__language
|
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 translation(language):
|
def translation(language):
|
||||||
"""
|
"""
|
||||||
Returns a translation object.
|
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
|
global _translations
|
||||||
|
if not language in _translations:
|
||||||
t = _translations.get(language, None)
|
_translations[language] = DjangoTranslation(language)
|
||||||
if t is not None:
|
return _translations[language]
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def activate(language):
|
def activate(language):
|
||||||
|
@ -244,7 +241,6 @@ def get_language():
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
# If we don't have a real translation object, assume it's the default language.
|
# If we don't have a real translation object, assume it's the default language.
|
||||||
from django.conf import settings
|
|
||||||
return settings.LANGUAGE_CODE
|
return settings.LANGUAGE_CODE
|
||||||
|
|
||||||
|
|
||||||
|
@ -255,8 +251,6 @@ def get_language_bidi():
|
||||||
* False = left-to-right layout
|
* False = left-to-right layout
|
||||||
* True = right-to-left layout
|
* True = right-to-left layout
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
base_lang = get_language().split('-')[0]
|
base_lang = get_language().split('-')[0]
|
||||||
return base_lang in settings.LANGUAGES_BIDI
|
return base_lang in settings.LANGUAGES_BIDI
|
||||||
|
|
||||||
|
@ -273,7 +267,6 @@ def catalog():
|
||||||
if t is not None:
|
if t is not None:
|
||||||
return t
|
return t
|
||||||
if _default is None:
|
if _default is None:
|
||||||
from django.conf import settings
|
|
||||||
_default = translation(settings.LANGUAGE_CODE)
|
_default = translation(settings.LANGUAGE_CODE)
|
||||||
return _default
|
return _default
|
||||||
|
|
||||||
|
@ -294,7 +287,6 @@ def do_translate(message, translation_function):
|
||||||
result = getattr(t, translation_function)(eol_message)
|
result = getattr(t, translation_function)(eol_message)
|
||||||
else:
|
else:
|
||||||
if _default is None:
|
if _default is None:
|
||||||
from django.conf import settings
|
|
||||||
_default = translation(settings.LANGUAGE_CODE)
|
_default = translation(settings.LANGUAGE_CODE)
|
||||||
result = getattr(_default, translation_function)(eol_message)
|
result = getattr(_default, translation_function)(eol_message)
|
||||||
if isinstance(message, SafeData):
|
if isinstance(message, SafeData):
|
||||||
|
@ -343,7 +335,6 @@ def do_ntranslate(singular, plural, number, translation_function):
|
||||||
if t is not None:
|
if t is not None:
|
||||||
return getattr(t, translation_function)(singular, plural, number)
|
return getattr(t, translation_function)(singular, plural, number)
|
||||||
if _default is None:
|
if _default is None:
|
||||||
from django.conf import settings
|
|
||||||
_default = translation(settings.LANGUAGE_CODE)
|
_default = translation(settings.LANGUAGE_CODE)
|
||||||
return getattr(_default, translation_function)(singular, plural, number)
|
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.
|
Returns a list of paths to user-provides languages files.
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
|
||||||
globalpath = os.path.join(
|
globalpath = os.path.join(
|
||||||
os.path.dirname(upath(sys.modules[settings.__module__].__file__)), 'locale')
|
os.path.dirname(upath(sys.modules[settings.__module__].__file__)), 'locale')
|
||||||
return [globalpath] + list(settings.LOCALE_PATHS)
|
return [globalpath] + list(settings.LOCALE_PATHS)
|
||||||
|
@ -424,7 +414,6 @@ def get_supported_language_variant(lang_code, strict=False):
|
||||||
"""
|
"""
|
||||||
global _supported
|
global _supported
|
||||||
if _supported is None:
|
if _supported is None:
|
||||||
from django.conf import settings
|
|
||||||
_supported = OrderedDict(settings.LANGUAGES)
|
_supported = OrderedDict(settings.LANGUAGES)
|
||||||
if lang_code:
|
if lang_code:
|
||||||
# some browsers use deprecated language codes -- #18419
|
# 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
|
If check_path is True, the URL path prefix will be checked for a language
|
||||||
code, otherwise this is skipped for backwards compatibility.
|
code, otherwise this is skipped for backwards compatibility.
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
|
||||||
global _supported
|
global _supported
|
||||||
if _supported is None:
|
if _supported is None:
|
||||||
_supported = OrderedDict(settings.LANGUAGES)
|
_supported = OrderedDict(settings.LANGUAGES)
|
||||||
|
@ -538,7 +526,6 @@ def templatize(src, origin=None):
|
||||||
does so by translating the Django translation tags into standard gettext
|
does so by translating the Django translation tags into standard gettext
|
||||||
function invocations.
|
function invocations.
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
|
||||||
from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK,
|
from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK,
|
||||||
TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK)
|
TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK)
|
||||||
src = force_text(src, settings.FILE_CHARSET)
|
src = force_text(src, settings.FILE_CHARSET)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
|
import gettext as gettext_module
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
|
@ -1338,3 +1339,26 @@ class CountrySpecificLanguageTests(TestCase):
|
||||||
r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt-pt,en-US;q=0.8,en;q=0.6,ru;q=0.4'}
|
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)
|
lang = get_language_from_request(r)
|
||||||
self.assertEqual('pt-br', lang)
|
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')
|
||||||
|
|
Loading…
Reference in New Issue