Мультиязычность и перевод в Django

5 марта 2016 г. 3:40

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

1. Настраиваем settings.py

Несколько простых строк в settings.py позволят добавить многоязычность сайту:

LANGUAGE_CODE = 'ru'  # язык сайта по умолчанию

LANGUAGES = (
    ('ru', 'Russian'),
    ('en', 'English'),
)

USE_I18N = True  # активация системы перевода django

# месторасположение файлов перевода
LOCALE_PATHS = (
    'locale',
    # os.path.join(PROJECT_DIR, 'locale'),
)

Если вы используете django-cms, то добавьте ещё этот милдварь 'cms.middleware.language.LanguageCookieMiddleware', . Это полезно для запоминания, какой язык был определён, и при последующем заходе на сайт будет активизироваться именно этот язык.

2. Подготавливаем слова и фразы для перевода

В моделях:

from django.utils.translation import ugettext_lazy as _
...
title = models.CharField(max_length=100, verbose_name=_('Title'))

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

В формах:

from django.utils.translation import ugettext_lazy as _
...
title = forms.CharField(label=_('Title'), max_length=100)

В представлениях:

from django.utils.translation import ugettext as _
...
text = _('My variable')

В шаблонах:

{% load i18n %}
...
{% trans 'Hello' %} - для перевода простой фразы
{% trans "Title" context "my title" %} - для перевода простой фразы с контекстом
{% blocktrans %}Hello, dear {{ request.user.username }}{% endblocktrans %} - для перевода фразы блока с дополнительными переменными, тегами и фильтрами
{% custom_tag _('some text') value|yesno:_('yes,no') %} - для перевода фразы в тегах и фильтрах

Внимание: Не используйте знак "|" (вертикальная черта) при переводе фразы в фильтре или теге (к примеру, {{ value|some_filter:_('foo|bar') }}), потому что в файле перевода появится что-то подобное FFFFFFFXXXXXXXX и перевод работать не будет.

Пример работы с уже переведёнными словами другими приложениями, например, Name

# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import pgettext_lazy
from django import forms


class ContactForm(forms.Form):
    name = forms.CharField(max_length=255, label=pgettext_lazy('form label', 'Name'))
    phone = forms.CharField(max_length=100, label=_('Phone'))

Обратите внимание, что я не использовал оператор as для сокращения обращения к функции pgettext_lazy. Почему-то с pgettext_lazy это не работает. То есть, нельзя записать для сокращения что-то вроде from django.utils.translation import ugettext_lazy as _p и использовать name = forms.CharField(max_length=9, label=_p('form label', 'Name')) - перевод в файл не добавится.

Если бы мы использовали как обычно _('Name'), то наш бы перевод не применился, так как другое приложение (в моём случае это был django-taggit) перевело бы так: "Название", а в форме обратной связи нам нужно перевести как "Имя".

Чтобы использовался наш перевод, нужно выполнить один из вариантов:

  1. В INSTALLED_APPS переместить наше приложение перед другими приложениями, которые переводят наше используемое слово.
  2. Использовать pgettext_lazy, который создаёт для слова контекст.

Немного подробнее про pgettext_lazy

По контексту (первый аргумент функции pgettext_lazy) Django определяет вариант перевода.

Например, мы можем Name в одном случае будет "Название", в другом - "Имя", в третьем - "Наименование".

name = pgettext_lazy('form label', 'Name')  # name = Имя (отправителя)
name = pgettext_lazy('article label', 'Name')  # name = Наименование (детали)
name = pgettext_lazy('city label', 'Name')  # name = Название (города)

3. Создаём файлы для перевода и компилируем

Для создания файл перевода нужно запустить команду makemessages с указанием языка опцией -l. Таким образом, нужно вызвать команду столько раз сколько у вас языков (если ошибаюсь, поправьте), например:

$ django-admin makemessages -l ru
$ django-admin makemessages -l en

Лучше использовать django-admin, а не python manage.py, так как django-admin не будет проверять корректность всех команд (из-за чего команда создания перевода может быть прервана).

Возможен запуск с конкретного приложения:

$ cd my_app
/my_app$ django-admin makemessages -l en

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

