Миграция¶
Если ваш проект уже использует gettext, то вопросов, которые решают, применима ли эта библиотека, немного: обесценивает ли она имеющиеся каталоги, уживается ли с кодом, который вы менять пока не готовы, и какая часть переезда должна случиться разом. Ответы, от самого короткого:
| Вопрос | Ответ |
|---|---|
Продолжат ли работать существующие .po и .mo? |
Да. Те же файлы, те же инструменты. |
| Могут ли старые и новые вызовы жить в одном файле? | Да, и одно сопоставление экстрактора покрывает оба. |
| Меняется ли msgid? | С .format() — нет. С %-формата — да. |
| Обязан ли весь проект переехать разом? | Нет. Одна точка вызова — уже полноценное изменение. |
| А что с Jinja, шаблонами Django, JavaScript? | Не затронуты, каталоги те же. |
Остальная часть страницы — подробности за каждым из этих ответов.
С .format(): msgid не меняется¶
Это случай, в котором миграция не стоит почти ничего. Сообщение на str.format
и сообщение на t-строке дают один и тот же ключ каталога, потому что ключ в
обоих случаях — это текст, в котором {name} остался на месте:
# Before
_("Hello {name}").format(name=name)
# After — the msgid is still "Hello {name}"
tr(t"Hello {name}")
Поэтому существующий перевод остаётся привязанным. Пусть в каталоге лежит
Измените вызов, переизвлеките сообщения и обновите каталоги:
$ pybabel extract -F babel.cfg -o locales/messages.pot .
extracting messages from app.py (encoding="utf-8")
writing PO template file to locales/messages.pot
$ pybabel update -i locales/messages.pot -d locales
updating catalog locales/ja/LC_MESSAGES/messages.po based on locales/messages.pot
Вернувшаяся запись отличается двумя строками метаданных — и больше ничем: комментарием-маркером, который опознаёт её как сообщение из t-строки, и номером строки в исходнике:
Ни флага fuzzy, ни повторного перевода — ни на одном языке. Сообщение
выводится сразу же:
$ pybabel compile -d locales
compiling catalog locales/ja/LC_MESSAGES/messages.po to locales/ja/LC_MESSAGES/messages.mo
$ python app.py
こんにちは Ada
update --check сообщит, что каталоги устарели
Этого комментария-маркера и сдвинувшихся номеров строк достаточно, чтобы
pybabel update --check заявил, что каталог нужно перегенерировать: он
сравнивает запись целиком, а не только перевод. Запускайте настоящий
pybabel update в том же коммите, что и изменение кода, и коммитьте
каталоги вместе с ним — той же привычки уже требуют
ворота CI.
С %-формата: msgid меняется, поэтому переводы становятся fuzzy¶
Синтаксис printf живёт внутри сообщения, поэтому его замена переписывает ключ
каталога. Обойти это невозможно, и это честная цена отказа от %(name)s:
pybabel update распознаёт новое сообщение как близкого родственника
удалённого и переносит старый перевод, пометив его как fuzzy:
#. gettext-tstrings
#: app.py:4
#, fuzzy, python-brace-format, python-format
msgid "Hello {name}"
msgstr "こんにちは %(name)s"
Про это состояние надо знать три вещи:
- Во время выполнения ничего не ломается. Записи fuzzy исключаются из
скомпилированного
.mo, поэтому приложение выводит исходное сообщение, пока человек не подтвердит пару, — та же деградация, через которую проходит любое переформулированное сообщение. pybabel compileсообщает о каждой такой записи, потому что перенесённый%(name)sне является допустимым заполнителем в фигурных скобках, и завершается ненулевым статусом. Этот список — ваша рабочая очередь, а не ложная тревога: перечисленные записи действительно нужно править.- Старый флаг
python-formatедет следом и должен быть удалён вместе с флагомfuzzy, иначеmsgfmt --check-formatпродолжит применять правила printf к сообщению в формате с фигурными скобками.
Для именованных printf-заполнителей правка механическая — %(name)s
превращается в {name}, и больше ничего не сдвигается, — поэтому большой
каталог проходится скриптом с последующей вычиткой переводчиком, а не
переводится заново. Позиционный %s механическим не будет: у него нет имени,
которое можно перенести, а выбор этого имени и есть смысл изменения.
Поэтому на практике %-формат мигрируют осознанно — по модулю, по релизу, по
языку, — а не одним махом, от которого все каталоги разом краснеют.
Старые и новые вызовы уживаются¶
Экстрактор, читающий t-строки, читает и обычные вызовы gettext, поэтому одно сопоставление покрывает файл в середине миграции:
from gettext_tstrings import tr
from myapp.i18n import _
name = "Ada"
print(_("Save changes"))
print(tr(t"Hello {name}"))
Оба сообщения попадают в один шаблон, и только у сообщения из t-строки есть комментарий-маркер, включающий дополнительные проверки этой библиотеки:
#: app.py:5
msgid "Save changes"
msgstr ""
#. gettext-tstrings
#: app.py:6
#, python-brace-format
msgid "Hello {name}"
msgstr ""
Он распознаёт _(), четыре стандартных имени gettext, псевдонимы tr() /
ntr() и отложенные lazy_gettext() / lazy_pgettext(). Собственный helper
нужно объявить в сопоставлении.
Во время выполнения оба стиля одинаково независимы: gettext.translation()
возвращает один объект переводов, и _, и точки входа этой библиотеки читают
из него.
Что не переезжает¶
- Языки шаблонов.
{% trans %}из Jinja2, теги шаблонов Django и их экстракторы Babel продолжают работать без изменений и продолжают наполнять те же PO-каталоги. t-строки — синтаксис Python; они относятся к исходникам Python. - Ваши файлы каталогов. Ни смены формата, ни нового файла, ни шага конвертации.
- Ваша платформа перевода. Обмен через
.poидентичен, а флагpython-brace-format, который несёт сообщение из t-строки, — тот же самый флаг, который несёт сообщение из.format(), так что QA заполнителей продолжает работать. - Код не на Python. Каталог JavaScript или C в том же проекте не затрагивается.
Контрольный список миграции¶
- Добавьте extra
babelтам, где запускаетсяpybabel, и переведите сопоставлениеpythonвbabel.cfgна методgettext_tstrings— одно сопоставление тогда покрывает оба стиля, а-kпродолжает работать для обычных вызовов. - Переводите сначала точки вызова с
.format(). Переизвлеките сообщения, выполнитеpybabel updateи закоммитьте каталоги вместе с кодом; записей fuzzy быть не должно. - Переводите точки вызова с
%-форматом партиями, которые реально пройдут ревью, переписывая перенесённые заполнители и снимая флагиfuzzyиpython-format. - Почините то, что отвергает ограничение: интерполяция должна быть простым
именем, поэтому
t"Hello {user.name}"сперва становится локальной переменной. Это правка в точке вызова, а не в каталоге. - Когда проход завершён, включите
strict = trueв опциях сопоставления, чтобы неизвлекаемое сообщение роняло сборку, а не исчезало из шаблона. - Добавьте проверку времени выполнения из В продакшене:
рендеринг одного сообщения на каждый поставляемый язык через строгий
Translator.
Шаги 2 и 3 — обычные коммиты. Ничто в этом списке не требует единого дня переключения.