Мультиязычность и перевод в 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) перевело бы так: "Название", а в форме обратной связи нам нужно перевести как "Имя".
Чтобы использовался наш перевод, нужно выполнить один из вариантов:
- В
INSTALLED_APPS
переместить наше приложение перед другими приложениями, которые переводят наше используемое слово. - Использовать
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. Поддержка нескольких языков сайта
Похожие статьи:
Представляю вашему вниманию книгу, написанную моим близким другом Максимом Макуриным: Секреты эффективного управления ассортиментом.
Книга предназначается для широкого круга читателей и, по мнению автора, будет полезна специалистам отдела закупок и логистики, категорийным и финансовым менеджерам, менеджерам по продажам, аналитикам, руководителям и директорам, в компетенции которых принятие решений по управлению ассортиментом.
Комментарии: 28
23.06.2018 10:18 #
китайский не подгружается((
Ответить
24.06.2018 6:21 #
Александр, с китайским не имел дело. Расскажите по-подробнее, что именно не загружается. Какое поведение, трейсбек и пр.
Ответить
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
Ответить
20.03.2019 11:31 #
Добрый ден ь А после выполнения команды python manage.py makemessages -l en файлы django.po должны формироваться уже с переводом или нет ? У меня почему-то вот та к
: .\accounts\models.py:7 0msgid "last name " msgstr " "
скрин - https://s.mail.ru/Kfvm/Hn6R7Pg5E
Ответить
21.03.2019 5:16 #
Добрый день, Александр!
Нет, перевод нужно самому писать. А потом выполнить команду
python manage.py compilemessages
Ответить
21.03.2019 10:36 #
Спасибо Артем. Кажется потихоньку разобрался как это все заводится и работает . Спасибо за статью сэкономили немного времени
Ответить
21.03.2019 20:43 #
Рад, что статья оказалось полезной!
Ответить
27.12.2019 8:20 #
Добрый день. подскажите как переводить такие вещи -
при подготовке файла перевода пишет
Я так понимаю проблема в знаке %, пробовал в наглую писать, но после обновления файла перевода все возвращается, а это просто знак процента)
Ответить
27.12.2019 12:55 #
Интересный вопрос! Пока не знаю ответа на него. Нужно потестировать. Я запишу себе задачку разобраться с этим
Ответить
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:
Тут нужны какие-то обходные пути использовать типа добавления модели с внешним ключом на модель FlatPage и в админке уже
inline
подцеплять. Типа так:И в шаблоне писать как-то так:
Фильтр нужно будет свой написать
get_content
, который принимает context и, используяcontext['request'].language
, вытаскивает нужный контент с моделиFlatPageTranslation
.Надеюсь, мысль понятна :)
Вообще может лучше использовать django-cms? Он легковесный и очень гибкий.
Ответить
17.08.2020 14:21 #
Спасибо большое
Ответить
31.05.2021 10:58 #
Доброго времени суток, Артем ! https://vivazzi.pro/it/translate-django/#1-nastraivaem-settingsp y у вас в тексте слово "милдварь" написано скорей всего с небольшой ошибкой.
Ответить
02.06.2021 4:33 #
Добрый день, Аскар!
Это, так скажем, русское удобное сокращение, чем писать "милдваре" или по-английски middleware, ну или вообще русским переводом "Промежуточный слой" : )
Может вы предложите какой-то другой вариант? рассмотрим)
Ответить
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 написано:
Это для нового файла, созданного мною (как вы в статье описали).
По факту мне нужно изменить перевод в админском шаблоне, который коряво переводится пакетом django-import-export. Как сделать перевод Delete для всего проекта, а не отдельного файла?
Ответить
01.04.2022 2:33 #
Предложенный мною способ как раз-таки и применяет перевод для всего проекта.
Я вот что думаю, точно ли вообще в шаблоне пакета import-export должно быть написано Delete:
См: 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 #
Ага, точно.. Я посмотрел тикет и да, там надо всё-таки посмотреть, как на самом деле должно переводится по смыслу и сделать пул-риквест, но пока заниматься этим нет времени.. Столько везде подобных мелких задач в разных пакетах :)
Ответить