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
gettext
method, 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
.po
files created by running themakemessages
command and the.mo
files created by running thecompilemessages
command) 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 --all
This 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
.po
filescompile the
.po
files 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
pipenv
on my projects, but you can use whatever you like):pip install django-statici18n
Add
'statici18n'
to yourINSTALLED_APPS
setting:INSTALLED_APPS = [ ... statici18n', ]
Make sure
django.template.context_processors.i18n
is part of yourcontext_processors
section of theTEMPLATES
settingNow 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.po
file, it’s time to compile it into staticfiles with thecompilejsi18n
command.python manage.py compilejsi18n
But a word of caution! Because
django-statici18n
will 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 djangojs
Run:
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:
<
script
type="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. 🤞🤞