Различия между версиями 5 и 6
Версия 5 от 2023-04-04 20:09:06
Размер: 14005
Редактор: FrBrGeorge
Комментарий:
Версия 6 от 2023-05-30 18:57:44
Размер: 15141
Редактор: FrBrGeorge
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 2: Строка 2:
(''лекция этого года практически совпадает с прошлогодней'')
Строка 81: Строка 80:
'''TODO''' Перевести GradeProject2021 с install() на gettext.translation()
Строка 83: Строка 81:
 
Строка 87: Строка 85:
{{{#!python {{{#!highlight py3
Строка 94: Строка 92:

=== Переключение локали ===
Если программа должна выводить сообщения попеременно на разных языках, одним объектом типа `gettext.Translation` обойтись нельзя — в нём только один вариант перевода. На каждый используемый язык нужно регистрировать отдельный перевод: или из `.mo`-файла, или затыкать экземпляром [[py3doc:gettext.html#gettext.NullTranslations|NullTranslations]]. Локаль можно брать из какой-нибудь глобальной переменной, но лучше задавать с помощью [[py3doc:locale.html#locale.setlocale|locale.setlocale()]]:
{{{#!highlight py3
LOCALES = {
    ("ru_RU", "UTF-8"): gettext.translation("tra", "po", ["ru"]),
    ("en_US", "UTF-8"): gettext.NullTranslations(),
}

def _(text):
    return LOCALES[locale.getlocale()].gettext(text)


for loc in LOCALES:
    locale.setlocale(locale.LC_ALL, loc)
    print(_("The message"))
}}}
При наличии скомпилированного перевода в `po/ru/LC_MESSAGES/tra.mo` получим
{{{
Сообщенька
The message
}}}

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

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

  • Интернационализация (i18n) — приведение исходного кода в готовое для локализации состояние
  • Локализация (l10n) — адаптация культурных и языковых предпочтений ПО

Объекты ПО

  • Сообщения и тексты в составе ПО
  • Размер и оформление интерфейсных элементов (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:

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

  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
  • Если параметр fallback установлен в False (по умолчанию), gettext.translation() бросит прерывание, если не найдёт перевода (например, на английский). С fallback=True в этом случае просто вернётся NullTranslations-объект, который тупо не станет ничего переводить, а оставит как есть.

  • ngettext нужен для поддержки plural, см. далее

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

Если программа должна выводить сообщения попеременно на разных языках, одним объектом типа 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

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

  • Функция 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-файл перевод можно очень разными редакторами

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

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

  • Т. н. «неточные переводы» помечаются значащим комментарием fuzzy.

    • При компиляции эти переводы не попадают в .mo-файл, но и не исчезают

    • Неиспользованные переводы (которые были в старой версии, но отсутствуют в шаблоне), не удаляются, а комментируются
  • Дальнейшая работа с переводом — повторение цикла extractupdate → правка ru.pocompile

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

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

  • Если в исходном проекте одинаковые надписи значат разное (например, «Copy» в одном месте — «Копировать», а в другом «Копия») — это плохой дизайн ,надписи надо менять

  • Надписи типа «Save as…» или «Press button to continue» не придётся переводить по многу раз
  • Появится больше автоматически подобранных fuzzy-переводов
  • Фактически это будет ещё и словарь терминологии, который можно править при необходимости

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

Д/З

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

  • ВАЖНО: скомпилированный .mo файл (при использовании gettext в качестве инструмента перевода) в git-архив не кладётся. Это генерат

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