Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

사이드 프로젝트로 만든 GitHub 번역용 크롬 익스텐션

작년 11월에 구글 번역신경망이 적용되면서 그동안 형편없던 영어-한글 번역에 새로운 세상이 왔음을 알게 되었다. 많은 개발자가 이를 보고 뭔가 이용할 수 있는 게 없을까 하는 생각을 했을 테고 내 주변에도 간단한 도구나 확장으로 번역해 보는 걸 만들었지만, API가 없어서 제대로 뭔가를 만들기는 어려웠다. 나도 그러던 중 뭔가 만들어 볼 수 있지 않을까 해서 Google 클라우드 번역 API를 보다 보니 언제 나왔는지 몰라도 신경망에 기반을 둔 번역이 프리미엄으로 제공되는 것을 알게 되었다.

Standard 번역이 기존의 기계번역이고 신경망 번역이 Premium인데 아직 베타 상태라 신청을 해서 허락을 받아야 사용할 수 있다. 원래는 만들어서 공개할 생각이었지만 일단 신청서는 팀 내부에서만 쓰려고 이런 번역 크롬 익스텐션을 만들려고 한다고 신청서를 작성해 놓고 기대도 안 하고 잊고 있었다.

구글에서 받은 승인 메일

그랬더니 웬걸 갑자기 승인되었다고 메일이 왔다. 앗, 이러면 진짜 만들어 봐야겠는데.... 하고는 만들게 되었다.

GitHub의 댓글 번역

영문 글도 많이 보고 GitHub도 많이 쓰는데 블로그나 기사는 맘먹고 집중해서 읽으면 그나마 읽을 수 있는데 GitHub 이슈나 Pull Request에서 이뤄지는 다양한 논의는 이해하기가 쉽지 않았다. 해커뉴스의 댓글도 마찬가지. 이런 글은 정리해서 쓴 글도 아니고 여러 사람이 다양한 의견을 내놓으므로 내 어설픈 영어로는 문맥을 파악하기가 어려웠고 집중해서 읽기에는 글이 너무 많았다. 나는 한글로 댓글을 보듯이 하나하나 정독하는 게 아니라 훑어보면서 대략적인 문맥만 파악하기를 원했다.

Google 번역 크롬 익스텐션이 있지만 나는 이런 도구로는 내용을 파악하기가 어려웠다. 왜냐하면, 원래 글의 모양 그대로 보여주는 것이 아니라 어떤 문단이 어떻게 번역되었는지 비교하기가 어려웠고 구글 번역의 경우 줄 바꿈이 있으면 별도의 문장으로 번역하는데 GitHub은 마크다운을 많이 쓰다 보니 결과 번역 품질이 좋지 않았다.

내가 원한 건...

  1. GitHub 화면에서 간단한 액션으로 번역 결과를 봐야 한다.
  2. 번역이 이상하면 원문을 봐야 하므로 원문이 같이 나왔으면 좋겠다.
  3. GitHub은 코드나 이미지도 있고 마크다운으로 스타일도 주므로 번역 글을 쉽게 보려면 이러한 스타일이 최대한 유지되어야 한다.

이 정도 목표를 가지고 크롬 익스텐션을 만들기 시작했다.

대충 보면 알겠지만, 처음에는 쉽게 생각했다. 내용을 가져와서 번역하고 그 결과를 화면에 뿌려주면 되는 게 끝이다. 원래는 연초의 일주일 정도 기분전환용 프로젝트로 시작했다. 하지만 1월 내내 이거만 붙잡고 있었지만...

Issue Translator for GitHub

GitHub의 트레이드마크를 침해하지 않으려고 위와 같은 이름을 사용했다. 개발과정은 뒤에 설명하겠지만 일단 결론부터 얘기하면 다음과 같은 크롬 익스텐션을 만들었다. 저장소 참고.

익스텐션의 옵션 화면

먼저 Issue Translator for GitHub를 크롬에 설치하고 API 키와 번역할 언어를 선택한다.(기준은 항상 영어)

