Пользователь
0,0
рейтинг
31 октября 2012 в 18:30

Разработка → Интернационализация локального проекта django

Хорошо, когда при разработке проекта под django, разработчики проекта изначально озаботились его интернационализацией.

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

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

Встретившись с этой задачей, я понял, что выполняя ее вручную, я умру от скуки, если прежде того меня не стошнит. При этом я с удивлением обнаружил, что инструментов, которые бы облегчали мне жизнь хотя бы наполовину, до сих пор нет. Четыре дня работы — и на свет появился проект django-make-i18n, который я вам и представляю.

Всю работу выполняет один достаточно объемный скрипт на python. Дополнительно к питону (у меня 2.7), сам скрипт зависит только от polib и не требует наличия окружения django.

Запуская скрипт в первый раз и напуская его на каталог проекта, вы получаете заготовку файла django.po, в которой собраны все обнаруженные строки на локальном (по умолчанию — русском) языке. Пойманные строки размещены в разделах msgstr, а msgid заполнены начальным значением «NEEDS TO BE EDITED [nnn]».

Исходные файлы не изменяются, вместо этого рядом с исходным проектом создается параллельный, с добавлением к имени каталога расширения .i18n.

Далее вам предстоит пройтись по файлу django.po и произвести «обратный перевод», то есть заменить начальные значения msgid каким-нибудь внятным эквивалентом на английском языке. Надо только следить, чтобы у всех строк msgid значения при этом оставались уникальными.

Второй запуск скрипта анализирует файл django.po и производит подстановку обращений к подсистеме перевода вместо исходных локальных строк. Например, допустим Вы имеете файл views.py, в котором размещен следующий код:

    if request.method == 'POST': # If the form has been submitted...
        post = True
        logging.info('POST запрос обнаружен')
        request_form = RequestVideoForm(request.POST)
        if request_form.is_valid(): # All validation rules pass
            logging.info('Форма верна')
            ok = True
            send_request(request,request_form,request.region.urlname,'feedback@doroga.tv')
            message = u'Ваша заявка была отправлена'
            request_form = None
            # clean form
        else:
            message = u'Исправьте данные в форме'


После первого запуска скрипта, в django.po обнаружатся строки примерно следующего содержания:

msgid "NEEDS TO BE EDITED [333]"
msgstr "POST запрос обнаружен"

msgid "NEEDS TO BE EDITED [334]"
msgstr "Форма верна"   

msgid "NEEDS TO BE EDITED [335]"
msgstr "Ваша заявка была отправлена"

msgid "NEEDS TO BE EDITED [336]"
msgstr "Исправьте данные в форме"



Если вы ничего не измените в файле django.po, то после второго запуска скрипта, будет создана копия файла views.py, содержащая примерно следующие строки:

    if request.method == 'POST': # If the form has been submitted...
        post = True
        logging.info(_('NEEDS TO BE EDITED [333]'))
        request_form = RequestVideoForm(request.POST)
        if request_form.is_valid(): # All validation rules pass
            logging.info(_('NEEDS TO BE EDITED [334]'))
            ok = True
            send_request(request,request_form,request.region.urlname,'feedback@doroga.tv')
            message = _('NEEDS TO BE EDITED [335]')
            request_form = None
            # clean form
        else:
            message = _('NEEDS TO BE EDITED [336]')
        
    else:
        logging.info(_('NEEDS TO BE EDITED [337]'))
        request_form = RequestVideoForm(auto_id=True)



Попробуем заполнить строки в django.po следующим образом:

msgid "POST request has been found"
msgstr "POST запрос обнаружен"

msgid "Form is valid"   
msgstr "Форма верна"

msgid "Your request has just been sent"
msgstr "Ваша заявка была отправлена"

msgid "Fix form data"   
msgstr "Исправьте данные в форме"


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

    if request.method == 'POST': # If the form has been submitted...
        post = True
        logging.info(_('POST request has been found'))
        request_form = RequestVideoForm(request.POST)
        if request_form.is_valid(): # All validation rules pass
            logging.info(_('Form is valid'))
            ok = True
            send_request(request,request_form,request.region.urlname,'feedback@doroga.tv')
            message = _('Your request has just been sent')
            request_form = None
            # clean form
        else:
            message = _('Fix form data')



Дополнительно, скрипт добавляет в начало файла код импорта пакета интернационализации:

from django.utils.translation import ugettext as _


на случай, если вы случайно забыли сделать это.

Те же операции, с учетом специфики языка и использования django, выполняются на файлах html и js. При этом для JavaScript создается файл djangojs.po.

Осталось скомпилировать сообщения:

python manage.py compilemessages --all


