import gettext import inspect import os import threading threadlocal = threading.local() class message(object): def __init__(self, domain, msgid, charset='UTF-8'): self.domain = domain if not isinstance(msgid, unicode): msgid = unicode(msgid, charset) self.msgid = msgid self.moddata = None def translate(self, *args, **kwargs): raise LanguageNotSet( 'make sure _.set_language() is called before an i18n.message ' 'object is resolved! (for %r)' % (self.msgid,)) def __mod__(self, right): self.moddata = right return self def __rmod__(self, left): return left % unicode(self) def __contains__(self, s): return s in unicode(self) def __eq__(self, s): return unicode(self) == s def __neq__(self, s): return unicode(self) != s def __add__(self, s): return unicode(self) + s def __radd__(self, s): return s + unicode(self) def __unicode__(self): return self.translate(self.msgid, self.moddata) def __str__(self): u = self.__unicode__() return u.encode('UTF-8') def __repr__(self): u = self.__unicode__() return repr(u) __repr__ = __str__ def __add__(self, right): return unicode(self) + right def __radd__(self, left): return left + unicode(self) def __getattr__(self, name): u = self.__unicode__() return getattr(u, name) class LanguageNotSet(Exception): """ raised if _.get_language() is not yet called """ class domaintranslator(object): def __init__(self, domain, translator, finder, charset='UTF-8'): self.domain = domain self.translator = translator self.finder = finder self.charset = charset self.cats = {} def __call__(self, msgid, charset='UTF-8'): if not isinstance(msgid, unicode): m = message(self.domain, msgid, charset) else: m = message(self.domain, msgid) m.domain = self.domain m.translate = self.translate return m def translate(self, msgid, data): lang = getattr(threadlocal, 'lang', None) if lang is None: raise LanguageNotSet( 'make sure _.set_language() is called before an i18n.message ' 'object is resolved! (for %r)' % (msgid,)) mopath = self.finder(self.domain, lang) cat = self.cats.get(lang) if cat is None: self.cats[lang] = cat = gettext.GNUTranslations(open(mopath)) ret = cat.gettext(msgid) if not isinstance(ret, unicode): ret = unicode(ret, self.charset) if data is not None: ret = ret % data return ret def set_language(self, lang): threadlocal.lang = lang class translator(object): """ translator class register domains and finders using the 'register' method, where 'domain' is a string representing the translation domain, and 'finder' a callable that should return the path to a .mo file based on a language string and the domain to use this, instantiate with the required arguments, the returned value is a callable that returns a message object when called. this callable is usually stored under the name _, to allow pygettext to find the calls and register the arguments as msgids. somewhere in the application (before any of the strings are resolved!) the 'set_language' method should be called with a language string argument to set the language to translate to - the finder method will be called with this string to return the location of a .mo file (handling things like unsupported language strings, etc. by itself). example: >>> import os >>> from i18n.i18n import translator >>> here = os.path.dirname(__file__) >>> def finder(domain, locale): ... locale = locale.split('_')[0] ... if locale in ('en', 'nl'): ... return os.path.join( ... here, 'test', '%s-%s.mo' % (domain, locale)) >>> t = translator() >>> _ = t.register('i18ntest', finder) >>> foo = _('foo') >>> class SomeClass(object): ... def __init__(self): ... _.set_language('en') ... def foo(self): ... return foo >>> s = SomeClass() >>> unicode(s.foo) u'bar' """ def __init__(self, charset='UTF-8'): self.charset = charset self.finders = {} self.translators = {} def register(self, domain, finder): translator = self.translators.get(domain) if translator is not None: return translator self.finders[domain] = finder ret = domaintranslator(domain, self, finder) self.translators[domain] = ret return ret