Міграція¶
Якщо ваш проєкт уже користується 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, тож застосунок рендерить початкове повідомлення, доки людина не підтвердить пару, — та сама деградація, яку проходить будь-яке переформульоване повідомлення. - CI лишається зеленим, поки вони fuzzy. Чекер заповнювачів пропускає
записи fuzzy — так само, як це робить
msgfmt --check-format, — бо запис, який не здатен дістатися середовища виконання, не має валити збірку. Щойно перекладач знімає прапорець, запис перевіряється, як усі інші, — тож%(name)s, залишений у підтвердженому перекладі, буде впійманий саме тоді, коли він інакше почав би рендеритися. - Старий прапорець
python-formatїде разом і має бути видалений разом із прапорцемfuzzy, інакшеmsgfmt --check-formatі далі застосовуватиме правила printf до повідомлення у brace-форматі.
Для іменованих заповнювачів printf правка механічна — %(name)s стає {name},
і більше нічого не рухається, — тож великий каталог означає прохід скриптом і
подальший перегляд перекладачем, а не повторний переклад. Позиційний %s
механічним не є: у нього немає імені, яке можна перенести, а обрати його — і є
сенсом цієї зміни.
Тому міграція може йти в тому темпі, який дозволяє рев'ю: неконвертований запис fuzzy — це видима одиниця роботи в каталозі, а не зламана збірка.
Старі й нові виклики співіснують¶
Видобувач, що читає 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(). Ваш власний
помічник треба назвати в зіставленні.
Під час виконання обидва стилі однаково незалежні: 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 — звичайні коміти. Ніщо в цьому списку не потребує дня великого перемикання.