CommandError: errors happened while running xgettext on CompoundDoc.py
xgettext: ./env/lib/python2.7/site-packages/xlwt/CompoundDoc.py:1: Кодировка "windows-1252" неизвестна.  Продолжение работы с ASCII.
xgettext: Non-ASCII string at ./env/lib/python2.7/site-packages/xlwt/CompoundDoc.py:209.
          Please specify the source encoding through --from-code.

Ранее до использования pipenv я держал папку виртуального окружения в корне проекта, поэтому команда makemessages обошла все установленные зависимости для проекта и встретила проблему с переводом пакета xlwt. Для решения проблемы нужно исключить ненужные папки для команды, например, так:

$ django-admin makemessages -l en -i env -i services

Команда исключит папки env и services.

После того как папки с языками создались для обновления перевода всех языков можно воспользоваться командой:

$ django-admin makemessages -a
# или
$ django-admin makemessages -a -i env -i services

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

Фрагмент файла ru/LC_MESSAGES/django.po:

msgctxt "form label"
msgid "Name"
msgstr "Имя"

Обратите внимание, что если мы использовали pgettext_lazy, то в файле django.po будет ещё добавлен msgctxt для переводимого слова, например:

msgctxt "form label"
msgid "Name"
msgstr "Имя"

msgctxt "article label"
msgid "Name"
msgstr "Наименование"

msgctxt "city label"
msgid "Name"
msgstr "Название"

Иногда django может пометить фразы как fuzzy. Например:

#: settings.py:306
msgid "Maltsev Artem Blog"
msgstr "Блог Мальцева Артёма"

#: templates/base.html:11
#, fuzzy
#| msgid "Maltsev Artem Blog"
msgid "Maltsev Artem"
msgstr "Блог Мальцева Артёма"

Django облегчает нам работу с переводом: если находит похожий msgid, то использует его msgstr, то есть его перевод. Таким образом, нам остаётся всего лишь подправить перевод и удалить строчку #, fuzzy, иначе перевод не будет применён. Строку, начинающуюся с #| msgid ... (после #, fuzzy), можно тоже удалить, чтобы место не занимала. В итоге наш пример преобразуется следующим образом:

#: settings.py:306
msgid "Maltsev Artem Blog"
msgstr "Блог Мальцева Артёма"

#: templates/base.html:11
msgid "Maltsev Artem"
msgstr "Мальцев Артём"

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

$ django-admin compilemessages

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

~/projects/vivazzi/spec $ django-admin compilemessages

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

4. Добавляем в шаблон код смены языка

Начиная с Django 1.9 переключатель языка можно добавить так:

from django.conf import settings
from django.conf.urls import include, url
from django.conf.urls.i18n import i18n_patterns
from django.contrib import admin
from django.conf.urls.static import static

admin.autodiscover()

i18n_urls = (
    url(r'^admin/', include(admin.site.urls)),
    ...
    url(r'^i18n/', include('django.conf.urls.i18n')),
)

urlpatterns = [
    ...
]

urlpatterns.extend(i18n_patterns(*i18n_urls, prefix_default_language=False))
urlpatterns.extend(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT))

Как написано в документации, для работоспособности переключателя не нужно url(r'^i18n/', include('django.conf.urls.i18n')) применять с функцией i18n_patterns. Но у меня почему-то не сработало, а вот код выше вполне работает. Я использую django-cms и может быть здесь идёт какой-то конфликт (ниже вы увидите соответствующий код). Если знаете в чём дело, пишите в комментариях, буду рад подправить статью.

Затем можно добавить сам 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="selected"{% endif %}>
                {{ language.name_local }} ({{ language.code }})
            </option>
        {% endfor %}
    </select>
    <input type="submit" value="{% trans 'Change' %}" />
</form>

Это пример взят из документации и немного изменён (добавил перевод кнопки).

Если вы используете django-cms, то всё ещё проще: достаточно просто использовать в шаблоне следующий код (без дополнительных подключений django.conf.urls.i18n урлов):

{% load i18n menu_tags %}

{% for language in languages %}
<li{% if current_language == language.0 %} class="active"{% endif %}>
    <a href="{% page_language_url language.0 %}" title="{% trans 'Change language to' %} {{ language.1 }}">{{ language.0|upper }}</a>
</li>
{% endfor %}

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

