|
Documentación
Agenda una demoPlataforma
PlataformaMCPCLIAPI
Flujos de trabajo
GuíasRegistro de cambios

Bienvenida

  • Descripción general
  • Autenticación
  • Errores y códigos de estado
  • Firmas de webhook

Localización

  • Descripción general
  • Crear trabajos
  • Bloquea las claves no traducibles
  • Monitorear un grupo de trabajos
  • Obtener un trabajo
  • Listar trabajos
  • Entrega de webhooks
  • Progreso en tiempo real (WebSocket)

Pipeline

  • Descripción general
  • Edición con IA previa a la localización
  • Revisión humana
  • evaluación de IA (post-edición)
  • Reescribe para que suene natural
  • Verificación de retrotraducción
  • Configura el pipeline
  • Ver ejecuciones del pipeline

Aprovisionamiento

  • Descripción general
  • Crear un trabajo de aprovisionamiento
  • Tipos de fuente
  • Lo que extrae la IA
  • Entrega de webhooks
  • Progreso en vivo (WebSocket)

Síncrono

  • Localize
  • Recognize

Gestión del motor

  • Sugerencias del motor

Progreso en tiempo real por WebSocket

Creaste un grupo de trabajos. En algún lugar, un usuario está mirando un spinner, y "traduciendo a 14 idiomas…" es cierto, pero no sirve de mucho: nunca avanza. Quieres que el conteo suba frente a sus ojos: 3 listos, luego 4, luego un idioma que falló y, al final, terminado.

Consultar el grupo de trabajos te lleva hasta ahí, pero hace mucho ruido, y cada consulta te entrega una instantánea nueva que tienes que comparar con la anterior para entender qué cambió de verdad. El WebSocket le da la vuelta a eso. Te conectas una sola vez y el servidor envía un evento cada vez que se resuelve un idioma, y cada mensaje incluye el estado completo del grupo, así que renderizas la instantánea y nunca reconcilias un delta. Si se pierde un frame, te reconectas o reinicias la pestaña, el siguiente mensaje vuelve a ser toda la verdad.

text
GET /jobs/localization/groups/:groupId/ws

¿Recién empiezas con la localización asíncrona? Comienza por la Descripción general. El groupId aquí es el mismo que recibiste cuando creaste los trabajos.

En esta página

  • Tipos de mensaje
  • Payloads de los mensajes
  • Cómo conectarlo a tu interfaz
  • Mantén tu API key del lado del servidor

Tipos de mensaje#

Por el socket viajan cuatro tipos de mensaje. Cada uno te dice qué acaba de pasar y, al mismo tiempo, te entrega el estado actual de todo el grupo.

TipoCuándoCampos clave
snapshotAl establecer la conexión inicialEstado completo del grupo
job.completedUn idioma termina correctamentejobId, locale, más el estado completo del grupo
job.failedUn idioma fallajobId, locale, error, más el estado completo del grupo
group.completedTodos los trabajos ya se resolvierongroupId, status, más el estado completo del grupo. El servidor cierra la conexión después de este mensaje.

Cada mensaje contiene un objeto snapshot con el estado actual del grupo: totalJobs, completedJobs, completedWithWarningsJobs, failedJobs y un mapa jobs indexado por ID de trabajo, cada uno con su locale y status. Esos conteos son los mismos que reporta el endpoint del grupo de trabajos, así que una instantánea del socket y una consulta al endpoint REST coinciden en cuánto ha avanzado el grupo.

renderiza la instantánea, nunca reconcilies

Nunca necesitas llevar registro de qué eventos ya viste, reproducir mensajes perdidos ni fusionar una actualización parcial con el estado local. Lee snapshot en cada mensaje y pinta tu interfaz a partir de ahí. Una reconexión vuelve a enviar primero snapshot, así que un cliente que acaba de unirse y otro que ha estado escuchando todo el tiempo convergen en el mismo estado.

Payloads de los mensajes#

Estos son los frames exactos que envía el servidor. Los ID tienen formatos reales (ljg_ para el grupo, ljb_ para cada trabajo); snapshot se abrevia con "..." solo cuando repite la estructura que ya se mostró.

Al conectarte, el servidor envía el estado actual:

json
{
  "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" }
    }
  }
}

A medida que cada idioma termina, el evento identifica el idioma que cambió e incluye la instantánea actualizada:

json
{
  "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" }
    }
  }
}

Un fallo es un mensaje normal, no una conexión caída. job.failed incluye el idioma y un error, además de la misma instantánea completa: el idioma que falló aparece como status: "failed" en el mapa jobs, todos los demás idiomas siguen llegando por streaming y el socket sigue hasta group.completed:

