ВозможностиПлагинЦеныРесурсы
Изменить язык
РесурсыКак автоматизировать перевод .po в вашем CI/CD-пайплайне

Как автоматизировать перевод .po в вашем CI/CD-пайплайне

SimplePoTranslate Team9 июня 2026 г.
Как автоматизировать перевод .po в вашем CI/CD-пайплайне

Ваша команда сливает изменения в main четырнадцать раз в день. Каждое из этих слияний может добавлять новую строку для пользователя — метку кнопки, сообщение об ошибке, подсказку. И каждая из этих строк изначально существует только на английском языке. Где-то в процессе выпуска продукта человек должен заметить новые строки, экспортировать их, перевести, вставить обратно и перекомпилировать. Этот человек является узким местом, и в быстро развивающейся команде такое узкое место означает, что ваши немецкие пользователи видят английские плейсхолдеры в течение двух спринтов.

Решение состоит в том, чтобы автоматизировать перевод в вашем CI/CD-пайплайне, чтобы новые строки переводились сразу же после слияния, которое их внесло, — без участия человека на критическом пути для рутинных случаев. Сегодня это вполне достижимо, но только если вы избежите ловушки, в которую попадает большинство команд: самостоятельно создаваемых вызовов к LLM, которые незаметно портят ваши плейсхолдеры %s и структуру .po. Это руководство описывает реалистичную схему CI-пайплайна, рабочий шаблон GitHub Actions и проектные решения — идемпотентность, перевод только новых строк, шлюзы для проверки, — которые отличают полезный пайплайн от того, который выпускает неработающие сборки.

Почему ручной перевод является узким местом при выпуске

Ответ на вопрос «почему бы просто не переводить перед каждым релизом» заключается в том, что сроки никогда не совпадают. Строки постоянно добавляются разработчиками, но перевод выполняется партиями другим человеком по другому графику. Промежуток между этими двумя ритмами — это то место, где накапливается ваш долг по локализации.

Проблема двух ритмов

Представьте ручной процесс. Разработчик добавляет __( 'Export to CSV', 'mytextdomain' ) и делает слияние. Никто не перегенерирует .pot. Две недели спустя кто-то запускает wp i18n make-pot, замечает сорок новых непереведенных строк (некоторые от разработчиков, которые уже ушли в отпуск), угадывает смысл половины из них и отправляет .po переводчику. Перевод возвращается, вставляется, и, возможно, плейсхолдеры сохранились, а возможно, и нет. Тем временем три релиза вышли с этими строками на английском языке.

Каждый шаг здесь выполняется вручную, пакетно и подвержен ошибкам. Цель автоматизации CI/CD — превратить это в нечто, что запускается автоматически при слиянии, переводит только то, что фактически изменилось, и громко выдает ошибку, когда что-то выглядит не так — превращая перевод из периодической рутины в невидимую часть пайплайна.

Схема пайплайна: Извлечение, Разница, Перевод, Коммит

На высоком уровне автоматическая задача перевода выполняется в четыре этапа при слиянии в main: перегенерация шаблона, определение новых строк, перевод только этих строк и коммит результатов обратно. Весь процесс должен быть идемпотентным — запуск его при слиянии без изменений строк должен приводить к нулевой разнице и нулевому шуму.

Извлечение и сравнение

Первый шаг — извлечение. Перегенерируйте .pot из текущего исходного кода, чтобы он отражал каждую строку в кодовой базе:

wp i18n make-pot . languages/myplugin.pot

Второй шаг — сравнение. Это самое важное проектное решение во всём пайплайне. Вы не хотите переводить каждую строку при каждом слиянии — это тратит API-вызовы, рискует повторно перевести строки, которые человек уже исправил, и создаёт огромные различия для ревью. Вместо этого вы сливаете свежий .pot в каждый существующий .po и переводите только те записи, которые теперь пусты (действительно новые строки):

