Поддержка нескольких языков в Django

Django предлагает поддержку нескольких языков из коробки. На самом деле, Django переведен более чем на 100 языков. В этом уроке мы рассмотрим, как добавить поддержку нескольких языков в ваш проект Django.

Цели

К концу этого урока вы должны уметь:

  1. Объясните разницу между интернационализацией и локализацией
  2. Добавление языковых префиксов в URL
  3. Перевод шаблонов
  4. Позволяет пользователям переключаться между языками
  5. Перевести модели
  6. Добавить поддержку локали

Настройка проекта

Вот краткий обзор приложения, которое вам предстоит создать:

Example App

Это может показаться простым, но это поможет вам освоить добавление интернационализации в Django.

Для начала клонируйте ветку base из репозитория django-lang:

$ git clone https://github.com/Samuel-2626/django-lang --branch base --single-branch
$ cd django-lang

Далее создайте и активируйте виртуальную среду, установите зависимости проекта, примените миграции и создайте суперпользователя:

$ python3.9 -m venv env
$ source env/bin/activate

(env)$ pip install -r requirements.txt
(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
(env)$ python manage.py createsuperuser

Не стесняйтесь заменить virtualenv и Pip на Poetry или Pipenv. Подробнее об этом читайте в Настройка проекта Python — виртуальные среды и управление пакетами и Современные среды Python - управление зависимостями и рабочими пространствами.

Обратите внимание на модель Course в course/models.py:

from django.db import models

class Course(models.Model):
    title = models.CharField(max_length=90)
    description = models.TextField()
    date = models.DateField()
    price = models.DecimalField(max_digits=10, decimal_places=2)

    def __str__(self):
        return self.title

Выполните следующую команду управления, чтобы добавить некоторые данные в вашу базу данных:

$ python manage.py add_courses

В следующем разделе мы кратко рассмотрим интернационализацию и локализацию.

Интернационализация против локализации

Интернационализация и локализация - две стороны одной медали. Вместе они позволяют доставлять содержимое вашего веб-приложения в различные локали.

  • Интернационализация, обозначаемая i18n (18 - это количество букв от i до n), представляет собой процесс разработки приложения таким образом, чтобы его можно было использовать в различных локалях. Этим процессом обычно занимаются разработчики.
  • Локализация, представленная символом l10n (10 - количество букв между l и n), с другой стороны, представляет собой процесс перевода вашего приложения на определенный язык и локаль. Обычно этим занимаются переводчики.

Для получения более подробной информации просмотрите Локализация против интернационализации от W3C.

Напомним, что Django, благодаря своей интернационализации , был переведен более чем на 100 языков:

С помощью фреймворка интернационализации мы можем легко помечать строки для перевода как в коде Python, так и в наших шаблонах. Она использует инструментарий GNU gettext для создания и управления обычным текстовым файлом, представляющим язык, известный как message file. Файл сообщения имеет расширение .po. После завершения перевода для каждого языка создается еще один файл, который заканчивается расширением .mo. Он называется скомпилированным переводом.

Начнем с установки набора инструментов gettext.

На macOS рекомендуется использовать Homebrew:

$ brew install gettext
$ brew link --force gettext

В большинстве дистрибутивов Linux он поставляется предустановленным. И наконец, для Windows шаги по установке можно найти здесь.

В следующем разделе мы подготовим наш проект Django к интернационализации и локализации.

Фреймворк интернационализации Django

Django поставляется с некоторыми настройками интернационализации по умолчанию в файле settings.py:

# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

Первая настройка - LANGUAGE_CODE. По умолчанию для него установлено значение United States English (en-us). Это специфическое для конкретной местности имя. Давайте изменим его на общее название - English (en).

LANGUAGE_CODE = 'en'

См. список языковых идентификаторов для получения дополнительной информации.

Чтобы LANGUAGE_CODE вступил в силу, USE_I18N должен быть True, что включает систему перевода Django.

Обратите внимание на остальные настройки:

TIME_ZONE = 'UTC'

USE_L10N = True

USE_TZ = True

Примечания:

  1. "UTC" является значением по умолчанию TIME_ZONE.
  2. Поскольку USE_L10N установлен в True, Django будет отображать числа и даты, используя формат текущей локали.
  3. Наконец, если USE_TZ имеет значение True, то даты будут отображаться с учетом часового пояса.

Давайте добавим несколько дополнительных настроек в дополнение к существующим:

from django.utils.translation import gettext_lazy as _

LANGUAGES = (
    ('en', _('English')),
    ('fr', _('French')),
    ('es', _('Spanish')),
)

Что здесь происходит?

  1. Мы указали языки, на которых должен быть доступен наш проект. Если это не указано, Django будет считать, что наш проект должен быть доступен на всех поддерживаемых языках.
  2. Этот параметр LANGUAGE состоит из кода языка и названия языка. Напомним, что коды языков могут быть локальными, например 'en-gb', или общими, например 'en'.
  3. Кроме того, для перевода названий языков вместо gettext используется gettext_lazy, чтобы предотвратить циклический импорт. Вам следует почти всегда использовать gettext_lazy, когда вы находитесь в глобальной области видимости.

Добавьте django.middleware.locale.LocaleMiddleware в список настроек MIDDLEWARE. Это промежуточное ПО должно располагаться после SessionMiddleware, потому что LocaleMiddleware должен использовать данные сессии. Оно также должно быть размещено перед CommonMiddleware, потому что CommonMiddleware нуждается в активном языке для разрешения запрашиваемых URL. Таким образом, порядок очень важен.

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware', # new
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Это промежуточное ПО используется для определения текущего языка на основе данных запроса.

Добавьте каталог пути локали для вашего приложения, в котором будут находиться файлы сообщений:

LOCALE_PATHS = [
    BASE_DIR / 'locale/',
]

Django смотрит на параметр LOCALE_PATHS для файлов перевода. Имейте в виду, что пути к локалям, которые появляются первыми, имеют наивысший приоритет.

Вам нужно создать каталог "locale" внутри вашего корневого проекта и добавить новую папку для каждого языка:

locale
├── en
├── es
└── fr

Откройте оболочку и выполните следующую команду из каталога проекта, чтобы создать файл сообщений .po для каждого языка:

(env)$ django-admin makemessages --all --ignore=env

Теперь у вас должно быть:

locale
├── en
│   └── LC_MESSAGES
│       └── django.po
├── es
│   └── LC_MESSAGES
│       └── django.po
└── fr
    └── LC_MESSAGES
        └── django.po

Обратите внимание на один из файлов сообщений .po:

  1. msgid: представляет строку перевода в том виде, в каком она отображается в исходном коде.
  2. msgstr: представляет перевод языка, который по умолчанию пуст. Вам придется указать фактический перевод для любой заданной строки.

В настоящее время только LANGUAGES из нашего файла settings.py были помечены для перевода. Поэтому для каждого msgstr в директориях "fr" и "es" введите вручную французский или испанский эквивалент слова соответственно. Вы можете редактировать файлы .po из вашего обычного редактора кода; однако рекомендуется использовать редактор, разработанный специально для .po, например Poedit.

Для этого учебника сделайте следующие изменения:

# locale/fr/LC_MESSAGES/django.po

msgid "English"
msgstr "Anglais"

msgid "French"
msgstr "Français"

msgid "Spanish"
msgstr "Espagnol"


# locale/es/LC_MESSAGES/django.po

msgid "English"
msgstr "Inglés"

msgid "French"
msgstr "Francés"

msgid "Spanish"
msgstr "Español"

Пример редактирования:

Poedit Example

Далее скомпилируем сообщения, выполнив следующую команду:

(env)$ django-admin compilemessages --ignore=env

Для каждого языка был сгенерирован файл сообщений .mo:

locale
├── en
│   └── LC_MESSAGES
│       ├── django.mo
│       └── django.po
├── es
│   └── LC_MESSAGES
│       ├── django.mo
│       └── django.po
└── fr
    └── LC_MESSAGES
        ├── django.mo
        └── django.po

Вот и все для этого раздела!

Вы уже многое узнали, давайте подведем итоги, прежде чем переходить к другим концепциям. Напомним, что цель этого руководства - научить вас добавлять поддержку нескольких языков в ваш проект Django. В первом разделе вы настроили проект и посмотрели, что вы будете создавать. Затем вы узнали о разнице между интернационализацией и локализацией, а также о том, как работает фреймворк интернационализации Django под капотом. Наконец, мы настроили проект на поддержку нескольких языков и увидели его в действии:

  1. Добавили интернационализацию в наш проект Django
  2. Установили языки, на которых мы хотим, чтобы проект был доступен
  3. Сгенерировали файлы сообщений с помощью gettext_lazy
  4. Вручную добавили переводы
  5. Скомпилировал переводы

Перевод шаблонов, моделей и форм

Вы можете перевести имена полей и форм модели, пометив их для перевода с помощью функции gettext или gettext_lazy:

Отредактируйте файл course/models.py следующим образом:

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

class Course(models.Model):
    title = models.CharField(_('title'), max_length=90)
    description = models.TextField(_('description'))
    date = models.DateField(_('date'))
    price = models.DecimalField(_('price'), max_digits=10, decimal_places=2)

    def __str__(self):
        return self.title
(env)$ django-admin makemessages --all --ignore=env

Не стесняйтесь обновить msgstr переводы для французского и испанского языков вручную или с помощью интерфейса Poedit, а затем скомпилировать сообщения

(env)$ django-admin compilemessages --ignore=env

Мы также можем сделать это для форм, добавив метку.

Например:

from django import forms
from django.utils.translation import gettext_lazy as _

class ExampleForm(forms.Form):
    first_name = forms.CharField(label=_('first name'))

Для перевода наших шаблонов Django предлагает теги шаблонов {% trans %} и {% blocktrans %} для перевода строк. Чтобы использовать теги шаблонов перевода, необходимо добавить {% load i18n %} в верхней части HTML-файла.

Тег шаблона {% trans %} позволяет пометить литерал для перевода. Django просто выполняет функцию gettext на заданном тексте.

Тег {% trans %} полезен для простых строк перевода, но он не может обрабатывать содержимое для перевода, включающее переменные.

Тег шаблона {% blocktrans %}, с другой стороны, позволяет помечать содержимое, включающее литералы и переменные.

Обновите следующий элемент в файле course/templates/index.html, чтобы увидеть это в действии:

<h1>{% trans "TestDriven.io Courses" %}</h1>

Не забудьте добавить {% load i18n %} в начало файла.

(env)$ django-admin makemessages --all --ignore=env

Обновите следующие msgstr переводы:

# locale/fr/LC_MESSAGES/django.po

msgid "TestDriven.io Courses"
msgstr "Cours TestDriven.io"


# locale/es/LC_MESSAGES/django.po

msgid "TestDriven.io Courses"
msgstr "Cursos de TestDriven.io"

Компиляция сообщений:

(env)$ django-admin compilemessages --ignore=env

Использование интерфейса перевода Rosetta

Мы будем использовать стороннюю библиотеку под названием Rosetta для редактирования переводов с помощью того же интерфейса, что и на административном сайте Django. Она позволяет легко редактировать файлы .po и автоматически обновлять скомпилированные файлы переводов.

Rosetta уже установлена в качестве части зависимостей; поэтому все, что вам нужно сделать, это добавить ее в установленные приложения:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'course.apps.CourseConfig',
    'rosetta',  # NEW
]

