четверг, 11 апреля 2013 г.

Интернационализация Django

Сразу хочу сказать, ничего нового я не напишу. Более того, не претендую на всеобъемлющее и полное описание. Наконец, касаюсь только одной версии Django - 1.5.0. С другими не требовалось делать интернационализированные сайты. Ну и последнее замечание - статья больше для себя, даже не для новичков: все давно можно найти на просторах Интернет, но если кому-то пригодится будет здорово.

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

Итак. Задача стоит следующим образом: необходимо сделать сайт на, например, двух языках: русском и английском. И сразу первый сюрприз: основной язык необходимо считать английским, а русский - локализацией. Этого требует американское происхождение Django. Итак. Основной язык en, локализация ru.

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

  • Нужны переводы текста в Python-коде: всякого рода "Error!", "Page NOT found" надо корректно уметь переводить.
  • Нужны переводы текста в HTML-шаблонах: "News" в заголовке при переключении на русский язык должно стать "Новости"
  • Нужен и перевод текста, хранящегося в БД.
  • URI уникальные для каждого языка.

Рассмотрим средства для решения трех последних - локализацию в коде пока не использовал, потому тонкости не смогу рассказать: по этим граблям ещё предстоит пройтись в будущем.

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

  1. Перевод в шаблонах. Используется средство gettext. Файл проекта settings.py надо модифицировать так:

    import os
    LANGUAGE_CODE = 'ru'

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

    LOCALE_PATHS = (
    os.path.join(os.path.dirname(__file__), 'locale'),
    )
    MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    )

    Из замечаний хочется сказать, что django.middleware.locale.LocaleMiddleware должна быть повыше и после django.contrib.sessions.middleware.SessionMiddleware. Так же, и главное, что этого нет в официальной доке, указываем LOCALE_PATHS. Это ложь, конечно. Есть, но особого акцента на этом не ставится. В свое время пару дней потратил на борьбу с "мельницами" - лучше указать, ведь "Явное, лучше неявного.".

    Подготовка к переводам шаблонов (на самом деле и переводов в Python-коде, если этот перевод готовить) завершена. Как же процесс перевода этой части проходит? В шаблонах все фразы отмечаем шаблонными тэгами {% trans "English" %} и {% blocktrans %}"English"{% endblocktrans %}, а в самом начале, после, если имеется, команды extend каждого шаблона указываем, что данный шаблон подлежит переводу - {% load i18n %}. Создаем каталог locale в месте, указанном в LOCALE_PATHS, обычно - корень проекта. Запускаем команду

    django-admin.py makemessages -l ru -a

    и Django создает локализацию: генерируется файл django.po. В нем много групп по три строки

    #: abi/templates/base_event.html:13
    msgid "Main"
    msgstr ""

    где первая строка — места, в которых встретилась фраза, помеченная для перевода, вторая строка — фраза для перевода ("English"), а третья — перевод, который надо вписать ("Английский"). Специфику перевода, зависящего от числа рассматривать не буду. После того как будут заполнены строки переводов, выполняем команду по сборке интернационализации:

    django-admin.py compilemessages.

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

    Сейчас при переключении языков на сайте фразы на английском и русском должны так же соответственно языку меняться. Идем дальше.

  2. Перевод текста, хранящегося в БД. Это никак не описывается в официальной доке Django: так что интернационализировать данные в БД будем сторонним решением. Для начала могу сказать, что очень много решений для перевода БД устарели и не работают в версии Django 1.5.0, хотя в сети много инструкций и статей по их использованию. Соответственно выбираем работающий инструмент django-transmeta. Каждый класс в моделях подготавливается для работы с django-transmeta отдельно. Допустим у нас есть такая модель:

    class EventRecord(models.Model):
           title = models.CharField(max_length = 160)
           title_side = models.CharField(max_length = 160)
           descript = models.CharField(max_length = 120, blank = True)
           body = models.TextField()
           report = models.BooleanField()

           def __unicode__(self):
                 return self.title

           class Meta:
                 ordering = ['id']

    Положим, мы хотим, чтобы поля title, title_side, descript и body имели перевод. Для этого модель надо изменить следующим образом:

    from transmeta import TransMeta


    class EventRecord(models.Model):
           __metaclass__ = TransMeta
           title = models.CharField(max_length = 160)
           title_side = models.CharField(max_length = 160)
           descript = models.CharField(max_length = 120, blank = True)
           body = models.TextField()
           report = models.BooleanField()

           def __unicode__(self):
                 return self.title

           class Meta:
                 ordering = ['id']
                 translate = ('title', 'title_side', 'descript', 'body')

    Теперь необходимо выполнить команду ./manage.py sync_transmeta_db[1] и transmeta изменит поля в БД. Конкретнее, вместо поля body, будут созданы поля body_ru и поле body_en. Еще конкретнее: body будет переименовано в body_ru (основной язык). Эти поля необходимо, например, в админке, заполнить. Теперь в зависимости от языка[2] будут выбираться поля БД с соответствующим языковым префиксом.

  3. URI. Тут довольно просто, чтобы URI зависел от языка, используйте from django.conf.urls.i18n import i18n_patterns и

    urlpatterns += i18n_patterns('event.views',
           url(r'^event/(\d+)/$', 'event', name = 'event_anon'),
    )

    вместо patterns.

    Что ещё полезно, так это не конструировать URI вручную, а использовать шаблонный тэг {% url %} - он поддерживает интернационализацию и будет указывать на страницу с тем же языком.

Примечания
  1. При использовании sqlite3 команда не выполняется: из-за ограниченных возможностей ALTER таблицы не могут измениться так, как требуется для django-transmeta. Самый просто вариант - проделать указанные процедуры вручную. Для этого создаем дамп в формате SQL и модифицируем CREATE TABLES и INSERT для таблиц, модели которых должны иметь перевод. Сохраняем и пересоздаем sqlite из дампа - в админке все поля должны быть доступны для внесения перевода.
  2. И в самой официальной документации, и во многих статьях по теме было много сказано об изменении переменной django-language. Например, в статье, внизу создается форма... В общем всё странно и непонятно (было мне). Много читал, думал. Решил сделать сам. (с) Идея такая: язык зависит от текущего урла, в котором всегда строго в данный момент прописан язык. Используется функция, модифицирующая, согласно этой идее, request:

    def set_lang(request):

           '''Set 'django_language' in request.session'''

           url = request.META['PATH_INFO'].split('/')[1]
           request.session['django_language'] = url
           return request

    Для меня решение подошло. Для ваших целей может не подойти. Но так понятнее, верно? ;)

3 комментария:

  1. Спасибо, помогло, особенно про явное указание LOCALE_PATHS

    ОтветитьУдалить
  2. А я в свою очередь могу всем настоятельно рекомендовать html5 шаблон , улучшает и рационализирует разметку документов, и много прочего!

    ОтветитьУдалить