# Merge new template into each locale, preserving existing translations.
# Newly-added strings appear with empty msgstr; --backup=none keeps the tree clean.
for po in languages/*.po; do
    msgmerge --update --backup=none "$po" languages/myplugin.pot
done

После msgmerge только совершенно новые строки имеют пустой msgstr. Всё, что было переведено ранее, остаётся нетронутым. Именно это свойство делает пайплайн идемпотентным и позволяет сохранять различия для ревью небольшими и легко проверяемыми.

Перевод и коммит

Третий шаг отправляет только эти пустые записи на этап перевода. Четвёртый шаг фиксирует обновленный .po, компилирует .mo и перегенерирует любой JSON. Далее мы подключим всё это к GitHub Actions.

Шаблон GitHub Actions

Вот ответ на вопрос «как это на самом деле выглядит в файле рабочего процесса»: задача, запускаемая при пуше в main, которая выполняет цикл извлечения-сравнения-перевода-коммита и открывает пул-реквест с результатами, вместо того чтобы коммитить напрямую в main.

Файл рабочего процесса

name: Translate on merge

on:
  push:
    branches: [ main ]

jobs:
  translate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install WP-CLI and gettext
        run: |
          sudo apt-get update && sudo apt-get install -y gettext
          curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
          sudo mv wp-cli.phar /usr/local/bin/wp && sudo chmod +x /usr/local/bin/wp

      - name: Regenerate template and merge into locales
        run: |
          wp i18n make-pot . languages/myplugin.pot
          for po in languages/*.po; do
            msgmerge --update --backup=none "$po" languages/myplugin.pot
          done

      - name: Translate only new (empty) strings
        env:
          TRANSLATE_API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
        run: ./scripts/translate-new-strings.sh languages/

      - name: Compile .mo and JSON
        run: |
          wp i18n make-mo languages/
          wp i18n make-json languages/ --no-purge

      - name: Open pull request with translations
        uses: peter-evans/create-pull-request@v6
        with:
          branch: chore/auto-translations
          title: "chore: auto-translate new strings"
          commit-message: "chore: auto-translate new strings"

Где скрывается настоящая работа

Шаг translate-new-strings.sh — это место, где происходит фактический перевод. Наивная версия этого скрипта считывает каждую пустую запись, отправляет её в LLM API по одной строке за раз и вставляет ответ обратно. Эта наивная версия — именно та ловушка, и стоит явно объяснить, почему.

Почему самописные вызовы LLM ломают ваши файлы

Короткий ответ: необработанные вызовы LLM рассматривают ваши строки .po как прозу, а ваши строки .po — это не проза; это структурированные данные с плейсхолдерами, формами множественного числа и контекстом, которые вызов завершения чата с радостью уничтожает.

Повреждение плейсхолдеров, которое вы не увидите в тестах

Отправьте Deleted %d of %s files в общую конечную точку чата, и вы можете получить Supprimé %d des %s fichiers (хорошо) или Supprimé %d de % s fichiers (пробел прокрался в плейсхолдер, и теперь sprintf() выдаёт ошибку во время выполнения). Отправьте запись множественного числа, и модель может слить две формы в одну, нарушая правила для языков с более чем двумя категориями множественного числа. Отправьте строку с <a href="%s">, и модель может перевести URL или удалить тег. Ни одна из этих ошибок не проявится в ваших тестах, если вы специально не тестируете сгенерированный вывод в каждой локали — они проявляются как ошибки выполнения в продакшене для пользователей, от которых вы не можете получить отчёты об ошибках.

Ловушка обслуживания

Вы можете попробовать защититься от этого с помощью промпт-инжиниринга и постобработки регулярными выражениями, и многие команды так и делают. Проблема в том, что теперь вы поддерживаете хрупкий механизм перевода в качестве побочного проекта, заново открывая для себя каждый краевой случай плейсхолдера, который экосистема Gettext уже решила. Мы каталогизируем конкретные способы искажения переменных — и как их предотвратить — в статье перевод PO-файлов без нарушения переменных кода. Урок здесь применим напрямую: качество модели редко является проблемой; проблема в структурной обработке вокруг неё.

Встраивание облачного переводчика, управляемого API, в CI

Именно здесь API-ориентированный сервис перевода заменяет ваш translate-new-strings.sh с его догадками. Вместо того чтобы вручную создавать вызовы LLM и постобработку регулярными выражениями, ваш CI-шаг загружает измененный .po в сервис, который уже понимает структуру Gettext и возвращает чистый вывод. Форма пайплайна остается идентичной — извлечение, сравнение, перевод, коммит — но хрупкий промежуточный шаг становится единым API-вызовом.

API-вызов, который заменяет ваш скрипт

SimplePoTranslate создан именно для этого. Он предоставляет облачный API, подходящий для автоматизации и CI, так что шаг перевода в вашем рабочем процессе становится запросом, который передаёт .po и получает обратно переведённый, без перебора строк. Его функция Syntax Locking автоматически удерживает на своих местах %s, %1$s, {count}, HTML и кодовые токены — весь класс ошибок из предыдущего раздела обрабатывается движком, а не регулярными выражениями, которые вы поддерживаете. Благодаря полной поддержке множественного числа Gettext и msgctxt, формы множественного числа и контекст сохраняются при циклическом обмене, что не может гарантировать вызов завершения чата.

Идемпотентность и шлюз проверки остаются вашими

Два проектных решения остаются за вами, независимо от того, какой движок вы используете. Во-первых, идемпотентность: продолжайте переводить только пустые записи после msgmerge, чтобы операции слияния без изменений не приводили к различиям. Во-вторых, шлюз для проверки: пусть задача открывает пул-реквест, как это делает описанный выше шаблон, вместо того чтобы коммитить напрямую в main. Машинный перевод отлично подходит для быстрого вывода строк в продакшн, но человеческий взгляд перед слиянием улавливает редкие ошибки контекста — а PR даёт вам этот взгляд, не блокируя обычный случай. Команды, работающие со множеством локалей или клиентских сайтов, узнают эту схему из статьи идеальный рабочий процесс локализации для агентств, где тот же шаблон «автоматизировать, затем проверить» масштабируется на десятки проектов.

Заключение

Чтобы автоматизировать перевод в CI/CD без выпуска неработающих сборок, шаблон остается неизменным: перегенерируйте .pot при слиянии, выполните msgmerge его в каждую локаль, чтобы выявить только новые строки, переведите только эти строки и зафиксируйте результат за шлюзом проверки через пул-реквест. Идемпотентность поддерживает чистоту ваших различий; шлюз проверки предотвращает попадание редких некачественных переводов в продакшн.

Важно правильно реализовать сам шаг перевода. Самостоятельно написанные вызовы LLM будут искажать плейсхолдеры и формы множественного числа так, как ваши тесты не смогут обнаружить, и вы потратите больше времени на поддержку «клея» для перевода, чем на код функций, которые он обслуживает. API-ориентированный движок с Syntax Locking полностью устраняет этот режим отказа, так что ваш пайплайн переводит новые строки при том же слиянии, которое их внесло — и ваши неанглоязычные пользователи перестают видеть английские плейсхолдеры.

Готовы автоматизировать перевод в своём пайплайне, не ломая плейсхолдеры? Попробуйте SimplePoTranslate бесплатно — кредитная карта не требуется. Начните с бесплатного тарифа, проверьте API на своих .po файлах и интегрируйте его в CI, когда будете готовы.

Поделиться этой статьёй