json
{
  "type": "job.failed",
  "jobId": "ljb_C3d4E5f6G7h8I9j0",
  "locale": "ja",
  "error": "Model timeout after 30 seconds",
  "snapshot": { "...": "..." }
}

Cuando todos los trabajos ya se resolvieron, el servidor envía un evento final y cierra la conexión:

json
{
  "type": "group.completed",
  "groupId": "ljg_A1b2C3d4E5f6G7h8",
  "status": "completed",
  "snapshot": { "...": "..." }
}

El status final es completed cuando todos los idiomas se completaron correctamente, completed_with_warnings cuando todos los idiomas produjeron salida pero una o más etapas opcionales del pipeline fallaron en al menos uno de ellos, partial cuando algunos idiomas se completaron correctamente y otros fallaron, y failed cuando todos fallaron. Para ver qué significa cada uno de esos estados para el grupo en su conjunto, consulta Track a job group.

Renderiza desde la instantánea ante cualquier cosa que no reconozcas

Haz switch sobre los tipos de mensaje que conoces y, para cualquier cosa que no reconozcas, vuelve a renderizar desde snapshot. Cada mensaje incluye una instantánea completa, así que un cliente que por defecto pinta a partir de ella sigue siendo correcto incluso ante un frame para el que no tiene una rama específica.

Cómo conectarlo a tu interfaz#

El grupo es tu modelo de progreso. Cuando creaste los trabajos, el 202 te devolvió un groupId y un arreglo jobs: una entrada por idioma. Inicializa tu registro de progreso a partir de esa respuesta y ya tendrás la estructura que el socket irá completando: el total hacia el que contar y un contador que empieza en cero.

javascript
const { groupId, jobs } = await response.json();

await db.translationProgress.create({
  contentId: content.id,
  groupId,
  totalLanguages: jobs.length,
  completedLanguages: 0,
});

Luego abre el socket con ese groupId y, en cada mensaje, lee snapshot y vuelve a pintar. Mira cómo sube el contador a medida que se resuelven los idiomas y detente cuando llegue group.completed:

javascript
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;
  }
});

Ejecutándolo con un grupo de tres idiomas, eso imprime la ejecución en tiempo real:

text
1/3 complete
fr ready (2/3)
ja failed: Model timeout after 30 seconds
All translations done: partial

El contador avanzó por sí solo, un idioma falló sin tumbar el stream y partial te dijo en qué terminó la ejecución: justo lo que tu spinner necesita para convertirse en una barra de progreso real. Fíjate que el bucle nunca acumula estado: cada rama lee del snapshot del mensaje actual, así que el mismo código es correcto en la conexión inicial, en cada actualización y al reconectarte.

Mantén tu API key del lado del servidor#

El socket se autentica con tu API key, la misma clave con alcance de organización que usan los endpoints REST. Eso significa que el navegador es el lugar equivocado para abrirlo: una API key en el JavaScript del cliente da acceso a todos los motores de tu organización a cualquiera que vea el código fuente.

Conéctate desde tu backend, no desde el navegador

Abre el WebSocket desde tu servidor, donde la clave ya vive, y luego distribuye los eventos al navegador por tu propio canal: un WebSocket o un stream de server-sent events que tú controlas. Tu frontend obtiene progreso en tiempo real; tu clave nunca sale de tu infraestructura.

Esto refleja el modelo de webhook: la conexión que toca Lingo.dev vive del lado del servidor, y lo que le llega al usuario es lo que tu propia app decida reenviar.

Dónde encaja esto#

El WebSocket es la vista en tiempo real: está vinculado a un solo grupo y se cierra cuando ese grupo termina. Para una entrega duradera de servidor a servidor que sobreviva al cierre de una pestaña o a un deploy, combínalo con webhooks: el socket impulsa la interfaz mientras la ejecución está en pantalla, y el webhook registra cada resultado en cuanto llega. Conecta ambos desde la misma llamada de creación y tus usuarios verán el progreso conforme ocurre mientras tu backend conserva la salida sin importar quién la esté viendo.

Entrega por webhook
Entrega duradera de servidor a servidor de cada idioma a medida que se completa
Crear trabajos
Envía contenido para traducir y obtén el groupId al que te conectas aquí
Track a job group
Estados del grupo y qué significa la finalización parcial para el grupo

¿Te resultó útil esta página?

Max PrilutskiyMax Prilutskiy·Actualizado hace 12 días·6 min de lectura