2008-03-08 11:55:47 +08:00
|
|
|
"""Translation helper functions."""
|
2012-06-08 00:08:47 +08:00
|
|
|
from __future__ import unicode_literals
|
2005-11-04 12:59:46 +08:00
|
|
|
|
2015-01-28 20:35:27 +08:00
|
|
|
import gettext as gettext_module
|
2007-10-27 03:52:42 +08:00
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import sys
|
2013-01-21 02:07:10 +08:00
|
|
|
import warnings
|
2015-01-28 20:35:27 +08:00
|
|
|
from collections import OrderedDict
|
|
|
|
from threading import local
|
2007-10-27 03:52:42 +08:00
|
|
|
|
2013-12-24 19:25:17 +08:00
|
|
|
from django.apps import apps
|
2014-05-01 00:04:30 +08:00
|
|
|
from django.conf import settings
|
2014-07-12 03:01:37 +08:00
|
|
|
from django.conf.locale import LANG_INFO
|
2014-06-21 21:00:35 +08:00
|
|
|
from django.core.exceptions import AppRegistryNotReady
|
2014-10-09 23:46:40 +08:00
|
|
|
from django.core.signals import setting_changed
|
2013-10-28 23:57:56 +08:00
|
|
|
from django.dispatch import receiver
|
2015-01-28 20:35:27 +08:00
|
|
|
from django.utils import lru_cache, six
|
2012-12-08 18:13:52 +08:00
|
|
|
from django.utils._os import upath
|
2015-01-28 20:35:27 +08:00
|
|
|
from django.utils.encoding import force_text
|
|
|
|
from django.utils.safestring import SafeData, mark_safe
|
2012-07-20 22:16:57 +08:00
|
|
|
from django.utils.six import StringIO
|
2015-01-28 20:35:27 +08:00
|
|
|
from django.utils.translation import (
|
|
|
|
LANGUAGE_SESSION_KEY, TranslatorCommentWarning, trim_whitespace,
|
|
|
|
)
|
2011-01-17 17:52:47 +08:00
|
|
|
|
2014-03-18 01:06:11 +08:00
|
|
|
# Translations are cached in a dictionary for every language.
|
2005-11-07 05:55:39 +08:00
|
|
|
# The active translations are stored by threadid to make them thread local.
|
2005-11-04 12:59:46 +08:00
|
|
|
_translations = {}
|
2011-01-17 17:52:47 +08:00
|
|
|
_active = local()
|
2005-11-04 12:59:46 +08:00
|
|
|
|
2005-11-07 05:55:39 +08:00
|
|
|
# The default translation is based on the settings file.
|
2005-11-04 12:59:46 +08:00
|
|
|
_default = None
|
|
|
|
|
2010-11-04 18:48:27 +08:00
|
|
|
# magic gettext number to separate context from message
|
2012-06-08 00:08:47 +08:00
|
|
|
CONTEXT_SEPARATOR = "\x04"
|
2010-11-04 18:48:27 +08:00
|
|
|
|
2012-10-21 07:25:35 +08:00
|
|
|
# Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9
|
|
|
|
# and RFC 3066, section 2.1
|
2007-10-27 03:52:42 +08:00
|
|
|
accept_language_re = re.compile(r'''
|
2012-10-21 07:25:35 +08:00
|
|
|
([A-Za-z]{1,8}(?:-[A-Za-z0-9]{1,8})*|\*) # "en", "en-au", "x-y-z", "es-419", "*"
|
2011-06-27 00:51:54 +08:00
|
|
|
(?:\s*;\s*q=(0(?:\.\d{,3})?|1(?:.0{,3})?))? # Optional "q=1.00", "q=0.8"
|
|
|
|
(?:\s*,\s*|$) # Multiple accepts per header.
|
2007-10-27 03:52:42 +08:00
|
|
|
''', re.VERBOSE)
|
|
|
|
|
2014-12-30 22:41:31 +08:00
|
|
|
language_code_re = re.compile(
|
|
|
|
r'^[a-z]{1,8}(?:-[a-z0-9]{1,8})*(?:@[a-z0-9]{1,20})?$',
|
|
|
|
re.IGNORECASE
|
|
|
|
)
|
2013-11-20 23:31:53 +08:00
|
|
|
|
2014-12-30 22:41:31 +08:00
|
|
|
language_code_prefix_re = re.compile(r'^/([\w@-]+)(/|$)')
|
2011-06-16 01:29:10 +08:00
|
|
|
|
2013-01-21 02:07:10 +08:00
|
|
|
|
2013-10-28 23:57:56 +08:00
|
|
|
@receiver(setting_changed)
|
|
|
|
def reset_cache(**kwargs):
|
|
|
|
"""
|
|
|
|
Reset global state when LANGUAGES setting has been changed, as some
|
|
|
|
languages should no longer be accepted.
|
|
|
|
"""
|
2013-11-12 14:54:01 +08:00
|
|
|
if kwargs['setting'] in ('LANGUAGES', 'LANGUAGE_CODE'):
|
|
|
|
check_for_language.cache_clear()
|
2014-11-19 04:53:54 +08:00
|
|
|
get_languages.cache_clear()
|
2013-11-12 14:54:01 +08:00
|
|
|
get_supported_language_variant.cache_clear()
|
2013-10-28 23:57:56 +08:00
|
|
|
|
|
|
|
|
2007-10-27 03:52:42 +08:00
|
|
|
def to_locale(language, to_lower=False):
|
2008-02-06 09:04:30 +08:00
|
|
|
"""
|
|
|
|
Turns a language name (en-us) into a locale name (en_US). If 'to_lower' is
|
|
|
|
True, the last component is lower-cased (en_us).
|
|
|
|
"""
|
2005-11-04 12:59:46 +08:00
|
|
|
p = language.find('-')
|
|
|
|
if p >= 0:
|
2007-10-27 03:52:42 +08:00
|
|
|
if to_lower:
|
2013-11-04 02:08:55 +08:00
|
|
|
return language[:p].lower() + '_' + language[p + 1:].lower()
|
2007-10-27 03:52:42 +08:00
|
|
|
else:
|
2010-01-02 05:38:34 +08:00
|
|
|
# Get correct locale for sr-latn
|
2013-11-04 02:08:55 +08:00
|
|
|
if len(language[p + 1:]) > 2:
|
|
|
|
return language[:p].lower() + '_' + language[p + 1].upper() + language[p + 2:].lower()
|
|
|
|
return language[:p].lower() + '_' + language[p + 1:].upper()
|
2005-11-04 12:59:46 +08:00
|
|
|
else:
|
|
|
|
return language.lower()
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
def to_language(locale):
|
2008-03-08 11:55:47 +08:00
|
|
|
"""Turns a locale name (en_US) into a language name (en-us)."""
|
2005-11-04 12:59:46 +08:00
|
|
|
p = locale.find('_')
|
|
|
|
if p >= 0:
|
2013-11-04 02:08:55 +08:00
|
|
|
return locale[:p].lower() + '-' + locale[p + 1:].lower()
|
2005-11-04 12:59:46 +08:00
|
|
|
else:
|
|
|
|
return locale.lower()
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
class DjangoTranslation(gettext_module.GNUTranslations):
|
|
|
|
"""
|
2005-11-07 05:55:39 +08:00
|
|
|
This class sets up the GNUTranslations context with regard to output
|
2011-06-10 04:01:28 +08:00
|
|
|
charset.
|
2005-11-04 12:59:46 +08:00
|
|
|
|
2014-05-01 00:04:30 +08:00
|
|
|
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.
|
|
|
|
"""
|
2016-03-06 08:01:39 +08:00
|
|
|
domain = 'django'
|
|
|
|
|
|
|
|
def __init__(self, language, domain=None, localedirs=None):
|
2014-05-01 00:04:30 +08:00
|
|
|
"""Create a GNUTranslations() using many locale directories"""
|
|
|
|
gettext_module.GNUTranslations.__init__(self)
|
2016-03-06 08:01:39 +08:00
|
|
|
if domain is not None:
|
|
|
|
self.domain = domain
|
2015-11-09 21:58:24 +08:00
|
|
|
self.set_output_charset('utf-8') # For Python 2 gettext() (#25720)
|
2005-11-07 05:55:39 +08:00
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
self.__language = language
|
2010-09-27 23:25:38 +08:00
|
|
|
self.__to_language = to_language(language)
|
2014-05-01 00:04:30 +08:00
|
|
|
self.__locale = to_locale(language)
|
2015-12-19 00:00:53 +08:00
|
|
|
self._catalog = None
|
2014-05-01 00:04:30 +08:00
|
|
|
|
2016-03-06 08:01:39 +08:00
|
|
|
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()
|
|
|
|
|
2014-05-01 00:04:30 +08:00
|
|
|
self._add_local_translations()
|
2016-04-04 08:37:32 +08:00
|
|
|
if self.__language == settings.LANGUAGE_CODE and self.domain == 'django' and self._catalog is None:
|
2015-12-19 00:00:53 +08:00
|
|
|
# default lang should have at least one translation file available.
|
|
|
|
raise IOError("No translation files found for default language %s." % settings.LANGUAGE_CODE)
|
2016-03-06 08:01:39 +08:00
|
|
|
self._add_fallback(localedirs)
|
2016-01-07 01:33:29 +08:00
|
|
|
if self._catalog is None:
|
|
|
|
# No catalogs found for this language, set an empty catalog.
|
|
|
|
self._catalog = {}
|
2014-05-01 00:04:30 +08:00
|
|
|
|
|
|
|
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'.
|
|
|
|
"""
|
2015-12-19 00:00:53 +08:00
|
|
|
return gettext_module.translation(
|
2016-03-06 08:01:39 +08:00
|
|
|
domain=self.domain,
|
2014-05-01 00:04:30 +08:00
|
|
|
localedir=localedir,
|
|
|
|
languages=[self.__locale],
|
|
|
|
codeset='utf-8',
|
|
|
|
fallback=use_null_fallback)
|
|
|
|
|
|
|
|
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')
|
2015-12-19 00:00:53 +08:00
|
|
|
translation = self._new_gnu_trans(localedir)
|
|
|
|
self.merge(translation)
|
2014-05-01 00:04:30 +08:00
|
|
|
|
|
|
|
def _add_installed_apps_translations(self):
|
|
|
|
"""Merges translations from each installed app."""
|
2014-06-21 21:00:35 +08:00
|
|
|
try:
|
|
|
|
app_configs = reversed(list(apps.get_app_configs()))
|
|
|
|
except AppRegistryNotReady:
|
|
|
|
raise AppRegistryNotReady(
|
|
|
|
"The translation infrastructure cannot be initialized before the "
|
|
|
|
"apps registry is ready. Check that you don't make non-lazy "
|
|
|
|
"gettext calls at import time.")
|
|
|
|
for app_config in app_configs:
|
2014-05-01 00:04:30 +08:00
|
|
|
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)
|
|
|
|
|
2016-03-06 08:01:39 +08:00
|
|
|
def _add_fallback(self, localedirs=None):
|
2014-05-01 00:04:30 +08:00
|
|
|
"""Sets the GNUTranslations() fallback with the default language."""
|
2015-02-28 03:17:04 +08:00
|
|
|
# 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'):
|
2014-05-01 00:04:30 +08:00
|
|
|
return
|
2016-03-06 08:01:39 +08:00
|
|
|
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
|
|
|
|
)
|
2014-05-01 00:04:30 +08:00
|
|
|
self.add_fallback(default_translation)
|
|
|
|
|
|
|
|
def merge(self, other):
|
|
|
|
"""Merge another translation into this catalog."""
|
2015-12-19 00:00:53 +08:00
|
|
|
if not getattr(other, '_catalog', None):
|
|
|
|
return # NullTranslations() has no _catalog
|
|
|
|
if self._catalog is None:
|
|
|
|
# Take plural and _info from first catalog found (generally Django's).
|
|
|
|
self.plural = other.plural
|
|
|
|
self._info = other._info.copy()
|
|
|
|
self._catalog = other._catalog.copy()
|
|
|
|
else:
|
|
|
|
self._catalog.update(other._catalog)
|
2005-11-04 12:59:46 +08:00
|
|
|
|
|
|
|
def language(self):
|
2014-05-01 00:04:30 +08:00
|
|
|
"""Returns the translation language."""
|
2005-11-04 12:59:46 +08:00
|
|
|
return self.__language
|
2005-11-07 05:55:39 +08:00
|
|
|
|
2010-09-27 23:25:38 +08:00
|
|
|
def to_language(self):
|
2014-05-01 00:04:30 +08:00
|
|
|
"""Returns the translation language name."""
|
2010-09-27 23:25:38 +08:00
|
|
|
return self.__to_language
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
def translation(language):
|
|
|
|
"""
|
2016-03-06 08:01:39 +08:00
|
|
|
Returns a translation object in the default 'django' domain.
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
|
|
|
global _translations
|
2014-05-01 06:33:46 +08:00
|
|
|
if language not in _translations:
|
2014-05-01 00:04:30 +08:00
|
|
|
_translations[language] = DjangoTranslation(language)
|
|
|
|
return _translations[language]
|
2005-11-04 12:59:46 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
def activate(language):
|
|
|
|
"""
|
2014-03-18 01:06:11 +08:00
|
|
|
Fetches the translation object for a given language and installs it as the
|
|
|
|
current translation object for the current thread.
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
2015-01-08 18:11:35 +08:00
|
|
|
if not language:
|
|
|
|
return
|
2011-01-17 17:52:47 +08:00
|
|
|
_active.value = translation(language)
|
2005-11-04 12:59:46 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
def deactivate():
|
|
|
|
"""
|
2005-11-07 05:55:39 +08:00
|
|
|
Deinstalls the currently active translation object so that further _ calls
|
|
|
|
will resolve against the default translation object, again.
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
2011-01-17 17:52:47 +08:00
|
|
|
if hasattr(_active, "value"):
|
|
|
|
del _active.value
|
2005-11-04 12:59:46 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
def deactivate_all():
|
|
|
|
"""
|
|
|
|
Makes the active translation object a NullTranslations() instance. This is
|
|
|
|
useful when we want delayed translations to appear as the original string
|
|
|
|
for some reason.
|
|
|
|
"""
|
2011-01-17 17:52:47 +08:00
|
|
|
_active.value = gettext_module.NullTranslations()
|
2015-01-08 18:11:35 +08:00
|
|
|
_active.value.to_language = lambda *args: None
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
def get_language():
|
2008-03-08 11:55:47 +08:00
|
|
|
"""Returns the currently selected language."""
|
2011-01-17 17:52:47 +08:00
|
|
|
t = getattr(_active, "value", None)
|
2005-11-04 12:59:46 +08:00
|
|
|
if t is not None:
|
|
|
|
try:
|
2010-09-27 23:25:38 +08:00
|
|
|
return t.to_language()
|
2005-11-04 12:59:46 +08:00
|
|
|
except AttributeError:
|
|
|
|
pass
|
2005-11-07 05:55:39 +08:00
|
|
|
# If we don't have a real translation object, assume it's the default language.
|
2006-05-02 09:31:56 +08:00
|
|
|
return settings.LANGUAGE_CODE
|
2005-11-04 12:59:46 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2006-05-16 15:35:20 +08:00
|
|
|
def get_language_bidi():
|
|
|
|
"""
|
|
|
|
Returns selected language's BiDi layout.
|
2010-10-11 20:20:07 +08:00
|
|
|
|
2010-05-09 05:38:27 +08:00
|
|
|
* False = left-to-right layout
|
|
|
|
* True = right-to-left layout
|
2006-05-16 15:35:20 +08:00
|
|
|
"""
|
2015-04-03 18:11:54 +08:00
|
|
|
lang = get_language()
|
|
|
|
if lang is None:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
base_lang = get_language().split('-')[0]
|
|
|
|
return base_lang in settings.LANGUAGES_BIDI
|
2006-06-02 12:20:32 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2005-12-04 20:06:16 +08:00
|
|
|
def catalog():
|
|
|
|
"""
|
2008-03-08 11:55:47 +08:00
|
|
|
Returns the current active catalog for further processing.
|
2005-12-04 20:06:16 +08:00
|
|
|
This can be used if you need to modify the catalog or want to access the
|
|
|
|
whole message catalog instead of just translating one string.
|
|
|
|
"""
|
2011-01-17 17:52:47 +08:00
|
|
|
global _default
|
|
|
|
|
|
|
|
t = getattr(_active, "value", None)
|
2005-12-04 20:06:16 +08:00
|
|
|
if t is not None:
|
|
|
|
return t
|
|
|
|
if _default is None:
|
|
|
|
_default = translation(settings.LANGUAGE_CODE)
|
|
|
|
return _default
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
def do_translate(message, translation_function):
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
2007-10-22 01:14:25 +08:00
|
|
|
Translates 'message' using the given 'translation_function' name -- which
|
|
|
|
will be either gettext or ugettext. It uses the current thread to find the
|
|
|
|
translation object to use. If no current translation is activated, the
|
|
|
|
message will be run through the default translation object.
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
2011-01-17 17:52:47 +08:00
|
|
|
global _default
|
|
|
|
|
2012-11-14 17:50:15 +08:00
|
|
|
# str() is allowing a bytestring message to remain bytestring on Python 2
|
|
|
|
eol_message = message.replace(str('\r\n'), str('\n')).replace(str('\r'), str('\n'))
|
2014-09-07 19:56:34 +08:00
|
|
|
|
|
|
|
if len(eol_message) == 0:
|
|
|
|
# Returns an empty value of the corresponding type if an empty message
|
|
|
|
# is given, instead of metadata, which is the default gettext behavior.
|
|
|
|
result = type(message)("")
|
2007-11-17 20:11:54 +08:00
|
|
|
else:
|
2014-09-07 19:56:34 +08:00
|
|
|
_default = _default or translation(settings.LANGUAGE_CODE)
|
|
|
|
translation_object = getattr(_active, "value", _default)
|
|
|
|
|
|
|
|
result = getattr(translation_object, translation_function)(eol_message)
|
|
|
|
|
2007-11-17 20:11:54 +08:00
|
|
|
if isinstance(message, SafeData):
|
|
|
|
return mark_safe(result)
|
2014-09-07 19:56:34 +08:00
|
|
|
|
2007-11-17 20:11:54 +08:00
|
|
|
return result
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
def gettext(message):
|
2012-08-18 17:30:48 +08:00
|
|
|
"""
|
|
|
|
Returns a string of the translation of the message.
|
|
|
|
|
|
|
|
Returns a string on Python 3 and an UTF-8-encoded bytestring on Python 2.
|
|
|
|
"""
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
return do_translate(message, 'gettext')
|
|
|
|
|
2012-07-22 04:24:13 +08:00
|
|
|
if six.PY3:
|
|
|
|
ugettext = gettext
|
|
|
|
else:
|
|
|
|
def ugettext(message):
|
|
|
|
return do_translate(message, 'ugettext')
|
2005-11-04 12:59:46 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2010-11-04 18:48:27 +08:00
|
|
|
def pgettext(context, message):
|
2012-10-15 16:00:22 +08:00
|
|
|
msg_with_ctxt = "%s%s%s" % (context, CONTEXT_SEPARATOR, message)
|
|
|
|
result = ugettext(msg_with_ctxt)
|
2010-11-04 18:48:27 +08:00
|
|
|
if CONTEXT_SEPARATOR in result:
|
|
|
|
# Translation not found
|
2014-05-03 01:31:22 +08:00
|
|
|
# force unicode, because lazy version expects unicode
|
|
|
|
result = force_text(message)
|
2010-11-04 18:48:27 +08:00
|
|
|
return result
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
def gettext_noop(message):
|
|
|
|
"""
|
2005-11-07 05:55:39 +08:00
|
|
|
Marks strings for translation but doesn't translate them now. This can be
|
|
|
|
used to store strings in global variables that should stay in the base
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
language (because they might be used externally) and will be translated
|
|
|
|
later.
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
|
|
|
return message
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
def do_ntranslate(singular, plural, number, translation_function):
|
2011-01-17 17:52:47 +08:00
|
|
|
global _default
|
2005-11-04 12:59:46 +08:00
|
|
|
|
2011-01-17 17:52:47 +08:00
|
|
|
t = getattr(_active, "value", None)
|
2005-11-04 12:59:46 +08:00
|
|
|
if t is not None:
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
return getattr(t, translation_function)(singular, plural, number)
|
2005-11-04 12:59:46 +08:00
|
|
|
if _default is None:
|
2005-11-16 20:13:28 +08:00
|
|
|
_default = translation(settings.LANGUAGE_CODE)
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
return getattr(_default, translation_function)(singular, plural, number)
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
def ngettext(singular, plural, number):
|
|
|
|
"""
|
2012-08-18 17:30:48 +08:00
|
|
|
Returns a string of the translation of either the singular or plural,
|
|
|
|
based on the number.
|
|
|
|
|
|
|
|
Returns a string on Python 3 and an UTF-8-encoded bytestring on Python 2.
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
"""
|
|
|
|
return do_ntranslate(singular, plural, number, 'ngettext')
|
|
|
|
|
2012-07-22 04:24:13 +08:00
|
|
|
if six.PY3:
|
|
|
|
ungettext = ngettext
|
|
|
|
else:
|
|
|
|
def ungettext(singular, plural, number):
|
|
|
|
"""
|
|
|
|
Returns a unicode strings of the translation of either the singular or
|
|
|
|
plural, based on the number.
|
|
|
|
"""
|
|
|
|
return do_ntranslate(singular, plural, number, 'ungettext')
|
2005-11-04 12:59:46 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2010-11-04 18:48:27 +08:00
|
|
|
def npgettext(context, singular, plural, number):
|
2012-10-15 16:00:22 +08:00
|
|
|
msgs_with_ctxt = ("%s%s%s" % (context, CONTEXT_SEPARATOR, singular),
|
|
|
|
"%s%s%s" % (context, CONTEXT_SEPARATOR, plural),
|
|
|
|
number)
|
|
|
|
result = ungettext(*msgs_with_ctxt)
|
2010-11-04 18:48:27 +08:00
|
|
|
if CONTEXT_SEPARATOR in result:
|
|
|
|
# Translation not found
|
2012-07-22 04:24:13 +08:00
|
|
|
result = ungettext(singular, plural, number)
|
2010-11-04 18:48:27 +08:00
|
|
|
return result
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2011-02-13 03:12:28 +08:00
|
|
|
def all_locale_paths():
|
|
|
|
"""
|
|
|
|
Returns a list of paths to user-provides languages files.
|
|
|
|
"""
|
|
|
|
globalpath = os.path.join(
|
2012-12-08 18:13:52 +08:00
|
|
|
os.path.dirname(upath(sys.modules[settings.__module__].__file__)), 'locale')
|
2011-02-13 03:12:28 +08:00
|
|
|
return [globalpath] + list(settings.LOCALE_PATHS)
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2014-03-30 02:44:11 +08:00
|
|
|
@lru_cache.lru_cache(maxsize=1000)
|
2005-11-04 12:59:46 +08:00
|
|
|
def check_for_language(lang_code):
|
|
|
|
"""
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
Checks whether there is a global language file for the given language
|
|
|
|
code. This is used to decide whether a user-provided language is
|
2013-11-20 23:31:53 +08:00
|
|
|
available.
|
2014-03-30 02:44:11 +08:00
|
|
|
|
|
|
|
lru_cache should have a maxsize to prevent from memory exhaustion attacks,
|
|
|
|
as the provided language codes are taken from the HTTP request. See also
|
|
|
|
<https://www.djangoproject.com/weblog/2007/oct/26/security-fix/>.
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
2013-11-20 23:31:53 +08:00
|
|
|
# First, a quick check to make sure lang_code is well-formed (#21458)
|
2015-04-03 18:11:54 +08:00
|
|
|
if lang_code is None or not language_code_re.search(lang_code):
|
2013-11-20 23:31:53 +08:00
|
|
|
return False
|
2011-02-13 03:12:28 +08:00
|
|
|
for path in all_locale_paths():
|
|
|
|
if gettext_module.find('django', path, [to_locale(lang_code)]) is not None:
|
|
|
|
return True
|
|
|
|
return False
|
2005-11-04 12:59:46 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2014-11-19 04:53:54 +08:00
|
|
|
@lru_cache.lru_cache()
|
|
|
|
def get_languages():
|
|
|
|
"""
|
|
|
|
Cache of settings.LANGUAGES in an OrderedDict for easy lookups by key.
|
|
|
|
"""
|
|
|
|
return OrderedDict(settings.LANGUAGES)
|
|
|
|
|
|
|
|
|
2013-11-12 14:54:01 +08:00
|
|
|
@lru_cache.lru_cache(maxsize=1000)
|
|
|
|
def get_supported_language_variant(lang_code, strict=False):
|
2013-02-24 21:43:45 +08:00
|
|
|
"""
|
|
|
|
Returns the language-code that's listed in supported languages, possibly
|
|
|
|
selecting a more generic variant. Raises LookupError if nothing found.
|
2013-05-19 18:43:34 +08:00
|
|
|
|
|
|
|
If `strict` is False (the default), the function will look for an alternative
|
|
|
|
country-specific variant when the currently checked is not found.
|
2013-11-12 14:54:01 +08:00
|
|
|
|
|
|
|
lru_cache should have a maxsize to prevent from memory exhaustion attacks,
|
|
|
|
as the provided language codes are taken from the HTTP request. See also
|
|
|
|
<https://www.djangoproject.com/weblog/2007/oct/26/security-fix/>.
|
2013-02-24 21:43:45 +08:00
|
|
|
"""
|
2013-05-18 20:37:04 +08:00
|
|
|
if lang_code:
|
2014-07-12 03:01:37 +08:00
|
|
|
# If 'fr-ca' is not supported, try special fallback or language-only 'fr'.
|
|
|
|
possible_lang_codes = [lang_code]
|
|
|
|
try:
|
|
|
|
possible_lang_codes.extend(LANG_INFO[lang_code]['fallback'])
|
|
|
|
except KeyError:
|
|
|
|
pass
|
2013-05-19 18:43:34 +08:00
|
|
|
generic_lang_code = lang_code.split('-')[0]
|
2014-07-12 03:01:37 +08:00
|
|
|
possible_lang_codes.append(generic_lang_code)
|
2014-11-19 04:53:54 +08:00
|
|
|
supported_lang_codes = get_languages()
|
2014-07-12 03:01:37 +08:00
|
|
|
|
|
|
|
for code in possible_lang_codes:
|
2014-11-19 04:53:54 +08:00
|
|
|
if code in supported_lang_codes and check_for_language(code):
|
2013-05-18 20:37:04 +08:00
|
|
|
return code
|
2013-05-19 18:43:34 +08:00
|
|
|
if not strict:
|
|
|
|
# if fr-fr is not supported, try fr-ca.
|
2014-11-19 04:53:54 +08:00
|
|
|
for supported_code in supported_lang_codes:
|
2013-11-12 14:54:01 +08:00
|
|
|
if supported_code.startswith(generic_lang_code + '-'):
|
2013-05-19 18:43:34 +08:00
|
|
|
return supported_code
|
2013-02-24 21:43:45 +08:00
|
|
|
raise LookupError(lang_code)
|
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2013-11-12 14:54:01 +08:00
|
|
|
def get_language_from_path(path, strict=False):
|
2011-06-16 01:29:10 +08:00
|
|
|
"""
|
|
|
|
Returns the language-code if there is a valid language-code
|
|
|
|
found in the `path`.
|
2013-05-19 18:43:34 +08:00
|
|
|
|
|
|
|
If `strict` is False (the default), the function will look for an alternative
|
|
|
|
country-specific variant when the currently checked is not found.
|
2011-06-16 01:29:10 +08:00
|
|
|
"""
|
|
|
|
regex_match = language_code_prefix_re.match(path)
|
2013-05-19 18:43:34 +08:00
|
|
|
if not regex_match:
|
|
|
|
return None
|
|
|
|
lang_code = regex_match.group(1)
|
|
|
|
try:
|
2013-11-12 14:54:01 +08:00
|
|
|
return get_supported_language_variant(lang_code, strict=strict)
|
2013-05-19 18:43:34 +08:00
|
|
|
except LookupError:
|
|
|
|
return None
|
2011-06-16 01:29:10 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2012-02-18 21:37:30 +08:00
|
|
|
def get_language_from_request(request, check_path=False):
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
Merged Unicode branch into trunk (r4952:5608). This should be fully
backwards compatible for all practical purposes.
Fixed #2391, #2489, #2996, #3322, #3344, #3370, #3406, #3432, #3454, #3492, #3582, #3690, #3878, #3891, #3937, #4039, #4141, #4227, #4286, #4291, #4300, #4452, #4702
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5609 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2007-07-04 20:11:04 +08:00
|
|
|
Analyzes the request to find what language the user wants the system to
|
|
|
|
show. Only languages listed in settings.LANGUAGES are taken into account.
|
|
|
|
If the user requests a sublanguage where we have a main language, we send
|
|
|
|
out the main language.
|
2012-02-18 21:37:30 +08:00
|
|
|
|
|
|
|
If check_path is True, the URL path prefix will be checked for a language
|
|
|
|
code, otherwise this is skipped for backwards compatibility.
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
2012-02-18 21:37:30 +08:00
|
|
|
if check_path:
|
2013-11-12 14:54:01 +08:00
|
|
|
lang_code = get_language_from_path(request.path_info)
|
2012-02-18 21:37:30 +08:00
|
|
|
if lang_code is not None:
|
|
|
|
return lang_code
|
2011-06-16 01:29:10 +08:00
|
|
|
|
2014-11-19 04:53:54 +08:00
|
|
|
supported_lang_codes = get_languages()
|
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
if hasattr(request, 'session'):
|
2014-03-21 21:53:16 +08:00
|
|
|
lang_code = request.session.get(LANGUAGE_SESSION_KEY)
|
2014-11-19 04:53:54 +08:00
|
|
|
if lang_code in supported_lang_codes and lang_code is not None and check_for_language(lang_code):
|
2005-11-04 12:59:46 +08:00
|
|
|
return lang_code
|
2005-11-07 05:55:39 +08:00
|
|
|
|
2008-03-01 02:38:44 +08:00
|
|
|
lang_code = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME)
|
2010-02-16 20:13:48 +08:00
|
|
|
|
2013-02-24 21:43:45 +08:00
|
|
|
try:
|
2013-11-12 14:54:01 +08:00
|
|
|
return get_supported_language_variant(lang_code)
|
2013-02-24 21:43:45 +08:00
|
|
|
except LookupError:
|
|
|
|
pass
|
2005-11-07 05:55:39 +08:00
|
|
|
|
2007-10-27 03:52:42 +08:00
|
|
|
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
|
2008-02-06 09:04:30 +08:00
|
|
|
for accept_lang, unused in parse_accept_lang_header(accept):
|
|
|
|
if accept_lang == '*':
|
2007-10-27 03:52:42 +08:00
|
|
|
break
|
|
|
|
|
2013-11-12 14:54:01 +08:00
|
|
|
if not language_code_re.search(accept_lang):
|
2007-10-27 03:52:42 +08:00
|
|
|
continue
|
|
|
|
|
2013-05-18 20:37:04 +08:00
|
|
|
try:
|
2013-11-12 14:54:01 +08:00
|
|
|
return get_supported_language_variant(accept_lang)
|
2013-05-18 20:37:04 +08:00
|
|
|
except LookupError:
|
|
|
|
continue
|
2005-11-07 05:55:39 +08:00
|
|
|
|
2013-02-24 21:43:45 +08:00
|
|
|
try:
|
2013-11-12 14:54:01 +08:00
|
|
|
return get_supported_language_variant(settings.LANGUAGE_CODE)
|
2013-02-24 21:43:45 +08:00
|
|
|
except LookupError:
|
|
|
|
return settings.LANGUAGE_CODE
|
2005-11-04 12:59:46 +08:00
|
|
|
|
|
|
|
dot_re = re.compile(r'\S')
|
2013-11-03 07:53:29 +08:00
|
|
|
|
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
def blankout(src, char):
|
|
|
|
"""
|
2005-11-07 05:55:39 +08:00
|
|
|
Changes every non-whitespace character to the given char.
|
2006-01-27 23:49:50 +08:00
|
|
|
Used in the templatize function.
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
|
|
|
return dot_re.sub(char, src)
|
|
|
|
|
2013-11-03 03:01:17 +08:00
|
|
|
|
2011-10-19 12:59:47 +08:00
|
|
|
context_re = re.compile(r"""^\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?'))\s*""")
|
2015-08-28 23:09:20 +08:00
|
|
|
inline_re = re.compile(
|
|
|
|
# Match the trans 'some text' part
|
|
|
|
r"""^\s*trans\s+((?:"[^"]*?")|(?:'[^']*?'))"""
|
|
|
|
# Match and ignore optional filters
|
|
|
|
r"""(?:\s*\|\s*[^\s:]+(?::(?:[^\s'":]+|(?:"[^"]*?")|(?:'[^']*?')))?)*"""
|
|
|
|
# Match the optional context part
|
|
|
|
r"""(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?\s*"""
|
|
|
|
)
|
2012-09-28 11:34:45 +08:00
|
|
|
block_re = re.compile(r"""^\s*blocktrans(\s+.*context\s+((?:"[^"]*?")|(?:'[^']*?')))?(?:\s+|$)""")
|
2005-11-04 12:59:46 +08:00
|
|
|
endblock_re = re.compile(r"""^\s*endblocktrans$""")
|
|
|
|
plural_re = re.compile(r"""^\s*plural$""")
|
|
|
|
constant_re = re.compile(r"""_\(((?:".*?")|(?:'.*?'))\)""")
|
2008-03-08 11:55:47 +08:00
|
|
|
|
2011-10-19 12:59:47 +08:00
|
|
|
|
2010-12-05 01:42:54 +08:00
|
|
|
def templatize(src, origin=None):
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
2005-11-07 05:55:39 +08:00
|
|
|
Turns a Django template into something that is understood by xgettext. It
|
|
|
|
does so by translating the Django translation tags into standard gettext
|
|
|
|
function invocations.
|
2005-11-04 12:59:46 +08:00
|
|
|
"""
|
2014-11-11 04:40:26 +08:00
|
|
|
from django.template.base import (Lexer, TOKEN_TEXT, TOKEN_VAR,
|
|
|
|
TOKEN_BLOCK, TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK)
|
2012-08-30 04:40:51 +08:00
|
|
|
src = force_text(src, settings.FILE_CHARSET)
|
2014-06-06 14:40:04 +08:00
|
|
|
out = StringIO('')
|
2011-10-19 12:59:47 +08:00
|
|
|
message_context = None
|
2005-11-04 12:59:46 +08:00
|
|
|
intrans = False
|
|
|
|
inplural = False
|
2013-11-03 03:01:17 +08:00
|
|
|
trimmed = False
|
2005-11-04 12:59:46 +08:00
|
|
|
singular = []
|
|
|
|
plural = []
|
2010-11-17 23:37:33 +08:00
|
|
|
incomment = False
|
|
|
|
comment = []
|
2013-01-21 02:07:10 +08:00
|
|
|
lineno_comment_map = {}
|
|
|
|
comment_lineno_cache = None
|
2016-01-20 01:45:50 +08:00
|
|
|
# Adding the u prefix allows gettext to recognize the Unicode string
|
|
|
|
# (#26093).
|
|
|
|
raw_prefix = 'u' if six.PY3 else ''
|
2013-01-21 02:07:10 +08:00
|
|
|
|
2013-11-08 22:44:37 +08:00
|
|
|
def join_tokens(tokens, trim=False):
|
|
|
|
message = ''.join(tokens)
|
|
|
|
if trim:
|
|
|
|
message = trim_whitespace(message)
|
|
|
|
return message
|
|
|
|
|
2015-03-06 23:53:25 +08:00
|
|
|
for t in Lexer(src).tokenize():
|
2010-11-17 23:37:33 +08:00
|
|
|
if incomment:
|
|
|
|
if t.token_type == TOKEN_BLOCK and t.contents == 'endcomment':
|
2012-06-08 00:08:47 +08:00
|
|
|
content = ''.join(comment)
|
2011-03-19 20:56:38 +08:00
|
|
|
translators_comment_start = None
|
|
|
|
for lineno, line in enumerate(content.splitlines(True)):
|
2012-08-30 04:40:51 +08:00
|
|
|
if line.lstrip().startswith(TRANSLATOR_COMMENT_MARK):
|
2011-03-19 20:56:38 +08:00
|
|
|
translators_comment_start = lineno
|
|
|
|
for lineno, line in enumerate(content.splitlines(True)):
|
|
|
|
if translators_comment_start is not None and lineno >= translators_comment_start:
|
2012-06-08 00:08:47 +08:00
|
|
|
out.write(' # %s' % line)
|
2011-03-19 20:56:38 +08:00
|
|
|
else:
|
2012-06-08 00:08:47 +08:00
|
|
|
out.write(' #\n')
|
2010-11-17 23:37:33 +08:00
|
|
|
incomment = False
|
|
|
|
comment = []
|
|
|
|
else:
|
|
|
|
comment.append(t.contents)
|
|
|
|
elif intrans:
|
2005-11-04 12:59:46 +08:00
|
|
|
if t.token_type == TOKEN_BLOCK:
|
|
|
|
endbmatch = endblock_re.match(t.contents)
|
|
|
|
pluralmatch = plural_re.match(t.contents)
|
|
|
|
if endbmatch:
|
|
|
|
if inplural:
|
2011-10-19 12:59:47 +08:00
|
|
|
if message_context:
|
2016-01-20 01:45:50 +08:00
|
|
|
out.write(' npgettext({p}{!r}, {p}{!r}, {p}{!r},count) '.format(
|
2013-11-08 22:44:37 +08:00
|
|
|
message_context,
|
|
|
|
join_tokens(singular, trimmed),
|
2016-01-20 01:45:50 +08:00
|
|
|
join_tokens(plural, trimmed),
|
|
|
|
p=raw_prefix,
|
|
|
|
))
|
2011-10-19 12:59:47 +08:00
|
|
|
else:
|
2016-01-20 01:45:50 +08:00
|
|
|
out.write(' ngettext({p}{!r}, {p}{!r}, count) '.format(
|
2013-11-08 22:44:37 +08:00
|
|
|
join_tokens(singular, trimmed),
|
2016-01-20 01:45:50 +08:00
|
|
|
join_tokens(plural, trimmed),
|
|
|
|
p=raw_prefix,
|
|
|
|
))
|
2005-11-04 12:59:46 +08:00
|
|
|
for part in singular:
|
|
|
|
out.write(blankout(part, 'S'))
|
|
|
|
for part in plural:
|
|
|
|
out.write(blankout(part, 'P'))
|
|
|
|
else:
|
2011-10-19 12:59:47 +08:00
|
|
|
if message_context:
|
2016-01-20 01:45:50 +08:00
|
|
|
out.write(' pgettext({p}{!r}, {p}{!r}) '.format(
|
2013-11-08 22:44:37 +08:00
|
|
|
message_context,
|
2016-01-20 01:45:50 +08:00
|
|
|
join_tokens(singular, trimmed),
|
|
|
|
p=raw_prefix,
|
|
|
|
))
|
2011-10-19 12:59:47 +08:00
|
|
|
else:
|
2016-01-20 01:45:50 +08:00
|
|
|
out.write(' gettext({p}{!r}) '.format(
|
|
|
|
join_tokens(singular, trimmed),
|
|
|
|
p=raw_prefix,
|
|
|
|
))
|
2005-11-04 12:59:46 +08:00
|
|
|
for part in singular:
|
|
|
|
out.write(blankout(part, 'S'))
|
2011-10-19 12:59:47 +08:00
|
|
|
message_context = None
|
2005-11-04 12:59:46 +08:00
|
|
|
intrans = False
|
|
|
|
inplural = False
|
|
|
|
singular = []
|
|
|
|
plural = []
|
|
|
|
elif pluralmatch:
|
|
|
|
inplural = True
|
|
|
|
else:
|
2010-12-05 01:42:54 +08:00
|
|
|
filemsg = ''
|
|
|
|
if origin:
|
|
|
|
filemsg = 'file %s, ' % origin
|
2014-09-04 20:15:09 +08:00
|
|
|
raise SyntaxError(
|
|
|
|
"Translation blocks must not include other block tags: "
|
|
|
|
"%s (%sline %d)" % (t.contents, filemsg, t.lineno)
|
|
|
|
)
|
2005-11-04 12:59:46 +08:00
|
|
|
elif t.token_type == TOKEN_VAR:
|
|
|
|
if inplural:
|
|
|
|
plural.append('%%(%s)s' % t.contents)
|
|
|
|
else:
|
|
|
|
singular.append('%%(%s)s' % t.contents)
|
|
|
|
elif t.token_type == TOKEN_TEXT:
|
2015-04-16 05:01:11 +08:00
|
|
|
contents = t.contents.replace('%', '%%')
|
2005-11-04 12:59:46 +08:00
|
|
|
if inplural:
|
2010-11-04 22:06:24 +08:00
|
|
|
plural.append(contents)
|
2005-11-04 12:59:46 +08:00
|
|
|
else:
|
2010-11-04 22:06:24 +08:00
|
|
|
singular.append(contents)
|
2013-01-21 02:07:10 +08:00
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
else:
|
2013-01-21 02:07:10 +08:00
|
|
|
# Handle comment tokens (`{# ... #}`) plus other constructs on
|
|
|
|
# the same line:
|
|
|
|
if comment_lineno_cache is not None:
|
|
|
|
cur_lineno = t.lineno + t.contents.count('\n')
|
|
|
|
if comment_lineno_cache == cur_lineno:
|
|
|
|
if t.token_type != TOKEN_COMMENT:
|
|
|
|
for c in lineno_comment_map[comment_lineno_cache]:
|
|
|
|
filemsg = ''
|
|
|
|
if origin:
|
|
|
|
filemsg = 'file %s, ' % origin
|
|
|
|
warn_msg = ("The translator-targeted comment '%s' "
|
|
|
|
"(%sline %d) was ignored, because it wasn't the last item "
|
|
|
|
"on the line.") % (c, filemsg, comment_lineno_cache)
|
|
|
|
warnings.warn(warn_msg, TranslatorCommentWarning)
|
|
|
|
lineno_comment_map[comment_lineno_cache] = []
|
|
|
|
else:
|
|
|
|
out.write('# %s' % ' | '.join(lineno_comment_map[comment_lineno_cache]))
|
|
|
|
comment_lineno_cache = None
|
|
|
|
|
2005-11-04 12:59:46 +08:00
|
|
|
if t.token_type == TOKEN_BLOCK:
|
|
|
|
imatch = inline_re.match(t.contents)
|
|
|
|
bmatch = block_re.match(t.contents)
|
|
|
|
cmatches = constant_re.findall(t.contents)
|
|
|
|
if imatch:
|
|
|
|
g = imatch.group(1)
|
2011-10-19 12:59:47 +08:00
|
|
|
if g[0] == '"':
|
|
|
|
g = g.strip('"')
|
|
|
|
elif g[0] == "'":
|
|
|
|
g = g.strip("'")
|
2015-04-16 05:01:11 +08:00
|
|
|
g = g.replace('%', '%%')
|
2011-10-19 12:59:47 +08:00
|
|
|
if imatch.group(2):
|
|
|
|
# A context is provided
|
|
|
|
context_match = context_re.match(imatch.group(2))
|
|
|
|
message_context = context_match.group(1)
|
|
|
|
if message_context[0] == '"':
|
|
|
|
message_context = message_context.strip('"')
|
|
|
|
elif message_context[0] == "'":
|
|
|
|
message_context = message_context.strip("'")
|
2016-01-20 01:45:50 +08:00
|
|
|
out.write(' pgettext({p}{!r}, {p}{!r}) '.format(
|
|
|
|
message_context, g, p=raw_prefix
|
|
|
|
))
|
2011-10-19 12:59:47 +08:00
|
|
|
message_context = None
|
|
|
|
else:
|
2016-01-20 01:45:50 +08:00
|
|
|
out.write(' gettext({p}{!r}) '.format(g, p=raw_prefix))
|
2005-11-04 12:59:46 +08:00
|
|
|
elif bmatch:
|
2007-09-15 13:14:16 +08:00
|
|
|
for fmatch in constant_re.findall(t.contents):
|
|
|
|
out.write(' _(%s) ' % fmatch)
|
2011-10-19 12:59:47 +08:00
|
|
|
if bmatch.group(1):
|
|
|
|
# A context is provided
|
|
|
|
context_match = context_re.match(bmatch.group(1))
|
|
|
|
message_context = context_match.group(1)
|
|
|
|
if message_context[0] == '"':
|
|
|
|
message_context = message_context.strip('"')
|
|
|
|
elif message_context[0] == "'":
|
|
|
|
message_context = message_context.strip("'")
|
2005-11-04 12:59:46 +08:00
|
|
|
intrans = True
|
|
|
|
inplural = False
|
2013-11-03 03:01:17 +08:00
|
|
|
trimmed = 'trimmed' in t.split_contents()
|
2005-11-04 12:59:46 +08:00
|
|
|
singular = []
|
|
|
|
plural = []
|
|
|
|
elif cmatches:
|
|
|
|
for cmatch in cmatches:
|
2012-06-08 00:08:47 +08:00
|
|
|
out.write(' _(%s) ' % cmatch)
|
2010-11-17 23:37:33 +08:00
|
|
|
elif t.contents == 'comment':
|
|
|
|
incomment = True
|
2005-11-04 12:59:46 +08:00
|
|
|
else:
|
|
|
|
out.write(blankout(t.contents, 'B'))
|
|
|
|
elif t.token_type == TOKEN_VAR:
|
|
|
|
parts = t.contents.split('|')
|
|
|
|
cmatch = constant_re.match(parts[0])
|
|
|
|
if cmatch:
|
2012-06-08 00:08:47 +08:00
|
|
|
out.write(' _(%s) ' % cmatch.group(1))
|
2005-11-04 12:59:46 +08:00
|
|
|
for p in parts[1:]:
|
|
|
|
if p.find(':_(') >= 0:
|
2013-10-27 01:50:40 +08:00
|
|
|
out.write(' %s ' % p.split(':', 1)[1])
|
2005-11-04 12:59:46 +08:00
|
|
|
else:
|
|
|
|
out.write(blankout(p, 'F'))
|
2010-11-17 23:37:33 +08:00
|
|
|
elif t.token_type == TOKEN_COMMENT:
|
2013-01-21 02:07:10 +08:00
|
|
|
if t.contents.lstrip().startswith(TRANSLATOR_COMMENT_MARK):
|
|
|
|
lineno_comment_map.setdefault(t.lineno,
|
|
|
|
[]).append(t.contents)
|
|
|
|
comment_lineno_cache = t.lineno
|
2005-11-04 12:59:46 +08:00
|
|
|
else:
|
|
|
|
out.write(blankout(t.contents, 'X'))
|
2014-05-24 17:51:57 +08:00
|
|
|
return out.getvalue()
|
2006-05-02 09:31:56 +08:00
|
|
|
|
2013-11-03 07:53:29 +08:00
|
|
|
|
2007-10-27 03:52:42 +08:00
|
|
|
def parse_accept_lang_header(lang_string):
|
|
|
|
"""
|
|
|
|
Parses the lang_string, which is the body of an HTTP Accept-Language
|
|
|
|
header, and returns a list of (lang, q-value), ordered by 'q' values.
|
|
|
|
|
|
|
|
Any format errors in lang_string results in an empty list being returned.
|
|
|
|
"""
|
|
|
|
result = []
|
2013-11-12 14:54:01 +08:00
|
|
|
pieces = accept_language_re.split(lang_string.lower())
|
2007-10-27 03:52:42 +08:00
|
|
|
if pieces[-1]:
|
|
|
|
return []
|
|
|
|
for i in range(0, len(pieces) - 1, 3):
|
2013-10-27 09:27:42 +08:00
|
|
|
first, lang, priority = pieces[i:i + 3]
|
2007-10-27 03:52:42 +08:00
|
|
|
if first:
|
|
|
|
return []
|
2013-05-17 22:33:36 +08:00
|
|
|
if priority:
|
2013-09-10 00:48:56 +08:00
|
|
|
try:
|
|
|
|
priority = float(priority)
|
|
|
|
except ValueError:
|
|
|
|
return []
|
2013-05-17 22:33:36 +08:00
|
|
|
if not priority: # if priority is 0.0 at this point make it 1.0
|
2013-09-04 02:22:21 +08:00
|
|
|
priority = 1.0
|
2007-10-27 03:52:42 +08:00
|
|
|
result.append((lang, priority))
|
2010-08-07 00:31:44 +08:00
|
|
|
result.sort(key=lambda k: k[1], reverse=True)
|
2007-10-27 03:52:42 +08:00
|
|
|
return result
|