FunkcjeWtyczkaCennikZasoby
Zmień język
ZasobyJak zautomatyzować tłumaczenie .po w potoku CI/CD

Jak zautomatyzować tłumaczenie .po w potoku CI/CD

SimplePoTranslate Team9 czerwca 2026
Jak zautomatyzować tłumaczenie .po w potoku CI/CD

Twój zespół łączy zmiany do main czternaście razy dziennie. Każde z tych połączeń może dodać nowy ciąg znaków widoczny dla użytkownika – etykietę przycisku, komunikat o błędzie, podpowiedź. I każdy z tych ciągów znaków zaczyna swoje życie wyłącznie w języku angielskim. Gdzieś w procesie wydawniczym człowiek musi zauważyć nowe ciągi, wyeksportować je, zlecić ich przetłumaczenie, wkleić z powrotem i ponownie skompilować. Ten człowiek jest wąskim gardłem, a w szybko działającym zespole to wąskie gardło oznacza, że Twoi niemieccy użytkownicy widzą angielskie placeholdery przez dwa sprinty.

Rozwiązaniem jest zautomatyzowanie tłumaczenia w potoku CI/CD, tak aby nowe ciągi znaków były tłumaczone w ramach tego samego połączenia, które je wprowadziło – bez udziału człowieka na krytycznej ścieżce dla rutynowych przypadków. Jest to dziś w pełni osiągalne, ale tylko jeśli uniknie się pułapki, w którą wpada większość zespołów: ręcznego tworzenia surowych wywołań LLM, które po cichu uszkadzają Twoje placeholdery %s i strukturę .po. Ten przewodnik przedstawia realistyczny wzorzec potoku CI, działający szkielet GitHub Actions oraz decyzje projektowe – idempotencję, tłumaczenie tylko nowych elementów, bramki przeglądowe – które odróżniają potok pomocny od tego, który generuje zepsute kompilacje.

Dlaczego ręczne tłumaczenie jest wąskim gardłem w procesie wydawniczym

Odpowiedź na pytanie „dlaczego nie tłumaczyć po prostu przed każdą publikacją” jest taka, że terminy nigdy się nie pokrywają. Ciągi znaków są dodawane na bieżąco przez programistów, ale tłumaczenie odbywa się w partiach przez inną osobę, według innego harmonogramu. Luka między tymi dwoma rytmami to miejsce, gdzie narasta Twój dług lokalizacyjny.

Problem dwóch rytmów

Wyobraź sobie ręczny przepływ pracy. Programista dodaje __( 'Export to CSV', 'mytextdomain' ) i łączy zmiany. Nikt nie regeneruje pliku .pot. Dwa tygodnie później ktoś uruchamia wp i18n make-pot, zauważa czterdzieści nowych, nieprzetłumaczonych ciągów znaków (niektóre od programistów, którzy wyjechali na wakacje), zgaduje intencje połowy z nich i wysyła plik .po do tłumacza. Tłumaczenie wraca, zostaje wklejone, i może placeholdery przetrwały, a może nie. W międzyczasie trzy wydania zostały wysłane z tymi ciągami znaków w języku angielskim.

Każdy z tych kroków jest ręczny, wykonywany partiami i podatny na błędy. Celem automatyzacji CI/CD jest sprowadzenie tego do czegoś, co uruchamia się automatycznie po połączeniu, tłumaczy tylko to, co faktycznie się zmieniło, i głośno sygnalizuje błąd, gdy coś wygląda nie tak – zamieniając tłumaczenie z okresowego obowiązku w niewidzialną część potoku.

Wzorzec potoku: Wyodrębnij, Porównaj, Tłumacz, Zatwierdź

Ogólnie rzecz biorąc, zautomatyzowane zadanie tłumaczenia składa się z czterech kroków wykonywanych po połączeniu zmian do main: regeneracja szablonu, wykrywanie nowości, tłumaczenie tylko tych ciągów znaków oraz zatwierdzanie wyników. Cały proces powinien być idempotentny – uruchomienie go w przypadku połączenia bez zmian w ciągach znaków musi skutkować zerowym diffem i zerowym szumem.

Wyodrębnij i Porównaj

