i18n — 한국어를 1순위로
i18n — 한국어를 1순위로
국제화 (internationalization · i18n) 는 앱이 여러 언어·지역에서 자연스럽게 동작하도록 만드는 작업입니다.
1. 용어와 표준
- i18n (internationalization) — 코드를 특정 언어/지역에 묶지 않게 만드는 일.
- l10n (localization) — 그 위에 특정 지역의 번역·포맷을 얹는 일.
- locale — 언어와 지역을 합친 식별자 (BCP 47 표준).
ko·ko-KR·en-US·zh-Hant-TW등.
표준:
- BCP 47 — 언어 태그 표준.
ko-KR. - CLDR (Unicode Common Locale Data Repository) — 각 locale 의 날짜·숫자·단위·복수 규칙 데이터. Unicode Consortium 이 관리.
- ICU MessageFormat — 메시지 안의 변수·복수·성별 처리를 위한 표기.
- Intl API — ECMAScript 표준 내장 객체.
Intl.NumberFormat·Intl.DateTimeFormat·Intl.RelativeTimeFormat·Intl.PluralRules·Intl.Collator·Intl.Segmenter(2021+).
브라우저·Node 가 Intl 을 가지고 있어서 실제로는 라이브러리가 Intl 위에 얇은 층을 두는 경우가 많습니다.
2. React 진영의 라이브러리
| 라이브러리 | 첫 릴리스 | 만든이 | 비고 |
|---|---|---|---|
| react-i18next | 2014 (i18next) | Jan Mühlemann | i18next (JS) 의 React 바인딩. 가장 넓게 채택. |
| react-intl (FormatJS) | 2014 | Yahoo (현 OpenJS Foundation) | ICU MessageFormat 1 급 지원. |
| LinguiJS | 2017 | Tomáš Roch | ICU + 매크로 + 컴파일된 카탈로그. |
| next-intl | 2022 | Jan Amann | Next.js App Router 특화. |
| Tolgee | 2021 | Tolgee | 인-컨텍스트 편집 강조. |
Next.js App Router 는 라우팅 자체가 i18n 을 가정하지 않기 때문에 next-intl 같은 도구가 자라났습니다. 이 도구는 [locale] 동적 세그먼트와 미들웨어를 묶어 locale 분기를 단순화합니다.
3. 메시지 카탈로그
흔한 두 형태입니다.
JSON / NS 분리 (i18next 스타일):
// public/locales/ko/common.json
{ "save": "저장", "cancel": "취소" }
import { useTranslation } from "react-i18next"
const { t } = useTranslation("common")
t("save") // "저장"
ICU MessageFormat (FormatJS / Lingui / next-intl):
{ "items": "{count, plural, =0 {항목 없음} other {#개의 항목}}" }
t("items", { count: 3 }) // "3개의 항목"
ICU 는 표준이고 도구 간 이식성이 좋습니다. 단순 키-값보다 학습 곡선이 있습니다.
4. 키 네이밍 관습
자주 쓰이는 두 갈래.
① 점 표기 (네임스페이스): auth.login.title · cart.item.removed
② 자연어 키: "이 항목을 정말 삭제할까요?" 자체를 키로 사용 (Lingui 매크로)
장단점이 갈립니다. 점 표기는 검색이 쉽고 충돌이 적지만 키만 보고 의미를 알기 어렵습니다. 자연어 키는 의미가 명확하지만 텍스트가 바뀌면 키도 바뀝니다 (빌드 도구가 보강).
5. 날짜·숫자 — Intl 활용
new Intl.NumberFormat("ko-KR", { style: "currency", currency: "KRW" })
.format(123456) // "₩123,456"
new Intl.DateTimeFormat("ko-KR", { dateStyle: "long" })
.format(new Date()) // "2026년 4월 25일"
new Intl.RelativeTimeFormat("ko", { numeric: "auto" })
.format(-1, "day") // "어제"
date-fns (2014) 와 dayjs (2018) 도 자체 locale 패키지를 가집니다. 새 코드에서는 Intl 만으로 충분한 경우가 늘었다는 평이 흔합니다.
6. 한국어 조사 처리
한국어는 명사 끝의 받침에 따라 조사가 달라집니다.
- 받침 있음: 책을 · 사람이 · 학교과 (→ "와")
- 받침 없음: 사과를 · 친구가 · 학교와
{name} 을(를) 삭제했습니다 같은 회피 표기는 자연스럽지 않습니다. 자주 쓰이는 길:
- 끝 글자 코드포인트 (
(ch - 0xAC00) % 28 !== 0) 로 받침 유무를 판정해 적절한 조사를 고르는 헬퍼. - 메시지를 다시 써서 조사를 회피 (예: "삭제 완료: {name}").
- 라이브러리: es-hangul · hangul-js.
7. 한국어 숫자·복수·콜레이션
한국어는 만 (萬) 단위로 자릿수를 묶습니다 ("12,345" 보다 "1만 2345" 가 자연스러울 때가 있습니다). 영문권 표준 천 단위 콤마와 충돌하기 때문에 UI 에 따라 선택합니다.
영어와 달리 한국어는 단/복수가 거의 구분되지 않습니다 (사과 1개 · 사과 3개 모두 같은 단어 형태). ICU 의 plural 카테고리가 한국어에서는 사실상 other 하나라는 점이 자주 헷갈리는 자리입니다.
콜레이션 (정렬) 은 가나다 순. Intl.Collator("ko-KR") 가 기본 처리합니다.
["하늘", "가을", "나무"].sort(new Intl.Collator("ko-KR").compare)
// ["가을", "나무", "하늘"]
8. 폰트와 IME
Pretendard (길형진, 2021) 가 한국어 UI 에서 사실상 표준 한 갈래로 자리잡았습니다 (가변 폰트 · OFL 라이선스). 한자·일본어 글리프가 섞이면 폰트 폴백을 잘 정의해야 누락이 안 보입니다. 행간 (line-height) 은 영어 기준 1.4 가 한국어에서는 답답할 수 있습니다. 1.6~1.8 이 자주 권장됩니다.
한글 입력 중에는 keydown 이 조합 단계에서 발생해 자동 검색·즉시 제출 같은 패턴이 의도와 다르게 동작합니다. compositionstart · compositionend 이벤트로 조합 중을 식별합니다.
const [composing, setComposing] = useState(false)
<input
onCompositionStart={() => setComposing(true)}
onCompositionEnd={() => setComposing(false)}
onKeyDown={(e) => {
if (composing) return
if (e.key === "Enter") submit()
}}
/>
9. next-intl 최소 예
// messages/ko.json
{ "Home": { "title": "안녕하세요", "items": "{count}개" } }
// app/[locale]/page.tsx
import { useTranslations } from "next-intl"
export default function Page() {
const t = useTranslations("Home")
return <h1>{t("title")}</h1>
}
10. 자주 걸리는 자리
하드코딩 누락 — UI 텍스트 일부가 코드에 직접 박히면 뒤늦게 발견됩니다. ESLint 룰 (react/jsx-no-literals) 또는 추출 도구로 보강합니다.
번역 키 일치 검증 — 다국어 추가 시 누락된 키가 런타임에서야 드러납니다. CI 에서 키 차집합 검사를 추가합니다.
시간대 — new Date(...) 는 클라이언트 시간대에 의존합니다. 서버 렌더와 클라이언트 렌더의 결과가 다르면 hydration mismatch. UTC 로 다루고 표시 시점에만 변환합니다.
숫자 0/1 분기를 영어 기준으로 — 1 item / 2 items 분기는 한국어에서 의미가 없습니다. ICU plural 의 =0 one other 를 한국어 카탈로그에서는 other 하나로 통일하는 편이 자연스럽습니다.
RTL 가능성 — 향후 아랍어·히브리어 추가 가능성이 있다면 CSS 의 logical property (margin-inline-start) · dir="rtl" 호환을 미리 봅니다.
하고픈 말
i18n 은 도입 시점이 늦어질수록 비용이 빠르게 자랍니다. 한국어 단일 앱이라도 키 분리는 처음부터 권장합니다. 조사 처리는 라이브러리보다 메시지 다시 쓰기가 가장 단순한 길입니다.
Next
- material3-tokens
- forms-zod
Unicode CLDR · ICU MessageFormat · BCP 47 · MDN Intl · react-i18next · FormatJS · LinguiJS · next-intl · Pretendard · es-hangul 을 참고합니다.