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&#43;Display:700,900&amp;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

There are no comments yet.

Log in to leave a reply

Related posts

Developing a Web Application with Django Part 1: Getting Started

1/12/2021

Developing a Web Application with Django Part 2: Templates

15/12/2021

Developing a Web Application with Django Part 3 : Models

7/1/2022