What's the correct way to set up Django translation?

28.4k views Asked by At

I've got an issue with translations not working on Django 1.6. I've added this to my settings.py:

LANGUAGE_CODE = 'en-us'
ugettext = lambda s: s
LANGUAGES = (
    ('en', ugettext('English')),
    ('de', ugettext('German')),
)

Also added middlewares:

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

As well as to my *.py files whenever I'm using a string which shall be l10nd:

from django.utils.translation import ugettext_lazy as _

My templates start with:

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

And inside the template, I used the trans placeholder. E.g.

<h1>{% trans "Register a tank" %}</h1>

I have provided translations in locale/de/LC_MESSAGES/django.po:

msgid "Register a tank"
msgstr "Einen neuen Tank anmelden"

My browser is set to request German content first: Browser settings

What did I miss?

P.S. The project I'm currently fuzzy around is hosted on GitHub: https://github.com/frlan/blankspot

6

There are 6 answers

11
Omid Raha On BEST ANSWER

Add LOCALE_PATHS to settings.py and set it as below:

import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))

LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'locale'),
)

Note that LOCALE_PATHS must be a tuple (look at the comma at the end of the path).

Now based on LOCALE_PATHS, the locale folder should be in the root of your project.

And be sure that you run the commands django-admin.py makemessages -l de and django-admin.py compilemessages from the root of your project.

djPrj
  |
  +---> djPrj
  |
  +---> djApp
  |
  +---> locale
  |
  +---> templates

Also rearrange your MIDDLEWARE_CLASSES to be LocaleMiddleware after SessionMiddleware and before CommonMiddleware as mentioned here:

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    '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',
)

Restart your service (python manage.py runserver) and check again.


Just to ensure that your localization is applied to your Django admin page with the default django.mo file of Django, do the following test:

First in main urls.py of project replace patterns with i18n_patterns:

from django.conf.urls.i18n import i18n_patterns

urlpatterns = i18n_patterns('',
    url(r'^admin/', include(admin.site.urls)),
    # ...
)

Now go to the admin page with a de prefix, like: http://127.0.0.1:8000/de/admin/ And the admin page should be shown in German.

OK, are you able to see the admin page of Django in German?

Also check your view with the de prefix too.


According to your project code, some sentences are not in trans blocks. Put them as:

{% trans "your sentence" %}

Also you must use ugettext_lazy instead of ugettext in your code for views and models (Read here and here.)

Replace this:

from django.utils.translation import ugettext as _ 

with:

from django.utils.translation import ugettext_lazy as _

And now everything will work.

4
Nilesh On

Please set translated string in django.po and then use python manage.py compilemessages

for e.g 

#: path/to/python/module.py:23
msgid "Welcome to my site."
msgstr "put appropriate translated string here"

Suggestion-: You can use django-rosetta package to add translated string from UI interface. It is easy to add T-string from django-admin. https://github.com/mbi/django-rosetta

1
sk1p On

You need to enable the LocaleMiddleware in your settings, to tell Django to do language detection based on the browser-settings. Changing your language preferences effectly sets the Accept-Language header. You might need to check in an incognito window, because other means of language detection have a higher priority, such as the user's session and the django_language cookie.

0
miraculixx On

Check the cookies and the session -- according to How Django Discovers Language Preference, the process is this:

  1. language prefix in the URL, when using i18n_patterns in URLconf
  2. _language key in the current user's session
  3. django_language cookie (or as specified by settings.LANGUAGE_COOKIE_NAME)
  4. Accept-Language HTTP header (this is what your browser setting sends)
  5. settings.LANGUAGE_CODE

Since your browser settings are set to prefer 'de', I suspect the LocaleMiddleware must decide otherwise in one of the previous steps 1. - 3.

0
Super Kai - Kazuya Ito On

For example below, you can translate English to French. *I use Django 4.2.3.

First, create locale folder just under django-project as shown below. *You can see my answer explaining how to set templates:

django-project
 |-core
 |  |-settings.py
 |  └-urls.py
 |-my_app1
 |  |-views.py
 |  |-urls.py
 |  |-models.py
 |  |_admin.py
 |  └-apps.py
 |-my_app2
 |-templates
 |  └-index.html  
 └-locale # Here

Then, set core/settings.py as shown below. *LocaleMiddleware must be between SessionMiddleware and CommonMiddleware. 'en-us' is set to LANGUAGE_CODE by default in core/settings.py:

# "core/settings.py"

MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware', # Here
    'django.middleware.common.CommonMiddleware',
    ...
]

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True

from django.utils.translation import gettext_lazy as _

LANGUAGES = ( # Here
    ('en', _('English')),
    ('fr', _('French'))
)

And, Hello and World are translated with gettext() and outputted on console and index.html is rendered in test() in my_app1/views.py as shown below. *You can see my answer explaining where to use gettext() and gettext_lazy():

# "my_app1/views.py"

from django.shortcuts import render
from django.utils.translation import gettext as _

def test(request):
    print(_("Hello"), _("World")) # Here
    return render(request, 'index.html')

And, test() path is set to urlpatterns in my_app1/urls.py as shown below. *You can see my question and my answer explaining how to translate url in Django:

# "my_app1/urls.py"

from django.urls import path
from . import views

app_name = "my_app1"

urlpatterns = [
    path('', views.test, name="test") # Here
]

And, admin and my_app1 paths are set to urlpatterns with i18n_patterns() in core/urls.py as shown below. *You can see my question and my answer explaining prefix_default_language=False with i18n_patterns():

# "core/urls.py"

from django.contrib import admin
from django.urls import path, include
from django.conf.urls.i18n import i18n_patterns

