diff options
| author | Georg Bauer <gb@hugo.westfalen.de> | 2005-09-29 17:01:05 +0000 |
|---|---|---|
| committer | Georg Bauer <gb@hugo.westfalen.de> | 2005-09-29 17:01:05 +0000 |
| commit | 98d74306099d73a4e6765bf677207b240f82dd16 (patch) | |
| tree | 062de72e915bce4475f5bf7470970cfb9cbee74e /django | |
| parent | a60b2050c643314996434117892136fba8adb487 (diff) | |
i18n patch - references #65
git-svn-id: http://code.djangoproject.com/svn/django/branches/i18n@726 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
| -rwxr-xr-x | django/bin/compile-messages.py | 24 | ||||
| -rwxr-xr-x | django/bin/make-messages.py | 47 | ||||
| -rw-r--r-- | django/conf/admin_templates/index.html | 10 | ||||
| -rw-r--r-- | django/conf/locale/de/LC_MESSAGES/django.mo | bin | 0 -> 951 bytes | |||
| -rw-r--r-- | django/conf/locale/de/LC_MESSAGES/django.po | 51 | ||||
| -rw-r--r-- | django/conf/settings.py | 7 | ||||
| -rw-r--r-- | django/core/defaulttags.py | 43 | ||||
| -rw-r--r-- | django/core/validators.py | 2 | ||||
| -rw-r--r-- | django/middleware/locale.py | 38 | ||||
| -rw-r--r-- | django/utils/translation.py | 195 |
10 files changed, 411 insertions, 6 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"> </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"> </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 Binary files differnew file mode 100644 index 0000000000..7dad6c8f66 --- /dev/null +++ b/django/conf/locale/de/LC_MESSAGES/django.mo 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 + |
