Part V – Internationalization and Localization. Languages and Time zones

In this part of the TaskBuster Django Tutorial, we’ll talk about Internationalization, Localization and Time Zones.

We will define two different languages for our website, create urls specific for each of them, and make our website to support these two languages.

Moreover, we’ll talk about the time zone and how we can show the local time on a template.

The outline of this part is:

Let’s start! ūüôā

Internationalization – Settings

Let’s work on how we can implement internationalization, i.e. that our page can support multiple languages.

taskbuster_internationalization

Make Django spread around the world by talking different languages!

First, we will create a folder to save all the translation files. Those files will be created automatically by Django, writing the strings that we want to translate. I’ll show you how you can tell Django which strings you want to translate latter, but the idea is that you edit the files and put the translation for each string manually. This way, Django will choose one language or another depending on the user preferences.

The translation’s folder will be located inside the¬†taskbuster¬†folder:

Next, open your settings/base.py file and make sure you have

and the template context processor django.template.context_processors.i18n is inside the  TEMPLATES['OPTIONS']['context_processors'] setting:

Note: You can also find the value of a specific setting by using the Django shell. For example:

and it will output the current value of that variable.

Next, add the Locale middleware in the correct position, to be able to determine the user’s language preferences through the request context:

Next, specify the languages you want to use:
We will use English and Catalan (but feel free to put the languages you want, you can find its codes¬†here). The¬†ugettext_lazy¬†function is used to mark the language names for translation, and it’s usual to use the function’s shortcut¬†_.

Note: there is another function, ugettext, used for translation. The difference between these two functions is that ugettext translates the string immediately whereas ugettext_lazy translates the string when rendering a template.

For our settings.py, we need to use ugettext_lazy because the other function would cause import loops. In general, you should use ugettext_lazy in your model.py and forms.py files as well.

Moreover, the LANGUAGE_CODE¬†setting defines the default language that Django will use if no translation is found. I’ll leave the default:

And finally, specify the locale folder that we created before:

Don’t forget the trailing coma.

Internationalization – Urls

Ok, now that we have configured the settings, we need to think about how we want the app to behave with different languages. Here we will take the following approach: we will include a language prefix on each url that will tell Django which language to use. For the Home page it will be something like:

  • mysite.com/en
  • mysite.com/ca
And for the rest of urls, like mysite.com/myapp, it will be:
  • mysite.com/en/myapp
  • mysite.com/ca/myapp

This way the user may change from one language to another easily. However, we don’t want that neither the¬†robots.txt¬†nor the¬†humans.txt¬†files follow this structure (search engines will look at mysite.com/robots.txt and mysite.com/humans.txt to find them).

One way to implement this is with the following urls.py file:

Note that we left the robots.txt and humans.txt files with the same url, and the ones that we want to be translated use the i18n_patterns function.

Run your local server and visit the home page, it should redirect to /en or /ca. You can learn more about how Django discovers language preference in the official documentation.

But how does the user change its language preferences? Well, Django comes with a view that does this for you ūüėČ

This view expects a POST request with a language parameter. However, we will take care of that view in another time in this tutorial, when customizing the top navigation bar. The idea is to have a drop-down menu with all the possible languages, and just select one to change it.

Before proceeding, let’s run our tests,

Oh…. one failed! Well actually both unittest in taskbuster/test.py fail, as the template rendered when trying to use reverse("home")¬†is not found. This is because we need to set an active language for the reverse to work properly. First, write this at the top of the file:

and next, activate the selected language just after the test declaration. For example:

And the same for the other test: test_uses_base_template.

Now, tests pass. You should do the same for the¬†functional_tests/test_all_users.py: import the¬†activate¬†method at the beginning of the file and add the¬†activate(‘en’)¬†as the last step on the¬†setUp¬†method.

Internationalization – Templates

Let’s focus now on how we can translate the h1 Hello World title of the Home Page. Open the¬†index.html¬†template and look for <h1>Hello, world!</h1>.

We will use two different template tags:

  • trans¬†is used to translate a single line – we will use it for the title
  • blocktrans¬†is used for extended content – we will use it for a paragraph

Change the h1 and p contents of the jumbotron container for the following code:

Moreover, to have access to the previous template tags, you will have to write {% load i18n %} near the top of your template. In this case, after the extends from base.html tag.

Internationalization – Translation

Finally, we are able to translate our strings!

Go to the terminal, inside the taskbuster_project folder (at the same level as the manage.py file), and run:

This will create a message file for the language we want to translate. As we will write all our code in english, there is no need to create a message file for that language.

But ohh!! we get an ugly error that says that we don’t have the¬†GNU gettext¬†installed (if you don’t get the error, good for you! skip this installation part then!). Go to the¬†GNU gettext home page¬†and download the last version. Inside the zip file you’ll find the installation instructions on a file named¬†INSTALL.

Basically, you should go inside the package folder (once unzipped) and type:

to configure the installation for your system. Next, type

to compile the package. I always wonder why some installations print all that awful code on your terminal!

If you want, use

to run package tests before installing them, to see that everything works. Finally, run

to install the package.

Okey! Let’s go back to our developing environment, and try to generate our message file!

Yes! It worked!

Now go to the¬†taskbuster/locale¬†folder to see what’s in there.

