Translating our models

The last thing that we want to look at is how to translate our model data. Open the site and change to the French language. Your home page should be similar to the following screenshot:

Translating our models

You will notice that even though the static content—the one that we put in the template ourselves—is translated, the dynamic names of the movies are not. While this is acceptable for some sites, your model data should be translated as well in order to be truly internationalized. Django by default does not have any built-in method to achieve this, but it's pretty easy.

Tip

What I'm about to show you is something that the Django modeltranslation library already provides. I have used it in a large scale project and it works pretty well, so if you want to skip this section, you can just go ahead and use the library. However, it's nice to have an overview of how you can achieve it without any external help.

You can find the library at https://github.com/deschler/django-modeltranslation.

What we need is some way to store more than one language for each of the text fields in our models. You could come up with a scheme where you store both the English and French translation of the string in the same field using some separator, and then separate the two when displaying the model.

Another way to achieve the same result would be to add an extra field for each language. For our current example, that would mean adding an extra field for each field that we want to translate.

Both approaches have their pros and cons. The first one is difficult to maintain; as you add more than just one language to translate to, the data format becomes difficult to maintain.

The second approach adds database fields, something that may not always be possible. Plus, it requires a fundamental change in how to access the data. However, if you have the option, I always advice going with the option that results in cleaner and easy-to-understand code, which in this case means adding extra fields per language.

For our MovieDetails model, this means having an extra field each for the title and description fields to store the French translation. Edit your main/models.py file so that the MovieDetails model matches the following code:

class MovieDetails(models.Model):
    title = models.CharField(max_length=500)
    title_fr = models.CharField(max_length=500)

    description = models.TextField()
    description_fr = models.TextField()

    stars = models.PositiveSmallIntegerField()

    def __str__(self):
        return self.title

Next, create and run the migrations to add these new fields to the database:

> python manage.py makemigrations
You are trying to add a non-nullable field 'description_fr' to moviedetails without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
>>> ''
You are trying to add a non-nullable field 'title_fr' to moviedetails without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now()
>>> ''
Migrations for 'main':
  0002_auto_20160216_2300.py:
    - Add field description_fr to moviedetails
    - Add field title_fr to moviedetails
    - Alter field movie on moviereview

As you can see in the preceding CLI session, when I created the migrations, I was asked to provided a default value for the new fields. I just entered the empty string. We can fix the value later from the admin.

Finally, run the new migration:

> python manage.py migrate
Operations to perform:
  Apply all migrations: admin, contenttypes, main, auth, sessions
Running migrations:
  Rendering model states... DONE
  Applying main.0002_auto_20160216_2300... OK

Once this is done, open up the admin and see the edit page for one of the objects you have in your database. It should seem similar to the following screenshot:

Translating our models

In the preceding screenshot, you can see that two new fields have been added to the admin. I translated the English value for these fields using Google Translate and filled in the French language field values. Click on Save. Now, our models have the French language data alongside the English values; but how to display them?

You could put a couple of if/else conditions in your template code to decide which language field to use. However, that gets messy quickly. Right now, our model only has two fields that are translated, but imagine a model with 10 such fields. How many conditions would you have? We're only talking about supporting one language. Finally, we only have two templates that need to be modified, the list view and detail view. In a real-world, more complicated application, your models could be used from a hundred different places. The if/else approach gets too difficult to maintain very quickly.

Instead, what we will do is give our model methods to intelligently return to us the correct value for a field, depending on the current language. Let's modify our main/models.py file again. First, import the get_language method at the top:

from django.utils.translation import get_language

Next, modify the MovieDetails model again and add these three new methods (highlighted in the code):

class MovieDetails(models.Model):
    title = models.CharField(max_length=500)
    title_fr = models.CharField(max_length=500)

    description = models.TextField()
    description_fr = models.TextField()

    stars = models.PositiveSmallIntegerField()

    def get_title(self):
        return self._get_translated_field('title')

    def get_description(self):
        return self._get_translated_field('description')

    def _get_translated_field(self, field_name):
        original_field_name = field_name

        lang_code = get_language()
        
        if lang_code != 'en':
            field_name = '{}_{}'.format(field_name, lang_code)
        field_value = getattr(self, field_name)
                
        if field_value:
            return field_value
        else:
            return getattr(self, original_field_name)

    def __str__(self):
        return self.title

There's nothing Django-specific in the new methods. The main work-horse is the _get_translated_field method. Given a field name, it looks at the current language, and if the language is something other than English, appends the language code to the field name. It then gets the value for the new field name from the object. If the value is empty because we didn't translate the field, it follows the Django convention and just returns the value of the original non-translated field.

Now, modify main/templates/movies_list.html to use these new methods:

{% extends "base.html" %}
{% load i18n %}

{% block content %}
<h2>{% trans "Movies List" %}</h2>

<ul>
    {% for movie in object_list %}
<li><a href="{% url 'movie-details' pk=movie.pk %}">{{ movie.get_title }}</a> | {{ movie.stars }} {% trans "Stars" %}</li>
    {% endfor %}
</ul>
{% endblock %}

The only change here is that instead of using the value of movie.title directly, we use movie.get_title. This is the one major drawback of this approach. Now everywhere in your project where you need the title or description values, you'll have to use the get_title and get_description methods instead of just using the field values directly.

Note

The same goes for saving the fields. You'll have to figure out which field name to write to, depending on the active language. While neither of these are complicated, they do add some discomfort to the whole process. However, that's the price you pay for this power.

The django-modeltranslation package that I mentioned before has a good solution to this problem. It uses code in the model to automatically decides which language to return whenever you access any field. So, instead of using obj.get_title(), you would write obj.title and get the correct field for the currently activated language. For your projects, you might want to look into this. I didn't use this in this chapter because I wanted to give you a way to work with basic Django and show you one possible way to do things yourself instead of relying on third-party libraries.

Open up the French version of the site again and you should see that the one object we translated should have the translated version of the title, whereas the others will just show the non-translated versions:

Translating our models

Doing the same for the details template should be simple and is left up to you!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.255.36