Krok pierwszy to ekstrakcja. Zregeneruj plik .pot z bieżącego źródła, tak aby odzwierciedlał każdy ciąg znaków w bazie kodu:

wp i18n make-pot . languages/myplugin.pot

Krok drugi to porównanie (diff). To najważniejsza decyzja projektowa w całym potoku. Nie chcesz ponownie tłumaczyć każdego ciągu znaków przy każdym połączeniu – to marnuje wywołania API, grozi ponownym tłumaczeniem ciągów, które człowiek już poprawił, i generuje ogromne różnice w przeglądach. Zamiast tego, połącz świeży plik .pot z każdym istniejącym plikiem .po i tłumacz tylko te wpisy, które są teraz puste (autentycznie nowe ciągi znaków):

# 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

Po msgmerge tylko zupełnie nowe ciągi znaków mają pusty msgstr. Wszystko, co było wcześniej przetłumaczone, pozostaje nienaruszone. Ta właściwość sprawia, że potok jest idempotentny i utrzymuje małe oraz możliwe do przeglądu różnice.

Tłumacz i Zatwierdź

Krok trzeci przesyła tylko te puste wpisy do etapu tłumaczenia. Krok czwarty zatwierdza zaktualizowany plik .po, kompiluje .mo i regeneruje wszelkie pliki JSON. Następnie wszystko to włączymy do GitHub Actions.

Szkielet GitHub Actions

Oto odpowiedź na pytanie „jak to faktycznie wygląda w pliku workflow”: zadanie uruchamiane przy pushu do main, które wykonuje cykl wyodrębnij-porównaj-tłumacz-zatwierdź i otwiera pull request z wynikami, zamiast zatwierdzać bezpośrednio do main.

Plik workflow

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"

Gdzie kryje się prawdziwa praca

Krok translate-new-strings.sh to miejsce, gdzie odbywa się faktyczne tłumaczenie. Naiwna wersja tego skryptu odczytuje każdy pusty wpis, przesyła go do API LLM po jednym ciągu znaków naraz i wkleja odpowiedź z powrotem. Ta naiwna wersja to właśnie pułapka i warto jasno wyjaśnić dlaczego.

Dlaczego ręcznie tworzone wywołania LLM psują Twoje pliki

Krótka odpowiedź: surowe wywołania LLM traktują Twoje ciągi .po jako prozę, a Twoje ciągi .po nie są prozą – to ustrukturyzowane dane z placeholderami, formami liczby mnogiej i kontekstem, które wywołanie chat-completion z radością niszczy.

Uszkodzenie placeholderów, którego nie zobaczysz w testach

Wyślij Deleted %d of %s files do ogólnego punktu końcowego czatu, a możesz otrzymać z powrotem Supprimé %d des %s fichiers (dobrze) lub Supprimé %d de % s fichiers (spacja wkradła się do placeholdera, a teraz sprintf() zgłasza błąd w czasie wykonania). Wyślij wpis w liczbie mnogiej, a model może połączyć dwie formy w jedną, łamiąc języki z więcej niż dwoma kategoriami liczby mnogiej. Wyślij ciąg znaków z <a href="%s">, a model może przetłumaczyć adres URL lub usunąć tag. Żaden z tych błędów nie pojawi się w Twoich testach, chyba że specjalnie testujesz renderowany wynik w każdej lokalizacji – pojawiają się one jako błędy wykonawcze w produkcji dla użytkowników, od których nie możesz odczytać raportów o błędach.

Pułapka konserwacji

Możesz próbować bronić się przed tym inżynierią promptów i przetwarzaniem końcowym za pomocą wyrażeń regularnych, i wiele zespołów tak robi. Problem polega na tym, że utrzymujesz teraz kruchy silnik tłumaczeniowy jako projekt poboczny, na nowo odkrywając każdy przypadek graniczny placeholderów, który ekosystem Gettext już rozwiązał. Konkretne sposoby zniekształcania zmiennych – i jak im zapobiegać – katalogujemy w artykule tłumaczenie plików PO bez uszkadzania zmiennych kodu. Lekcja jest prosta: jakość modelu rzadko jest problemem; problemem jest jego strukturalna obsługa.

