# 설계서: 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`, fallback `ko`.
  - 메시지 디렉토리 `src/messages/{ko,en,ja,es}.json`.
  - Provider (`<NextIntlClientProvider>`) 루트 layout 적용.
  - `LanguageSwitcher` 컴포넌트 (헤더 우측).
  - 로케일 저장: localStorage `tasteby_locale`.
  - 초기 번역 키 ~30개: 헤더(로그인/검색/메뉴) + 주요 액션(저장/취소/삭제/확인) + 페이지 제목.
- **제외**
  - URL 라우팅 i18n (`/ko/...`).
  - SEO meta tags i18n.
  - 식당명/리뷰 등 사용자 콘텐츠 번역.
  - 어드민 페이지(운영자만 사용, 한국어 유지).
  - 식당 카드/상세 시트 전체 키 추출 (점진).

## 3. 인수조건

- [ ] `npm i next-intl` + 4개 메시지 파일 생성.
- [ ] `tasteby_locale`이 localStorage에 있으면 사용, 없으면 브라우저 언어 감지(`navigator.language`) → 매칭 안되면 `ko`.
- [ ] 헤더 우측 LanguageSwitcher 드롭다운(국기 + 코드).
- [ ] 초기 번역 키 약 30개 — 4개 언어 모두 채움.
- [ ] 미번역 키는 `ko` fallback (에러 없이).
- [ ] 빌드/배포 회귀 없음.

## 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)
```json
{
  "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. 흐름

1. 사용자 첫 방문 → `tasteby_locale` 없음 → `navigator.language.split('-')[0]`이 LOCALES에 있으면 사용, 아니면 `ko`.
2. LocaleProvider가 해당 로케일 메시지 파일 import → NextIntlClientProvider에 전달.
3. 컴포넌트는 `useTranslations('header')` 등으로 호출.
4. 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) — 후속.