데모

이제 GitHub에서 이슈나 풀 리퀘이스트에 가면 댓글마다 오른쪽 위에 지구본 모양의 번역 버튼이 나타난다. 이 버튼을 누르면 구글 번역 API로 번역한 결과를 아래에 보여준다. 이때는 원글의 스타일이나 링크를 최대한 유지한다. 생각보다는 잘 나와서 GitHub을 볼 때 애용하고 있다.

하지만

이 크롬 익스텐션은 Chrome 웹 스토어에 아직 등록하지 않았다. 왜냐하면, 구글 번역 API 프리미엄이 아직 베타 상태이기 때문이다. 그래서 공개하더라도 구글에 신청해서 키를 받지 않는다면 이 플러그인을 쓸 수가 없다. 2월 중에는 베타가 끝나고 공개되지 않을까 해서 기다리고 있었는데 계속 나오지 않아서 그냥 공개하고 나중에 베타가 끝나면 스토어에 올리거나 하기로 했다. 참고로 구글 번역 API는 유료 API이므로 내가 과금을 하지 않으려면 사용자가 구글 클라우드에 가입해서 API를 사용하게 해야 한다.

일단 이 글은 개발 정리용이다.

초기 개발

처음에는 아주 간단히 생각했다. 한꺼번에 전체를 다 번역하려고 하다 보니 API가 유료라서 불필요한 글자 수를 줄이기 위해 댓글마다 버튼을 추가하고 버튼을 누르면 댓글 내용을 구글 API에 던졌다가 받아서 아래에 보여주면 끝날 거라고 생각했다. 그래서 며칠이면 될 것 같았다.

일단 버튼이 필요하므로 GitHub의 Octicons에서 지구본 모양을 골랐다. Octions 모듈에서 API를 잘 제공해서 아이콘의 SVG 패스를 바로 출력해 준다.

$ octicons.globe.path
'<path fill-rule="evenodd" d="M7 1C3.14 1 0 4.14 0 8s3.14 7 7 7c.48 0 .94-.05 1.38-.14-.....'

이를 크롬 익스텐션이 로드될 때 댓글 영역을 찾아서 댓글 상단에 버튼을 넣었더니 원래 있던 듯 자연스럽게 어울렸다.

번역 버튼

크롬 익스텐션을 만들 때 편한 점은 크로스 브라우징을 신경 쓰지 않아도 된다는 거다. 그래서 jQuery나 다른 라이브러리도 사용하지 않고 V8에서 지원하는 JavaScript로만 작성하려고 querySelector등을 열심히 사용하고 API 요청도 fetch API를 사용했다. 하다 보니 API가 초당 10개가 넘으면 오류가 발생해서 요청마다 약간의 딜레이를 주어서 1초에 10개 이상이 나가지 않도록 제어해야 했다.

대충 GitHub의 댓글 DOM 구조를 파악해보니 한 댓글의 각 문단이 <p>태그로 묶이는 걸 알 수 있었다. 댓글 내용 중에 코드 블럭이나 이미지는 블럭할 필요가 없으므로 이는 제외하고 다른 <p>태그만 가져와서 구글 번역을 했더니 다행히도! 번역하면서 HTML 마크업까지 그대로(사실 100%는 아니다) 돌려주었다. 이를 댓글 하단에 보여주니까 제법 그럴듯했다.

그래서 이것저것 테스트하면서 다양한 케이스를 좀 살펴본 다음에 지인 중심으로 플러그인을 뿌렸다. 어차피 Premium API가 비공개이므로 시간이 충분하다는 생각이었다.

익스텐션 개선

