Localizing JavaScript and JSON in Django Apps
While diving deeper into Django Internationalization (i18n) features, I found my biggest foe yet: JavaScript!
Pluckd is the Google for company employees: it finds the right person inside your company based on how they are referenced by their peers. It was written in Django, a Python web framework.
Translating Pluckd's Landing Page into Portuguese quickly paid off - literally! We had our first paid customer …and it was a Brazilian company!
This was a clear sign we had to translate the rest of the app. It as time to face my fears and fight the battle I was hoping to avoid: translating JavaScript.
Unlike templates, forms, views and models, translating JavaScript is not straightforward. There are 2 reasons for that:
The
gettextmethod, used to mark what’s to be translated in your python files, is not available to your JavaScript files.Also not available is the translated catalog (i.e. the
.pofiles created by running themakemessagescommand and the.mofiles created by running thecompilemessagescommand) with all the translations.
Django’s solution is the JavaScriptCatalog. It creates a global function gettext that can be used on your JavaScript files.
How to use it
Start by adding the following path to your urls.py.
from django.views.i18n import JavaScriptCatalog
urlpatterns = [
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
]This is the place where the JavaScript file with the global gettext function will be generated. As any JavaScript file, you must then call it with a script tag. Put it before any other script that will need translations or else they won’t have access the the gettext function!
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>Now you can mark your JavaScript files for translation!
For example, this is how to translate a string:
gettext('you really should register for Pluckd beta!')For interpolation, you must use the following (remember that the values must be in an Array)
const my_string = gettext('This post has %s retweets and %s likes!');
const interpolated_string = interpolate(my_string, [10000, 9999]);And finally, for plurals, use the ngettext method just like you would in a Python file, where the third argument is the object count:
const n_strings = ngettext(
'This post has one retweet and one like!',
'This post has %s retweets and %s likes!',
1
);
const interpolated_string = interpolate(n_strings, [100, 141]);After everything’s correctly marked for translation, create the .po file with:
python manage.py makemessages -d djangojs --allThis will create a djangojs.po file right next to your django.po file. It’s kind of a bummer that the translations for JavaScript sit separately from the other translations. I was unable to find out how to have a single .po file.
If you know, please do reach out! 😉
Now it’s business as usual:
translate the
.pofilescompile the
.pofiles withpython manage.py compilemessages
The fine print in JavaScript translation
There is a big caveat on how Django translates JavaScript files: the catalog will be generated from the .mo files with every request. This adds an overhead to your server.
The Django docs recommends 3 options:
server-side caching
client-side caching
pre-generating the JavaScript catalog as part of the static files
The docs say the 3rd option is a ‘radical’ one. Allow me to disagree: it’s not only the option that makes the most sense, but also the easiest to implement. Caching, on the other hand, is a complicated topic. It involves new installs (which sometimes translates into new costs), a plethora of options to study and then decide on and, more importantly, this learning curve also costs time.
Static files on the other hand is a mandatory learning step in Django. If you are at the point your site needs translations, chances are you’ve covered the static files configuration already.
In order to pre-generate the JavaScript files as static files, I used the package django-statici18n.
Using django-statici18n
Although I distill the installation process in 6 steps, my experience was nothing like that! There were tears, pains and false-starts I had to endure so you don’t have to. 😅
Install it (I use
pipenvon my projects, but you can use whatever you like):pip install django-statici18nAdd
'statici18n'to yourINSTALLED_APPSsetting:INSTALLED_APPS = [ ... statici18n', ]Make sure
django.template.context_processors.i18nis part of yourcontext_processorssection of theTEMPLATESsettingNow you have to tell django-statici18n where you keep your static files. For instance, if you keep your static assets in a folder named
static, add the following to yoursettings.py:STATICI18N_ROOT = os.path.join(BASE_DIR, 'static')Next, after translating the
djangojs.pofile, it’s time to compile it into staticfiles with thecompilejsi18ncommand.python manage.py compilejsi18nBut a word of caution! Because
django-statici18nwill add the translation files in the static folders, you should tell Django to ignore that location when generating new JavaScript translations, otherwise the command will raise an error.So, instead of running:
python manage.py makemessages -d djangojsRun:
python manage.py makemessages -d djangojs --ignore=static/jsi18n/*Remember to update the ignore parameter if your static assets are saved somewhere else.
Last step: update the script tag in your templates. Replace this:
<scripttype="text/javascript" src="{%url'javascript-catalog' %}"></script>For:
{% load statici18n %} <script src="{% statici18n LANGUAGE_CODE %}"></script>
And we’re done!
Now Pluckd is fully translated! Hopefully, this means we’ll be able to adquire more customers through word-of-month from that first Brazilian customer. 🤞🤞

