Spec-Zone.ru › Django 6.0

Перевод

Обзор

Чтобы сделать проект Django переводимым, необходимо добавить минимальное количество специальных элементов в код Python и шаблоны. Эти элементы называются строками для перевода. Они сообщают Django: «Этот текст следует перевести на язык конечного пользователя, если для него доступен перевод на этот язык». Помечать строки для перевода — ваша задача; система может переводить только те строки, о которых ей известно.

Затем Django предоставляет средства для извлечения строк для перевода в файл сообщений. Этот файл — удобный способ для переводчиков указать эквиваленты строк для перевода на целевом языке. После того как переводчики заполнят файл сообщений, его необходимо скомпилировать. Этот процесс основан на наборе инструментов GNU gettext.

После этого Django на лету переводит веб-приложения на каждый доступный язык в соответствии с языковыми предпочтениями пользователей.

Механизмы интернационализации Django включены по умолчанию, а это означает небольшие дополнительные затраты, связанные с i18n, в некоторых частях платформы. Если вы не используете интернационализацию, уделите пару секунд и задайте USE_I18N = False в файле настроек. Тогда Django выполнит некоторые оптимизации и не будет загружать механизмы интернационализации.

Примечание

Убедитесь, что для вашего проекта включен перевод (быстрее всего проверить, содержит ли MIDDLEWARE значение django.middleware.locale.LocaleMiddleware). Если вы ещё этого не сделали, см. раздел Как Django определяет языковые предпочтения.

Интернационализация: в коде Python

Стандартный перевод

Укажите строку для перевода с помощью функции gettext(). Принято импортировать её под более коротким псевдонимом _, чтобы меньше печатать.

Примечание

Модуль стандартной библиотеки Python gettext помещает _() в глобальное пространство имён в качестве псевдонима для gettext(). В Django мы решили не следовать этой практике по нескольким причинам:

  1. В некоторых случаях для конкретного файла в качестве метода перевода по умолчанию следует использовать gettext_lazy(). Если _() отсутствует в глобальном пространстве имён, разработчику приходится задуматься о том, какая функция перевода подходит лучше всего.
  2. Символ подчёркивания (_) используется для обозначения «предыдущего результата» в интерактивной оболочке Python и тестах doctest. Установка глобальной функции _() приводит к конфликту. Явный импорт gettext() под именем _() позволяет избежать этой проблемы.

Какие функции можно импортировать под псевдонимом _?

Из-за особенностей работы xgettext (используется командой makemessages) под именем _ можно импортировать только функции, принимающие один строковый аргумент:

  • gettext()
  • gettext_lazy()

В этом примере текст "Welcome to my site." помечен как строка для перевода:

from django.http import HttpResponse
from django.utils.translation import gettext as _


def my_view(request):
    output = _("Welcome to my site.")
    return HttpResponse(output)

Вместо псевдонима можно было бы написать полное имя. Этот пример идентичен предыдущему:

from django.http import HttpResponse
from django.utils.translation import gettext


def my_view(request):
    output = gettext("Welcome to my site.")
    return HttpResponse(output)

Перевод работает и с вычисляемыми значениями. Этот пример идентичен двум предыдущим:

def my_view(request):
    words = ["Welcome", "to", "my", "site."]
    output = _(" ".join(words))
    return HttpResponse(output)

Перевод работает и с переменными. Вот ещё один идентичный пример:

def my_view(request):
    sentence = "Welcome to my site."
    output = _(sentence)
    return HttpResponse(output)

(Ограничение при использовании переменных или вычисляемых значений, как в двух предыдущих примерах, заключается в том, что утилита Django для обнаружения строк для перевода django-admin makemessages не сможет найти эти строки. Подробнее о makemessages будет рассказано далее.)

Строки, передаваемые в _() или gettext(), могут содержать подстановочные параметры, заданные с помощью стандартного синтаксиса именованной подстановки строк в Python. Например:

def my_view(request, m, d):
    output = _("Today is %(month)s %(day)s.") % {"month": m, "day": d}
    return HttpResponse(output)

Этот способ позволяет переводчикам менять порядок подстановочных параметров в соответствии с особенностями языка. Например, английский перевод может выглядеть как "Today is November 26.", а испанский — как "Hoy es 26 de noviembre.", где подстановочные параметры месяца и дня поменяны местами.

Поэтому, если у вас больше одного параметра, следует использовать именованную подстановку (например, %(day)s), а не позиционную (например, %s или %d). При использовании позиционной подстановки переводчики не смогут менять порядок подстановочных параметров.

Поскольку извлечение строк выполняется командой xgettext, Django поддерживает только синтаксис, поддерживаемый gettext. Строки f-strings в Python нельзя напрямую использовать с функциями gettext, поскольку выражения f-строк вычисляются до того, как строка попадает в gettext. Это означает, что _(f"Welcome {name}") не будет работать как ожидается: переменная подставляется до выполнения перевода. Вместо этого используйте именованную подстановку строк:

# Good
_("Welcome %(name)s") % {"name": name}

# Good
_("Welcome {name}").format(name=name)

# Bad
_(f"Welcome {name}")  # f-string evaluated before translation.

Для шаблонных строк JavaScript требуется gettext 0.21+.

Комментарии для переводчиков

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

def my_view(request):
    # Translators: This message appears on the home page only
    output = gettext("Welcome to my site.")

Комментарий появится в итоговом файле .po и будет связан с расположенной ниже конструкцией для перевода; большинство средств перевода также отображают такие комментарии.

Примечание

Для полноты картины приведём соответствующий фрагмент итогового файла .po:

#. Translators: This message appears on the home page only
# path/to/python/file.py:123
msgid "Welcome to my site."
msgstr ""

Это также работает в шаблонах. Подробнее см. раздел Комментарии для переводчиков в шаблонах.

Пометка строк без выполнения перевода

Используйте функцию django.utils.translation.gettext_noop(), чтобы пометить строку как строку для перевода, не переводя её. Позже строка переводится из переменной.

Используйте её для постоянных строк, которые следует хранить на исходном языке, поскольку они передаются между системами или пользователями (например, строки в базе данных), но переводить их нужно как можно позже — например, непосредственно перед показом пользователю.

Образование множественного числа

Используйте функцию django.utils.translation.ngettext(), чтобы указать сообщения с формами множественного числа.

Функция ngettext() принимает три аргумента: строку перевода для единственного числа, строку перевода для множественного числа и количество объектов.

Эта функция полезна, когда необходимо локализовать приложение Django для языков, в которых число и сложность форм множественного числа превышают два варианта, используемых в английском языке («object» для единственного числа и «objects» для всех случаев, когда count не равно единице, независимо от его значения).

Например:

from django.http import HttpResponse
from django.utils.translation import ngettext


def hello_world(request, count):
    page = ngettext(
        "there is %(count)d object",
        "there are %(count)d objects",
        count,
    ) % {
        "count": count,
    }
    return HttpResponse(page)

В этом примере количество объектов передаётся в языки перевода как переменная count.

Обратите внимание, что образование множественного числа — сложная задача, и в каждом языке оно работает по-разному. Сравнение count с 1 не всегда является правильным правилом. Этот код выглядит продуманным, но для некоторых языков даст неверные результаты:

from django.utils.translation import ngettext
from myapp.models import Report

count = Report.objects.count()
if count == 1:
    name = Report._meta.verbose_name
else:
    name = Report._meta.verbose_name_plural

text = ngettext(
    "There is %(count)d %(name)s available.",
    "There are %(count)d %(name)s available.",
    count,
) % {"count": count, "name": name}

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

text = ngettext(
    "There is %(count)d %(name)s object available.",
    "There are %(count)d %(name)s objects available.",
    count,
) % {
    "count": count,
    "name": Report._meta.verbose_name,
}