Вуаля — ваш проект подготовлен к интернационализации. Конечно, еще остается много всяких деталей самой интернационализации под django, но они выходят за рамки данной статьи.
Всеволод Новиков @nnseva
карма
33,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (14)

  • 0
    Это все хорошо, а если нужен перевод не просто русский-ангийский, а русский-ангийский-немецкий, как быть с оформлением файла django.po? Он ведь по большму счету представляет собой хеш таблицу где строчному ключу соответствует только одно значение.
    • 0
      docs.djangoproject.com/en/1.3/howto/i18n/
      Файлик кладётся в
      $APPPATH/locale/<language>/LC_MESSAGES/django.(po|mo)
      • 0
        Спасибо, буду, впредь, более внимательно смотреть доки.
    • 0
      Согласно документации по django, вам нужно иметь по одному файлу django.po для каждого из языков (ну кроме английского, который используется для идентификации сообщений).

      Вы размещаете файлы django.po в подкаталогах каталога locale (либо в проекте — для всего проекта, либо в приложении — для конкретного приложения), соответствующих поддерживаемым языкам. Например, для русского — locale/ru/LC_MESSAGES/django.po, для французского locale/fr/LC_MESSAGES/django.po, и так далее.

      Подсистема интернационализации django определяет текущий язык по параметрам запроса и отдает содержимое, соответствующее фразам, заготовленным в файле django.po в каталоге для языка пользователя. Если каталог с описанием фраз для данного языка не найден, по умолчанию используются строки msgid.

      В контексте статьи, вы можете заготовить файл django.po для русского языка с помощью django-make-i18n, а затем скопировать его в каталоги для других языков и вставить фразы на этих языках.
      • 0
        Что бы не редактировать все в ручную есть батарейка django-rosetta, она расскажет вам какие данные уже переведены, а какие ещё нет, и предоставит удобный интерфейс добавления/удаления переводов

        manage.py makemessages
        На основе уже имеющихся в проекте ugettext и подобных функций строит .po файлы
  • +5
    Поделюсь и своим опытом создания многоязычных приложений.
    Одним из немаловажных моментов является переключатель языка и пара нюансов вокруг этого, которые некоторые не соблюдают(и даже не осознают что они есть)

    Допустим пишем мы сайт для русского и английского, в настройках у нас имеется:

    ...
    LANGUAGES = (
        ("ru", "Russian"),
        ("en", "English"),    
    )
    ...
    


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

    Но некоторые умудряются еще и так:

    from django.utils.translation import ugettext_lazy as _
    бла-бла..
    LANGUAGES = (
        ("ru", _("Russian")),
        ("en", _("English")),    
    )
    ...
    

    Это зачем вообще так делать? Это значит что если включен русский, тов списке будет: Русский, Английский.
    А теперь представьте себе как англоязычным людям искать свой английский? Что если языков много?
    Представляете если вы заходите на китайский сайт, там есть русский, но вы не можете об этом знать и переключить! Потомучто в выпадающем меню написано «Русский» — по-китайски!!!

    Лучше уж все оставить на английском верно?

    А ну на самом деле есть более элегантное решение, штатными средствами Django:

    Оставляем это как угодно:

    ...
    LANGUAGES = (
        ("ru", "Russian"),
        ("en", "English"),    
    )
    ...
    


    а дальше создаем форму для переключения зыков:

    from django.utils.translation import get_language_info
    from django.conf import settings
    
    #Сначала мы формируем нормальный CHOICES:
    
    LANGUAGES = [
        (code, get_language_info(code).get("name_local"))
        for code, lang in settings.LANGUAGES
    ]
    
    class SettingsForm(forms.Form):
        
        if settings.USE_I18N:
            language = forms.ChoiceField(
                label=_("Language"),
                choices=LANGUAGES,
                required=False
            )
    


    Теперь у нас в списке каждый язык написан на своем собственном языке: Русский, English
    И носителю каждого из языков, будет легко и просто найти свой родной язык!
    • +1
      Да, отличное решение!
  • 0
    Еще бы прикрутить сюда автоматический перевод с помощью гугло транслейт.
    Было бы совсем для лентяев.
    • +1
      Не получится.

      Во-первых, строки с форматированием будут содержать placeholders (как ето будет по-рюсски?) вида %(member)s или %s, на которых транслятор сойдет с ума.

      Во-вторых, даже на нормальных фразах с чуть более сложной грамматикой, любой известный мне автотранслятор тоже сходит с ума. Я все-таки предпочитаю честно отдать перевод на откуп человеку, чем создавать необоснованную иллюзию сделанной работы. А то получится например, как у китайцев, пытающихся перевести названия своих поделок для андроида (далее цитата из А-Маркета, топ поиска «Игры для детей»):

      ===========

      «Плоды памяти игру для детей»

      Описание

      «Яблоко, апельсин, клубника… Есть очень много фруктов здесь! И ваша задача найти эти же фрукты и сопоставить их. Эта забавная игра памяти также помогает Вам обучение памяти способности. Удачи!»

      ===========

      Похоже на русский язык? Да, немного :)
  • 0
    Не понял, в чем принципиальное отличие от manage makemessages?
    django-rosetta решает большинство описанных здесь проблем.
    • 0
      manage.py makemessages собирает только сообщения, помеченные обращениями к подсистеме i18n (в исходниках — *gettext, в шаблонах — trans и blocktrans).

      Данный скрипт собирает сообщения в таком проекте django, в котором нет обращений к подсистеме i18n (не было изначально предусмотрено при разработке), и помечает их обращениями к подсистеме i18n. При этом, собираются только те сообщения, которые содержат локальный язык (распознаются регекспами).

      Тем самым, выполняется задача интернационализации такого проекта, который изначально не был предусмотрен к интернационализации. Приведенный скрипт не заменяет собой makemessages, а дополняет его функции.

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

      django-rosetta дает лишь графицский интерфейс к редактированию файлов .po, никак не помогая решать поставленную задачу сбора локальных строк.
      • 0
        ясно ) какой-то адский костыль
        • 0
          Ога, но когда я обнаружил что такого еще не было, стало очень грустно. Пришлось смастрячить и поделиться.

          Реально я думаю, потребность в таком костыле у каждого программера наверно возникнет два-три раза за всю карьеру. Тем не менее, считаю 4 потраченных дня — потраченными недаром. Список обнаруженных в проекте строк для локализации — 495, я как минимум потратил времени ровно столько, сколько потратил бы, собирая их вручную. Не учитывая внесенных в проект ошибок при ручной правке. Если хотя бы один человек успешно воспользуется моей тулой на достаточно обширном проекте — буду считать, что свою задачу я выполнил.
        • 0
          Почему костыль? Просто утилита.

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