Вам также нужно будет добавить URL Rosetta к вашей основной конфигурации URL в django_lang/urls.py:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('rosetta/', include('rosetta.urls')),  # NEW
    path('', include('course.urls')),
]

Создайте и примените миграции, а затем запустите сервер:

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
(env)$ python manage.py runserver

Убедитесь, что вы вошли в систему как администратор, а затем перейдите по адресу http://127.0.0.1:8000/rosetta/ в вашем браузере:

Rosetta Homepage

Под проектами нажмите на каждое приложение, чтобы отредактировать переводы.

Rosetta French

После завершения редактирования переводов нажмите кнопку "Сохранить и перевести следующий блок", чтобы сохранить переводы в соответствующем файле .po. После этого Rosetta скомпилирует файл сообщений, поэтому нет необходимости вручную выполнять команду django-admin compilemessages --ignore=env.

Обратите внимание, что после добавления новых переводов в производственной среде вам придется перезагрузить сервер после выполнения команды django-admin compilemessages --ignore=env или после сохранения переводов с помощью Rosetta, чтобы изменения вступили в силу.

Добавьте языковой префикс к URL-адресам

С помощью фреймворка интернационализации Django вы можете обслуживать каждую языковую версию под отдельным расширением URL. Например, английская версия вашего сайта может обслуживаться под расширением /en/, французская - под /fr/ и так далее. Такой подход позволяет оптимизировать сайт для поисковых систем, так как каждый URL будет проиндексирован для каждого языка, что, в свою очередь, позволит лучше ранжироваться на каждом языке. Для этого фреймворк интернационализации Django должен определить текущий язык по запрашиваемому URL; поэтому LocalMiddleware должен быть добавлен в настройки MIDDLEWARE вашего проекта, что мы уже сделали.

