Vytvořili jste skupinu úloh. Někde uživatel sleduje spinner a „překládá se do 14 jazyků…“ je sice pravda, ale k ničemu — nikam to neposouvá. Chcete, aby před očima viděl, jak číslo roste: 3 hotové, pak 4, pak jeden jazyk selže a nakonec hotovo.
Polling skupiny úloh vás k tomu dovede, ale je ukecaný a každý dotaz vrací nový snapshot, který musíte porovnat s předchozím, abyste zjistili, co se vlastně změnilo. WebSocket to obrací. Připojíte se jednou a server pošle událost pokaždé, když se nějaký jazyk vyřeší — a každá zpráva nese kompletní stav skupiny, takže vykreslujete snapshot a nikdy neskládáte delty. Vypadne frame, znovu se připojíte, restartujete kartu: další zpráva je zase celá pravda.
GET /jobs/localization/groups/:groupId/wsJste v asynchronní lokalizaci noví? Začněte v Přehled. groupId tady je to, které jste dostali zpátky, když jste vytvořili úlohy.
Na této stránce
Typy zpráv#
Socketem putují čtyři typy zpráv. Každá vám řekne, co se právě stalo, a zároveň přidá aktuální stav celé skupiny.
| Typ | Kdy | Klíčová pole |
|---|---|---|
snapshot | Při navázání spojení | Kompletní stav skupiny |
job.completed | Jazyk se úspěšně dokončí | jobId, locale a kompletní stav skupiny |
job.failed | Jazyk selže | jobId, locale, error a kompletní stav skupiny |
group.completed | Všechny úlohy jsou vyřešené | groupId, status a kompletní stav skupiny. Po této zprávě server spojení uzavře. |
Každá zpráva obsahuje objekt snapshot s aktuálním stavem skupiny: totalJobs, completedJobs, completedWithWarningsJobs, failedJobs a mapu jobs indexovanou podle ID úlohy, kde má každá položka své locale a status. Tyto počty jsou stejné jako ty, které vrací endpoint skupiny úloh — takže snapshot ze socketu a poll přes REST endpoint se shodnou na tom, kam až skupina postoupila.
vykreslujte snapshot, nikdy neslučujte
Nikdy nemusíte sledovat, které události už jste viděli, přehrávat zmeškané zprávy ani spojovat částečnou aktualizaci s lokálním stavem. U každé zprávy přečtěte snapshot a z něj vykreslete své UI. Po opětovném připojení se nejdřív znovu pošle snapshot, takže klient, který se právě připojil, a klient, který poslouchal celou dobu, skončí ve stejném stavu.
Payloady zpráv#
Tohle jsou přesně ty framey, které server posílá. ID mají reálný tvar (ljg_ pro skupinu, ljb_ pro každou úlohu); snapshot je zkrácené pomocí "..." jen tam, kde opakuje už dřív ukázanou strukturu.
Po připojení server pošle aktuální stav:
{
"type": "snapshot",
"snapshot": {
"groupId": "ljg_A1b2C3d4E5f6G7h8",
"totalJobs": 3,
"completedJobs": 1,
"completedWithWarningsJobs": 0,
"failedJobs": 0,
"jobs": {
"ljb_A1b2C3d4E5f6G7h8": { "locale": "de", "status": "completed" },
"ljb_B2c3D4e5F6g7H8i9": { "locale": "fr", "status": "processing" },
"ljb_C3d4E5f6G7h8I9j0": { "locale": "ja", "status": "queued" }
}
}
}Jakmile se jednotlivé jazyky dokončí, událost pojmenuje jazyk, který se změnil, a přidá aktualizovaný snapshot:
{
"type": "job.completed",
"jobId": "ljb_B2c3D4e5F6g7H8i9",
"locale": "fr",
"snapshot": {
"groupId": "ljg_A1b2C3d4E5f6G7h8",
"totalJobs": 3,
"completedJobs": 2,
"completedWithWarningsJobs": 0,
"failedJobs": 0,
"jobs": {
"ljb_A1b2C3d4E5f6G7h8": { "locale": "de", "status": "completed" },
"ljb_B2c3D4e5F6g7H8i9": { "locale": "fr", "status": "completed" },
"ljb_C3d4E5f6G7h8I9j0": { "locale": "ja", "status": "processing" }
}
}
}Selhání je normální zpráva, ne spadlé spojení. job.failed nese jazyk a error a zároveň stejný kompletní snapshot — selhaný jazyk má v mapě jobs hodnotu status: "failed", všechny ostatní jazyky se dál streamují a socket běží až do group.completed:
{
"type": "job.failed",
"jobId": "ljb_C3d4E5f6G7h8I9j0",
"locale": "ja",
"error": "Model timeout after 30 seconds",
"snapshot": { "...": "..." }
}Jakmile jsou všechny úlohy vyřešené, server pošle finální událost a spojení uzavře:
{
"type": "group.completed",
"groupId": "ljg_A1b2C3d4E5f6G7h8",
"status": "completed",
"snapshot": { "...": "..." }
}Koncové status je completed, když každý jazyk uspěl, completed_with_warnings, když každý jazyk vytvořil výstup, ale jedna nebo víc pipeline fází označených jako nepovinné selhala alespoň u jednoho z nich, partial, když některé jazyky uspěly a jiné selhaly, a failed, když selhaly všechny. Co přesně každý z těchto stavů znamená pro skupinu jako celek, najdete v Track a job group.
Na vše, co nepoznáte, vykreslujte ze snapshotu
Rozlišujte typy zpráv, které znáte, a u všeho ostatního prostě znovu vykreslete z snapshot. Každá zpráva nese kompletní snapshot, takže klient, který z něj standardně vykresluje, zůstane správně i u frameu, pro který nemá vlastní větev.
Jak to napojit do vašeho UI#
Skupina je váš model průběhu. Když jste vytvořili úlohy, odpověď 202 vám vrátila groupId a pole jobs — jednu položku pro každý jazyk. Založte z této odpovědi svůj záznam průběhu a získáte tvar, který socket postupně doplní: celkový počet, ke kterému počítáte, a počítadlo začínající na nule.
const { groupId, jobs } = await response.json();
await db.translationProgress.create({
contentId: content.id,
groupId,
totalLanguages: jobs.length,
completedLanguages: 0,
});Pak proti tomuto groupId otevřete socket a u každé zprávy přečtěte snapshot a překreslete. Sledujte, jak počítadlo roste s tím, jak přibývají hotové jazyky, a skončete ve chvíli, kdy dorazí group.completed:
import WebSocket from "ws";
const groupId = "ljg_A1b2C3d4E5f6G7h8";
const ws = new WebSocket(
`wss://api.lingo.dev/jobs/localization/groups/${groupId}/ws`,
{ headers: { "X-API-Key": process.env.LINGO_API_KEY } }
);
ws.on("message", (raw) => {
const event = JSON.parse(raw);
const { snapshot } = event;
switch (event.type) {
case "snapshot":
console.log(`${snapshot.completedJobs}/${snapshot.totalJobs} complete`);
break;
case "job.completed":
console.log(`${event.locale} ready (${snapshot.completedJobs}/${snapshot.totalJobs})`);
break;
case "job.failed":
console.error(`${event.locale} failed: ${event.error}`);
break;
case "group.completed":
console.log(`All translations done: ${event.status}`);
ws.close();
break;
}
});Při běhu nad skupinou o třech jazycích se vypíše průběh takto:
1/3 complete
fr ready (2/3)
ja failed: Model timeout after 30 seconds
All translations done: partialPočítadlo se posouvalo samo, jeden jazyk selhal, aniž by shodil stream, a partial vám řeklo, kde běh skončil — přesně to, co váš spinner potřebuje, aby se z něj stal skutečný ukazatel průběhu. Všimněte si, že smyčka nikdy nehromadí stav: každá větev čte z snapshot v právě zpracovávané zprávě, takže stejný kód funguje správně při prvním připojení, při každé aktualizaci i po opětovném připojení.
Nechte API klíč na serveru#
Socket se ověřuje vaším API klíčem, stejným klíčem v rozsahu organizace, který používají REST endpointy. To znamená, že prohlížeč je to poslední místo, odkud ho otevírat — API klíč v klientském JavaScriptu zpřístupní každý engine ve vaší organizaci komukoli, kdo si zobrazí zdroják.
Připojujte se z backendu, ne z prohlížeče
WebSocket otevřete ze svého serveru, kde už klíč stejně žije, a pak události pošlete do prohlížeče přes vlastní kanál — WebSocket nebo stream server-sent events, který máte pod kontrolou. Frontend dostane průběh v reálném čase; klíč nikdy neopustí vaši infrastrukturu.
Tohle kopíruje model webhooků: spojení, které komunikuje s Lingo.dev, běží na serveru a k uživateli se dostane jen to, co se vaše aplikace rozhodne přeposlat dál.
Kam to zapadá#
WebSocket je živý pohled — je navázaný na jednu skupinu a zavře se, jakmile je hotovo. Pro spolehlivé doručování mezi servery, které přežije zavření karty nebo deploy, ho spárujte s webhooky: socket pohání UI, když je běh na obrazovce, webhook zaznamená každý výsledek v okamžiku, kdy dorazí. Zapojte obojí ze stejného create call a uživatelé uvidí průběh v reálném čase, zatímco váš backend si výstup uchová bez ohledu na to, kdo se zrovna dívá.