Если нужно заменить дефолтный перевод слова

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

Когда Django ищет перевод слова, он сначала заглядывает в папку locale на самом верхнем уровне проекта, а затем уже смотрит последовательно по приложениям, объявленым в INSTALLED_APPS. И как только находит перевод слова (в скомпилированном файле django.mo необходимого языка), то его возвращает, заканчивая при этом поиск.

Поэтому нужно создать папку locale на самом верхнем уровне проекта и добавить в него __init__.py, а затем вызвать команду для добавления языка, например, русского:

django-admin makemessages -l ru

В файле project/locale/ru/django.po добавить:

msgid "Delete"
msgstr "Удалить"

Правда, при вызове django-admin makemessages -a Django посчитает, что слова в вашем коде нигде нет и закомментирует их, поэтому можно добавить в каком-нибудь файле или в отдельном файле в корне проекта что-то типа:

# for_translation.py
from django.utils.translation import gettext as _
translations = [_('Delete'), ]

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

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

Решение возможных ошибок

1. Не подгружается китайский язык

Спасибо loujessler за совет: /it/translate-django/#comment_435:

Попробуйте вместо знака тире "-" ставить "_" и вместо нижнего регистра буквы "h" на "H". Таким образом, это будет выглядеть так:

django-admin makemessages -l zh_Hans

Дополнительные материалы по теме

Частичное использование языкового префикса для отдельных url - 1. Поддержка нескольких языков сайта

Оцените статью

4.8 из 5 (всего 5 оценок)

captcha
Отмеченные звёздочкой поля ( * ) являются обязательными для заполнения.

Спасибо за ваш отзыв!

После нажатия кнопки "Отправить" ваше сообщение будет доставлено мне на почту.

Автор статьи

Артём Мальцев

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

Права на использование материала, расположенного на этой странице https://vivazzi.pro/ru/it/translate-django/:

Разрешается копировать материал с указанием её автора и ссылки на оригинал без использования параметра rel="nofollow" в теге <a>. Использование:

Автор статьи: Артём Мальцев
Ссылка на статью: <a href="https://vivazzi.pro/ru/it/translate-django/">https://vivazzi.pro/ru/it/translate-django/</a>

Больше: Правила использования сайта

Представляю вашему вниманию книгу, написанную моим близким другом Максимом Макуриным: Секреты эффективного управления ассортиментом.

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

Комментарии: 28

Александр Б.
Александр Б.

23.06.2018 10:18 #