Далее добавьте функцию i18n_patterns в django_lang/urls.py:

from django.conf.urls.i18n import i18n_patterns
from django.contrib import admin
from django.urls import path, include
from django.utils.translation import gettext_lazy as _

urlpatterns = i18n_patterns(
    path(_('admin/'), admin.site.urls),
    path('rosetta/', include('rosetta.urls')),
    path('', include('course.urls')),
)

Снова запустите сервер разработки и перейдите по адресу http://127.0.0.1:8000/ в браузере. Вы будете перенаправлены на запрашиваемый URL с соответствующим языковым префиксом. Посмотрите на URL в браузере; теперь он должен выглядеть так http://127.0.0.1:8000/en/.

Измените запрашиваемый URL с en на fr или es. Заголовок должен измениться.

Перевод моделей с помощью django-parler

Фреймворк интернационализации Django не поддерживает перевод моделей "из коробки", поэтому мы будем использовать стороннюю библиотеку django-parler. Существует множество плагинов, которые выполняют эту функцию, однако этот является одним из самых популярных.

Как это работает?

django-parler создаст отдельную таблицу базы данных для каждой модели, содержащей переводы. Эта таблица включает все переведенные поля. В ней также есть внешний ключ для связи с оригинальным объектом.

django-parler уже установлен как часть зависимостей, поэтому просто добавьте его в установленные приложения:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'course.apps.CourseConfig',
    'rosetta',
    'parler',  # NEW
]

