• 3.1
  • 3.2
  • 5.0
  • Версия документации: 6.1

Перевод

Обзор

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

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

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

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

Примечание

Удостоверьтесь, что вы активировали механизм перевода для вашего проекта, для этого достаточно проверить наличие django.middleware.locale.LocaleMiddleware в параметре конфигурации MIDDLEWARE_CLASSES. Если механизм не активирован, то обратитесь к Как Django определяет языковую настройку.

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

Обычный перевод

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

Примечание

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

  1. Для поддержки интернационального набора символов (Unicode), функция ugettext() гораздо более полезна, чем gettext(). Иногда вам потребуется использовать функцию ugettext_lazy() в качестве стандартного метода выделения переводимой строки в определённом файле. При отсутствии _() в глобальном пространстве имён разработчик сам решает какая функция будет ему наиболее полезна в каждом конкретном случае.

  2. Символ подчёркивания (_) используется в интерактивном интерпретаторе Python и в доктестах в качестве «результата предыдущей операции». Определение глобальной функции _() приведёт к путанице. Явное импортирование ugettext() в виде _() решает эту проблему.

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

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

  • Перевод подстроки выполняется с помощью функции ugettext().

  • Перевод подстроки выполняется с помощью функции ugettext().

В данном примере, текст "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-admin makemessages, не сможет найти эти строки. Далее мы подробно рассмотрим makemessages.)

Строка, передаваемая в _() или ugettext(), может принимать заполнители (placeholders), определённые стандартом языка Python для строк. Пример:

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

Этот метод позволяет переводам для конкретного языка изменить порядок текста-заполнителя. Например, английский перевод может быть «Сегодня 26 ноября», а испанский перевод может быть «Hoy es 26 de noviembre». С заменой заполнителей месяца и дня.

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

Поскольку извлечение строк выполняется командой xgettext, Django поддерживает только синтаксисы, поддерживаемые gettext. Python f-strings не может использоваться напрямую с функциями 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 ""

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

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

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

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

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

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

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

Эта функция очень полезна, когда требуется локализовать 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,
}

Примечание

При использовании ungettext(), проверьте, что вы используете уникальное имя для каждой переменной, указанной в строке. В вышеприведённом примере, обратите внимание на то, как мы используем переменную 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'

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

Иногда слова имеют несколько значений, например, «Май» на английском языке, который относится к названию месяца и глаголу. Чтобы переводчики могли правильно переводить эти слова в разных контекстах, вы можете использовать функцию 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, а не надеяться на механизм их автоматического определения через имя класса:

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)})

Если вам не нравится писать такое длинное имя, как ugettext_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"))

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

Ленивый перевод и перевод множественного числа

При использовании ленивого перевода для строки во множественном числе (n[p]gettext_lazy) вы обычно не знаете аргумент 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)

Объединение строк: string_concat()

Метод 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 иauthor|title asauthor_t%}

Другие блочные теги (например, {% для %} или {%, если %}) не допускаются внутри тега blocktranslate.

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

Этот тег также поддерживает склонение, например:

  • Определите переменную count и свяжите с ней значение счётчика. По этому значению будет выбираться форма склонения.

  • Укажите форму единственного и множественного числа, разделив их тегом {% Multiple %} внутри тегов {%blocktranslate %} и {% endblocktranslate %}.

Пример:

{% blocktranslate count counter=list|length %}
There is {{ counter }} {{ 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 %}

приведет к появлению записи "Первое предложение. Второй абзац." в файле .po по сравнению с "\n Первое предложение.\n Второй абзац.\n", если опция обрезанный не была указана.

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

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

{% 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 %}

Первая фраза «Welcome to our page» будет использовать текущий язык, в то время как вторая будет на Английском.

Другие теги

Эти теги также требуют наличия {% 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, то имеем дело с «right-to-left» языком, т.е.: еврейский, арабский. Если False, то это «left-to-right» язык, т.е.: английский, французский, немецкий и так далее.

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 %}.

В дополнение к стилю списка кортежей из параметра конфигурации 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, т.е. вы можете использовать gettext и остальные функции в JavaScript коде.

Основным решением описанных проблем является представление django.views.i18n.javascript_catalog(), которое выдаёт библиотеку с кодом JavaScript, функции которой реализуют интерфейс gettext, а также массив переведённых строк. Переведённые строки собираются из приложений или Django, в зависимости от того, что вы указали info_dict или URL. Пути, указанные в параметре конфигурации LOCALE_PATHS, также принимаются во внимание.

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

class JavaScriptCatalog

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

Атрибуты

domain

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

packages

Список имен приложений среди установленных приложений. Эти приложения должны содержать каталог «locale». Все эти каталоги, а также все каталоги, найденные в LOCALE_PATHS (которые всегда включены) объединяются в один каталог. По умолчанию установлено значение «Нет», что означает, что все доступные переводы из всех 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",
    ),
]

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

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

from django.conf.urls.i18n import i18n_patterns

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

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

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

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

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

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

Здесь используется запрос на поиск 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, и для проверки используются регулярные выражения. Интерполяция в JavaScript выполняется не так быстро, как на Python, так что используйте её только при реальной необходимости.

