summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xdjango/bin/compile-messages.py24
-rwxr-xr-xdjango/bin/make-messages.py47
-rw-r--r--django/conf/admin_templates/index.html10
-rw-r--r--django/conf/locale/de/LC_MESSAGES/django.mobin0 -> 951 bytes
-rw-r--r--django/conf/locale/de/LC_MESSAGES/django.po51
-rw-r--r--django/conf/settings.py7
-rw-r--r--django/core/defaulttags.py43
-rw-r--r--django/core/validators.py2
-rw-r--r--django/middleware/locale.py38
-rw-r--r--django/utils/translation.py195
-rw-r--r--setup.py2
11 files changed, 412 insertions, 7 deletions
diff --git a/django/bin/compile-messages.py b/django/bin/compile-messages.py
new file mode 100755
index 0000000000..0b5127f6b2
--- /dev/null
+++ b/django/bin/compile-messages.py
@@ -0,0 +1,24 @@
+#!/usr/bin/python
+
+import os
+import sys
+import getopt
+
+basedir = None
+
+if os.path.isdir(os.path.join('conf', 'locale')):
+ basedir = os.path.abspath(os.path.join('conf', 'locale'))
+elif os.path.isdir('locale'):
+ basedir = os.path.abspath('locale')
+else:
+ print "this script should be run from the django svn tree or your project or app tree"
+ sys.exit(1)
+
+for (dirpath, dirnames, filenames) in os.walk(basedir):
+ for file in filenames:
+ if file.endswith('.po'):
+ sys.stderr.write('processing file %s in %s\n' % (file, dirpath))
+ pf = os.path.splitext(os.path.join(dirpath, file))[0]
+ cmd = 'msgfmt -o %s.mo %s.po' % (pf, pf)
+ os.system(cmd)
+
diff --git a/django/bin/make-messages.py b/django/bin/make-messages.py
new file mode 100755
index 0000000000..e068382292
--- /dev/null
+++ b/django/bin/make-messages.py
@@ -0,0 +1,47 @@
+#!/usr/bin/python
+
+import os
+import sys
+import getopt
+
+basedir = None
+
+if os.path.isdir(os.path.join('conf', 'locale')):
+ basedir = os.path.abspath(os.path.join('conf', 'locale'))
+elif os.path.isdir('locale'):
+ basedir = os.path.abspath('locale')
+else:
+ print "this script should be run from the django svn tree or your project or app tree"
+ sys.exit(1)
+
+(opts, args) = getopt.getopt(sys.argv[1:], 'l:d:')
+
+lang = None
+domain = 'django'
+
+for o, v in opts:
+ if o == '-l':
+ lang = v
+ elif o == '-d':
+ domain = v
+
+if lang is None or domain is None:
+ print "usage: make-messages.py -l <language> -d <domain>"
+ sys.exit(1)
+
+basedir = os.path.join(basedir, lang, 'LC_MESSAGES')
+if not os.path.isdir(basedir):
+ os.makedirs(basedir)
+
+lf = os.path.join(basedir, '%s.po' % domain)
+
+for (dirpath, dirnames, filenames) in os.walk("."):
+ for file in filenames:
+ if file.endswith('.py') or file.endswith('.html'):
+ sys.stderr.write('processing file %s in %s\n' % (file, dirpath))
+ if os.path.isfile(lf):
+ cmd = 'xgettext -j -d %s -L Python -p %s %s' % (domain, basedir, os.path.join(dirpath, file))
+ else:
+ cmd = 'xgettext -d %s -L Python -p %s %s' % (domain, basedir, os.path.join(dirpath, file))
+ os.system(cmd)
+
diff --git a/django/conf/admin_templates/index.html b/django/conf/admin_templates/index.html
index 7610b3a023..1a6949cf6c 100644
--- a/django/conf/admin_templates/index.html
+++ b/django/conf/admin_templates/index.html
@@ -23,13 +23,13 @@
{% endif %}
{% if model.perms.add %}
- <td class="x50"><a href="{{ model.admin_url }}add/" class="addlink">Add</a></td>
+ <td class="x50"><a href="{{ model.admin_url }}add/" class="addlink">{% i18n _('Add') %}</a></td>
{% else %}
<td class="x50">&nbsp;</td>
{% endif %}
{% if model.perms.change %}
- <td class="x75"><a href="{{ model.admin_url }}" class="changelink">Change</a></td>
+ <td class="x75"><a href="{{ model.admin_url }}" class="changelink">{% i18n _('Change') %}</a></td>
{% else %}
<td class="x75">&nbsp;</td>
{% endif %}
@@ -47,12 +47,12 @@
{% block sidebar %}
<div id="content-related">
<div class="module" id="recent-actions-module">
- <h2>Recent Actions</h2>
- <h3>My Actions</h3>
+ <h2>{% i18n _('Recent Actions') %}</h2>
+ <h3>{% i18n _('My Actions') %}</h3>
{% load auth.log %}
{% get_admin_log 10 as admin_log for_user user %}
{% if not admin_log %}
- <p>None available</p>
+ <p>{% i18n _('None available') %}</p>
{% else %}
<ul class="actionlist">
{% for entry in admin_log %}
diff --git a/django/conf/locale/de/LC_MESSAGES/django.mo b/django/conf/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 0000000000..7dad6c8f66
--- /dev/null
+++ b/django/conf/locale/de/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/de/LC_MESSAGES/django.po b/django/conf/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..68b7de76fe
--- /dev/null
+++ b/django/conf/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,51 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2005-09-29 14:11+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=iso-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: core/validators.py:56
+msgid "This value must contain only letters, numbers and underscores."
+msgstr "Der Wert darf nur Buchstaben, Ziffern und Unterstriche enthalten."
+
+#: conf/admin_templates/404.html:3 conf/admin_templates/404.html:7
+msgid "Page not found"
+msgstr "Seite nicht gefunden"
+
+#: conf/admin_templates/404.html:9
+msgid "We're sorry, but the requested page could not be found."
+msgstr ""
+"Es tut uns leid, aber die angeforderte Seite kann nicht gefunden werden."
+
+#: conf/admin_templates/index.html:26
+msgid "Add"
+msgstr "Zufügen"
+
+#: conf/admin_templates/index.html:32
+msgid "Change"
+msgstr "Ändern"
+
+#: conf/admin_templates/index.html:50
+msgid "Recent Actions"
+msgstr "Kürzliche Aktionen"
+
+#: conf/admin_templates/index.html:51
+msgid "My Actions"
+msgstr "Meine Aktionen"
+
+#: conf/admin_templates/index.html:55
+msgid "None available"
+msgstr "Keine vorhanden"
+
diff --git a/django/conf/settings.py b/django/conf/settings.py
index 29b8f043aa..2001a06ffd 100644
--- a/django/conf/settings.py
+++ b/django/conf/settings.py
@@ -55,3 +55,10 @@ for k in dir(me):
if not k.startswith('_') and k != 'me' and k != k.upper():
delattr(me, k)
del me, k
+
+# as the last step, install the translation machinery and
+# remove the module again to not clutter the namespace.
+from django.utils import translation
+translation.install()
+del translation
+
diff --git a/django/core/defaulttags.py b/django/core/defaulttags.py
index 103eb4c9f4..29eaf4cf70 100644
--- a/django/core/defaulttags.py
+++ b/django/core/defaulttags.py
@@ -1,8 +1,11 @@
"Default tags used by the template system, available to all templates."
+import re
import sys
import template
+from django.utils import translation
+
class CommentNode(template.Node):
def render(self, context):
return ''
@@ -283,6 +286,28 @@ class WidthRatioNode(template.Node):
return ''
return str(int(round(ratio)))
+class I18NNode(template.Node):
+
+ def __init__(self, cmd):
+ self.cmd = cmd
+ self.i18n_re = re.compile(r'^\s*_\((.*)\)\s*$')
+
+ def render(self, context):
+ m = self.i18n_re.match(self.cmd)
+ if m:
+ s = m.group(1)
+ if s.startswith("'") and s.endswith("'"):
+ s = s[1:-1]
+ elif s.startswith('"""') and s.endswith('"""'):
+ s = s[3:-3]
+ elif s.startswith('"') and s.endswith('"'):
+ s = s[1:-1]
+ else:
+ raise template.TemplateSyntaxError("i18n must be called as {% i18n _('some message') %}")
+ return translation.gettext(s) % context
+ else:
+ raise template.TemplateSyntaxError("i18n must be called as {% i18n _('some message') %}")
+
def do_comment(parser, token):
"""
Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
@@ -745,6 +770,22 @@ def do_widthratio(parser, token):
raise template.TemplateSyntaxError("widthratio final argument must be an integer")
return WidthRatioNode(this_value_var, max_value_var, max_width)
+def do_i18n(parser, token):
+ """
+ translate a given string with the current
+ translation object.
+
+ For example::
+
+ {% i18n _('test') %}
+
+ """
+ args = token.contents.split(' ', 1)
+ if len(args) != 2:
+ raise template.TemplateSyntaxError("'i18n' requires one argument (got %r)" % args)
+
+ return I18NNode(args[1].strip())
+
template.register_tag('comment', do_comment)
template.register_tag('cycle', do_cycle)
template.register_tag('debug', do_debug)
@@ -761,3 +802,5 @@ template.register_tag('load', do_load)
template.register_tag('now', do_now)
template.register_tag('templatetag', do_templatetag)
template.register_tag('widthratio', do_widthratio)
+template.register_tag('i18n', do_i18n)
+
diff --git a/django/core/validators.py b/django/core/validators.py
index 697c2efe81..bc70a330e0 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -53,7 +53,7 @@ class CriticalValidationError(Exception):
def isAlphaNumeric(field_data, all_data):
if not alnum_re.search(field_data):
- raise ValidationError, "This value must contain only letters, numbers and underscores."
+ raise ValidationError, _("This value must contain only letters, numbers and underscores.")
def isAlphaNumericURL(field_data, all_data):
if not alnumurl_re.search(field_data):
diff --git a/django/middleware/locale.py b/django/middleware/locale.py
new file mode 100644
index 0000000000..becab282ee
--- /dev/null
+++ b/django/middleware/locale.py
@@ -0,0 +1,38 @@
+"this is the locale selecting middleware that will look at accept headers"
+
+from django.utils import translation
+
+# this is a cache that will build a map from modules to applications
+_module_to_app = {}
+
+class LocaleMiddleware:
+ """
+ This is a very simple middleware that parses a request
+ and decides what translation object to install in the current
+ thread context. This allows pages to be dynamically
+ translated to the language the user desires (if the language
+ is available, of course).
+ """
+
+ def process_view(self, request, view_func, param_dict):
+ global _module_to_app
+
+ lang = translation.get_language_from_request(request)
+
+
+ def findapp(module):
+ app = _module_to_app.get(view_func.__module__, None)
+ if app is not None:
+ return app
+
+ from django.conf import settings
+ for app in settings.INSTALLED_APPS:
+ if module.startswith(app):
+ _module_to_app[module] = app
+ return app
+ return '*'
+
+ app = findapp(view_func.__module__)
+
+ translation.activate(app, lang)
+
diff --git a/django/utils/translation.py b/django/utils/translation.py
new file mode 100644
index 0000000000..293c7ed261
--- /dev/null
+++ b/django/utils/translation.py
@@ -0,0 +1,195 @@
+"translation helper functions"
+
+import os
+import gettext as gettext_module
+
+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 = {}
+
+class DjangoTranslation(gettext_module.GNUTranslations):
+ """
+ This class sets up the GNUTranslations context with
+ regard to output charset. Django allways uses utf-8
+ as the output charset.
+ """
+
+ def __init__(self, *args, **kw):
+ gettext_module.GNUTranslations.__init__(self, *args, **kw)
+ self.__charset = self.charset()
+ self.set_output_charset('utf-8')
+ self.__app = '?.?.?'
+ self.__language = '??'
+
+ def set_app_and_language(self, app, language):
+ self.__app = app
+ self.__language = language
+
+ def __repr__(self):
+ return "<DjangoTranslation app:%s lang:%s>" % (self.__app, self.__language)
+
+def translation(appname, language):
+ """
+ This function returns a translation object.
+ app must be the fully qualified name of the
+ application.
+
+ This function will first look into the app
+ messages directory for the django message file,
+ then in the project messages directory for the
+ django message file and last in the global
+ messages directory for the django message file.
+ """
+
+ t = _translations.get((appname, language), None)
+ if t is not None:
+ return t
+
+ from django.conf import settings
+
+ globalpath = os.path.join(os.path.dirname(settings.__file__), 'locale')
+
+ try:
+ t = gettext_module.translation('django', globalpath, [language, settings.LANGUAGE_CODE], DjangoTranslation)
+ t.set_app_and_language(appname, language)
+ except IOError: t = gettext_module.NullTranslations()
+ _translations[(appname, language)] = t
+
+ if appname != '*':
+ parts = appname.split('.')
+ project = __import__(parts[0], {}, {}, [])
+ app = __import__(appname, {}, {}, ['views'])
+
+ apppath = os.path.join(os.path.dirname(app.__file__), 'locale')
+ projectpath = os.path.join(os.path.dirname(project.__file__), 'locale')
+
+ try:
+ t = gettext_module.translation('django', projectpath, [language, settings.LANGUAGE_CODE], DjangoTranslation)
+ t.set_app_and_language(appname, language)
+ except IOError: t = None
+ if t is not None:
+ t.add_fallback(_translations[(appname, language)])
+ _translations[(appname, language)] = t
+
+ try:
+ t = gettext_module.translation('django', apppath, [language, settings.LANGUAGE_CODE], DjangoTranslation)
+ t.set_app_and_language(appname, language)
+ except IOError: t = None
+ if t is not None:
+ t.add_fallback(_translations[(appname, language)])
+ _translations[(appname, language)] = t
+
+ return _translations[(appname, language)]
+
+def activate(appname, 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.
+ """
+ t = translation(appname, language)
+ _active[currentThread()] = t
+
+def deactivate():
+ """
+ This function deinstalls the currently active translation
+ object so that further _ calls will resolve against the
+ default translation object, again.
+ """
+ del _active[currentThread()]
+
+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 get_language_from_request(request):
+ """
+ analyze the request to find what language the user
+ wants the system to show.
+ """
+ global _accepted
+
+ if hasattr(request, 'session'):
+ lang = getattr(request.session, 'django_language', None)
+ if lang is not None:
+ return lang
+
+ lang = request.COOKIES.get('django_language', None)
+ if lang is not None:
+ return lang
+
+ from django.conf import settings
+
+ 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
+ return (lang, order)
+
+ langs = [_parsed(el) for el in accept.split(',')]
+ langs.sort(lambda a,b: cmp(a[1], b[1]))
+
+ globalpath = os.path.join(os.path.dirname(settings.__file__), 'locale')
+
+ for lang, order in langs:
+ if os.path.isfile(os.path.join(globalpath, lang, 'LC_MESSAGES', 'django.mo')):
+ _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
+
diff --git a/setup.py b/setup.py
index 58c5094070..c9a2f23918 100644
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,7 @@ setup(
'admin_templates/registration/*.html',
'admin_media/css/*.css', 'admin_media/img/admin/*.gif',
'admin_media/img/admin/*.png', 'admin_media/js/*.js',
- 'admin_media/js/admin/*js'],
+ 'admin_media/js/admin/*js', 'locale/*/*/*.mo'],
},
scripts = ['django/bin/django-admin.py'],
zip_safe = False,