Также добавьте следующий код в настройки:

PARLER_LANGUAGES = {
    None: (
        {'code': 'en',}, # English
        {'code': 'fr',}, # French
        {'code': 'es',}, # Spanish
    ),
    'default': {
        'fallbacks': ['en'],
        'hide_untranslated': False,
    }
}

Здесь вы определили доступные языки (английский, французский, испанский) для django-parler. Вы также указали английский язык в качестве языка по умолчанию и отметили, что django-parler не должен скрывать непереведенное содержимое.

Что нужно знать?

  1. django-parler предоставляет класс модели TranslatableModel и обертку TranslatedFields для перевода полей модели.
  2. django-parler управляет переводами, генерируя другую модель для каждой переводимой модели.

Обратите внимание, что, поскольку Django использует отдельную таблицу для переводов, некоторые функции Django будут недоступны. Кроме того, этот переход удалит предыдущие записи в вашей базе данных.

Обновите course/models.py еще раз, чтобы он выглядел так:

from django.db import models
from parler.models import TranslatableModel, TranslatedFields


class Course(TranslatableModel):
    translations = TranslatedFields(
        title=models.CharField(max_length=90),
        description=models.TextField(),
        date=models.DateField(),
        price=models.DecimalField(max_digits=10, decimal_places=2),
    )

    def __str__(self):
        return self.title

Далее создайте миграции:

(env)$ python manage.py makemigrations

Прежде чем продолжить, замените следующую строку в только что созданном файле миграции:

bases=(parler.models.TranslatedFieldsModelMixin, models.Model),

Со следующим:

bases = (parler.models.TranslatableModel, models.Model)

В django-parler была обнаружена незначительная проблема, которую мы только что решили. Если этого не сделать, миграции не будут применяться.

Далее примените миграции:

(env)$ python manage.py migrate

Одной из замечательных особенностей django_parler является то, что он легко интегрируется с административным сайтом Django. Он включает в себя класс TranslatableAdmin, который переопределяет класс ModelAdmin, предоставляемый Django для управления переводами.