китайский не подгружается((

Ответить

Артём Мальцев
Артём Мальцев автор

24.06.2018 6:21 #

Александр, с китайским не имел дело. Расскажите по-подробнее, что именно не загружается. Какое поведение, трейсбек и пр.

Ответить

loujessler
loujessler

18.12.2019 19:00 #

Тоже мучался с данной проблемой, но удалось решить её. Нужно всего-навсего вместо тире "-" ставить "_" и вместо нижнего регистра буквы "h" на "H" .

Таким образом, это будет выглядеть так :

django-admin makemessages -l zh_Hans

Ответить

Артём Мальцев
Артём Мальцев автор

21.12.2019 5:53 #

loujessler, благодарю за совет! Добавил вашу подсказку в конец данной статьи: https://vivazzi.pro/it/translate-django/#reshenie-vozmozhnyh-oshibok

Ответить

Aleksander
Aleksander

20.03.2019 11:31 #

Добрый ден ь А после выполнения команды python manage.py makemessages -l en файлы django.po должны формироваться уже с переводом или нет ? У меня почему-то вот та к

: .\accounts\models.py:7 0

msgid "last name " msgstr " "

скрин - https://s.mail.ru/Kfvm/Hn6R7Pg5E

Ответить

Артём Мальцев
Артём Мальцев автор

21.03.2019 5:16 #

Добрый день, Александр!
Нет, перевод нужно самому писать. А потом выполнить команду python manage.py compilemessages

Ответить

Aleksander
Aleksander

21.03.2019 10:36 #

Спасибо Артем. Кажется потихоньку разобрался как это все заводится и работает . Спасибо за статью сэкономили немного времени

Ответить

Артём Мальцев
Артём Мальцев автор

21.03.2019 20:43 #

Рад, что статья оказалось полезной!

Ответить

Aleksander
Aleksander

27.12.2019 8:20 #

Добрый день. подскажите как переводить такие вещи -

{% trans 'указываем: -5% (минус пять процентов)' %}

при подготовке файла перевода пишет

#, fuzzy, python-format
#| msgid " указываем: -5%  (минус пять процентов)"
msgid " указываем: -5%%  (минус пять процентов)"

Я так понимаю проблема в знаке %, пробовал в наглую писать, но после обновления файла перевода все возвращается, а это просто знак процента)

Ответить

Артём Мальцев
Артём Мальцев автор

27.12.2019 12:55 #

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

Ответить

Aleksander
Aleksander

27.12.2019 12:57 #

Спасибо! Если найду решение отпишусь тоже

Ответить

Константин
Константин

16.08.2020 13:01 #

Спасибо за статью. Отличная ! Можешь подсказать как сделать i18n во Flatpages в админке добавить,
чтобы было Content[ru] и Content[en]

Ответить

Артём Мальцев
Артём Мальцев автор

17.08.2020 10:45 #

Благодарю за отзыв!
Скорее всего никак не сделать, так как, если посмотреть на код модели flatpages https://github.com/django/django/blob/master/django/contrib/flatpages/models.py#L11, то можно увидеть, что только для одного языка поле content:

class FlatPage(models.Model):
    url = models.CharField(_('URL'), max_length=100, db_index=True)
    title = models.CharField(_('title'), max_length=200)
    content = models.TextField(_('content'), blank=True)

Тут нужны какие-то обходные пути использовать типа добавления модели с внешним ключом на модель FlatPage и в админке уже inline подцеплять. Типа так:

class FlatPageTranslation(models.Model):
    page = models.ForeignKey(FlatPage)
    language = models.CharField(_('Language'), choices=settings.LANGUAGES, default='ru')
    content = models.TextField(_('content'), blank=True)

И в шаблоне писать как-то так:

{{ flatpage|get_content }}

Фильтр нужно будет свой написать get_content, который принимает context и, используя context['request'].language, вытаскивает нужный контент с модели FlatPageTranslation.

Надеюсь, мысль понятна :)

Вообще может лучше использовать django-cms? Он легковесный и очень гибкий.

Ответить

Константин
Константин

17.08.2020 14:21 #

Спасибо большое

Ответить

Askar Musaev
Askar Musaev

31.05.2021 10:58 #

Доброго времени суток, Артем ! https://vivazzi.pro/it/translate-django/#1-nastraivaem-settingsp y у вас в тексте слово "милдварь" написано скорей всего с небольшой ошибкой.

Ответить

Артём Мальцев
Артём Мальцев автор

02.06.2021 4:33 #

Добрый день, Аскар!
Это, так скажем, русское удобное сокращение, чем писать "милдваре" или по-английски middleware, ну или вообще русским переводом "Промежуточный слой" : )

Может вы предложите какой-то другой вариант? рассмотрим)

Ответить

Askar Musaev
Askar Musaev

05.06.2021 7:10 #

Нет, Артем, ничего я предлагать не хотел ) Я не знал что так удобно сокращают данное слово, просто подумал, что вы отпечатались . Всего вам наилучшего и спасибо за пост!

Ответить

Артём Мальцев
Артём Мальцев автор

07.06.2021 7:31 #

Благодарю, Аскар, за оценку статьи!

Программисты часто слова сокращают или транслитерируют, например, "юзать" вместо "использовать". Хотя я всё же стараюсь сохранять чистоту языка, а то некоторых послушаешь и не сразу поймёшь о чём он говорит :)

Ответить

Сергей.
Сергей.

03.03.2022 17:40 #

Установил пакет django-import-export для django. Он "переписал" файлы перевода. Теперь в админ-панеле кнопка Delete переведена как "Удалено" вместо "Удалить". Как можно этот корявый перевод исправить? Во внешнем пакете ведь нельзя ничего править...

Ответить

Артём Мальцев
Артём Мальцев автор

14.03.2022 5:34 #

Я добавил в статью материал по этому вопросу: https://vivazzi.pro/ru/it/translate-django/#esli-nuzhno-zamenit-defoltnyy-perevod-slova

Если что-то не получится, пишите - разберёмся :)

