Localizar OpenClaw en ruso mediante proxy sin cambios de código
Los desarrolladores de OpenClaw no incluyeron infraestructura de i18n: las cadenas están codificadas directamente en los componentes JSX sin usar react-intl ni i18next. Hacer una bifurcación con traducciones manuales no es práctico debido a las actualizaciones diarias. La solución? Un proxy transparente entre nginx y la aplicación que inyecta un script de traducción en las respuestas HTML.
Flujo de tráfico:
- nginx (puerto 443) → proxy (puerto 18790) → OpenClaw (puerto 18789).
El proxy modifica solo el HTML, preservando la lógica de la aplicación. Las conexiones WebSocket para el chat se pasan sin cambios.
Un servidor Node.js (~150 líneas) usa http-proxy para interceptar las respuestas. Truco clave: sobrescribir res.write y res.end para almacenar en búfer el cuerpo de la respuesta.
import http from "node:http";
import httpProxy from "http-proxy";
const TARGET = process.env.TARGET_ORIGIN || "http://127.0.0.1:18789";
const proxy = httpProxy.createProxyServer({ target: TARGET, ws: true });
const server = http.createServer((req, res) => {
if (req.url === "/ru-overlay.js") {
// servir el script de traducción
res.writeHead(200, { "Content-Type": "application/javascript" });
res.end(overlayScript);
return;
}
// interceptar respuesta
const _write = res.write.bind(res);
const _end = res.end.bind(res);
let chunks = [];
res.write = (chunk) => { chunks.push(chunk); };
res.end = (chunk) => {
if (chunk) chunks.push(chunk);
let body = Buffer.concat(chunks).toString("utf-8");
if (res.getHeader("content-type")?.includes("text/html")) {
body = body.replace(
"</body>",
`<script src="/ru-overlay.js"></script></body>`
);
}
// eliminar content-length, sino el navegador trunca la respuesta
// por cambio de longitud
res.removeHeader("content-length");
_write(body);
_end();
};
proxy.web(req, res);
});
El manejo de WebSocket requiere escuchar el evento upgrade:
server.on("upgrade", (req, socket, head) => {
proxy.ws(req, socket, head);
});
Script del lado del cliente con MutationObserver
OpenClaw es una SPA: el DOM se reconstruye dinámicamente al navegar. Una simple exploración de nodos de texto al cargar no funcionará. MutationObserver rastrea nuevos nodos y los traduce instantáneamente.
Diccionario de traducción en JSON (~200 líneas):
const dict = {
"Settings": "Configuración",
"New conversation": "Nuevo diálogo",
"Send a message": "Enviar mensaje",
"Skills": "Habilidades",
"Memory": "Memoria",
"Schedule": "Horario",
"Delete": "Eliminar",
"Cancel": "Cancelar",
"Save": "Guardar",
// ... otras 200 entradas
};
function translateNode(node) {
if (node.nodeType !== Node.TEXT_NODE) return;
const key = node.textContent.trim();
if (dict[key]) {
node.textContent = node.textContent.replace(key, dict[key]);
}
}
// pasada inicial
const walker = document.createTreeWalker(
document.body, NodeFilter.SHOW_TEXT
);
while (walker.nextNode()) translateNode(walker.currentNode);
// observar nuevos elementos
new MutationObserver((mutations) => {
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
const w = document.createTreeWalker(
node, NodeFilter.SHOW_TEXT
);
while (w.nextNode()) translateNode(w.currentNode);
} else {
translateNode(node);
}
}
}
}).observe(document.body, { childList: true, subtree: true });
Componentes clave del script:
- TreeWalker para recorrer nodos de texto.
- MutationObserver con opciones
{ childList: true, subtree: true }. - Coincidencia exacta de claves usando trim() para minimizar colisiones.
Limitaciones de este enfoque
- Atributos de marcadores de posición: No capturados por TreeWalker—requieren un observador separado para campos input/select.
- Homónimos contextuales: "Run" en un botón vs. texto necesita traducciones diferentes—el diccionario no puede distinguir contexto.
- Cadenas nuevas: Los elementos sin traducir permanecen en inglés hasta que se actualice el diccionario tras la liberación de OpenClaw.
Rendimiento: el retraso de traducción es de ~10–50 ms—imperceptible para los usuarios.
Despliegue
Instalar mediante script:
git clone https://github.com/perfectinn/openclaw-ru-layercd openclaw-ru-layersudo bash scripts/install.sh --patch-nginx
Configura automáticamente el servicio systemd y nginx. Deshacer: sudo bash scripts/uninstall.sh.
Versión Docker:
docker build -t openclaw-ru-layer .
docker run --rm -p 18790:18790 \
-e TARGET_ORIGIN=http://host.docker.internal:18789 \
openclaw-ru-layer
Ventajas clave:
- No se necesitan cambios en OpenClaw—funciona con cualquier versión actual.
- MutationObserver garantiza traducción en tiempo real del contenido dinámico de la SPA.
- Actualizar el diccionario solo requiere añadir entradas al JSON—sin reiniciar el servidor.
- Los WebSocket se transmiten correctamente para mantener la funcionalidad del chat.
- Carga mínima: ~150 líneas de código, 5–6 actualizaciones del diccionario en dos meses.
Alternativas y escalabilidad
Enviar una solicitud de fusión al repositorio principal no es viable—requeriría refactorización completa (extraer cadenas a JSON, envolver componentes con librerías de i18n). La solución actual se despliega en minutos.
Para otras SPAs, posibles mejoras:
- Shadow DOM: Caminante adicional para recorrer raíces de sombra.
- Diferenciación del Virtual DOM: Conectar con la API de React DevTools (experimental).
- Service Worker: Interceptar solicitudes fetch a nivel del navegador.
— Editorial Team
Aún no hay comentarios.