In this tutorial, you will learn how to make your Django website accessible to users speaking different languages.
0. Download the project we are going to work on
The GitHub repository for this project can be found here: https://github.com/openescuela/opsatipstutos/tree/main/tips6-before-Internationalisation
1. Configure internationalization tools
mysite/settings.py
.
.
.
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware', # new
]
.
.
.
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
gettext = lambda s: s
LANGUAGES = (
('ar', ('Arabic')),
('en', ('English')),
('fr', ('French')),
)
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATIC_URL = '/static/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
.
.
.
mysite/urls.py
from django.contrib import admin
from django.urls import path, include, re_path
from django.conf import settings
from django.conf.urls.static import static
from django.conf.urls.i18n import i18n_patterns
from django.conf.urls import url
urlpatterns = [
path('admin/', admin.site.urls),
path('', include("blogs.urls")),
url(r'^i18n/', include('django.conf.urls.i18n')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
2. Mark strings to be translated in code: in templates
There are several ways to mark strings for translation in templates : {% trans %}, {% translate %}, and {% blocktranslate %}
blogs/templates/blogs/index.html
{% load static %}
{% load i18n %}
<!doctype html>
.
.
.
<a class="p-2 link-secondary" href="#">{% trans 'World' %}</a>
<a class="p-2 link-secondary" href="#">{% trans 'Technology' %}</a>
.
.
.
</html>
3. Create message files with the makemessages subcommand:
We will indicate the language to be processed and we will ignore the virtual environment files.
$ django-admin makemessages -l ar --ignore=myenv/*
$ django-admin makemessages -l fr --ignore=myenv/*
these two commands must create in the directory locale two folders named ar and fr in which there is a folder named LC_MESSAGES with a file named django.po.
4. Complete the obtained message files
locale/ar/LC_MESSAGES/django.po
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-31 17:29+0000\n"
"PO-Revision-Date: 2022-01-31 17:30+0000\n"
"Last-Translator: <>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
"X-Translated-Using: django-rosetta 0.9.8\n"
#: blogs/templates/blogs/index.html:38
msgid "World"
msgstr "عالم "
#: blogs/templates/blogs/index.html:39
msgid "Technology"
msgstr "تكنولوجيا "
locale/fr/LC_MESSAGES/django.po
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-01-31 17:29+0000\n"
"PO-Revision-Date: 2022-01-31 17:32+0000\n"
"Last-Translator: <>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Translated-Using: django-rosetta 0.9.8\n"
#: blogs/templates/blogs/index.html:38
msgid "World"
msgstr "Monde"
#: blogs/templates/blogs/index.html:39
msgid "Technology"
msgstr "Technologie"
5. Compile .po to .mo
$ python manage.py compilemessages
6. Add set_language redirect view to be able to change the language
blogs/index.py
.
.
.
<nav class="nav d-flex justify-content-between">
<a class="p-2 link-secondary" href="#">{% trans 'World' %}</a>
<a class="p-2 link-secondary" href="#">{% trans 'Technology' %}</a>
<a class="p-2 link-secondary" href="#">Politics</a>
<a class="p-2 link-secondary" href="#">Science</a>
<a class="p-2 link-secondary" href="#">Travel</a>
<li class="nav-item dropdow">
<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}">
<select name="language">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
<input type="submit" value="{% trans 'Go' %}">
</form>
</li>
</nav>
.
.
.
7. Run server
Run your server and go to the browser with the localhost http://127.0.0.1:8000/ to show the obtained results.
python manage.py runserver
8. Use Rosetta to simplify message translation and compilation
Rosetta is a Django application that eases the translation process of your Django projects.
a. Install Rosetta
$ pip install django-rosetta
b. Add Rosetta to the installed app
mysite/settings.py
.
.
.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blogs',
'rosetta', # new
]
.
.
.
c. Add Rosetta URL to the project URLs
mysite/urls.py
from django.contrib import admin
from django.urls import path, include, re_path
from django.conf import settings
from django.conf.urls.static import static
from django.conf.urls.i18n import i18n_patterns
from django.conf.urls import url
urlpatterns = [
path('admin/', admin.site.urls),
path('', include("blogs.urls")),
url(r'^i18n/', include('django.conf.urls.i18n')),
re_path(r'^rosetta/', include('rosetta.urls')), # new
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
d. Run server
Run your server and go to the browser with these address: http://127.0.0.1:8000/rosetta/
9. Mark strings to be translated in code: in python code
To translate strings directly in python code you should use these functions: gettext() or gettext_lazy()
blogs/models.py
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
class Category(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=50)
image = models.ImageField()
resume = models.TextField()
content = models.TextField()
publish = models.DateTimeField(default=timezone.now)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='post_category')
def __str__(self):
return self.title
class ContactMe(models.Model):
email = models.EmailField(help_text=_("Your email address will not be published"), verbose_name=_("Email"))
name = models.CharField(max_length=50, verbose_name=_("Name"))
message = models.TextField(verbose_name=_("Message"))
def __str__(self):
return self.name
Complete the strings that you want to translate in the template
blogs/templates/blogs/index.html
{% load static %}
{% load i18n %}
<!doctype html>
{% get_current_language as LANGUAGE_CODE %}
{% if LANGUAGE_CODE == 'ar' %}
<html lang="en" dir="rtl">
{% else %}
<html lang="en">
{% endif %}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.88.1">
<title>Blog Template · Bootstrap v5.1</title>
<link rel="canonical" href="https://getbootstrap.com/docs/5.1/examples/blog/">
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<!-- Favicons -->
<link rel="apple-touch-icon" href="/docs/5.1/assets/img/favicons/apple-touch-icon.png" sizes="180x180">
<link rel="icon" href="/docs/5.1/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
<link rel="icon" href="/docs/5.1/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
<link rel="manifest" href="/docs/5.1/assets/img/favicons/manifest.json">
<link rel="mask-icon" href="/docs/5.1/assets/img/favicons/safari-pinned-tab.svg" color="#7952b3">
<link rel="icon" href="/docs/5.1/assets/img/favicons/favicon.ico">
<meta name="theme-color" content="#7952b3">
<!-- Custom styles for this template -->
<link href="https://fonts.googleapis.com/css?family=Playfair+Display:700,900&display=swap" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="{% static 'blogs/css/styles.css' %}" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="nav-scroller py-1 mb-2">
<nav class="nav d-flex justify-content-between">
<a class="p-2 link-secondary" href="#">{% trans 'World' %}</a>
<a class="p-2 link-secondary" href="#">{% trans 'Technology' %}</a>
<a class="p-2 link-secondary" href="#">{% translate "Politics" %}</a>
<a class="p-2 link-secondary" href="#">{% translate "Science" %}</a>
<a class="p-2 link-secondary" href="#">{% translate "Travel" %}</a>
<li class="nav-item dropdow">
<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}">
<select name="language">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
<input type="submit" value="{% trans 'Go' %}">
</form>
</li>
</nav>
</div>
</div>
<main class="container">
<div class="p-4 p-md-5 mb-4 text-white rounded bg-dark">
<div class="col-md-6 px-0">
<h1 class="display-4 fst-italic">{{longerPost.title}}</h1>
<p class="lead my-3">{{longerPost.resume}}</p>
<p class="lead mb-0"><a href="#" class="text-white fw-bold">{% trans 'Continue reading' %}</a></p>
</div>
</div>
<div class="row mb-2">
{% for post in posts %}
<div class="col-md-6">
<div class="row g-0 border rounded overflow-hidden flex-md-row mb-4 shadow-sm h-md-250 position-relative">
<div class="col p-4 d-flex flex-column position-static">
<strong class="d-inline-block mb-2 text-primary">{{post.category}}</strong>
<h3 class="mb-0">{{post.title}}</h3>
<div class="mb-1 text-muted">{{post.publish}}</div>
<p class="card-text mb-auto">{{post.resume}}</p>
<a href="#" class="stretched-link">{% trans 'Continue reading' %}</a>
</div>
<div class="col-auto d-none d-lg-block">
<img src="{{post.image.url}}" width="100%" height="225"/>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="row">
<div class="col">
<div class="card">
<h2>{% trans 'Contact me' %}</h2>
<form class="" action="/" method="post">{% csrf_token %}
{{contactme.as_p}}
<button type="submit" class="btn btn-primary">{% trans 'Submit' %}</button>
</form>
</div>
</div>
</div>
</main>
<footer class="blog-footer">
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</body>
</html>
Create again the message files with the makemessages subcommand:
$ django-admin makemessages -l ar --ignore=myenv/*
$ django-admin makemessages -l fr --ignore=myenv/*
Run your server, navigate to the address http://127.0.0.1:8000/rosetta/, and complete the traduction of the strings.
Save the translation and go to the localhost http://127.0.0.1:8000/ to show the results.
10. Use the modeltranslation application to translate dynamic content of Django models
The modeltranslation application is used to translate dynamic content of existing Django models to an arbitrary number of languages without having to change the original model classes.
a. install modeltranslation
$ pip install django-modeltranslation
b. add modeltranslation to the installed app
.
.
.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blogs',
'rosetta',
'modeltranslation', #new
]
.
.
.
c. Registering Models for Translation
Create a translation.py
in the blogs app directory and register the model and the translation option.
blogs/translation.py
from modeltranslation.translator import translator, TranslationOptions
from .models import Post
class PostTranslationOptions(TranslationOptions):
fields = ('title', 'resume')
translator.register(Post, PostTranslationOptions)
d. Run migration
$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py runserver
c. Go to the admin page and complete the translation of the choices fields of each post.
d. go to the localhost to show the obtained results
if everything is ok you should have something like this :
0 comment