Testing translation without translation files.
As part of my django i18n-ing efforts, I wanted to test that a part of our code was really translating strings. However our app wasn't translated yet, so I didn't have any .po
/.mo
files around, so django was just reverting to the English strings. This is what Django is supposed to do. However I was unable to know for sure that my code was really translating the strings. I wanted some way to see the strings change.
Use the source
One thing I love about python & django is that you can dive into the source and look at what's going on, from looking at the source, I found out that there was a module level cache of the translation objects (django.utils.translations.trans_real:18). Once you manually activate the language, then that object is stored in django, and it will be reused if that language is needed again. However it only stores one instance per thread.
DjangoTranslation objects have a dictionary _catalog
, the keys are the msgids, the values are the translated strings. You could manually change the translation strings to make sure that they are being used.
You could change individual strings (by changing the french_translation._catalog[msgid]
strings), however I wanted to change all the strings. You can do this with a decorator (sorta).
Sample code
For this example I'm testing translating things into French, fr
, if you want to change what language you want to test to, then change the "fr"
below.
# A decorator function that just adds 'XXX ' to the front of all strings
def wrap_with_xxx(func):
def new_func(*args, **kwargs):
output = func(*args, **kwargs)
return "XXX "+output
return new_func
old_lang = translation.get_language()
# Activate french, so that if the fr files haven't been loaded, they will be loaded now.
translation.activate("fr")
# Just a sanity checker, i.e. this code doesn't work with multi-threaded applications.
assert len(translation.trans_real._active) == 1, "Weird (multi-threaded?) things are happening here"
# translations.trans_real._active is dict. One key for each thread. Each thread
# only has one active DjangoTranslation object that does the translation for
# code in this thread
french_translation = translation.trans_real._active.values()[0]
# wrap the ugettext and ungettext functions so that 'XXX ' will prefix each translation
french_translation.ugettext = wrap_with_xxx(french_translation.ugettext)
french_translation.ungettext = wrap_with_xxx(french_translation.ungettext)
# Turn back on our old translations
translation.activate(old_lang)
del old_lang
Now call your function that uses translated text, and if you have set the language to french, there will be 'XXX' before every string if it used the translation. If it didn't use the translation there will be no XXX.
The approach is quite hacky, and might change if Django changes how their internal code works. This has been tested on Django 1.1. It also doesn't work with multi-threaded applications.
## Mentions * I asked this on StackOverflow - Testing django internationalization - Mocking gettext * It's included in OpenConsent test framework