Spec-Zone.ru › Django 4.0

Перевод

Обзор

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

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

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

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

Примечание

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

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

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

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

Примечание

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

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

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

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

  • gettext()
  • gettext_lazy()

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

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

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

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

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

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

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

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

Перевод работает с переменными. Опять же, вот идентичный пример:

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

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

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

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

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

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

Поскольку извлечение строк выполняется командой xgettext , Django поддерживает только синтаксисы, поддерживаемые gettext. В частности, Python f-строки пока не поддерживаются xgettext, а JavaScript шаблоны строк требуют gettext 0.21+.

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

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

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

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

Примечание

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

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

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

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

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

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

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

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

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

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

Например:

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

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

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

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

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

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

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

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

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

Примечание

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

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

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

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

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

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

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

Например:

from django.utils.translation import pgettext

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

или:

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

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

будут отображаться в файле .po как:

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

Поддержка контекстуальных маркеров также реализована в тегах шаблонов translate и blocktranslate.

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

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

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

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

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

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

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

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

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

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

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

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

Значения verbose_names модели

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

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

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

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

Методы модели description аргумент декоратора @display

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy
...
name = gettext_lazy('John Lennon')
instrument = gettext_lazy('guitar')
result = format_lazy('{name}: {instrument}', name=name, instrument=instrument)

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

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

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

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 используют два тега шаблонов и немного другой синтаксис, чем в Python-коде. Чтобы предоставить шаблону доступ к этим тегам, поместите {% load i18n %} в начало вашего шаблона. Как и все теги шаблонов, этот тег должен быть загружен во всех шаблонах, использующих переводы, даже в тех, которые расширяют другие шаблоны, которые уже загрузили тег i18n.

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

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

translate тег шаблона

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

blocktranslate тег шаблона

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

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

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

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

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

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

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

Примечание

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

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

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

Этот тег также обеспечивает работу с множественным числом. Для его использования:

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

Пример:

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

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

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

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

Обратный поиск URL-адресов не может быть выполнен внутри тега blocktranslate и должен быть получен (и сохранён) предварительно:

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

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

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

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

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

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

Другая функция, которую поддерживает {% blocktranslate %} — это опция trimmed. Эта опция удалит символы новой строки из начала и конца содержимого тега "First sentence. Second paragraph.", заменит все пробелы в начале и конце строки и объединит все строки в одну, используя пробел в качестве разделителя. Это очень полезно для отступа содержимого тега {% blocktranslate %} без отображения символов отступа в соответствующей записи в файле PO, что упрощает процесс перевода.

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

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

приведёт к записи "First sentence. Second paragraph." в файле PO по сравнению с "\n  First sentence.\n  Second paragraph.\n", если опция trimmed не была указана.

Переданные строковые литералы в теги и фильтры

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

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

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

Примечание

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

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

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

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

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

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

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

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

Примечание

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

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

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

# ...

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

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

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

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

{% load i18n %}

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

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

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

Другие теги

Эти теги также требуют {% load i18n %}.

get_available_languages

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

get_current_language

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

get_current_language_bidi

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

i18n обработчик контекста

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

get_language_info

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

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

Затем вы можете получить доступ к информации:

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

get_language_info_list

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

В дополнение к списку кортежей в стиле LANGUAGES, {% get_language_info_list %} поддерживает списки кодов языков. Если вы сделаете это в своём представлении:

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

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

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

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

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

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

Международный язык: в коде JavaScript

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

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

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

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

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

class JavaScriptCatalog

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

Атрибуты

domain

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

packages

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

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

from django.views.i18n import JavaScriptCatalog

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

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

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

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

Пример с i18n_patterns():

from django.conf.urls.i18n import i18n_patterns

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

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

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

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

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

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

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

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

gettext

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

document.write(gettext('this is to be translated'));

ngettext

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

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

interpolate

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

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

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

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

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

get_format

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

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

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

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

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

gettext_noop

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

document.write(gettext_noop('this will not be translated'));

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

pgettext

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

document.write(pgettext('month name', 'May'));

npgettext

Функция npgettext также ведет себя как Python-аналог (npgettext()), предоставляя **склоненное** контекстно-зависимое слово:

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

pluralidx

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

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

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

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

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

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

class JSONCatalog

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

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 ожидает код языка как единственный аргумент.

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

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

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

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

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

Утилиты Gettext

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

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

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

django-admin makemessages -l de

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

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

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

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

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

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

django-admin makemessages -l de -e txt

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

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

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

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

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

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

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

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

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

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

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

Нет утилит Gettext?

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

Работа на Windows?

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

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

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

_("Welcome to my site.")

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

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

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

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

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

Учтите кодировку

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

Нечёткие записи

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

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

django-admin makemessages -a

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

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

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

django-admin compilemessages

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

Работа на Windows?

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

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

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

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

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

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

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

from django.utils.translation import gettext as _
output = _("10%% interest")

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

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

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

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

django-admin makemessages -d djangojs -l de

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

gettext на Windows

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

Вы также можете использовать gettext бинарные файлы, полученные из других источников, при условии, что команда xgettext --version работает должным образом. Не пытайтесь использовать утилиты перевода Django с пакетом gettext, если команда xgettext --version в командной строке Windows вызывает всплывающее окно с сообщением «xgettext.exe сгенерировал ошибки и будет закрыт Windows».

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

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

from django.core.management.commands import makemessages

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

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

from django.core.management.commands import makemessages

class Command(makemessages.Command):

    def add_arguments(self, parser):
        super().add_arguments(parser)
        parser.add_argument(
            '--extra-keyword',
            dest='xgettext_keywords',
            action='append',
        )

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

Разное

Предварительно настроенная функция переадресации для выбора языка

set_language(request)

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

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

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

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

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

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

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

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

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

Вот пример кода HTML-шаблона:

{% load i18n %}

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

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

Явное задание активного языка

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

from django.conf import settings
from django.http import HttpResponse
from django.utils import translation
user_language = 'fr'
translation.activate(user_language)
response = HttpResponse(...)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)

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

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

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

Например:

from django.utils import translation

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

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

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

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

from django.utils import translation

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

Cookie языка

Можно использовать ряд настроек для изменения параметров cookie языка:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

(Подробнее о middleware см. документацию по middleware.)

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

  • Сначала он ищет префикс языка в запрошенном URL. Это выполняется только при использовании функции i18n_patterns в вашем корневом URLconf. Подробнее об использовании префикса языка и о том, как интернационализировать URL-шаблоны, см. Международный язык в URL-шаблонах.
  • Если это не удаётся, ищется cookie.

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

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

Примечания:

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

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

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

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

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

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

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

from django.http import HttpResponse

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

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

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

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

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

См. также

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

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

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

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

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

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

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

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

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

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

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

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

Spec-Zone.ru

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