Обработка многоязычности в Django: как избежать трагедии «Polish» → «польский язык» (Контекстные маркеры)

Когда вы действительно поддерживаете многоязычность (i18n), вы, вероятно, сталкивались с такими ситуациями:

  • Кнопка помечена как “Polish” (смысл «отшлифовать»), но переводится как “польский язык”
  • В компоненте выбора даты “May” переводится как имя человека в некоторых языках
  • В меню “Book” переводится только как «книга», но не как «забронировать».

Причина проста.

Компьютер не понимает «контекст». Если строки одинаковы, он считает их одинаковыми.

В этой статье мы покажем, как использовать Контекстные маркеры в Django, чтобы оставить исходный код неизменным, но при этом привязать к одной и той же строке разные переводы.


Почему это происходит? Базовый принцип gettext



Система перевода Django использует GNU gettext. Он работает по очень простым правилам:

  • Исходная строка → msgid
  • Перевод → msgstr
  • Если msgid одинаковы, то используется один и тот же msgstr
# django.po

msgid "Polish"
msgstr "польский язык"

После такой записи, где бы вы ни использовали «Polish», будет применён один и тот же перевод.

Таким образом, два UI‑элемента не различаются:

  • «Polish» = глагол «отшлифовать»
  • «Polish» = название языка/страны

Чтобы избежать этого, иногда меняют исходный код:

<!-- Не рекомендуется -->
{% trans "Polish (verb)" %}
{% trans "Polish (language)" %}

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

Наша цель:

  • Оставить исходную строку как "Polish"
  • Добавить отдельное описание того, в каком контексте она используется

Для этого используется Контекстный маркер.


Решение в шаблонах: {% translate %} + context

В шаблонах Django можно добавить context к тегу {% translate %} (или к старому {% trans %}):

1) Исходный код (конфликт)

{% load i18n %}

<button>{% translate "Polish" %}</button>
<span>{% translate "Polish" %}</span>

В .po файле обе строки имеют msgid "Polish", поэтому переводится только один вариант.

2) Улучшенный код (с контекстом)

{% load i18n %}

<button>
  {% translate "Polish" context "verb: to refine UI" %}
</button>

<span>
  {% translate "Polish" context "language name" %}
</span>

Важно:

  • Текст после context не виден пользователю.
  • Это мета‑информация для переводчика.
  • Описывайте контекст коротко и ясно.

  • "verb: to refine UI"

  • "language name"
  • "menu label"
  • "button text"

3) Можно использовать и в {% blocktranslate %}

{% load i18n %}

{% blocktranslate context "greeting message" with username=user.username %}
  Hello {{ username }}
{% endblocktranslate %}

Таким образом, одна и та же строка может использоваться в разных контекстах.


Решение в Python‑коде: pgettext



В коде (views, models, forms) вместо gettext используют функции pgettext и его варианты.

  • pgettext(context, message)
  • pgettext_lazy(context, message) – ленивый вызов
  • npgettext(context, singular, plural, number) – множественное число + контекст

1) Пример

from django.utils.translation import pgettext

def my_view(request):
    # 1. Название месяца "May"
    month = pgettext("month name", "May")

    # 2. Имя человека "May"
    person = pgettext("person name", "May")

    # 3. Модальный глагол "may"
    verb = pgettext("auxiliary verb", "may")

2) Использование pgettext_lazy в модели

from django.db import models
from django.utils.translation import pgettext_lazy

class Order(models.Model):
    type = models.CharField(
        verbose_name=pgettext_lazy("order model field", "Order type"),
        max_length=20,
    )

    STATUS_CHOICES = [
        ("open",  pgettext_lazy("order status", "Open")),
        ("opened", pgettext_lazy("log action", "Open")),
    ]

    status = models.CharField(
        max_length=20,
        choices=STATUS_CHOICES,
    )

3) Множественное число: npgettext

from django.utils.translation import npgettext

def get_notification(count):
    return npgettext(
        "user notification",
        "You have %(count)d message",
        "You have %(count)d messages",
        count
    ) % {"count": count}

Как выглядит .po файл?

После добавления контекста, при выполнении python manage.py makemessages -l ko в .po появляется поле msgctxt.

# django.po

# 1) "Polish" = глагол «отшлифовать»
msgctxt "verb: to refine UI"
msgid "Polish"
msgstr "отшлифовать"

# 2) "Polish" = название языка/страны
msgctxt "language name"
msgid "Polish"
msgstr "польский язык"

msgid одинаковый, но msgctxt различается, поэтому переводчики видят два разных элемента.


Как писать хороший контекст?

Контекст не виден пользователю, но является важной подсказкой для переводчика.

1) Описывайте роль

  • "button label"
  • "menu item"
  • "tooltip"
  • "error message"
  • "form field label"

2) Добавляйте доменную концепцию

  • "File""file menu item", "uploaded file object"
  • "Order""e-commerce order", "sorting order"

3) Не включайте перевод в контекст

Контекст должен быть на исходном языке (обычно английском) и описывать концепцию.


Когда использовать контекстные маркеры?

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

  1. Короткие строки (1–2 слова) – кнопки, вкладки, пункты меню.
  2. Строки, используемые в разных значениях в UI – "Open", "Book", "Order".
  3. Переводчики работают без доступа к экрану – внешние платформы, файлы .po.

Итоги

Размышляющий разработчик

  • Django по умолчанию сопоставляет один перевод с одним msgid.
  • Поэтому омонимия часто приводит к конфликтам.
  • Не меняйте исходный текст. Вместо этого:
  • В шаблонах используйте {% translate "…" context "…" %}.
  • В Python‑коде – pgettext, pgettext_lazy, npgettext.
  • Это добавит msgctxt в .po, позволяя иметь разные переводы для одной строки.

Таким образом, вы сохраняете читаемость кода и повышаете качество и поддерживаемость переводов.


Похожие статьи