Wprowadzanie napędzanego API tłumacza chmurowego do CI

To tutaj usługa tłumaczeniowa oparta na API zastępuje Twoje zgadywanki w translate-new-strings.sh. Zamiast ręcznie tworzyć wywołania LLM i przetwarzać post-regex, Twój krok CI przesyła zmieniony plik .po do usługi, która już rozumie strukturę Gettext i zwraca czysty wynik. Kształt potoku pozostaje identyczny – wyodrębnij, porównaj, tłumacz, zatwierdź – ale kruchy środkowy krok staje się pojedynczym wywołaniem API.

Wywołanie API, które zastępuje Twój skrypt

SimplePoTranslate został stworzony dokładnie do tego celu. Udostępnia chmurowe API przystosowane do automatyzacji i CI, dzięki czemu krok tłumaczenia w Twoim workflow staje się zapytaniem, które przekazuje plik .po i otrzymuje z powrotem przetłumaczony, bez pętli dla każdego ciągu znaków. Jego Syntax Locking automatycznie utrzymuje w miejscu %s, %1$s, {count}, HTML i tokeny kodu – cała klasa błędów z poprzedniej sekcji jest obsługiwana przez silnik, a nie przez wyrażenia regularne, które musisz utrzymywać. Dzięki pełnemu wsparciu dla Gettext plural i msgctxt, formy liczby mnogiej i kontekst przetrwają podróż w obie strony, czego nie może zagwarantować wywołanie chat-completion.

Idempotencja i bramka przeglądowa pozostają Twoje

Dwie decyzje projektowe nadal należą do Ciebie, niezależnie od tego, jakiego silnika używasz. Po pierwsze, idempotencja: kontynuuj tłumaczenie tylko pustych wpisów po msgmerge, aby scalenia bez operacji nie generowały różnic. Po drugie, bramka przeglądowa: zleć zadaniu otwarcie pull requestu, tak jak to robi powyższy szkielet, zamiast zatwierdzać bezpośrednio do main. Tłumaczenie maszynowe jest doskonałe do szybkiego wprowadzania ciągów znaków, ale ludzki rzut oka przed scaleniem wychwytuje rzadkie błędy kontekstowe – a PR daje Ci ten rzut oka bez blokowania typowego przypadku. Zespoły zarządzające wieloma lokalizacjami lub wieloma witrynami klientów rozpoznają ten schemat z idealnego przepływu pracy lokalizacyjnego dla agencji, gdzie ten sam wzorzec automatyzuj-a-następnie-przeglądaj skaluje się na dziesiątki projektów.

Podsumowanie

Aby zautomatyzować tłumaczenie w CI/CD bez dostarczania uszkodzonych kompilacji, wzorzec jest spójny: regeneruj plik .pot przy scaleniu, msgmerge do każdej lokalizacji, aby ujawnić tylko nowe ciągi znaków, przetłumacz tylko te ciągi i zatwierdź wynik za bramką przeglądową pull requestu. Idempotencja utrzymuje Twoje różnice w czystości; bramka przeglądowa zapobiega przedostawaniu się rzadkich, złych tłumaczeń do produkcji.

Częścią, którą należy prawidłowo wykonać, jest sam krok tłumaczenia. Ręcznie tworzone wywołania LLM uszkodzą placeholdery i formy liczby mnogiej w sposób, którego Twoje testy nie wykryją, a Ty poświęcisz więcej czasu na utrzymanie „kleju” tłumaczeniowego niż na kod funkcji, którą obsługuje. Silnik napędzany API z Syntax Locking eliminuje ten cały tryb awaryjny, dzięki czemu Twój potok tłumaczy nowe ciągi znaków w tym samym scaleniu, które je wprowadziło – a Twoi użytkownicy nieanglojęzyczni przestają widzieć angielskie placeholdery.

Gotowy do zautomatyzowania tłumaczenia w swoim potoku bez uszkadzania placeholderów? Wypróbuj SimplePoTranslate za darmo – karta kredytowa nie jest wymagana. Zacznij od darmowego planu, zweryfikuj API na własnych plikach .po i podłącz go do CI, gdy będziesz gotowy.