본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Frontend

다국어 페이지: lang·hreflang과 국제화

작성 2026. 10. 21.추가
목차6개 항목
출처와 참고

Front End Interview Handbook의 「HTML 질문」 중 다국어 페이지 문항 두 개를 바탕으로, W3C 국제화 문서와 Google 검색 센터 문서로 확인해 재구성한 노트다. 인용 부호·방향 격리·루비 요소는 web.dev의 「Learn HTML」 강좌를 바탕으로 보강했다. 시간대·통화·법인 같은 시스템 쪽 국제화는 ByteByteGo의 System Design 101 가이드 「How do we design a system for internationalization?」를 바탕으로 보탰다. 대상 지역 네트워크와 문화 차이는 「Frontend_TIL: 다국어 페이지를 만들때 고려해야할 사항」을 바탕으로 보강했다.

두 가지 일: 국제화와 현지화

용어뜻예
국제화(i18n, internationalization)어떤 언어·지역에도 맞출 수 있게 코드와 디자인을 준비하는 일문자열을 코드에서 빼기, 날짜를 로캘별로 포맷하기
현지화(l10n, localization)특정 언어·지역에 맞게 실제로 옮기는 일번역, 통화·단위·이미지 교체

i18n을 먼저 해 두지 않으면 언어를 하나 늘릴 때마다 화면을 다시 만든다.

언어별 페이지를 내보내는 구조

flowchart TB
  n1["요청<br/>(Accept-Language: ko-KR,ko;q=0.9,en;q=0.8)"] --> n2["서버·프레임워크가 지원 언어 중 가장 맞는 것을 고름<br/>(첫 방문 제안용)"]
  n2 --> n3["언어별 URL로 응답: /ko/…, /en/…"]
  n3 --> n4["템플릿 + 언어별 메시지 파일(JSON·YAML·PO 등)로 HTML 생성"]
  n4 --> n5["#lt;html lang=#quot;ko#quot;#gt; + hreflang으로 다른 언어판 연결"]
결정권장
URL언어마다 다른 URL(/ko/, ko.example.com, example.kr). 쿠키나 헤더로만 언어를 바꾸면 같은 URL에 여러 언어가 섞여 검색 엔진이 한 언어판만 본다
언어 고르기Accept-Language는 첫 제안에만 쓴다. 자동으로 다른 언어판으로 넘기지 않고, 어디서든 언어를 바꿀 수 있는 선택기를 둔다. 브라우저 언어와 사용자가 원하는 언어는 다를 수 있다
선택기언어 이름을 그 언어로 쓴다(「English」, 「日本語」). 국기는 나라를 뜻하지 언어를 뜻하지 않으므로 쓰지 않는다
같은 페이지의 섞인 언어한 페이지는 한 언어가 기본이다. 일부 다른 언어 문장에는 그 요소에 lang을 따로 준다

Next.js 같은 프레임워크는 로캘별 라우팅을 지원하고, Accept-Language 감지는 proxy.ts(Next.js 16 이전 이름은 middleware)에서 한다. 메시지 관리는 i18next·FormatJS 같은 라이브러리가 맡는다.

HTML로 언어 표시하기

<html lang="ko" dir="ltr">
<head>
  <meta charset="utf-8">
  <link rel="alternate" hreflang="ko" href="https://example.com/ko/">
  <link rel="alternate" hreflang="en" href="https://example.com/en/">
  <link rel="alternate" hreflang="x-default" href="https://example.com/">
</head>
<body>
  <p>이 문서의 원제는 <span lang="en">Open Web Platform</span>이다.</p>
  <p>제품명 <span translate="no">riverlog</span></p>
표시하는 일
lang요소 내용의 언어(BCP 47 태그: ko, en-GB, zh-Hant). 스크린 리더 발음, 맞춤법 검사, 하이픈 나누기, 글꼴 선택, 번역 제안, CSS :lang()이 이 값을 쓴다. <html>에 꼭 둔다
dir글 방향. ltr, rtl(아랍어·히브리어), 사용자 입력처럼 모를 때 auto. CSS direction이 아니라 HTML 속성으로 준다
translate="no"브랜드명·코드처럼 자동 번역에서 빼야 할 부분
hreflang같은 내용의 다른 언어·지역판을 검색 엔진에 알린다
Content-Language 헤더대상 독자의 언어 정보일 뿐, 문서 언어 선언은 lang이 맡는다. 같은 뜻의 <meta http-equiv="content-language">도 쓰지 않는다
<q>인용 부호를 lang에 맞게 그린다(영어 “ ”, 프랑스어 « »). 다른 언어 인용에는 lang을 단다
<bdi> / <bdo dir>방향을 모르는 글(사용자 이름)을 주변과 격리 / 방향을 강제로 뒤집기
<ruby>·<rt>한자·가나 위의 읽는 법 주석

hreflang의 규칙:

  • <link>, HTTP Link 헤더, XML 사이트맵 중 하나로 넣는다. 셋은 효과가 같다.
  • 양방향이어야 한다. 각 언어판이 자기 자신과 다른 모든 판을 나열한다. 한쪽만 가리키면 무시된다.
  • 값은 ISO 639-1 언어 코드에 선택적으로 지역 코드를 붙인다(en, en-GB). 지역 코드만 쓸 수는 없다.
  • x-default는 맞는 언어가 없을 때 보낼 페이지다. 언어 선택 페이지나 자동 분기하는 첫 화면에 쓴다.
  • Google은 페이지 언어를 lang이나 hreflang이 아니라 보이는 본문으로 판단한다. 번역은 본문까지 실제로 옮겨야 한다.

