company_banner

Django: краткое руководство по интернационализации

Автор оригинала: Stephan Schoening
  • Перевод
Перевод приложения на разные языки и его локализация — это нечто такое, чем приходится заниматься всем разработчикам. В материале, перевод которого мы сегодня публикуем, представлено краткое руководство по интернационализации Django-приложений.

Часть того, о чём тут пойдёт речь, применимо к локализации любых Python-проектов. Разобрав основы, мы поговорим об ускорении работ по интернационализации. В частности — о применении платформы Phrase.



Предполагается, что у читателя этого материала имеется работающее Django-приложение, и то, что у него установлен пакет gettext (установить его можно, например, командой pip install gettext). Если вы раньше не пользовались Django (популярным веб-фреймворком, основанным на Python), то вам, возможно, будет полезно сначала взглянуть на это официальное руководство, а потом вернуться к данной статье.

Базовые настройки рабочей среды


Итак, представим, что у вас имеется Django-проект mysite, и приложение, называемое polls. Структура проекта должна выглядеть примерно так:

/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py
    polls/
        migrations/
        __init__.py
        admin.py
        models.py
        tests.py
        views.py

Первый шаг нашей работы заключается в проверке того, активирована ли опция интернационализации в конфигурации проекта. Для того чтобы это сделать, нужно внести следующие изменения в mysite/settings.py:

# mysite/settings.py
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True

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


Теперь нужно пометить все строки, которые должны быть переведены на разные языки. Представим, что у вас имеется следующий файл шаблона polls/templates/polls/index.html:

<!-- polls/templates/polls/index.html -->
<h1>Welcome to our site!</h1>
<p>Here you find polls.</p>

Этот файл нужно переработать следующим образом:

<!-- polls/templates/polls/index.html -->
{% load i18n %}
<h1>{% trans 'WelcomeHeading' %}</h1>
<p>{% trans 'WelcomeMessage' %}</p>

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

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


Если требуется локализовать строки, находящиеся внутри Python-кода (например, в файле polls/views.py), нужно импортировать в файл функцию ugettext. Ей, что вполне нормально, можно назначить псевдоним _. Вот как будет выглядеть локализованный вариант простой функции из файла views.py:

# polls/views.py
from django.http import HttpResponse
from django.utils.translation import ugettext as _
def index(request):
    output = _('StatusMsg')
    return HttpResponse(output)

Создание файлов перевода


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

django-admin makemessage -l de

Здесь de можно заменить на код локали для языка, который планируется добавить в приложение. В нашем примере выполнение этой команды приведёт к созданию gettext-файла polls/locale/de/LC_MESSAGES/django.po следующего содержания:

# polls/locale/de/LC_MESSAGES/django.po
...
#: templates/polls/index.html:3
msgid "WelcomeHeading"
msgstr ""
#: templates/polls/index.html:4
msgid "WelcomeMessage"
msgstr ""

Теперь в этот файл можно ввести переводы строк:

# polls/locale/de/LC_MESSAGES/django.po
...
#: templates/polls/index.html:3
msgid "WelcomeHeading"
msgstr "Willkommen auf unserer Seite!"
#: templates/polls/index.html:4
msgid "WelcomeMessage"
msgstr "Hier findet Ihr Umfragen."

Когда перевод будет готов — нужно всё скомпилировать, выполнив следующую команду:

$ django-admin compilemessages

Выполнять эту команду нужно, опять же, в директории polls.

Для того чтобы быстро проверить работоспособность перевода, нужно поменять код языка в файле mysite/settings.py. Например — так:

# mysite/settings.py
LANGUAGE_CODE = 'de'

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

Ускорение процесса локализации приложений с использованием Phrase


Если вы используете Phrase для организации работ по локализации приложений, то вам, на самом деле, не нужно вручную создавать и редактировать *.po-файлы! Вам достаточно создать и перевести ключи WelcomeHeading и WelcomeMessage в Phrase и использовать функцию экспорта для загрузки *.po-файлов.

Если у вас установлено наше средство командной строки Phrase Client, то ваша работа упрощается ещё сильнее. Достаточно создать в корневой директории проекта конфигурационный файл .phraseapp.yml со следующим содержимым:

# .phraseapp.yml
phraseapp:
  access_token: <your access token>
  project_id: <your project's id on PhraseApp>
  file_format: po
  pull:
    targets:
        file: "polls/locale/<locale_code>/LC_MESSAGES/django.po

