다국어 지원(i18n, Internationalization)은 필요할 때 나중에 붙이면 된다고 생각하기 쉽습니다. 하지만 i18n 을 사후 도입할 때 진짜 비싸지는 영역은 번역도 코드 리팩토링도 아니라 — URL 구조와 그 위에 쌓여 있던 SEO 자산입니다.
한국어 단독으로 출발한 서비스에 영어를 붙이려는 시점이 오면, 검색 인덱스·외부 링크·백링크가 한꺼번에 깨지고 회복하는 데 한 해 단위의 시간이 걸리는 일이 흔합니다. 하드코딩된 문자열·레이아웃·날짜 포맷 같은 코드 측 비용은 그 다음 순위입니다. 글로벌 진출 가능성이 조금이라도 있다면 처음부터 i18n 아키텍처와 URL 구조를 잡아두는 것이 가장 가벼운 길입니다.
URL 구조 — 나중에 바꾸면 SEO 자산이 통째로 날아간다
코드와 디자인보다 더 비싸지는 영역이 URL 구조입니다. 검색 엔진과 AI 크롤러는 언어별로 페이지를 색인하기 때문에, 다국어 서비스라면 보통 /ko/products, /en/products 처럼 언어 코드가 경로에 들어가는 구조 또는 ko.example.com, en.example.com 같은 서브도메인 구조 중 하나로 가야 합니다. 이 구조 자체가 SEO 자산입니다 — 시간이 지나면서 외부 링크, 검색 순위, 캐시된 인덱스, 백링크가 모두 그 URL 위에 누적되기 때문입니다.
그런데 한국어 단독으로 출발했을 때 보통 /products, /pricing 처럼 언어 코드 없는 URL 을 쓰고, 나중에 영어 버전을 붙이려는 시점에 두 가지 길 모두 비싼 결정을 강요합니다. 한국어를 /ko/ 아래로 옮기면 기존에 외부에서 걸려 있던 링크와 검색 인덱스가 한꺼번에 깨집니다 — 영구 리다이렉트(301)를 깔아도 검색 엔진이 새 URL 을 학습하기까지 수개월이 걸리고 그 사이 트래픽이 빠집니다. 한국어는 그대로 두고 영어만 /en/ 아래에 두면 두 언어가 서로 다른 규칙으로 살게 되어 hreflang 태그·canonical URL·사이트맵을 구분 처리하는 코드가 곳곳에 누더기처럼 끼어 들어갑니다.
처음부터 /ko/ 와 /en/ 의 자리만 잡아두면 — 영어가 아직 없어도 — 이런 비용이 아예 발생하지 않습니다. 출시 시점에는 /ko/ 만 활성화돼 있고 /en/ 은 빈자리로 남아 있어도, 구조 자체가 갖춰져 있으면 나중에 영어를 붙일 때 URL 도, 검색 인덱스도, 외부 링크도 그대로 살아 있습니다. i18n 의 코드·디자인 비용은 글로벌 진출을 안 하면 쓰지 않을 수도 있는 보험이지만, URL 구조 비용은 한 번 잘못 깔면 되돌리는 데만 한 해를 쓰게 되는 영구 자산입니다.
SEO 가 망가지면 잃는 것 — 기회비용은 트래픽만이 아니다
URL 구조를 사후에 바꾸는 비용이 무거운 이유는 단순히 트래픽이 일시적으로 빠지기 때문만이 아닙니다. 검색 인덱스가 새 URL 을 학습하는 수개월 동안 검색 유입이 끊기고, 그 사이 경쟁사가 같은 검색어 자리를 차지합니다. 한 번 다른 사이트에 내준 키워드는 인덱스가 회복된 뒤에도 자동으로 1순위로 돌아오지 않습니다. 빠진 트래픽은 곧 잃은 매출·리드이고, 빼앗긴 자리는 그 뒤로도 계속 비용을 만듭니다.
해외 진출 시점에 가서 더 분명해지는 비용도 있습니다. 한국어 단독으로 운영해 온 시간 동안 쌓인 SEO 자산은 영어권으로 그대로 옮겨지지 않기 때문에, /en/ 자리를 늦게 만들수록 영어권 검색 권위와 백링크는 그 시점에 0 부터 다시 누적해야 합니다. 거기에 ChatGPT·Perplexity·Gemini 같은 AI 검색·답변 엔진은 언어별 구조가 정리된 사이트를 더 잘 인용하기 때문에, 구조를 늦게 잡으면 이 도구들이 사이트를 학습하는 한 라운드를 통째로 놓치는 일이 발생합니다.
세일즈·제휴 측면에서도 같은 비용이 따라옵니다. 해외 영업이나 글로벌 파트너십이 시작됐는데 자기 사이트의 영어 버전이 없으면, 첫 미팅에서 곧장 신뢰가 깎입니다. 광고·PR 비용을 들여 해외 트래픽을 끌어와도, 한국어로만 된 페이지에 도착한 사용자는 즉시 이탈하기 때문에 그 광고비는 그대로 매몰되고, "글로벌도 가능한 회사" 라는 인상을 만들기는 더욱 어려워집니다.
이 모든 기회비용 위에 코드 레벨 비용이 부수적으로 더해집니다. 컴포넌트 코드 곳곳에 박힌 수천 개의 하드코딩 문자열을 번역 키로 빼내는 작업, 한국어에서 영어로 옮길 때 30~50% 늘어나는 텍스트가 깨뜨리는 고정 폭 레이아웃, 날짜·통화·숫자 구분자·주소 체계의 지역별 차이, 아랍어·히브리어 같은 RTL 언어를 위한 CSS 논리적 속성(logical properties) 전환까지 — 어느 하나도 가볍지 않지만, 위의 기회비용에 비하면 측정 가능하고 일정 잡을 수 있는 비용이라는 점에서 차원이 다릅니다.
올바른 구현 방법
모든 사용자 노출 텍스트를 번역 JSON 파일로 분리하는 것이 기본입니다. 프레임워크별로 사실상 표준화된 i18n 라이브러리들이 있습니다 — Next.js 의 next-i18next·next-intl, Nuxt/Vue 의 vue-i18n, SvelteKit 의 paraglide-js, React 단독 환경의 react-intl, Astro·Remix 등도 각자 권장하는 솔루션이 있습니다. 어느 쪽이든 키-값 구조로 텍스트를 분리해 두면 번역 협업이 훨씬 수월해집니다.
저희는 번역 키를 기능 단위로 네임스페이스를 분리합니다. common.json 에는 "확인", "취소" 같은 공통 UI 요소를, errors.json 에는 에러 메시지를, billing.json·orders.json·account.json 같은 도메인 단위 파일에는 해당 영역의 용어를 모으는 식입니다 (예: billing 에는 "결제 수단"·"환불 정책", orders 에는 "주문 내역"·"배송 상태").
이렇게 하면 특정 페이지에서 필요한 번역만 동적으로 로딩할 수 있어 초기 번들 크기도 최적화됩니다.
번역 협업 워크플로 — 스프레드시트에서 JSON 까지
한 가지 짚어둘 점은, 이 JSON 파일을 사람이 직접 손으로 수정하는 게 아니라는 것입니다. 저희는 번역 작업을 한 장의 스프레드시트(Google Sheets) 위에서 관리합니다. 한 행이 하나의 번역 키이고 각 언어가 열로 들어가는 단순한 구조입니다. 클라이언트, 번역 담당자(요즘은 AI 도구로 1차 번역을 돌리고 사람이 검수하는 흐름이 흔합니다), 개발팀이 같은 시트 위에서 동시에 작업할 수 있고, 버전 기록·코멘트·승인 과정까지 시트 자체의 협업 기능을 그대로 활용할 수 있기 때문입니다.
실제 빌드 시점에는 이 시트를 파싱하는 스크립트가 돌면서 common.json·errors.json·billing.json 같은 네임스페이스별 JSON 파일을 자동으로 만들어냅니다. JSON 을 사람이 직접 편집하면 콤마 누락 같은 자잘한 syntax 오류가 빈번하고 비개발자는 손대기 어렵지만, 시트 + 파싱 흐름은 비개발자도 자기 영역만 안전하게 만질 수 있고 빌드는 항상 유효한 JSON 위에서 돌아갑니다.
이 구조의 또 다른 장점은 검수 방식이 바뀌어도 코드가 영향을 받지 않는다는 점입니다. AI 번역기 → 사람 검수, 클라이언트 마케팅팀 직접 편집, 외부 번역 에이전시 위탁처럼 워크플로 자체가 시트 위에서 자유롭게 바뀌어도 최종 결과물은 늘 같은 형태의 JSON 으로 컴파일됩니다.
언어별 폰트와 변형 — 잘못 매칭하면 글자부터 어긋난다
다국어 콘텐츠에서 의외로 자주 놓치는 부분이 언어별로 폰트(글자체) 가 달라야 한다는 점입니다. 가장 민감한 케이스는 중국어로, 본토와 싱가포르가 쓰는 간체(zh-CN) 와 대만·홍콩·마카오가 쓰는 번체(zh-TW·zh-HK) 는 같은 한자라도 글자 모양 자체가 다릅니다. zh-CN 사용자에게 번체 글리프가, 또는 그 반대 상황이 노출되면 단순한 "오타" 수준이 아니라 "내 언어가 아니다" 라는 정치·문화적 감정 반감으로 곧장 이어집니다.
일본어도 마찬가지입니다. 일본은 자체적으로 정리한 신자체(新字体) 를 쓰는데, 같은 한자라도 중국 간체·번체와 형태가 다릅니다 (예: 賣·卖·売). 중국어용 글리프를 그대로 일본 사용자에게 노출하면 일본인 입장에서는 "외국 글자" 처럼 보입니다. 한 페이지 안에서 한글·일본어·중국어가 섞여 있을 때 어느 한 언어의 글리프로 다른 언어를 잘못 그려버리는 사고가 의외로 자주 일어납니다.
방지하는 방법은 두 가지를 같이 가는 것입니다. 첫째, 한·중·일 공용 폰트(noto-sans-cjk 같은) 를 사용하되 HTML lang 속성과 CSS lang 셀렉터로 사용자 로케일을 명시적으로 지정해, 브라우저·OS 가 폰트 안의 올바른 글리프 세트를 고르게 합니다. 둘째, 처음부터 zh-CN, zh-TW, ja, ko 를 서로 다른 언어로 다룹니다 — 번역 키도, 폰트 설정도, 검수도 모두 분리합니다.
같은 갈래가 다른 언어에도 있습니다. 영어는 미국식(en-US)·영국식(en-GB)·호주식(en-AU) 로 철자(color/colour)와 단어 선택이 갈리고, 스페인어는 스페인 본토(es-ES) 와 라틴아메리카(es-419·es-MX) 의 어휘·격식이 크게 다르며, 포르투갈어도 브라질(pt-BR) 과 포르투갈(pt-PT) 차이가 작지 않습니다. i18n 라이브러리는 BCP 47 지역 코드 단위로 번역을 분리할 수 있도록 설계되어 있으니, 처음부터 ko 가 아니라 ko-KR, en 이 아니라 en-US·en-GB 같은 식으로 구분 가능한 구조로 잡아두는 것이 안전합니다.
실무 적용 시에는 중국어를 한 묶음이 아니라 처음부터 zh-CN·zh-TW 별개 트랙으로 두고, 일본어·한국어도 마찬가지로 분리해 두는 것이 안전합니다. 어느 한쪽 시장을 나중에 추가하더라도 키 구조와 폰트 설정을 다시 흔들 일이 없습니다.
흔한 실수들
번역된 문자열을 프로그래밍적으로 조합하는 것이 가장 위험한 실수입니다. "총 " + count + "건의 결과"라는 코드를 영어로 "Total " + count + " results"로 단순 치환하면 동작하지만, 일본어에서는 "合計" + count + "件の結果"가 되어야 하고 단어 순서가 완전히 달라지는 언어에서는 의미가 깨집니다.
ICU 메시지 포맷을 사용해 "총 {count}건의 결과"처럼 플레이스홀더 방식으로 작성해야 합니다. 복수형 처리도 놓치기 쉬운데, 영어는 단수/복수 두 가지이지만 러시아어는 3가지, 아랍어는 6가지 복수형이 있습니다.
또한 사용자가 작성한 게시글이나 댓글 같은 UGC(User Generated Content)는 시스템 번역 대상이 아니라는 점을 아키텍처 설계 시 명확히 구분해야 합니다.
이미지나 아이콘에 텍스트가 직접 포함된 경우도 간과하기 쉬운 문제입니다. 배너 이미지에 "지금 가입하세요"라는 텍스트가 이미지 자체에 렌더링되어 있으면 언어별로 이미지를 별도 제작해야 합니다. 처음부터 텍스트는 HTML 오버레이로 배치하고, 이미지는 텍스트 없는 범용 디자인을 사용하는 것이 올바른 접근입니다.
프로덕트 메이커의 i18n 원칙
저희는 글로벌 진출이 1~2년 안의 로드맵에 들어 있다면, 한국어 단독으로 출시하는 프로젝트라도 i18n 아키텍처와 언어별 URL 구조를 처음부터 잡아둡니다. 출시 시점에는 다른 언어 파일이 비어 있고 /en/ 같은 경로도 빈자리로 남아 있어도, 나중에 영어를 붙일 때 검색 인덱스·외부 링크가 그대로 살아 있고 코드 곳곳을 다시 들춰낼 일도 없습니다.
초기 단계에서 작은 추가 비용을 받아들이는 대신, 사후 도입했을 때 따라오는 SEO 자산 손실과 대규모 리팩토링 비용을 모두 피하는 선택입니다. 글로벌 가능성이 0%에 가깝다고 단언할 수 있는 서비스가 아니라면, 처음부터 자리만 잡아두는 쪽이 거의 항상 더 가볍습니다.