Blog/Webfejlesztés
LLM-fordítási pipeline i18n-hez: glosszárium, helyőrzők és review
Termék- és weboldalszövegek fordítása LLM-mel úgy, hogy a helyőrzők, a többes szám és a hangnem épen maradjon: glosszárium, ICU üzenetek, QA-ellenőrzések.
Balázs Csorba··9 perc olvasás
- Translation
- i18n
- LLM
- Vue
- Localization

A lényeg röviden
- A helyőrzőket, tageket és az ICU szintaxist még a modell előtt védd le, utána pedig ellenőrizd; ez a vizsgálat szinte minden valódi hibát megfog.
- Értékeket fordíts, soha ne kulcsokat, és tartsd változatlanul a fájlszerkezetet, hogy a build diffelhető és átnézhető maradjon.
- A glosszárium és a ne-fordítsd lista több konzisztenciát ad, mint bármilyen prompt-szövegezés, mert géppel kikényszeríthető.
- A németnél és a magyarnál előre kell dönteni: tegezés vagy magázás, összetett szavak kezelése és az idézőjelek stílusa.
- A modell fordít, a szkriptek ellenőriznek, az ember pedig csak azt a szöveget nézi át, ahol egy hiba drága.
Minden termékweboldal előbb-utóbb kap egy fordítási folyamatot, akár tervezik, akár nem. A szokásos változat egy táblázat, egy egyszer megírt szkript és az a szabály, hogy a német szövegeknek a release-ág levágása előtt készen kell lenniük. Ez addig működik, amíg egy helyőrzőt át nem neveznek, el nem tűnik egy többes számú alak, vagy ugyanaz a gombfelirat háromféleképpen nem lesz lefordítva.
Ez a bejegyzés azt a folyamatot írja le, amelyet Vue és Nuxt oldalakon használok: a szövegek JSON-ban élnek, a modell glosszáriummal és explicit kontextussal fordítja az értékeket, a szkriptek gépi ellenőrzést végeznek, az ember pedig csak a drága hibák kockázatát hordozó szövegeket nézi át. Ez szándékosan unalmas, és pont ez a lényeg: a fordítás legyen build-lépés, ne esemény.
Mi törik el valójában
A sima prózai szöveg gépi fordítása az európai nyelveknél megoldott kérdés. Szoftverben az törik el, amit a mondat nem hordoz: a szöveg belsejében lévő {count}, a link körüli HTML-tag, a többes szám, amelyre a németnek és a magyarnak is szüksége van, a terméknév, amelynek angolul kell maradnia.
- Egy eltűnt vagy átnevezett helyőrző, amely a működő oldalt futásidejű hibává vagy üres hellyé teszi.
- Egy elrontott ICU üzenet, mert a többes szám kategóriái nyelvenként eltérnek, összefűzéssel pedig nem fejezhetők ki.
- Lefordított HTML- vagy Markdown-tagek, amelyek vagy a megjelenítést törik el, vagy a látható szövegbe kerülnek.
- Következetlen terminológia: ugyanazt a funkciót három néven említi egy oldal.
- Hangnem-eltolódás, amikor egy formális német felület hirtelen tegezni kezdi az olvasót.
A folyamat
A kinyerés az egyetlen lépés, amely forrásszövegeket állít elő. A komponensbe égetett szöveg soha nem lesz lefordítva, ezért az első hasznos ellenőrzés egy linter, amely hibát jelez a sablonokban látható szövegkonstansokra. A fordítási lépés állapotmentes: beolvassa a forrásfájlt, a glosszáriumot és ugyanazon kulcs korábbi fordítását, majd új értékeket ír.
Először a helyőrzők és tagek védelme
A helyőrző szerződés a szöveg és az azt megjelenítő kód között. A modell nem fordíthatja le, nem rendezheti át és nem hagyhatja el, a kód pedig nem feltételezheti, hogy épen maradt. A megbízható minta: a kérések előtt kinyerni minden helyőrzőt, majd a válasz után összevetni a listát; az eltérés kemény hiba, nem figyelmeztetés.
import re
PLACEHOLDER = re.compile(r"\{[A-Za-z_][\w]*(?:,[^}]*)?\}|\{[0-9]+\}|%[sd]|</?[a-z][^>]*>")
def slots(text: str) -> list[str]:
"Every placeholder and tag in the string, in order of appearance."
return PLACEHOLDER.findall(text)
def check(source: str, target: str) -> list[str]:
"Returns the differences; empty list means the translation is safe to ship."
a, b = slots(source), slots(target)
return [f"missing {s}" for s in a if s not in b] + [f"added {s}" for s in b if s not in a]
A tagek ugyanezt a kezelést érdemlik. Ha a forrás <strong>{name}</strong> szöveget tartalmaz, a fordítás áthelyezheti a taget, de mindkét felét és a helyőrzőt meg kell tartania. Egy egyszerű sorrendi összevetés a gyakori eseteket kapja el, egy HTML-parser a többit.
A többes szám és a select az ICU dolga
A német és a magyar is megkülönbözteti az egyes és többes számot, ezért a csak angolul helyes többes szám ártalmatlannak tűnik egészen az első nullás számlálóig. Az ICU MessageFormat ezt rendesen megoldja, a Vue I18n pedig beszéli az ICU-t, így az üzenet a locale-fájlban maradhat és egységként fordítható.
| Szöveg | en | de | hu |
|---|---|---|---|
| Kosár darabszám | {count, plural, one {# item} other {# items}} | {count, plural, one {# Artikel} other {# Artikel}} | {count, plural, one {# termék} other {# termék}} |
| Tárhelyméret | {size} GB of {total} GB used | {size} GB von {total} GB belegt | {size} GB a {total} GB-ból |
<p>{{ $t('cart.items', { count: items.length }, items.length) }}</p>
// en.json
"cart": { "items": "{count, plural, one {# item} other {# items}}" }
// de.json
"cart": { "items": "{count, plural, one {# Artikel} other {# Artikel}}" }
// hu.json
"cart": { "items": "{count, plural, one {# termék} other {# termék}}" }
Két szokás tartja működésben ezt. Soha ne építs mondatot úgy, hogy egy lefordított töredéket számhoz vagy névhez fűzöl, mert a szórend eltér; a szám az üzeneten belül maradjon. És a folyamat soha ne lásson félig lefordított többes számot: az egész ICU üzenetet egyetlen szövegként fordítsd, ugyanazokkal a kategórianevekkel, és egy ellenőrzés igazolja, hogy a forrás minden kategóriája létezik a célban.
Értékeket fordíts, soha ne kulcsokat
A fordítási lépés egy JSON-objektumot kap, és azonos alakú objektumot ad vissza. A kulcsok azonosítók, érintetlenek maradnak, csak az értékek változnak. Strukturált kimenettel ez API-szinten kikényszeríthető, séma nélkül is ez a legegyszerűbben ellenőrizhető szerződés: a kulcshalmaznak a művelet előtt és után azonosnak kell lennie.
{
"page": "checkout", // context for the model, not translated
"glossary": [["cart", "Warenkorb"], ["checkout", "Kassa"]],
"strings": {
"cart.title": "Your cart",
"cart.empty": "Your cart is empty",
"cart.items": "{count, plural, one {# item} other {# items}}"
}
}A csoportosítás fontosabb, mint a méret. Az azonos oldalhoz, komponenshez vagy hangnemhez tartozó szövegek egy kérésbe kerülnek, mert a modell a szomszédokat kontextusként használja. Kérésenként 20-60 szöveg jó tartomány: az újrapróbálkozás olcsó, a kontextus pedig specifikus marad. Csak azt fordítsd újra, ami változott, a forrásérték hash-összevetésével, így az első teljes kör után a napi költség a nullához közelít.
Glosszárium és fordítási memória
A glosszárium a folyamat legolcsóbb konzisztenciaeszköze: forráskifejezések listája a jóváhagyott célkifejezésekkel, valamint írásmódra és ragozásra vonatkozó szabállyal. A modell bemenetként kapja, egy ellenőrzés pedig utólag kikényszeríti. Ha tiltott fordítás jelenik meg, a szöveg egyszer ismétlődik az idézett szabálysértéssel, utána pedig review-ba kerül, nem ciklusba.
| Forrás | német | magyar | Szabály |
|---|---|---|---|
| cart | Warenkorb | kosár | főnév, németül nagybetűs |
| checkout | Kassa | fizetés | ne fordítstd, ne ragozd |
| AI agent | AI-Agent | AI-ügynök | az angol kifejezés marad, első előfordulásnál magyarázattal |
A fordítási memória a másik fél: a korábbi kiadásokból származó pontos egyezések modellhívás nélkül újrahasznosulnak, a közelítő egyezések javaslatként mennek tovább. Nyelvenkénti JSON-felállásban a memória csupán forrásszövegek és elfogadott célok táblája, a hatás ugyanaz: a modell csak az igazán újat látja, a reviewer pedig csak azt, ami a legutóbbi kiadás óta változott.
Német és magyar sajátosságok
A német a következetlenséget jobban bünteti, mint a nyelvtani hibát. Válassz egy megszólítási formát termékenként, dokumentáld, és tedd a glosszáriummal együtt szállított stílusútmutatóba; egy webshop, amely a láblécben magáz és a bevezetőben tegez, összerakottnak hat. A főnevek nagybetűsek maradnak, az összetett szavak egybe írandók, az idézőjelek pedig a német „így” formát követik.
A magyar agglutináló és nincs nyelvtani neme, ami két gyakorlati módon változtatja meg a folyamatot. Egyrészt a „felhasználó” vagy „ügyfél” nem vihető tovább névmással: a fordítás vagy megismétli a főnevet, vagy nélküle fogalmaz. Másrészt a toldalékok a főnévhez kapcsolódnak, ezért a toldalék belsejébe kerülő helyőrzőt kerülni kell, vagy át kell alakítani az üzenetet, amíg a toldalék a szó mellé kerül.
- Nyelvenként egy oldalas hangnem-jegyzet, a glosszárium mellett a repóban.
- Minden visszatérő kifejezés legyen glosszáriumelem, ne a promptban ismételt szöveg.
- Egy anyanyelvi lektor egyszer javítsa a hangnem-jegyzetet, utána a javítások lint-szabállyá válnak.
- A végeredményt a layoutban teszteld, ne a JSON-ban: a német és a magyar általában hosszabb az angolnál.
Ellenőrzések, amelyek szinte mindent elkapnak
Az első szakasz minden hibatípusához tartozik gépi ellenőrzés. Ugyanabban a commitban futtasd őket, mint a fordítást, ne egy későbbi takarító sprintben; egy folyamat, amely nem tud hangosan elbukni, csendben szállít hibás szövegeket.
// verify-locales.mjs - runs in CI, exits non-zero on the first real problem
const checks = [
['placeholder parity', (src, tgt) => sameSlots(src, tgt)],
['icu category parity', (src, tgt) => sameIcuCategories(src, tgt)],
['no empty values', (src, tgt) => tgt.trim().length > 0],
['glossary respected', (src, tgt) => glossaryViolations(src, tgt).length === 0],
['length sanity', (src, tgt) => tgt.length < src.length * 2.2],
]
- Helyőrző- és tag-paritás, szövegenként és ICU üzenetenként.
- Azonos kulcshalmaz a forrás- és a cél-locale fájlban.
- Nincs üres vagy változatlan érték ott, ahol a forrás megváltozott.
- A glosszárium-sértések a sértő kifejezéssel együtt, hogy az újrapróbálkozó prompt idézhesse.
- Hosszarány-korlátok, amelyek a csonkítást és a szétrobbanó bővülést még a designer előtt jelzik.
Költség és átbocsátás
A fordítás az LLM-számla kis része, mert a szövegek rövidek. Ökölszabály: ezer darab, egyenként húsz szavas szöveg nagyjából 20k bemeneti és 25k kimeneti token; egy középkategóriás modellnél, három dollár per millió bemeneti és tizenöt dollár per millió kimeneti token áron ez körülbelül negyvenöt cent ezer szövegre. A csúcsmodatok ennek többszörösébe kerülnek, és itt ritkán térülnek meg.
| Kör | Mit lát | Költségtényező |
|---|---|---|
| Első teljes futás | Minden forrásszöveg és a glosszárium | Forrásmennyiség, egyszer |
| Kiadáskülönbség | Csak a megváltozott forrású kulcsok | Változási arány |
| Pontos memória-találat | Semmit, memóriából másolva | Nulla |
| Review | Diffs, jelölt szabálysértések, minták | Emberi percek |
Az átbocsátás kérdése fontosabb az árnál. Néhány ezer szöveg teljes köré percek alatt lefut és CI-ben is futtatható; a szűk keresztmetszet a review, ezért a folyamat a reviewereknek a megváltozott kulcsok diffjét és a megjelölt szövegek listáját mutassa, ne egy elejétől olvasandó fájlt.
Ahol az ember még számít
A review-kapu akkor éri meg az árát, ha a hiba drága: onboarding, árazási oldalak, jogi szövegek, bármi, amit az ügyfél visszaidézhet. A hibaüzenetek és beállításcímkék hosszú farkánál az automatikus ellenőrzések és a glosszárium jobbak egy sietős emberi körnél, és ennek tagadása csak a következő kiadásra tolja a munkát.
- A folyamat nyelvenkénti diffet és megjelölt szövegek listáját állítja elő.
- A reviewer csak a megjelölt és jól látható elemeket hagyja jóvá vagy javítja.
- A jóváhagyott értékek visszaíródnak, ami frissíti a fordítási memóriát.
- A build publikálja az oldalt, és az ellenőrzések a publikált artefaktumon újra lefutnak.
Ebből semmi sem egzotikus. Forrásszövegek JSON-ban, mellettük glosszárium, egy modell, amely értékeket fordít, egy szkript, amely szerződéseket ellenőriz, és egy ember, aki a diffet nézi. A folyamat szándékosan unalmas, mert egy termék érdekes részei soha nem a fordítások.
Források
- Unicode ICU User Guide: Formatting Messages
- Vue I18n: Message Format Syntax
- Nuxt i18n: Getting started
- Unicode CLDR: Plural rules
- OpenAI: Structured outputs
- DeepL API: Glossaries
- W3C: Localization vs. internationalization
- GNU gettext manual
- RFC 5646: Tags for Identifying Languages
- Rat für deutsche Rechtschreibung
- Microsoft Writing Style Guide
Gyakori kérdések
Kell még translation management rendszer?
Nem feltétlenül. Egy nyelvankénti JSON-fájl review-kapuval a repositoryban a legtöbb termékoldalhoz elég. Egy TMS akkor térül meg, ha külső fordítók, sok nyelv vagy release-ágak vannak.
Mekkora legyen egy fordítási batch?
Elég kicsi ahhoz, hogy egy hiba olcsón újrapróbálható legyen, elég nagy ahhoz, hogy legyen kontextus. A gyakorlatban 20-60 szöveg kérésenként, oldal vagy komponens szerint csoportosítva.
Láthatja a modell a teljes fájlt kontextusként?
Igen, csak olvasási segítségként. Küldd a forrásfájlt a fordítandó szövegek megjelölésével, és a válasz csak a lefordított értékeket tartalmazza. Így a prompt és a válaszséma is kicsi marad.
Mi a helyzet az olyan nyelvekkel, ahol nincs angol típusú többes szám?
Az ICU kezeli, de csak ha a forrásszöveg is ICU-t használ. A számlálókat és többes számokat először ICU üzenetbe kell tenni, és magát az üzenetet fordítani, nem az összefűzött mondatot.
Kell még utószerkesztés?
A jól látható szövegeknél igen. A ritkán olvasott hibaüzeneteknél és beállításcímkéknél egy jó glosszárium és az automatikus ellenőrzések általában jobbak, mint egy sietős emberi átfutás.