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

О процессе в целом

Объекты ПО

Другие объекты продукта

Локализация: инструменты

Эти инструменты применяются реже:

Централизованные инструменты (в основном, перевода)

Тактика локализации

Достоинства и недостатки тактик

Gettext и Babel

Про gettext:

Про Babel:

Создание перевода

  1. I18n:
    • Подключим в текущий namespace волшебную функцию «_()» — «перевести»

    • Обмажем этой функцией все строки, нуждающиеся в переводе
    • Внимание! f-strings (форматные строки вида «f"...{имя}..."») по понятным причинам (строка-результат зависит от вычисления упомянутых в ней выражений) в python gettext не поддерживаются. Пользуйтесь методом .format().

    • Не удаляйте комментарий #, python-brace-format из po-файла, он нужен:)

  2. (pybabel extract) Создадим на основе i18n-ванного исходного кода шаблон перевода, файл dopmain.pot (domain — это довольно произвольное название того, что мы переводим)

  3. (pybabel init) Создадим прототип перевода (для русского — ru.po)

  4. (текстовый редактор или специализированный инструмент редактирования .po) Переведём все строки в ru.po

  5. (pybabel compile) Скомпилируем перевод в файл ru.mo

Обновление перевода

Вышла новая версия программы (или сами поправили). Там «поехали» строки с сообщениями (появились новые, пропали/изменились/переместились старые).

  1. (pybabel extract) Сгенерируем новый шаблон

  2. (pybabel update) Обновим содержимое ru.po на основании шаблона и старого ru.po. У update много искусственного мозга:

    • Не теряет старые переводы (только комментирует)
    • Размечает новые сообщения возможными fuzzy-переводами из старых/закомментированных

  3. Допереводим ru.po

  4. (pybabel compile) Компилируем новый перевод

Процесс локализации

Пример перевода начинается с этого коммита

Основную информацию см. там

Воспользуемся рецептом из методички:

   1 import gettext
   2 translation = gettext.translation(домен, каталог_с_переводами, fallback=True)
   3 _, ngettext = translation.gettext, translation.ngettext

Переключение локали

Если программа должна выводить сообщения попеременно на разных языках, одним объектом типа gettext.Translation обойтись нельзя — в нём только один вариант перевода. На каждый используемый язык нужно регистрировать отдельный перевод: или из .mo-файла, или затыкать экземпляром NullTranslations. Локаль можно брать из какой-нибудь глобальной переменной, но лучше задавать с помощью locale.setlocale():

   1 LOCALES = { 
   2     ("ru_RU", "UTF-8"): gettext.translation("tra", "po", ["ru"]),
   3     ("en_US", "UTF-8"): gettext.NullTranslations(),
   4 }
   5 
   6 def _(text):
   7     return LOCALES[locale.getlocale()].gettext(text)
   8 
   9 
  10 for loc in LOCALES:
  11     locale.setlocale(locale.LC_ALL, loc)
  12     print(_("The message"))

При наличии скомпилированного перевода в po/ru/LC_MESSAGES/tra.mo получим

Сообщенька
The message

Множественные формы

Описание того, какие числа какой множественной форме соответствуют содержится в заголовке ru.po. Это описание компилируется в некоторый код .mo-файла, который gettext интерпретирует (замечание vslutov@, спасибо).

См. пример модификации этого описания в репозитории к лекции по Linux-разработке.

Некоторые замечания

Обновление перевода

Про память переводов

Очень важно соблюдать единообразие при переводе одинаковых надписей. Для этого все переводы по проекту можно сложить в т. н. «compendium» (он же translation memory, она же память переводов) — большой .po-файл со всеми возможными переводами, накопившимися за историю работы

В Babel нет поддержки compendium :(, но она есть в msgmerge из GNU Gettext

Д/З

В семестровом проекте должен присутствовать перевод. Если вы пишете какой-то модуль/бот/библиотеку без UI, переводите документацию (см. Руководство по переводу документации Sphinx)

LecturesCMC/PythonDevelopment2023/09_I18n (последним исправлял пользователь FrBrGeorge 2023-05-30 18:57:44)