Translating my Django landing page
What I've learned trying to translate Pluckd for Brazilian audiences
“This is looking good, but you’ll never manage to sell that in Brazil”, said a good friend of mine. I had just invited the first beta users to try what, so far, had only been a night project: Pluckd.
Pluckd is my attempt to do with people what Google does with webpages: find the right one. Google determines what site you are looking for based on how many times it was referenced by other sites; Pluckd wants find the right person for a job position based on how they are referenced by their peers. Pluckd was written using a Python web framework called Django I was keen on learning.
The problem with trying to enter the Brazilian market is that not many Brazilians are comfortable with a product written in English.
So, with that in mind, and trying to amaze my friend (I mean, if I can’t fulfill my beta users’ expectations, what good is my product for?) I’ve set on my journey towards Django Internationalization, or i18n for short.
1. Preparing the site for translation
I18n starts by defining what sentences you want to translate. This is achieved differently depending on the file type. I’ll try to summarize here how I’ve done it, but the Django Docs are much more thorough in that regard:
Templates
Begin by adding the following tag at the top of every single html file that will contain a translation. Yes, this means the include
files too.
{% load i18n %}
After that, you use the trans
template tag for short sentences, and the blocktrans
for large ones:
{% trans ‘Short sentence I want to translate‘ %}
{% blocktrans %}Large sentences that may even include {{ variables }}{% endblocktrans %}
Luckily, the relevant JavaScript scripts were already part of the templates, so I was able to translate them in the same fashion. For actual JavaScripts files, more work is required.
Views, Forms and Models
For the other files, there are 2 functions to chose: gettext()
and gettext_lazy()
.
The difference is that the _lazy function store a lazy reference to the string – not the actual translation. The translation itself will be done when the string is used. It’s common practice to alias these functions as “_”:
from django.utils.translation import gettext_lazy as _
or
from django.utils.translation import gettext as _
Then, you mark what you want to translate like so:
label=_('First name')
Covering all the code I wrote, looking for what to translate, was a LOT of work. It was easy to miss a sentence here and there. So my learning was:
Prepare your Django projects for translation by default. Even if you don’t plan to translate it right now, you never know when you will.
2. Creating the translation files
Now this took me sometime. You have to create a folder called locale
and then, on settings.py
tell Django where this folder is located, as well as turning internationalization (i18n) and localization (l10n) on:
LANGUAGES = (
('en', _('English')),
('pt-br', _('Portuguese')),
)
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
USE_I18N = True
USE_L10N = True
USE_TZ = True
After that, you are supposed to run the following command to create the translation files:
django-admin makemessages -l pt-br
That’s when my problems truly began. There are two things to unpack here and finding those configuration subtleties was not trivial.
For Pluckd, instead of keeping the Django apps in the root project folder, I kept them all in a folder called ‘pluckd’. The idea was to keep the root folder for pipeline-related stuff, like the
gitignore
, thepackage.json
andPipfile
files.But that also meant that the command
django-admin
did not work. After a few attempts, going back to usingpython manage.py makemessages -l pt_br
did the trick.Even though I wrote pt-br in lowercase at the settings file, I shouldn’t have used that on the
makemessages
command. Instead, I should have used pt_BR, with the locale (i.e. ‘BR’) in caps. The Django docs have a passing mention on the fact you should use underscore, but nothing about having the locale in uppercase. Meanwhile, thesettings.LANGUAGE
should be written with hyphens and lowercase.
This will create a django.po
file. Those are very weird-looking and nothing like the elegance I was used to when trying my hand at translation with another web frame, Ruby on Rails. The django.po
file look like this:
#: .\templates\pages\pricing.html:14
msgid "Pluckd - Start using for free"
msgstr "Pluckd - Comece sem pagar"
The first line is where the translation was found. The django.po
contained the translation for all the Django apps. Later, I was able to find how to separate my translations per app: just create their own locale folder. The problem: as my templates were inside a root folder called templates, the sentences I had marked there were not found by their app-specific locale folder.
The second line is that line you marked for translation.
DO NOT CHANGE IT the
msgid
line, even if it contains weird spaces and line breaks taken from template. Otherwise, Django won’t find what to translate.
Finally, the msgstr
line is where you’ll write your translation. I was lucky I didn’t have to handle pluralization at this time.
After all’s done, use the command python manage.py complilemessages
to compile them into a binary file. This is what will actually be used when the site is up and running.
For this to work in my PC, I had to install gettext for Windows.
3. Testing it locally
For testing it locally, first I forced the translation on settings.py:
LANGUAGE_CODE = 'pt-br'
It was the first thing that worked without a hitch and it felt great!
Then, in order for the translation to be activated depending on the browser’s settings from the user, I simply had to add a middleware. Finally, something easy!
MIDDLEWARE = [
'django.middleware.locale.LocaleMiddleware',
'other middlewares',
.... ,
]
I changed the LANGUAGE_CODE
settings back to ‘en
’ so that the default version of the site would be English. It was now time to commit the changes and upload them to Heroku.
4. Testing in the Staging Environment
Yeah... it did not work on Heroku. At all.
This is another reason you should always have a Staging Environment, by the way.
My binary files were on .gitignore
This, I soon learned, was a good practice. Binary files can change all the time and become a real hassle when trying to merge with commits by other people. It was best to compile them on Heroku itself.
One after the other, here are the problems I’ve faced:
Heroku run failed
Well, remember I had to install gettext for Windows? I had to do something similar on Heroku too! Luckily there was a third-party buildpack for that. It hasn’t been updated in a good while, which worried me a little, but in the end, it worked very well. To install it, I had to run this command.
heroku buildpacks:set https://github.com/grauwoelfchen/heroku-buildpack-gettext.git
I forced an empty deploy so I could activate that buildpack and then I was finally able to run heroku run python manage.py compilemessages
.
I could see all files from the Django package being compiled - and that worked: the Django Admin was being correctly translated. But Pluckd itself was still in English.
Heroku was unable to find the translated .mo file
This is where having the locale not in caps caused problems. In my computer, the file system was not case-sensitive, which explains why everything ran perfectly. But Heroku runs on Linux, which is case-sensitive, therefore I could not find the .mo
binary file.
Heroku was STILL unable to find the translated .mo file
Turns out, the django.mo
file was created but for some reason it was immediately deleted. I knew Heroku’s uploaded media files were ephemeral - and would be deleted once the dyno was shut down. But the fact that any changes to the filesystem would be deleted as soon as the Heroku run command was done was caught me by surprise.
Reality trumps best practices
In the end, I had to remove the .mo
files from .gitignore
so they would be committed as well. The other option was to serve them from a storage like Amazon S3 which sounded like more failure points.
So, now we have the pluckd.co beta landing page finally translated!
If you speak Portuguese give it a look and tell me how I did with my translation!
when i run this :
heroku buildpacks:set https://github.com/grauwoelfchen/heroku-buildpack-gettext.git
and push it push the same buildpack not my django project