Мультиязычность в Django: что советуют на практике
Перевод интерфейса, URL и контента из базы решаются разными инструментами. Разбираем, что делать штатной i18n, а где ставить django-parler или django-modeltranslation, и чем платишь за каждый выбор.

В Django есть встроенная система интернационализации, и на демо она выглядит так, будто закрывает всё. На практике через неделю после запуска вы упираетесь в вопрос, который штатный gettext не решает: как хранить в базе описание товара на русском, английском и казахском одновременно. Штатный механизм переводит строки в коде и шаблонах, но не поля моделей. Это разные задачи, и путаница между ними — главная причина, по которой мультиязычность в Django переписывают дважды.
Материал для Django 5.x. Разбираем выбор между тремя слоями: перевод интерфейса, локализация URL и перевод контента из базы. Языковое определение по браузеру и работа с часовыми поясами — смежные темы, здесь их не разворачиваем.
Три задачи, которые часто путают
Прежде чем ставить пакеты, разделите то, что вы переводите. Это три независимых слоя, и для каждого — свой инструмент.
- Статичный текст интерфейса — надписи на кнопках, ошибки форм, письма. Живёт в коде и шаблонах. Это штатная i18n Django через
gettextи.po-файлы. - URL и роутинг — префикс языка в адресе (
/en/catalog/), переведённые слаги. Штатный механизмi18n_patternsплюсreverseс активным языком. - Контент из базы — то, что редактор вводит в админке: названия, описания, статьи блога. Штатная i18n сюда не лезет вообще. Нужен сторонний пакет.
Если попытаться перевести контент базы через .po-файлы — а такое встречается — вы получите нередактируемые тексты, которые нельзя менять без деплоя. Обратная ошибка тоже бывает: тянут тяжёлый пакет для перевода моделей ради трёх строк в шаблоне.
Слой 1: интерфейс через gettext
Тут всё штатно и стабильно много лет. Оборачиваете строки в gettext_lazy в коде и в {% translate %} в шаблонах, собираете .po, компилируете в .mo.
# settings.py — Django 5.x
USE_I18N = True
LANGUAGE_CODE = "ru"
LANGUAGES = [
("ru", "Русский"),
("en", "English"),
("kk", "Қазақша"),
]
# каталог, куда собираются переводы
LOCALE_PATHS = [BASE_DIR / "locale"]# models.py — строки, которые видит пользователь
from django.utils.translation import gettext_lazy as _
class Order(models.Model):
STATUS_CHOICES = [
("new", _("Новый")),
("paid", _("Оплачен")),
]Сбор и компиляция — двумя командами. Проверка: после compilemessages в каталоге locale/en/LC_MESSAGES/ появится файл django.mo, и переключение языка начнёт менять надписи.
# собрать строки из кода и шаблонов
python manage.py makemessages -l en -l kk
# после ручного перевода .po — скомпилировать
python manage.py compilemessagesСлой 2: язык в URL
Оберните маршруты в i18n_patterns, и Django добавит префикс языка. Ключевой момент — LocaleMiddleware должен стоять после сессии и до CommonMiddleware, иначе определение языка сломается тихо.
# urls.py
from django.conf.urls.i18n import i18n_patterns
from django.urls import path, include
urlpatterns = i18n_patterns(
path("catalog/", include("catalog.urls")),
prefix_default_language=True, # /ru/ тоже с префиксом
)Флаг prefix_default_language=False уберёт префикс у языка по умолчанию — но тогда следите за каноническими URL, иначе одна страница будет доступна и на /, и на /en/ без редиректа, что не любят поисковики.
Слой 3: перевод контента базы — здесь и начинается выбор
Штатных средств нет, а решений два зрелых: django-modeltranslation и django-parler. Они устроены принципиально по-разному, и это определяет цену.
django-modeltranslation: колонки на каждый язык
Пакет добавляет в ту же таблицу по колонке на язык: было поле title, стало title_ru, title_en, title_kk. Регистрируется через файл translation.py:
# translation.py
from modeltranslation.translator import register, TranslationOptions
from .models import Product
@register(Product)
class ProductTranslationOptions(TranslationOptions):
fields = ("title", "description")Плюс: никаких JOIN — все переводы в одной строке, выборка быстрая, сортировка и фильтрация по переведённому полю работают штатным ORM без ухищрений. Минус: каждый новый язык — это миграция схемы (новые колонки). Добавить язык на лету, из админки, нельзя.
django-parler: отдельная таблица переводов
Здесь переводы лежат в связанной таблице: одна строка на пару «объект + язык». Модель наследует TranslatableModel, переводимые поля выносятся в TranslatedFields:
# models.py — с django-parler
from parler.models import TranslatableModel, TranslatedFields
class Product(TranslatableModel):
sku = models.CharField(max_length=32) # не переводится
translations = TranslatedFields(
title=models.CharField(max_length=200),
description=models.TextField(),
)Плюс: новый язык не трогает схему — просто добавляются строки. Гибко, если языков много или они добавляются со временем. Минус: почти любая выборка с обращением к переведённому полю тянет JOIN или отдельный запрос. Сортировка и фильтрация по переводу — заметно сложнее, чем у modeltranslation, и легко словить N+1, если забыть prefetch_related.
| Критерий | django-modeltranslation | django-parler |
|---|---|---|
| Хранение | Колонки в той же таблице | Отдельная таблица переводов |
| Новый язык | Миграция схемы | Без миграции, только данные |
| Фильтр/сортировка по переводу | Штатным ORM, просто | Через JOIN, сложнее |
| Риск N+1 | Низкий | Высокий без prefetch |
| Число языков | Комфортно до 5–7 | Комфортно и при десятках |
Правило, которое экономит переписывание: если языки известны заранее и их немного, а вы много фильтруете и сортируете каталог — берите modeltranslation. Если языки добавляются в процессе или их много — parler. Смешивать оба в одном проекте не стоит.
Когда что выбирать
- Каталог с фильтрами, 2–4 фиксированных языка — modeltranslation. Выборки останутся быстрыми, поиск по переведённому названию работает через обычный
filter. - Блог или база знаний, языки добавляются со временем — parler. Добавление языка не требует деплоя со сменой схемы.
- Только интерфейс, контент базы одноязычный — ни один пакет не нужен, хватит штатной i18n.
Что ломается на практике
- N+1 с parler. Список из 100 товаров без
prefetch_related('translations')даёт 100+ запросов. Ловится через django-debug-toolbar сразу. - Забытый fallback. Если для объекта нет перевода на активный язык, у modeltranslation вернётся пустая строка или значение из настроек fallback, у parler — исключение
TranslationDoesNotExist, если fallback не настроен. Настройте поведение явно. - makemessages не видит строки. Частая причина — строка не обёрнута в
gettextили собрана f-строкой, которуюgettextне разбирает. Переводите шаблон строки, подставляйте через именованные аргументы. - Миграция после регистрации modeltranslation. После правки
translation.pyнуженmakemigrations— новые колонки не появятся сами.
Что делать дальше
Начните с разделения слоёв на бумаге: что интерфейс, что URL, что контент. Настройте штатную i18n и i18n_patterns — это не спорная часть. Для контента базы примите решение по таблице выше до первой модели, потому что миграция с одного пакета на другой на живых данных — это отдельный болезненный проект. После запуска повесьте django-debug-toolbar и посмотрите на число SQL-запросов на страницах списков: если используете parler и видите десятки запросов там, где ждали единицы — добавляйте prefetch_related. Актуальные имена классов и опций сверяйте с документацией конкретной версии пакета: у modeltranslation и parler API между мажорными релизами менялся.
Контент-конвейер: Из одной идеи — текст, изображение, озвучка и ролик. Один заказ вместо четырёх.
Спросить за 60 ₽Источники: Django documentation — Internationalization and localization, django-parler documentation
Частые вопросы
А если у меня Django 4.2, а не 5.x — примеры подойдут?
Можно ли перейти с modeltranslation на parler позже?
Почему после compilemessages переводы всё равно не появляются?
django-parler даёт N+1 на списках — это можно убрать?
Нужно ли ставить сторонний пакет, если языков всего два?
Как быть с переведёнными слагами в URL?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.