Files
joungmin 6cbf7feaf5 feat(i18n): #352 다국어 뼈대 ko/en/ja/es
- 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
2026-06-15 15:58:21 +09:00
..

설계서: 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)

{
  "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) — 후속.