Hogyan automatizáljuk a .po fordítást a CI/CD pipeline-odban

A csapatod naponta tizennégyszer olvaszt össze a main ágba. Minden ilyen összevonás új, felhasználók számára látható szöveget adhat hozzá – egy gombfeliratot, hibaüzenetet, vagy eszköztárat. És minden ilyen szöveg kezdetben csak angolul létezik. Valahol a kiadási folyamatban egy embernek észre kell vennie az új szövegeket, exportálnia kell őket, lefordíttatnia, visszamásolnia és újrafordítania. Ez az ember szűk keresztmetszet, és egy gyorsan szállító csapatnál ez azt jelenti, hogy a német felhasználóid két sprinten keresztül angol helykitöltőket látnak.
A megoldás az, hogy automatizáljuk a fordítást a CI/CD pipeline-odban, így az új szövegek ugyanazzal az összevonással lefordítódnak, ami bevezette őket – emberi beavatkozás nélkül a kritikus útvonalon a rutin esetekben. Ez ma már teljesen megvalósítható, de csak akkor, ha elkerülöd azt a csapdát, amibe a legtöbb csapat beleesik: a kézi, nyers LLM hívásokat, amelyek észrevétlenül megrongálják a %s helykitöltőket és a .po struktúrájának integritását. Ez az útmutató bemutat egy realisztikus CI pipeline mintát, egy működő GitHub Actions vázat, és azokat a tervezési döntéseket – idempotencia, csak új fordítások, felülvizsgálati kapuk – amelyek elválasztják a hasznos pipeline-t a hibás buildeket szállító pipeline-tól.
Miért jelentenek szűk keresztmetszetet a manuális fordítások a kiadási folyamatban
A válasz arra, hogy „miért ne fordítanánk le minden kiadás előtt”, az, hogy az időzítés sosem egyezik. A szövegeket folyamatosan adják hozzá a fejlesztők, de a fordítás kötegelve, egy másik személy által, eltérő ütemezés szerint történik. A két ütemezés közötti rés az, ahol a lokalizációs adósságod felhalmozódik.
A kétütemű probléma
Képzeld el a manuális folyamatot. Egy fejlesztő hozzáadja a __( 'Export to CSV', 'mytextdomain' ) szöveget és összevonja. Senki sem generálja újra a .pot fájlt. Két héttel később valaki lefuttatja a wp i18n make-pot parancsot, negyven új, lefordítatlan szöveget észlel (néhányat olyan fejlesztőktől, akik azóta szabadságra mentek), találgatja a felük szándékát, és elküld egy .po fájlt egy fordítónak. A fordítás visszajön, beillesztésre kerül, és talán a helykitöltők épen maradtak, talán nem. Eközben három kiadás jelent meg azokkal a szövegekkel angolul.
Minden lépés manuális, kötegelt és hibára hajlamos. A CI/CD automatizálás célja, hogy ezt egy olyan folyamattá alakítsa, amely összevonáskor automatikusan fut, csak azt fordítja le, ami ténylegesen megváltozott, és hangosan hibát jelez, ha valami rosszul néz ki – így a fordítás egy időszakos feladatból a pipeline láthatatlan részévé válik.
A Pipeline minta: Kinyerés, Különbség, Fordítás, Véglegesítés
Magas szinten egy automatizált fordítási feladat négy lépést futtat le a main ágba történő összevonáskor: újragenerálja a sablont, észleli, mi az új, csak ezeket a szövegeket fordítja le, és véglegesíti az eredményeket. Az egésznek idempotensnek kell lennie – ha olyan összevonáson futtatjuk, ahol nincs szöveges változás, akkor nulla különbséget és nulla zajt kell produkálnia.
Kinyerés és különbség
Az első lépés a kinyerés. Generáld újra a .pot fájlt az aktuális forrásból, hogy az tükrözze a kódbázis minden szövegét:
wp i18n make-pot . languages/myplugin.pot
A második lépés a különbség. Ez a legfontosabb tervezési döntés az egész pipeline-ban. Nem akarod újra lefordítani minden szöveget minden összevonáskor – ez API hívásokat pazarol, kockáztatja, hogy újra lefordítsanak olyan szövegeket, amelyeket egy ember már kijavított, és hatalmas felülvizsgálati különbségeket eredményez. Ehelyett összevonod az új .pot fájlt minden meglévő .po fájlba, és csak azokat a bejegyzéseket fordítod le, amelyek most üresek (azaz az új szövegeket):
# 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
Az msgmerge után csak a vadonatúj szövegek msgstr értéke üres. Minden korábban lefordított szöveg érintetlen marad. Ez a tulajdonság teszi a pipeline-t idempotenssé, és ez tartja a felülvizsgálati különbségeidet kicsinek és áttekinthetőnek.
Fordítás és véglegesítés
A harmadik lépésben ezeket az üres bejegyzéseket egy fordítási lépésbe küldjük. A negyedik lépésben véglegesítjük a frissített .po fájlt, lefordítjuk a .mo fájlt, és újragenerálunk minden JSON fájlt. Mindezeket a GitHub Actionsbe fogjuk bekötni.
Egy GitHub Actions váz
Íme a válasz arra, hogy „hogyan néz ki ez valójában egy workflow fájlban”: egy feladat, amely a main ágba történő push esetén aktiválódik, lefuttatja a kinyerés-különbség-fordítás-véglegesítés ciklust, és pull requestet nyit az eredményekkel ahelyett, hogy közvetlenül a main ágba véglegesítené.
A Workflow fájl
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"
Ahol az igazi munka rejtőzik
A translate-new-strings.sh lépés az, ahol a tényleges fordítás történik. Ennek a szkriptnek a naiv változata minden üres bejegyzést beolvas, egyenként elküldi egy LLM API-nak, és visszamásolja a választ. Ez a naiv változat pontosan a csapda, és érdemes kifejteni, miért.
Miért teszik tönkre a fájljaidat a kézzel írt LLM hívások
A rövid válasz: a nyers LLM hívások prózaként kezelik a .po szövegeidet, de a .po szövegek nem prózák – strukturált adatok helykitöltőkkel, többes számú alakokkal és kontextussal, amelyet egy chat-kiegészítő hívás boldogan tönkretesz.
A helykitöltő korrupció, amit nem fogsz látni a tesztekben
Küldd el a Deleted %d of %s files szöveget egy általános chat végpontnak, és visszakaphatod a Supprimé %d des %s fichiers (rendben) vagy a Supprimé %d de % s fichiers szöveget (egy szóköz került a helykitöltőbe, és most a sprintf() hibát dob futásidőben). Küldj többes számú bejegyzést, és a modell két alakot egybevonhat, tönkretéve azokat a nyelveket, amelyek több mint két többes számú kategóriával rendelkeznek. Küldj egy <a href="%s"> címkével ellátott szöveget, és a modell lefordíthatja az URL-t, vagy elhagyhatja a címkét. Ezen hibák egyike sem jelenik meg a tesztjeidben, hacsak nem teszteled kifejezetten a megjelenített kimenetet minden lokáléban – futásidejű hibákként jelennek meg éles környezetben olyan felhasználók számára, akikről nem tudsz hibajelentéseket olvasni.
A karbantartási csapda
Megpróbálhatod kivédeni ezt prompt engineeringgel és regex utófeldolgozással, és sok csapat teszi is. A probléma az, hogy most egy törékeny fordítómotort tartasz fenn mellékprojektként, újra felfedezve minden helykitöltő élest, amit a Gettext ökoszisztéma már megoldott. Felsoroljuk azokat a specifikus módokat, ahogyan a változók torzulhatnak – és hogyan előzhetjük meg ezeket – a PO fájlok fordítása anélkül, hogy megtörné a kódváltozókat című cikkben. Az ottani tanulság közvetlenül alkalmazható: a modell minősége ritkán a probléma; a köré épülő strukturális kezelés az.
Egy API-vezérelt felhőalapú fordító beillesztése a CI-be
Itt lép be egy API-vezérelt fordítási szolgáltatás, amely felváltja a translate-new-strings.sh találgatásaidat. A kézzel írt LLM hívások és regex utófeldolgozás helyett a CI lépésed feltölti a megváltozott .po fájlt egy olyan szolgáltatásra, amely már ismeri a Gettext struktúrát és tiszta kimenetet ad vissza. A pipeline alakja azonos marad – kinyerés, különbség, fordítás, véglegesítés – de a törékeny középső lépés egyetlen API hívássá válik.
Az API hívás, amely felváltja a szkriptedet
A SimplePoTranslate pontosan erre készült. Egy felhőalapú API-t biztosít, amely alkalmas automatizálásra és CI-re, így a workflow-d fordítási lépése egy kéréssé válik, amely átadja a .po fájlt és visszakapja a lefordítottat, szövegenkénti ciklusok nélkül. A Syntax Locking automatikusan a helyén tartja a %s, %1$s, {count}, HTML és kódtartalmakat – az előző szakaszban említett hibák teljes osztályát a motor kezeli, nem pedig az általad karbantartott regex. A teljes Gettext többes szám és msgctxt támogatásnak köszönhetően a többes számú alakok és a kontextus túlélik az oda-vissza utat, amit egy chat-kiegészítő hívás nem garantálhat.
Az idempotencia és a felülvizsgálati kapu a te kezedben marad
Két tervezési döntés továbbra is a tiéd marad, függetlenül attól, hogy melyik motort használod. Először is, az idempotencia: csak az msgmerge utáni üres bejegyzéseket fordítsd le, így a no-op összevonások nem generálnak különbséget. Másodszor, egy felülvizsgálati kapu: a feladat nyisson pull requestet, ahogy a fenti váz is teszi, ahelyett, hogy közvetlenül a main ágba véglegesítené. A gépi fordítás kiválóan alkalmas a szövegek gyors élesítésére, de egy emberi pillantás az összevonás előtt elkapja a ritka kontextusbeli hibát – és egy PR megadja ezt a lehetőséget anélkül, hogy blokkolná a gyakori eseteket. Azok a csapatok, amelyek sok lokáléval vagy sok ügyféloldallal dolgoznak, felismerik ezt a mintát az ügynökségek ideális lokalizációs munkafolyamatából, ahol ugyanaz az automatizálás-majd-felülvizsgálat minta több tucat projektre skálázható.
Összegzés
Ahhoz, hogy automatizáljuk a fordítást a CI/CD-ben anélkül, hogy hibás buildeket szállítanánk, a minta következetes: generáld újra a .pot fájlt összevonáskor, msgmerge-ld bele minden lokáléba, hogy csak az új szövegek jelenjenek meg, fordítsd le csak ezeket a szövegeket, és véglegesítsd az eredményt egy pull request felülvizsgálati kapu mögé. Az idempotencia tisztán tartja a különbségeket; a felülvizsgálati kapu pedig megakadályozza, hogy a ritka rossz fordítás éles környezetbe kerüljön.
A helyesen elvégzendő rész maga a fordítási lépés. A kézzel írt LLM hívások megrongálják a helykitöltőket és a többes számú alakokat olyan módon, amit a tesztjeid nem fognak észrevenni, és több időt fogsz tölteni a fordítási ragasztó karbantartásával, mint az általa kiszolgált funkciókód karbantartásával. Egy API-vezérelt motor Syntax Lockinggal kiküszöböli ezt a teljes hibamódot, így a pipeline-od lefordítja az új szövegeket ugyanazon az összevonáson, amely bevezette őket – és a nem angolul beszélő felhasználóid többé nem fognak angol helykitöltőket látni.
Készen állsz a fordítás automatizálására a pipeline-odban anélkül, hogy tönkretennéd a helykitöltőket? Próbáld ki ingyen a SimplePoTranslate-et — hitelkártya nélkül. Kezdj az ingyenes szinttel, ellenőrizd az API-t a saját
.pofájljaid ellen, és kösd be a CI-be, amikor készen állsz.