Spec-Zone.ru › Django 5.2

Перевод

Обзор

Для того, чтобы проект 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. В частности, Python f-строки пока не поддерживаются xgettext, а 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 ""

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

Помечать строки как no-op

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

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

Множественное число

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

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

Эта функция полезна, когда вам нужно, чтобы ваше приложение Django было локализовано на языках, где количество и сложность форм множественного числа превышает два форма, используемых в английском языке («объект» для единственного числа и «объекты» для всех случаев, когда 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, вы должны предоставить текст verbose name в нижнем регистре для связи, так как Django автоматически приведёт его к верблюжьему регистру при необходимости.

Значения verbose names модели

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

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() может быть использован там, где вы бы использовали строку (объект str) в другом коде Django, но он может не работать с произвольным 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 используется в строке (обычно во время рендеринга шаблона).

Другие способы использования lazy для отложенных переводов

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

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) [source]

Функция 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). Смотрите раздел о представлении перенаправления set_language для примера отображения селектора языка с использованием {% get_language_info_list %}.

В дополнение к списку кортежей в стиле 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 }} (Ложь)
  • {{ LANGUAGE_CODE|language_name_translated }} («německy», если активный язык чешский)

Международная локализация: в коде JavaScript

Добавление переводов в JavaScript создает некоторые проблемы:

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

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

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

Вид JavaScriptCatalog

class JavaScriptCatalog [source]

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

Атрибуты

domain

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

packages

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

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

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",
    ),
]

Если ваша основная конфигурация URL использует 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 для поиска URL-адреса вида каталога JavaScript. После загрузки каталога ваш код 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

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

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

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

Вид JSONCatalog

class JSONCatalog [source]

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

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

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

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

Заметка о производительности

Различные JavaScript/JSON-представления i18n генерируют каталог из файлов .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",
    ),
]

Кэширование на стороне клиента сэкономит пропускную способность и ускорит загрузку сайта. Если вы используете ETags (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().

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

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

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

i18n_patterns(*urls, prefix_default_language=True) [source]

Эта функция может быть использована в корневом 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 на другом языке используйте тег шаблона 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. Для получения дополнительной информации ознакомьтесь с документацией по работе с каталогами сообщений.

Нет утилит 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) — это пустая строка. Затем само содержимое будет записано в следующих строках как по одной строке на каждое значение. Эти строки конкатенируются напрямую. Не забывайте о trailing spaces в строках; в противном случае они будут соединены без пробелов!

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

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

Размытые записи

makemessages иногда генерирует записи перевода, помеченные как размытые, например, когда переводы выводятся из ранее переведённых строк. По умолчанию размытые записи не обрабатываются 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 (Byte Order Mark), поэтому если ваш текстовый редактор добавляет такие метки в начало файлов по умолчанию, вам нужно будет его перенастроить.

Отладка: 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 сгенерировал ошибки и будет закрыт 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) [source]

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

Активируйте это представление, добавив следующую строку в ваш файл URLconf:

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

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

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

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

Представление ожидает вызова через метод POST с параметром language, установленным в request. Если поддержка сеансов включена, представление сохраняет выбор языка в сеансе пользователя. Оно также сохраняет выбор языка в 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 предоставляет богатый набор инструментов i18n для использования в представлениях и шаблонах, он не ограничивает использование только кодом 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 и языка, установленного middleware.

Функции, представляющие особый интерес, это 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. А для получения двоичных файлов .mo, используемых gettext, используйте django-admin compilemessages.

Вы также можете запустить 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/5.2/topics/i18n/translation/

Spec-Zone.ru

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