Spec-Zone.ru › Django 4.1

Перевод

Обзор

Для того, чтобы проект 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_name модели

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

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

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

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

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

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

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

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

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

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

Результат вызова gettext_lazy() может быть использован везде, где вы бы использовали строку (объект 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(lang_code) [source]

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

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

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

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

Международные настройки: в коде шаблонов

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

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

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

translate тег шаблона

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

blocktranslate тег шаблона

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

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

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

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

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

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

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

Примечание

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

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

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

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

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

Пример:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Переменные-литералы, переданные в теги и фильтры

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

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

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

Примечание

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

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

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

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

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

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

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

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

Примечание

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

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

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

# ...

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

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

Изменение языка в шаблонах

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

{% load i18n %}

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

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

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

Другие теги

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

get_available_languages

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

get_current_language

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

get_current_language_bidi

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

i18n процессор контекста

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

get_language_info

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

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

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

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

get_language_info_list

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

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

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

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

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

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

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

  • {{ LANGUAGE_CODE|language_name }} («Немецкий»)
  • {{ LANGUAGE_CODE|language_name_local }} («Deutsch»)
  • {{ LANGUAGE_CODE|language_bidi }} (false)
  • {{ 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'
    
  • Именованная интерполяция: Этот режим выбирается путем передачи необязательного булевого параметра 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='jsi18n-%s' % get_version())(JavaScriptCatalog.as_view()),
         name='javascript-catalog'),
]

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

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

last_modified_date = timezone.now()

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

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

Международная локализация: в шаблонах URL

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

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

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

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

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

i18n_patterns(*urls, prefix_default_language=True)

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

Нет утилит Gettext?

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

Работа на Windows?

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

END_OF_DOCUMENT_MARKER

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

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

_("Welcome to my site.")

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

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

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

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

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

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

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

Если вам нужно запустить 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.1/topics/i18n/translation/

Spec-Zone.ru

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