Перевод

Введение

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

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

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

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

Примечание

Есть также независимый, но связанный параметр USE_L10N, который управляет применением локального форматирования для данных. Обратитесь к Формат локализации для получения подробностей.

Примечание

Make sure you’ve activated translation for your project (the fastest way is to check if MIDDLEWARE includes django.middleware.locale.LocaleMiddleware). If you haven’t yet, see Как Django определяет языковую настройку.

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

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

Specify a translation string by using the function gettext(). It’s convention to import this as a shorter alias, _, to save typing.

Примечание

The u prefixing of gettext functions was originally to distinguish usage between unicode strings and bytestrings on Python 2. For code that supports only Python 3, they can be used interchangeably. A deprecation for the prefixed functions may happen in a future Django release.

Примечание

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

  1. Sometimes, you should use gettext_lazy() as the default translation method for a particular file. Without _() in the global namespace, the developer has to think about which is the most appropriate translation function.
  2. The underscore character (_) is used to represent «the previous result» in Python’s interactive shell and doctest tests. Installing a global _() function causes interference. Explicitly importing gettext() as _() avoids this problem.

What functions may be aliased as _?

Because of how xgettext (used by makemessages) works, only functions that take a single string argument can be imported as _:

В данном примере, текст "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)

You could code this without using the alias. This example is identical to the previous one:

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

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

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

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

Перевод работает с переменными. И снова, аналогичный пример:

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

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

The strings you pass to _() or gettext() can take placeholders, specified with Python’s standard named-string interpolation syntax. Example:

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

Since string extraction is done by the xgettext command, only syntaxes supported by gettext are supported by Django. Python f-strings and JavaScript template strings are not yet supported by xgettext.

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

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

Use the function django.utils.translation.gettext_noop() to mark a string as a translation string without translating it. The string is later translated from a variable.

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

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

Use the function django.utils.translation.ngettext() to specify pluralized messages.

ngettext() takes three arguments: the singular translation string, the plural translation string and the number of objects.

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

Например:

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

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

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

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

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

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

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

Don’t try to implement your own singular-or-plural logic; it won’t be correct. In a case like this, consider something like the following:

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

Примечание

When using ngettext(), make sure you use a single name for every extrapolated variable included in the literal. In the examples above, note how we used the name Python variable in both translation strings. This example, besides being incorrect in some languages as noted above, would fail:

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

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

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

Примечание

Множественная форма и файлы перевода

Django не поддерживает собственные правила множественного числа в файлах перевода. Так как все файлы перевода объединяются вместе, будет использоваться только правило главного файла перевода Django (из django/conf/locale/<lang_code>/LC_MESSAGES/django.po). Правила множественной формы всех остальных *.po файлов будут проигнорированы. Поэтому вам не следует использовать их в вашем проекте и приложении.

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

Некоторые слова имеют множество значений, такие как "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 ""

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

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

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

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

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

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

Поля модели и связанные с ними значения атрибутов verbose_name и help_text

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

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

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

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

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

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

Значения для подписи модели

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

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

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

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

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

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

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

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

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

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

The result of a gettext_lazy() call can be used wherever you would use a string (a str object) in other Django code, but it may not work with arbitrary Python code. For example, the following won’t work because the requests library doesn’t handle gettext_lazy objects:

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

You can avoid such problems by casting gettext_lazy() objects to text strings before passing them to non-Django code:

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

If you don’t like the long gettext_lazy name, you can alias it as _ (underscore), like so:

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

Using gettext_lazy() and ngettext_lazy() to mark strings in models and utility functions is a common operation. When you’re working with these objects elsewhere in your code, you should ensure that you don’t accidentally convert them to strings, because they should be converted as late as possible (so that the correct locale is in effect). This necessitates the use of the helper function described next.

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

Используя ленивый перевод для срок с множественным числом ([u]n[p]gettext_lazy), вы не знаете значение аргумента number при определении строки. Однако, вы можете использовать аргумент number вместо числа. При определении перевода number будет определяться из переданных аргументов. Например:

from django import forms
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 forms.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 forms.ValidationError(self.error_message % number)

Formatting strings: format_lazy()

