Zurück zur Startseite

OpenClaw-Übersetzung ins Russische ohne Quellcode

Artikel beschreibt die Implementierung der Laufzeitlokalisierung von OpenClaw ins Russische via transparenter Proxy-Server. Node.js-Service injiziert Übersetzungsskript in HTML, MutationObserver handhabt dynamisches DOM der SPA-Anwendung. Lösung erfordert kein Forken der Quellen und funktioniert mit täglichen Updates.

Russisches OpenClaw-Interface: Proxy ohne Fork
Advertisement 728x90

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.

Google AdInline article slot

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.

Google AdInline article slot

Ü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.

Google AdInline article slot

Bereitstellung

Installation per Skript:

  • git clone https://github.com/perfectinn/openclaw-ru-layer
  • cd openclaw-ru-layer
  • sudo 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

Advertisement 728x90

Weiterlesen