This is a short recipe how to make a multi-language site
First of all, I load all my languages during the WebKit start-up into a variable, which is visible from any place, i.e. I create this variable in __builtin__ dictionary. So, in my contextInitialize() method is:
__builtin__.__dict__['LANGUAGES'] = Languages()
where Languages() is a dictionary-like object, where keys are language codes and items are gettext.translation() objects:
import gettext
import os
class Languages:
def __init__(self):
self._langs = {}
self.load()
def __getitem__(self, lang):
return self._langs[lang]
def load(self):
localeDir = 'yourLocaleDirectory'
dirItems = os.listdir(localeDir)
for dirItem in dirItems:
if os.path.isdir(os.path.join(localeDir, dirItem)):
try:
self._langs[dirItem] = gettext.translation('yourDomain', localeDir, [dirItem])
except Exception, e:
print 'Error: Loading language: "%s"' % dirItem
def gettextFunc(self, lang):
if self._langs.has_key(lang):
return self._langs[lang].gettext
else:
return lambda x: x
So. I have all the translations in memory and now I need to use them. I need to know, which language to choose. Because my site is also for registered users, there are two ways:
If the user is logged in, language code is read from his profile.
If the user is not logged in, I pass choosen language code via GET method from page to page, but if the user did not choose language, some default one is used.
In awake() method of a servlet I set right gettext function:
awake(self, transaction):
...
self._ = LANGUAGES.gettextFunc('languageCode')
...
Every string, that needs to be translated, I mark as usually _('stringToTranslate') and in the beginig of every method Where I need it, I just do:
someMethod(self):
_ = self._
That's it. :-)
One last thing. Strings that needs to be translated are not just in servlets, but on other places too (for example in some modules), so I need some _() function there. At the begining of such a place I have:
_ = lambda x: x
If you have lots of situations where you need to internationalize code that won't have access to your context (i.e., to self._), then you might want to try keeping track of language on a per-thread manner. (If you are using that last recipe, _ = lambda x: x, then you might want to try this instead.) An untested example of this:
def awake(self, trans):
self._ = LANGUAGES.gettextFunc('languageCode')
languagecontext.register(self._)
def sleep(self, trans):
languagecontext.deregister()
# then in languagecontext:
import threading
class LanguageContextTracker:
def __init__(self):
self._threads = {}
def curName(self):
return threading.currentThread().getName()
def register(self, obj):
self._threads[self.curName()] = obj
def deregister(self):
try:
del self._threads[self.curName()]
except KeyError:
pass
def language(self):
return self._threads[self.curName()]
TheLanguageContextTracker = LanguageContextTracker()
register = TheLanguageContextTracker.register
deregister = TheLanguageContextTracker.deregister
language = TheLanguageContextTracker.language
Then use languagecontext.language() to get your _ object, even after you've lost track of the servlet you are associated with.
(If someone implements this, please correct this code as necessary)
-- Ian Bicking
Based on very light testing the above approach seems to work.
Although I implemented it slightly differently. Instead of gettext -functions TheLanguageContextTracker keeps track of preferred translation languages (as strings like 'en', 'fi',...).
My LangUtil.py looks like this:
import threading
import gettext
class LanguageContextTracker:
def __init__(self):
self._threads = {}
def curName(self):
return threading.currentThread().getName()
def register(self, obj):
self._threads[self.curName()] = obj
def deregister(self):
try:
del self._threads[self.curName()]
except KeyError:
pass
def language(self):
return self._threads[self.curName()]
TheLanguageContextTracker = LanguageContextTracker()
register = TheLanguageContextTracker.register
deregister = TheLanguageContextTracker.deregister
language = TheLanguageContextTracker.language
_translations = {}
def initialize():
"Load translations and put them to a dictionary. ADD ERROR HANDLING HERE"
_translations['fi'] = gettext.translation("mydomain","C:/mywebkitproject/locale",["fi"])
_translations['en'] = gettext.translation("mydomain","C:/mywebkitproject/locale",["en"])
def mygettext(s):
"Look up the preferred translation and return the translated string"
try:
return _translations[language()].lgettext(s)
except:
return s
In a Page -class from which all other Page -classes in the site inherit I have the following:
def awake(self,trans):
if trans.request().hasField("lang"):
trans.session().setValue("lang",trans.request().field("lang"))
LangUtil.register(trans.session().value('lang','en'))
SiteTemplate.awake(self,trans) # I've got a Cheetah template at the top of my Page-class hierarcy
def sleep(self,trans):
SiteTemplate.sleep(self,trans)
LangUtil.deregister()
And into Launch.py I've added the following:
# Initialize translations and have _ point to LangUtil.mygettext import LangUtil LangUtil.initialize() __builtins__.__dict__['_'] = LangUtil.mygettext
-- jranki
One issue with the above implementation is that LangUtil can be loaded multiple times. Once in Launch.py and another time in your Page class. Having the effect that when _ is called, there is never anything in the LanguageContextTracker._threads variable.
A better solution is within Launch.py to store the module you loaded:
__builtins__.__dict__['__langutil__'] = LangUtil
and then in your Page class:
__langutil__.register(trans.session().value('lang','en'))
within the LangUtil you could use the threading.local() object which has been around since Python2.4 to store the thread specific language string. Instead of using the _threads dictionary.
class LangUtil:
def __init__(self):
self.data = threading.local()
def register(self, obj):
self.data.lang = obj
def language(self):
return self.data.lang
-- bcctech