Перевод

Быстрый обзор

Для того чтобы сделать проект Django переводимым, необходимо добавить минимальное количество крючков в ваш Python-код и шаблоны. Эти крючки называются translation strings. Они говорят Django: «Этот текст должен быть переведен на язык конечного пользователя, если перевод для этого текста доступен на этом языке». Вы несете ответственность за маркировку переводимых строк; система может переводить только те строки, о которых она знает.

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

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

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

Примечание

Существует также независимый, но связанный параметр USE_L10N, который определяет, должен ли Django реализовывать локализацию формата. Более подробную информацию смотрите в Локализация формата.

Примечание

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

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

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

Задайте строку перевода с помощью функции gettext(). Принято использовать более короткий псевдоним _ для экономии набора текста.

Примечание

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

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

Какие функции могут быть алиасированы как _?

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

В этом примере текст "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 :py<<<2 >>> пока не поддерживается <<<3 >>>, а строки шаблонов JavaScript требуют <<<4 >>> 0.21+.

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

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

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

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

Примечание

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

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

Это также работает в шаблонах. См. раздел Комментарии для переводчиков в шаблонах для более подробной информации.

Пометка строк как бесполезных

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

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

Плюрализация

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

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

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

Например:

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

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

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

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

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

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

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

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

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

Примечание

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

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

Вы получите ошибку при выполнении django-admin compilemessages:

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

Контекстуальные маркеры

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

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

Например:

from django.utils.translation import pgettext

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

или:

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

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

появится в файле .po как:

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

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

Ленивый перевод

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

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

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

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

Поля модели и отношения verbose_name и help_text значений опций

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

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

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

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

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

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

Модель verbose имена значения

Рекомендуется всегда предоставлять явные опции verbose_name и verbose_name_plural, а не полагаться на резервное англоцентричное и несколько наивное определение словесных имен, которое Django выполняет, глядя на имя класса модели:

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

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

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

Методы модели short_description значения атрибутов

Для методов модели вы можете предоставить переводы для Django и администратора сайта с помощью атрибута short_description:

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'),
    )

    def is_mouse(self):
        return self.kind.type == MOUSE_TYPE
    is_mouse.short_description = _('Is it a mouse?')

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

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

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

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

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

mark_safe_lazy = lazy(mark_safe, str)

И позже:

lazy_string = mark_safe_lazy(_("<p>My <strong>string!</strong></p>"))

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

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

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

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

Невозможно подмешать шаблонную переменную внутрь строки в рамках {% translate %}. Если ваши переводы требуют строк с переменными (placeholders), используйте {% 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 %} также поддерживает contextual markers, используя ключевое слово context:

{% translate "May" context "month name" %}
Changed in Django 3.1:

Тег trans был переименован в translate. Тег trans по-прежнему поддерживается в качестве псевдонима для обратной совместимости.

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. Это означает, что применяются те же notes regarding ngettext variables.

Обратный поиск 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 %} также поддерживает contextual markers, используя ключевое слово 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 не была указана.

Changed in Django 3.1:

Тег blocktrans был переименован в blocktranslate. Тег blocktrans по-прежнему поддерживается в качестве псевдонима для обратной совместимости.

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

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

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

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

Примечание

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

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

Как и в случае с Python code, эти примечания для переводчиков могут быть указаны с помощью комментариев, либо с помощью тега comment:

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

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

или с помощью {##} one-line comment constructs:

{# 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 %} возвращает список кортежей, в котором первым элементом является language code, а вторым - имя языка (переведенное в текущую активную локаль).

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). См. the section about the set_language redirect view пример отображения селектора языков с помощью {% 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[исходный код]

Представление, которое создает библиотеку кода 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'),
]

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

Пример с i18n_patterns():

from django.conf.urls.i18n import i18n_patterns

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

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

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

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

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

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

Здесь используется обратный поиск URL для нахождения 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'
    
  • Именованная интерполяция: Этот режим выбирается путем передачи необязательного параметра boolean 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'

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

Это полезно для поддержания согласованности форматирования со значениями, отображаемыми в 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[исходный код]

Для того чтобы использовать другую библиотеку на стороне клиента для обработки переводов, вы можете воспользоваться представлением 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='js18n-%s' % get_version())(JavaScriptCatalog.as_view()),
         name='javascript-catalog'),
]

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

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)[исходный код]

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

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

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

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

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

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

Утилиты Gettext

Команда makemessagescompilemessages, о которой речь пойдет позже) используют команды из набора инструментов GNU gettext: xgettext, msgfmt, msgmerge и msguniq.

Минимальная версия поддерживаемых утилит gettext - 0.15.

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

django-admin makemessages -l de

…где de - это locale name для файла сообщения, который вы хотите создать. Например, 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

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

При creating message files from JavaScript source code необходимо использовать специальный домен djangojs, не -e js.

Использование шаблонов Jinja2?

makemessages не понимает синтаксис шаблонов Jinja2. Для извлечения строк из проекта, содержащего шаблоны Jinja2, используйте Message Extracting из 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. Для получения дополнительной информации читайте документацию по working with message catalogs.

Нет gettext?

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

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

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

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

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

_("Welcome to my site.")

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

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

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

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

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

Обратите внимание на свою кодировку

В связи с тем, как работают инструменты gettext и потому, что мы хотим разрешить использование в ядре Django и ваших приложениях исходных строк, отличных от ASCII, вы должны использовать 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 в строках со знаками процента

В некоторых случаях, например, в строках со знаком процента, за которым следует пробел и string conversion type (например, _("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). Сама работа по переводу подразумевает редактирование существующих файлов такого типа, но если вы хотите создать свои собственные файлы сообщений, или хотите протестировать или скомпилировать измененный файл сообщений, загрузите a precompiled binary installer.

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

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

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

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

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

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

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

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

  • Если запрос принимает HTML-контент (на основании HTTP-заголовка Accept), то откат всегда будет выполнен.
  • Если запрос не принимает HTML, откат будет выполнен, только если был установлен параметр next. В противном случае будет возвращен код состояния 204 (No Content).
Changed in Django 3.1:

В старых версиях различие для отката основано на том, установлено ли для заголовка X-Requested-With значение XMLHttpRequest. Это значение устанавливается методом jQuery ajax().

Вот пример 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 заставляет это предпочтение сохраняться в будущих запросах.

Changed in Django 3.0:

В старых версиях можно было установить язык в текущем сеансе.

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

Хотя 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 и языка, установленного промежуточным ПО.

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

Если вы хотите запускать Django с вашим родным языком, все, что вам нужно сделать, это установить LANGUAGE_CODE и убедиться в существовании соответствующих message files и их скомпилированных версий (.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',
]

(Подробнее о промежуточном ПО см. в middleware documentation).

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

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

  • Если это не удается, он ищет печенье.

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

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

  • В противном случае используется глобальная настройка LANGUAGE_CODE.

Примечания:

  • В каждом из этих мест предпочтение языка ожидается в стандарте language format, в виде строки. Например, бразильский португальский - 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 строит в памяти единый каталог литералов-переводов. Для этого он ищет переводы, следуя следующему алгоритму относительно порядка, в котором он рассматривает различные пути к файлам для загрузки скомпилированного message files (.mo) и приоритета нескольких переводов для одного литерала:

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

См.также

Переводы литералов, включенных в JavaScript-активы, ищутся по аналогичному, но не идентичному алгоритму. Более подробную информацию см. в разделе JavaScriptCatalog.

Вы также можете поместить custom format files в каталоги LOCALE_PATHS, если вы также установите FORMAT_MODULE_PATH.

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

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

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

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

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

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

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

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

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