I added the translation to my Django project in several stages and with several approaches. In the template I use the Django built-in approach with {% trans 'text' %}
and with {% blocktrans %}
. In the view I use the gettext()
method and in the models I use the lazy_gettext()
method. There was one problem missing, in my JSON data file I had already created a lot of content and this file was growing. I was looking for a way to handle this situation. I found the package: django-modeltranslation
which was recommended to me by someone in the Django forum.
1. General notes on translating:
# portfolio/settings.py
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
# add this middleware:
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
LANGUAGE_CODE = "en-us"
TIME_ZONE = "Europe/Paris"
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = (
("en", _("English")),
("de", _("German")),
("fr", _("French")),
)
LOCALE_PATHS = [
os.path.join(BASE_DIR, "locale"),
]
"django.middleware.locale.LocaleMiddleware",
: The following algorithm is used by LocaleMiddleware to determine the user's language preferenceUSE_I18N
: This boolean specifies whether Django's translation system should be active or not.USE_L10N
:USE_TZ
: A boolean that specifies if datetimes will be timezone-aware by default or not.LANGUAGES
:LOCALE_PATHS
: It is a list of directories where Django looks for any translation files.NOTE:
In Django, "timezone-aware" refers to the ability of the application to handle date and time objects that include timezone information. Here's a simple breakdown:
By adopting timezone-awareness, a Django application is better equipped to handle global user bases, ensuring that times are consistently and accurately displayed according to users' locations.
# portfolio/urls.py
from django.conf import settings
from django.conf.urls.i18n import i18n_patterns
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("i18n/", include("django.conf.urls.i18n")),
]
urlpatterns += i18n_patterns(
path("", include("doridoro.urls", namespace="doridoro")),
path("portfolio/", include("projects.urls", namespace="projects")),
)
path("i18n/", include("django.conf.urls.i18n")),
: This URL enables the language redirect view.urlpatterns += i18n_patterns()
: This function must be set in the root URL config. And Django will automatically prepend the current active language code to all URL patterns defined in this function.urlpatterns += i18n_patterns(
path("", include("doridoro.urls", namespace="doridoro")),
path("portfolio/", include("projects.urls", namespace="projects")),
prefix_default_language=False
)
prefix_default_language=False
: This attribute of the i18n_patterns()
function, when set to False
, the URL will not set the language code to /en/
in the URL.<!-- base.html -->
<!-- Language switch form -->
<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="Go">
</form>
<!-- End of Language switch form -->
In the Django framework, there are several ways to create the django.po
message files for each language:
1) create folder: local
in root directory and create the django.po
files for each language you have set in your settings.py
file:
(.venv) $ django-admin makemessages -l fr
(.venv) $ django-admin makemessages -l de
django-admin makemessages
: Is a Django utility command used to extract translatable strings from your project's source code and template files. Its main purpose is to create or update message files (.po files) for internationalization (i18n) purposes.-l
: The --locale
or -l
option determines which specific .po
file gets created or updated.
2) create folder: local
in root directory of your project and inside local
you create the folders: fr
and de
(.venv) $ django-admin makemessages --all
--all, -a
: This option creates or updates all language specific .po
files.
Enter all translation to the django.po
files in de
and fr
folder:
# django.po
#: core/templates/base.html:49 doridoro/templates/about.html:12
msgid "About"
msgstr "Info"
#: doridoro/models.py:123
msgid "If the job is ongoing, the end date should be empty."
msgstr ""
"Wenn es sich um einen laufenden Auftrag handelt, sollte das Enddatum leer "
"sein."
#: doridoro/templates/index.html:11
#, python-format
msgid ""
"\n"
" I'm a <span>%(profession)s</span> from Germany, living in France.\n"
" "
msgstr ""
"\n"
" Ich bin eine <span>%(profession)s</span> aus Deutschland und wohne "
"in Frankreich.\n"
" "
#: doridoro/templates/resume.html:20 doridoro/templates/resume.html:32
#: doridoro/templates/resume.html:46
msgid "to"
msgstr "bis"
#: doridoro/templates/skills.html:10
msgid ""
"\"Bring organisation talent, programming skills and sometimes biscuits to "
"work.\""
msgstr ""
"\"Bringe Organisationstalent, Programmierkenntnisse und manchmal auch Kekse "
"mit zur Arbeit.\""
#: projects/models.py:71
#, python-format
msgid "The uploaded file is not a valid image. -- %s"
msgstr "Die hochgeladene Datei ist kein gültiges Bild. -- %s"
msgid "Empathy"
msgstr "Einfühlungsvermögen"
#
: Is the comment command, anything after the #
will be ignored. Be careful, because sometimes the Django framework comments out your translation. Check it every time you change this file!msgid
: Is the string of the translation that will appear in the source code. Don't change it.msgstr
: This is where you put the language-specific translation. It starts out blank, so it's your responsibility to change it. Make sure you keep the quotation marks around your translation.Complete the messages with the command when you have entered all your translations in your .po
files:
.venv) $ django-admin compliemessages
django-admin compliemessages
: For use with the built-in gettext support, compiles .po
files created by makemessages into .mo
files.
The above explanation is a general overview of the translation framework of the Django framework.
---
Now I want to show you two different approaches to adding translation to my Django portfolio.
A) Use gettext()
method in view:
I have to translate some of my skills into German and French. To use another way to translate these skills, I decided to use the gettext()
method in my Django view.
# doridoro/views.py
class SkillsView(TemplateView):
template_name = "skills.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["doridoro"] = DoriDoro.objects.first()
context["programming_skills_published"] = self.get_skills_data().filter(
category=Skill.SkillChoices.PROGRAMMING_SKILLS
)
context["soft_skills"] = self.translate_skills_name(
category=Skill.SkillChoices.SOFT_SKILLS
)
context["other_skills_published"] = self.get_skills_data().filter(
category=Skill.SkillChoices.OTHER
)
context["strength"] = self.translate_skills_name(
category=Skill.SkillChoices.STRENGTH
)
context["languages"] = self.get_languages_data()
return context
def get_skills_data(self):
return Skill.objects.filter(published=True)
def translate_skills_name(self, category):
tags = self.get_skills_data().values_list("name", "category")
result_dict = {"SOFT_SKILLS": (), "STRENGTH": ()}
for tag in tags:
if tag[1] in ["SOFT_SKILLS", "STRENGTH"]:
translated_name = gettext(tag[0])
result_dict[tag[1]] += ((translated_name, tag[1]),)
return result_dict[category]
def get_languages_data(self):
languages = Language.objects.all()
language_tuple = ()
for language in languages:
language_tuple += ((language.name, language.get_level_display()),)
return language_tuple
The PROGRAMMING_SKILLS
and OTHER
skills didn't need to be translated because these skills are the same in all languages, but the SOFT_SKILLS
and STRENGTH
skills do.
def translate_skills_name(self, category):
: This function obtains a list of tuples containing skill names and their categories. It filters through the list and translates the skill name with the gettext()
method and returns the tuple corresponding to the category.def get_languages_data(self):
: This function gathers and formats language information from a database into a tuple of tuples, making it easy to access a list of languages and their corresponding levels.The translation for these skill names will be saved in the .po
file of the project. These .po
files are stored in the directory locale
. You set this
Possible improvements:
B) Integrate django-modeltranslation
:
First, you need to install the django-modeltranslation
package and add it to your Django project's INSTALLED_APPS
.
pip install django-modeltranslation
In your settings.py
file, add 'modeltranslation'
to your INSTALLED_APPS
list.
INSTALLED_APPS = [
'modeltranslation', # add the package here
'django.contrib.admin',
...
]
Add the package name before django.contrib.admin
to use the TranslationAdmin
instead of the admin.ModelAdmin
in the admin.py
file.
Create translation.py
files:
Create a translation.py
file in the same directory as your models.py
. This file will contain the translation options for your models.
Define TranslationOptions
:
In translation.py
, define a class that inherits from TranslationOptions
for each model you want to translate. You will specify which fields should be translatable.
Here’s an example using a simple Hobby
model with name
and published
attributes:
# doridoro/models.py
class Hobby(models.Model):
name = models.CharField(max_length=100)
published = models.BooleanField(
default=True, verbose_name=_("hobby visible on website")
)
class Meta:
verbose_name_plural = "hobbies"
def __str__(self):
return f"{self.name} ({self.published})"
And this shows the translation.py
file of the Hobby
model:
# doridoro/translation.py
from modeltranslation.translator import register, TranslationOptions
from doridoro.models import Hobby
@register(Hobby)
class HobbyTranslationOptions(TranslationOptions):
fields = ("name",)
In this file, you specify which field of the Hobby
model should be available for multiple languages. In this case, only the name
will be available in different languages.
Update Database Schema:
Run makemigrations
and migrate
to create the necessary database tables and columns for the translated fields.
python manage.py makemigrations
python manage.py migrate
Accessing Translated Fields:
After setting up the translation options, django-modeltranslation
will automatically create translated versions of the specified fields for each language you have configured in your project. For example, if you have configured your project to support English and French, the Hobby
model will have name_en
, name_de
and name_fr
fields.
Admin Integration:
To integrate translations into the Django admin, you need to register the translated model in the admin with TranslationAdmin
.
# doridoro/admin.py
from django.contrib import admin
from modeltranslation.admin import TranslationAdmin
from doridoro.models import Hobby
@admin.register(Hobby)
class HobbyAdmin(TranslationAdmin):
list_display = ["name", "published"]
By following these steps, you can easily set up and use TranslationOptions
in your Django models to handle multiple languages using django-modeltranslation
.