Интернационализация и локализация
Лекция прошлого года, практически совпадает с сегодняшней
на сайте esyr.org (лекцию читал Андрей Черепанов, который в то время курировал сообщество русских переводчиков KDE, почему фотографии при этом такие, вопрос к фотографу ☺)
О процессе в целом
- Локализация (l10n) — адаптация культурных и языковых предпочтений ПО
- Интернационализация (i18n) — приведение исходного кода в готовое для локализации состояние
Объекты ПО
- Сообщения и тексты в составе ПО
- Размер и оформление интерфейсных элементов (RTL, например)
- Изображения: текст на них и культурная атрибуция
Управляющие клавиши (иероглифы, ага )
Параметры, чувствительные к локали (locale)
- Ссылки на внешние ресурсы (поддержка, издатель и пр.; телефоны, url и пр.)
- (в сложных случаях) Шрифты
Другие объекты продукта
- Электронная документация
- Непрограммные данные (шаблоны документов и форм и т. п.)
- Бумажная документация и др. сопутствующие предметы
Локализация: инструменты
- Общие словари (?)
- Память перевода (локальные общие словари)
- Нечёткий перевод (при лёгком изменении ресурса или при добавлении похожего)
- Множественные формы
Эти инструменты применяются реже:
- Другие производные словоформы: склонение, спряжение и прочее.
- …
- Искусственный мозг
- …
- Естественный мозг
Централизованные инструменты (в основном, перевода)
- Я нашёл как минимум пару свободных и написанных на Питоне!
Тактика локализации
- «По бедности»: на каждый язык отдельный программный продукт или библиотека
По идентификатору: print(translate(IRON_MESSAGE, LANG_RU))
По примеру; print(translate("Did you plug your iron out?", LANG_RU))
actually, print(_("Did you plug your iron out?")), язык берётся из локали
Достоинства и недостатки тактик
Gettext и Babel
Про gettext:
Про Babel:
Создание перевода
- I18n:
Подключим в текущий namespace волшебную функцию «_()» — «перевести»
- Обмажем этой функцией все строки, нуждающиеся в переводе
Внимание! f-strings (форматные строки вида «f"...{имя}..."») по понятным причинам (строка-результат зависит от вычисления упомянутых в ней выражений) в python gettext не поддерживаются. Пользуйтесь методом .format().
…или, если очень надо, вот такой конструкцией (but why?)
Не удаляйте комментарий #, python-brace-format из po-файла, он нужен:)
(pybabel extract) Создадим на основе i18n-ванного исходного кода шаблон перевода, файл dopmain.pot (domain — это довольно произвольное название того, что мы переводим)
(pybabel init) Создадим прототип перевода (для русского — ru.po)
(текстовый редактор или специализированный инструмент редактирования .po) Переведём все строки в ru.po
(pybabel compile) Скомпилируем перевод в файл ru.mo
Обновление перевода
Вышла новая версия программы (или сами поправили). Там «поехали» строки с сообщениями (появились новые, пропали/изменились/переместились старые).
(pybabel extract) Сгенерируем новый шаблон
(pybabel update) Обновим содержимое ru.po на основании шаблона и старого ru.po. У update много искусственного мозга:
- Не теряет старые переводы (только комментирует)
Размечает новые сообщения возможными fuzzy-переводами из старых/закомментированных
Допереводим ru.po
(pybabel compile) Компилируем новый перевод
Процесс локализации
Пример перевода начинается с этого коммита
Основную информацию см. там
При использовании gettext.install() соответствующие функции приезжают в пространство имён совершенно прозрачно, и у линтеров едет крыша.
Как успокоить Flake8? Добавить builtins = _, ngettext в .flake8
Как успокоить pylint? Добавить в .pylintrc
[VARIABLES] additional-builtins=_, ngettext
Возможно, правильное решение — воспользоваться другим рецептом из методички:
Если параметр fallback установлен в False (по умолчанию), gettext.translation() бросит прерывание, если не найдёт перевода (например, на английский). С fallback=True в этом случае просто вернётся NullTranslations-объект, который тупо не станет ничего переводить, а оставит как есть.
ngettext нужен для поддержки plural, см. далее
Множественные формы
Функция ngettext() в английском варианте различает форму единственного и множественного числа (эту функцию надо отдельно попросить добавить в пространство имён при gettext.install())
- Запуск программы а разных локалях:
$ LC_ALL=en_US.UTF-8 python3 DateTime.py $ LC_ALL=ru_RU.UTF-8 python3 DateTime.py
- Русский перевод имеет 3 множественные формы — единственное число, немного (2-4), и много (5 и больше), которые время от времени возникают в разных числительных.
Описание того, какие числа какой множественной форме соответствуют содержится в заголовке ru.po. Это описание компилируется в некоторый код .mo-файла, который gettext интерпретирует (замечание vslutov@, спасибо).
См. пример модификации этого описания в репозитории к лекции по Linux-разработке.
Некоторые замечания
Для обвешивания нужных строк _()-ми в vim достаточно такой команды :
:%s/\("[^"]*"\)/_(\1)/gc
Domain на самом деле определяет не имя файла, а т. н. домен — один из трёх ключей, (язык, тип, домен), по которым происходит поиск перевода. Просто в Linux это превращается в путь к файлу /usr/share/locale/язык/тип/домен.mo. Наша программа не будет ходить в /usr/share/locale, вместо этого переводы ожидаются в подкаталоге ru/LC_MESSAGES того каталога, откуда запущена программа
в итоговом проекте недостаточно будет указать каталог «.» как в примере, там надо будет вычислять этот путь в соответствии с тем, куда и как продукт задеплоен
Редактировать .po-файл перевод можно очень разными редакторами
- многие IDE поддерживают работу с переводами
Я пользуюсь vim + vim-plugin-po-after-ftplugin
В числе прочего в переводе может нуждаться имя файла с картинкой: если на картинке была надпись или культурно обусловленный знак, в локализованной версии понадобится другой файл.
Обновление перевода
Т. н. «неточные переводы» помечаются значащим комментарием fuzzy.
При компиляции эти переводы не попадают в .mo-файл, но и не исчезают
- Неиспользованные переводы (которые были в старой версии, но отсутствуют в шаблоне), не удаляются, а комментируются
Дальнейшая работа с переводом — повторение цикла extract → update → правка ru.po → compile
Про память переводов
Очень важно соблюдать единообразие при переводе одинаковых надписей. Для этого все переводы по проекту можно сложить в т. н. «compendium» (он же translation memory, она же память переводов) — большой .po-файл со всеми возможными переводами, накопившимися за историю работы
Если в исходном проекте одинаковые надписи значат разное (например, «Copy» в одном месте — «Копировать», а в другом «Копия») — это плохой дизайн ,надписи надо менять
- Надписи типа «Save as…» или «Press button to continue» не придётся переводить по многу раз
- Появится больше автоматически подобранных fuzzy-переводов
- Фактически это будет ещё и словарь терминологии, который можно править при необходимости
В Babel нет поддержки compendium :(, но она есть в msgmerge из GNU Gettext
Д/З
В семестровом проекте должен присутствовать перевод. Если вы пишете какой-то модуль/бот/библиотеку без UI, переводите документацию (см. Руководство по переводу)
ВАЖНО: скомпилированный .mo файл (при использовании gettext в качестве инструмента перевода) в git-архив не кладётся. Это генерат