urlpatterns = i18n_patterns(
    path('admin/', admin.site.urls), # Here
    path('my_app1/', include('my_app1.urls')) # Here
)

And, 3 tags are set in templates/index.py as shown below. *For the translation in Django Templates, {% load i18n %} is needed and {% translate %} or {% trans %} can translate:

# "templates/index.py"

{% load i18n %}

{% translate "Hello" %} {% trans "World" %}

And, name field and person model are translated in my_app1/models.py as shown below:

# "my_app1/models.py"

from django.db import models
from django.utils.translation import gettext_lazy as _

class Person(models.Model):
    first_name = models.CharField(
        max_length=20, verbose_name=_("first name") # Here
    )
    last_name = models.CharField(
        max_length=20, verbose_name=_("last name") # Here
    )

    class Meta:
        verbose_name = _('person') # Here
        verbose_name_plural = _('persons') # Here

And, this is my_app1/admin.py below:

# "my_app1/admin.py"

from django.contrib import admin
from .models import Person

@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
    pass

And, my_app1 is translated in my_app1/apps.py as shown below:

# "my_app1/apps.py"

from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _

class MyApp1Config(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'my_app1'
    verbose_name = _('my_app1') # Here

Now, run the command below. *You can see my answer explaining the command below:

django-admin makemessages -l fr

Then, django.po is created in locale/fr/LC_MESSAGES/ as shown below:

django-project
 |-core
 |  |-settings.py
 |  └-urls.py
 |-my_app1
 |  |-views.py
 |  |-urls.py
 |  |-models.py
 |  |_admin.py
 |  └-apps.py
 |-my_app2
 |-templates
 |  └-index.html  
 └-locale
    └-fr
       └-LC_MESSAGES
          └-django.po # Here

Then, add Bonjour, Monde, Anglais and Français to each msgstr "" respectively in locale/fr/LC_MESSAGES/django.po as shown below:

# "locale/fr/LC_MESSAGES/django.po"

...

#: .\core\settings.py:140
msgid "English"
msgstr "Anglais" # Here

#: .\core\settings.py:141
msgid "French"
msgstr "Français" # Here

#: .\my_app1\apps.py:7
msgid "my_app1"
msgstr "mon_app1" # Here

#: .\my_app1\models.py:6
msgid "first name"
msgstr "prénom"

#: .\my_app1\models.py:7
msgid "last name"
msgstr "nom de famille"

#: .\my_app1\models.py:13
msgid "person"
msgstr "personne"

#: .\my_app1\models.py:14
msgid "persons"
msgstr "personnes"

#: .\my_app1\views.py:5 .\templates\index.html:3
msgid "Hello"
msgstr "Bonjour" # Here

#: .\my_app1\views.py:5 .\templates\index.html:3
msgid "World"
msgstr "Monde" # Here

...

Then, run the command below to compile django.po to django.mo. *You can see my answer explaining the command below:

django-admin compilemessages

Then, django.po is compiled to django.mo in locale/fr/LC_MESSAGES/ as shown below:

django-project
 |-core
 |  |-settings.py
 |  └-urls.py
 |-my_app1
 |  |-views.py
 |  |-urls.py
 |  |-models.py
 |  |_admin.py
 |  └-apps.py
 |-my_app2
 |-templates
 |  └-index.html
 └-locale
    └-fr
       └-LC_MESSAGES
          |-django.po
          └-django.mo # Here

Now, http://localhost:8000/en/my_app1/ can show Hello World as shown below:

enter image description here

And, Hello World below is outputted on console:

Hello World

And, http://localhost:8000/fr/my_app1/ can show Bonjour Monde as shown below:

enter image description here

And, Bonjour Monde below is outputted on console:

Bonjour Monde

And, http://localhost:8000/en/admin/my_app1/person/add/ can show the English version of Django Admin including the app, model and field labels as shown below:

enter image description here

And, http://localhost:8000/fr/admin/my_app1/person/add/ can show the French version of Django Admin including the app, model and field labels as shown below:

enter image description here

In addition, you set English as the 1st language in your browser as shown below. *This is Google Chrome:

enter image description here

Now, if you access http://localhost:8000/my_app1/, then you are redirected to http://localhost:8000/en/my_app1/ as shown below:

enter image description here

And, you set French as the 1st language in your browser as shown below.

enter image description here

Now, if you access http://localhost:8000/my_app1/, then you are redirected to http://localhost:8000/fr/my_app1/ as shown below:

enter image description here

In addition again, you can remove i18n_patterns() from admin and my_app1 paths in core/urls.py as shown below:

# "core/urls.py"

from django.contrib import admin
from django.urls import path, include
# from django.conf.urls.i18n import i18n_patterns

urlpatterns = [
    path('admin/', admin.site.urls),
    path('my_app1/', include('my_app1.urls'))
]

# urlpatterns = i18n_patterns(
#     path('admin/', admin.site.urls), # Here
#     path('my_app1/', include('my_app1.urls')) # Here
# )

Then, you set English as the 1st language in your browser as shown below:

enter image description here

Now, http://localhost:8000/my_app1/ can show Hello World as shown below:

enter image description here

And, you set French as the 1st language in your browser as shown below.

enter image description here

Now, http://localhost:8000/my_app1/ can show Bonjour Monde as shown below:

enter image description here

Lastly, you can easily create i18n switcher following my answer and my answer explaining how to create i18n switcher for Django and Django Admin respectively.

0
James J. Ye On

In my case, I used en-gb as the parameter to run

django-admin.py makemessages -l en-gb

Instead, it should be en_GB.

django-admin.py makemessages -l en_GB