Chrome 페이지 번역을 쓰다 보면 번역된 문장의 원문을 다시 확인하고 싶을 때가 있죠. 같은 기능을 하는ImmersiveTranslate 가 있지만 몇 번 쓰다보면 하루 사용량 제한이 있어 구독을 유도하더군요. 이 정도는 무료로 쓸 수 있어야 되지 않을까 해서 만들게 되었습니다.
Translight(빛번역) 는 영어 웹페이지의 원본 DOM을 가능한 한 그대로 유지하면서 각 문단 아래에 한국어 번역문을 추가하는 Chrome 확장 프로그램입니다.
Chrome 내장 Translator API를 사용하기 때문에 최초 모델 다운로드 이후에는 외부 번역 서버나 별도의 API 키가 필요하지 않습니다. 당연히 빛처럼 빠르고 로그인, 구독도 필요 없고 완전 무료입니다.
- GitHub: https://github.com/neverworkalone/translight
- Chrome Web Store: https://chromewebstore.google.com/detail/translight-·-빛번역/…
처음에는 “번역할 문단을 찾아서 Translator API로 번역하고 바로 밑에 노드 하나 추가하면 되겠지”라고 생각했습니다.
그런데 번역보다 어려웠던 건 원래 웹페이지를 깨뜨리지 않고 번역문을 끼워 넣는 일이었습니다.
사이트마다 DOM과 CSS가 너무 달랐습니다.
YouTube에서는 화면에 보이지 않는 내부 요소 때문에 번역문이 원문 위에 붙었고, Yelp에서는 display: table-cell 요소 옆에 번역문을 삽입했더니 원래 컬럼 폭이 무너지면서 레이아웃이 흔들렸습니다.
IMDb에서는 더 재미있는 문제가 있었습니다. 번역문을 넣으면 카드 높이가 바뀌고, 사이트의 hydration 과정이 내부 DOM을 다시 렌더링하면서 Translight가 넣은 번역문을 삭제했습니다. Translight는 번역문이 사라진 것을 감지하고 다시 넣고, IMDb는 다시 렌더링하고…… 서로 DOM을 뺏고 넣으면서 화면이 흔들리는 상태가 됐습니다.
결국 단순히 원문 다음에 translation element 삽입이라는 규칙 하나로는 해결할 수 없었습니다.
현재는 원본 TextNode나 innerHTML을 교체하지 않고 Translight가 만든 노드를 별도로 관리하면서, 원본 요소의 display, 중첩 구조, 주변 block 구조 등에 따라 삽입 위치를 결정합니다. 번역을 끄면 Translight가 추가한 노드와 스타일만 제거해서 원래 페이지로 돌아가도록 했습니다.
동적 페이지도 별도의 문제였습니다.
SPA navigation, 무한 스크롤, 댓글 추가 같은 페이지에서는 최초 DOM만 번역해서는 안 되기 때문에 변경된 subtree를 감지해서 새 콘텐츠만 다시 수집합니다. 그런데 이 과정에서 깊은 DOM의 모든 후보마다 다시 후손을 탐색하다 보니 한때 수집 과정이 사실상 O(N²) 으로 커지는 문제가 생겼습니다.
깊이 100 / 200 / 400짜리 DOM을 만들어 계측해 보니 탐색한 후손 수가 각각 약 5천 / 2만 / 8만으로 증가했습니다. 이후 후보 요소를 한 번 수집하고 그 정보를 재사용하는 방식으로 바꿔 이 경로를 선형화했습니다.
브라우저 확장이라 비동기 상태 관리에서도 예상하지 못한 문제가 나왔습니다.
탭이 닫힌 뒤에도 이미 시작된 비동기 작업이 끝나면서 삭제했던 tab state를 다시 만들어내는 race condition이 있었고, Chrome이 나중에 같은 tab ID를 재사용하면 이전 탭의 상태가 영향을 줄 가능성이 있었습니다. 지금은 탭 lifecycle epoch를 두고 종료 이전에 시작된 작업이 이후 상태를 기록하지 못하도록 막고 있습니다.
사이트 호환 문제를 하나씩 고치다 보니 테스트 방식도 바뀌었습니다.
처음에는 DOM 단위 테스트 위주였지만 실제 Chrome에서만 나타나는 레이아웃, SPA navigation, MutationObserver, OFF → ON 복원 같은 문제가 계속 나왔습니다.
그래서 지금은 문제가 발견된 사이트의 DOM 구조를 reproduction fixture로 남기고, Chrome for Testing에서 실제 packaged extension 전체 경로를 실행하는 회귀 테스트도 함께 사용합니다.
Codex로 개발을 함에 있어 AI의 한계도 찾았습니다. 너무 정석적인 방법을 쓰려고 하더군요. Yelp에서 display: table-cell 구조에 번역문을 추가하면 테이블이 위 아래로 출렁이는 문제가 생겼습니다. Chrome for Testing 에서 실제 웹사이트 견본으로 Translight를 설치해서 재현테스트를 하는데 결정적으로 CTF에서는 Translation model 다운로드가 안됩니다. Codex는 번역이 안돼서 문제를 재현할 수 없다고 뱉어버렸죠. 실제 CTF에서는 model 다운로드가 안되더군요. 그런데 사실 이 문제는 번역 품질 문제가 아니라 꼭 번역이 필요하진 않습니다. 그냥 가짜 번역 provider 하나 만들어서 dummy로 텍스트를 채워넣어도 문제를 재현할 수 있죠. 나중에는 AI가 그런 것도 배우겠지만 아직까지는 인간의 꼼수가 필요한 세상인 듯 합니다.
만들기 전에는 번역 엔진이 이 프로젝트에서 가장 어려운 부분일 거라고 생각했는데, 지금은 반대로 느낍니다.
번역하는 것보다, 남의 웹페이지를 망가뜨리지 않으면서 번역하는 게 훨씬 어려웠습니다.
아직 모든 웹사이트에서 완벽하게 동작한다고 할 수는 없습니다. 그래서 특히 다음 쪽의 피드백을 받아보고 싶습니다.
- 번역 위치나 레이아웃이 이상해지는 사이트
- SPA나 무한 스크롤에서 번역이 빠지는 경우
- 현재 DOM 처리 방식에서 위험해 보이는 부분
- Chrome Translator API를 실제 제품에 적용해 보신 분들의 경험
직접 사용해 보시거나 코드를 보시고 의견 주시면 이후 회귀 테스트와 개선에 반영해보겠습니다.