i18n and django

Jacob did set up branch commit rights for me and a branch for i18n stuff. So I worked today on the ideas in the patch on ticket 65 by nesh. I did write the stuff mostly from scratch because I wanted some things a bit differently and now it is available for testing.

So first on how you can use the i18n stuff with your django checkout. You need to have a current svn trunk checkout and go to the root of your checkout and issue the following command:

svn switch http://code.djangoproject.com/svn/django/branches/i18n/

After that you should have a tree with my patches applied. I currently only translated very little stuff to make the patch and changes as small as possible, but I already added a german translation file with stuf for the admin index and the isAlphaNumeric validator. I think I will add some more stuff to the translations soon.

The patch only addresses the translation part - other things like date formatting, number formatting, timezone handling should go into different patches to make each one of them as small as possible. The translation object (that's the beast that is responsible for turning strings into their new form) is built on request. This gives us the chance to look at various places that might help in deciding what language to present to the user. The code starts by looking into the session for a django_language variable. If that isn't found, it looks into the cookies for a django_lanaguage cookie. If that isn't found, too, it digs at the HTTP headers. It looks for the Http-accept-language header and splits that up by languages and sorts them by preference. It will use the first language (ordered by preference, highest preferences first) found in the django messagefile repository. If none of those languages can be found, it will ultimately fall back to the default translation object that is defined by the LANGUAGE_CODE setting in your settings file.Message files can be stored in three different places: the django project message files are stored in the django.conf package in a locale subdirectory. This is much like the admin_media and admintemplate directories. The locale subdirectory is structured as is typical with locale storage: one subdirectory per language and a LCMESSAGES directory in there. The language domain for django message files is allways django. The next place where django looks for message files is in the project - if you have a locale directory in your project, you can store additional message files there. The third place is the application - you can have a locale directory besides your apps views directory. All locale directories are structured the same.

A translation object for a given language is actually a concatenation of four translation objects: first the application translation object. This will have a fallback to the project translation. That in turn will fallback to the global translation object which will fallback to the translation object for the default language. That way higher levels can override translations from lower levels and applications can provide their own translations.

The application for the translations is actually discovered by module introspection - it uses the viewfunc to call on a URL to discover what application carries this viewfunc and uses that to look for local translations. There are two tools provided to manage translations: make-messages.py and compile-messages.py. Both tools can be called in either the root of the django svn tree or in the project directory or the application directory. make-messages.py will scan the current directory and everything below that for strings to translate and will create a django.po file in the locale directory for the given language. compile-messages.py will just turn all .po files into .mo files. Adding translations is easy. In python code you just surround strings (only string constants!) with ('...') or ("..."). That will mark those strings for translation so that make-messages.py can pull them out and write them to the .po files. And it will translate the string on runtime, using the current translation object as discovered from the request. In templates there is the template tag {% i18n ('....') %} - same syntax as with python code, only you need to wrap it as a template tag. Those strings will be pulled from the .html files into the .po, too. The i18n tag supports string interpolation from the context: {% i18n ('blah %(blubb)s blubber') %} would first translate the string and then interpolate the context variable blubb into the translated result.

A hint: when writing strings to translate, don't use positional parameters for interpolation (the %s stuff) but use named parameters (%(blah)s) instead. That way people building translations can reorder the string without breaking your code - some languages have different orders from english.

Using the translations is easy, too: you just need to set your default language in the LANGUAGE_CODE setting and add the django.middleware.locale.LocaleMiddleware to your middleware setting. You need to put it to the top - especially in the admin it should come before the AdminUserRequired middleware - but it needs to be after the SessionMiddleware, if you use that.

That's it for a start, play with it and tell me when something goes wrong. Best place to tell is on the ticket in the django trac.

tags: Django, Programmierung, Python

Andreas Sept. 29, 2005, 8:02 p.m.

i18n für Django? Genial. Vielen Dank für deine Arbeit!

Kannst du mir sagen, wie sich die generelle Verwendung des i18n-Branches -- d.h. nicht nur im Admin-Interface -- auf die Performance einer Django-Anwendung auswirkt? Ist eine Verwendung bei stärker beanspruchten Anwendungen sinnvoll?

hugo Sept. 29, 2005, 11:29 p.m.

Ich bin gerade erst damit angefangen, von daher kann ich zur Performance nicht viel sagen. Allerdings benutze ich intern die gettext Funktionen, das sollte also einigermaßen schnell sein. Python gettext verwaltet die Strings in dictionaries und Lookups sind da ja recht fix. Allerdings wird natürlich schon sicherlich was an Performance für die Übersetzungen drauf gehen - aber ohne das kriegt man halt keine übersetzten Texte ...

Warscheinlich baue ich die Library noch etwas um so das bei nicht-Benutzung der locale Middleware auch garantiert die _() nur eine Dummy-Funktion ist, also möglichst geringer Overhead herrscht. Dann kann man einfach selber entscheiden, ob man die Übersetzungen braucht und aktivieren will.

amit upadhyay Oct. 1, 2005, 4:12 p.m.

Hi,

In templates there is the template tag {% i18n _(’….’) %}

Is it not slightly redundent, having both i18n and _()? Was i18n not enough? The lesser to type or fewer things to know, the fewer things can go wrong.

hugo Oct. 1, 2005, 5:51 p.m.

No, it's not redundant. Actually it's needed. The django template system requires a template tag to be between those {% ... %}. And the xgettext tool requires something like _(...) to recognize strings to be translated. So it's usefull to build the template tag identical to the way you would mark translations in the python source, as you then can use xgettext to pull out strings of templates.

But I added a shortcut: you can now do {{ _('blah') }} to just translate simple strings. That's much more intuitive than using the i18n tag. And will work now with all tags that accept constant strings, too.