get_format

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

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

Предоставляет доступ к следующим настройкам:

Важно использовать форматирование аналогичное форматированию значений в 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 (не множественное).

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

class JSONCatalog

Если вы используете другие клиентские библиотеки для перевода, вам может пригодится представление json_catalog(). Оно работает аналогично javascript_catalog(), но возвращает JSON.

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

Формат ответа следующий:

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

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

Представление javascript_catalog() создаёт каталог из .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 (USE_ETAGS = True), у вас уже всё включено. С другой стороны, вы можете использовать условные декораторы. В следующем примере, кэш сбрасывается при перезапуске сервера приложений.

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-адресов переводимыми с помощью функции django.utils.translation.gettext_lazy().

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

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

Языковой префикс в шаблонах URL

i18n_patterns(*urls, prefix_default_language=True)

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

Установка для 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. Попытка использовать её в других местах приведёт к исключению ImproperlyConfigured.

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

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

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

Шаблоны URL могут быть помечены как подлежащие переводу с помощью функции ugettext_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 только внутри блока, который добавляет языковой префикс для шаблонов (using 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 файлов несложен. Каждый .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 работают внутри, и поскольку мы хотим разрешить использование исходных строк, отличных от 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: кодировка и использование спецификации.

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

Устранение неполадок: gettext() неправильно определяет python-format в строках со знаками процента.

В некоторых случаях, например, строки со знаком процента, за которым следует пробел и тип преобразования string (например, _("10% Interest")), gettext() неправильно помечает строки с python-format.

Если вы попытаетесь скомпилировать файлы сообщений с неправильно помеченными строками, вы получите сообщение об ошибке, например: «Количество спецификаций формата в «msgid» и «msgstr» не совпадает» или «msgstr» не является допустимой строкой формата Python, в отличие от «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, как вы это делаете для обычных файлов сообщений.

gettext на Windows

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

Вы также можете использовать двоичные файлы 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)

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

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

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

(Следует отметить, что данный пример привязывает представление к /i18n/setlang/.)

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

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

Представление ожидает вызова через метод POST с установленным в запросе параметром языка. Представление сохраняет выбор языка в файле 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 (т.е. соответствующий каталог с переводами есть в наличии). Вы можете загрузить каталог переводов, активировать его и переводить текст на нужный язык, но не забудьте вернуться на оригинальный язык, так как активация каталога переводов выполняется на уровне потока и такое изменение будет влиять на код, работающий в том же потоке.

Например:

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

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

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

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

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

Чтобы использовать LocaleMiddleware, добавьте 'django.middleware.locale.LocaleMiddleware' в настройку MIDDLEWARE_CLASSES. Так как порядок middleware важен, используйте следующую инструкцию:

  • Удостоверьтесь, что она указана одной из первых мидлварей.

  • Она должна идти после SessionMiddleware, так как LocaleMiddleware использует сессию. И она должна идти до CommonMiddleware, так как CommonMiddleware нуждается в активном языке для определения запрошенного URL.

  • Если вы используете CacheMiddleware, поместите LocaleMiddleware после неё.

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

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

(Для получения подробностей по мидлварям обратитесь к соответствующей документации.)

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

  • Сначала он ищет префикс языка в запрошенном URL-адресе. Это выполняется только тогда, когда вы используете функцию i18n_patterns в корневой конфигурации URL. См. Интернационализация: в шаблонах URL для получения дополнительной информации о языковом префиксе и о том, как интернационализировать шаблоны URL.

  • Если и с сессией не сложилось, то принимается за cookie.

    Имя cookie определяется параметром конфигурации LANGUAGE_COOKIE_NAME. (Название про умолчанию django_language.)

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

  • Если совсем всё плохо, тогда используется значение параметра конфигурации LANGUAGE_CODE.

Заметим:

  • Везде подразумевается, что значение языка указано в стандартном формате, в виде строки. Например, для бразильского варианта португальского языка это будет pt-br.

  • Если базовый язык доступен, а вариант нет, то Django будет использовать базовый язык. Например, если пользователь указал de-at (австрийский вариант немецкого), но у Django есть только de, то именно он и будет использоваться.

  • Выбор может производиться только из списка, определенного параметром конфигурации LANGUAGES. Если вам надо ограничить диапазон имеющихся языков (потому что ваше приложение не имеет столько переводов), укажите в LANGUAGES список поддерживаемых языков. Например:

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

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

  • Если вы определяете параметр конфигурации LANGUAGES, как было показано выше, вы можете помечать имена языков как переводимые. Но следует использовать ugettext_lazy() вместо ugettext(), чтобы исключить циклический импорт.

    Приведёт пример файла настроек:

    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 строк происходит аналогично, но с небольшими отличиями. Обратитесь к документации на представление javascript_catalog для подробностей.

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

Имя каталога, содержащего перевод, должно быть названо в соответствии соглашению по наименованию локалей. Т.е. de, pt_BR, es_AR и так далее.

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

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

  • Во всех указанных путях в параметре конфигурации 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.

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

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

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

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

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

Back to Top