Как стать автором
Обновить

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

Спасибо за статью-инструкцию. Подобное на русском языке ранее не встречал
Слежу за темой интернационализации сайтов с 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 и рестарт сервера.

В итоге, могу сказать, что тема пелотки мультиязычности еще очень, как не раскрыта, и перевод только констант, как рассказано в статье, это маленькая песчинка на верхушке интернационального айсберга. В котором еще есть перевод чисел и других значений, и дат и много чего еще, о чем ни автор, ни я, даже не вспомнили.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
а можете подробнее рассказать, как это потом на фронтэнде решается?
НЛО прилетело и опубликовало эту надпись здесь
Откройте для себя сайт djbook.ru. Официальная документация на русском языке. Половина статьи это пересказ документации, вторая половина про интеграцию непонятно чего, непонятно зачем. В чем преимущества по сравнению с другими решениями? Я на начальном этапе использовал Rosetta. Простое и быстрое решение править переводы прямо в админке. Но с усложнением проекта, с необходимость дать переводчикам инструмент для перевода независимо от сайта, то полностью перешли на weblate. Выбор пал из за возможностей как у конкурентов и бесплатностью. Плюс можно развернуть на своём сервере.
У нас поддержка переводов 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, есть и другие платформы для переводов.
А у меня встречался заказчик, который, во-первых, работал с арабскими языками (RTL, все дела). Если с переводом текста мы более-менее разобрались, то когда пришла очередь чисел — я пожалел что я не уволился раньше. Числа идут из базы и надо их переводить везде в шаблонах тоже. Плюс часть вычисляется на месте в Javascript и надо их переводить там тоже.
А еще есть сторонние (frontend) библиотеки, которые класть хотели на какую-то там интернационализацию и выводят все как хотят.

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

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

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