Previously, we explored the basics of how the Django framework supports internationalization. Now, it’s time to take a deeper dive into advanced Django internationalization. In this guide, we’ll cover practical techniques such as pluralization, context-aware translations, dynamic language switching, localizing dates and times, and handling RTL languages like Hebrew.
By mastering these features, you can build highly adaptable multilingual applications that deliver seamless experiences to users across different cultures and regions, including accurate handling of localized time.
Before diving into advanced Django internationalization, ensure you have the following set up:
Python and Django installed: You’ll need Python 3.8 or higher and Django installed on your system.
gettext installed: Django relies on gettext for managing translation files.
A code editor: Use any editor you’re comfortable with, such as VS Code or PyCharm.
A basic app prepared for i18n: You should already have an app configured for basic internationalization, including translatable strings and language settings.
First of all, let's check the base.html template we'll be using for this tutorial. In my sample app it's stored under blogs/templates/base.html:
{% load i18n %}<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My Django App</title></head><body> <header> <nav> <h1>My Django App</h1> {% block language_switcher %} <form method="post" action="{% url 'set_language' %}"> {% csrf_token %} <label for="language-select">{% trans "Choose your language" %}:</label> {% get_current_language as current_language %} {% load language_tags %} <select id="language-select" name="language"> {% get_current_language as current_language %} {% get_languages as languages %} {% for code, name in languages %} <option value="{{ code }}" {% if current_language == code %}selected{% endif %}>{{ name }}</option> {% endfor %} </select> <button type="submit">{% trans "Switch" %}</button> </form> {% endblock %} </nav> </header> <main> {% block content %} {% endblock %} </main></body></html>
As you can see, it displays a language switcher and all in all it looks nice. However, there's one issue I don't really like: the <html lang="en"> attribute is currently hardcoded. Ideally, it should reflect the active language of the application. To make it dynamic, we can use Django’s {% get_current_language %} template tag.
This ensures that the lang attribute dynamically updates to the current language code, such as en or lv, depending on the active locale.
By making this change, your app’s HTML structure will accurately reflect the current locale, improving accessibility and SEO.
Handling pluralization in Django
Next, let's see how to introduce pluralization in our app. Django makes this easy with the {% blocktrans %} template tag, which allows you to handle pluralization dynamically in templates.
Let's open blogs/templates/blogs/home.html template that currently has the following content:
from django.shortcuts import renderfrom django.utils.translation import gettext as _def home(request): return render(request, 'blogs/home.html') # Render the specific home template
Suppose we would like to pluralize the phrase "You have X messages".
Step 1: Add a message count to the view
First, pass the number of messages to the template from your view. Update your home view like this:
Here, message_count represents the number of messages, which will be used for pluralization in the template.
Step 2: Use {% blocktrans %} in the template
Update your home.html template to display a pluralized message based on the message_count:
{% block content %}<h2>{% trans "Hello, world!" %}</h2><p> {% blocktrans count message_count as count %}You have {{ count }} message.{% plural %}You have {{ count }} messages.{% endblocktrans %}</p>{% endblock %}
{% blocktrans %}: Marks the block of text as translatable and handles pluralization.
count message_count as count:
{% plural %}: Separates the singular and plural forms of the message.
{{ count }}: Dynamically inserts the number into the message.
Django uses this structure to decide which version of the message to display based on the value of message_count.
Step 3: Provide pluralized translations
Use the makemessages command to extract the translatable strings into .po files for both English and Latvian:
python manage.py makemessages -l en -l lv
Now, open blogs/locale/en/LC_MESSAGES/django.po message file. You’ll see the following entry for the pluralized translations:
#: .\blogs\templates\blogs\home.html:10#, python-formatmsgid "You have %(count)s message."msgid_plural "You have %(count)s messages."msgstr[0] ""msgstr[1] ""
English has only two plural forms: singular and plural. Since the source strings are already in English, there’s no need to change the translations here. The header of the .po file includes the pluralization rule for English:
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
This defines that English has two forms:
Plural (msgstr[1]) for all other cases (count != 1).
Singular (msgstr[0]) for when count == 1.
Next, open blogs/locale/lv/LC_MESSAGES/django.po. The header will include this line:
Latvian has three plural forms, so the translations for msgstr will differ:
#: .\blogs\templates\blogs\home.html:10#, python-formatmsgid "You have %(count)s message."msgid_plural "You have %(count)s messages."msgstr[0] ""msgstr[1] ""msgstr[2] ""
So, under reach msgstr you'll need to provide an individual form, like this:
msgstr[0] "Jums ir %(count)s ziņa." # Singular (1)
msgstr[1] "Jums ir %(count)s ziņas." # Few (2, 3, 4)
msgstr[2] "Jums ir %(count)s ziņu." # Many or zero (0, 5+)
Ensure each msgstr corresponds to the proper plural form based on the pluralization rules for Latvian. Without these, translations might break for specific numeric values.
After adding translations, compile the .po files into .mo files:
python manage.py compilemessages
Restart the server and test to confirm everything is working as expected.
Step 4: Handle pluralization in Python files
For pluralization in Python code, Django provides the ngettext function. This allows you to programmatically handle singular and plural forms of strings in your logic using the gettext Python capabilities.
Here’s how to use ngettext to handle pluralized translations:
from django.shortcuts import renderfrom django.utils.translation import ngettext, gettext as _def home(request): message_count = 1 message = ngettext( "You have %(count)s message.", # Singular form "You have %(count)s messages.", # Plural form message_count ) % {'count': message_count} print(message) return render(request, 'blogs/home.html', {"message_count": message_count})
ngettext(singular, plural, count):
Takes the singular and plural forms of the string, and the numeric value (count) to decide which form to use.
Uses the same pluralization rules defined in the .po files.
%(count)s substitution:
Dynamically inserts the value of count into the translated string.
Now you can start your development server and see this feature in action!
Localizing date and time in Django
Localizing dates and times ensures that they are formatted correctly based on the user’s locale. Django makes it easy to display dates and times in a way that respects the user's language and regional preferences. Here’s how to do it.
Step 1: Enable localization settings
First, ensure that localization is enabled in your settings.py file:
Please note that in this case we use now() to return a time-zone aware object. We cannot apply localtime() to a so-called "naive datetime format" that does not provide timezone information.
Step 3: Format dates and times in templates
In templates, you can use the date and time filters to format dates and times dynamically. For example:
<p>{% trans "The current date and time is:" %} {{ current_time|date:"DATETIME_FORMAT" }}</p>
Make sure to provide the current_time in the view:
You can override the default formats for specific locales by creating a formats.py file in your app. For example, create a format file in your app directory:
Django will use these formats when the active locale matches lv. For example, you can do this:
<p>{% trans "The current date and time is:" %} {{ current_time|date:"TIME_FORMAT" }}</p>
Step 5: Handle time zones
When working with time zones, Django uses the USE_TZ setting to store all times in UTC by default and converts them to the user’s local time for display.
To activate a specific time zone for a user dynamically, use the activate function from django.utils.timezone:
# ...from django.utils.timezone import activatedef home(request): # ... other code ... # Choose and activate the timezone: user_timezone = 'Europe/Riga' activate(user_timezone) return render(request, 'blogs/home.html', {"message_count": message_count, "current_time": now()})
Now the localized time will be displayed according to the currently activated timezone.
Adding i18n_patterns for language-prefixed URLs
Using i18n_patterns, we can add target language prefixes to URLs, such as /en/ for English or /lv/ for Latvian. This approach enhances user experience by clearly indicating the current language in the URL.
Step 1: Modify urlpatterns with i18n_patterns
Wrap your existing URL patterns in i18n_patterns to enable language prefixes inside the urls.py file:
from django.contrib import adminfrom django.urls import path, includefrom django.conf.urls.i18n import i18n_patternsfrom blogs.views import homeurlpatterns = [ path('i18n/', include('django.conf.urls.i18n')), # Language switcher]urlpatterns += i18n_patterns( path('admin/', admin.site.urls), path('', home, name='home'), # Home view with language prefix)
The i18n_patterns function automatically adds the active language code (e.g., en, lv) as a prefix to all URLs within it.
For example:
/en/ for English.
/lv/ for Latvian.
Django dynamically resolves the language from the URL and activates it for the current request.
Step 2: Update templates with language-prefixed URLs
When using language-prefixed URLs, Django provides the {% url %} tag to generate correct links automatically. Update your templates to use language-aware URLs:
<a href="{% url 'home' %}">{% trans "Home" %}</a>
Django will automatically prepend the language code to the URL based on the user's preferred language.
Context-aware translations in Django
Some words or phrases can have different meanings depending on the context. For example, the word "File" could be a noun ("File this document") or a verb ("File a report"). Django provides tools like pgettext and npgettext to handle such context-aware translations effectively.
Step 1: Use pgettext in Python code
The pgettext function allows you to provide a context for your translation strings. Here’s how to use it:
from django.utils.translation import pgettextdef home(request): # ... file_noun = pgettext("noun", "File") print(file_noun) # Context for "File" as a verb file_verb = pgettext("verb", "File") print(file_verb)
In this example:
"noun" and "verb" are contexts to differentiate the meaning of "File".
These contexts will appear in the .po file during extraction.
Step 2: Use pgettext in templates
For context-aware translations in templates, use the {% trans %} tag with the context parameter:
<p>{% trans "File" context "noun" %}</p><p>{% trans "File" context "verb" %}</p>
This works just like pgettext in Python but is used directly in the template.
Step 3: Extract context-aware strings
When you run makemessages, context-aware translations will be included in the .po file with a msgctxt field:
Step 4: Use npgettext for context-aware pluralization
For pluralizable strings with context, use npgettext. Here’s an example:
from django.utils.translation import npgettextdef plural_context_example(request): count = 3 # Example count message = npgettext( "email context", "You have %(count)d email.", "You have %(count)d emails.", count ) % {'count': count} print(message)
Use context-aware translations when:
A word or phrase has multiple meanings depending on usage.
You want to avoid confusion for translators by clearly defining the intended meaning.
Supporting RTL languages in Django
Right-to-left (RTL) languages like Hebrew and Arabic require special handling to ensure that your application layout and text direction are correctly displayed. So, let's expand our advanced Django internationalization app with support for RTL!
Step 1: Enable RTL support
Open the settings.py file and add a new locale name to the list. Also make sure to set LANGUAGE_BIDI to True:
Managing PO files in Django can get overwhelming, especially as your app grows. Enter Lokalise—a powerful translation management system that simplifies the entire internationalization process.
With Lokalise, you get:
Easy integration with other services and platforms.
Collaborative tools for translators and developers.
Quality assurance to maintain consistent translations.
A centralized dashboard for managing all your locales.
It’s designed to save you time and help you scale your Django app to reach users worldwide.
Log in and create a project with any name. Use "Web and mobile" project type.
Upload your .po files, and edit translations via the dashboard.
Download your translations back to the Django project.
Use Lokalise’s API, CLI, or Python SDK for seamless integration with your Django app.
Conclusion
In this guide, we explored advanced Django internationalization techniques to help you create highly adaptable and multilingual applications. From handling pluralization and context-aware translations to implementing language-prefixed URLs with i18n_patterns, we’ve covered practical methods to refine your app’s localization.
We also delved into localizing dates, times, and RTL languages, ensuring your app accommodates diverse cultural and linguistic preferences. With tools like Lokalise for translation management and Django’s built-in utilities, you now have the knowledge to handle complex internationalization scenarios effortlessly.
Mastering these advanced Django internationalization features will not only make your app more user-friendly but also enhance its appeal to global audiences. Keep experimenting, and let Django handle the heavy lifting as you focus on building great experiences for your users worldwide.
Ilya is a lead of content/documentation/onboarding at Lokalise, an IT tutor and author, web developer, and ex-Microsoft/Cisco specialist. His primary programming languages are Ruby, JavaScript, Python, and Elixir. He enjoys coding, teaching people and learning new things. In his free time he writes educational posts, participates in OpenSource projects, goes in for sports and plays music.
Ilya is a lead of content/documentation/onboarding at Lokalise, an IT tutor and author, web developer, and ex-Microsoft/Cisco specialist. His primary programming languages are Ruby, JavaScript, Python, and Elixir. He enjoys coding, teaching people and learning new things. In his free time he writes educational posts, participates in OpenSource projects, goes in for sports and plays music.
An SRT file is a plain text file used to add subtitles to videos. It’s one of the simplest and most common formats out there. If you’ve ever turned on captions on a YouTube video, there’s a good chance it was using an SRT file behind the scenes. People use SRT files for all kinds of things: social media clips, online courses, interviews, films, you name it. They’re easy to make, easy to edit, and they work pretty much everywhere without hassle. In this post, we’ll
Libraries and frameworks to translate JavaScript apps
In our previous discussions, we explored localization strategies for backend frameworks like Rails and Phoenix. Today, we shift our focus to the front-end and talk about JavaScript translation and localization. The landscape here is packed with options, which makes many developers a
Character encoding: Types, UTF-8, Unicode, and more explained
In this article, we’ll explore various types of character encoding used in the world of information technology. We’ll break down why encoding matters, explain how they function, and highlight the key differences between ASCII, UTF-8, UTF-16, and the Unicode Standard. Understanding these differences is essential for correctly handling text in any software application, especially when working with localized time and date formats that rely on proper ch