Code is in django.utils.encoding. Originally written by Hugo Bauer. Currently more or less maintained by me, because everybody else took one step backwards when volunteers were needed and I was asleep at the time. The lazy() decorator is the main entry point here. It takes a function and any number of types. These types are types that are returned by the function and it works best if the function always returns something that can act as *any* of these types at once. For lazy translations, we do things like lazy(ugettext, unicode), since ugettext() always returns a Unicode object. It would not be correct to write lazy(ugettext, unicode, str), since the returned objects from ugettext() cannot always be treated as str types. Internally, the constructor for a lazy() object runs through all the methods on each of the types and adds proxy calls for those onto the resulting Promise instance that is returned. This returns a class with a constructor which takes arbitrary arguments and returns a Promise instance (actually, a __proxy__ instance). When one of the proxy methods are called on the __proxy__ instance (at a later time), it then calls the original function on the given arguments and finally invokes the proxy method. Why do we need this? When the object is created (e.g. a ugettext_lazy() translation call), the environment or something else might not be set up the same as it will be at usage time. For example, the current locale will be different when the string is marked for translation and when it is sent back to the user. We want to translate in the final locale (as late as possible), not when the marking is done, which might be at import time. For example, consider ugettext_lazy = lazy(ugettext, unicode). And suppose s = ugettext_lazy('Some string') Now, s is Promise instance (it's actually a subclass of Promise, but that detail is irrelevant). If we call unicode(s) Python wants to execute s.__unicode__(), in effect. The __unicode__ method exists on the Promise -- because it was one of the methods on the 'unicode' type passed into the lazy() constructor. The __unicode__ proxy method calls ugettext() on the arguments given when s was constructed ('Some string') and gets back a result (a unicode object, representing a Unicode string). it then calls __unicode__ on that result -- effectively a no-op in this case -- and returns the resulting unicode object. Most of the time, this approach works very well and smoothly. There are a few tricky bits, particularly with string handling, however. We'll get to that in a minute. Firstly, though, if you think about how a proxy object works, ugettext_lazy() and friends act as very good proxies for strings in almost all cases. Whenever we try to put them into a string, such as u'foo %s bar' % proxy_instance, Python calls the right method to convert via the %s format marker. Similarly, force_unicode() and friends end up coercing it from a __proxy__ to a unicode more or less transparently. Django's smart_unicode() method has to be Promise-aware so that it doesn't do the coercion too early. It passes Promise instances through unmolested, since they are already transparently coercible to unicode objects when required, so nothing special needs to be done. That is why we can call smart_unicode() with impunity on string-like things that might be lazy translations. So, the problems... -------------------- The __str__ method needs special handling. This is because __str__ is a method on the "type" class in Python, so it will always exist and that is the version that is retrieved via getattr(). You cannot replace an instances __str__ method in __init__ by assigning to self.__str__, for example. So the lazy() call has to handle ___str__ methods specially (which it does). Similar special handling is in place for __unicode__, __mod__ (for u'%s' % foo handling) and __cmp__ (so that s == u'foo' works). There is also a Python 2.3 issue here (a bug in 2.3): u'%s' % foo should call foo.__unicode__, if it exists. But in 2.3, it calls foo.__str__ always. So we need to write this as u'%s' % unicode(foo). Debugging problems ------------------ If something is sent to HTML as , rather than a string, try to work out why it's __unicode__ (or __str__, if appropriate) method is not being called. Sometimes adding explicit unicode() or force_unicode() calls can help. Sometimes that is just papering over the real problem. More than once, we've discovered that the object was being treated as a str, rather than a unicode (__str__ was called on something that was meant to be a unicode object) and that was usually a bug.