피드백 받다 보니 번역 버튼이 나오지 않는다는 얘기가 나왔다. 난 테스트를 하느라고 다양한 댓글을 여러 탭에 띄워놓고 테스트하느라고 몰랐는데 일반적인 패턴에서 사용하다 보니 다른 페이지에서 이슈나 풀 리퀘스트로 넘어가면 버튼이 보이지 않고 페이지를 갱신해야 보였다. 상황을 이해했을 때 대충 짐작이 갔지만, GitHub이 pjax를 쓰고 있어서 메뉴를 바꿀 때 페이지를 새로 로드하는게 아니라 내용 부분만 바꿔친다. 크롬 익스텐션은 페이지 로딩을 방해하지 않으려고 페이지 로딩 후에 익스텐션을 활성화하는데 이게 한 번만 실행되므로 pjax로 메뉴가 바뀌면 댓글에 번역 버튼이 추가되지 않았다. 나는 페이지가 로딩되면 댓글이 있는지 확인 후에 버튼을 넣어주도록 구현해 놓고 있었다.

먼저 GitHub의 동작을 파악하기 위해서 테스트해보니 push state로 조작하는 걸 알 수 있었고 push state의 상태변경을 탐지하기 위해 찾아보니 chrome.webNavigation로 할 수 있는 걸 알게 되었다. manifest.json에서 webNavigation에 대한 권한을 얻은 후 백그라운드 스크립트에서 다음과 같이 히스토리가 변경되는 이벤트를 받았다. 문서에 잘 나와 있지 않아서 꽤 헤맸는데 페이지에서 어떤 스크립트를 동작하게 하는 것을 크롬 익스텐션에서는 콘텐츠 스크립트라고 부르는데 이 이벤트 탐지는 백그라운드 스크립트에서 해야 한다.

if (chrome.webNavigation && chrome.webNavigation.onHistoryStateUpdated) {
  chrome.webNavigation.onHistoryStateUpdated.addListener((details) => {
    chrome.tabs.sendMessage(details.tabId, {action: 'rerun', url: details.url});
  });
}

여기서 받은 이벤트를 chrome.tabs.sendMessage로 해당 탭에 메시지를 보내도록 했다. 콘텐츠 스크립트에서는 다음과 같이 받아서 재실행을 해주었다.

chrome.extension.onMessage.addListener(function(msg) {
  if (msg.action === 'rerun') {
    if (msg.url === location.href) {
      // 버튼 추가 재 실행
    }
  }
});

조건문이 있는 이유는 테스트하다 보니 페이지가 바뀔 때 현재 페이지 URL과 다음 페이지 URL 이렇게 2번씩 이벤트가 발생하므로 1개는 무시하기 위해서 넣은 것이다.

그리고 번역품질이 좋지 않다는 피드백이 있었다. 피드백 받은 댓글로 테스트를 해보니 실제로 번역품질이 많이 안 좋았다. 구글 번역에서 좀 더 테스트를 해보니까 특수기호가 구글 번역결과에 많은 영향을 준다. 아주 간단한 예를 들어 다음과 같은 문장을 보자.

This commit implements the Web IDL USVString conversion, which mandates all unpaired Unicode surrogates be turned into U+FFFD REPLACEMENT CHARACTER.

이는 다음과 같이 번역된다.

이 커밋은 모든 <b> 비 유니 코드 </ b> 대리자가 U + FFFD REPLACEMENT CHARACTER로 변경되도록하는 Web IDL USVString 변환을 구현합니다.

하지만 <b> 태그가 없다면 다음처럼 더 자연스럽게 번역이 된다.

이 커밋은 웹 IDL USVString 변환을 구현합니다.이 변환은 모든 비 유니 코드 사로 게이트를 U + FFFD REPLACEMENT CHARACTER로 변환하도록 요구합니다.

이는 아주 사소한 비교이지만 실제로는 더 다양한 케이스가 있고 태그하나 들어가냐 아니냐에 따라서 번역품질이 많이 달라졌다. ㅠ 결국은 번역을 해주고자 하므로 스타일 등을 버리고 문장만 반역한다면 훨씬 좋은 결과를 얻을 수 있었고 테스트한 지인에게 물어봐도 번역품질이 더 좋은 게 낫다는 얘기를 했다.

번역 품질 개선

