From 03256b411b4904c720c64dea8cb5ebe67859e39d Mon Sep 17 00:00:00 2001 From: Documentation Bot Date: Thu, 13 Nov 2025 04:14:56 +0000 Subject: [PATCH] docs: Sync documentation from private repository --- DATA_MODELS.md | 17 +++ PROJECT_STRUCTURE.md | 164 +++++++++++++++++--------- ROADMAP.md | 5 +- TECH_STACK.md | 266 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 393 insertions(+), 59 deletions(-) diff --git a/DATA_MODELS.md b/DATA_MODELS.md index 5bd7ddf..fb2b4d2 100644 --- a/DATA_MODELS.md +++ b/DATA_MODELS.md @@ -655,6 +655,7 @@ interface MonitoringData extends WritingStats { displayName: string; // 표시 이름 speedHistory: SpeedDataPoint[]; // 속도 히스토리 (최근 10개) currentSpeed: number; // 현재 속도 (글자/분) + isActive: boolean; // 활성 상태 (false면 나간 상태) preview?: string; // 미리보기 (선택적) } ``` @@ -696,6 +697,7 @@ interface MonitoringData extends WritingStats { lastUpdated: 1731398400000, displayName: "철수", currentSpeed: 420, // 글자/분 (클라이언트 계산) + isActive: true, // 활성 상태 (30초 타임아웃 체크) speedHistory: [ // 최근 10개 (클라이언트 계산) { speed: 480, timestamp: 1731398370000 }, { speed: 420, timestamp: 1731398375000 }, @@ -708,6 +710,21 @@ interface MonitoringData extends WritingStats { } ``` +**활성 상태 판단 로직**: +```typescript +// 1. Firebase에서 데이터 수신 → isActive: true +// 2. 30초 타임아웃 체크 +const timeSinceUpdate = Date.now() - lastUpdated; +if (timeSinceUpdate > 30000) { + isActive = false; // 30초 이상 업데이트 없음 → 나감 +} + +// 3. Firebase에서 삭제되어도 마지막 통계 유지 +if (!newData[userId] && prevData[userId]) { + keep prevData[userId] with isActive: false; +} +``` + #### 9.2. previewRequests (미리보기 요청) **경로**: `previewRequests/{userId}/{requestId}` diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 1ba9e90..a7acfe9 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -1,23 +1,42 @@ # 라온누리 - 프로젝트 구조 -> 최종 업데이트: 2025-11-12 (실시간 글쓰기 모니터링 시스템) +> 최종 업데이트: 2025-11-13 (다국어 지원 시스템) 초등학생을 위한 창작 글쓰기 교육 플랫폼 +**최신 업데이트** (2025-11-13): +- 🌏 **다국어 지원 시스템 (i18n)** + - next-intl 라이브러리 기반 + - 한국어(ko), 영어(en) 지원 + - **[locale] 라우팅**: URL에 언어 코드 표시 (`/ko/home`, `/en/home`) + - **브라우저 언어 자동 감지**: `Accept-Language` 헤더 기반 자동 리다이렉트 + - **언어 전환 버튼**: LocaleSwitcher 컴포넌트 (Navbar 우측) + - **번역 파일**: `messages/ko.json`, `messages/en.json` + - **완성된 페이지**: Navbar, Landing 페이지, Home 페이지 전체 번역 완료 + - **타입 안전**: useTranslations 훅으로 타입 체크 + - **쿠키 저장**: 사용자가 선택한 언어 기억 (`NEXT_LOCALE`) + - **site.ts 텍스트 이동**: 사이트 메타데이터도 번역 파일로 관리 + **최신 업데이트** (2025-11-12 PM): - 📡 **실시간 글쓰기 모니터링 시스템** - Firebase Realtime Database 기반 실시간 통신 (Redis Pub/Sub 방식) - - 팀 주제 선택 시 해당 주제로 작성 중인 학생 실시간 표시 + - **모든 팀 멤버 표시** (getUsersByTeam) + - **3가지 상태 관리 및 자동 정렬**: + - 🟢 작성 중 (isActive: true, lastUpdated < 30초) - 초록 배지, 핑크 테두리 + - 🟠 나감 (isActive: false, 마지막 통계 유지) - 주황 배지, 주황 테두리 + - ⚪ 대기 중 (한 번도 작성 안 함) - 회색 배지, 투명도 60% - 5초 주기 자동 업데이트 (글자 수, 단어 수, lastUpdated 타임스탬프) - - **작성 속도 계산** (클라이언트 측, 글자/분 단위) - - **Sparkline 그래프** (Area Chart, 최근 10개 데이터 포인트) - - **인터랙티브 툴팁** (마우스 오버 시 속도 + 몇 초 전 데이터인지 표시) - - 미리보기 요청-응답 시스템 (관리자 클릭 → 학생 응답 → Dialog 표시) - - onDisconnect() 자동 정리 (페이지 이탈 시 세션 자동 삭제) - - LiveWritingMonitor 컴포넌트 (주제 Select, 실시간 카드 그리드) + - **작성 속도 계산** (클라이언트 측, charDiff * 12 = 글자/분) + - **Sparkline 그래프** (Area Chart, 최근 10개 데이터 포인트, 0 표시) + - **인터랙티브 툴팁** (마우스 오버 시 속도 + 몇 초 전 데이터) + - 미리보기 요청-응답 (관리자 클릭 → 학생 응답 → Dialog, 작성 중만) + - **30초 타임아웃**: 업데이트 없으면 "나감" 처리 + - **마지막 통계 유지**: Firebase 삭제되어도 클라이언트 상태 유지 + - onDisconnect() 자동 정리 (페이지 이탈 시 세션 삭제) + - LiveWritingMonitor 컴포넌트 (주제 Select, StudentMonitorCard) - WritingSessionManager (Realtime DB 작업, 상세 디버그 로그) - 완전 무료 (동시 접속 100명까지, 1GB/day) - - Security Rules로 권한 관리 (본인 쓰기, 인증된 사용자 읽기) + - Security Rules (본인 쓰기, 인증된 사용자 읽기) **최신 업데이트** (2025-11-12 AM): - ✅ **Writing API 구현 완료** @@ -113,25 +132,39 @@ ## 페이지 구조 +### 🌏 다국어 라우팅 (2025-11-13) + +모든 페이지는 `[locale]` 세그먼트를 통해 다국어를 지원합니다: +- **한국어**: `/ko/*` (기본값) +- **영어**: `/en/*` +- **자동 감지**: 브라우저 언어 설정에 따라 첫 방문 시 자동 리다이렉트 +- **언어 전환**: Navbar의 지구본 버튼(🌐 KO/EN) 클릭 + +**URL 예시**: +- 랜딩: `/ko`, `/en` +- 홈: `/ko/home`, `/en/home` +- 글쓰기: `/ko/write`, `/en/write` +- 팀: `/ko/team`, `/en/team` + ### ✅ 구현 완료 -| 페이지 | 경로 | 설명 | 주요 기능 | -|-------|------|------|---------| -| **랜딩 페이지** | `/` | 서비스 소개 및 홍보 (비로그인 전용) | Hero, Features, How It Works, CTA, Footer
로그인 시 `/home`으로 자동 리다이렉트 | -| **유저 홈** | `/home` | 인증된 사용자 대시보드 | 환영 메시지, 빠른 시작 대시보드, 최근 활동
비로그인 시 `/`로 자동 리다이렉트
정식 계정은 "내 팀" 카드 추가 표시 | -| **글쓰기** | `/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)
🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)
제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)
🆕 **다중 글조각 관리** (최대 10개), "새 글쓰기" / "저장된 글조각" 버튼
🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)
템플릿 미리채우기 (제목/내용), Firestore 저장
비로그인도 접근 가능 (저장 시 로그인 유도) | -| **테스트** | `/test` | 팀 코드 시스템 테스트 페이지 | 팀 코드 생성/검증 테스트
팀/학생 생성 테스트
학생 로그인 테스트
authStore 상태 확인 | -| **팀 목록** | `/team` | 내가 만든 팀 목록 (정식 계정 전용) | 팀 카드 그리드, 팀 정보 (코드, 멤버 수, 보안 설정)
"새 팀 만들기" 버튼 | -| **팀 생성** | `/team/create` | 새 팀 만들기 (정식 계정 전용) | 팀 이름 입력, 팀 코드 자동 생성
🆕 **5단계 보안 레벨 선택** (RadioCard, 애니메이션)
🆕 **명단 관리 (Level 2/4)**: TagsInput으로 Enter/쉼표 입력
생성 후 `/team/[teamId]`로 이동 | -| **팀 멤버 페이지** | `/team/[teamId]` | 팀 멤버용 페이지 (멤버/소유자 모두 접근) | 팀 정보, 팀 코드 복사, 멤버 목록
소유자는 "팀 관리" 버튼 표시
팀 코드 로그인 후 기본 이동 페이지 | -| **팀 관리** | `/team/[teamId]/manage` | 팀 관리 페이지 (소유자 전용) | 팀 정보 및 코드, 보안 설정 표시
**팀 주제 관리 (생성/삭제)**
🆕 **주제별 학생 분석** (TopicMemberAnalysisSection)
멤버 목록 및 관리
🆕 **멤버 메뉴 - 팀 내 글 분석** (by-team)
"멤버 페이지 보기" 버튼 | +| 페이지 | 경로 | 설명 | 주요 기능 | 다국어 | +|-------|------|------|---------|--------| +| **랜딩 페이지** | `/[locale]` | 서비스 소개 및 홍보 (비로그인 전용) | Hero, Features, How It Works, CTA, Footer
로그인 시 `/home`으로 자동 리다이렉트
🆕 **전체 번역 완료** (사이트명, 태그라인, 모든 섹션) | ✅ 완료 | +| **유저 홈** | `/[locale]/home` | 인증된 사용자 대시보드 | 환영 메시지, 빠른 시작 대시보드, 최근 활동
비로그인 시 `/`로 자동 리다이렉트
정식 계정은 "내 팀" 카드 추가 표시
🆕 **전체 번역 완료** (웰컴 메시지, 모든 액션 카드) | ✅ 완료 | +| **글쓰기** | `/[locale]/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)
🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)
제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)
🆕 **다중 글조각 관리** (최대 10개), "새 글쓰기" / "저장된 글조각" 버튼
🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)
템플릿 미리채우기 (제목/내용), Firestore 저장
비로그인도 접근 가능 (저장 시 로그인 유도) | 🔜 예정 | +| **테스트** | `/[locale]/test` | 팀 코드 시스템 테스트 페이지 | 팀 코드 생성/검증 테스트
팀/학생 생성 테스트
학생 로그인 테스트
authStore 상태 확인 | 🔜 예정 | +| **팀 목록** | `/[locale]/team` | 내가 만든 팀 목록 (정식 계정 전용) | 팀 카드 그리드, 팀 정보 (코드, 멤버 수, 보안 설정)
"새 팀 만들기" 버튼 | 🔜 예정 | +| **팀 생성** | `/[locale]/team/create` | 새 팀 만들기 (정식 계정 전용) | 팀 이름 입력, 팀 코드 자동 생성
🆕 **5단계 보안 레벨 선택** (RadioCard, 애니메이션)
🆕 **명단 관리 (Level 2/4)**: TagsInput으로 Enter/쉼표 입력
생성 후 `/team/[teamId]`로 이동 | 🔜 예정 | +| **팀 멤버 페이지** | `/[locale]/team/[teamId]` | 팀 멤버용 페이지 (멤버/소유자 모두 접근) | 팀 정보, 팀 코드 복사, 멤버 목록
소유자는 "팀 관리" 버튼 표시
팀 코드 로그인 후 기본 이동 페이지 | 🔜 예정 | +| **팀 관리** | `/[locale]/team/[teamId]/manage` | 팀 관리 페이지 (소유자 전용) | 팀 정보 및 코드, 보안 설정 표시
**팀 주제 관리 (생성/삭제)**
🆕 **주제별 학생 분석** (TopicMemberAnalysisSection)
멤버 목록 및 관리
🆕 **멤버 메뉴 - 팀 내 글 분석** (by-team)
"멤버 페이지 보기" 버튼 | 🔜 예정 | ### 🚧 구현 예정 | 페이지 | 경로 | 설명 | 상태 | |-------|------|------|------| -| **학습하기** | `/learn` | 레슨/코스 학습 | ❌ 미구현 (Navbar 링크만 존재) | -| **스티커** | `/stickers` | 스티커 컬렉션 | ❌ 미구현 (Navbar 링크만 존재) | +| **학습하기** | `/[locale]/learn` | 레슨/코스 학습 | ❌ 미구현 (Navbar 링크만 존재) | +| **스티커** | `/[locale]/stickers` | 스티커 컬렉션 | ❌ 미구현 (Navbar 링크만 존재) | | **마이페이지** | Dialog (별도 페이지 없음) | 사용자 대시보드 다이얼로그 | 🔜 계획 중 (UserProfileButton에서 열림) | --- @@ -185,15 +218,21 @@ | 컴포넌트 | 파일명 | 설명 | 상태 | |---------|--------|------|------| -| **Navbar** | `Navbar.tsx` | 상단 네비게이션 바 | ✅ 완료 | +| **Navbar** | `Navbar.tsx` | 상단 네비게이션 바 (다국어 지원) | ✅ 완료 | +| **LocaleSwitcher** | `LocaleSwitcher.tsx` | 🆕 **언어 전환 버튼** (KO ↔ EN, 지구본 아이콘) | ✅ 완료 | -**네비게이션 링크**: -- 홈 (`/` 또는 `/home`) - 인증 상태에 따라 동적 변경 - - 비로그인: `/` (랜딩 페이지) - - 로그인: `/home` (유저 대시보드) -- 글쓰기 (`/write`) - 미구현 -- 학습하기 (`/learn`) - 미구현 -- 스티커 (`/stickers`) - 미구현 +**네비게이션 링크** (다국어 지원): +- 홈 (`/[locale]` 또는 `/[locale]/home`) - 인증 상태에 따라 동적 변경 + - 비로그인: `/[locale]` (랜딩 페이지) + - 로그인: `/[locale]/home` (유저 대시보드) +- 글쓰기 (`/[locale]/write`) - 미구현 +- 학습하기 (`/[locale]/learn`) - 미구현 +- 스티커 (`/[locale]/stickers`) - 미구현 + +**주요 기능** (2025-11-13): +- ✅ next-intl 타입 안전 Link 사용 +- ✅ useTranslations 훅으로 번역 텍스트 관리 +- ✅ LocaleSwitcher로 실시간 언어 전환 --- @@ -310,6 +349,11 @@ **주요 기능**: - ✅ **LiveWritingMonitor** (2025-11-12): - 주제 선택 드롭다운 (Chakra UI Select) + - **모든 팀 멤버 표시** (getUsersByTeam) + - **3가지 상태 관리 및 정렬**: + - 🟢 작성 중 (isActive: true, lastUpdated < 30초) + - 🟠 나감 (isActive: false, 마지막 통계 유지) + - ⚪ 대기 중 (한 번도 작성 안 함) - StudentMonitorCard 컴포넌트 (개별 학생 카드) - 실시간 통계: 글자 수, 단어 수, 마지막 업데이트 시간 ("N초 전") - **작성 속도 계산** (클라이언트 측, charDiff * 12 = 글자/분) @@ -320,9 +364,11 @@ - **인터랙티브 툴팁** (Recharts Tooltip) - 속도 값 ("N자/분") - 시간 정보 ("N초 전") - - 미리보기 버튼 (Dialog로 현재 작성 중인 글 표시) + - 미리보기 버튼 (작성 중인 학생만) + - **30초 타임아웃**: 업데이트 없으면 "나감" 처리 + - **마지막 통계 유지**: 나간 학생도 통계 표시 - 실시간 구독/구독 해제 (useEffect cleanup) - - 빈 상태 UI (작성 중인 학생 없을 때) + - 시각적 구분 (색상별 테두리, 아바타, 배지) - ✅ SecurityLevelSelector: RadioCard 기반, 선택 시 추가 UI 애니메이션으로 표시 - ✅ AllowListManager: Level 2/4용 명단 관리, Enter/쉼표로 구분 입력 - ✅ 그라데이션 배경 (RadioCard와 자연스럽게 연결) @@ -514,35 +560,45 @@ project_w/ ├── database.rules.json # 🆕 Firebase Realtime DB Security Rules │ +├── messages/ # 🆕 다국어 번역 파일 +│ ├── ko.json # 한국어 번역 +│ └── en.json # 영어 번역 +│ ├── src/ +├── i18n/ # 🆕 다국어 설정 +│ ├── routing.ts # 라우팅 설정 (locales, defaultLocale) +│ └── request.ts # 번역 메시지 로더 +│ +├── middleware.ts # 🆕 next-intl 미들웨어 (언어 감지, 리다이렉트) +│ ├── app/ # Next.js App Router -│ ├── layout.tsx # 루트 레이아웃 -│ ├── page.tsx # 랜딩 페이지 (/) - 로그인 시 /home 리다이렉트 -│ ├── home/ -│ │ └── page.tsx # ✅ 유저 홈 페이지 (/home) -│ ├── write/ -│ │ └── page.tsx # ✅ 글쓰기 페이지 (/write) - 🆕 실시간 모니터링 전송 -│ ├── test/ -│ │ └── page.tsx # ✅ 테스트 페이지 (/test) - 팀 코드 시스템 테스트 +│ ├── layout.tsx # 루트 레이아웃 (메타데이터만) +│ ├── [locale]/ # 🆕 다국어 라우팅 +│ │ ├── layout.tsx # locale별 레이아웃 (Provider, Navbar, Auth) +│ │ ├── page.tsx # ✅ 랜딩 페이지 (/[locale]) - 🆕 전체 번역 완료 +│ │ ├── home/ +│ │ │ └── page.tsx # ✅ 유저 홈 페이지 (/[locale]/home) - 🆕 전체 번역 완료 +│ │ ├── write/ +│ │ │ └── page.tsx # ✅ 글쓰기 페이지 (/[locale]/write) - 🆕 실시간 모니터링 +│ │ ├── test/ +│ │ │ └── page.tsx # ✅ 테스트 페이지 (/[locale]/test) +│ │ └── team/ # ✅ 팀 관리 페이지들 +│ │ ├── page.tsx # ✅ 팀 목록 (/[locale]/team) +│ │ ├── create/ +│ │ │ └── page.tsx # ✅ 팀 생성 (/[locale]/team/create) +│ │ └── [teamId]/ +│ │ ├── page.tsx # ✅ 팀 멤버 페이지 (/[locale]/team/[teamId]) +│ │ └── manage/ +│ │ └── page.tsx # ✅ 팀 관리 페이지 (/[locale]/team/[teamId]/manage) │ ├── globals.css -│ ├── favicon.ico -│ ├── team/ # ✅ 팀 관리 페이지들 -│ │ ├── page.tsx # ✅ 팀 목록 (/team) -│ │ ├── create/ -│ │ │ └── page.tsx # ✅ 팀 생성 (/team/create) -│ │ └── [teamId]/ -│ │ ├── page.tsx # ✅ 팀 멤버 페이지 (/team/[teamId]) -│ │ └── manage/ -│ │ └── page.tsx # ✅ 팀 관리 페이지 (/team/[teamId]/manage) -│ └── [미구현 페이지들] -│ ├── learn/ # 학습 페이지 -│ ├── stickers/ # 스티커 페이지 -│ └── admin/ # 관리자 +│ └── favicon.ico │ ├── components/ # React 컴포넌트 │ ├── auth/ # ✅ 인증 (로그인, 프로필, 팀 코드) -│ ├── landing/ # ✅ 랜딩 페이지 카드들 -│ ├── navigation/ # ✅ 네비게이션 바 +│ ├── landing/ # ✅ 랜딩 페이지 카드들 (다국어 지원) +│ ├── navigation/ # ✅ 네비게이션 바 (다국어 지원) +│ │ ├── Navbar.tsx # 다국어 링크 텍스트 +│ │ └── LocaleSwitcher.tsx # 🆕 언어 전환 버튼 │ ├── seo/ # ✅ SEO 컴포넌트 │ ├── ui/ # ✅ Chakra UI 기본 │ ├── writing/ # ✅ 글쓰기 에디터 diff --git a/ROADMAP.md b/ROADMAP.md index 5fc7923..33d1bc0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,6 +1,6 @@ # 라온누리 - 개발 로드맵 -> 최종 업데이트: 2025-11-12 (실시간 글쓰기 모니터링 시스템) +> 최종 업데이트: 2025-11-13 (다국어 지원 시스템) 초등학생을 위한 창작 글쓰기 교육 플랫폼 개발 계획 @@ -83,7 +83,8 @@ | **Writing API 구현** | **POST /api/writing (글 생성), GET/PUT/DELETE /api/writing/[id] (조회/수정/삭제), POST /api/writing/user (목록), POST /api/writing/recent (최근 글), src/lib/server/writing.ts (Firestore CRUD), 권한 체크 (작성자만), 텍스트 통계 자동 계산** | **2025-11-12** | | **패턴 분석 - 팀 소유자 기능** | **3가지 분석 타입 (self/by-team/by-topic), API 권한 체크, 팀 주제 필터링, WritingPatternDialog Props 확장, TopicMemberAnalysisSection (UI만), 팀 관리 페이지에 멤버 분석 메뉴** | **2025-11-12** | | **Content Hash 기반 3단계 캐싱** | **글 목록 해시 생성 (id+updatedAt), L1: localStorage (영구, LRU 10개), L2: Firestore patternAnalyses (영구), L3: Server in-memory (5분, 50개), 변경 자동 감지, AI 비용 절감 (전체 사용자 기준 1회 분석), contentHash.ts + patternCacheManager.ts + patternAnalysis.ts** | **2025-11-12** | -| **실시간 글쓰기 모니터링** | **Firebase Realtime Database 기반 실시간 통신 (Redis Pub/Sub 방식), WritingSessionManager (5초 주기 통계 전송, 미리보기 요청-응답, 상세 디버그 로그), LiveWritingMonitor 컴포넌트 (주제 Select, 실시간 카드 그리드, StudentMonitorCard), 글쓰기 페이지 모니터링 전송 (contentRef로 최적화), onDisconnect() 자동 정리, 작성 속도 계산 (클라이언트 측, 글자/분), Sparkline 그래프 (Area Chart, 최근 10개 히스토리, 0도 표시), 인터랙티브 툴팁 (속도 + 몇 초 전 데이터), 마지막 업데이트 시간 표시, database.rules.json (topicId 레벨 읽기 권한), @chakra-ui/charts + recharts 패키지, 완전 무료 (100명까지)** | **2025-11-12** | +| **실시간 글쓰기 모니터링** | **Firebase Realtime Database 기반 실시간 통신 (Redis Pub/Sub 방식), WritingSessionManager (5초 주기 통계 전송, 미리보기 요청-응답, 상세 디버그 로그), LiveWritingMonitor (주제 Select, 모든 팀 멤버 표시, StudentMonitorCard), 3가지 상태 관리 (작성 중/나감/대기 중, 정렬 순서, 색상별 시각화), 글쓰기 페이지 모니터링 (contentRef 최적화), onDisconnect() 자동 정리, 작성 속도 계산 (클라이언트, 글자/분, charDiff*12), Sparkline 그래프 (Area Chart, 최근 10개, 0 표시), 인터랙티브 툴팁 (속도+시간), 30초 타임아웃 체크 (lastUpdated), 마지막 통계 유지 (Firebase 삭제되어도 유지), database.rules.json (topicId 레벨 읽기), @chakra-ui/charts+recharts, 완전 무료 (100명)** | **2025-11-12** | +| **다국어 지원 시스템 (i18n)** | **next-intl 라이브러리 설치 및 설정, [locale] 라우팅 구조 변경 (/ko/*, /en/*), middleware.ts 생성 (브라우저 언어 자동 감지, Accept-Language 헤더), 번역 파일 생성 (messages/ko.json, messages/en.json), Navbar 번역 적용 (useTranslations 훅), LocaleSwitcher 컴포넌트 (지구본 아이콘 + KO/EN 토글), Landing 페이지 전체 번역 (Hero, Features, Steps, CTA, Footer), Home 페이지 전체 번역 (Welcome, QuickStart 9개 액션 카드, RecentActivity), site.ts 텍스트를 번역 파일로 이동 (사이트명, 태그라인, 저작권), localeDetection: true 설정, NEXT_LOCALE 쿠키 저장, next.config.ts에 next-intl 플러그인 추가, i18n/routing.ts + i18n/request.ts 설정, 타입 체크 통과** | **2025-11-13** | ### 🚧 진행 중 diff --git a/TECH_STACK.md b/TECH_STACK.md index 9567cb9..9613e61 100644 --- a/TECH_STACK.md +++ b/TECH_STACK.md @@ -1,6 +1,6 @@ # 라온누리 - 기술 스택 및 개발 환경 -> 최종 업데이트: 2025-11-12 (실시간 글쓰기 모니터링 시스템) +> 최종 업데이트: 2025-11-13 (다국어 지원 시스템) --- @@ -25,6 +25,7 @@ | **Framer Motion** | 12.23.24 | 애니메이션 라이브러리 | | **React Icons** | 5.5.0 | 아이콘 세트 | | **Tiptap** | latest | 리치 텍스트 에디터 | +| **next-intl** | latest | 🆕 **다국어 지원 (i18n)** | ### Backend & Database @@ -526,12 +527,20 @@ LiveWritingMonitor 컴포넌트 - **LiveWritingMonitor** (`src/components/team/LiveWritingMonitor.tsx`): - 주제 선택 Select 컴포넌트 (Chakra UI Select) - - 실시간 학생 카드 그리드 (StudentMonitorCard) + - **모든 팀 멤버 표시** (getUsersByTeam) + - **3가지 상태 관리**: + - 🟢 작성 중 (isActive: true, lastUpdated < 30초) - 초록 배지, 핑크 테두리 + - 🟠 나감 (isActive: false, 마지막 통계 유지) - 주황 배지, 주황 테두리 + - ⚪ 대기 중 (한 번도 작성 안 함) - 회색 배지, 투명도 60% + - **정렬 순서**: 작성 중 → 나감 → 대기 중 + - StudentMonitorCard 컴포넌트 (개별 학생 카드) - 유저 정보와 통계 자동 결합 (userManager 활용) - **작성 속도 실시간 계산** (클라이언트 측, 글자/분) - **Sparkline 그래프** (Area Chart, 최근 10개 히스토리) - **인터랙티브 툴팁** (속도 값 + 몇 초 전 데이터) - - 미리보기 Dialog + - 미리보기 Dialog (작성 중인 학생만) + - **30초 타임아웃**: 업데이트 없으면 "나감" 처리 + - **마지막 통계 유지**: Firebase 삭제되어도 클라이언트 상태 유지 - 마지막 업데이트 시간 표시 ("N초 전") **Realtime DB 구조**: @@ -1162,6 +1171,257 @@ interface Draft { --- +### 12. 다국어 지원 시스템 (i18n) + +#### 핵심 개념 + +**목적**: 한국어/영어 사용자 모두 접근 가능한 글로벌 플랫폼 구축 + +**라이브러리**: next-intl (Next.js App Router 표준 i18n 라이브러리) + +**지원 언어**: +- 한국어 (ko) - 기본값 +- 영어 (en) + +#### 아키텍처 + +``` +브라우저 요청 (/) + ↓ +Middleware (src/middleware.ts) + ├─> Accept-Language 헤더 확인 + ├─> NEXT_LOCALE 쿠키 확인 + └─> 적절한 locale로 리다이렉트 + ├─> 한국어 우선: /ko + └─> 영어 우선: /en + ↓ +[locale] 라우팅 (src/app/[locale]/*) + ├─> layout.tsx (locale별 레이아웃) + │ ├─> NextIntlClientProvider (번역 메시지 주입) + │ ├─> Provider (Chakra UI) + │ ├─> Navbar (다국어 메뉴) + │ └─> AuthInitializer + │ + └─> page.tsx (각 페이지) + └─> useTranslations('namespace') 훅 사용 + └─> {t('key')} 형태로 번역 표시 +``` + +#### 번역 파일 구조 + +**파일 위치**: `messages/{locale}.json` + +```json +// messages/ko.json +{ + "site": { + "name": "라온누리", + "tagline": "재미있게 글쓰기를 배워보자!", + "subtitle": "친구들과 함께 신나는 글쓰기 모험을 떠나요" + }, + "navbar": { + "home": "홈", + "write": "글쓰기", + "learn": "학습하기", + "stickers": "스티커" + }, + "landing": { + "hero": { + "cta": "지금 시작하기", + "teamCode": "팀 코드로 참여" + }, + "features": {...}, + "howItWorks": {...} + }, + "home": { + "hero": { + "welcome": "환영합니다, {name}님!", // 파라미터 지원 + "subtitle": "오늘도 멋진 글쓰기를 시작해볼까요?" + }, + "quickStart": {...} + } +} +``` + +#### 설정 파일 + +**i18n/routing.ts** - 라우팅 설정: +```typescript +export const routing = defineRouting({ + locales: ['ko', 'en'], + defaultLocale: 'ko', + localePrefix: 'always', // URL에 항상 표시 (/ko/*, /en/*) + localeDetection: true // 브라우저 언어 자동 감지 +}); + +// next-intl 타입 안전 내비게이션 API +export const {Link, redirect, usePathname, useRouter} = createNavigation(routing); +``` + +**i18n/request.ts** - 번역 메시지 로더: +```typescript +export default getRequestConfig(async ({requestLocale}) => { + let locale = await requestLocale; + + if (!locale || !routing.locales.includes(locale)) { + locale = routing.defaultLocale; + } + + return { + locale, + messages: (await import(`../../messages/${locale}.json`)).default + }; +}); +``` + +**middleware.ts** - 자동 언어 감지: +```typescript +import createMiddleware from 'next-intl/middleware'; +import {routing} from './i18n/routing'; + +export default createMiddleware(routing); + +export const config = { + matcher: ['/', '/(ko|en)/:path*'] +}; +``` + +#### 컴포넌트 사용 패턴 + +**Server Component** (기본): +```typescript +import {useTranslations} from 'next-intl'; + +export default function Page() { + const t = useTranslations('namespace'); + + return