There is a folder named¬†ca¬†(or the language you chose to translate) with a folder named¬†LC_MESSAGES¬†inside. If you go inside it, you’ll find another file named¬†django.po. Inspect that file with your editor.

There is some metadata at the beginning of the file, but after that you’ll see the strings we marked for translation:

  • The language’s names¬†“English”¬†and¬†“Catalan”¬†in the¬†base.py¬†settings file
  • The¬†Welcome to TaskBuster!¬†title on the¬†index.html¬†file
  • The paragraph after the title on the¬†index.html¬†file
Each of these sentences appear in a line beginning with msgid. You have to put your translation in the next line, the one that starts with msgstr.

Translating the title is simple:

And with a paragraph, you have to be careful to start and end each line with "":

Also, note the final space at the end of the line. If you don’t include that space, the words at the end of the line and at the beginning of the next line will concatenate.

Once you have all the translations set, you must compile them with:

You can run your local server and see the effect by going to the home page, but I prefer writing a test first! ūüôā

In the functional_tests/test_all_users.py add the following tests:

Remember to change the Benvingut a TaskBuster!¬†¬†sentence and the activate('ca')¬†if you’re using another language!

I hope all of your tests passed! ūüôā

Localization

Django is capable to render times and dates in the template using the format specified for the current locale. This means that two people with different locales may see a different date format on the template.

To enable Localization, edit the base.py settings file and make sure that:

This way, when including a value in a template, Django will try to render it using the¬†locale‘s format. However, we also need a way to deactivate this automatic formatting. For example, when using javascript, we need that the value we use has a uniform format for all locals.

Let’s write a test for it! In the Home Page, we will show today’s date and time using both local and non-local formats.

Open the functional_tests/test_all_users.py and write these imports at the top:

and next, add this test method inside the HomeNewVisitorTest class:

Next, run the test to see what should we implement first. You can run just this test using the command:

The test complains because it can’t find the element with a¬†local-date¬†id. Let’s create it!

Open again taskbuster/index.html template and load the localization template tags at the beginning of the file (for example, after loading the internationalization template tags):

Note: that it’s a lowercase L10N, not a 110N!!

Next, look for the container div. We will edit the first two columns to display the date on their h2 headers:

Note the unlocalize filter on the second header. This disables the localization format while rendering the today variable. Moreover, there is another template tag to disable large blocks:

You can learn more about this in the Django documentation.

Save and run the test again. It fails again, because we didn’t pass the¬†today¬†variable in the home view. Let’s do that!

Open the views.py and edit the home view:

Rerun the test! Yeah! It passed! ūüôā

Time Zones

If your project has to handle dates and times from all over the world, it’s better to¬†work in UTC (coordinated universal time). This way all your dates and times have a uniform convention in your database, so you can compare them no matter the time zone of the user.

And don’t worry, Django translates them automatically to the desired time zone in forms and templates.

To enable the time zone support, open your base.py settings file and make sure you have

Django also recommends to install¬†pytz, a Python package that allows for timezones calculations. Moreover, it’s also a package needed to use¬†Celery, a task queue manager that we will use latter in this tutorial. Let’s install it!

Activate your development environment and type:

and add it into the base.txt requirements file (you can see the version installed with pip freeze). Remember to install it too in your testing environment.

There are two different kind of datetime objects, the ones that are aware of the time zone and the ones that are not. You can use the datetime methods is_aware() and is_naive() to determine which one it is.

If we enable time zone support, all the datetime instances will be aware. Therefore, in order to interact with some saved datetime, we should create an aware datetime instance.

For example:

Moreover, if you have time zone support enabled, i.e. USE_TZ=True, there is a shortcut to obtain the current aware time:

However, as explained in the Django documentation, there is no way to determine the user time zone preferences through the HTTP header. What we will do is to ask the preferred time zone to the user and save it in his user profile. But that will come latter in this tutorial, sorry ūüėČ

Meanwhile, we will define a default time zone with the setting variable:

which is the time zone here in Barcelona ūüôā You can pick¬†your time zone from here.

So let’s see how to display the current time here in Barcelona (using the default time zone), the UTC time, and the time in New York.

Open your functional_tests/test_all_users.py and write the following test:

we will check that the Barcelona, UTC and New York times are different.

Run this test and see it fail because the element with an id of time-tz is missing. Open the index.html template and edit the third column:

Where I used the time filter to display only the time. Run the test again, and it will fail because it can’t find the¬†utc¬† filter. In your index.html file add at the top of the file:

Run your tests again and this time they will fail because¬†the views didn’t pass any¬†now¬†variable. Open the¬†views.py¬†and add this import at the beginning of the file:

and add the now variable into the home view:

Run your tests again, and they should pass!

Ok, this is the end of this part of the tutorial. Don’t forget to commit your changes:

The last command only if you want to push it in your cloud repository, like Bitbucket or GitHub.

 

In the next part of the tutorial, we’ll cover¬†Documentation. Yes, it may sound a boring subject to you, but I’m sure that you really appreciate a package with a understandable and well structured documentation, right?

Don’t miss it!

Do you like this tutorial? Don’t forget to share¬†it with your friends! ūüôā
Thanks!

Google+TwitterLinkedInFacebookReddit

Please, add +Marina Mele in your comments. This way I will get a notification email and I will answer you as soon as possible! :-)