- next-intl 5.x 도입
- src/i18n/config.ts: LOCALES 상수, detectBrowserLocale, LOCALE_LABELS(국기/네이티브명)
- src/i18n/LocaleProvider.tsx: NextIntlClientProvider wrap + localStorage tasteby_locale 저장
- src/messages/{ko,en,ja,es}.json: 초기 30개 키 (header/actions/filter/restaurant/review 5 카테고리)
- src/components/LanguageSwitcher.tsx: 헤더용 드롭다운 (국기 + native, ARIA listbox, 44px 터치)
- providers.tsx: LocaleProvider로 AuthProvider 감싸기
- page.tsx 헤더에 LanguageSwitcher 배치
설계서: docs/design/352-i18n-skeleton/README.md (Approved)
언어 선택 근거:
- ko: 기본
- en: 글로벌 1순위
- ja: 일본 사용자 + 한국 음식 관광
- es: 5억 화자, 라틴아메리카 + 스페인 확장
미번역 키는 ko fallback. URL 라우팅(/en/)/SEO meta/사용자 콘텐츠 번역은 후속.
Refs: #352
6.0 KiB
6.0 KiB
설계서: i18n 뼈대 — ko/en/ja/es (#352)
상태: Approved 작성: [AI] Architect · 최종수정: 2026-06-15 추적성 — Redmine: #352 · 구현 파일:
frontend/package.json,frontend/next.config.ts,frontend/src/i18n/*(신규),frontend/src/messages/{ko,en,ja,es}.json(신규),frontend/src/components/LanguageSwitcher.tsx(신규) · 테스트: 수동 (언어 전환 + ko fallback)
1. 목적
장기적으로 영어/일본어/스페인어 시장으로 확장 가능하도록 i18n 뼈대 구축. 본 이슈는 뼈대만 — 약 30개 키 초기 번역.
2. 범위
- 포함
next-intl라이브러리 도입 (Next.js 16 App Router 권장).- 4개 로케일:
ko, en, ja, es. 기본ko, fallbackko. - 메시지 디렉토리
src/messages/{ko,en,ja,es}.json. - Provider (
<NextIntlClientProvider>) 루트 layout 적용. LanguageSwitcher컴포넌트 (헤더 우측).- 로케일 저장: localStorage
tasteby_locale. - 초기 번역 키 ~30개: 헤더(로그인/검색/메뉴) + 주요 액션(저장/취소/삭제/확인) + 페이지 제목.
- 제외
- URL 라우팅 i18n (
/ko/...). - SEO meta tags i18n.
- 식당명/리뷰 등 사용자 콘텐츠 번역.
- 어드민 페이지(운영자만 사용, 한국어 유지).
- 식당 카드/상세 시트 전체 키 추출 (점진).
- URL 라우팅 i18n (
3. 인수조건
npm i next-intl+ 4개 메시지 파일 생성.tasteby_locale이 localStorage에 있으면 사용, 없으면 브라우저 언어 감지(navigator.language) → 매칭 안되면ko.- 헤더 우측 LanguageSwitcher 드롭다운(국기 + 코드).
- 초기 번역 키 약 30개 — 4개 언어 모두 채움.
- 미번역 키는
kofallback (에러 없이). - 빌드/배포 회귀 없음.
4. 컨텍스트 & 제약
- Next.js 16 + App Router +
"use client"컴포넌트 다수. - 기존
auth-context처럼 i18n도 React Context 패턴. next-intl은 server/client 모두 지원, 본 프로젝트는 client-side switching이라NextIntlClientProvider중심.- URL 라우팅 변경 없이 단순 메시지만 교체(낮은 비용).
- 폰트: Pretendard Variable이 한국어/영어 잘 표시, 일본어는 시스템 폰트 fallback OK, 스페인어는 라틴 문자라 OK.
5. 아키텍처 개요
frontend/
├── src/
│ ├── i18n/
│ │ ├── config.ts ← 로케일 목록/기본값 상수
│ │ ├── LocaleProvider.tsx ← NextIntlClientProvider wrap + localStorage 저장
│ │ └── useTranslations.ts ← (next-intl 재export)
│ ├── messages/
│ │ ├── ko.json ← 기본
│ │ ├── en.json
│ │ ├── ja.json
│ │ └── es.json
│ ├── components/
│ │ └── LanguageSwitcher.tsx ← 헤더용
│ └── app/
│ └── layout.tsx ← LocaleProvider로 감싸기
6. 데이터 모델
메시지 키 (초기 ~30)
{
"header": {
"search": "검색", "login": "로그인", "logout": "로그아웃",
"menu": "메뉴", "myReviews": "내 리뷰", "favorites": "즐겨찾기"
},
"actions": {
"save": "저장", "cancel": "취소", "delete": "삭제",
"edit": "수정", "confirm": "확인", "close": "닫기",
"loading": "로딩 중...", "submit": "제출"
},
"filter": {
"title": "필터", "cuisine": "음식 종류", "price": "가격대",
"region": "지역", "channel": "채널", "reset": "초기화"
},
"restaurant": {
"rating": "평점", "address": "주소", "phone": "전화",
"website": "웹사이트", "closed": "폐업", "tempClosed": "임시휴업"
},
"review": {
"title": "리뷰", "write": "리뷰 작성", "noReviews": "아직 리뷰가 없습니다"
}
}
총 5개 카테고리 × 평균 6개 = 30개 키.
7. 함수 명세
| 함수/컴포넌트 | 책임 | 비고 |
|---|---|---|
i18n/config.ts |
LOCALES, DEFAULT_LOCALE 상수 | 단순 |
LocaleProvider.tsx |
NextIntlClientProvider wrap + 메시지 동적 로딩 + localStorage 동기화 | client |
LanguageSwitcher.tsx |
헤더 드롭다운 (국기 + 코드) | client, 44px 터치 |
messages/<lang>.json |
키 → 텍스트 | flat or nested |
8. 흐름
- 사용자 첫 방문 →
tasteby_locale없음 →navigator.language.split('-')[0]이 LOCALES에 있으면 사용, 아니면ko. - LocaleProvider가 해당 로케일 메시지 파일 import → NextIntlClientProvider에 전달.
- 컴포넌트는
useTranslations('header')등으로 호출. - LanguageSwitcher에서 변경 → localStorage 저장 → 페이지 새로고침 또는 state 업데이트.
9. 엣지케이스
- 메시지 파일 누락 키: next-intl 기본 동작은 키 자체 표시 + 콘솔 경고. fallback 처리는 messages 명시.
- localStorage 비활성/SSR: typeof window 체크.
- 로케일 코드 대소문자: 항상 소문자 정규화.
- placeholder 변수: next-intl ICU 메시지 형식 지원 (
{name}등). 초기 키에는 미적용.
10. 테스트
- 수동:
- 한국어 첫 방문 → "검색" 표시.
- LanguageSwitcher에서 English → "Search" 표시.
- 새로고침 후 영어 유지.
- 메시지 누락 키 → 콘솔 경고 + 키 표시.
11. 리스크 & 대안
- 선택:
next-intl(Next.js 16 App Router 권장, ICU 메시지, 활발 유지보수). - 대안 A:
react-i18next— 더 일반적이지만 App Router 통합 next-intl이 더 매끄러움. - 대안 B: 자체 구현 + Context — 의존성 ↓ but 기능/표준화 ↓.
- 트레이드오프: 30개 키는 단순하지만 ICU 메시지(복수형, 성별 등) 필요 시 next-intl 가치 큼.
12. 미해결 질문
- 식당명/리뷰 콘텐츠 번역 — 사용자 작성이라 자동 번역(LLM)? 별도 정책 결정.
- URL 라우팅 i18n (
/en/) — 후속. - SEO meta tags i18n — 후속.
- 어드민 페이지는 운영자 한국어 유지 — 확정.
- 통화/날짜 포맷(Intl API) — 후속.