Localization (L10n)

Playdoh comes with all the libraries and tools you will need in production or development for localization.

Localization works by searching for localizable text in your source code and extracting (or merging them) into .po files in locale/.


All new locales must be whitelisted before they show up in production. See Creating New Locales for details.


The one set of low level tools not provided is gettext.

# for ubuntu/debian
aptitude install gettext

# for mac
brew install gettext

Testing It Out, Right Now

Playdoh comes with a sample app called examples which is also localized into French. This is so that you can get the hang of how things work out of the box. To get started, simply follow these quick steps:

  1. ./bin/compile-mo.sh locale/
  2. Set DEV=True in settings/local.py
  3. ./manage.py runserver
  4. Point your browser to


If you don’t have any localization you need to do the following:

  1. ./manage.py extract -c
  2. ./manage.py merge -c

This will populate your locale/ directory.


Once your directories are set up you’ll need to tend to your strings and their localization.


You can localize strings both in python code as well as Jinja templates.

In python:

from tower import ugettext as _, ugettext_lazy as _lazy

yodawg = _lazy('The Internet')

def myview(request):
    return render('template.html', {msg=_('Hello World'), msg2=yodawg})

_lazy is used when we are not in scope of a request. This lets us evaluate a string, such as yodawg, at the last possible second when we finally can draw upon the request’s context. E.g. in a template:

{{ msg2 }}

Will be evaluated to whatever The Internet is in the requester’s locale.

In Jinja we can use the following syntax for localized strings:

<h1>{{ _('Hello') }}</h1>

{% trans link='http://mozilla.org' %}
<p>Go to this <a href="{{ link }}">site</a>.</p>
{% endtrans %}

Updating our strings

If you make changes to your strings you can update them:

./manage.py extract
./manage.py merge

Note: you do not need -c for either command if you’ve created the required directories.

If you want to see these changes in your project:

./bin/compile-mo.sh locale/

Creating New Locales

In your project’s settings.py add the new locale to the PROD_LANGUAGES tuple. During development (when settings.DEV is True) all locales will show up if their po files exist but in production, locales will only show up if they exist in PROD_LANGUAGES and their po files exist.

For example, make Polish and Brazilian Portuguese show up in production when each locale is fully ready to go live like this:


Then run ./manage.py merge -c which will create directories for any locales that are missing in /locale.

If you want your visible locales in development to match that of production, you can set dev to use the same whitelist, like this in settings:


Good Practices

Let’s say you have some template:


<p>Is it <a href="http://about.me/lionel.richie">me</a> you're looking for?</p>

Let’s say you are told to translate this. You could do the following:

{% trans %}

<p>Is it <a href="http://about.me/yo">me</a> you're looking for?</p>
{% endtrans %}

This has a few problems, however:

  • It forces every localizer to mimic your HTML, potentially breaking it.
  • If you decide to change the HTML, you need to either update your .po files or buy all your localizers a nice gift because of all the pain you’re inflicting upon them.
  • If the URL changes, your localizer has to update everything.

Here’s an alternative:


  {% trans about_url='http://about.me/lionel.richie' %}
    Is it <a href="{{ about_url }}">me</a> you're looking for?
  {% endtrans %}

or if you have multiple paragraphs:


{% trans about_url='http://about.me/lionel.richie' %}
    Is it <a href="{{ about_url }}">me</a> you're looking for?
    I can see it in your eyes.
{% endtrans %}

Here are the advantages:

  • Localizers have to do minimal HTML.
  • The links and even structure of the document can change, but the localizations can stay put.

Be mindful of work that localizers will have to do.