Pull to refresh

Comments 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 и рестарт сервера.

В итоге, могу сказать, что тема пелотки мультиязычности еще очень, как не раскрыта, и перевод только констант, как рассказано в статье, это маленькая песчинка на верхушке интернационального айсберга. В котором еще есть перевод чисел и других значений, и дат и много чего еще, о чем ни автор, ни я, даже не вспомнили.
UFO just landed and posted this here
UFO just landed and posted this here
а можете подробнее рассказать, как это потом на фронтэнде решается?
UFO just landed and posted this here
Откройте для себя сайт 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 или кто там еще был… Я на этом месте прямо сказал что не буду это делать.

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