Django Portfolio Journal

Add translation - German and French


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"),
]

Legend:

    • "django.middleware.locale.LocaleMiddleware",: The following algorithm is used by LocaleMiddleware to determine the user's language preference
    • USE_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:

    1. Timezone-Aware DateTime Objects: These are date and time representations that contain timezone information. For example, a timezone-aware object would not just say "3:00 PM," but "3:00 PM in Central European Time (CET)."
    2. Benefits: Handling timezones ensures that date and time computations are accurate across different regions. For instance, when scheduling or displaying events for users in various time zones, timezone-aware datetime objects help convert to and from the user's local time correctly.
    3. Implementation: Django uses Python's pytz library to provide timezone support, allowing date and time inputs to be converted into timezone-aware objects easily. This can also involve storing times in UTC (Coordinated Universal Time) internally and converting them to the desired timezone when needed.
    4. Settings: You can enable timezone features by adjusting the USE_TZ setting in Django's settings file to True. This ensures that all datetime operations within Django use timezone-aware datetime objects.

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")),
)

Legend:

    • 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
)

Legend:

    • 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

Legend:

    • 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

Legend:

    • --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"

Legend:

    • #: 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

Legend:

    • 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.

Legend:

    • 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:

    • Create a custom Model Manager to retrieve only the published skills.

 

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.

    1. 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.

    2. 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.

Designed by BootstrapMade and modified by DoriDoro