Затем надо выполнить в корневой директории проекта следующую команду:

phraseapp pull && django-admin compilemessages

Благодаря этому будут обновлены все переводы проекта.

Кроме того, ещё больше упростить работу может использование в вашем Django-проекте нашего редактора In-Context Editor. Для этого достаточно установить django-phrase с помощью pip:

pip install django-phrase

Затем достаточно отредактировать шаблоны, при работе с которыми вы планируете использовать In-Context-Editor:

<!-- polls/templates/polls/index.html -->
{% load i18n %}
{% load phrase_i18n %}
{% phrase_javascript %}
<h1>{% trans 'WelcomeHeading' %}</h1>
<p>{% trans 'WelcomeMessage' %}</p>

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

И наконец, в конфигурационный файл нужно добавить следующее:

# mysite/settings.py
PHRASE_ENABLED = True
PHRASE_PROJECT_ID = 'YOUR_PROJECT_ID'
PHRASE_PREFIX = '{{__'
PHRASE_SUFFIX = '__}}'

После этого всё будет готово к работе.

Выбор локалей


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

# mysite/settings.py
from django.utils.translation import ugettext_lazy as _
...
MIDDLEWARE_CLASSES = (
    ...,
    'django.middleware.locale.LocaleMiddleware',
    ...,
)
...
LANGUAGE_CODE = 'en-us'
LANGUAGES = (
    ('en-us', _('English')),
    ('de', _('German')),
)

При таком подходе, если у пользователя будет локаль German, он увидит вариант перевода de. В противном случае в качестве перевода по умолчанию будет использоваться en-us. Проверить правильность работы этого механизма можно с помощью curl:

curl http://localhost:8000/polls -H "Accept-Language: de"

Эта команда должна вернуть примерно следующее:

<h1>Willkommen auf unserer Seite!</h1>
<p>Hier findet Ihr Umfragen.</p>

Итоги


В этом материале мы рассмотрели основы интернационализации Django-приложений. Здесь же мы рассказали о сервисе Phrase, который способен ускорить и упростить работу. Надеемся, то, о чём вы узнали, вам пригодится.

Уважаемые читатели! Как вы подходите к интернационализации ваших Python-проектов?

RUVDS.com
VDS/VPS-хостинг. Скидка 10% по коду HABR