Отредактируйте course/admin.py следующим образом:

from django.contrib import admin
from parler.admin import TranslatableAdmin

from .models import Course

admin.site.register(Course, TranslatableAdmin)

Выполните следующую команду управления, чтобы снова добавить некоторые данные в вашу базу данных:

(env)$ python manage.py add_courses

Запустите сервер, а затем перейдите по адресу http://127.0.0.1:8000/admin/ в браузере. Выберите один из курсов. Для каждого курса теперь доступно отдельное поле для каждого языка.

Django Parler Example

Обратите внимание, что мы едва коснулись поверхности того, что django-parler может сделать для нас. Пожалуйста, обратитесь к docs, чтобы узнать больше.

Позволяя пользователям переключать языки

В этом разделе мы покажем, как предоставить пользователям возможность переключения между языками с нашей домашней страницы.

Обновите файл index.html следующим образом:

{% load i18n %}

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
      crossorigin="anonymous"
    />
    <title>TestDriven.io</title>
      <style>
        h1, h3 {
          color: #266150;
        }
        li {
          display: inline;
          text-decoration: none;
          padding: 5px;
        }
        a {
          text-decoration: none;
          color: #DDAF94;
        }
        a:hover {
          color: #4F4846;
        }
        .active {
          background-color: #266150;
          padding: 5px;
          text-align: right;
          border-radius: 7px;
        }
      </style>
  </head>
  <body>
    <div class="container">
      <h1>{% trans "TestDriven.io Courses" %}</h1>

      {% get_current_language as CURRENT_LANGUAGE %}
      {% get_available_languages as AVAILABLE_LANGUAGES %}
      {% get_language_info_list for AVAILABLE_LANGUAGES as languages %}
      <div class="languages">
        <p>{% trans "Language" %}:</p>
        <ul class="languages">
          {% for language in languages %}
            <li>
              <a href="/{{ language.code }}/"
                {% if language.code == CURRENT_LANGUAGE %} class="active"{% endif %}>
                {{ language.name_local }}
              </a>
            </li>
          {% endfor %}
        </ul>
      </div>

      {% for course in courses %}
        <div class="card p-4">
          <h3>
            {{ course.title }}
            <em style="font-size: small">{{ course.date }}</em>
          </h3>
          <p>{{ course.description }}</p>
          <strong>Price: $ {{ course.price }}</strong>
        </div>
        <hr />
      {% empty %}
        <p>Database is empty</p>
      {% endfor %}

    </div>
  </body>
</html>

Что здесь происходит?

Мы:

  1. Загрузите теги интернационализации с помощью тега {% load i18n %}.
  2. Получение текущего языка с помощью тега {% get_current_language %}.
  3. Также получил доступные языки, определенные в настройках LANGUAGES с помощью тега шаблона {% get_available_languages %}.
  4. Затем с помощью тега {% get_language_info_list %} включили атрибуты языка.
  5. Построил HTML-список для отображения всех доступных языков и добавил атрибут active class к активному языку, чтобы выделить его.

Перейдите по адресу http://127.0.0.1:8000/ в браузере, чтобы увидеть изменения. Переключитесь между несколькими языками, а также обратите внимание на то, как меняется префикс URL для каждого языка.

Добавить поддержку локализации

Помните, как мы устанавливали USE_L10N на True? С помощью этого параметра Django будет пытаться использовать специфический для локали формат при выводе значения в шаблоне. Таким образом, даты, время и числа будут иметь различные форматы в зависимости от локали пользователя.

Перейдите обратно на http://127.0.0.1:8000/ в браузере, чтобы увидеть изменения, и вы заметите, что формат даты изменился. Десятичные числа в английской версии вашего сайта отображаются с разделителем десятичных знаков точкой, в то время как в испанской и французской версиях они отображаются через запятую. Это связано с различиями в форматах локалей для каждого из языков.

Example App

Заключение

В этом уроке вы узнали об интернационализации и локализации, а также о том, как настроить проект Django на интернационализацию с помощью фреймворка интернационализации Django. Мы также использовали Rosetta для упрощения обновления и компиляции файлов сообщений и django-parler для перевода наших моделей.

Возьмите полный код из репозитория django-lang на GitHub.

Вернуться на верх