{t('key')}

; +} +``` + +**Client Component**: +```typescript +"use client"; +import {useTranslations} from 'next-intl'; + +export default function ClientComponent() { + const t = useTranslations('namespace'); + + return

{t('key')}

; +} +``` + +**파라미터가 있는 번역**: +```typescript +const t = useTranslations('home'); + +// messages/ko.json: "welcome": "환영합니다, {name}님!" +

{t('hero.welcome', {name: userName})}

+// → "환영합니다, 홍길동님!" +``` + +**타입 안전 Link** (locale 자동 처리): +```typescript +import {Link} from '@/i18n/routing'; + +홈으로 +// 현재 locale이 ko면 → /ko/home +// 현재 locale이 en이면 → /en/home +``` + +#### 언어 전환 버튼 + +**LocaleSwitcher** (`src/components/navigation/LocaleSwitcher.tsx`): +```typescript +const locale = useLocale(); // 현재 언어 +const router = useRouter(); +const pathname = usePathname(); + +const toggleLocale = () => { + const nextLocale = locale === 'ko' ? 'en' : 'ko'; + router.replace(pathname, {locale: nextLocale}); +}; + +return ( + +); +``` + +**동작**: +- `/ko/home`에서 버튼 클릭 → `/en/home`으로 이동 +- `NEXT_LOCALE` 쿠키에 저장 (다음 방문 시 기억) + +#### 브라우저 언어 자동 감지 + +**첫 방문 시나리오**: +``` +1. 사용자가 / 접속 (쿠키 없음) +2. Middleware가 Accept-Language 헤더 확인 + - "en-US,en;q=0.9" → /en/으로 리다이렉트 + - "ko-KR,ko;q=0.9" → /ko/로 리다이렉트 + - 지원하지 않는 언어 → /ko/ (기본값) +3. NEXT_LOCALE 쿠키 저장 + +다음 방문 시: +1. 쿠키에서 저장된 언어 확인 +2. 해당 언어로 바로 리다이렉트 +``` + +#### 완성된 다국어 페이지 + +| 페이지 | 경로 | 상태 | 번역 항목 | +|-------|------|------|-----------| +| **Navbar** | 모든 페이지 | ✅ 완료 | 홈, 글쓰기, 학습하기, 스티커 (4개) | +| **Landing** | `/[locale]` | ✅ 완료 | Hero(사이트명, 태그라인, CTA 버튼), Features(4개 카드), Steps(3단계), CTA 섹션, Footer (총 20+ 항목) | +| **Home** | `/[locale]/home` | ✅ 완료 | 웰컴 메시지, QuickStart(9개 액션 카드), RecentActivity (총 15+ 항목) | +| **Write** | `/[locale]/write` | 🔜 예정 | - | +| **Team** | `/[locale]/team/*` | 🔜 예정 | - | + +#### next.config.ts 설정 + +```typescript +import createNextIntlPlugin from 'next-intl/plugin'; + +const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts'); + +const nextConfig = { + reactCompiler: true, +}; + +export default withNextIntl(nextConfig); +``` + +#### 참고 파일 + +**설정**: +- `src/i18n/routing.ts` - 라우팅 설정 +- `src/i18n/request.ts` - 번역 로더 +- `src/middleware.ts` - 언어 감지 미들웨어 +- `messages/ko.json`, `messages/en.json` - 번역 파일 + +**컴포넌트**: +- `src/components/navigation/LocaleSwitcher.tsx` - 언어 전환 버튼 +- `src/components/navigation/Navbar.tsx` - 다국어 메뉴 +- `src/app/[locale]/page.tsx` - Landing 페이지 (전체 번역) +- `src/app/[locale]/home/page.tsx` - Home 페이지 (전체 번역) + +**타입 안전성**: +- next-intl은 타입 추론을 지원하지만, JSON 구조에 따라 자동 완성됨 +- 없는 키 사용 시 런타임 에러 (개발 모드에서는 경고) + +#### 장점 + +✅ **타입 안전**: useTranslations 훅으로 타입 체크 +✅ **Server/Client 지원**: RSC에서도 번역 가능 +✅ **자동 코드 스플리팅**: 필요한 번역만 로드 +✅ **SEO 친화적**: locale별 URL (/ko/*, /en/*) +✅ **쿠키 기반 기억**: 사용자 선택 언어 저장 +✅ **브라우저 자동 감지**: Accept-Language 헤더 + +--- + ## 참고 문서 - [PROJECT_STRUCTURE.md](./PROJECT_STRUCTURE.md) - 프로젝트 구조