summaryrefslogtreecommitdiff
path: root/django/utils/translation.py
diff options
context:
space:
mode:
Diffstat (limited to 'django/utils/translation.py')
-rw-r--r--django/utils/translation.py447
1 files changed, 447 insertions, 0 deletions
diff --git a/django/utils/translation.py b/django/utils/translation.py
new file mode 100644
index 0000000000..0d40a636c8
--- /dev/null
+++ b/django/utils/translation.py
@@ -0,0 +1,447 @@
+"translation helper functions"
+
+import os
+import re
+import sys
+import gettext as gettext_module
+from cStringIO import StringIO
+
+from django.utils.functional import lazy
+
+try:
+ import threading
+ hasThreads = True
+except ImportError:
+ hasThreads = False
+
+if hasThreads:
+ currentThread = threading.currentThread
+else:
+ def currentThread():
+ return 'no threading'
+
+# translations are cached in a dictionary for
+# every language+app tuple. The active translations
+# are stored by threadid to make them thread local.
+_translations = {}
+_active = {}
+
+# the default translation is based on the settings file
+_default = None
+
+# this is a cache for accept-header to translation
+# object mappings to prevent the accept parser to
+# run multiple times for one user
+_accepted = {}
+
+def to_locale(language):
+ "turn a language name (en-us) into a locale name (en_US)"
+ p = language.find('-')
+ if p >= 0:
+ return language[:p].lower()+'_'+language[p+1:].upper()
+ else:
+ return language.lower()
+
+def to_language(locale):
+ "turns a locale name (en_US) into a language name (en-us)"
+ p = locale.find('_')
+ if p >= 0:
+ return locale[:p].lower()+'-'+locale[p+1:].lower()
+ else:
+ return locale.lower()
+
+class DjangoTranslation(gettext_module.GNUTranslations):
+ """
+ This class sets up the GNUTranslations context with
+ regard to output charset. Django uses a defined
+ DEFAULT_CHARSET as the output charset on Python 2.4 -
+ with Python 2.3, you need to use DjangoTranslation23.
+ """
+
+ def __init__(self, *args, **kw):
+ from django.conf import settings
+ gettext_module.GNUTranslations.__init__(self, *args, **kw)
+ # starting with Python 2.4, there is a function to define
+ # the output charset. Before 2.4, the output charset is
+ # identical with the translation file charset.
+ try:
+ self.set_output_charset(settings.DEFAULT_CHARSET)
+ except AttributeError:
+ pass
+ self.django_output_charset = settings.DEFAULT_CHARSET
+ self.__language = '??'
+
+ def merge(self, other):
+ self._catalog.update(other._catalog)
+
+ def set_language(self, language):
+ self.__language = language
+
+ def language(self):
+ return self.__language
+
+ def __repr__(self):
+ return "<DjangoTranslation lang:%s>" % self.__language
+
+
+class DjangoTranslation23(DjangoTranslation):
+
+ """
+ This is a compatibility class that is only used with Python 2.3.
+ The reason is, Python 2.3 doesn't support set_output_charset on
+ translation objects and so needs this wrapper class to make sure
+ that input charsets from translation files are correctly translated
+ to output charsets.
+
+ With a full switch to Python 2.4, this can be removed from the source.
+ """
+
+ def gettext(self, msgid):
+ res = self.ugettext(msgid)
+ return res.encode(self.django_output_charset)
+
+ def ngettext(self, msgid1, msgid2, n):
+ res = self.ungettext(msgid1, msgid2, n)
+ return res.encode(self.django_output_charset)
+
+def translation(language):
+ """
+ This function returns a translation object. app must be the fully
+ qualified name of the application.
+
+ 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 that is different
+ from the requested language.
+ """
+ global _translations
+
+ t = _translations.get(language, None)
+ if t is not None:
+ return t
+
+ from django.conf import settings
+
+ # set up the right translation class
+ klass = DjangoTranslation
+ if sys.version_info < (2, 4):
+ klass = DjangoTranslation23
+
+ globalpath = os.path.join(os.path.dirname(settings.__file__), 'locale')
+
+ parts = os.environ['DJANGO_SETTINGS_MODULE'].split('.')
+ project = __import__(parts[0], {}, {}, [])
+ projectpath = os.path.join(os.path.dirname(project.__file__), 'locale')
+
+ def _fetch(lang, fallback=None):
+
+ global _translations
+
+ loc = to_locale(lang)
+
+ res = _translations.get(lang, None)
+ if res is not None:
+ return res
+
+ def _translation(path):
+ try:
+ t = gettext_module.translation('django', path, [loc], klass)
+ t.set_language(lang)
+ return t
+ except IOError, e:
+ return None
+
+ res = _translation(globalpath)
+
+ def _merge(path):
+ t = _translation(path)
+ if t is not None:
+ if res is None:
+ return t
+ else:
+ res.merge(t)
+ return res
+
+ if hasattr(settings, 'LOCALE_PATHS'):
+ for localepath in settings.LOCALE_PATHS:
+ if os.path.isdir(localepath):
+ res = _merge(localepath)
+
+ if os.path.isdir(projectpath):
+ res = _merge(projectpath)
+
+ for appname in settings.INSTALLED_APPS:
+ p = appname.rfind('.')
+ if p >= 0:
+ app = getattr(__import__(appname[:p], {}, {}, [appname[p+1:]]), appname[p+1:])
+ else:
+ app = __import__(appname, {}, {}, [])
+
+ apppath = os.path.join(os.path.dirname(app.__file__), 'locale')
+
+ if os.path.isdir(apppath):
+ res = _merge(apppath)
+
+ 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):
+ """
+ This function fetches the translation object for a given
+ tuple of application name and language and installs it as
+ the current translation object for the current thread.
+ """
+ _active[currentThread()] = translation(language)
+
+def deactivate():
+ """
+ This function deinstalls the currently active translation
+ object so that further _ calls will resolve against the
+ default translation object, again.
+ """
+ global _active
+
+ if _active.has_key(currentThread()):
+ del _active[currentThread()]
+
+def get_language():
+ """
+ This function returns the currently selected language.
+ """
+ t = _active.get(currentThread(), None)
+ if t is not None:
+ try:
+ return to_language(t.language())
+ except AttributeError:
+ pass
+ # if we don't have a real translation object, we assume
+ # it's the default language.
+ from django.conf.settings import LANGUAGE_CODE
+ return LANGUAGE_CODE
+
+def gettext(message):
+ """
+ This function will be patched into the builtins module to
+ provide the _ helper function. It will use the current
+ thread as a discriminator to find the translation object
+ to use. If no current translation is activated, the
+ message will be run through the default translation
+ object.
+ """
+ global _default, _active
+
+ t = _active.get(currentThread(), None)
+ if t is not None:
+ return t.gettext(message)
+ if _default is None:
+ from django.conf import settings
+ _default = translation(settings.LANGUAGE_CODE)
+ return _default.gettext(message)
+
+def gettext_noop(message):
+ """
+ This function is used to just mark strings for translation
+ but to not translate them now. This can be used to store
+ strings in global variables that should stay in the base
+ language (because they might be used externally) and will
+ be translated later on.
+ """
+ return message
+
+def ngettext(singular, plural, number):
+ """
+ This function returns the translation of either the singular
+ or plural, based on the number.
+ """
+ global _default, _active
+
+ t = _active.get(currentThread(), None)
+ if t is not None:
+ return t.ngettext(singular, plural, number)
+ if _default is None:
+ from django.conf import settings
+ _default = translation('*', settings.LANGUAGE_CODE)
+ return _default.ngettext(singular, plural, number)
+
+gettext_lazy = lazy(gettext, str)
+ngettext_lazy = lazy(ngettext, str)
+
+def check_for_language(lang_code):
+ """
+ This function checks wether there is a global language
+ file for the given language code. This is used to decide
+ wether a user-provided language is available.
+ """
+ from django.conf import settings
+ globalpath = os.path.join(os.path.dirname(settings.__file__), 'locale')
+ if gettext_module.find('django', globalpath, [to_locale(lang_code)]) is not None:
+ return True
+ else:
+ return False
+
+def get_language_from_request(request):
+ """
+ analyze the request to find what language the user
+ wants the system to show.
+ """
+ global _accepted
+
+ from django.conf import settings
+ globalpath = os.path.join(os.path.dirname(settings.__file__), 'locale')
+
+ if hasattr(request, 'session'):
+ lang_code = request.session.get('django_language', None)
+ if lang_code is not None and check_for_language(lang_code):
+ return lang_code
+
+ lang_code = request.COOKIES.get('django_language', None)
+ if lang_code is not None and check_for_language(lang_code):
+ return lang_code
+
+ accept = request.META.get('HTTP_ACCEPT_LANGUAGE', None)
+ if accept is not None:
+
+ t = _accepted.get(accept, None)
+ if t is not None:
+ return t
+
+ def _parsed(el):
+ p = el.find(';q=')
+ if p >= 0:
+ lang = el[:p].strip()
+ order = int(float(el[p+3:].strip())*100)
+ else:
+ lang = el
+ order = 100
+ if lang.find('-') >= 0:
+ (lang, sublang) = lang.split('-')
+ lang = lang.lower() + '_' + sublang.upper()
+ return (lang, order)
+
+ langs = [_parsed(el) for el in accept.split(',')]
+ langs.sort(lambda a,b: -1*cmp(a[1], b[1]))
+
+ for lang, order in langs:
+ langfile = gettext_module.find('django', globalpath, [to_locale(lang)])
+ if langfile:
+ # reconstruct the actual language from the language
+ # filename, because otherwise we might incorrectly
+ # report de_DE if we only have de available, but
+ # did find de_DE because of language normalization
+ lang = langfile[len(globalpath):].split('/')[1]
+ _accepted[accept] = lang
+ return lang
+
+ return settings.LANGUAGE_CODE
+
+def install():
+ """
+ This installs the gettext function as the default
+ translation function under the name _.
+ """
+ __builtins__['_'] = gettext
+
+
+dot_re = re.compile(r'\S')
+def blankout(src, char):
+ """
+ This is used in the templateize function and changes every
+ non-whitespace character to the given char.
+ """
+ return dot_re.sub(char, src)
+
+inline_re = re.compile(r"""^\s*trans\s+((?:".*?")|(?:'.*?'))\s*""")
+block_re = re.compile(r"""^\s*blocktrans(?:\s+|$)""")
+endblock_re = re.compile(r"""^\s*endblocktrans$""")
+plural_re = re.compile(r"""^\s*plural$""")
+constant_re = re.compile(r"""_\(((?:".*?")|(?:'.*?'))\)""")
+def templateize(src):
+ from django.core.template import tokenize, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK
+ """
+ This function 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.
+ """
+ out = StringIO()
+ intrans = False
+ inplural = False
+ singular = []
+ plural = []
+ for t in tokenize(src):
+ if intrans:
+ if t.token_type == TOKEN_BLOCK:
+ endbmatch = endblock_re.match(t.contents)
+ pluralmatch = plural_re.match(t.contents)
+ if endbmatch:
+ if inplural:
+ out.write(' ngettext(%r,%r,count) ' % (''.join(singular), ''.join(plural)))
+ for part in singular:
+ out.write(blankout(part, 'S'))
+ for part in plural:
+ out.write(blankout(part, 'P'))
+ else:
+ out.write(' gettext(%r) ' % ''.join(singular))
+ for part in singular:
+ out.write(blankout(part, 'S'))
+ intrans = False
+ inplural = False
+ singular = []
+ plural = []
+ elif pluralmatch:
+ inplural = True
+ else:
+ raise SyntaxError, "translation blocks must not include other block tags: %s" % t.contents
+ 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:
+ if inplural:
+ plural.append(t.contents)
+ else:
+ singular.append(t.contents)
+ else:
+ 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)
+ if g[0] == '"': g = g.strip('"')
+ elif g[0] == "'": g = g.strip("'")
+ out.write(' gettext(%r) ' % g)
+ elif bmatch:
+ intrans = True
+ inplural = False
+ singular = []
+ plural = []
+ elif cmatches:
+ for cmatch in cmatches:
+ out.write(' _(%s) ' % cmatch)
+ 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:
+ out.write(' _(%s) ' % cmatch.group(1))
+ for p in parts[1:]:
+ if p.find(':_(') >= 0:
+ out.write(' %s ' % p.split(':',1)[1])
+ else:
+ out.write(blankout(p, 'F'))
+ else:
+ out.write(blankout(t.contents, 'X'))
+ return out.getvalue()
+