Want to make your Django app accessible to users around the globe? This beginner’s guide to Django i18n (internationalization) will walk you through the basics of adapting your application for different languages and cultures. Whether you’re starting fresh or updating an existing project, this guide has you covered.
We’ll begin by enabling Django i18n in your project, then explore how to integrate it with tools like Lokalise. By the end, you’ll have a solid grasp of the foundational techniques for software internationalization in Django.
manage.py: A command-line utility for managing your Django project.
settings.py: The configuration file where you’ll enable i18n features.
urls.py: Defines the URL mappings for your app.
db.sqlite3: A lightweight database created by default.
With your Django project ready, we can now focus on enabling i18n.
Step 3: Enable Django i18n
Before diving into the techniques, let’s check if i18n (internationalization) is enabled in Django. The good news? It’s enabled by default!
3.1 Adjust settings
In your project’s settings.py, look for the following variable:
USE_I18N = True
This setting ensures that Django supports i18n out of the box. Even if you’re not actively using i18n, keeping this enabled has no performance overhead.
3.2 Set the default language
You can specify the default language code for your project with the LANGUAGE_CODE setting:
LANGUAGE_CODE = 'en'
This will be the fallback language for your app if no translations are available.
3.3 Define supported languages
To support multiple languages, add the LANGUAGES setting to settings.py. This tells Django which languages your app will support:
LANGUAGES = [ ('en', 'English'), ('lv', 'Latvian'),]
3.4 Adjust text direction
For languages like Arabic or Hebrew that read right-to-left (RTL), you can configure the LANGUAGE_BIDI variable:
LANGUAGE_BIDI = True # Set to True for RTL languages, False otherwise
That’s it! With these settings, your Django project is ready to handle internationalization and localization.
Step 4: Create a Django app
Now that i18n is enabled, let’s create an app called blogs to implement and test i18n features. Apps are the modular components of a Django project where the actual functionality lives.
Run this command in your project directory (make sure to cd blog before that):
Next, register your app in INSTALLED_APPS in settings.py to make sure Django recognizes it:
INSTALLED_APPS = [ # Other apps... 'blogs',]
With the app set up, we’re now ready to implement basic i18n techniques.
Step 5: Add translatable strings with gettext
Before creating translation files, we need to add some text in our app that Django can process for translation. Django uses gettext (or gettext_lazy for lazy evaluation) to mark strings for translation in Python code.
Let’s add some translatable strings to the views.py file in our blogs app. Open blogs/views.py and update it like this:
from django.utils.translation import gettext as _def home(request): message = _("Hello, world!") print(message)
Here’s what’s happening:
gettext (imported as _): This marks "Hello, world!" as a translatable string. Django will detect it when we generate the translation files. By convention, the _ function is used as a shorthand for gettext. It keeps the code cleaner and easier to read.
Step 6: Create translation files in Django
To enable translations, Django uses .po (Portable Object) files to store translation strings. Let’s generate these files so you can start managing translations for your app.
When you run the makemessages command, Django creates a .po file for the specified language. For example, if you used -l en, the message file is located at blogs/locale/en/LC_MESSAGES/django.po. Here’s what it might look like:
msgid: The original string in your code (in English by default).
msgstr: The translation for the string, which is currently empty.
Since the en file already contains the default English text, you don’t need to add translations there. Instead, run makemessages again for other languages (like Latvian, French, or Arabic) to create .po files for those locales. We'll see how to achieve it a bit later.
Step 7: Set a locale in Django
Switching the language of your Django app is easy with the built-in set_language view. Let’s walk through how to set it up and ensure everything works smoothly.
7.1 Use Django's built-in set_language view
Django provides a built-in view (django.conf.urls.i18n) to handle language switching. This view allows users to switch locales and saves their language preference in the session.
First, include the i18n URL pattern in your urls.py:
from django.conf.urls.i18n import i18n_patternsfrom django.urls import path, includeurlpatterns = [ path('i18n/', include('django.conf.urls.i18n')), # Language switching # Other URLs...]
This URL pattern connects the set_language view to your app, so it can handle requests to change the language.
7.2 Create a language switcher form
To allow users to change the language, add a form that interacts with the set_language view. A good place to add this is in a base HTML file (like base.html) so it’s accessible across the app.
In your blogs app (or whichever app you’re working in), create a folder named templates. This is where Django looks for HTML files.
Next, inside the templates directory, create a base.html file. This file will serve as a foundation for all your other templates. Here’s a sample markup with a language switcher:
Open the settings.py file and find the MIDDLEWARE part. Make sure to add LocaleMiddleware to the list:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', # <=== add this
This middleware automatically detects the user’s language preference (from the session, cookie, or Accept-Language header) and activates it for the current request.
7.4 Testing it out
Add a simple view to render the template. Update views.py in your blogs app:
from django.shortcuts import renderfrom django.utils.translation import gettext as _, get_languagedef home(request): user_language = get_language() # Fetch the active language print(user_language) # Display current language in the terminal # Translatable message message = _("Hello, world!") print(message) return render(request, 'base.html')
In this view we fetch the currently chosen locale name for the user, print it, and then display translated message in the terminal.
In your blogs/urls.py (or your main urls.py if you don’t have one), add this route:
Visit http://127.0.0.1:8000/ to see your app. You should see the language switcher form. Select a language and click Switch—it should save your preference and apply the selected locale.
Step 8: Extract and translate strings
Now that we have a translatable string in the home view, let’s extract it and create a translation for Latvian (lv).
8.1 Extract translatable strings
To extract translatable strings into .po files, use the makemessages command. Run this command from your project’s root directory:
python manage.py makemessages -l lv
After running this command, your project structure will look like this:
The .mo file is what Django uses to display translated content. Please note that you should never edit MO files directly: instead, change the PO files and then re-run the compilemessages command.
8.4 Note on lazy translations in Django i18n
By default, Django translates strings immediately when you use gettext() (or its alias _). This works perfectly for most scenarios, but there are times when you need the translation to happen later, closer to when the string is actually used.
Django provides gettext_lazy() for this purpose, allowing you to defer translation until the string's value is accessed. Let’s unpack why this is useful.
Why use lazy loading?
Here’s why lazy translation is needed:
Translations in class attributes: In Django models, forms, or any place where you’re defining class-level attributes, gettext() would evaluate the translation immediately—possibly before the correct language is even set. Using gettext_lazy() ensures the string is translated when it’s actually used (e.g., rendered in a template).
Translation timing: Lazy translations defer the evaluation of the string until it’s absolutely necessary. This avoids problems with translations being evaluated too early in a multi-language context.
Here’s how the two differ:
from django.utils.translation import gettext as _from django.utils.translation import gettext_lazy as _lazy# Immediate translationtranslated_now = _("Hello, world!")print(translated_now) # Translates the string immediately based on the current active language# Lazy translationtranslated_later = _lazy("Hello, world!")print(translated_later) # Does NOT translate the string immediately
gettext (_): The string is translated as soon as the line of code runs, using the active language.
gettext_lazy (_lazy): The string is stored as a lazy object and only translated when its value is accessed (e.g., during rendering).
Step 9: Perform translations in templates
9.1 Create template for the home page
Now, let’s create a dedicated template for the home view. Place this file in blogs/templates/blogs/home.html:
{% extends 'base.html' %}: Inherits the structure from base.html.
{% load i18n %} registers the trans and blocktrans tags for use in your template.
{% block title %}: Overrides the <title> tag for this specific page.
{% block content %}: Embeds the page-specific content into the {% block content %} placeholder in base.html.
9.2 Update the home view
Make sure the home view renders the home.html template:
def home(request): user_language = get_language() # Fetch the active language print(f"Current language: {user_language}") return render(request, 'blogs/home.html') # Render the specific home template
Now you can reload your server and make sure everything is working nice and the welcoming message is properly translated!
Step 10: Make the language switcher dynamic
To adjust the selected value in the language switcher based on the current locale, we can leverage Django’s get_language function or the LANGUAGE_CODE context variable (automatically added by LocaleMiddleware).
10.1 Displaying the currently set locale
Open up the base.html template and make the following changes:
We read the current locale using get_current_language
Then we simply mark one of the elements as selected
10.2 Listing all supported locales using template tag
Hardcoding all supported locales in the template is not the best approach. Let's make it more dynamic! To achieve that, first create a templatetags directory under blogs. Inside create an empty __init__.py file and a language_tags.py file with the following content:
This way we're reading the LANGUAGES set in the settings.py.
10.3 Iterating over the list of all supported locales
Now return to the base.html file and adjust the language switcher:
{% 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 %}
Key points:
{% trans "Choose your language" %} performs string translation
{% load language_tags %} prepares our language tags
{% for code, name in languages %} iterates over all supported locales
Great!
10.4 Adding missing translations
Let's also add missing translations for the "Choose your language" phrase and the "Switch" button. Run the following command:
python manage.py makemessages -l en -l lv
This should update both translation files for English and Latvian in one go.
Here are Latvian translations:
#: .\blogs\templates\base.html:18msgid "Choose your language"msgstr "Izvēlieties savu valodu"#: .\blogs\templates\base.html:28msgid "Switch"msgstr "Pārslēgt"#: .\blogs\templates\blogs\home.html:7msgid "Hello, world!"msgstr "Sveika, pasaule!"
Having done that, don't forget to produce new MO files:
python manage.py compilemessages
That's it!
Use Lokalise for Django i18n
As your app grows and you start managing translations for multiple languages, handling .po files manually can become tedious and error-prone. That’s where Lokalise steps in. Lokalise is a collaborative localization platform that streamlines the process of managing translations.
Setting Up Lokalise
Create a Lokalise account: Sign up at app.lokalise.com and create a new project. Choose the project type "Web and Mobile".
This command should download translation bundle (an archive) and automatically extract it into your Django project.
Conclusion to Django i18n
In this guide, we set up Django i18n to make your app ready for multiple languages. We configured key settings, marked translatable content, managed translation files, and built a language switcher. With these basics in place, your app is ready to offer a localized experience to users.
Thank you for staying with me, and until next time!
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
Syncing Lokalise translations with GitLab pipelines
In this guide, we’ll walk through building a fully automated translation pipeline using GitLab CI/CD and Lokalise. From upload to download, with tagging, version control, and merge requests. Here’s the high-level flow: Upload your source language files (e.g. English JSON files) to Lokalise from GitLab using a CI pipeline.Tag each uploaded key with your Git branch name. This helps keep translations isolated per feature or pull request