Примечание

При использовании ngettext() обязательно указывайте одно и то же имя для каждой подставляемой переменной в строковом литерале. В примерах выше обратите внимание, что в обеих строках перевода используется переменная Python name. Следующий пример приведёт к ошибке; помимо того, что, как отмечалось выше, он неверен для некоторых языков:

text = ngettext(
    "There is %(count)d %(name)s available.",
    "There are %(count)d %(plural_name)s available.",
    count,
) % {
    "count": Report.objects.count(),
    "name": Report._meta.verbose_name,
    "plural_name": Report._meta.verbose_name_plural,
}

При запуске django-admin compilemessages вы получите ошибку:

a format specification for argument 'name', as in 'msgstr[0]', doesn't exist in 'msgid'

Контекстные маркеры

Иногда слова имеют несколько значений. Например, английское "May" обозначает и название месяца, и глагол. Чтобы переводчики могли правильно переводить такие слова в разных контекстах, используйте функцию django.utils.translation.pgettext() или функцию django.utils.translation.npgettext(), если строке требуется форма множественного числа. Обе функции принимают строку контекста в качестве первого аргумента.

В итоговом файле .po строка будет появляться столько раз, сколько для неё указано различных контекстных маркеров (контекст будет указан в строке msgctxt). Это позволяет переводчику предложить отдельный перевод для каждого контекста.

Например:

from django.utils.translation import pgettext

month = pgettext("month name", "May")

или:

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


class MyThing(models.Model):
    name = models.CharField(
        help_text=pgettext_lazy("help text for MyThing model", "This is the help text")
    )

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

msgctxt "month name"
msgid "May"
msgstr ""

Контекстные маркеры также поддерживаются тегами шаблонов translate и blocktranslate.

Отложенный перевод

Используйте отложенные версии функций перевода из django.utils.translation (их легко распознать по суффиксу lazy в имени), чтобы переводить строки отложенно — при обращении к значению, а не при вызове функции.

Эти функции сохраняют отложенную ссылку на строку, а не сам перевод. Перевод выполняется, когда строка используется в строковом контексте, например при рендеринге шаблона.

Это необходимо, когда вызовы этих функций находятся в участках кода, выполняемых во время загрузки модуля.

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

Значения параметров verbose_name и help_text полей моделей и связей

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

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


class MyThing(models.Model):
    name = models.CharField(help_text=_("This is the help text"))

Можно пометить названия связей ForeignKey, ManyToManyField или OneToOneField как переводимые, используя их параметры verbose_name:

class MyThing(models.Model):
    kind = models.ForeignKey(
        ThingKind,
        on_delete=models.CASCADE,
        related_name="kinds",
        verbose_name=_("kind"),
    )

Как и для verbose_name, для связи следует указать название в нижнем регистре: Django автоматически преобразует его в формат заголовка, когда потребуется.

Значения описательных названий моделей

Рекомендуется всегда явно задавать параметры verbose_name и verbose_name_plural, а не полагаться на запасной, ориентированный на английский язык и довольно наивный способ, с помощью которого Django определяет описательные названия по имени класса модели:

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


class MyThing(models.Model):
    name = models.CharField(_("name"), help_text=_("This is the help text"))

    class Meta:
        verbose_name = _("my thing")
        verbose_name_plural = _("my things")

Аргумент description методов модели для декоратора @display

Для методов модели можно предоставить переводы Django и сайту администратора с помощью аргумента description декоратора display():

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


class MyThing(models.Model):
    kind = models.ForeignKey(
        ThingKind,
        on_delete=models.CASCADE,
        related_name="kinds",
        verbose_name=_("kind"),
    )

    @admin.display(description=_("Is it a mouse?"))
    def is_mouse(self):
        return self.kind.type == MOUSE_TYPE

Работа с объектами отложенного перевода

Результат вызова gettext_lazy() можно использовать везде, где в другом коде Django используется строка (объект str), но он может не работать с произвольным кодом Python. Например, следующий код не сработает, поскольку библиотека requests не обрабатывает объекты gettext_lazy:

body = gettext_lazy("I \u2764 Django")  # (Unicode :heart:)
requests.post("https://example.com/send", data={"body": body})

Чтобы избежать таких проблем, преобразуйте объекты gettext_lazy() в текстовые строки перед передачей коду, не относящемуся к Django:

requests.post("https://example.com/send", data={"body": str(body)})

Если вам не нравится длинное имя gettext_lazy, можно назначить ему псевдоним _ (подчёркивание), например:

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


class MyThing(models.Model):
    name = models.CharField(help_text=_("This is the help text"))

Использование gettext_lazy() и ngettext_lazy() для пометки строк в моделях и вспомогательных функциях — распространённая практика. При работе с такими объектами в других частях кода следите за тем, чтобы случайно не преобразовать их в строки: это следует делать как можно позже, чтобы к тому моменту была активна нужная локаль. Для этого понадобится вспомогательная функция, описанная далее.

Отложенные переводы и множественное число

При отложенном переводе строки с формами множественного числа (n[p]gettext_lazy) обычно неизвестен аргумент number в момент определения строки. Поэтому вместо целого числа в качестве аргумента number можно передать имя ключа. Тогда при подстановке строка number будет получена из словаря по этому ключу. Например:

from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import ngettext_lazy


class MyForm(forms.Form):
    error_message = ngettext_lazy(
        "You only provided %(num)d argument",
        "You only provided %(num)d arguments",
        "num",
    )

    def clean(self):
        # ...
        if error:
            raise ValidationError(self.error_message % {"num": number})

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

class MyForm(forms.Form):
    error_message = ngettext_lazy(
        "You provided %d argument",
        "You provided %d arguments",
    )

    def clean(self):
        # ...
        if error:
            raise ValidationError(self.error_message % number)

Форматирование строк: format_lazy()

Метод Python str.format() не будет работать, если format_string или любой из аргументов str.format() содержит объекты отложенного перевода. Вместо этого можно использовать django.utils.text.format_lazy(): она создаёт отложенный объект, который вызывает метод str.format() только при включении результата в строку. Например:

from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy

...
name = gettext_lazy("John Lennon")
instrument = gettext_lazy("guitar")
result = format_lazy("{name}: {instrument}", name=name, instrument=instrument)

В этом случае отложенные переводы в result будут преобразованы в строки только при использовании самого result в строковом контексте (обычно во время рендеринга шаблона).

Другие способы применения отложенного перевода

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

from django.utils.functional import lazy
from django.utils.translation import gettext_lazy as _


def to_lower(string):
    return string.lower()


to_lower_lazy = lazy(to_lower, str)

А затем:

lazy_string = to_lower_lazy(_("My STRING!"))

Локализованные названия языков

get_language_info(lang_code) [исходный код]

Функция get_language_info() предоставляет подробную информацию о языках:

>>> from django.utils.translation import activate, get_language_info
>>> activate("fr")
>>> li = get_language_info("de")
>>> print(li["name"], li["name_local"], li["name_translated"], li["bidi"])
German Deutsch Allemand False

Атрибуты name, name_local и name_translated словаря содержат название языка на английском, на самом этом языке и на текущем активном языке соответственно. Атрибут bidi имеет значение True только для языков с письмом справа налево.

Источник информации о языках — модуль django.conf.locale. К шаблонному коду также доступен аналогичный способ получения этой информации. См. далее.

Интернационализация: в коде шаблонов

Для переводов в шаблонах Django используются два тега шаблонов и синтаксис, немного отличающийся от синтаксиса в коде Python. Чтобы предоставить шаблону доступ к этим тегам, поместите {% load i18n %} ближе к началу шаблона. Как и все теги шаблонов, этот тег нужно загрузить во всех шаблонах, использующих переводы, в том числе в шаблонах, расширяющих другие шаблоны, в которых тег i18n уже загружен.