Ответить

Сергей
Сергей

31.03.2022 13:15 #

Спасибо за ответ.
Сделал по вашей инструкции, но получил ошибку : AttributeError: module 'locale' has no attribute 'normalize '

Я так понимаю, идет "пересечение" с встроенным модулем python locale .

Если удалить файл "init.py", то эта ошибка пропадает. Но перевод остается тот же корявый...

Ответить

Артём Мальцев
Артём Мальцев автор

01.04.2022 2:25 #

Разместите полный трейсбек ошибки, посмотрю. А без него непонятно, где ошибка появляется

Ответить

Гость
Гость

31.03.2022 13:26 #

В продолжении сообщения от 31.03.2022 16:15.

Возможно можно и без "init.py" (с нижними подчеркиванимями).
У меня в файле django.po написано:

#: .\main\for_trans.py:2
msgid "Delete"
msgstr "Удалить"

Это для нового файла, созданного мною (как вы в статье описали).
По факту мне нужно изменить перевод в админском шаблоне, который коряво переводится пакетом django-import-export. Как сделать перевод Delete для всего проекта, а не отдельного файла?

Ответить

Артём Мальцев
Артём Мальцев автор

01.04.2022 2:33 #

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

Я вот что думаю, точно ли вообще в шаблоне пакета import-export должно быть написано Delete:

{% for row in result.valid_rows %}


              {% if row.import_type == 'new' %}
                {% trans "New" %}
              {% elif row.import_type == 'skip' %}
                {% trans "Skipped" %}
              {% elif row.import_type == 'delete' %}
                {% trans "Delete" %}
              {% elif row.import_type == 'update' %}
                {% trans "Update" %}
              {% endif %}

            {% for field in row.diff %}
              {{ field }}
            {% endfor %}

        {% endfor %}

См: https://github.com/django-import-export/django-import-export/blob/main/import_export/templates/admin/import_export/import.html#L156

Может быть там вообще должно стоять "Deleted" и "Updated"? Например, Skipped написано именно, как Skipped, а не Skip.

Если по логике нужно использовать "Deleted" и "Updated", то нужно написать разработчикам тикен, чтобы поправили, или свой пул-риквест сделать

Ответить

Сергей.
Сергей.

08.04.2022 11:28 #

Оказывается, им уже писали: https://github.com/django-import-export/django-import-export/issues/115 8

Самостоятельно делать pull-реквест не решусь. Боюсь сломать что-нибудь. Пишу код только для собственных проектов. Может вы попробуйте?

Ответить

Артём Мальцев
Артём Мальцев автор

19.04.2022 1:58 #

Чтобы мне сделать пул-риквест, это нужно склонировать, протестировать этот пакет, логику посмотреть, чтобы понимать, как должно на самом деле быть - на это всё время надо. Я если буду пользоваться данным пакетом когда-нибудь, то обращу на этот момент, а пока не буду тестировать.

Кстати, https://github.com/django-import-export/django-import-export/issues/115 ведёт на пул-риквест https://github.com/django-import-export/django-import-export/pull/115 , который не имеет отношения к переводу. Может ссылка неправильная https://github.com/django-import-export/django-import-export/issues/115

Ответить

Гость
Гость

20.04.2022 17:00 #

Да, там почему-то в конце восьмерка отдельно от ссылки получается. Ссылка оканчивается на 1158, а не на 115.

Ответить

Артём Мальцев
Артём Мальцев автор

22.04.2022 2:40 #

Ага, точно.. Я посмотрел тикет и да, там надо всё-таки посмотреть, как на самом деле должно переводится по смыслу и сделать пул-риквест, но пока заниматься этим нет времени.. Столько везде подобных мелких задач в разных пакетах :)

Ответить

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

Но зарегистрировавшись, вы сможете:

  • получать оповещения об ответах
  • просматривать свои комментарии
  • иметь возможность использовать все функции разработанных сервисов

Для комментирования от своего имени войдите или зарегистрируйтесь на сайте Vuspace

Отправить

На данный момент нет специального поиска, поэтому я предлагаю воспользоваться обычной поисковой системой, например, Google, добавив "vivazzi" после своего запроса.

Попробуйте

Выберите валюту для отображения денежных единиц