CSS에서는 :lang(fr)이나 [lang|="fr"]로 언어별 스타일을 준다. :lang()은 조상에게서 물려받은 언어까지 맞춘다. 검색 쪽 다른 메타 태그와 canonical은 SEO 기초에, head의 <link rel="alternate">는 문서 head와 메타데이터에 있다.

설계·개발할 때 주의할 것

주의이유와 방법
문자열을 이어 붙이지 않는다"오늘은 " + date + "입니다"는 어순이 다른 언어에서 깨진다. 자리 표시자가 있는 메시지 전체를 번역 단위로 둔다("오늘은 {date}입니다", "Today is {date}"). 복수형은 ICU MessageFormat이나 Intl.PluralRules로 다룬다
날짜·숫자·통화는 로캘로 포맷한다같은 날짜도 5/31/2026(미국), 31/05/2026(영국), 2026. 5. 31.(한국)으로 쓴다. Intl.DateTimeFormat, Intl.NumberFormat, Intl.RelativeTimeFormat을 쓴다(→ 날짜 라이브러리)
텍스트 길이가 달라진다영어보다 독일어·핀란드어가 길고, 한·중·일어는 짧지만 글자가 높다. 버튼·제목·레이블은 고정 폭 대신 늘어날 공간을 두고, 넘침을 줄임표·줄바꿈 중 무엇으로 처리할지 정한다. 본문처럼 흐르는 글은 문제가 적다
글 방향이 바뀐다RTL 언어에서는 레이아웃 전체가 뒤집힌다. margin-left 대신 margin-inline-start 같은 논리적 속성을 쓰면 dir만 바꿔도 맞는다. 세로쓰기는 writing-mode로 한다
이미지에 글자를 넣지 않는다언어마다 이미지를 따로 만들어야 하고 검색·번역·스크린 리더가 읽지 못한다. 글자는 텍스트와 웹 폰트로 올린다
글꼴과 글자 크기언어마다 필요한 글꼴과 알맞은 크기가 다르다. :lang()으로 나눈다(→ 반응형 디자인과 다국어 타이포그래피)
색·아이콘·이름의 문화 차이색과 손짓 아이콘의 의미는 문화마다 다르다. 이름·주소·전화번호 입력 폼은 한 형식을 강제하지 않는다
인코딩모든 문서와 데이터는 UTF-8로 저장·전송한다
대상 지역의 네트워크나라마다 회선 속도와 데이터 요금이 크게 다르다. 본사 사무실 회선이 아니라 대상 지역의 느린 망을 기준으로 이미지·폰트 용량을 줄이고, 지역 가까이 배포한 CDN으로 내보낸다(→ 웹폰트 성능, 이미지 CDN)

시스템 쪽 국제화: 텍스트 밖의 것들

해외 시장에 서비스를 내면 화면 번역만으로 끝나지 않는다. 백엔드와 사업 구조도 나라별 차이를 받아야 한다.

영역설계
문구 관리모든 문구를 코드 밖 번역 시스템(리소스 파일, 번역 관리 도구)에 둔다. 코드에 안내 문구를 직접 쓰지 않는다. 서버가 만드는 이메일·알림·오류 메시지도 같은 원칙이다
시간대저장과 표시를 분리한다. DB와 백엔드는 UTC 타임스탬프로 저장·계산하고, 화면에서만 사용자 시간대로 바꿔 보여 준다. "매일 오전 9시" 같은 예약은 UTC가 아니라 사용자 시간대 기준으로 저장한다
통화표시 통화와 정산 통화를 나눠 정의한다. 가격 표시는 현지 통화로, 정산은 법인의 기준 통화로 한다. 환율을 조회·고정하는 환전(FX) 서비스를 두고, 금액은 부동소수점이 아니라 최소 단위 정수나 고정 소수로 다룬다
법인과 회계나라별로 법인을 따로 세우면 세금·회계 기준이 다르다. 여러 장부 방식과 법인 간 자금 관리를 지원해야 한다
지역별 업무 규칙결제 수단, 주소 형식, 영업일, 규제가 나라마다 다르다. 이런 규칙을 코드 곳곳의 if (country == ...)로 흩뜨리지 말고 지역별 설정·전략으로 분리한다

정리

  • 언어마다 다른 URL을 주고, <html lang>과 hreflang으로 표시한다.
  • Accept-Language는 제안에만 쓰고, 언어 선택은 사용자가 언제든 바꿀 수 있게 둔다.
  • 번역 단위는 문장 전체다. 문자열을 조각내 이어 붙이지 않는다.
  • 날짜·숫자는 Intl, 방향은 dir과 논리적 속성, 길이는 늘어날 여백으로 대비한다.

관련 노트: SEO 기초, 문서 head와 메타데이터, 텍스트 의미 요소와 문자 참조, HTML 기초와 HTML5, 쓰기 모드와 논리적 속성, 반응형 디자인과 다국어 타이포그래피, 날짜 라이브러리: date-fns와 Day.js

원본 기록8

댓글

GitHub 계정으로 댓글을 남길 수 있어요.