Django Message Files
Introduction
When building applications for global audiences, supporting multiple languages is essential. Django's internationalization (i18n) framework provides robust tools for creating multilingual applications. At the heart of Django's translation system are message files - special files that contain all the translatable strings from your application along with their translations.
In this tutorial, we'll explore how Django message files work, how to create them, and how to use them effectively to internationalize your Django application.
Understanding Message Files
Message files are structured text files that store translated strings for your application. Django uses the GNU gettext format for these files, which involves several file types:
.po
files (Portable Object) - Human-readable files containing original strings and their translations.mo
files (Machine Object) - Compiled binary versions of.po
files for production use.pot
files (Portable Object Template) - Template files used as a starting point for translations
These files are organized by language code (such as fr
, es
, ja
) within your project structure.
Setting Up Internationalization
Before working with message files, make sure your Django project is configured for internationalization:
- Ensure these settings are properly configured in your
settings.py
:
INSTALLED_APPS = [
# ...other apps
'django.contrib.i18n',
]
MIDDLEWARE = [
# ...other middleware
'django.middleware.locale.LocaleMiddleware',
]
USE_I18N = True
USE_L10N = True
LANGUAGE_CODE = 'en-us' # Default language
LANGUAGES = [
('en', 'English'),
('es', 'Spanish'),
('fr', 'French'),
# Add more languages as needed
]
LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'),
]
- Create a
locale
directory at your project root:
mkdir -p locale
Marking Strings for Translation
Before generating message files, you need to mark which strings should be translated:
In Python Files
from django.utils.translation import gettext as _
from django.utils.translation import ngettext
from django.utils.translation import gettext_lazy as _
# Simple translation
welcome_message = _("Welcome to our website!")
# Translation with variables
def greet(name):
return _("Hello, %(name)s!") % {'name': name}
# Plural forms
def display_results(count):
return ngettext(
"%(count)d result found.",
"%(count)d results found.",
count
) % {'count': count}
In Template Files
{% load i18n %}
<!-- Simple translation -->
<h1>{% translate "Welcome to our website" %}</h1>
<!-- Translation with variables -->
<p>{% blocktranslate with username=user.username %}Hello, {{ username }}!{% endblocktranslate %}</p>
<!-- Plural forms -->
{% blocktranslate count counter=list|length %}
There is {{ counter }} item.
{% plural %}
There are {{ counter }} items.
{% endblocktranslate %}
Creating Message Files
After marking strings for translation, you need to extract them into message files:
Step 1: Extract Messages
Run the makemessages
command to extract all marked strings into .po
files:
python manage.py makemessages -l es # Create/update Spanish translations
python manage.py makemessages -l fr # Create/update French translations
This command scans your code for translation strings and creates/updates .po
files in your locale
directory.
Step 2: Examine the Generated .po
Files
Let's look at an example .po
file (locale/es/LC_MESSAGES/django.po
):
#: myapp/views.py:23
msgid "Welcome to our website!"
msgstr "¡Bienvenido a nuestro sitio web!"
#: myapp/views.py:27
#, python-format
msgid "Hello, %(name)s!"
msgstr "¡Hola, %(name)s!"
#: myapp/templates/results.html:12
#, python-format
msgid "%(count)d result found."
msgid_plural "%(count)d results found."
msgstr[0] "%(count)d resultado encontrado."
msgstr[1] "%(count)d resultados encontrados."
Each entry in the .po
file contains:
- A reference to where the string appears in your code
- The original string (
msgid
) - The translation (
msgstr
) - For plural forms, separate
msgstr[n]
entries for each plural form
Step 3: Translate Strings
Edit the .po
files to provide translations for each string. You can do this manually with a text editor, or use specialized PO file editors like Poedit.
Step 4: Compile Message Files
After translating, compile the .po
files into binary .mo
files:
python manage.py compilemessages
Django uses these compiled .mo
files in production for better performance.
Message Files Structure
The message files are organized in a specific directory structure:
locale/
en/
LC_MESSAGES/
django.po
django.mo
es/
LC_MESSAGES/
django.po
django.mo
fr/
LC_MESSAGES/
django.po
django.mo
Advanced Message File Features
Contextual Markers
Sometimes the same string might need different translations depending on context:
from django.utils.translation import pgettext
# Different meanings of "May"
month = pgettext("month name", "May")
verb = pgettext("verb", "May")
In the .po
file:
msgctxt "month name"
msgid "May"
msgstr "Mayo" # Spanish
msgctxt "verb"
msgid "May"
msgstr "Puede" # Spanish
Lazy Translation
For strings that should only be translated when they're used (common in models and forms):
from django.utils.translation import gettext_lazy as _
class Product(models.Model):
name = models.CharField(_("product name"), max_length=100)
description = models.TextField(_("product description"))
Domain-specific Message Files
You can create separate message files for different parts of your application:
python manage.py makemessages -l es -d djangojs # For JavaScript translations
This creates djangojs.po
files for JavaScript translations.
Real-world Example: A Multilingual Blog
Let's see how message files work in a real-world example of a blog application:
Models
from django.db import models
from django.utils.translation import gettext_lazy as _
class BlogPost(models.Model):
title = models.CharField(_("title"), max_length=200)
content = models.TextField(_("content"))
publication_date = models.DateTimeField(_("publication date"), auto_now_add=True)
class Meta:
verbose_name = _("blog post")
verbose_name_plural = _("blog posts")
Views
from django.shortcuts import render
from django.utils.translation import gettext as _
from .models import BlogPost
def blog_index(request):
posts = BlogPost.objects.all()
count = posts.count()
message = ngettext(
"There is %(count)d post available.",
"There are %(count)d posts available.",
count
) % {'count': count}
return render(request, 'blog/index.html', {
'posts': posts,
'message': message,
})
Template
{% load i18n %}
{% block content %}
<h1>{% translate "Blog" %}</h1>
<p>{{ message }}</p>
{% for post in posts %}
<article>
<h2>{{ post.title }}</h2>
<div class="meta">
{% blocktranslate with date=post.publication_date|date:"SHORT_DATE_FORMAT" %}
Published on {{ date }}
{% endblocktranslate %}
</div>
<div class="content">
{{ post.content }}
</div>
<a href="{% url 'blog_detail' post.id %}">
{% translate "Read more" %}
</a>
</article>
{% empty %}
<p>{% translate "No posts available." %}</p>
{% endfor %}
{% endblock %}
Language Switcher
To allow users to change languages:
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.path }}">
<select name="language" onchange="this.form.submit()">
{% for lang_code, lang_name in LANGUAGES %}
<option value="{{ lang_code }}" {% if lang_code == LANGUAGE_CODE %}selected{% endif %}>
{{ lang_name }}
</option>
{% endfor %}
</select>
</form>
Working with Message Files in Development
Updating Translation Files
When you add new translatable strings to your code:
- Run
makemessages
again to update.po
files:
python manage.py makemessages -l es -l fr
- Translate the new strings in the
.po
files - Compile the updated translations:
python manage.py compilemessages
Tips for Efficient Translation Management
- Use comments for translators: Add translator comments in your code:
# Translators: This is a greeting shown on the homepage
welcome_message = _("Welcome to our website!")
-
Keep strings complete: Don't split sentences into multiple translation strings.
-
Use format strings for variables: Never concatenate strings for translation.
# Bad
_("Hello") + " " + user.name
# Good
_("Hello %(name)s") % {'name': user.name}
- Consider translation context: Provide context with
pgettext()
for ambiguous terms.
Summary
Django message files are the foundation of internationalization in Django applications. They store translations of your application's text in a structured format that Django can use to display content in different languages.
The workflow involves:
- Marking translatable strings in your code
- Extracting these strings with
makemessages
- Translating the strings in
.po
files - Compiling the translations with
compilemessages
- Using the translations in your application
By properly managing your message files, you can create truly multilingual Django applications that provide a localized experience for users around the world.
Additional Resources
- Django's Translation Documentation
- GNU gettext utilities
- Poedit - A cross-platform editor for message files
- Weblate - A web-based translation management system
Exercises
- Set up internationalization in a new Django project and create message files for two languages.
- Create a simple form with translatable labels and error messages.
- Implement a language switcher that allows users to change the display language.
- Add translations for dynamic content such as blog posts or product descriptions.
- Create a custom template tag that handles pluralization for a specific use case in your application.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)