Python’s str.format() method will not work when either the format_string or any of the arguments to str.format() contains lazy translation objects. Instead, you can use django.utils.text.format_lazy(), which creates a lazy object that runs the str.format() method only when the result is included in a string. For example:

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

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

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

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

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

mark_safe_lazy = lazy(mark_safe, str)

А затем:

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

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

get_language_info()

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

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

The name, name_local, and name_translated attributes of the dictionary contain the name of the language in English, in the language itself, and in your current active language respectively. The bidi attribute is True only for bi-directional languages.

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

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

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

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

Translated strings will not be escaped when rendered in a template. This allows you to include HTML in translations, for example for emphasis, but potentially dangerous characters (e.g. ") will also be rendered unchanged.

Шаблонный тег trans

Шаблонный тег {% trans %} может переводить как обычную строку, заключенную в одинарные или двойные кавычки, так и содержимое переменой:

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

При использовании опции noop, обращение к переменной происходит, но перевод не выполняется. Это удобно, когда надо пометить контент для перевода в будущем:

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

Internally, inline translations use an gettext() call.

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

Невозможно использовать шаблонные переменные внутри строки для тега {% trans %}. Если же ваш перевод требует наличия переменой в строке, используйте шаблонный тег {% blocktrans %}.

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

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

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

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

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

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

Тег {% trans %} также поддерживает контекстные маркеры с помощью атрибута context:

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

Шаблонный тег blocktrans

В отличии от тега trans, тег blocktrans позволяет отмечать сложные предложения, состоящие из строк и переменных, обеспечивая перевод с помощью подстановок:

{% blocktrans %}This string will have {{ value }} inside.{% endblocktrans %}

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

{% blocktrans with amount=article.price %}
That will cost $ {{ amount }}.
{% endblocktrans %}

{% blocktrans with myvar=value|filter %}
This will have {{ myvar }} inside.
{% endblocktrans %}

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

{% blocktrans with book_t=book|title author_t=author|title %}
This is {{ book_t }} by {{ author_t }}
{% endblocktrans %}

Примечание

Как и прежде, тег поддерживает старое форматирование: {% blocktrans with book|title as book_t and author|title as author_t %}

Внутри тега blocktrans запрещается использовать другие блочные теги (например {% for %} или {% if %}).

If resolving one of the block arguments fails, blocktrans will fall back to the default language by deactivating the currently active language temporarily with the deactivate_all() function.

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

  • Определите переменную count и свяжите с ней значение счётчика. По этому значению будет выбираться форма склонения.
  • Укажите единственную и множественную формы, разделив их с помощью тега {% plural %} внутри тегов {% blocktrans %} и {% endblocktrans %}.

Пример:

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

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

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

When you use both the pluralization feature and bind values to local variables in addition to the counter value, keep in mind that the blocktrans construct is internally converted to an ngettext call. This means the same notes regarding ngettext variables apply.

Обратное разрешение URL не можно быть выполнено внутри blocktrans и должно выполняться заранее:

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

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

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

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

Тег {% blocktrans %} также поддерживает контекстные маркеры с помощью атрибута context:

{% blocktrans with name=user.username context "greeting" %}Hi {{ name }}{% endblocktrans %}

Другой особенностью {% blocktrans %} является поддержка опции trimmed. Эта опция удаляет символы завершения строки из начала и конца содержимого данного тега, убирая пробелы в начале и конце строк и объединяя все строки в одну, разделяя их пробелами. Это очень удобно при форматировании контента тега с помощью отступов, так как эти пробелы не попадают в содержимое PO файлов, упрощая процесс перевода.

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

{% blocktrans trimmed %}
  First sentence.
  Second paragraph.
{% endblocktrans %}

выразится в записи``»First sentence. Second paragraph.»`` внутри PO файла, что несравнимо с "\n  First sentence.\n  Second sentence.\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 %}
{% trans "View" %}

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

или с помощью {##} однострочного комментария:

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

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

Примечание

Для полноты изложения приведём соответствующий фрагмент .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>{% trans "Welcome to our page" %}</p>

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

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

Другие теги

Эти теги также требуют наличия {% load i18n %} в шаблоне.

get_available_languages

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

get_current_language

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

get_current_language_bidi

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

i18n context processor

If you enable the django.template.context_processors.i18n context processor, then each RequestContext will have access to LANGUAGES, LANGUAGE_CODE, and LANGUAGE_BIDI as defined above.

get_language_info

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

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

Затем вы можете узнать следующее:

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

get_language_info_list

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

In addition to LANGUAGES style list of tuples, {% get_language_info_list %} supports lists of language codes. If you do this in your view:

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

Шаблонные фильтры

There are also some filters available for convenience:

  • {{ 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 коде.

The main solution to these problems is the following JavaScriptCatalog view, which generates a JavaScript code library with functions that mimic the gettext interface, plus an array of translation strings.

The JavaScriptCatalog view

class JavaScriptCatalog

A view that produces a JavaScript code library with functions that mimic the gettext interface, plus an array of translation strings.

Attributes

domain

Translation domain containing strings to add in the view output. Defaults to 'djangojs'.

packages

A list of application names among installed applications. Those apps should contain a locale directory. All those catalogs plus all catalogs found in LOCALE_PATHS (which are always included) are merged into one catalog. Defaults to None, which means that all available translations from all INSTALLED_APPS are provided in the JavaScript output.

Example with default values:

from django.views.i18n import JavaScriptCatalog

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

Example with custom packages:

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

If your root URLconf uses i18n_patterns(), JavaScriptCatalog must also be wrapped by i18n_patterns() for the catalog to be correctly generated.

Example with i18n_patterns():

from django.conf.urls.i18n import i18n_patterns

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

The precedence of translations is such that the packages appearing later in the packages argument have higher precedence than the ones appearing at the beginning. This is important in the case of clashing translations for the same literal.

If you use more than one JavaScriptCatalog view on a site and some of them define the same strings, the strings in the catalog that was loaded last take precedence.

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

To use the catalog, pull in the dynamically generated script like this:

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

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

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

gettext

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

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

ngettext

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

var object_count = 1 // or 0, or 2, or 3, ...
s = ngettext('literal for the singular case',
        'literal for the plural case', object_count);

interpolate

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

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

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

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

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

get_format

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

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

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

Важно использовать форматирование аналогичное форматированию значений в Python.

gettext_noop

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

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

Полезно при написании кода, который будет использовать перевод в будущем.

pgettext

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

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

npgettext

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

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

pluralidx

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

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

В самом простом варианте возвращает false для 1 и true для остальных значений.

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

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

The JSONCatalog view

class JSONCatalog

In order to use another client-side library to handle translations, you may want to take advantage of the JSONCatalog view. It’s similar to JavaScriptCatalog but returns a JSON response.

See the documentation for JavaScriptCatalog to learn about possible values and use of the domain and packages attributes.

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

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

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

The various JavaScript/JSON i18n views generate the catalog from .mo files on every request. Since its output is constant, at least for a given version of a site, it’s a good candidate for caching.

Server-side caching will reduce CPU load. It’s easily implemented with the cache_page() decorator. To trigger cache invalidation when your translations change, provide a version-dependent key prefix, as shown in the example below, or map the view at a version-dependent 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'),
]

Client-side caching will save bandwidth and make your site load faster. If you’re using ETags (ConditionalGetMiddleware), you’re already covered. Otherwise, you can apply conditional decorators. In the following example, the cache is invalidated whenever you restart your application server:

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 мог определить требуемый язык по запрошенному ресурсу.
  • Making URL patterns themselves translatable via the django.utils.translation.gettext_lazy() function.

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

Using either one of these features requires that an active language be set for each request; in other words, you need to have django.middleware.locale.LocaleMiddleware in your MIDDLEWARE setting.

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

i18n_patterns(*urls, prefix_default_language=True)

This function can be used in a root URLconf and Django will automatically prepend the current active language code to all URL patterns defined within i18n_patterns().

Setting prefix_default_language to False removes the prefix from the default language (LANGUAGE_CODE). This can be useful when adding translations to existing site so that the current URLs won’t change.

Example URL patterns:

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 автоматически подставит префикс языка к шаблонам, которые были добавлены через функцию 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/'

With prefix_default_language=False and LANGUAGE_CODE='en', the URLs will be:

>>> activate('en')
>>> reverse('news:index')
'/news/'

>>> activate('nl')
>>> reverse('news:index')
'/nl/news/'

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

i18n_patterns() is only allowed in a root URLconf. Using it within an included URLconf will throw an ImproperlyConfigured exception.

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

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

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

URL patterns can also be marked translatable using the gettext_lazy() function. Example:

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

After you’ve created the translations, the reverse() function will return the URL in the active language. Example:

>>> 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/'

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

In most cases, it’s best to use translated URLs only within a language code prefixed block of patterns (using i18n_patterns()), to avoid the possibility that a carelessly translated URL causes a collision with a non-translated URL pattern.

Генерация URL в шаблонах

Если локализованные URL генерируются в шаблонах, они всегда используют текущий язык. Для получения URL для другого языка следует использовать шаблонный тег language. Этот тег включает выбранный язык внутри своего блока:

{% load i18n %}

{% get_available_languages as languages %}

{% trans "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 пуст.

By default django-admin makemessages examines every file that has the .html, .txt or .py file extension. If you want to override that default, use the --extension or -e option to specify the file extensions to examine:

django-admin makemessages -l de -e txt

Разделяйте множество расширений с помощью запятой и/или используйте опцию многократно:

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

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

When creating message files from JavaScript source code you need to use the special djangojs domain, not -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?

If you don’t have the gettext utilities installed, makemessages will create empty files. If that’s the case, either install the gettext utilities or copy the English message file (locale/en/LC_MESSAGES/django.po) if available and use it as a starting point, which is an empty translation file.

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

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

Each .po file contains a small bit of metadata, such as the translation maintainer’s contact information, but the bulk of the file is a list of messages – mappings between translation strings and the actual translated text for the particular language.

Например, если ваше 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 файлов (по умолчанию при их создании). Это означает, что все будут использовать одинаковую кодировку, что очень важно в момент, когда Django обрабатывает PO файлы.

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

django-admin makemessages -a

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

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

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

django-admin compilemessages

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

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

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

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

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

Troubleshooting: gettext() incorrectly detects python-format in strings with percent signs

In some cases, such as strings with a percent sign followed by a space and a string conversion type (e.g. _("10% interest")), gettext() incorrectly flags strings with python-format.

If you try to compile message files with incorrectly flagged strings, you’ll get an error message like number of format specifications in 'msgid' and 'msgstr' does not match or 'msgstr' is not a valid Python format string, unlike 'msgid'.

To workaround this, you can escape percent signs by adding a second percent sign:

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

Or you can use no-python-format so that all percent signs are treated as literals:

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

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

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

django-admin makemessages -d djangojs -l de

This would create or update the message file for JavaScript for German. After updating message files, run django-admin compilemessages the same way as you do with normal Django message files.

gettext на Windows

This is only needed for people who either want to extract message IDs or compile message files (.po). Translation work itself involves editing existing files of this type, but if you want to create your own message files, or want to test or compile a changed message file, download a precompiled binary installer.

Вы также можете использовать бинарники gettext, взятые где-то, если команда xgettext --version работает правильно. Не пытайтесь выполнять команды Django, использующие пакет gettext, если команда xgettext --version, введённая в консоли Windows, выбрасывает окно с текстом «xgettext.exe has generated errors and will be closed by Windows».

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

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

from django.core.management.commands import makemessages

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

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

from django.core.management.commands import makemessages

class Command(makemessages.Command):

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

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

Разное

Перенаправляющее представление set_language

set_language(request)

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

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

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

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

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

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

The view expects to be called via the POST method, with a language parameter set in request. If session support is enabled, the view saves the language choice in the user’s session. It also saves the language choice in a cookie that is named django_language by default. (The name can be changed through the LANGUAGE_COOKIE_NAME setting.)

After setting the language choice, Django looks for a next parameter in the POST or GET data. If that is found and Django considers it to be a safe URL (i.e. it doesn’t point to a different host and uses a safe scheme), a redirect to that URL will be performed. Otherwise, Django may fall back to redirecting the user to the URL from the Referer header or, if it is not set, to /, depending on the nature of the request:

  • For AJAX requests, the fallback will be performed only if the next parameter was set. Otherwise a 204 status code (No Content) will be returned.
  • For non-AJAX requests, the fallback will always be performed.

Приведём пример 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.

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

You may want to set the active language for the current session explicitly. Perhaps a user’s language preference is retrieved from another system, for example. You’ve already been introduced to django.utils.translation.activate(). That applies to the current thread only. To persist the language for the entire session in a cookie, set the LANGUAGE_COOKIE_NAME cookie on the response:

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)

You would typically want to use both: django.utils.translation.activate() changes the language for this thread, and setting the cookie makes this preference persist in future requests.

Changed in Django 3.0:

In older versions, you could set the language in the current session.

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

Несмотря на то, что 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

Calling this function with the value 'de' will give you "Willkommen", regardless of LANGUAGE_CODE and language set by 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')

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

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

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

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

Как Django определяет языковую настройку

Once you’ve prepared your translations – or, if you want to use the translations that come with Django – you’ll need to activate translation for your app.

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

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

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

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

To use LocaleMiddleware, add 'django.middleware.locale.LocaleMiddleware' to your MIDDLEWARE setting. Because middleware order matters, follow these guidelines:

  • Make sure it’s one of the first middleware installed.
  • Она должна идти после SessionMiddleware, так как LocaleMiddleware использует сессию. И она должна идти до CommonMiddleware, так как CommonMiddleware нуждается в активном языке для определения запрошенного URL.
  • Если вы используете CacheMiddleware, поместите LocaleMiddleware после неё.

For example, your MIDDLEWARE might look like this:

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

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

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

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

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

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

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

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

Замечания:

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

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

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

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

    This example restricts languages that are available for automatic selection to German and English (and any sublanguage, like de-ch or en-us).

  • If you define a custom LANGUAGES setting, as explained in the previous bullet, you can mark the language names as translation strings – but use gettext_lazy() instead of gettext() to avoid a circular import.

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

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

Once LocaleMiddleware determines the user’s preference, it makes this preference available as request.LANGUAGE_CODE for each HttpRequest. Feel free to read this value in your view code. Here’s an example:

from django.http import HttpResponse

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

Следует отметить, что для статичного перевода (без мидлвари) язык надо брать из settings.LANGUAGE_CODE, а для динамичного перевода (с мидлварью) — из request.LANGUAGE_CODE.

Как Django находит переводы

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

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

См.также

The translations for literals included in JavaScript assets are looked up following a similar but not identical algorithm. See JavaScriptCatalog for more details.

You can also put custom format files in the LOCALE_PATHS directories if you also set FORMAT_MODULE_PATH.

In all cases the name of the directory containing the translation is expected to be named using locale name notation. E.g. de, pt_BR, es_AR, etc. Untranslated strings for territorial language variants use the translations of the generic language. For example, untranslated pt_BR strings use pt translations.

This way, you can write applications that include their own translations, and you can override base translations in your project. Or, you can build a big project out of several apps and put all translations into one big common message file specific to the project you are composing. The choice is yours.

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

  • Во всех указанных путях в параметре конфигурации LOCALE_PATHS происходит поиск <language>/LC_MESSAGES/django.(po|mo)
  • $APPPATH/locale/<language>/LC_MESSAGES/django.(po|mo)
  • $PYTHONPATH/django/conf/locale/<language>/LC_MESSAGES/django.(po|mo)

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

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

Using a non-English base language

Django makes the general assumption that the original strings in a translatable project are written in English. You can choose another language, but you must be aware of certain limitations:

  • gettext only provides two plural forms for the original messages, so you will also need to provide a translation for the base language to include all plural forms if the plural rules for the base language are different from English.
  • When an English variant is activated and English strings are missing, the fallback language will not be the LANGUAGE_CODE of the project, but the original strings. For example, an English user visiting a site with LANGUAGE_CODE set to Spanish and original strings written in Russian will see Russian text rather than Spanish.