Quickstart
Add LOCALE_PATHS
setting to the project settings file and change the default language:
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'fi'
Edit a template file, load the i18n
tag and use the trans
tag to translate a string:
{% load i18n %}
<p>{% trans "Life is life." %}</p>
Create a directory for the translation files and run the makemessages
command:
mkdir locale
django-admin makemessages -l fi --ignore venv
Edit locale/fi/LC_MESSAGES/django.po
and add the translation:
msgid "Life is life."
msgstr "Elämä on laiffii." # HERE
Compile the messages:
django-admin compilemessages --ignore venv
Result:
<p>Elämä on laiffii.</p>
Full Tutorial
Setup
Run these commands to setup a new project:
python3 -m venv venv && \
source venv/bin/activate && \
pip install django --upgrade pip && \
django-admin startproject config . && \
python manage.py startapp pages && \
python manage.py makemigrations && \
python manage.py migrate && \
python manage.py createsuperuser
Edit the project settings.py
file and add these lines to it:
INSTALLED_APPS = [
# HERE
'pages.apps.PagesConfig',
'django.contrib.staticfiles',
# ...
]
# HERE
LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# HERE
'DIRS': [os.path.join(BASE_DIR, 'templates')],
# ..
},
]
- The
LOCALE_PATHS
setting defines a list of directories where Django looks for translation files.
Home view
Edit the pages
app views.py
file and add these lines to it:
from django.views.generic import TemplateView
class HomePageView(TemplateView):
template_name = 'pages/home.html'
URLs
Create a file called urls.py
in the pages app directory and add these lines to it:
from django.urls import path
from .views import HomePageView
app_name = 'pages'
urlpatterns = [
path('',
HomePageView.as_view(),
name='home')
]
Edit the main urls.py
file and add these lines to it:
from django.contrib import admin
# HERE
from django.urls import include, path
urlpatterns = [
# HERE
path('', include('pages.urls')),
path('admin/', admin.site.urls),
]
Homepage template
Create a directory called templates
in the project root. Create a directory called pages
in it and add a file called home.html
in the pages
directory. Add these lines in the home.html
file:
{% load i18n %}
<h1>Home</h1>
<p>{% trans "Life is life." %}</p>
Load the i18n
tag in every template that uses translation tags (even if the parent template already loads it). Visit the homepage and you should see this:
Translation
Run these commands:
mkdir locale
django-admin makemessages -l fi --ignore venv
- The
makemessages
command creates a message file (or updates it). -l
option specifies the language we want to translate strings to.--ignore venv
ignores the virtual environment directory.
Edit locale/fi/LC_MESSAGES/django.po
and add the translation:
#: templates/pages/home.html:3
msgid "Life is life."
msgstr "Elämä on laiffii."
Run this command:
django-admin compilemessages --ignore venv
The compilemessages
command creates .mo
files from .po
files. .mo
files are binary files optimized for the gettext
function. The gettext
function translates a message and returns it as a string.
Now if we change the LANGUAGE_CODE
setting in the settings.py
file to fi
, we can see the translation in action:
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'fi'
Language preference
Let's chance the LANGUAGE_CODE
back to en-us
:
LANGUAGE_CODE = 'en-us'
# LANGUAGE_CODE = 'fi'
You can also use Django's LocaleMiddleware
to determine user’s language preference:
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware',
# HERE
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
]
Add it in the settings.py
file MIDDLEWARE
setting (between SessionMiddleware
and CommonMiddleware
).
- First the
LocaleMiddleware
tries to determine user's language using URL language prefix (mysite.com/fi/about/). - Then it looks for a cookie.
- Then it looks at the
Accept-Language
HTTP header (sent by the browser). - Failing all the above, it uses the
LANGUAGE_CODE
setting.
URL Language Prefix
The first thing the LocaleMiddleware
looks for is URL language prefix if you are using the i18n_patterns
function in the main URL configuration file. Let's try that.
Edit the config/urls.py
file, import i18n_patterns
and use it like this:
from django.contrib import admin
from django.urls import include, path
from django.conf.urls.i18n import i18n_patterns
urlpatterns = i18n_patterns(
path('', include('pages.urls')),
path('admin/', admin.site.urls),
prefix_default_language=False
)
- The
i18n_patterns
function prepends the current active language code to all URL patterns defined inside the function. - The
prefix_default_language=False
argument hides the language prefix for the default language.
Now you can add /fi/
language prefix to the URL to see the Finnish translation:
# shows finnish translation
http://127.0.0.1:8000/fi/
# shows english translation
http://127.0.0.1:8000/
You can also see the admin login page translated to Finnish in:
http://127.0.0.1:8000/fi/admin/
Cookie
Next place the LocaleMiddleware
looks for the language preference is a cookie called django_language
. You can change the language cookie name by changing the LANGUAGE_COOKIE_NAME
setting in the settings.py
file.
Let's print out the current language code in the template:
{% load i18n %}
<h1>Home</h1>
<p>{% trans "Life is life." %}</p>
{% get_current_language as LANGUAGE_CODE %}
LANGUAGE_CODE: {{ LANGUAGE_CODE }}
- The
get_current_language
tag returns the current language.
Here is an example on how to set up the cookie manually. Edit the pages app views.py
file and add a render_to_response
method to it:
from django.shortcuts import render
from django.views.generic import TemplateView
# HERE
from django.conf import settings
class HomePageView(TemplateView):
# HERE
def render_to_response(self, context, **response_kwargs):
from django.utils import translation
user_language = 'fi'
translation.activate(user_language)
response = super(HomePageView, self).render_to_response(context, **response_kwargs)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
return response
template_name = 'pages/home.html'
translation.activate()
changes the language for the current thread.response.set_cookie()
makes the preference persistent.
Now the language for the home page will always be Finnish. Our custom code overrides all other behaviours.
You can do this with function-based views:
def home(request):
from django.utils import translation
user_language = 'fi'
translation.activate(user_language)
response = render(request, 'pages/home.html')
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
return response
Add it to pages app URLs:
from django.urls import path
# HERE
from .views import HomePageView, home
app_name = 'pages'
urlpatterns = [
path('',
HomePageView.as_view(),
name='home'),
# HERE
path('home/',
home,
name='home_function')
]
Browser detection
The Accept-Language
header is sent by the browser. In Chrome you can change the preferred language in here: chrome://settings/?search=language
. In this example I moved the Finnish language on top:
Comment out the language setting code and clear the cookie:
class HomePageView(TemplateView):
def render_to_response(self, context, **response_kwargs):
# from django.utils import translation
# user_language = 'fi'
# translation.activate(user_language)
response = super(HomePageView, self).render_to_response(context, **response_kwargs)
# response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
response.delete_cookie(settings.LANGUAGE_COOKIE_NAME)
return response
template_name = 'pages/home.html'
Do this in a function-based view:
def home(request):
# from django.utils import translation
# user_language = 'fi'
# translation.activate(user_language)
response = render(request, 'pages/home.html')
# response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
return response
Also remove the i18n_patterns()
function from the pages app urls.py
file:
from django.contrib import admin
from django.urls import include, path
# from django.conf.urls.i18n import i18n_patterns
urlpatterns = [
path('', include('pages.urls')),
path('admin/', admin.site.urls),
]
# urlpatterns = i18n_patterns(
# path('', include('pages.urls')),
# path('admin/', admin.site.urls),
# prefix_default_language=False
# )
Refresh the home page and it should be translated to the Finnish language. So, now we can't use language prefixes in the URL, or load the default language without the prefix, neither we have a django_languge
cookie. Now your browser language preference determines the language.
If your browser doesn't send the Accept-Language
header, the global LANGUAGE_CODE
that we set in the settings.py
is used.
URL translation
To translate URLs it's recommended to use language prefixes, so let's enable them again.
Edit the project urls.py
file and make these changes:
from django.contrib import admin
from django.urls import include, path
from django.conf.urls.i18n import i18n_patterns
# HERE
from django.utils.translation import gettext_lazy as _
# urlpatterns = [
# path('', include('pages.urls')),
# path('admin/', admin.site.urls),
# ]
# HERE
urlpatterns = i18n_patterns(
path('', include('pages.urls')),
path(_('admin/'), admin.site.urls),
prefix_default_language=False
)
- The
gettext_lazy
function translates the string when the value is accessed, not when we call the function.
Run the makemessages
command:
django-admin makemessages -l fi --ignore venv
Edit the messages file:
#: config/urls.py:13
msgid "admin/"
msgstr "yllapito/"
Run the compilemessages
command:
django-admin compilemessages --ignore venv
Edit the home.html
template file and add these lines to it:
{% load i18n %}
<h1>{{ title }}</h1>
<p>{% trans "Life is life." %}</p>
{% get_current_language as LANGUAGE_CODE %}
LANGUAGE_CODE: {{ LANGUAGE_CODE }}
<!-- START -->
<br><br>
<a href="{% url 'admin:index' %}">
{% trans 'Site administration' %}
</a>
<!-- END -->
The home page should now have a translated link to the admin. Both the text and the link should be translated. Visit /fi/yllapito/
to see the admin translated in Finnish:
Language switcher
Let's add a switcher form that redirects the client to the home page and changes the active language. If the default language is en-us
the switcher will redirect to (/
), not (/en/
) or (/en-us/
). For other languages it redirects to the home page but leaves the language prefix (/fi/
).
Edit the project settings.py
file and add the languages you want to show up in the switcher using the LANGUAGES
setting:
LANGUAGE_CODE = 'en-us'
LANGUAGES = [
('en-us', 'English'),
('fi', 'Finnish'),
]
Edit the pages app home.html
file and add this form to it:
<br><br>
<form action="{% url 'change_language' %}" method="post">
{% csrf_token %}
<select name="language">
{% get_available_languages as LANGUAGES %}
{% for language in LANGUAGES %}
<option value="{{ language.0 }}" {% if language.0 == LANGUAGE_CODE %} selected{% endif %}>
{{ language.0|language_name_local }} ({{ language.0 }})
</option>
{% endfor %}
</select>
<input type="submit" value="Change language">
</form>
get_available_languages
returns a list of language tuples in the form of ('language code', 'language translated to currently active language')- The
language_name_local
filter returns the language name translated to its original language.
Edit the pages app views.py
file and add this function to it:
from django.http import HttpResponseRedirect
def change_language(request):
response = HttpResponseRedirect('/')
if request.method == 'POST':
language = request.POST.get('language')
if language:
if language != settings.LANGUAGE_CODE and [lang for lang in settings.LANGUAGES if lang[0] == language]:
redirect_path = f'/{language}/'
elif language == settings.LANGUAGE_CODE:
redirect_path = '/'
else:
return response
from django.utils import translation
translation.activate(language)
response = HttpResponseRedirect(redirect_path)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language)
return response
if request.method == 'POST':
checks if the request is aPOST
method. If not, we redirect the client to the home page. Otherwise we examine the selectedlanguage
option.- If
language
is not equal to the defaultLANGUAGE_CODE
and it can be found in theLANGUAGES
list, we set theredirect_path
variable to thelanguage
value. It will be something likefi
ores
. - The
[lang for lang in settings.LANGUAGES if lang[0] == language]
list comprehension returnsTrue
if it finds the language in theLANGUAGES
list.lang[0]
gets the first element of the tuple. That's the language code (for example (en-us
) in('en-us', 'English')
). - If
language
is equal to the defaultLANGUAGE_CODE
, we set theredirect_path
as/
. - If the
language
is not the default language and we can't find it in theLANGUAGES
list, we redirect the client to the home page. Otherwise we activate the language and redirect to theredirect_path
.
Edit the main urls.py
file:
from django.contrib import admin
from django.urls import include, path
from django.conf.urls.i18n import i18n_patterns
from django.utils.translation import gettext_lazy as _
# HERE
from pages.views import change_language
# HERE
urlpatterns = [
path('change_language/',
change_language,
name='change_language')
]
# HERE (add +)
urlpatterns += i18n_patterns(
path('', include('pages.urls')),
path(_('admin/'), admin.site.urls),
prefix_default_language=False
)
The official docs offer slightly different switcher implementation.
Translations in Python code
You can use gettext
function and its variations to translate messages outside templates. Edit the pages app views.py
file and add a title
variable to the context:
from django.utils.translation import gettext as _
class HomePageView(TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = _('Home')
return context
template_name = 'pages/home.html'
gettext
returns a translated string. We import it as_
so we don't have to type so much.- Override the
get_context_data
method to change the context. - The
context
object is a dictionary that maps Python objects to template variables. What you put in thecontext
dictionary will be available in the template. context = super().get_context_data(self, **kwargs)
gets the context.context['title'] = _('Home')
changes the context.
With function-based views you can pass the context like this:
def home(request):
return render(request,
'pages/home.html',
{'title': _('Home')})
Edit the pages app home.html
template and print out the title:
{% load i18n %}
<!-- START -->
<h1>{{ title }}</h1>
<!-- END -->
<p>{% trans "Life is life." %}</p>
{% get_current_language as LANGUAGE_CODE %}
LANGUAGE_CODE: {{ LANGUAGE_CODE }}
Run the makemessages
command:
django-admin makemessages -l fi --ignore venv
Edit the translation file:
#: pages/views.py:21 pages/views.py:29
msgid "Home"
msgstr "Etusivu"
Run the compilemessages
command:
django-admin compilemessages --ignore venv
The home page should now display the translated title:
Model translation
Let's use the django-modeltranslation
package to translate database items:
pip install django-modeltranslation
Edit the pages app models.py
file and add these lines to it:
from django.db import models
class Post(models.Model):
text = models.TextField()
Edit the pages app admin.py
file and add these lines to it:
from django.contrib import admin
from .models import Post
admin.site.register(Post)
Create a file called translation.py
in the pages app directory and add these lines to it:
from modeltranslation.translator import translator, TranslationOptions
from .models import Post
class PostTranslationOptions(TranslationOptions):
fields = ('text', )
translator.register(Post, PostTranslationOptions)
Make sure to add the comma (,
) in fields = ('text', )
if you have only one field.
Edit the project settings.py
file and add modeltranslation
to the INSTALLED_APPS
list:
INSTALLED_APPS = [
'pages.apps.PagesConfig',
'modeltranslation',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
]
# Also mark the languages for translation like this:
from django.utils.translation import ugettext_lazy as _
LANGUAGES = (
('en-us', _('English')),
('fi', _('Finnish')),
)
Run migrations:
python manage.py makemigrations && \
python manage.py migrate
Edit the pages app views.py
file and make these changes to the HomePageView
:
# HERE
from .models import Post
class HomePageView(TemplateView):
def get_context_data(self, **kwargs):
# HERE
posts = Post.objects.all()
context = super().get_context_data(**kwargs)
context['title'] = _('Home')
# HERE
context['posts'] = posts
return context
Create a post:
Edit the home.html
template file and loop through the posts like this:
<ul>
{% for post in posts %}
<li>{{ post.text }}</li>
{% endfor %}
</ul>