返回首页

无需源代码的 OpenClaw 俄语翻译

文章描述了通过透明代理服务器实现 OpenClaw 运行时俄语本地化的实现。Node.js 服务将翻译脚本注入 HTML,MutationObserver 处理 SPA 应用的动态 DOM。该解决方案无需分叉源代码,并支持日常更新。

俄语 OpenClaw 界面:无需分叉的代理
Advertisement 728x90

无需代码修改,通过代理实现OpenClaw俄语本地化

OpenClaw开发团队未集成国际化支持:所有文本直接硬编码在JSX组件中,未使用react-intl或i18next。手动翻译分支无法应对每日更新,不切实际。解决方案?在nginx与应用之间部署一个透明代理,自动向HTML响应注入翻译脚本。

流量路径:

  • nginx(端口443) → 代理(端口18790) → OpenClaw(端口18789)。

代理仅修改HTML内容,完全保留应用逻辑。聊天功能使用的WebSocket连接则原样透传。

Google AdInline article slot

一个约150行的Node.js服务器,利用http-proxy拦截响应。关键技巧:重写res.write和res.end方法以缓冲响应体。

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") {
    // 提供翻译脚本
    res.writeHead(200, { "Content-Type": "application/javascript" });
    res.end(overlayScript);
    return;
  }

  // 拦截响应
  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头,避免浏览器因长度变化截断响应
    res.removeHeader("content-length");
    _write(body);
    _end();
  };

  proxy.web(req, res);
});

WebSocket处理需监听upgrade事件:

server.on("upgrade", (req, socket, head) => {
  proxy.ws(req, socket, head);
});

客户端脚本:使用MutationObserver实时翻译

OpenClaw是单页应用(SPA),页面导航时DOM会动态重建。仅在加载时遍历文本节点的方法无效。MutationObserver可实时监控新增节点并即时翻译。

Google AdInline article slot

翻译词典采用JSON格式(约200行):

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",
  // ... 其余200条映射
};

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

// 初始遍历
const walker = document.createTreeWalker(
  document.body, NodeFilter.SHOW_TEXT
);
while (walker.nextNode()) translateNode(walker.currentNode);

// 监听新元素添加
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 });

核心脚本组件:

  • 使用TreeWalker遍历文本节点。
  • MutationObserver启用{ childList: true, subtree: true }选项,确保全面覆盖。
  • 通过trim()精确匹配键值,有效减少误译冲突。

该方案的局限性

  • 占位符属性:TreeWalker无法捕获输入框/下拉框中的占位符——需额外添加属性观察器。
  • 同形异义词:按钮中的"Run"与正文中的"Run"需不同翻译,但词典无法区分上下文。
  • 新增文本:新出现的未翻译内容仍显示英文,直到词典随OpenClaw版本更新后才修复。

性能表现:翻译延迟约10–50毫秒,用户几乎无感知。

Google AdInline article slot

部署方式

通过脚本一键安装:

  • git clone https://github.com/perfectinn/openclaw-ru-layer
  • cd openclaw-ru-layer
  • sudo bash scripts/install.sh --patch-nginx

自动配置systemd服务与nginx。回滚操作:sudo bash scripts/uninstall.sh

Docker版本:

docker build -t openclaw-ru-layer .
docker run --rm -p 18790:18790 \
  -e TARGET_ORIGIN=http://host.docker.internal:18789 \
  openclaw-ru-layer

核心优势:

  • 无需修改OpenClaw源码,兼容任意当前版本。
  • MutationObserver保障动态SPA内容的实时翻译。
  • 词典更新只需添加JSON条目,无需重启服务。
  • WebSocket完整透传,聊天功能不受影响。
  • 开销极低:约150行代码,两个月内仅需5–6次词典更新。

替代方案与扩展性

向主仓库提交PR不可行——需重构整个国际化系统(提取字符串、封装i18n库)。当前方案可在数分钟内部署。

针对其他SPA,可拓展方向包括:

  • Shadow DOM:增加对影子根节点的遍历支持。
  • 虚拟DOM差异:接入React DevTools API(实验性)。
  • Service Worker:在浏览器层拦截fetch请求。

— Editorial Team

Advertisement 728x90

继续阅读