홈으로 돌아가기

소스 코드 없이 OpenClaw 러시아어 번역

이 기사는 투명 프록시 서버를 통한 OpenClaw의 러시아어 런타임 현지화 구현을 설명합니다. Node.js 서비스가 HTML에 번역 스크립트를 삽입하고, MutationObserver가 SPA 애플리케이션의 동적 DOM을 처리합니다. 이 솔루션은 소스 포킹을 요구하지 않으며 매일 업데이트와 호환됩니다.

러시아어 OpenClaw 인터페이스: 포크 없이 프록시
Advertisement 728x90

코드 수정 없이 OpenClaw을 러시아어로 로컬라이즈하는 프록시 방법

OpenClaw 개발팀은 i18n 인프라를 포함하지 않았습니다. 문자열은 react-intl나 i18next 없이 JSX 컴포넌트에 하드코딩되어 있습니다. 매일 업데이트되는 상황에서 수동 번역으로 포크하는 것은 현실적이지 않습니다. 해결책은 무엇일까요? 정답은 nginx와 앱 사이에 위치한 투명한 프록시입니다. 이 프록시는 HTML 응답에 번역 스크립트를 삽입하여 작동합니다.

트래픽 흐름:

  • nginx (포트 443) → 프록시 (포트 18790) → OpenClaw (포트 18789)

프록시는 오직 HTML만 수정하며, 앱의 로직은 그대로 유지합니다. 채팅용 WebSocket 연결은 원본 상태로 통과됩니다.

Google AdInline article slot

Node.js 서버 (~150줄)는 http-proxy를 사용해 응답을 가로챕니다. 핵심 기술은 res.writeres.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
  • { childList: true, subtree: true } 옵션을 가진 MutationObserver
  • trim()을 사용한 정확한 키 매칭으로 충돌 최소화

이 접근 방식의 한계

  • 플레이스홀더 속성: TreeWalker가 감지하지 못함 → 입력/선택 필드용 별도의 속성 관찰자 필요
  • 맥락적 동음이의어: 버튼의 "Run"과 텍스트의 "Run"은 서로 다른 번역이 필요 → 사전은 맥락을 구분할 수 없음
  • 새로운 문자열: OpenClaw 출시 후 사전이 업데이트되기 전까지 미번역 요소는 영어로 남음

성능: 번역 지연 시간은 ~10–50ms로 사용자에게 거의 느껴지지 않음.

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줄 코드, 2개월간 5–6회 사전 업데이트만 필요

대안 및 확장성

메인 리포지토리에 PR을 제출하는 것은 불가능합니다. 전체 리팩터링(문자열을 JSON으로 추출, i18n 라이브러리로 컴포넌트 감싸기)이 필요하기 때문입니다. 현재 솔루션은 분단위 내 배포 가능합니다.

다른 SPA에도 적용 가능한 개선 방향:

  • Shadow DOM: 추가적인 워커로 색다른 루트 탐색 가능
  • 가상 DOM 차이 비교: React DevTools API에 연결 (실험적)
  • 서비스 워커: 브라우저 레벨에서 fetch 요청 가로채기

— Editorial Team

Advertisement 728x90

다음 읽기