OpenClaw in Russisch lokalisieren – ohne Codeänderungen per Proxy
Entwickler von OpenClaw haben keine i18n-Infrastruktur integriert: Texte sind direkt im JSX kodiert, ohne react-intl oder i18next. Eine Fork mit manueller Übersetzung ist aufgrund täglicher Updates nicht praktikabel. Die Lösung? Ein transparenter Proxy zwischen nginx und der Anwendung, der eine Übersetzungs-Skript in HTML-Antworten einfügt.
Ablauf des Datenverkehrs:
- nginx (Port 443) → Proxy (Port 18790) → OpenClaw (Port 18789).
Der Proxy verändert nur HTML und bewahrt die App-Logik. WebSocket-Verbindungen für den Chat werden unverändert durchgereicht.
Ein Node.js-Server (~150 Zeilen) nutzt http-proxy, um Antworten zu erfassen. Der entscheidende Trick: res.write und res.end werden überschrieben, um den Antwortkörper zu pufferen.
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") {
// liefert das Übersetzungs-Skript
res.writeHead(200, { "Content-Type": "application/javascript" });
res.end(overlayScript);
return;
}
// Antwort abfangen
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>`
);
}
// content-length entfernen, sonst schneidet Browser Antwort ab
// wegen geänderter Länge
res.removeHeader("content-length");
_write(body);
_end();
};
proxy.web(req, res);
});
WebSocket-Handling erfordert die Überwachung des Upgrade-Ereignisses:
server.on("upgrade", (req, socket, head) => {
proxy.ws(req, socket, head);
});
Client-seitiges Skript mit MutationObserver
OpenClaw ist eine SPA – der DOM wird bei Navigation dynamisch neu aufgebaut. Eine einfache Textknoten-Durchsuchung beim Laden funktioniert nicht. MutationObserver erfasst neue Knoten sofort und übersetzt sie in Echtzeit.
Übersetzungs-Wörterbuch als JSON (~200 Zeilen):
const dict = {
"Settings": "Settings",
"New conversation": "New conversation",
"Send a message": "Send message",
"Skills": "Skills",
"Memory": "Memory",
"Schedule": "Schedule",
"Delete": "Delete",
"Cancel": "Cancel",
"Save": "Save",
// ... weitere 200 Einträge
};
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]);
}
}
// Erste Durchlauf
const walker = document.createTreeWalker(
document.body, NodeFilter.SHOW_TEXT
);
while (walker.nextNode()) translateNode(walker.currentNode);
// Neue Elemente beobachten
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 });
Wichtige Komponenten des Skripts:
- TreeWalker zur Durchsuchung von Textknoten.
- MutationObserver mit
{ childList: true, subtree: true }. - Genauige Schlüsselübereinstimmung mittels trim(), um Kollisionen zu minimieren.
Grenzen dieses Ansatzes
- Platzhalterattribute: Werden von TreeWalker nicht erfasst – benötigen separate Beobachter für Eingabefelder und Selects.
- Kontextuelle Homonyme: „Run“ in einem Button vs. im Text braucht unterschiedliche Übersetzungen – Dictionary kann Kontext nicht unterscheiden.
- Neue Strings: Nicht übersetzte Elemente bleiben auf Englisch, bis das Wörterbuch nach OpenClaw-Release aktualisiert wird.
Leistung: Übersetzungsverzögerung liegt bei ~10–50 ms – für Nutzer spürbar nicht.
Bereitstellung
Installation per Skript:
git clone https://github.com/perfectinn/openclaw-ru-layercd openclaw-ru-layersudo bash scripts/install.sh --patch-nginx
Automatisch konfiguriert systemd-Dienst und nginx. Rückgängigmachen: sudo bash scripts/uninstall.sh.
Docker-Version:
docker build -t openclaw-ru-layer .
docker run --rm -p 18790:18790 \
-e TARGET_ORIGIN=http://host.docker.internal:18789 \
openclaw-ru-layer
Hauptvorteile:
- Keine Änderungen an OpenClaw nötig – funktioniert mit jeder aktuellen Version.
- MutationObserver sorgt für Echtzeit-Übersetzung dynamischer SPA-Inhalte.
- Wörterbuchaktualisierungen erfordern nur Hinzufügen von Einträgen in JSON – kein Server-Neustart.
- WebSockets werden korrekt weitergeleitet, Chat-Funktion bleibt erhalten.
- Minimaler Overhead: ~150 Zeilen Code, 5–6 Wörterbuch-Updates innerhalb zweier Monate.
Alternativen und Skalierung
Ein Pull Request im Haupt-Repo ist unrealistisch – würde umfassende Umgestaltung erfordern (Textextraktion in JSON, Wrap mit i18n-Bibliotheken). Die aktuelle Lösung lässt sich in Minuten deployen.
Für andere SPAs mögliche Erweiterungen:
- Shadow DOM: Zusätzlicher Walker für Shadow-Roots.
- Virtual DOM Diff: Hook in React DevTools API (experimentell).
- Service Worker: Abrufen von fetch-Anfragen auf Browser-Ebene abfangen.
— Editorial Team
Noch keine Kommentare.