블로그를 한/영 이중 언어로 바꾸며 겪은 시행착오 8가지
Jekyll(Chirpy) 블로그를 영어 기본 + 한국어 자동 선택 구조로 개편하면서 밟은 함정들 — 예약 게시, 테마 오버라이드, 서비스워커 좀비, 그리고 95편 배치 번역 운영기
Info
How I turned a Korean-only Jekyll (Chirpy) blog into a bilingual site — English by default, Korean auto-selected for Korean browsers — and the eight traps I hit along the way. The English version of this post covers the same lessons.
이 블로그는 원래 한국어 전용이었습니다. 앱을 글로벌로 내다 보니 블로그도 영어로 검색돼야 할 이유가 생겼고, 이틀에 걸쳐 글 95편을 번역하고, 영어를 기본으로, 한국 방문자만 한국어를 보는 구조로 개편했습니다. 지금 보고 계신 이 사이트가 그 결과물입니다.
결론부터: 번역 자체보다 정적 사이트 생성기와 테마의 암묵적 가정들이 훨씬 어려웠습니다. 같은 길을 가실 분을 위해 밟은 함정을 순서대로 남깁니다.
1. 한/영 병기는 SEO에 최악이다
처음엔 한 글에 한국어와 영어를 나란히 쓰는 방안을 검토했습니다. 결론은 명확한 ❌. 검색엔진은 URL 하나당 언어 하나를 가정합니다. 병기하면 페이지 언어 판정이 흔들리고, 키워드 밀도가 반으로 떨어지고, <title>과 <html lang>이 하나뿐이라 영문 검색어용 제목을 쓸 수 없습니다.
정답은 언어별 URL 분리 + 상호 hreflang입니다. 한국어는 기존 URL(/posts/…)을 그대로 두고, 영문판을 /en/posts/…에 두고, 두 문서가 서로를 <link rel="alternate" hreflang>으로 가리키게 했습니다. 기존에 색인된 한국어 URL을 하나도 깨지 않는 게 핵심입니다.
2. Accept-Language 자동 리다이렉트는 크롤러를 굶긴다
“브라우저 언어를 보고 자동으로 보내주면 되겠네”는 반쯤 함정입니다. Googlebot은 대부분 미국 IP + 영어 헤더로 크롤링하므로, 서버/스크립트가 무조건 리다이렉트하면 크롤러가 한쪽 언어를 영영 못 봅니다.
절충안: hreflang은 그대로 두고, 클라이언트 JS로 자동 선택하되 ①크롤러 UA는 제외 ②사용자가 스위처로 고른 언어는 localStorage에 기억해 자동보다 우선 ③이동은 location.replace(뒤로가기 오염 방지). 색인은 hreflang이, 사람은 JS가 맡는 분업입니다.
3. Jekyll 컬렉션은 예약 게시를 ‘반쯤만’ 지원한다
이 블로그는 미래 날짜 + 매일 cron 빌드로 예약 게시를 씁니다. _posts는 미래 글을 읽는 단계에서 걸러주지만, 영문판을 담은 컬렉션은 미래 문서가 site.en_posts에 그대로 남습니다. 페이지만 생성되지 않죠. 목록·사이트맵·검색 인덱스가 아직 없는 페이지를 링크하게 되고, htmlproofer가 빌드를 죽입니다.
컬렉션을 훑는 모든 곳에 날짜 필터가 필요합니다:
1
{% assign live = site.en_posts | where_exp: 'p', 'p.date <= site.time' %}
덤으로: 본문에서 같은 날 뒤 시간대에 공개될 글을 링크한다면, 공개 창(이 블로그는 09:00~10:40) 중간에 push하지 마세요. 링크 대상이 아직 없어 빌드가 실패합니다.
4. 테마는 레이아웃 ‘이름’으로 분기한다
가장 오래 헤맨 함정입니다. 홈을 언어 공용으로 다시 만들면서 레이아웃 이름을 lang-home으로 지었더니, 홈에서만 이미지 위에 회색 그라데이션이 남고 카드 클릭 영역이 이상해졌습니다.
원인: Chirpy는 page.layout == 'home'이라는 문자열 비교로 네 곳(JS 번들 선택, 이미지 래핑 방식, 상단바, <title>)을 분기합니다. 이름이 다르니 홈 전용 JS가 안 실려 로딩 플레이스홀더가 영영 남고, 콘텐츠용 이미지 래퍼(팝업 앵커)가 카드 링크 안에 중첩돼 파서가 <a>를 쪼갰습니다.
교훈: 커스텀 레이아웃 이름을 만들지 말고 테마 레이아웃을 같은 이름으로 오버라이드 하세요. 이름이 곧 계약입니다.
5. HTML 압축기가 //-주석으로 스크립트를 통째로 죽인다
자동 언어 선택 스크립트가 배포 후 전혀 동작하지 않았습니다. 콘솔에는 SyntaxError: Unexpected end of input 하나뿐. 원인은 Chirpy의 HTML 압축이 모든 줄바꿈을 제거하면서, 인라인 스크립트의 // 주석이 뒤에 붙은 코드를 전부 주석으로 삼켜버린 것이었습니다. 인라인 <script>에는 블록 주석(/* */)만 써야 합니다.
6. PWA를 끄면 서비스워커 좀비가 생긴다
개편 직후 “사이트가 느리고 화면이 깨진다”는 증상이 나왔는데, 새 코드 문제가 아니라 개편 전에 설치된 서비스워커가 옛 사이트를 캐시로 계속 서빙하고 있었습니다. 더 나쁜 건, PWA를 끄면서 sw.min.js가 사라지자 구 서비스워커가 갱신 경로를 잃고 영구 고착됐다는 점입니다.
해법은 같은 경로에 자폭 서비스워커를 두는 것입니다. 서비스워커 스크립트 요청은 서비스워커 캐시를 우회하므로, 다음 방문 때 반드시 새 버전이 설치되고 — 캐시 전체 삭제 → 자기 등록 해제 → 열린 탭 새로고침을 수행합니다.
7. jekyll-archives는 컬렉션을 모른다
카테고리/태그별 페이지(/categories/앱/ 같은)는 jekyll-archives가 site.posts에서만 만들어 줍니다. 영문 컬렉션은 대상이 아니죠. 처음엔 한 페이지에 전부 나열하는 식으로 때웠다가 “이상하다”는 피드백을 받고, 결국 번역 파이프라인이 카테고리·태그별 스텁 페이지를 자동 생성하도록 바꿨습니다(현재 카테고리 15 + 태그 72). 새 태그가 생기면 스텁도 따라 생기고, 사라진 태그의 스텁은 지워집니다. 수작업 0.
8. 95편 배치 번역은 ‘규격’보다 ‘결정적 후처리’가 지킨다
번역은 서브에이전트 여러 개로 병렬 처리했는데, 재밌는 현상이 있었습니다. 규격에 “구조를 1:1로 유지하라”와 “섹션명은 이 표대로 써라”를 함께 주면, 원문 제목이 표에 없을 때 배치마다 다른 판단을 합니다. 규격을 아무리 다듬어도 흔들리는 건 흔들립니다.
효과가 있었던 건 규격 강화가 아니라 기계적 검증과 후처리였습니다. 제목 개수·계층이 원문과 1:1인지 검사하는 스크립트를 하드 게이트로 두고, 날짜·경로·태그·링크 같은 기계적 항목은 번역가에게 맡기지 않고 후처리 도구가 원문 기준으로 덮어씁니다. 사람이든 AI든, 번역가는 산문만 만지게 하는 것이 정답이었습니다.
마무리
이틀간의 개편으로 지금 이 블로그는:
- 영문이 기본(루트
/), 한국어는/ko/와 기존/posts/…URL 그대로 - 브라우저 언어에 따라 자동 선택, 수동 선택은 기억
- 글 95편 × 2개 언어, hreflang 완비, 예약 게시는 두 언어 동시 공개
정적 사이트의 다국어화는 “번역”이 아니라 “시스템의 암묵적 가정 찾기”에 가까웠습니다. 이 글의 여덟 항목이 그 가정들의 목록입니다.