Комментарии 9

    +2
    Спасибо за статью-инструкцию. Подобное на русском языке ранее не встречал
      +3
      Слежу за темой интернационализации сайтов с 2009 года. Бегло говорю на трех языках(de, en, ru), в планах изучить итальянский и испанский(что бы смачно ругаться).

      В 2010 я с, уже бывшей, супругой — мы создали бюро мультиязычной поддержки в Австрии. Сначала это было создание мультиязычных PHP проектов, в основном мутации замшелой typo3, последние 5 лет я работаю с Django.

      В моем проекте winePad 7 языков: de/en/it/fr/nl/es/ru, планируем добавить венгерский и бельгийский. Объем переводов — 14 000 языковых констант плюс около 2 000 000 описаний, которые добавляются по 500 в день, и их надо переводить. Проект живет с 2011 года, за это время было много идей, как это все должно работать.

      В статье рассказано, как сделать перевод констант, причем предложен не самый лучший вариант. Объясню:
      Сидят у меня семь специализированных переводчиков, им нужен набор текстов, которые они могут перевести, откорректировать, внести, проверить, как выглядит на демо сервере, откорректировать еще раз, если криво выглядит в контексте — еще раз откорректировать, и, если все ок, выкатить на сервер.

      Работа с PO/MO файлами папки locales это ад для любого «не программиста», а если что задеть, так вообще все падает. Напомню, что еще и сервер надо перезапускать после каждого обновления переводного файла, каждого языка.

      Подходящим решением показалась django-rosetta, после ее установки стало немного проще. Но ненадолго. Розетта позволяет из бэкэнда переводить текстовые константы, причем она визуально разбивает таблицы переводов по языкам. Переводчикам понятно, есть выгрузка в эксель, можно переводить в специализированном переводческом по, и обратно загружать переводы. Вроде бы чего еще надо? Но тут выяснились и неудобства.
      • Розетта теряет переводы. Через неделю работы выяснилось, что некоторые переводы стали пропадать. Конечно косяк был найден (не устранен, кому надо — копайте в розетте способ обработки po файлов), мои переводчики оповещены.
      • Компиляция в MO и рестарт сервера. Это доверять переводчикам нельзя, настройка авто-рестарта после каждого обновления на продакшн не подходит, остается только вручную. Пока остановились на регулярном ночном авто рестарте сервера.
      • Безопасность. Невозможно настроить права доступа к переводам для разных переводчиков. Если редактор поправил языковую константу, то ее опять может поправить рядовой переводчик. История правок не сохраняется, невозможно проконтролировать кто и когда этот перевод сделал или поправил существующий.
      • Жесткость в языковой настройке. Есть только N языков в settings. Вот и переводите эти N языков. Про запас переводить еще два в Rosetta не получится.
      • Отсутствуют: Сопоставление переводов, настройка интерфейса, процентная плотность выполненых переводов и т.д.

      Напомню, также, что упомянутая в статье рекомендация от самих создателей gettext, обрамлять переводные константы в коде маркером "_()", это тот еще выстрел в ногу для соблюдающих стандартную конвенцию использования Single Underscore. (2.3.2. Reserved classes of identifiers)

      Кроме этого, за долгое время работы стало понятно, что писать константы осмысленными конструкциями _('Grape variety') хуже, чем маркировать их типа _('APP_15_71'). Инородный маркер при тестировании четко дает знать, что этот перевод пока отсутствует. Ну и при Дедлайне помогает переводчикам сначала перевести самый замечаемый контент.

      Но Языковые константы, те, что вы пометили тегом trans в шаблонах или обернули в gettext, — это только мелкая часть тех сложностей, с которыми вы столкнетесь, когда захотите говорить о настоящей мульти-язычности. Реально, все константы когда-нибудь будут переведены на 80% и вы, скорее всего, на этом успокоитесь.

      И когда основной темой для вас станет перевод данных из базы, вот тут-то и начнется рубилово…
      Я отлично понимаю, что этот вопрос останется актуальным максимум лет на 10-15, пока формируются надежные языковые базы автопереводчиков типа Google Translator. Пока же доверять им важные тексты никак нельзя:

      Один из наших бывших переводчиков немного поленился и внес нам в базу около 1000 автопереводов Гугла. Жаль, что линчевание у нас запрещено, и все, что нам оставалось — это переводчика уволить. Но тут и там иногда еще проскакивает гугло-перевод: «По мнению Джима-Отсоса....» В исходнике это был James Suckling, Internationally acclaimed wine critic and journalist.

      Так вот про рубилово. Если вы начнете думать, как переводить базу, то перед вами встанут решения:
      • хранить жестко заданное количество переводов прямо в таблицах моделей (django model translations, django-model-translate и т.д.). Новый язык — миграция базы, новое переводное поле — миграция. миграция, миграция....
      • Хранить переводы в PO файлах (упаси вас бог от этого). Рестарт сервера вам в помощь. И еще один и потом еще… Ой, а у меня переводы потерялись… А можно сервер сейчас стартовать...
      • Хранить переводы в структуре EAV (HVAD, Parler). N+1 запрос, и еще один и еще...


      Для winePad в конечном итоге я написал свой переводчик данных из базы — django-tof (лежит на githab). В TOF я скрестил Ежа с Ужом: простоту из model-translation с гибкостью EAV, но ад теперь начался у генератора текстов SQL запросов.
      Кому интересно, можете использовать as-is или поучаствовать в доработке.

      Кроме этого, во всех моих проектах переопределен шаблонный тег {% trans %}. Я использую вместо переводных констант данные из базы, для переводчиков — работает надежнее розетты, для дизайнеров шаблонов ничего не поменялось, просто другой load, для пользователей — быстродействие, как и у родного тега, c тем преимуществом, что не требуется каждый раз делать makemessages/compilemessages и рестарт сервера.

      В итоге, могу сказать, что тема пелотки мультиязычности еще очень, как не раскрыта, и перевод только констант, как рассказано в статье, это маленькая песчинка на верхушке интернационального айсберга. В котором еще есть перевод чисел и других значений, и дат и много чего еще, о чем ни автор, ни я, даже не вспомнили.
        0
        Работа с PO/MO файлами папки locales это ад для любого «не программиста»

        Так нет необходимости пускать «не программистов» прямо в код. Есть же всякие GUI для этого. типа того же Poedit и прочих
        0

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

          0
          а можете подробнее рассказать, как это потом на фронтэнде решается?
            0

            Средствами фронтенд-фреймворка, т.е. трансляции в одном месте вместо двух, нет проблем с синхронизацией обоих проектов.
            На фронтенд уходят только данные.


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

          0
          Откройте для себя сайт djbook.ru. Официальная документация на русском языке. Половина статьи это пересказ документации, вторая половина про интеграцию непонятно чего, непонятно зачем. В чем преимущества по сравнению с другими решениями? Я на начальном этапе использовал Rosetta. Простое и быстрое решение править переводы прямо в админке. Но с усложнением проекта, с необходимость дать переводчикам инструмент для перевода независимо от сайта, то полностью перешли на weblate. Выбор пал из за возможностей как у конкурентов и бесплатностью. Плюс можно развернуть на своём сервере.
            0
            У нас поддержка переводов i18n проектов на Django делаются с помощью библиотеки oslo.i18n.

            Пример:
            $ pip install oslo.i18n

            В проект добавляется интеграционный модуль:
            # myapp/_i18n.py
            
            import oslo_i18n
            
            DOMAIN = "myapp"
            
            _translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
            
            # The primary translation function using the well-known name "_"
            _ = _translators.primary
            
            # The contextual translation function using the name "_C"
            # requires oslo.i18n >=2.1.0
            _C = _translators.contextual_form
            
            # The plural translation function using the name "_P"
            # requires oslo.i18n >=2.1.0
            _P = _translators.plural_form
            
            def get_available_languages():
                return oslo_i18n.get_available_languages(DOMAIN)

            И далее, в файлах проекта все тексты и сообщения оформляются следуюцим образом:
            from myapp._i18n import _
            
            # ...
            
            variable = "openstack"
            some_object.name_msg = _('my name is: %s') % variable
            
            # ...
            
            # The contextual translation function using the name "_C"
            # msg = _C('context', 'string')
            # The plural translation function using the name "_P"
            # msg = _P('single', 'plural', count)
            
            try:
            
                # ...
            
            except AnException1:
            
                # Log only, log messages are no longer translated
                LOG.exception('exception message')
            
            except AnException2:
            
                # Raise only
                raise RuntimeError(_('exception message'))
            
            else:
            
                # Log and Raise
                msg = _('Unexpected error message')
                LOG.exception(msg)
                raise RuntimeError(msg)

            Шаблоны:
                {% load i18n %}
                {% trans "Some another message" %}


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

            В CI системе работают две регулярные ежедневные задачи. Первая выгружает свежий код и документацию из master и релизных веток Git репозитория приложения, обрабатывает его, экспортирует все строки в .po файлы, .po файлы выгружаются в локальный сервер переводов Zanata, где строки становятся доступны для перевода переводчиками. Вторая задача выгружает из Zanata переведённые .po файлы и если процент выполненного перевода выше заданного, то создаёт запросы на изменение исходного кода и документации, где обновляет файлы с переводами (если процент перевода упал — удаляет файлы перевода).

            Переводчики не работают с исходниками, а работают только с очень удобным интерфейсом Zanata. Если хотят — могут работать с .po файлами напрямую и импортировать/экспортировать их самостоятельно, но вроде так никто не делает. Zanata перестала развиваться (хотя прекрасно работает, особенно в новом интерфейсе), как альтернативу можно рассмотреть упомянутый выше Weblate, есть и другие платформы для переводов.
              0
              А у меня встречался заказчик, который, во-первых, работал с арабскими языками (RTL, все дела). Если с переводом текста мы более-менее разобрались, то когда пришла очередь чисел — я пожалел что я не уволился раньше. Числа идут из базы и надо их переводить везде в шаблонах тоже. Плюс часть вычисляется на месте в Javascript и надо их переводить там тоже.
              А еще есть сторонние (frontend) библиотеки, которые класть хотели на какую-то там интернационализацию и выводят все как хотят.

              А потом оказалось что некоторые пользователи хотят, например, текст на арабском, а числа — «нормальные», то есть «современные арабские», в то время как другим нужно все в «арабском арабском».

              А потом надо было еще и Django-admin перевести тоже. С учетом всех дополнений типа grapelli или кто там еще был… Я на этом месте прямо сказал что не буду это делать.

              Интернационализация — это боль и слезы. Программистов в первую очередь.

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

              Самое читаемое