Предупреждение

Переведённые строки не будут экранироваться при отображении в шаблоне. Это позволяет включать в переводы HTML, например для выделения текста, однако потенциально опасные символы (например, ") также будут отображаться без изменений.

Тег шаблона translate

Тег шаблона {% translate %} переводит либо постоянную строку (заключённую в одинарные или двойные кавычки), либо содержимое переменной:

<title>{% translate "This is the title." %}</title>
<title>{% translate myvar %}</title>

Если указан параметр noop, поиск переменной всё равно выполняется, но перевод пропускается. Это полезно, когда вы добавляете временное содержимое, которое потребуется перевести в будущем:

<title>{% translate "myvar" noop %}</title>

Внутристрочные переводы используют вызов gettext().

Если тегу передана переменная шаблона (myvar выше), тег сначала преобразует эту переменную в строку во время выполнения, а затем ищет эту строку в каталогах сообщений.

Невозможно включить переменную шаблона внутрь строки в {% translate %}. Если для перевода требуются строки с переменными (заполнителями), используйте вместо этого {% blocktranslate %}.

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

{% translate "This is the title" as the_title %}

<title>{{ the_title }}</title>
<meta name="description" content="{{ the_title }}">

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

{% translate "starting point" as start %}
{% translate "end point" as end %}
{% translate "La Grande Boucle" as race %}

<h1>
  <a href="/" title="{% blocktranslate %}Back to '{{ race }}' homepage{% endblocktranslate %}">{{ race }}</a>
</h1>
<p>
{% for stage in tour_stages %}
    {% cycle start end %}: {{ stage }}{% if forloop.counter|divisibleby:2 %}<br>{% else %}, {% endif %}
{% endfor %}
</p>

Тег {% translate %} также поддерживает контекстные маркеры с помощью ключевого слова context:

{% translate "May" context "month name" %}

Тег шаблона blocktranslate

В отличие от тега translate, тег blocktranslate позволяет помечать для перевода сложные предложения, состоящие из неизменяемого текста и содержимого переменных, используя заполнители:

{% blocktranslate %}This string will have {{ value }} inside.{% endblocktranslate %}

Чтобы перевести выражение шаблона — например, обращение к атрибутам объекта или применение фильтров шаблона, — необходимо связать это выражение с локальной переменной и использовать её внутри блока перевода. Примеры:

{% blocktranslate with amount=article.price %}
That will cost $ {{ amount }}.
{% endblocktranslate %}

{% blocktranslate with myvar=value|filter %}
This will have {{ myvar }} inside.
{% endblocktranslate %}

В одном теге blocktranslate можно использовать несколько выражений:

{% blocktranslate with book_t=book|title author_t=author|title %}
This is {{ book_t }} by {{ author_t }}
{% endblocktranslate %}

Примечание

Предыдущий, более подробный формат по-прежнему поддерживается: {% blocktranslate with book|title as book_t and author|title as author_t %}

Другие теги блоков (например, {% for %} или {% if %}) нельзя использовать внутри тега blocktranslate.

Если не удаётся разрешить один из аргументов блока, blocktranslate переключится на язык по умолчанию, временно отключив активный язык с помощью функции deactivate_all().

Этот тег также поддерживает формы множественного числа. Для этого:

  • Укажите и свяжите счётчик под именем count. Это значение будет использоваться для выбора нужной формы множественного числа.
  • Укажите формы единственного и множественного числа, разделив их тегом {% plural %} внутри тегов {% blocktranslate %} и {% endblocktranslate %}.

Пример:

{% blocktranslate count counter=list|length %}
There is only one {{ name }} object.
{% plural %}
There are {{ counter }} {{ name }} objects.
{% endblocktranslate %}

Более сложный пример:

{% blocktranslate with amount=article.price count years=i.length %}
That will cost $ {{ amount }} per year.
{% plural %}
That will cost $ {{ amount }} per {{ years }} years.
{% endblocktranslate %}

Если вы используете функцию множественного числа и связываете значения с локальными переменными помимо значения счётчика, помните, что конструкция blocktranslate внутри преобразуется в вызов ngettext. Это означает, что применимы те же примечания о переменных ngettext.

Обратное разрешение URL нельзя выполнять внутри blocktranslate; его следует выполнить заранее (и сохранить результат):

{% url 'path.to.view' arg arg2 as the_url %}
{% blocktranslate %}
This is a URL: {{ the_url }}
{% endblocktranslate %}

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

{% blocktranslate asvar the_title %}The title is {{ title }}.{% endblocktranslate %}
<title>{{ the_title }}</title>
<meta name="description" content="{{ the_title }}">

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

Тег {% blocktranslate %} также поддерживает контекстные маркеры с помощью ключевого слова context:

{% blocktranslate with name=user.username context "greeting" %}Hi {{ name }}{% endblocktranslate %}

Ещё одна возможность, поддерживаемая тегом {% blocktranslate %}, — параметр trimmed. Этот параметр удаляет символы новой строки в начале и конце содержимого тега {% blocktranslate %}, заменяет пробелы в начале и конце строк и объединяет все строки в одну, разделяя их пробелами. Это очень удобно, если нужно форматировать с отступами содержимое тега {% blocktranslate %}, не допуская попадания символов отступа в соответствующую запись файла .po, что упрощает процесс перевода.

Например, следующий тег {% blocktranslate %}:

{% blocktranslate trimmed %}
  First sentence.
  Second paragraph.
{% endblocktranslate %}

создаст запись "First sentence. Second paragraph." в файле .po, в отличие от "\n  First sentence.\n  Second paragraph.\n", если бы параметр trimmed не был указан.

Строковые литералы, передаваемые тегам и фильтрам

Строковые литералы, передаваемые тегам и фильтрам в качестве аргументов, можно переводить с помощью знакомого синтаксиса _():

{% some_tag _("Page not found") value|yesno:_("yes,no") %}

В этом случае и тег, и фильтр получат переведённую строку, поэтому им не нужно учитывать наличие переводов.

Примечание

В этом примере инфраструктуре перевода будет передана строка "yes,no", а не отдельные строки "yes" и "no". В переводе должна присутствовать запятая, чтобы код разбора фильтра знал, как разделить аргументы. Например, переводчик на немецкий может перевести строку "yes,no" как "ja,nein" (сохранив запятую).

Комментарии для переводчиков в шаблонах

Как и в коде Python, такие примечания для переводчиков можно указывать в комментариях, используя тег comment:

{% comment %}Translators: View verb{% endcomment %}
{% translate "View" %}

{% comment %}Translators: Short intro blurb{% endcomment %}
<p>{% blocktranslate %}A multiline translatable
literal.{% endblocktranslate %}</p>

или конструкции однострочных комментариев {# … #} :

{# Translators: Label of a button that triggers search #}
<button type="submit">{% translate "Go" %}</button>

{# Translators: This is a text of the base template #}
{% blocktranslate %}Ambiguous translatable block of text{% endblocktranslate %}

Примечание

Для полноты картины приведём соответствующие фрагменты итогового файла .po:

#. Translators: View verb
# path/to/template/file.html:10
msgid "View"
msgstr ""

#. Translators: Short intro blurb
# path/to/template/file.html:13
msgid ""
"A multiline translatable"
"literal."
msgstr ""

# ...

#. Translators: Label of a button that triggers search
# path/to/template/file.html:100
msgid "Go"
msgstr ""

#. Translators: This is a text of the base template
# path/to/template/file.html:103
msgid "Ambiguous translatable block of text"
msgstr ""

Переключение языка в шаблонах

Чтобы выбрать язык внутри шаблона, можно использовать тег шаблона language:

{% load i18n %}

{% get_current_language as LANGUAGE_CODE %}
<!-- Current language: {{ LANGUAGE_CODE }} -->
<p>{% translate "Welcome to our page" %}</p>

{% language 'en' %}
    {% get_current_language as LANGUAGE_CODE %}
    <!-- Current language: {{ LANGUAGE_CODE }} -->
    <p>{% translate "Welcome to our page" %}</p>
{% endlanguage %}

Первое вхождение «Добро пожаловать на нашу страницу» использует текущий язык, а второе всегда будет на английском.

Другие теги

Для этих тегов также требуется {% load i18n %}.

get_available_languages

{% get_available_languages as LANGUAGES %} возвращает список кортежей, где первый элемент — код языка, а второй — название языка (переведённое на текущий активный язык).

get_current_language

{% get_current_language as LANGUAGE_CODE %} возвращает предпочтительный язык текущего пользователя в виде строки. Пример: en-us. См. раздел Как Django определяет предпочтительный язык.

get_current_language_bidi

{% get_current_language_bidi as LANGUAGE_BIDI %} возвращает направление письма для текущей локали. Если результат — True, язык пишется справа налево, например иврит или арабский. Если результат — False, язык пишется слева направо, например английский, французский, немецкий и т. д.

Контекстный процессор i18n

Если включить контекстный процессор django.template.context_processors.i18n, каждый RequestContext получит доступ к LANGUAGES, LANGUAGE_CODE и LANGUAGE_BIDI, описанным выше.

get_language_info

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

{% get_language_info for LANGUAGE_CODE as lang %}
{% get_language_info for "pl" as lang %}

Затем можно обратиться к полученной информации:

Language code: {{ lang.code }}<br>
Name of language: {{ lang.name_local }}<br>
Name in English: {{ lang.name }}<br>
Bi-directional: {{ lang.bidi }}
Name in the active language: {{ lang.name_translated }}

get_language_info_list

Также можно использовать тег шаблона {% get_language_info_list %}, чтобы получить информацию о списке языков (например, об активных языках, указанных в параметре LANGUAGES). Пример отображения переключателя языка с помощью {% get_language_info_list %} см. в разделе о представлении перенаправления set_language.

Помимо списка кортежей в формате LANGUAGES, тег {% get_language_info_list %} поддерживает списки кодов языков. Если в представлении написать:

context = {"available_languages": ["en", "es", "fr"]}
return render(request, "mytemplate.html", context)

в шаблоне можно перебирать эти языки:

{% get_language_info_list for available_languages as langs %}
{% for lang in langs %} ... {% endfor %}

Фильтры шаблонов

Для удобства также доступны следующие фильтры:

  • {{ LANGUAGE_CODE|language_name }} («немецкий»)
  • {{ LANGUAGE_CODE|language_name_local }} («Deutsch»)
  • {{ LANGUAGE_CODE|language_bidi }} (False)
  • {{ LANGUAGE_CODE|language_name_translated }} («německy», если активен чешский язык)

Интернационализация: в коде JavaScript

Добавление переводов в JavaScript сопряжено с рядом проблем:

  • Код JavaScript не имеет доступа к реализации gettext.
  • Код JavaScript не имеет доступа к файлам .po или .mo; их должен предоставлять сервер.
  • Каталоги переводов для JavaScript должны быть как можно меньше.

Django предлагает интегрированное решение этих проблем: оно передаёт переводы в JavaScript, чтобы из кода JavaScript можно было вызывать gettext и другие функции.

Основное решение этих проблем — следующее представление JavaScriptCatalog, которое создаёт библиотеку кода JavaScript с функциями, имитирующими интерфейс gettext, и массивом строк перевода.

Представление JavaScriptCatalog

class JavaScriptCatalog [исходный код]

Представление, создающее библиотеку кода JavaScript с функциями, имитирующими интерфейс gettext, и массивом строк перевода.

Атрибуты

domain

Домен перевода, содержащий строки, которые нужно включить в вывод представления. По умолчанию — 'djangojs'.

packages

Список значений application names среди установленных приложений. В этих приложениях должен быть каталог locale. Все эти каталоги, а также все каталоги, найденные в LOCALE_PATHS (они включаются всегда), объединяются в один каталог. По умолчанию — None, то есть в вывод JavaScript включаются все доступные переводы из всех приложений, перечисленных в INSTALLED_APPS.

Пример со значениями по умолчанию:

from django.views.i18n import JavaScriptCatalog

urlpatterns = [
    path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
]

Пример с пользовательскими пакетами:

urlpatterns = [
    path(
        "jsi18n/myapp/",
        JavaScriptCatalog.as_view(packages=["your.app.label"]),
        name="javascript-catalog",
    ),
]

Если в корневой конфигурации URLconf используется i18n_patterns(), для правильного создания каталога необходимо также обернуть JavaScriptCatalog в i18n_patterns().

Пример с i18n_patterns():

from django.conf.urls.i18n import i18n_patterns

urlpatterns = i18n_patterns(
    path("jsi18n/", JavaScriptCatalog.as_view(), name="javascript-catalog"),
)

Переводы пакетов, указанных позже в аргументе packages, имеют более высокий приоритет, чем переводы пакетов, указанных в начале списка. Это важно, если для одного и того же литерала имеются конфликтующие переводы.

Если на сайте используется несколько представлений JavaScriptCatalog и некоторые из них определяют одинаковые строки, приоритет будет у строк из каталога, загруженного последним.

Использование каталога переводов JavaScript

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

<script src="{% url 'javascript-catalog' %}"></script>

Для поиска URL представления каталога JavaScript используется обратное разрешение URL. После загрузки каталога в коде JavaScript можно использовать следующие методы:

  • gettext
  • ngettext
  • interpolate
  • get_format
  • gettext_noop
  • pgettext
  • npgettext
  • pluralidx

gettext

Функция gettext работает аналогично стандартному интерфейсу gettext в коде Python:

document.write(gettext("this is to be translated"))

ngettext

Функция ngettext предоставляет интерфейс для образования форм множественного числа слов и фраз:

const objectCount = 1 // or 0, or 2, or 3, ...
const string = ngettext(
    'literal for the singular case',
    'literal for the plural case',
    objectCount
);

interpolate

Функция interpolate позволяет динамически подставлять значения в форматируемую строку. Синтаксис интерполяции заимствован из Python, поэтому функция interpolate поддерживает позиционную и именованную интерполяцию:

  • Позиционная интерполяция: obj содержит объект JavaScript Array, значения элементов которого последовательно подставляются в соответствующие заполнители fmt в том же порядке, в котором они встречаются. Например:

    const formats = ngettext(
      'There is %s object. Remaining: %s',
      'There are %s objects. Remaining: %s',
      11
    );
    const string = interpolate(formats, [11, 20]);
    // string is 'There are 11 objects. Remaining: 20'
    
  • Именованная интерполяция: этот режим выбирается, если передать необязательный логический параметр named со значением true. obj содержит объект JavaScript или ассоциативный массив. Например:

    const data = {
      count: 10,
      total: 50
    };
    
    const formats = ngettext(
        'Total: %(total)s, there is %(count)s object',
        'there are %(count)s of a total of %(total)s objects',
        data.count
    );
    const string = interpolate(formats, data, true);
    

Однако не злоупотребляйте интерполяцией строк: это всё ещё JavaScript, поэтому для подстановок в коде выполняются повторные замены с помощью регулярных выражений. Это не так быстро, как интерполяция строк в Python, поэтому используйте её только в случаях, когда она действительно необходима (например, совместно с ngettext для правильного образования форм множественного числа).

get_format

Функция get_format имеет доступ к настроенным параметрам форматирования i18n и может получить строку форматирования по имени соответствующего параметра:

document.write(get_format('DATE_FORMAT'));
// 'N j, Y'

Ей доступны следующие параметры:

  • DATE_FORMAT
  • DATE_INPUT_FORMATS
  • DATETIME_FORMAT
  • DATETIME_INPUT_FORMATS
  • DECIMAL_SEPARATOR
  • FIRST_DAY_OF_WEEK
  • MONTH_DAY_FORMAT
  • NUMBER_GROUPING
  • SHORT_DATE_FORMAT
  • SHORT_DATETIME_FORMAT
  • THOUSAND_SEPARATOR
  • TIME_FORMAT
  • TIME_INPUT_FORMATS
  • YEAR_MONTH_FORMAT

Это полезно для согласованного форматирования со значениями, отображаемыми Python.

gettext_noop

Эта функция имитирует функцию gettext, но ничего не делает и возвращает переданное ей значение:

document.write(gettext_noop("this will not be translated"))

Это полезно для временного добавления в код фрагментов, которые в будущем потребуется перевести.

pgettext

Функция pgettext работает как вариант для Python (pgettext()) и предоставляет контекстный перевод слова:

document.write(pgettext("month name", "May"))

npgettext

Функция npgettext также работает как вариант для Python (npgettext()) и предоставляет контекстный перевод слова с образованием формы множественного числа:

document.write(npgettext('group', 'party', 1));
// party
document.write(npgettext('group', 'party', 2));
// parties

pluralidx

Функция pluralidx работает подобно фильтру шаблона pluralize и определяет, следует ли использовать форму множественного числа для заданного count:

document.write(pluralidx(0));
// true
document.write(pluralidx(1));
// false
document.write(pluralidx(2));
// true

В простейшем случае, когда специальные правила образования множественного числа не нужны, для целого числа 1 возвращается false, а для всех остальных чисел — true.

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

Кроме того, если правила образования множественного числа сложны, представление каталога создаст условное выражение. Оно будет возвращать либо значение true (форму множественного числа следует использовать), либо false (форму множественного числа использовать не следует).

Представление JSONCatalog

class JSONCatalog [исходный код]

Если для обработки переводов требуется другая клиентская библиотека, можно воспользоваться представлением JSONCatalog. Оно похоже на JavaScriptCatalog, но возвращает ответ JSON.

Сведения о возможных значениях и использовании атрибутов domain и packages см. в документации по JavaScriptCatalog.

Формат ответа:

{
    "catalog": {
        # Translations catalog
    },
    "formats": {
        # Language formats for date, time, etc.
    },
    "plural": "..."  # Expression for plural forms, or null.
}

Примечание о производительности

Различные представления i18n для JavaScript и JSON создают каталог из файлов .mo при каждом запросе. Поскольку их вывод не меняется (по крайней мере, для заданной версии сайта), его целесообразно кэшировать.

Кэширование на стороне сервера снизит нагрузку на процессор. Его легко реализовать с помощью декоратора cache_page(). Чтобы при изменении переводов кэш инвалидировался, задайте префикс ключа, зависящий от версии, как показано в примере ниже, или используйте URL представления, зависящий от версии:

from django.views.decorators.cache import cache_page
from django.views.i18n import JavaScriptCatalog

# The value returned by get_version() must change when translations change.
urlpatterns = [
    path(
        "jsi18n/",
        cache_page(86400, key_prefix="jsi18n-%s" % get_version())(
            JavaScriptCatalog.as_view()
        ),
        name="javascript-catalog",
    ),
]

Кэширование на стороне клиента позволит сэкономить трафик и ускорить загрузку сайта. Если вы используете ETag (ConditionalGetMiddleware), всё уже настроено. В противном случае можно применить условные декораторы. В следующем примере кэш инвалидируется при каждом перезапуске сервера приложений:

from django.utils import timezone
from django.views.decorators.http import last_modified
from django.views.i18n import JavaScriptCatalog

last_modified_date = timezone.now()

urlpatterns = [
    path(
        "jsi18n/",
        last_modified(lambda req, **kw: last_modified_date)(
            JavaScriptCatalog.as_view()
        ),
        name="javascript-catalog",
    ),
]

Можно даже заранее создать каталог JavaScript в рамках процедуры развёртывания и раздавать его как статический файл. Этот радикальный подход реализован в django-statici18n.

Интернационализация: в шаблонах URL

Django предоставляет два механизма интернационализации шаблонов URL:

  • Добавление префикса языка в начало шаблонов URL, чтобы LocaleMiddleware могло определить язык для активации по запрошенному URL.
  • Пометка самих шаблонов URL как переводимых с помощью функции django.utils.translation.gettext_lazy().

Предупреждение

Для использования любой из этих возможностей необходимо задавать активный язык для каждого запроса; другими словами, параметр MIDDLEWARE должен содержать django.middleware.locale.LocaleMiddleware.

Префикс языка в шаблонах URL

i18n_patterns(*urls, prefix_default_language=True) [исходный код]

Эту функцию можно использовать в корневом URLconf: Django автоматически добавит код текущего активного языка в начало всех шаблонов URL, определённых внутри i18n_patterns().

Если установить prefix_default_language в False, для языка по умолчанию (LANGUAGE_CODE) префикс не добавляется. Это может пригодиться при добавлении переводов к существующему сайту, чтобы его текущие URL не изменились.

Пример шаблонов URL:

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

from about import views as about_views
from news import views as news_views
from sitemap.views import sitemap

urlpatterns = [
    path("sitemap.xml", sitemap, name="sitemap-xml"),
]

news_patterns = (
    [
        path("", news_views.index, name="index"),
        path("category/<slug:slug>/", news_views.category, name="category"),
        path("<slug:slug>/", news_views.details, name="detail"),
    ],
    "news",
)

urlpatterns += i18n_patterns(
    path("about/", about_views.main, name="about"),
    path("news/", include(news_patterns, namespace="news")),
)

После определения этих шаблонов URL Django автоматически добавит префикс языка к шаблонам URL, добавленным функцией i18n_patterns. Пример:

>>> from django.urls import reverse
>>> from django.utils.translation import activate

>>> activate("en")
>>> reverse("sitemap-xml")
'/sitemap.xml'
>>> reverse("news:index")
'/en/news/'

>>> activate("nl")
>>> reverse("news:detail", kwargs={"slug": "news-slug"})
'/nl/news/news-slug/'

При использовании prefix_default_language=False и LANGUAGE_CODE='en' URL будут такими:

>>> activate("en")
>>> reverse("news:index")
'/news/'

>>> activate("nl")
>>> reverse("news:index")
'/nl/news/'

Предупреждение

i18n_patterns() разрешено использовать только в корневом URLconf. При использовании во включённом URLconf будет вызвано исключение ImproperlyConfigured.

Предупреждение

Убедитесь, что у вас нет шаблонов URL без префикса, которые могут совпасть с автоматически добавляемым префиксом языка.

Перевод шаблонов URL

Шаблоны URL также можно пометить как переводимые с помощью функции gettext_lazy(). Пример:

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

from about import views as about_views
from news import views as news_views
from sitemaps.views import sitemap

urlpatterns = [
    path("sitemap.xml", sitemap, name="sitemap-xml"),
]

news_patterns = (
    [
        path("", news_views.index, name="index"),
        path(_("category/<slug:slug>/"), news_views.category, name="category"),
        path("<slug:slug>/", news_views.details, name="detail"),
    ],
    "news",
)

urlpatterns += i18n_patterns(
    path(_("about/"), about_views.main, name="about"),
    path(_("news/"), include(news_patterns, namespace="news")),
)

После создания переводов функция reverse() будет возвращать URL на активном языке. Пример:

>>> from django.urls import reverse
>>> from django.utils.translation import activate

>>> activate("en")
>>> reverse("news:category", kwargs={"slug": "recent"})
'/en/news/category/recent/'

>>> activate("nl")
>>> reverse("news:category", kwargs={"slug": "recent"})
'/nl/nieuws/categorie/recent/'

Предупреждение

В большинстве случаев лучше использовать переведённые URL только внутри блока шаблонов с префиксом кода языка (с помощью i18n_patterns()), чтобы избежать конфликта между небрежно переведённым URL и непереводимым шаблоном URL.

Обратное разрешение URL в шаблонах

Если в шаблонах выполняется обратное разрешение локализованных URL, всегда используется текущий язык. Чтобы создать ссылку на URL на другом языке, воспользуйтесь тегом шаблона language. Он включает заданный язык для заключённой в тег секции шаблона:

{% load i18n %}

{% get_available_languages as languages %}

{% translate "View this category in:" %}
{% for lang_code, lang_name in languages %}
    {% language lang_code %}
    <a href="{% url 'category' slug=category.slug %}">{{ lang_name }}</a>
    {% endlanguage %}
{% endfor %}

Тег language принимает в качестве единственного аргумента код языка.

Локализация: как создавать языковые файлы

После того как строковые литералы приложения помечены для последующего перевода, необходимо написать (или получить) сами переводы. Вот как это делается.

Файлы сообщений

Первый шаг — создать файл сообщений для нового языка. Файл сообщений — это текстовый файл, представляющий один язык и содержащий все доступные строки перевода и варианты их представления на этом языке. Файлы сообщений имеют расширение .po.

В Django есть инструмент django-admin makemessages, который автоматизирует создание и поддержку этих файлов.

Утилиты gettext

Команда makemessages (а также рассматриваемая далее compilemessages) использует команды из набора инструментов GNU gettext: xgettext, msgfmt, msgmerge и msguniq.

Поддерживается версия утилит gettext не ниже 0.19.

Чтобы создать или обновить файл сообщений, выполните эту команду:

django-admin makemessages -l de

…где de — это имя локали для создаваемого файла сообщений. Например, pt_BR для бразильского португальского, de_AT для австрийского немецкого или id для индонезийского.

Скрипт необходимо запускать из одного из двух мест:

  • Корневой каталог проекта Django (тот, в котором находится manage.py).
  • Корневой каталог одного из приложений Django.

Скрипт проходит по дереву исходного кода проекта или приложения и извлекает все строки, помеченные для перевода (см. раздел Как Django находит переводы и убедитесь, что LOCALE_PATHS настроен правильно). Он создает (или обновляет) файл сообщений в каталоге locale/LANG/LC_MESSAGES. В примере с de файл будет называться locale/de/LC_MESSAGES/django.po.

При запуске makemessages из корневого каталога проекта извлеченные строки автоматически распределяются по соответствующим файлам сообщений. То есть строка, извлеченная из файла приложения, содержащего каталог locale, попадет в файл сообщений в этом каталоге. Строка, извлеченная из файла приложения без каталога locale, попадет в файл сообщений в каталоге, указанном первым в LOCALE_PATHS, либо вызовет ошибку, если LOCALE_PATHS пуст.

По умолчанию django-admin makemessages проверяет все файлы с расширением .html, .txt или .py. Чтобы изменить это поведение по умолчанию, используйте параметр --extension или -e, чтобы указать расширения файлов для проверки:

django-admin makemessages -l de -e txt

Разделяйте несколько расширений запятыми и/или используйте -e или --extension несколько раз:

django-admin makemessages -l de -e html,txt -e xml

Предупреждение

При создании файлов сообщений из исходного кода JavaScript необходимо использовать специальный домен djangojs, а не -e js.

Используете шаблоны Jinja2?

makemessages не понимает синтаксис шаблонов Jinja2. Чтобы извлечь строки из проекта, содержащего шаблоны Jinja2, вместо этого используйте извлечение сообщений из Babel.

Вот пример файла конфигурации babel.cfg:

# Extraction from Python source files
[python: **.py]

# Extraction from Jinja2 templates
[jinja2: **.jinja]
extensions = jinja2.ext.with_

Не забудьте перечислить все используемые расширения! Иначе Babel не распознает теги, определенные этими расширениями, и полностью проигнорирует содержащие их шаблоны Jinja2.

Babel предоставляет возможности, аналогичные makemessages, в целом может заменить эту команду и не зависит от gettext. Дополнительные сведения см. в документации Babel о работе с каталогами сообщений.

Нет gettext?

Если утилиты gettext не установлены, команда makemessages создаст пустые файлы. В таком случае установите утилиты gettext или скопируйте английский файл сообщений (locale/en/LC_MESSAGES/django.po), если он доступен, и используйте его в качестве отправной точки — это пустой файл перевода.

Работаете в Windows?

Если вы используете Windows и вам нужно установить утилиты GNU gettext, чтобы команда makemessages работала, дополнительные сведения см. в разделе gettext в Windows.

Каждый файл .po содержит небольшой объем метаданных, например контактные данные ответственного за перевод, однако основная часть файла — это список сообщений: соответствий между строками перевода и фактическим текстом перевода на определенный язык.

Например, если ваше приложение Django содержит строку перевода для текста "Welcome to my site.", например:

_("Welcome to my site.")

…то команда django-admin makemessages создаст файл .po со следующим фрагментом — сообщением:

#: path/to/python/module.py:23
msgid "Welcome to my site."
msgstr ""

Краткое пояснение:

  • msgid — это строка перевода, которая встречается в исходном коде. Не изменяйте ее.
  • msgstr — место для перевода на соответствующий язык. Изначально оно пустое, поэтому заполнить его должны вы. Не удаляйте кавычки вокруг перевода.
  • Для удобства каждое сообщение содержит имя файла и номер строки, из которых была получена строка перевода. Эти данные приводятся в виде строки комментария, начинающейся с # и расположенной над строкой msgid.

Длинные сообщения — особый случай. В них первая строка сразу после msgstr (или msgid) представляет собой пустую строку. Затем сам текст записывается в следующих строках — по одной строке на каждую часть. Эти строки объединяются напрямую. Не забывайте о пробелах в конце строк, иначе они склеятся без пробелов!

Учитывайте кодировку

Из-за особенностей внутренней работы инструментов gettext и поскольку мы хотим разрешить использовать в ядре Django и ваших приложениях исходные строки, содержащие символы не из ASCII, файлы .po необходимо кодировать в UTF-8 (это значение используется по умолчанию при создании файлов .po). Так у всех будет одна и та же кодировка, что важно при обработке Django файлов .po.

Неуточненные записи

Иногда makemessages создает записи перевода с пометкой fuzzy, например когда переводы предполагаются на основе ранее переведенных строк. По умолчанию compilemessages не обрабатывает такие записи.

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

django-admin makemessages -a

Компиляция файлов сообщений

После создания файла сообщений, а также после каждого его изменения необходимо скомпилировать его в более эффективный формат для использования в gettext. Для этого воспользуйтесь утилитой django-admin compilemessages.

Этот инструмент обрабатывает все доступные файлы .po и создает файлы .mo — двоичные файлы, оптимизированные для использования в gettext. В том же каталоге, из которого вы запускали django-admin makemessages, выполните django-admin compilemessages следующим образом:

django-admin compilemessages

Вот и все. Переводы готовы к использованию.

Работаете в Windows?

Если вы используете Windows и вам нужно установить утилиты GNU gettext, чтобы команда django-admin compilemessages работала, дополнительные сведения см. в разделе gettext в Windows.

Файлы .po: кодировка и использование BOM.

Django поддерживает только файлы .po в кодировке UTF-8 и без BOM (метки порядка байтов). Если ваш текстовый редактор по умолчанию добавляет такие метки в начало файлов, измените его настройки.

Устранение неполадок: gettext() ошибочно обнаруживает python-format в строках со знаками процента

В некоторых случаях, например в строках со знаком процента, за которым следуют пробел и тип преобразования строки (например, _("10% interest")), функция gettext() ошибочно помечает строки как содержащие python-format.

Если попытаться скомпилировать файлы сообщений с такими ошибочно помеченными строками, появится сообщение об ошибке, например number of format specifications in 'msgid' and 'msgstr' does not match или 'msgstr' is not a valid Python format string, unlike 'msgid'.

Чтобы обойти эту проблему, можно экранировать знаки процента, добавив второй знак процента:

from django.utils.translation import gettext as _

output = _("10%% interest")

Или можно использовать no-python-format, чтобы все знаки процента считались литералами:

# xgettext:no-python-format
output = _("10% interest")

Создание файлов сообщений из исходного кода JavaScript

Файлы сообщений создаются и обновляются так же, как и другие файлы сообщений Django, — с помощью инструмента django-admin makemessages. Отличие лишь в том, что необходимо явно указать то, что в терминологии gettext называется доменом: в данном случае домен djangojs. Для этого передайте параметр -d djangojs, как показано ниже:

django-admin makemessages -d djangojs -l de

Эта команда создаст или обновит файл сообщений JavaScript для немецкого языка. После обновления файлов сообщений выполните django-admin compilemessages так же, как и для обычных файлов сообщений Django.

gettext в Windows

Это нужно только тем, кто хочет извлекать идентификаторы сообщений или компилировать файлы сообщений (.po). Для перевода достаточно редактировать существующие файлы этого типа, но если вы хотите создавать собственные файлы сообщений, проверять их или компилировать измененный файл, скачайте предварительно скомпилированный установщик.

Можно также использовать двоичные файлы gettext, полученные из другого источника, если команда xgettext --version работает правильно. Не пытайтесь использовать утилиты перевода Django с пакетом gettext, если команда xgettext --version, введенная в командной строке Windows, вызывает всплывающее окно с сообщением «xgettext.exe has generated errors and will be closed by Windows».

Настройка команды makemessages

Чтобы передать дополнительные параметры команде xgettext, необходимо создать пользовательскую команду makemessages и переопределить ее атрибут xgettext_options:

from django.core.management.commands import makemessages


class Command(makemessages.Command):
    xgettext_options = makemessages.Command.xgettext_options + ["--keyword=mytrans"]

Если требуется большая гибкость, можно также добавить новый аргумент в пользовательскую команду makemessages:

from django.core.management.commands import makemessages


class Command(makemessages.Command):
    def add_arguments(self, parser):
        super().add_arguments(parser)
        parser.add_argument(
            "--extra-keyword",
            dest="xgettext_keywords",
            action="append",
        )

    def handle(self, *args, **options):
        xgettext_keywords = options.pop("xgettext_keywords")
        if xgettext_keywords:
            self.xgettext_options = makemessages.Command.xgettext_options[:] + [
                "--keyword=%s" % kwd for kwd in xgettext_keywords
            ]
        super().handle(*args, **options)

Разное

Представление перенаправления set_language

set_language(request) [исходный код]

Для удобства в Django есть представление django.views.i18n.set_language(), которое задает языковые предпочтения пользователя и перенаправляет его на указанный URL или, по умолчанию, на предыдущую страницу.

Чтобы активировать это представление, добавьте следующую строку в URLconf:

path("i18n/", include("django.conf.urls.i18n")),

(Обратите внимание, что в этом примере представление доступно по адресу /i18n/setlang/.)

Предупреждение

Не включайте этот URL в i18n_patterns() — сам URL должен быть независимым от языка, чтобы все работало правильно.

Представление ожидает вызова методом POST с параметром language в запросе. Если поддержка сессий включена, представление сохраняет выбранный язык в сессии пользователя. Оно также сохраняет выбор языка в cookie, по умолчанию называемом django_language. (Это имя можно изменить с помощью настройки LANGUAGE_COOKIE_NAME.)

После выбора языка Django ищет параметр next в данных POST или GET. Если такой параметр найден и Django считает указанный URL безопасным (то есть он не указывает на другой хост и использует безопасную схему), будет выполнено перенаправление на этот URL. В противном случае Django может перенаправить пользователя на URL из заголовка Referer или, если он не задан, на /. Выбор зависит от характера запроса:

  • Если запрос допускает содержимое HTML (согласно HTTP-заголовку Accept), перенаправление выполняется всегда.
  • Если запрос не допускает HTML, перенаправление выполняется только при наличии параметра next. В противном случае возвращается код состояния 204 (нет содержимого).

Пример кода HTML-шаблона:

{% load i18n %}

<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
    <input name="next" type="hidden" value="{{ redirect_to }}">
    <select name="language">
        {% get_current_language as LANGUAGE_CODE %}
        {% get_available_languages as LANGUAGES %}
        {% get_language_info_list for LANGUAGES as languages %}
        {% for language in languages %}
            <option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
                {{ language.name_local }} ({{ language.code }})
            </option>
        {% endfor %}
    </select>
    <input type="submit" value="Go">
</form>

В этом примере Django получает URL страницы, на которую будет перенаправлен пользователь, из контекстной переменной redirect_to.

Явная установка активного языка

Возможно, вам потребуется явно установить активный язык для текущего сеанса. Например, языковые предпочтения пользователя могут извлекаться из другой системы. Вы уже знакомы с функцией django.utils.translation.activate(). Она действует только для текущего потока. Чтобы сохранить язык на время всего сеанса в cookie, установите cookie LANGUAGE_COOKIE_NAME в ответе:

from django.conf import settings
from django.http import HttpResponse
from django.utils import translation

user_language = "fr"
translation.activate(user_language)
response = HttpResponse(...)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)

Обычно следует использовать оба способа: django.utils.translation.activate() меняет язык для текущего потока, а установка cookie сохраняет это предпочтение для будущих запросов.

Использование переводов за пределами представлений и шаблонов

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

Например:

from django.utils import translation


def welcome_translated(language):
    cur_language = translation.get_language()
    try:
        translation.activate(language)
        text = translation.gettext("welcome")
    finally:
        translation.activate(cur_language)
    return text

Вызов этой функции со значением 'de' вернет "Willkommen" независимо от LANGUAGE_CODE и языка, установленного промежуточным ПО.

Особенно полезны следующие функции: django.utils.translation.get_language(), возвращающая язык, используемый в текущем потоке; django.utils.translation.activate(), активирующая каталог переводов для текущего потока; и django.utils.translation.check_for_language(), проверяющая, поддерживается ли указанный язык Django.

Чтобы упростить написание кода, также предусмотрен менеджер контекста django.utils.translation.override(), который сохраняет текущий язык при входе в контекст и восстанавливает его при выходе. С его помощью приведенный выше пример можно записать так:

from django.utils import translation


def welcome_translated(language):
    with translation.override(language):
        return translation.gettext("welcome")

Cookie языка

Для настройки параметров cookie языка можно использовать следующие параметры:

  • LANGUAGE_COOKIE_NAME
  • LANGUAGE_COOKIE_AGE
  • LANGUAGE_COOKIE_DOMAIN
  • LANGUAGE_COOKIE_HTTPONLY
  • LANGUAGE_COOKIE_PATH
  • LANGUAGE_COOKIE_SAMESITE
  • LANGUAGE_COOKIE_SECURE

Примечания по реализации

Особенности перевода в Django

Механизм перевода Django использует стандартный модуль gettext, входящий в состав Python. Если вы знакомы с gettext, возможно, вы заметите следующие особенности того, как Django выполняет перевод:

  • Домен строк — это django или djangojs. Этот домен строк используется для различения разных программ, хранящих данные в общей библиотеке файлов сообщений (обычно /usr/share/locale/). Домен django используется для строк перевода Python и шаблонов и загружается в глобальные каталоги переводов. Домен djangojs используется только для каталогов переводов JavaScript, чтобы они были как можно меньше.
  • Django не использует только xgettext. Он использует оболочки Python для xgettext и msgfmt. В основном это сделано для удобства.

Как Django определяет предпочтительный язык

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

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

Чтобы задать предпочтительный язык для всей установки, укажите LANGUAGE_CODE. Django использует этот язык как перевод по умолчанию — в качестве последней попытки, если с помощью одного из методов, используемых промежуточным ПО локали (см. ниже), не найден более подходящий перевод.

Если вам нужно лишь запустить Django на родном языке, достаточно указать LANGUAGE_CODE и убедиться, что соответствующие файлы сообщений и их скомпилированные версии (.mo) существуют.

Если вы хотите, чтобы каждый пользователь мог указать предпочтительный язык, необходимо также использовать LocaleMiddleware. LocaleMiddleware позволяет выбирать язык на основе данных запроса. Он адаптирует содержимое для каждого пользователя.

Чтобы использовать LocaleMiddleware, добавьте 'django.middleware.locale.LocaleMiddleware' в параметр MIDDLEWARE. Поскольку порядок промежуточного ПО имеет значение, следуйте этим рекомендациям:

  • Убедитесь, что оно входит в число первых установленных компонентов промежуточного ПО.
  • Оно должно идти после SessionMiddleware, поскольку LocaleMiddleware использует данные сессии. Оно также должно идти перед CommonMiddleware, поскольку CommonMiddleware требуется активированный язык для разрешения запрошенного URL.
  • Если вы используете CacheMiddleware, разместите LocaleMiddleware после него.

Например, ваш параметр MIDDLEWARE может выглядеть так:

MIDDLEWARE = [
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.locale.LocaleMiddleware",
    "django.middleware.common.CommonMiddleware",
]

(Дополнительные сведения о промежуточном ПО см. в документации по промежуточному ПО.)

LocaleMiddleware пытается определить предпочтительный язык пользователя, следуя такому алгоритму:

  • Сначала проверяется наличие префикса языка в запрошенном URL. Это выполняется только при использовании функции i18n_patterns в корневом URLconf. Дополнительные сведения о префиксе языка и интернационализации шаблонов URL см. в разделе Интернационализация: в шаблонах URL.
  • Если это не помогло, проверяется наличие cookie.

    Имя используемой cookie задаётся параметром LANGUAGE_COOKIE_NAME. (Имя по умолчанию — django_language.)

  • Если это не помогло, проверяется HTTP-заголовок Accept-Language. Этот заголовок отправляется браузером и сообщает серверу, какие языки вы предпочитаете, в порядке приоритета. Django проверяет языки в заголовке по очереди, пока не найдёт язык с доступными переводами.
  • Если это не помогло, используется глобальный параметр LANGUAGE_CODE.

Примечания:

  • Во всех этих случаях предполагается, что предпочтительный язык указан в виде строки в стандартном формате языка. Например, бразильский португальский — это pt-br.
  • Если базовый язык доступен, но указанный подъязык — нет, Django использует базовый язык. Например, если пользователь указывает de-at (австрийский немецкий), а в Django доступен только de, Django использует de.
  • Можно выбрать только языки, перечисленные в параметре LANGUAGES. Если вы хотите ограничить выбор языка подмножеством предоставляемых языков (поскольку ваше приложение поддерживает не все эти языки), задайте для LANGUAGES список языков. Например:

    LANGUAGES = [
        ("de", _("German")),
        ("en", _("English")),
    ]
    

    В этом примере список языков, доступных для автоматического выбора, ограничен немецким и английским (а также любыми их подъязыками, например de-ch или en-us).

  • Если вы задаёте собственный параметр LANGUAGES, как описано в предыдущем пункте, названия языков можно пометить как строки для перевода, но используйте gettext_lazy() вместо gettext(), чтобы избежать циклического импорта.

    Пример файла настроек:

    from django.utils.translation import gettext_lazy as _
    
    LANGUAGES = [
        ("de", _("German")),
        ("en", _("English")),
    ]
    

После того как LocaleMiddleware определит предпочтительный язык пользователя, это предпочтение становится доступным в виде request.LANGUAGE_CODE для каждого объекта HttpRequest. Это значение можно использовать в коде представления. Пример:

from django.http import HttpResponse


def hello_world(request, count):
    if request.LANGUAGE_CODE == "de-at":
        return HttpResponse("You prefer to read Austrian German.")
    else:
        return HttpResponse("You prefer to read another language.")

Обратите внимание: при статическом переводе (без промежуточного ПО) язык находится в settings.LANGUAGE_CODE, а при динамическом переводе (с промежуточным ПО) — в request.LANGUAGE_CODE.

Как Django обнаруживает переводы

Во время выполнения Django создаёт в памяти единый каталог исходных строк и переводов. Для этого он ищет переводы, следуя алгоритму, который определяет порядок проверки различных путей к файлам для загрузки скомпилированных файлов сообщений (.mo), а также приоритет нескольких переводов одной и той же строки:

  1. Наивысший приоритет имеют каталоги, перечисленные в LOCALE_PATHS: каталоги, указанные раньше, имеют более высокий приоритет, чем указанные позже.
  2. Затем проверяется наличие каталога locale в каждом из установленных приложений, перечисленных в INSTALLED_APPS, и, если он существует, используется. Каталоги приложений, указанных раньше, имеют более высокий приоритет, чем указанные позже.
  3. Наконец, в качестве запасного варианта используется базовый перевод Django из django/conf/locale.

См. также

Поиск переводов строк в ресурсах JavaScript выполняется по похожему, но не идентичному алгоритму. Подробнее см. в разделе JavaScriptCatalog.

Вы также можете поместить пользовательские файлы форматов в каталоги LOCALE_PATHS, если также зададите FORMAT_MODULE_PATH.

Во всех случаях имя каталога, содержащего перевод, должно соответствовать записи имени локали. Например: de, pt_BR, es_AR и т. д. Для непереведённых строк территориальных вариантов языка используются переводы общего языка. Например, для непереведённых строк pt_BR используются переводы pt.

Таким образом, вы можете создавать приложения с собственными переводами и переопределять базовые переводы в своём проекте. Или же можно собрать большой проект из нескольких приложений и поместить все переводы в один общий файл сообщений, предназначенный для этого проекта. Выбор за вами.

Все хранилища файлов сообщений имеют одинаковую структуру. Это:

  • Во всех путях, перечисленных в LOCALE_PATHS в файле настроек, выполняется поиск <language>/LC_MESSAGES/django.(po|mo)
  • $APPPATH/locale/<language>/LC_MESSAGES/django.(po|mo)
  • $PYTHONPATH/django/conf/locale/<language>/LC_MESSAGES/django.(po|mo)

Для создания файлов сообщений используется инструмент django-admin makemessages. А с помощью django-admin compilemessages создаются двоичные файлы .mo, используемые gettext.

Вы также можете запустить django-admin compilemessages --settings=path.to.settings, чтобы компилятор обработал все каталоги, указанные в параметре LOCALE_PATHS.

Использование базового языка, отличного от английского

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

  • gettext предоставляет только две формы множественного числа для исходных сообщений, поэтому, если правила образования множественного числа базового языка отличаются от английских, вам также потребуется предоставить перевод на базовый язык, включающий все формы множественного числа.
  • Если активирован вариант английского языка, а английские строки отсутствуют, резервным языком будет не значение LANGUAGE_CODE проекта, а исходные строки. Например, англоязычный пользователь сайта, где в параметре LANGUAGE_CODE указано испанское значение, а исходные строки написаны по-русски, увидит русский текст, а не испанский.

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/topics/i18n/translation/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API