하지만 원래 목표에서 스타일을 유지하는 게 목표였고 실제로 이 마크다운의 스타일을 유지하니까 보기가 편해서 스타일을 버리고 싶지 않았다. 주변 사람들한테 이 문제점을 얘기하다가 갑자기 좋은 생각이 났다. HTML 태그는 아무래도 장황한데 어차피 Markdown으로 입력한 내용이니까 HTML을 Markdown으로 변환한 뒤 번역을 하고 Markdown을 다시 HTML로 바꾸어서 출력하면 결과가 훨씬 나으면서 스타일도 유지할 수 있지 않을까 한 것이다. 마크다운 기호는 아무래도 HTML 태그보다는 더 글쓰기 기호에 가까우니 영향이 더 적을 거라고 생각했다.

HTML을 마크다운으로 바꿀 때는 to-markdown을 쓰고 마크다운을 다시 HTML로 바꿀 때는 markdown-it을 사용했다. 실제로 해보니 번역결과가 더 좋아진 것을 알 수 있었다.

대신 마크다운 기호를 구글 번역이 임의로 막 바꾸는 경우가 많았다. 이건 일정 패턴이 있는 게 아니라 문장에 따라 임의로 바꾸어서 스타일이 깨지므로 이를 최대한 정리해주어야 했다. 예를 들어 > * Chris> *Chris가 된다거나 [text](url)[text] (url)로 바꾼다거나 하는 등이다. 이를 하나씩 찾아서 수정하다 보니 더는 기억으로 제어할 수 없는 수준이 되어서 결국은 경우별 테스트 케이스를 작성해서 관리할 수밖에 없었다. (구글. 부들부들)

