Skip to content

Internationalisation (i18n)

Supported Languages

Code Language Status
en English Default, active
it Italiano Active
es Español Planned
de Deutsch Planned

All translations are done manually by Alex.


How Django i18n Works End-to-End

1. Mark strings in code/templates
2. uv run python manage.py makemessages -l it
   → scans all templates and Python files
   → creates/updates locale/it/LC_MESSAGES/django.po
3. Edit django.po — fill in msgstr translations manually
4. uv run python manage.py compilemessages
   → compiles django.po → django.mo (binary, read at runtime)
5. LocaleMiddleware detects language from:
   URL prefix → session → Accept-Language header → LANGUAGE_CODE default

Settings

# config/settings/base.py
LANGUAGE_CODE = 'en-us'
USE_I18N = True

LANGUAGES = [
    ('en', 'English'),
    ('it', 'Italiano'),
    # ('es', 'Español'),    # uncomment when translations are ready
    # ('de', 'Deutsch'),
]

LOCALE_PATHS = [BASE_DIR / 'locale']

Middleware (order matters — see Settings):

"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",   # after Session, before Common
"django.middleware.common.CommonMiddleware",


Template Usage

{% load i18n %}

<!-- Short string -->
<h1>{% trans "About Me" %}</h1>

<!-- String with a variable -->
{% blocktrans with name=user.name %}
  Hello, {{ name }}. Welcome to my site.
{% endblocktrans %}

Language Switcher Component

<!-- templates/components/_lang_switcher.html -->
{% load i18n %}
<form action="{% url 'set_language' %}" method="post">
  {% csrf_token %}
  <input name="next" type="hidden" value="{{ request.path }}">
  <select name="language" onchange="this.form.submit()">
    {% get_available_languages as languages %}
    {% for code, name in languages %}
      <option value="{{ code }}"
              {% if code == LANGUAGE_CODE %}selected{% endif %}>
        {{ name }}
      </option>
    {% endfor %}
  </select>
</form>

The set_language URL is provided by path('i18n/', include('django.conf.urls.i18n')) in config/urls.py.


Workflow Commands

# Extract all translatable strings → .po files (run after adding new {% trans %} tags)
uv run python manage.py makemessages -l it
uv run python manage.py makemessages -l es

# Compile .po → .mo after editing translations
uv run python manage.py compilemessages

# Both languages at once
uv run python manage.py makemessages --all

Note

The compiled .mo files should be committed to the repository so production deployments don't need to run compilemessages. The .po source files are the ones you edit; .mo files are derived artefacts.