Хабр, отличного всем времени суток! Скоро в OTUS стартует курс «Python Web-Developer»: мы приглашаем на бесплатный Demo-урок «Паттерны Page Controller и Front Controller: реализация в Django» и публикуем перевод статьи Nicolle Cysneiros — Full Stack Developer (Labcodes).
Согласно всегда правдивой информации на Википедии, в мире насчитывается около 360 миллионов носителей английского языка. Мы, как разработчики, настолько привыкли писать код и документацию на английском языке, что не осознаем, что это число – это всего. 4,67% населения всего мира. Единый язык общения между разработчиками – это, конечно, хорошо, но это не значит, что пользователь должен чувствовать дискомфорт при использовании вашего продукта.
В этой статье мы начнем говорить о понятиях интернационализации и локализации, а также обозначим их важность для вашего приложения. Затем рассмотрим некоторые элементы интернационализации, доступные разработчикам для работы над проектами на Python и Django. Под конец расскажем о том, как мы изменяли свой процесс разработки, чтобы добавить интернационализацию.
Локализация или интернационализация
Локализация — процесс адаптации приложения, продукта или документа для удобства пользователей разных стран и культур.
Тогда как интернационализация — процесс обеспечения возможности локализации продукта. То есть реализация программного обеспечения таким образом чтобы оно знало, когда и как показывать различный контент в зависимости от культурной или языковой принадлежности (локали) пользователя.
Как гласит документация Django: локализацию делают переводчики, а интернационализацию – разработчики.
Однако упрощенное определение интернационализации и локализации может создать ложное впечатление, будто речь идет только о переводе. Этот процесс также включает в себя несколько видов адаптации, которые позволяют пользователю чувствовать себя комфортнее при использовании вашего продукта, например:
Формат даты и валюты;
Конвертация валюты;
Преобразование единиц измерения;
Символы юникода и двунаправленны текст (см. ниже);
Часовые пояса, календарь и особые праздники.
С помощью таких адаптаций мы можем улучшить пользовательский опыт нашего приложения.
Как это делается в Python?
GNU gettext
Есть несколько инструментов, которые могут помочь локализовать ваше приложения на Python. Начнем с пакета GNU gettext, который является частью Translation Project. В этом пакете есть:
библиотека, которая в рантайме поддерживает извлечение переведенных сообщений;
набор соглашений о том, как нужно писать код для поддержки каталогов сообщений;
библиотека, поддерживающая синтаксический анализ и создание файлов, содержащих переведенные сообщения.
Следующий фрагмент кода – это просто Hello World в файле app.py
, где используется модуль gettext
в Python для создания объекта перевода (gettext.translation
) в домене приложения с указанием директории локали и языка, на который мы хотим перевести строки. Затем мы присваиваем функцию gettext
символу нижнего подчеркивания (обычная практика для уменьшения накладных расходов на ввод gettext
для каждой переводимой строки), и, наконец, ставим флаг строке «Hello World!»
, чтобы она была переведена.
import gettext
gettext.bindtextdomain("app", "/locale")
gettext.textdomain("app")
t = gettext.translation("app", localedir="locale", languages=['en_US'])
t.install()
_ = t.gettext
greeting = _("Hello, world!")
print(greeting)
После пометки переводимых строк в коде, мы можем собрать их с помощью инструмента командной строки GNU xgettext
. Этот инструмент сгенерирует PO-файл, который будет содержать все отмеченные нами строки.
xgettext -d app app.py
PO-файл (или файл Portable Object) содержит список записей, а структура записи выглядит следующим образом:
# translator-comments
#. extracted-comments
#: reference…
#, flag…
#| msgid previous-untranslated-string
msgid untranslated-string
msgstr translated-string
Мы можем добавить для строки комментарий для переводчиков, ссылки и флаги. После этого мы обращаемся к ID записи (msgid
), который представляет из себя непереведенную строку, помеченную в коде и строку записи (msgstr
) – переведенную версию этой строки.
Когда мы запускаем xgettext
в командной строке, передавая app.py
в качестве входного файла, получается такой PO-файл:
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-03 13:23-0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: app.py:7
msgid "Hello, world!"
msgstr ""
В начале файла у нас есть метаданные о файле, проекте и процессе перевода. Потом стоит непереведенная строка «Hello World!» в качестве ID записи и пустая строка для строки записи. Если для записи не указан перевод, то при переводе будет использоваться ID записи.
После генерации PO-файла можно начинать переводить термины на разные языки. Важно отметить, что библиотека GNU gettext будет искать переведенные PO-файлы в пути к папке определенного вида (<localedir>/<languagecode>/LCMESSAGES/<domain>.po
), то есть для каждого языка, который вы хотите поддерживать, должен быть один PO-файл.
|-- app.py
|-- locale
|-- en_US
| |-- LC_MESSAGES
| |-- app.po
|-- pt_BR
|-- LC_MESSAGES
| |-- app.po
Вот пример PO-файла с переводом на португальский:
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-05-03 13:23-0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: app.py:7
msgid "Hello, world!"
msgstr "Ola, mundo!"
Чтобы использовать переведенные строки в коде, нужно скомпилировать PO-файл в MO-файл с помощью команды msgfmt
.
msgfmt -o app.mo app.po
Когда MO-файл готов, можно изменить язык программы на португальский, подав его на вход функции перевода. Если мы запустим следующий код, отмеченная строка будет переведена как «Ola, mundo!»:
import gettext
gettext.bindtextdomain("app", "/locale")
gettext.textdomain("app")
t = gettext.translation("app", localedir="locale", languages=['pt_BR'])
t.install()
_ = t.gettext
greeting = _("Hello, world!")
print(greeting)
Модуль locale
У этого модуля есть доступ к базе данных локалей (locale) POSIX, и он особенно полезен для обработки форматов дат, чисел и валют. В примере ниже показано как использовать библиотеку locale:
import datetime
import locale
locale.setlocale(locale.LC_ALL, locale='en_US')
local_conv = locale.localeconv()
now = datetime.datetime.now()
some_price = 1234567.89
formatted_price = locale.format('%1.2f', some_price, grouping=True)
currency_symbol = local_conv['currency_symbol']
print(now.strftime('%x'))
print(f'{currency_symbol}{formatted_price}')
В данном примере мы импортируем модуль, меняем все настройки локалей на US English и извлекаем соглашения локали. С помощью метода locale.format
мы можем отформатировать число и не беспокоиться о разделителях в разрядах десятков и тысяч. С помощью директивы %x
для форматирования даты день, месяц и год будут стоять в правильном порядке для текущей локали. Из соглашений локали мы получим и корректный символ для обозначения валюты.
Ниже вы видите выходные данные того кода на Python. Мы видим, что дата соответствует формату Month/Day/Year
, десятичный разделитель – это точка, а разделитель разряда тысяч – запятая, а также есть знак доллара для валюты США.
$ python format_example.py
05/03/2019
$1,234,567.89
Теперь с тем же кодом, но изменив локаль на Portuguese Brazil, мы получим другой вывод, основанный на бразильских соглашениях форматирования: дата будет отображаться в формате Month/Day/Year
, запятая будет разделителем для десятков, а точка для тысяч, символ R$ будет говорить о том, что сумма указана в бразильских реалах.
import datetime
import locale
locale.setlocale(locale.LC_ALL, locale='pt_BR')
local_conv = locale.localeconv()
now = datetime.datetime.now()
some_price = 1234567.89
formatted_price = locale.format('%1.2f', some_price, grouping=True)
currency_symbol = local_conv['currency_symbol']
print(now.strftime('%x'))
print(f'{currency_symbol}{formatted_price}')
Легче ли дела обстоят в Django?
Переводы и форматирование
Интернационализация включается по умолчанию при создании проекта на Django. Модуль перевода инкапсулирует библиотеку GNU и предоставляет функционал gettext
с настройками перевода на основе языка, полученного из заголовка Accept-Language, который браузер передает в объекте запроса. Итак, весь тот код на Python, который мы видели раньше, оказывается инкапсулирован в модуль перевода из django utils, так что мы можем перепрыгнуть далеко вперед и просто использовать функцию gettext:
from django.http import HttpResponse
from django.utils.translation import gettext as _
def my_view(request):
greetings = _('Hello, World!')
return HttpResponse(greetings)
Для переводов, мы можем помечать переводимые строки в коде Python и в шаблоне (после загрузки тегов интернационализации). Тег trans
template переводит одну строку, тогда как тег blocktrans
может пометить как переводимый целый блок строк, включая переменный контент.
<p>{% trans "Hello, World!" %}</p>
<p>{% blocktrans %}This string will have {{ value }} inside.{% endblocktrans %}</p>
Помимо стандартной функции gettext
в Django есть ленивые переводы: помеченная строка будет переведена только тогда, когда значение используется в контексте строки, например, при рендеринге шаблона. Особенно полезно это бывает для перевода атрибутов help_text
и verbose_name
в моделях Django.
Аналогично интерфейсу командной строки GNU, django admin предоставляет команды эквивалентные тем, которые часто используются в процессе разработки. Чтобы собрать все строки, помеченные как переводимые в коде, вам просто нужно выполнить команды django admin makemessages
для каждой локали, которую вы хотите поддерживать в своей системе. Как только вы создадите папку locale в рабочей области проекта, эта команда автоматически создаст правильную структуру папок для PO-файла для каждого языка.
Чтобы скомпилировать все PO-файлы, вам просто нужно выполнить django admin compilemessages
. Если вам нужно скопировать PO-файл для конкретной локали, вы можете передать его в качестве аргумента django-admin compilemessages --locale=pt_BR
. Чтобы получить более полное представление о том, как работают переводы в Django, вы можете ознакомиться с документацией.
Django также использует заголовок Accept-Language для определения локали пользователя и правильного форматирования дат, времени и чисел. В примере ниже мы видим простую форму с DateField
и DecimalField
. Чтобы указать, что мы хотим получить эти входные данные в формате, согласующимся с локалью пользователя, нам просто нужно передать параметр localize
со значением True
в экземпляр поля формы.
from django import forms
class DatePriceForm(forms.Form):
date = forms.DateField(localize=True)
price = forms.DecimalField(max_digits=10, decimal_places=2, localize=True)
Как меняется процесс разработки?
После интернационализации приложения процесс развертывания должен быть адаптирован к процессу перевода. В нашем проекте мы отправляем любые новые термины в перевод сразу после развертывания в промежуточной среде. Развертывание на продакшн будет утверждено сразу после перевода всех терминов и компиляции PO-файлов.
Еще одно важное изменение в потоке разработки в нашем случае затронуло добавление интеграционных тестов для различных локалей на этапе QA. Команда QA будет симулировать локали, поддерживаемые нашим приложением, и проверять, все ли тексты были корректно переведены, валюты и единицы измерения преобразованы.
Наш главный вывод из всего этого процесса интернационализации заключается в том, что все эти шаги должны были быть заложены на этапе проектирования в самом начале проекта. Остановить всю разработку, чтобы провести интернационализацию – не лучшее решение. Если ваш проект уже не на ранней стадии, то я рекомендую следовать правилу бойскаута: начните помечать строки, которые необходимо перевести, когда внедряете новую функцию или исправляете некритический баг. Таким образом, мы все равно будете поставлять новые функции вместе с постепенным процессом интернационализации.
Интересно развиваться в данном направлении? Узнайте больше о курсе «Python Web-Developer» и записывайтесь на бесплатные Demo-уроки в OTUS!
EvilGenius18
Общее число людей говорящих на английском: 1.268 миллиарда.
Поэтому документацию, код и тд. могут читать не 4,67%, а 16% населения (1,268,000,000 / 7,818,022,350). А если взять только разработчиков (раз уж вы говорите про документацию, код), то это значение будет и того выше, возможно даже >90%
Более того, большинство разработчиков, не являющиеся носителями английского, даже предпочитают читать документацию на английском, поскольку все понятия привычны и понятны, в отличии от русских аналогов, в которые нужно постоянно мысленно переводить все встречающиеся определения: «поставляемый» (shipping), «подготовленный» (staged), «в процессе» (in progress), «материалы по теме, используемые в качестве примеров» (references), «узел» (node), «дочерний элемент» (child), «кортеж» (tuple), «жесткое кодирование» (hardcoding), «проблема с кодом» (bug), «проблема с программой» (issue), и тд. Или англицизмы, для которых в любом случае нужно понимать английские значения: «пул реквест», «линтер», и тд.
Из-за всего этого русская документация для русских носителей в большинстве случаев звучит более странно и читается труднее, чем английская документация, поскольку вынуждает постоянно держать в голове 2 перевода для каждого определения.