특히 이미지랑 링크는 골치 아팠는데 마크다움에서 링크는 [github](https://github.com/)처럼 만들어지는데 이 URL이 깨지는 경우가 많았고 [github](https://github.com/#anchor)와 같이 앵커가 붙은 경우는 # 뒤의 부분만 링크에서 떨어져나와서 번역문이 이상해지는 경우가 많았다. 이미지는 사용자가 업로드를 하므로 링크와 이미지가 섞여서 [![screentshot](img-url)](url)처럼 되는데 이때는 더 심각하게 꼬여버린다. 그래서 이모지로 플레이스 홀더를 만들어 번역 전에 갈아치운 후 번역이 끝나면 다시 복구하는 노가다 방식을 선택했다.

번역된 화면

결국, 다 좋은 결과가 나오는 건 아니지만, 내용을 파악하고 읽기 쉬운 형태 정도로는 나왔다.

마무리

Primium API 오픈 안 한다고 소스를 다듬다 보니 처음에는 익스텐션 코드밖에 없었는데 결국은 babel, webpack 2, karma 테스트까지 모두 사용하게 됐다. 기능은 거의 다 되었으므로 간격 같은 것도 맞추고 로딩 바 등을 넣어서 사용성을 개선하고 무료 아이콘을 찾아다가 넣었다. 처음 승인 메일이 올 때 1월까지만 스탠다드 요금을 받고 2월부터를 Primium 요금을 받는다고 해서 2월에는 열릴 줄 알았는데 영 안 열려서 더 시간 지나기 전에 저장소를 공개로 바꾸고 오픈을 했다.

누군가 구글에 요청해서 승인을 받는다면 사용할 수 있을 것이다. 참고로 구글 클라우드에 가입하면 60일 동안 $300 크레딧을 주는데 덕분에 무료로 잘 썼다. 맘 놓고 주변에도 뿌리고 실컷 테스트했는데 20달러 정도밖에 안 나가서 댓글 읽을 때 드는 시간을 생각하면 나로서는 충분히 지급할 정도의 금액이다.

2017/02/22 09:00 2017/02/22 09:00

기술 뉴스 #72 : 17-02-15

웹개발 관련

  • JavaScript Start-up Performance : 크롬 쪽 관점에서 쓴 글이긴 하지만 JavaScript의 초기 구동에 드는 시간을 분석하는 방법과 V8에서 어떻게 개선하고 있고 현재 개선하려고 가능한 방법을 설명한 글이다. JavaScript 파일의 로딩 시간 뿐 아니라 파싱과 컴파일 시간이 중요해진 점을 강조하고 이 시간을 정확히 측정하는 방법과 그동안 V8에서 이 시간을 줄이기 위해서 도입한 기술 등을 설명해서 Code caching, Script Streaming, 사전 컴파일된 JS 코드 등의 방법을 설명하고 있다.(영어)
  • What’s in an AMP URL? : Google에서 AMP 페이지를 볼 때 Original URL, AMP Cache URL, Google AMP Viewer URL 세 가지가 있는데 각 URL이 어떤 의미가 있고 왜 여러 가지 URL을 만들게 되었는지 기술적인 배경을 설명하고 있다. 구현하거나 사용할 때 꼭 인지하고 있어야 하는 부분은 아니지만, 배경지식으로 알고 있으면 좋을 것 같다.(영어)
  • "구글, 'https' 채택 안한 누리집에 안전하지 않은 곳 '낙인'" 기사에 대한 의견 : 크롬 최신 버전에서 HTTPS를 지원하지 않는다고 "안전하지 않음"이라고 표시되는 게 마치 부당하다는 듯 나온 기사에 강성훈 님이 기자한테 해당 기사에 대한 반박내용을 적은 글이다. 해당 글에 왜 HTTPS로 가는 게 중요한지가 배경부터 해서 아주 잘 나와 있고 나도 여기에 아주 공감하고 있다. 이후 네이버나 다음이 HTTPS로 가지 않은 이유가 충분히 있었지만 크롬 때문에 어쩔 수 없이 갔다는 기사도 보았는데 헛소리라고 밖에 보이지 않는다.(한국어)

그 밖의 프로그래밍 관련

  • 리액티브 프로그래밍 대 리액티브 시스템 : Lightbend(구 TypeSafe)의 CTO인 Jonas Bonér와 Viktor Klang가 작성한 Reactive Programming versus Reactive Systems의 번역 글이다. 길지만 읽고 싶었는데 번역해 주신 분이 있어서 쉽게 읽었다. 요즘 화두인 리액티브에서 리액티브 프로그래밍과 리액티브 시스템이 어떻게 다른지 개념을 설명하고 제대로 된 시스템을 만들기 위해서는 둘 다 필요한데 리엑티브 프로그래밍과 리액티브 시스템이 어떻게 상호 보완적인 역할을 하고 있는지 설명하고 있다. 리액티브 프로그래밍은 수명이 짧은 데이터 흐름 체인을 통해 연산하는 데 중점을 둔다. 이것은 주로 이벤트 기반이다. 반면 리액티브 시스템은 (메시징이라고도 하는) 메시지 기반으로 분산 시스템에서 통신과 조정을 통한 복원성과 탄력성에 중점을 둔다.(한국어)
  • Adventures with NPM or: How I Learned to Stop Shrinkwrapping and Love Yarn : npm에서 하위 의존성을 모두 고정하는 shrinkwrap의 문제점을 설명하고 yarn이 제공하는 yarn.lock 파일이 이 문제를 다 해결하고 속도도 좋다고 설명하고 있다. 아직 yarn으로 갈아타지 않았는데 이 글을 보니까 yarn으로 갈아타고 싶다.(영어)
  • Introducing Cloud Spanner: a global database service for mission-critical applications : 구글 클라우드 플랫폼에서 새로 공개한 새로운 데이터베이스로 ACID 트랜잭션을 지원하면서 NoSQL처럼 고가용성과 확장을 지원한다고 한다. 이렇게 보면 부족한 부분이 없어 보일 정도로 CAP 이론이 깨진 건가 싶지만 CAP 이론이 깨진 것은 아니라고 한다.(영어)
  • EVOLVING DISTRIBUTED TRACING AT UBER ENGINEERING : Uber의 아키텍처가 모노리스에서 마이크로서비스 아키텍처로 넘어가면서 기존에 모노리스용으로 만들었던 Merckx를 더는 사용할 수 없게 되고 RPC를 이용한 TChannel을 개발해서 분산추적을 위한 기반을 만든 후 현재의 Jaeger라는 프로젝트를 완성하기까지의 과정을 설명한 글이다.(영어)
  • 이벤트 소싱(Event Sourcing) 소개 : CQRS와 함께 최근에 많이 듣게 되는 이벤트 소싱을 이해하기 위해 이벤트 소싱이 전통적인 방식과 달리 데이터의 상태를 어떻게 관리하는지를 설명한 글이다. 흥미로우면서도 다 이해되진 않지만 이렇게 보다 보면 언젠가 더 이해가 될 때가 올 거라고 생각한다.(한국어)
  • 2/1/17 GitLab.com Database Incident(한글번역) : GitLab.com에서 데이터베이스 장애가 발생하면서 그 복구 과정을 실시간으로 리포팅을 했다. 심지어 YouTube로 복구하는 과정을 라이브로 스트리밍했다.(이는 실수로 알려졌지만, 그 이후에도 계속 방송을 했다) 꽤 심각한 장애 처리 과정을 실시간으로 공유하는 게 꽤 재미있다. 결국 데이터베이스 복구는 실패한 거로 알고 있다. 지금은 GitLab에서 올린 공식 포스트모템도 올라와 있다.(한국어)
  • 중급 파이썬: 파이썬 팁들 : Intermediate Python라는 책을 전체 번역한 온라인 문서다.(한국어)
  • flake8-import-order-spoqa : 스포카에서 Python의 코드 스타일 표준인 PEP8을 맞추는 flake8을 사용하면서 import의 관례를 맞추려고 import-order를 만들어서 사용하다가 최근에 표준도구인 flake8-import-order에서 스포카용 커스텀 import 규칙을 적용할 수 있는 flake8-import-order를 만든 과정을 설명한 글이다.(한국어)
  • AWS KMS를 이용한 암호화 API 구축하기 : AWS가 제공하는 KMS를 이용해서 데이터 암호화를 구현하는 방법을 설명하는 글이다. 여기서는 Lambda와 API Gateway로 API로 만들어서 암복호화를 사용할 수 있도록 구현했다.(한국어)
  • Open Source Guides : GitHub에서 오픈소스 프로젝트에 기여하는 방법과 오픈소스 프로젝트를 운영하는 방법 등에 대한 가이드를 별도의 웹사이트로 제공하기 시작했다.(영어)

볼만한 링크

IT 업계 뉴스

프로젝트

  • Lottie : Airbnb에서 공개한 오픈소스 프로젝트로 After Effect의 애니메이션을 iOS, Android, React Native에서 사용할 수 있게 해주는 라이브러리. 소개 글은 여기서 볼 수 있다.
  • GVFS : Microsoft에서 270GB에 350만개의 파일이 있는 윈도우의 코드 베이스를 git으로 관리하기 위해 일부 파일만 다운로드 받고 파일을 사용할 때 동적으로 파일을 다운받은 GVFS를 오픈소스로 공개했다. IDE 등에서 다른 변경은 필요 없지만 GVFS를 쓰기 위해서 MS에서 커스터마이징한 git을 써야 하는 것으로 보인다. 자세한 내용은 공지에 나와 있고 윈도우 프로젝트를 clone할 때 12시간 이상 걸리고 checkout에만 3시간 걸렸지만 GVFS로 수분/수십 분 정도 내에 할 수 있게 되었다고 한다.
  • Captain : 메뉴바에서 Docker 컨테이너를 관리할 수 있는 macOS 애플리케이션.
  • Traefik : 마이크로서비스 배포를 쉽게 하는 리버스 프락시 서버 및 로드밸런서로 Docker, Swarm, Kubernetes, Marathon, Mesos, Consul 등을 지원한다.
  • Portainer : Docker 호스트와 Swarm 클러스터의 관리 UI 프로젝트.

버전 업데이트

2017/02/15 23:45 2017/02/15 23:45