Let’s be honest: No developer wakes up in the morning and thinks, “Oh goody! Today I get to internationalize my giant website with tons of content and files. I bet supporting right-to-left languages is going to be a blast.”
However, I’m here to tell you that it’s not nearly as bad as you would expect.
In fact, Django makes it downright easy to do. Unfortunately, there’s not a lot of information on the web about internationalizing (also known as i18n) in Django besides the official documentation. Hopefully these tips and tricks will be useful for you.
What Django gives you
- Preferred language of the user, and uses the files you generate to serve translated and localized templates.
- Gives you tools for translating strings in both HTML files (i.e. templates) and Javascript files.
- Gives you helpful variables in your templates to help you serve the correct content for left-to-right and right-to-left users.
Step 1: Enabling Localization in Django
Create a folder in your site root’s directory (or elsewhere if you see fit), called locale
. This will contain a folder for each language, as well as the files used for translation themselves.
Open up your settings.py
and include or update the following settings:
# Path to locale folder
LOCALE_PATHS = (
'/path/to/folder/locale',
)
# The language your website is starting in
LANGUAGE_CODE = 'en'
# The languages you are supporting
LANGUAGES = (
('en', 'English'), # You need to include your LANGUAGE_CODE language
('fa', 'Farsi'),
('de', 'German'),
)
# Use internationalization
USE_I18N = True
# Use localization
USE_L10N = True
Also, in each of your views (e.g. in views.py
), you should be setting the request language as a session. For example:
if hasattr(request.user, 'lang'):
request.session['django_language'] = request.user.lang
Step 2: Internationalizing your Django content
This is really the easy part. Chances are, you’ve got a folder in your Django app called “templates”. Inside, you’ve got HTML, some variables, and whatnot. All you have to do is go through and mark up the strings that need to be translated, like so:
{% trans "My English" %}
{% trans myvar %}
You get a lot of flexibility here, as described in the documentation. Essentially what happens is that you label all of your strings that should be translated, and then Django generates a handy file that your translator can use to localize the interface.
Just make sure that at the top of any template you want localized, you actually load the i18n library.
{% load i18n %}
Test it out You only have to translate a string or two in order to see whether it’s working. Create your translation files using the following command:
$ django-admin.py makemessages --locale=de --extension=html --ignore=env --ignore=*.py
Explanation of the options:
--locale=de
Change this from de to whatever locale you’re going for.--extension=html
Tells the django engine only to look for .html files.--ignore=env
In my app, env/ is the folder where my virtual environment exists. I probably don’t want to localize everything that exists in this folder, so we can ignore it.--ignore=*.py
For some reason, django keeps trying to localize some of my python files that exist at the project root. To avoid this, I explicitly ignore such files.
Once you’ve run this django-admin.py
command, you should take a look inside your locale/
directory. If your app exists at something like /opt/app/
, you’ll find a file structure like this:
/opt/app --- /locale ------ /LC_MESSAGES --------- /de ------------ django.po
And within each of these django.po
files, you’ll find pairs of a string, and then a space for a translation, as so:
# path/to/templates/blah.html:123
msgid "My English."
msgstr ""
Obviously, if you’re in /opt/app/locale/LC_MESSAGES/de/django.po
you’d better provide a German translation as a msgstr
.
Now, compile your messages and we’ll see what we get!
$ django-admin.py compilemessages
Next to each django.po
file, you’ll now also have a django.mo
file. This is the binary file that Django actually uses to fetch translations in real time.
Restart uWSGI and your web server.
Add the language you just localized for to your preferred languages in your browser settings, and pull it to first place. In Chrome, this is Preferences » Advanced » Manage Languages.
When you reload your site, you should see that your string has been translated! Anything that you haven’t translate will remain visible in its original language (in my case, English).
Step 3: Translation Javascript (Javascript itself)
Open up your urls.py
. Append the following:
# 'Packages' should include the names of the app or apps you wish to localize
js_info_dict = {
'packages': ('app',)
}
And in your urlpatterns
, include:
url(r'^jsi8n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
Now, in your base template (whichever manages loading your javascript) and place this script first:
http://%%20url%20'django.views.i18n.javascript_catalog'%20%
Now you can go into any javascript file and simply place gettext("")
around any string and that string can be localized. For example:
this.$el.find('.a')[0].attr('title', gettext('Show Resources'));
Generating the Javascript messages file Just as before, when you ran the django-admin.py
command to gather all the strings needing translations in your html templates, you can do the same in your javascript files.
$ django-admin.py makemessages -d djangojs --locale de --ignore=env
Again, specify the locale and ignore the files inside my virtual environment. Now, look at the files you have in your locale/
subdirectories.
/opt/app --- /locale --- /LC_MESSAGES ------ /de --------- django.po --------- django.mo --------- djangojs.po
Simply open up djangojs.po
, translate a string, and run django-admin.py compilemessages
again. You’ll find, as you probably expected, a new file called djangojs.mo
. As before, restart uWSGI and your server, and spin it up in the browser. Again, be sure that you’ve got your test language set as your preferred language in your browser settings.
Step 3b: Translating Javascript Templates (Underscore)
This is where things get a little more interesting. The critical point is this: We want our underscore templates to be served through Django, not through our web server directly (e.g. through Apache or Nginx). These are the steps I took to achieve this:
- Move my underscore templates out of my
static/
folder, and into mytemplates/
folder. - Write a urlpattern that will cause my underscore templates to be run through the django template engine first.
- Update the references to templates in my Javascript (I use RequireJS and the text plugin).
1. Move Underscore Templates Previously, my project structure was something like this:
app/ — static/ —— css/ —— js/ ———- views/ ———- templates/ ————– underscore-template.html — templates/ —— django-template.html
And I had Nginx serving everything inside of static/
, well, directly, using the following directive in my Nginx conf file:
location /static {
alias /opt/app/static;
}
Now, instead of this, I want Django to do its magic before Backbone and Underscore go to town on the templates. So I create a folder inside app/templates/
called js/
. I move all my underscore templates here. So now I have:
app/ --- static/ ------ css/ ------ js/ --------- views/ --- templates/ ------ js/ --------- underscore-template.html --------- django-template.html
2. Write a urlpattern Now, I’m not positive this is the best way to do this, but it does work. Open up your urls.py
and add this line:
url(r'^templates/(?P<path>w+)', 'web.views.static'),
What happens now is that whenever Django receives a request for a URL that looks likemysite.com/templates/some/thing.html, it assigns some/thing.html
to a variable path
, and passes that to our web view. So now I open up app/web/views.py
and append this code:
def static(request, path):
# Update this to use os.path
directory = '/opt/app/' + request.META['REQUEST_URI'];
template = loader.get_template(directory)
# This allows the user to set their language
if hasattr(request.user, 'lang'):
request.session['django_language'] = request.user.lang
# I use this email_hash to generate gravatars, incidentally
context = RequestContext(request, {
'email_hash': hashlib.md5(request.user.email).hexdigest() if request.user.is_authenticated() else ''
})
return HttpResponse(template.render(context))
Now, we’re taking whatever request it was, grabbing that file, and passing it throughtemplate.render
. If needed, add this folder to your settings.py
:
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
'/opt/app/templates/',
'/opt/app/templates/js'
)
Now you can go into any of your underscore template files and mark them up using typical django syntax. Just make sure you remember to include {% load i18n %}
at the top of your underscore templates. For example:
{% load i18n %}
<!-- Page of Greek text for Reader view -->
<!-- Page header -->
<h1><%= work %> <small>{% trans "by" %} <%= author %>{% trans "," %} <a href="#" data-toggle="tooltip" title="{% trans 'Jump to another section' %}">section</a></small></h1>
<hr>
<!-- Greek goes here! -->
<span class="page-content">
<% _.each(words, function(word) { %>
<% if (word.get('sentenceCTS') == cts) { %>
<span lang="<%= word.get('lang') %>" data-cts="<%= word.get('wordCTS') %>" class="<% if (word.get('value').match(/[.,-·/#!$%^&*;:{}=-_`~()]/)) print('punct'); %>"><%= word.get('value') %></span>
<% } %>
<% }); %>
</span>
</div>
In the long run, it may be worth your time to simply switch your html templates purely to Django. However, since the syntax of Underscore and Django don’t clash, it’s a viable solution as far as I’ve experienced.
Once you’ve marked up your underscore templates, simply re-run the same django_admin.py makemessages
command as before.
Just don’t forget to go into your javascript files and change the paths where you’re importing your templates from, so they’re no longer pointing to a static directory. For example:
define(['jquery', 'underscore', 'backbone', 'text!/templates/js/underscore-template.html'], function($, _, Backbone, Template) {
var View = Backbone.View.extend({
tagName: 'div',
template: _.template(Template),
render: function() {
this.$el.html(this.template(this.model));
return this;
}
});
return View;
});
Supporting bidirectional languages
So far, I have had great success with the techniques suggested in this blogpost: RTL CSS with Sass. I’ll just give you a couple of pointers on how to make it easy to implement this with Django.
First, I installed the set_var template tag. This is because I want to use some of the usefulget_language
functions that Django makes available to me. Alternatively, you could probably clean this up by putting this logic in your views.py
.
Then, in my app/templates/base.html
, I make use of this template tag and template inheritance as so:
{% load i18n %}
{% load set_var %}
{% get_current_language_bidi as LANGUAGE_BIDI %}
{% if LANGUAGE_BIDI %}
{% set dir = "rtl" %}
{% else %}
{% set dir = "ltr" %}
{% endif %}
<!DOCTYPE html>
<html dir="{{ dir }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
{% trans "My app" %}
</title>
{% block css %}
<link href="/static/css/{{ css_file }}.{{ dir }}.css" rel="stylesheet">
{% endblock %}
http://%%20url%20'django.views.i18n.javascript_catalog'%20%
/static/js/lib/require.js
var csrf_token = "{{ csrf_token }}"; var locale = "{{ LANGUAGE_CODE }}"; var dir = "{{ dir }}";
</head>
<body>
{% block content %} {% endblock %}
</body>
</html>
What do we have here?
- We’re using Django to get the direction our page is – either ltr or rtl.
- We’re making it possible to replace the CSS file based on the page we’re on and the text direction.
- We make a couple of variables global (eek!) for use in our javascript.
Now, you can take any page which inherits from your base template, and set the css_file. For example:
{% extends "base.html" %}
{# Determine which CSS file to load #}
{% block css %}
{% with 'generic' as css_file %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block content %}
<r;!-- Content here -->
{% endblock %}
Note: This assumes that you are generating your CSS files with a command such as this:
$ sass generic.scss generic.ltr.css
And that inside of generic.scss
you’ve got an @import "directional"
wherein you switch the direction between LTR and RTL in order to generate your sets of CSS.
And that’s a wrap!
It’s essentially everything you need to internationalize your Django website and get django to do a first pass over your underscore templates. If you’ve got suggestions for improving this work flow, by all means, pass them my way! I hope this helps give you some ideas on how to use Django’s built in internationalization and localization tools to make your life easier 🙂
Let’s be honest: No developer wakes up in the morning and thinks, “Oh goody! Today I get to internationalize my giant website with tons of content and files. I bet supporting right-to-left languages is going to be a blast.”
However, I’m here to tell you that it’s not nearly as bad as you would expect.
In fact, Django makes it downright easy to do. Unfortunately, there’s not a lot of information on the web about internationalizing (also known as i18n) in Django besides the official documentation. Hopefully these tips and tricks will be useful for you.
What Django gives you
- Preferred language of the user, and uses the files you generate to serve translated and localized templates.
- Gives you tools for translating strings in both HTML files (i.e. templates) and Javascript files.
- Gives you helpful variables in your templates to help you serve the correct content for left-to-right and right-to-left users.
Step 1: Enabling Localization in Django
Create a folder in your site root’s directory (or elsewhere if you see fit), called locale
. This will contain a folder for each language, as well as the files used for translation themselves.
Open up your settings.py
and include or update the following settings:
# Path to locale folder
LOCALE_PATHS = (
'/path/to/folder/locale',
)
# The language your website is starting in
LANGUAGE_CODE = 'en'
# The languages you are supporting
LANGUAGES = (
('en', 'English'), # You need to include your LANGUAGE_CODE language
('fa', 'Farsi'),
('de', 'German'),
)
# Use internationalization
USE_I18N = True
# Use localization
USE_L10N = True
Also, in each of your views (e.g. in views.py
), you should be setting the request language as a session. For example:
if hasattr(request.user, 'lang'):
request.session['django_language'] = request.user.lang
Step 2: Internationalizing your Django content
This is really the easy part. Chances are, you’ve got a folder in your Django app called “templates”. Inside, you’ve got HTML, some variables, and whatnot. All you have to do is go through and mark up the strings that need to be translated, like so:
{% trans "My English" %}
{% trans myvar %}
You get a lot of flexibility here, as described in the documentation. Essentially what happens is that you label all of your strings that should be translated, and then Django generates a handy file that your translator can use to localize the interface.
Just make sure that at the top of any template you want localized, you actually load the i18n library.
{% load i18n %}
Test it out You only have to translate a string or two in order to see whether it’s working. Create your translation files using the following command:
$ django-admin.py makemessages --locale=de --extension=html --ignore=env --ignore=*.py
Explanation of the options:
--locale=de
Change this from de to whatever locale you’re going for.--extension=html
Tells the django engine only to look for .html files.--ignore=env
In my app, env/ is the folder where my virtual environment exists. I probably don’t want to localize everything that exists in this folder, so we can ignore it.--ignore=*.py
For some reason, django keeps trying to localize some of my python files that exist at the project root. To avoid this, I explicitly ignore such files.
Once you’ve run this django-admin.py
command, you should take a look inside your locale/
directory. If your app exists at something like /opt/app/
, you’ll find a file structure like this:
/opt/app --- /locale ------ /LC_MESSAGES --------- /de ------------ django.po
And within each of these django.po
files, you’ll find pairs of a string, and then a space for a translation, as so:
# path/to/templates/blah.html:123
msgid "My English."
msgstr ""
Obviously, if you’re in /opt/app/locale/LC_MESSAGES/de/django.po
you’d better provide a German translation as a msgstr
.
Now, compile your messages and we’ll see what we get!
$ django-admin.py compilemessages
Next to each django.po
file, you’ll now also have a django.mo
file. This is the binary file that Django actually uses to fetch translations in real time.
Restart uWSGI and your web server.
Add the language you just localized for to your preferred languages in your browser settings, and pull it to first place. In Chrome, this is Preferences » Advanced » Manage Languages.
When you reload your site, you should see that your string has been translated! Anything that you haven’t translate will remain visible in its original language (in my case, English).
Step 3: Translation Javascript (Javascript itself)
Open up your urls.py
. Append the following:
# 'Packages' should include the names of the app or apps you wish to localize
js_info_dict = {
'packages': ('app',)
}
And in your urlpatterns
, include:
url(r'^jsi8n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
Now, in your base template (whichever manages loading your javascript) and place this script first:
<script type="text/javascript" src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
Now you can go into any javascript file and simply place gettext("")
around any string and that string can be localized. For example:
this.$el.find('.a')[0].attr('title', gettext('Show Resources'));
Generating the Javascript messages file Just as before, when you ran the django-admin.py
command to gather all the strings needing translations in your html templates, you can do the same in your javascript files.
$ django-admin.py makemessages -d djangojs --locale de --ignore=env
Again, specify the locale and ignore the files inside my virtual environment. Now, look at the files you have in your locale/
subdirectories.
/opt/app --- /locale --- /LC_MESSAGES ------ /de --------- django.po --------- django.mo --------- djangojs.po
Simply open up djangojs.po
, translate a string, and run django-admin.py compilemessages
again. You’ll find, as you probably expected, a new file called djangojs.mo
. As before, restart uWSGI and your server, and spin it up in the browser. Again, be sure that you’ve got your test language set as your preferred language in your browser settings.
Step 3b: Translating Javascript Templates (Underscore)
This is where things get a little more interesting. The critical point is this: We want our underscore templates to be served through Django, not through our web server directly (e.g. through Apache or Nginx). These are the steps I took to achieve this:
- Move my underscore templates out of my
static/
folder, and into mytemplates/
folder. - Write a urlpattern that will cause my underscore templates to be run through the django template engine first.
- Update the references to templates in my Javascript (I use RequireJS and the text plugin).
1. Move Underscore Templates Previously, my project structure was something like this:
app/ — static/ —— css/ —— js/ ———- views/ ———- templates/ ————– underscore-template.html — templates/ —— django-template.html
And I had Nginx serving everything inside of static/
, well, directly, using the following directive in my Nginx conf file:
location /static {
alias /opt/app/static;
}
Now, instead of this, I want Django to do its magic before Backbone and Underscore go to town on the templates. So I create a folder inside app/templates/
called js/
. I move all my underscore templates here. So now I have:
app/ --- static/ ------ css/ ------ js/ --------- views/ --- templates/ ------ js/ --------- underscore-template.html --------- django-template.html
2. Write a urlpattern Now, I’m not positive this is the best way to do this, but it does work. Open up your urls.py
and add this line:
url(r'^templates/(?P<path>w+)', 'web.views.static'),
What happens now is that whenever Django receives a request for a URL that looks likemysite.com/templates/some/thing.html, it assigns some/thing.html
to a variable path
, and passes that to our web view. So now I open up app/web/views.py
and append this code:
def static(request, path):
# Update this to use os.path
directory = '/opt/app/' + request.META['REQUEST_URI'];
template = loader.get_template(directory)
# This allows the user to set their language
if hasattr(request.user, 'lang'):
request.session['django_language'] = request.user.lang
# I use this email_hash to generate gravatars, incidentally
context = RequestContext(request, {
'email_hash': hashlib.md5(request.user.email).hexdigest() if request.user.is_authenticated() else ''
})
return HttpResponse(template.render(context))
Now, we’re taking whatever request it was, grabbing that file, and passing it throughtemplate.render
. If needed, add this folder to your settings.py
:
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
'/opt/app/templates/',
'/opt/app/templates/js'
)
Now you can go into any of your underscore template files and mark them up using typical django syntax. Just make sure you remember to include {% load i18n %}
at the top of your underscore templates. For example:
{% load i18n %}
<!-- Page of Greek text for Reader view -->
<div class="page">
<!-- Page corner, functions as navigation -->
<div class="corner <%= side %>">
<a href="#" data-toggle="tooltip" title="<% side == 'left' ? print('Previous') : print('Next') %> Page" data-placement="<% side == 'left' ? print('right') : print('left') %>"></a>
</div>
<!-- Page header -->
<h1><%= work %> <small>{% trans "by" %} <%= author %>{% trans "," %} <a href="#" data-toggle="tooltip" title="{% trans 'Jump to another section' %}">section</a></small></h1>
<hr>
<!-- Greek goes here! -->
<span class="page-content">
<% _.each(words, function(word) { %>
<% if (word.get('sentenceCTS') == cts) { %>
<span lang="<%= word.get('lang') %>" data-cts="<%= word.get('wordCTS') %>" class="<% if (word.get('value').match(/[.,-·/#!$%^&*;:{}=-_`~()]/)) print('punct'); %>"><%= word.get('value') %></span>
<% } %>
<% }); %>
</span>
</div>
In the long run, it may be worth your time to simply switch your html templates purely to Django. However, since the syntax of Underscore and Django don’t clash, it’s a viable solution as far as I’ve experienced.
Once you’ve marked up your underscore templates, simply re-run the same django_admin.py makemessages
command as before.
Just don’t forget to go into your javascript files and change the paths where you’re importing your templates from, so they’re no longer pointing to a static directory. For example:
define(['jquery', 'underscore', 'backbone', 'text!/templates/js/underscore-template.html'], function($, _, Backbone, Template) {
var View = Backbone.View.extend({
tagName: 'div',
template: _.template(Template),
render: function() {
this.$el.html(this.template(this.model));
return this;
}
});
return View;
});
Supporting bidirectional languages
So far, I have had great success with the techniques suggested in this blogpost: RTL CSS with Sass. I’ll just give you a couple of pointers on how to make it easy to implement this with Django.
First, I installed the set_var template tag. This is because I want to use some of the usefulget_language
functions that Django makes available to me. Alternatively, you could probably clean this up by putting this logic in your views.py
.
Then, in my app/templates/base.html
, I make use of this template tag and template inheritance as so:
{% load i18n %}
{% load set_var %}
{% get_current_language_bidi as LANGUAGE_BIDI %}
{% if LANGUAGE_BIDI %}
{% set dir = "rtl" %}
{% else %}
{% set dir = "ltr" %}
{% endif %}
<!DOCTYPE html>
<html dir="{{ dir }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
{% trans "My app" %}
</title>
{% block css %}
<link href="/static/css/{{ css_file }}.{{ dir }}.css" rel="stylesheet">
{% endblock %}
<script type="text/javascript" src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
<script data-main="/static/js/main" src="/static/js/lib/require.js"></script>
<script>var csrf_token = "{{ csrf_token }}"; var locale = "{{ LANGUAGE_CODE }}"; var dir = "{{ dir }}"; </script>
</head>
<body>
{% block content %} {% endblock %}
</body>
</html>
What do we have here?
- We’re using Django to get the direction our page is – either ltr or rtl.
- We’re making it possible to replace the CSS file based on the page we’re on and the text direction.
- We make a couple of variables global (eek!) for use in our javascript.
Now, you can take any page which inherits from your base template, and set the css_file. For example:
{% extends "base.html" %}
{# Determine which CSS file to load #}
{% block css %}
{% with 'generic' as css_file %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block content %}
<r;!-- Content here -->
{% endblock %}
Note: This assumes that you are generating your CSS files with a command such as this:
$ sass generic.scss generic.ltr.css
And that inside of generic.scss
you’ve got an @import "directional"
wherein you switch the direction between LTR and RTL in order to generate your sets of CSS.
And that’s a wrap!
It’s essentially everything you need to internationalize your Django website and get django to do a first pass over your underscore templates. If you’ve got suggestions for improving this work flow, by all means, pass them my way! I hope this helps give you some ideas on how to use Django’s built in internationalization and localization tools to make your life easier 🙂
Django Tutorial | Internationalization & Localization
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:
1$ mkdir taskbuster/locale
Next, open your settings/base.py file and make sure you have
1USE_I18N = True
and the template context processor django.template.context_processors.i18n is inside the TEMPLATES[‘OPTIONS’][‘context_processors’] setting:
1234567891011TEMPLATES = [ { … ‘OPTIONS’: { ‘context_processors’: [ … ‘django.template.context_processors.i18n’, ], }, },]
Note: You can also find the value of a specific setting by using the Django shell. For example:
123$ python manage.py shell>>> from django.conf import settings>>> settings.TEMPLATES
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:
1234567MIDDLEWARE_CLASSES = ( … ‘django.contrib.sessions.middleware.SessionMiddleware’, ‘django.middleware.locale.LocaleMiddleware’, ‘django.middleware.common.CommonMiddleware’, …)
Next, specify the languages you want to use:
12345from django.utils.translation import ugettext_lazy as _LANGUAGES = ( (‘en’, _(‘English’)), (‘ca’, _(‘Catalan’)),)
We will use English and Catalan (but feel free to put the languages you want, you can find its codes
). 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:
1LANGUAGE_CODE = ‘en-us’
And finally, specify the locale folder that we created before:
123LOCALE_PATHS = ( os.path.join(BASE_DIR, ‘locale’),)
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 atmysite.com/robots.txt and mysite.com/humans.txt to find them).
One way to implement this is with the following urls.py file:
123456789101112131415# -*- coding: utf-8 -*-from django.conf.urls import include, urlfrom django.contrib import adminfrom django.conf.urls.i18n import i18n_patternsfrom .views import home, home_files urlpatterns = [ url(r’^(?P<filename>(robots.txt)|(humans.txt))$’, home_files, name=’home-files’),] urlpatterns += i18n_patterns( url(r’^$’, home, name=’home’), url(r’^admin/’, include(admin.site.urls)),)
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,
1$ python mange.py test taskbuster.test
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:
1from django.utils.translation import activate
and next, activate the selected language just after the test declaration. For example:
1234def test_uses_index_template(self): activate(‘en’) response = self.client.get(reverse(“home”)) self.assertTemplateUsed(response, “taskbuster/index.html”)
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:
12345678
{% trans “Welcome to TaskBuster!”%}
{% blocktrans %}TaskBuster is a simple Task manager that helps you organize your daylife. You can create todo lists, periodic tasks and more! If you want to learn how it was created, or create it yourself!, check www.django-tutorial.com{% endblocktrans %}
</div></div>
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:
1$ python manage.py makemessages -l ca
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:
1$ ./configure
to configure the installation for your system. Next, type
1$ make
to compile the package. I always wonder why some installations print all that awful code on your terminal!
If you want, use
1$ make check
to run package tests before installing them, to see that everything works. Finally, run
1$ make install
to install the package.
Okey! Let’s go back to our developing environment, and try to generate our message file!
1$ python manage.py makemessages -l ca
Yes! It worked!
Now go to the taskbuster/locale folder to see what’s in there.
12$ cd taskbuster/locale$ ls
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:
12msgid “Welcome to TaskBuster!"msgstr "Benvingut a TaskBuster!”
And with a paragraph, you have to be careful to start and end each line with “”:
12345678910msgid “”“TaskBuster is a simple Task manager that helps you organize your daylife. </”“br> You can create todo lists, periodic tasks and more! </br></br> If you ”“want to learn how it was created, or create it yourself!, check www.django-”“tutorial.com"msgstr ”“"TaskBuster és un administrador senzill de tasques que t’ajuda a administrar ”“el teu dia a dia. </br> Pots crear llistes de coses pendents, tasques ”“periòdiques i molt més! </br></br> Si vols apendre com s’ha creat, o”“crear-lo tu mateix!, visita la pàgina <a href=’http://www.marinamele.com/taskbuster-django-tutorial’ target=_’blank’>Taskbuster Django Tutorial</a>.”
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:
1$ python manage.py compilemessages -l ca
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:
1234567def test_internationalization(self): for lang, h1_text in [(‘en’, ‘Welcome to TaskBuster!’), (‘ca’, ‘Benvingut a TaskBuster!’)]: activate(lang) self.browser.get(self.get_full_url(“home”)) h1 = self.browser.find_element_by_tag_name(“h1”) self.assertEqual(h1.text, h1_text)
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!
Django Tutorial | Internationalization & Localization
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:
1$ mkdir taskbuster/locale
Next, open your settings/base.py file and make sure you have
1USE_I18N = True
and the template context processor django.template.context_processors.i18n is inside the TEMPLATES[‘OPTIONS’][‘context_processors’] setting:
1234567891011TEMPLATES = [ { … ‘OPTIONS’: { ‘context_processors’: [ … ‘django.template.context_processors.i18n’, ], }, },]
Note: You can also find the value of a specific setting by using the Django shell. For example:
123$ python manage.py shell>>> from django.conf import settings>>> settings.TEMPLATES
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:
1234567MIDDLEWARE_CLASSES = ( … ‘django.contrib.sessions.middleware.SessionMiddleware’, ‘django.middleware.locale.LocaleMiddleware’, ‘django.middleware.common.CommonMiddleware’, …)
Next, specify the languages you want to use:
12345from django.utils.translation import ugettext_lazy as _LANGUAGES = ( (‘en’, _(‘English’)), (‘ca’, _(‘Catalan’)),)
We will use English and Catalan (but feel free to put the languages you want, you can find its codes
). 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:
1LANGUAGE_CODE = ‘en-us’
And finally, specify the locale folder that we created before:
123LOCALE_PATHS = ( os.path.join(BASE_DIR, ‘locale’),)
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 atmysite.com/robots.txt and mysite.com/humans.txt to find them).
One way to implement this is with the following urls.py file:
123456789101112131415# -*- coding: utf-8 -*-from django.conf.urls import include, urlfrom django.contrib import adminfrom django.conf.urls.i18n import i18n_patternsfrom .views import home, home_files urlpatterns = [ url(r’^(?P<filename>(robots.txt)|(humans.txt))$’, home_files, name=’home-files’),] urlpatterns += i18n_patterns( url(r’^$’, home, name=’home’), url(r’^admin/’, include(admin.site.urls)),)
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,
1$ python mange.py test taskbuster.test
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:
1from django.utils.translation import activate
and next, activate the selected language just after the test declaration. For example:
1234def test_uses_index_template(self): activate(‘en’) response = self.client.get(reverse(“home”)) self.assertTemplateUsed(response, “taskbuster/index.html”)
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:
12345678<div class=“jumbotron”> <div class=“container”> <h1>{% trans “Welcome to TaskBuster!”%}</h1> <p>{% blocktrans %}TaskBuster is a simple Task manager that helps you organize your daylife. </br> You can create todo lists, periodic tasks and more! </br></br> If you want to learn how it was created, or create it yourself!, check www.django-tutorial.com{% endblocktrans %}</p> <p><a class=“btn btn-primary btn-lg” role=“button”>Learn more »</a></p> </div> </div></div>
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:
1$ python manage.py makemessages -l ca
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:
1$ ./configure
to configure the installation for your system. Next, type
1$ make
to compile the package. I always wonder why some installations print all that awful code on your terminal!
If you want, use
1$ make check
to run package tests before installing them, to see that everything works. Finally, run
1$ make install
to install the package.
Okey! Let’s go back to our developing environment, and try to generate our message file!
1$ python manage.py makemessages -l ca
Yes! It worked!
Now go to the taskbuster/locale folder to see what’s in there.
12$ cd taskbuster/locale$ ls
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:
12msgid “Welcome to TaskBuster!"msgstr "Benvingut a TaskBuster!”
And with a paragraph, you have to be careful to start and end each line with “”:
12345678910msgid “”“TaskBuster is a simple Task manager that helps you organize your daylife. </”“br> You can create todo lists, periodic tasks and more! </br></br> If you ”“want to learn how it was created, or create it yourself!, check www.django-”“tutorial.com"msgstr ”“"TaskBuster és un administrador senzill de tasques que t’ajuda a administrar ”“el teu dia a dia. </br> Pots crear llistes de coses pendents, tasques ”“periòdiques i molt més! </br></br> Si vols apendre com s’ha creat, o”“crear-lo tu mateix!, visita la pàgina <a href=’http://www.marinamele.com/taskbuster-django-tutorial’ target=_’blank’>Taskbuster Django Tutorial</a>.”
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:
1$ python manage.py compilemessages -l ca
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:
1234567def test_internationalization(self): for lang, h1_text in [(‘en’, ‘Welcome to TaskBuster!’), (‘ca’, ‘Benvingut a TaskBuster!’)]: activate(lang) self.browser.get(self.get_full_url(“home”)) h1 = self.browser.find_element_by_tag_name(“h1”) self.assertEqual(h1.text, h1_text)
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!
HTTP Cache 튜토리얼 | Knowledge Logger
ETag란?
컨텐트 기반의 캐쉬를 위해 특정 컨텐트에 대해 MD5 해쉬 등의 방법으로 다이제스트를 생성하면 해당 컨텐츠가 변경되었는지를 판별할수있는데, 이러한 다이제스트 값을 ETag(entity tag)로 사용한다.4
대부분의 HTTP서버들은 정적인 컨텐츠(파일이나, 내용이 변하지 않는 웹페이지 등)에 대해 ETag와 Last-Modified를 생성하여 헤더에 추가하도록 설정되어있으며, 이를 HTTP서버 관리자가 원하는대로 수정하는 것이 가능하다.
클라이언트가 특정 URL을 서버에 요청을 하면 웹서버는 해당 요청에 대한 응답을 하게되는데 해당 응답 헤더에 ETag, Last-Modified 항목이 포함되어있다. 클라이언트가 동일한 URL로 다시 요청을 할 경우 클라이언트는 요청 헤더의 If-None-Match필드에 ETag값을 포함시켜서 보내게 되고, 서버는 클라이언트에서 보내온 ETag와 현재 컨텐츠의 ETag를 비교하여 유효성을 검사한다.
ETag가 동일한 경우 응답 바디없이 헤더만 HTTP 304 Not modified를 리턴하고, ETag가 다른것이 발견되면 전체응답을 완전히 재전송하게된다.
HTTP Cache 튜토리얼 | Knowledge Logger
ETag란?
컨텐트 기반의 캐쉬를 위해 특정 컨텐트에 대해 MD5 해쉬 등의 방법으로 다이제스트를 생성하면 해당 컨텐츠가 변경되었는지를 판별할수있는데, 이러한 다이제스트 값을 ETag(entity tag)로 사용한다.4
대부분의 HTTP서버들은 정적인 컨텐츠(파일이나, 내용이 변하지 않는 웹페이지 등)에 대해 ETag와 Last-Modified를 생성하여 헤더에 추가하도록 설정되어있으며, 이를 HTTP서버 관리자가 원하는대로 수정하는 것이 가능하다.
클라이언트가 특정 URL을 서버에 요청을 하면 웹서버는 해당 요청에 대한 응답을 하게되는데 해당 응답 헤더에 ETag, Last-Modified 항목이 포함되어있다. 클라이언트가 동일한 URL로 다시 요청을 할 경우 클라이언트는 요청 헤더의 If-None-Match필드에 ETag값을 포함시켜서 보내게 되고, 서버는 클라이언트에서 보내온 ETag와 현재 컨텐츠의 ETag를 비교하여 유효성을 검사한다.
ETag가 동일한 경우 응답 바디없이 헤더만 HTTP 304 Not modified를 리턴하고, ETag가 다른것이 발견되면 전체응답을 완전히 재전송하게된다.
HTTP Cache 튜토리얼 | Knowledge Logger
ETag란?
컨텐트 기반의 캐쉬를 위해 특정 컨텐트에 대해 MD5 해쉬 등의 방법으로 다이제스트를 생성하면 해당 컨텐츠가 변경되었는지를 판별할수있는데, 이러한 다이제스트 값을 ETag(entity tag)로 사용한다.4
대부분의 HTTP서버들은 정적인 컨텐츠(파일이나, 내용이 변하지 않는 웹페이지 등)에 대해 ETag와 Last-Modified를 생성하여 헤더에 추가하도록 설정되어있으며, 이를 HTTP서버 관리자가 원하는대로 수정하는 것이 가능하다.
클라이언트가 특정 URL을 서버에 요청을 하면 웹서버는 해당 요청에 대한 응답을 하게되는데 해당 응답 헤더에 ETag, Last-Modified 항목이 포함되어있다. 클라이언트가 동일한 URL로 다시 요청을 할 경우 클라이언트는 요청 헤더의 If-None-Match필드에 ETag값을 포함시켜서 보내게 되고, 서버는 클라이언트에서 보내온 ETag와 현재 컨텐츠의 ETag를 비교하여 유효성을 검사한다.
ETag가 동일한 경우 응답 바디없이 헤더만 HTTP 304 Not modified를 리턴하고, ETag가 다른것이 발견되면 전체응답을 완전히 재전송하게된다.