docs: Sync documentation from private repository
This commit is contained in:
parent
d09f99a0cc
commit
03256b411b
@ -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}`
|
||||
|
||||
@ -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<br>로그인 시 `/home`으로 자동 리다이렉트 |
|
||||
| **유저 홈** | `/home` | 인증된 사용자 대시보드 | 환영 메시지, 빠른 시작 대시보드, 최근 활동<br>비로그인 시 `/`로 자동 리다이렉트<br>정식 계정은 "내 팀" 카드 추가 표시 |
|
||||
| **글쓰기** | `/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)<br>🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)<br>제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)<br>🆕 **다중 글조각 관리** (최대 10개), "새 글쓰기" / "저장된 글조각" 버튼<br>🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)<br>템플릿 미리채우기 (제목/내용), Firestore 저장<br>비로그인도 접근 가능 (저장 시 로그인 유도) |
|
||||
| **테스트** | `/test` | 팀 코드 시스템 테스트 페이지 | 팀 코드 생성/검증 테스트<br>팀/학생 생성 테스트<br>학생 로그인 테스트<br>authStore 상태 확인 |
|
||||
| **팀 목록** | `/team` | 내가 만든 팀 목록 (정식 계정 전용) | 팀 카드 그리드, 팀 정보 (코드, 멤버 수, 보안 설정)<br>"새 팀 만들기" 버튼 |
|
||||
| **팀 생성** | `/team/create` | 새 팀 만들기 (정식 계정 전용) | 팀 이름 입력, 팀 코드 자동 생성<br>🆕 **5단계 보안 레벨 선택** (RadioCard, 애니메이션)<br>🆕 **명단 관리 (Level 2/4)**: TagsInput으로 Enter/쉼표 입력<br>생성 후 `/team/[teamId]`로 이동 |
|
||||
| **팀 멤버 페이지** | `/team/[teamId]` | 팀 멤버용 페이지 (멤버/소유자 모두 접근) | 팀 정보, 팀 코드 복사, 멤버 목록<br>소유자는 "팀 관리" 버튼 표시<br>팀 코드 로그인 후 기본 이동 페이지 |
|
||||
| **팀 관리** | `/team/[teamId]/manage` | 팀 관리 페이지 (소유자 전용) | 팀 정보 및 코드, 보안 설정 표시<br>**팀 주제 관리 (생성/삭제)**<br>🆕 **주제별 학생 분석** (TopicMemberAnalysisSection)<br>멤버 목록 및 관리<br>🆕 **멤버 메뉴 - 팀 내 글 분석** (by-team)<br>"멤버 페이지 보기" 버튼 |
|
||||
| 페이지 | 경로 | 설명 | 주요 기능 | 다국어 |
|
||||
|-------|------|------|---------|--------|
|
||||
| **랜딩 페이지** | `/[locale]` | 서비스 소개 및 홍보 (비로그인 전용) | Hero, Features, How It Works, CTA, Footer<br>로그인 시 `/home`으로 자동 리다이렉트<br>🆕 **전체 번역 완료** (사이트명, 태그라인, 모든 섹션) | ✅ 완료 |
|
||||
| **유저 홈** | `/[locale]/home` | 인증된 사용자 대시보드 | 환영 메시지, 빠른 시작 대시보드, 최근 활동<br>비로그인 시 `/`로 자동 리다이렉트<br>정식 계정은 "내 팀" 카드 추가 표시<br>🆕 **전체 번역 완료** (웰컴 메시지, 모든 액션 카드) | ✅ 완료 |
|
||||
| **글쓰기** | `/[locale]/write` | Tiptap 기반 순수 텍스트 에디터 + 주제 선택 | 주제 선택 (자유 주제/개인 주제/팀 주제)<br>🆕 **주제 변경 경고** (작성 중 내용 초기화 알림, 임시 저장 안내)<br>제목 입력 (Editable), 순수 텍스트 에디터 (포맷팅 없음)<br>🆕 **다중 글조각 관리** (최대 10개), "새 글쓰기" / "저장된 글조각" 버튼<br>🆕 **강화된 자동 저장** (2초 debounce, 저장 상태 표시: 저장 중/저장됨)<br>템플릿 미리채우기 (제목/내용), Firestore 저장<br>비로그인도 접근 가능 (저장 시 로그인 유도) | 🔜 예정 |
|
||||
| **테스트** | `/[locale]/test` | 팀 코드 시스템 테스트 페이지 | 팀 코드 생성/검증 테스트<br>팀/학생 생성 테스트<br>학생 로그인 테스트<br>authStore 상태 확인 | 🔜 예정 |
|
||||
| **팀 목록** | `/[locale]/team` | 내가 만든 팀 목록 (정식 계정 전용) | 팀 카드 그리드, 팀 정보 (코드, 멤버 수, 보안 설정)<br>"새 팀 만들기" 버튼 | 🔜 예정 |
|
||||
| **팀 생성** | `/[locale]/team/create` | 새 팀 만들기 (정식 계정 전용) | 팀 이름 입력, 팀 코드 자동 생성<br>🆕 **5단계 보안 레벨 선택** (RadioCard, 애니메이션)<br>🆕 **명단 관리 (Level 2/4)**: TagsInput으로 Enter/쉼표 입력<br>생성 후 `/team/[teamId]`로 이동 | 🔜 예정 |
|
||||
| **팀 멤버 페이지** | `/[locale]/team/[teamId]` | 팀 멤버용 페이지 (멤버/소유자 모두 접근) | 팀 정보, 팀 코드 복사, 멤버 목록<br>소유자는 "팀 관리" 버튼 표시<br>팀 코드 로그인 후 기본 이동 페이지 | 🔜 예정 |
|
||||
| **팀 관리** | `/[locale]/team/[teamId]/manage` | 팀 관리 페이지 (소유자 전용) | 팀 정보 및 코드, 보안 설정 표시<br>**팀 주제 관리 (생성/삭제)**<br>🆕 **주제별 학생 분석** (TopicMemberAnalysisSection)<br>멤버 목록 및 관리<br>🆕 **멤버 메뉴 - 팀 내 글 분석** (by-team)<br>"멤버 페이지 보기" 버튼 | 🔜 예정 |
|
||||
|
||||
### 🚧 구현 예정
|
||||
|
||||
| 페이지 | 경로 | 설명 | 상태 |
|
||||
|-------|------|------|------|
|
||||
| **학습하기** | `/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) - 팀 코드 시스템 테스트
|
||||
│ ├── globals.css
|
||||
│ ├── favicon.ico
|
||||
│ ├── team/ # ✅ 팀 관리 페이지들
|
||||
│ │ ├── page.tsx # ✅ 팀 목록 (/team)
|
||||
│ ├── 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 # ✅ 팀 생성 (/team/create)
|
||||
│ │ │ └── page.tsx # ✅ 팀 생성 (/[locale]/team/create)
|
||||
│ │ └── [teamId]/
|
||||
│ │ ├── page.tsx # ✅ 팀 멤버 페이지 (/team/[teamId])
|
||||
│ │ ├── page.tsx # ✅ 팀 멤버 페이지 (/[locale]/team/[teamId])
|
||||
│ │ └── manage/
|
||||
│ │ └── page.tsx # ✅ 팀 관리 페이지 (/team/[teamId]/manage)
|
||||
│ └── [미구현 페이지들]
|
||||
│ ├── learn/ # 학습 페이지
|
||||
│ ├── stickers/ # 스티커 페이지
|
||||
│ └── admin/ # 관리자
|
||||
│ │ └── page.tsx # ✅ 팀 관리 페이지 (/[locale]/team/[teamId]/manage)
|
||||
│ ├── globals.css
|
||||
│ └── favicon.ico
|
||||
│
|
||||
├── components/ # React 컴포넌트
|
||||
│ ├── auth/ # ✅ 인증 (로그인, 프로필, 팀 코드)
|
||||
│ ├── landing/ # ✅ 랜딩 페이지 카드들
|
||||
│ ├── navigation/ # ✅ 네비게이션 바
|
||||
│ ├── landing/ # ✅ 랜딩 페이지 카드들 (다국어 지원)
|
||||
│ ├── navigation/ # ✅ 네비게이션 바 (다국어 지원)
|
||||
│ │ ├── Navbar.tsx # 다국어 링크 텍스트
|
||||
│ │ └── LocaleSwitcher.tsx # 🆕 언어 전환 버튼
|
||||
│ ├── seo/ # ✅ SEO 컴포넌트
|
||||
│ ├── ui/ # ✅ Chakra UI 기본
|
||||
│ ├── writing/ # ✅ 글쓰기 에디터
|
||||
|
||||
@ -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** |
|
||||
|
||||
### 🚧 진행 중
|
||||
|
||||
|
||||
266
TECH_STACK.md
266
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 <h1>{t('key')}</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
**Client Component**:
|
||||
```typescript
|
||||
"use client";
|
||||
import {useTranslations} from 'next-intl';
|
||||
|
||||
export default function ClientComponent() {
|
||||
const t = useTranslations('namespace');
|
||||
|
||||
return <p>{t('key')}</p>;
|
||||
}
|
||||
```
|
||||
|
||||
**파라미터가 있는 번역**:
|
||||
```typescript
|
||||
const t = useTranslations('home');
|
||||
|
||||
// messages/ko.json: "welcome": "환영합니다, {name}님!"
|
||||
<h1>{t('hero.welcome', {name: userName})}</h1>
|
||||
// → "환영합니다, 홍길동님!"
|
||||
```
|
||||
|
||||
**타입 안전 Link** (locale 자동 처리):
|
||||
```typescript
|
||||
import {Link} from '@/i18n/routing';
|
||||
|
||||
<Link href="/home">홈으로</Link>
|
||||
// 현재 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 (
|
||||
<Button onClick={toggleLocale}>
|
||||
<LuGlobe /> {locale.toUpperCase()}
|
||||
</Button>
|
||||
);
|
||||
```
|
||||
|
||||
**동작**:
|
||||
- `/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) - 프로젝트 구조
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user