diff --git a/HEADPLANE_KOREAN_LOCALIZATION_PLAN.md b/HEADPLANE_KOREAN_LOCALIZATION_PLAN.md new file mode 100644 index 0000000..a50cf8d --- /dev/null +++ b/HEADPLANE_KOREAN_LOCALIZATION_PLAN.md @@ -0,0 +1,320 @@ +# πŸ“‹ Headplane UI ν•œκΈ€ν™” κ³„νšμ„œ + +## πŸ” ν˜„μž¬ 상황 뢄석 + +### Headplane κ΅­μ œν™” ν˜„ν™© +- ❌ i18n 라이브러리 λ―Έμ‚¬μš© (react-i18next, next-intl λ“± μ—†μŒ) +- ❌ λͺ¨λ“  UI ν…μŠ€νŠΈκ°€ μ»΄ν¬λ„ŒνŠΈμ— ν•˜λ“œμ½”λ”© +- ❌ μ–Έμ–΄ μ„€μ • μ˜΅μ…˜ μ—†μŒ +- ❌ GitHub에 λ‹€κ΅­μ–΄ 지원 μš”μ²­μ΄λ‚˜ λ…Όμ˜ μ—†μŒ + +### 기술 μŠ€νƒ +- βœ… React 19.1.0 + TypeScript +- βœ… Vite λΉŒλ“œ μ‹œμŠ€ν…œ +- βœ… Docker λ©€ν‹°μŠ€ν…Œμ΄μ§€ λΉŒλ“œ +- βœ… pnpm νŒ¨ν‚€μ§€ λ§€λ‹ˆμ € + +### ν˜„μž¬ 접속 정보 +- **둜컬**: http://localhost:3000/admin/ +- **μ™ΈλΆ€**: http://192.168.0.151:3000/admin/ +- **API Key**: `8qRr1IB.tV95CmA0fLaCiGGIgBfeoN9daHceFkzI` + +## 🎯 ν•œκΈ€ν™” λͺ©ν‘œ + +### μš°μ„ μˆœμœ„ 1: 핡심 UI μš”μ†Œ +- [ ] 상단 헀더 "Headplane" β†’ "ν—€λ“œν”Œλ ˆμΈ" +- [ ] λ„€λΉ„κ²Œμ΄μ…˜ 메뉴 (Machines β†’ μž₯치 관리, Users β†’ μ‚¬μš©μž 관리, Settings β†’ μ„€μ • λ“±) +- [ ] 둜그인 νŽ˜μ΄μ§€ ν…μŠ€νŠΈ (API Key β†’ API ν‚€, Sign In β†’ 둜그인) +- [ ] 메인 λŒ€μ‹œλ³΄λ“œ 라벨듀 + +### μš°μ„ μˆœμœ„ 2: 상세 νŽ˜μ΄μ§€ +- [ ] ν…Œμ΄λΈ” 헀더 (Name β†’ 이름, IP Address β†’ IP μ£Όμ†Œ, Status β†’ μƒνƒœ λ“±) +- [ ] λ²„νŠΌ ν…μŠ€νŠΈ (Create β†’ 생성, Delete β†’ μ‚­μ œ, Edit β†’ νŽΈμ§‘ λ“±) +- [ ] 폼 라벨 및 ν”Œλ ˆμ΄μŠ€ν™€λ” + +### μš°μ„ μˆœμœ„ 3: λ©”μ‹œμ§€ 및 μ•Œλ¦Ό +- [ ] μ—λŸ¬ λ©”μ‹œμ§€ +- [ ] 성곡/확인 λ©”μ‹œμ§€ +- [ ] 도움말 ν…μŠ€νŠΈ + +## πŸ› οΈ κ΅¬ν˜„ λ°©μ•ˆλ³„ 비ꡐ + +### λ°©μ•ˆ 1: ν™˜κ²½ λ³€μˆ˜ 기반 (ꢌμž₯ - 단기) +**λ‚œμ΄λ„**: ⭐⭐ +**μ†Œμš” μ‹œκ°„**: 2-3μ‹œκ°„ +**μž₯점**: +- λΉ λ₯Έ κ΅¬ν˜„ κ°€λŠ₯ +- κΈ°μ‘΄ μ½”λ“œ μ΅œμ†Œ λ³€κ²½ +- Docker ν™˜κ²½λ³€μˆ˜λ‘œ μ‰½κ²Œ μ œμ–΄ +- ν˜„μž¬ ν™˜κ²½μ—μ„œ μ¦‰μ‹œ 적용 κ°€λŠ₯ + +**단점**: +- μ œν•œμ μΈ μœ μ—°μ„± +- ν…μŠ€νŠΈλ³„ κ°œλ³„ ν™˜κ²½λ³€μˆ˜ ν•„μš” + +**κ΅¬ν˜„ 방법**: +```typescript +// app/config/texts.ts 생성 +export const UI_TEXTS = { + app_title: process.env.HEADPLANE_TITLE || 'Headplane', + nav: { + machines: process.env.HEADPLANE_NAV_MACHINES || 'Machines', + users: process.env.HEADPLANE_NAV_USERS || 'Users', + settings: process.env.HEADPLANE_NAV_SETTINGS || 'Settings' + } +}; +``` + +### λ°©μ•ˆ 2: react-i18next λ„μž… (ꢌμž₯ - μž₯κΈ°) +**λ‚œμ΄λ„**: ⭐⭐⭐⭐ +**μ†Œμš” μ‹œκ°„**: 1-2일 +**μž₯점**: +- μ™„μ „ν•œ κ΅­μ œν™” 지원 +- μ–Έμ–΄ μ „ν™˜ κΈ°λŠ₯ +- ν‘œμ€€μ μΈ μ ‘κ·Ό 방식 +- ν–₯ν›„ ν™•μž₯μ„± μ’‹μŒ + +**단점**: +- λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈ μˆ˜μ • ν•„μš” +- 라이브러리 μ˜μ‘΄μ„± μΆ”κ°€ +- μƒλ‹Ήν•œ μ½”λ“œ λ³€κ²½ ν•„μš” + +**κ΅¬ν˜„ ꡬ쑰**: +``` +public/locales/ +β”œβ”€β”€ en/ +β”‚ └── translation.json +└── ko/ + └── translation.json +``` + +### λ°©μ•ˆ 3: λΈŒλΌμš°μ € ν™•μž₯ν”„λ‘œκ·Έλž¨ (μ¦‰μ‹œ 적용) +**λ‚œμ΄λ„**: ⭐ +**μ†Œμš” μ‹œκ°„**: 30λΆ„ +**μž₯점**: +- μ¦‰μ‹œ 적용 κ°€λŠ₯ +- μ½”λ“œ μˆ˜μ • λΆˆν•„μš” +- ν…ŒμŠ€νŠΈ λͺ©μ μœΌλ‘œ 졜적 + +**단점**: +- 개인 ν™˜κ²½μ—μ„œλ§Œ λ™μž‘ +- 동적 μ½˜ν…μΈ  ν•œκ³„ +- μΌμ‹œμ  ν•΄κ²°μ±… + +### λ°©μ•ˆ 4: 포크 ν›„ ν•˜λ“œμ½”λ”© μˆ˜μ • +**λ‚œμ΄λ„**: ⭐⭐⭐ +**μ†Œμš” μ‹œκ°„**: 4-6μ‹œκ°„ +**μž₯점**: +- μ™„μ „ν•œ μ œμ–΄ +- μ¦‰μ‹œ 적용 κ°€λŠ₯ + +**단점**: +- μ—…μŠ€νŠΈλ¦Ό μ—…λ°μ΄νŠΈ 어렀움 +- μœ μ§€λ³΄μˆ˜ λΆ€λ‹΄ + +## πŸ“… 단계별 μ‹€ν–‰ κ³„νš + +### Phase 1: μ¦‰μ‹œ 적용 (였늘) +#### 1-1. λΈŒλΌμš°μ € ν™•μž₯ν”„λ‘œκ·Έλž¨ μ œμž‘ +- [ ] Tampermonkey 슀크립트 μž‘μ„± +- [ ] 핡심 UI μš”μ†Œ λ²ˆμ—­ λ§€ν•‘ +- [ ] ν…ŒμŠ€νŠΈ 및 검증 + +#### 1-2. λΈŒλΌμš°μ € ν™•μž₯ 슀크립트 μ˜ˆμ‹œ +```javascript +// ==UserScript== +// @name Headplane ν•œκΈ€ν™” +// @match http://192.168.0.151:3000/* +// @match http://localhost:3000/* +// ==/UserScript== + +const translations = { + 'Headplane': 'ν—€λ“œν”Œλ ˆμΈ', + 'Machines': 'μž₯치 관리', + 'Users': 'μ‚¬μš©μž 관리', + 'Settings': 'μ„€μ •', + 'API Key': 'API ν‚€', + 'Sign In': '둜그인', + 'Welcome to Headplane': 'ν—€λ“œν”Œλ ˆμΈμ— μ˜€μ‹  것을 ν™˜μ˜ν•©λ‹ˆλ‹€', + 'Enter an API key to authenticate': 'API ν‚€λ₯Ό μž…λ ₯ν•˜μ—¬ μΈμ¦ν•΄μ£Όμ„Έμš”' +}; + +function translatePage() { + document.querySelectorAll('*').forEach(element => { + if (element.children.length === 0 && element.textContent.trim()) { + const text = element.textContent.trim(); + if (translations[text]) { + element.textContent = translations[text]; + } + } + }); +} + +// νŽ˜μ΄μ§€ λ‘œλ“œ μ‹œ λ²ˆμ—­ 적용 +translatePage(); + +// 동적 μ½˜ν…μΈ  λ³€κ²½ 감지 및 λ²ˆμ—­ 적용 +new MutationObserver(translatePage).observe(document.body, { + childList: true, + subtree: true +}); +``` + +### Phase 2: μž„μ‹œ ν•΄κ²°μ±… (1-2일 λ‚΄) +#### 2-1. ν™˜κ²½ λ³€μˆ˜ 기반 μ»€μŠ€ν„°λ§ˆμ΄μ§• +- [ ] ν…μŠ€νŠΈ μƒμˆ˜ 파일 생성 (`app/config/texts.ts`) +- [ ] μ£Όμš” μ»΄ν¬λ„ŒνŠΈμ— ν…μŠ€νŠΈ μƒμˆ˜ 적용 +- [ ] Docker Compose ν™˜κ²½λ³€μˆ˜ μ„€μ • +- [ ] μ»€μŠ€ν…€ Docker 이미지 λΉŒλ“œ +- [ ] μ»¨ν…Œμ΄λ„ˆ 재배포 및 ν…ŒμŠ€νŠΈ + +#### 2-2. Docker ν™˜κ²½λ³€μˆ˜ μ„€μ • μ˜ˆμ‹œ +```yaml +# docker-compose.yml에 μΆ”κ°€ +services: + headplane: + environment: + - TZ=Asia/Seoul + - HEADSCALE_URL=http://headscale:8080 + - HEADSCALE_API_KEY=${HEADSCALE_API_KEY} + # ν•œκΈ€ν™” ν™˜κ²½λ³€μˆ˜ + - HEADPLANE_TITLE=ν—€λ“œν”Œλ ˆμΈ + - HEADPLANE_NAV_MACHINES=μž₯치 관리 + - HEADPLANE_NAV_USERS=μ‚¬μš©μž 관리 + - HEADPLANE_NAV_SETTINGS=μ„€μ • + - HEADPLANE_LOGIN_TITLE=둜그인 + - HEADPLANE_API_KEY_LABEL=API ν‚€ +``` + +### Phase 3: μ™„μ „ν•œ ν•΄κ²°μ±… (1주일 λ‚΄) +#### 3-1. react-i18next 라이브러리 λ„μž… +- [ ] GitHubμ—μ„œ tale/headplane 포크 +- [ ] 둜컬 개발 ν™˜κ²½ ꡬ성 +- [ ] react-i18next 라이브러리 μ„€μΉ˜ +- [ ] i18n μ„€μ • 및 λ²ˆμ—­ 파일 ꡬ쑰 생성 +- [ ] μ£Όμš” μ»΄ν¬λ„ŒνŠΈ κ΅­μ œν™” 적용 +- [ ] μ–Έμ–΄ μ „ν™˜ UI μΆ”κ°€ +- [ ] μ»€μŠ€ν…€ Docker 이미지 λΉŒλ“œ 및 배포 + +#### 3-2. λ²ˆμ—­ 파일 ꡬ쑰 μ˜ˆμ‹œ +```json +// public/locales/ko/translation.json +{ + "app": { + "title": "ν—€λ“œν”Œλ ˆμΈ", + "description": "ν—€λ“œμŠ€μΌ€μΌ 관리 μ›Ή μΈν„°νŽ˜μ΄μŠ€" + }, + "navigation": { + "machines": "μž₯치 관리", + "users": "μ‚¬μš©μž 관리", + "settings": "μ„€μ •", + "accessControl": "μ ‘κ·Ό μ œμ–΄", + "dns": "DNS" + }, + "login": { + "title": "ν—€λ“œν”Œλ ˆμΈμ— μ˜€μ‹  것을 ν™˜μ˜ν•©λ‹ˆλ‹€", + "description": "API ν‚€λ₯Ό μž…λ ₯ν•˜μ—¬ ν—€λ“œν”Œλ ˆμΈμ— μΈμ¦ν•΄μ£Όμ„Έμš”.", + "apiKeyLabel": "API ν‚€", + "apiKeyPlaceholder": "API ν‚€λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”", + "signInButton": "둜그인", + "helpText": "ν„°λ―Έλ„μ—μ„œ 'headscale apikeys create' λͺ…령을 μ‹€ν–‰ν•˜μ—¬ API ν‚€λ₯Ό 생성할 수 μžˆμŠ΅λ‹ˆλ‹€." + }, + "machines": { + "title": "μž₯치 관리", + "description": "ν…ŒμΌλ„·μ— μ—°κ²°λœ μž₯μΉ˜λ“€μ„ κ΄€λ¦¬ν•©λ‹ˆλ‹€.", + "tableHeaders": { + "name": "이름", + "addresses": "μ£Όμ†Œ", + "version": "버전", + "lastSeen": "λ§ˆμ§€λ§‰ 접속" + } + }, + "users": { + "title": "μ‚¬μš©μž 관리", + "description": "ν—€λ“œμŠ€μΌ€μΌ μ‚¬μš©μžλ“€μ„ κ΄€λ¦¬ν•©λ‹ˆλ‹€." + } +} +``` + +## 🎯 ꢌμž₯ μ‹€ν–‰ λ°©μ•ˆ + +### μ¦‰μ‹œ μ‹€ν–‰: λ°©μ•ˆ 3 (λΈŒλΌμš°μ € ν™•μž₯ν”„λ‘œκ·Έλž¨) +ν˜„μž¬ μ‚¬μš© 쀑인 ν™˜κ²½μ—μ„œ λ°”λ‘œ ν•œκΈ€ UIλ₯Ό 확인할 수 μžˆλ„λ‘ **Tampermonkey 슀크립트**λΆ€ν„° μ‹œμž‘ + +### 단기 λͺ©ν‘œ: λ°©μ•ˆ 1 (ν™˜κ²½ λ³€μˆ˜ 기반) +Docker ν™˜κ²½λ³€μˆ˜λ₯Ό ν†΅ν•œ ν…μŠ€νŠΈ μ»€μŠ€ν„°λ§ˆμ΄μ§•μœΌλ‘œ **μ•ˆμ •μ μΈ ν•œκΈ€ν™”** κ΅¬ν˜„ + +### μž₯κΈ° λͺ©ν‘œ: λ°©μ•ˆ 2 (react-i18next λ„μž…) +μ™„μ „ν•œ κ΅­μ œν™” 지원을 μœ„ν•œ **ν‘œμ€€μ μΈ λ‹€κ΅­μ–΄ 지원** κ΅¬ν˜„ + +## πŸ“Š μ˜ˆμƒ λ²ˆμ—­ λ²”μœ„ + +### 핡심 λ²ˆμ—­ λŒ€μƒ (총 μ•½ 50-80개 ν…μŠ€νŠΈ) + +#### 곡톡 UI μš”μ†Œ +- Headplane β†’ ν—€λ“œν”Œλ ˆμΈ +- API Key β†’ API ν‚€ +- Sign In β†’ 둜그인 +- Settings β†’ μ„€μ • +- Profile β†’ ν”„λ‘œν•„ +- Logout β†’ λ‘œκ·Έμ•„μ›ƒ + +#### λ„€λΉ„κ²Œμ΄μ…˜ 메뉴 +- Machines β†’ μž₯치 관리 +- Users β†’ μ‚¬μš©μž 관리 +- Access Control β†’ μ ‘κ·Ό μ œμ–΄ +- DNS β†’ DNS μ„€μ • + +#### ν…Œμ΄λΈ” 및 폼 μš”μ†Œ +- Name β†’ 이름 +- IP Address β†’ IP μ£Όμ†Œ +- Status β†’ μƒνƒœ +- Version β†’ 버전 +- Last Seen β†’ λ§ˆμ§€λ§‰ 접속 +- Create β†’ 생성 +- Delete β†’ μ‚­μ œ +- Edit β†’ νŽΈμ§‘ + +## πŸš€ μ‹œμž‘ν•˜κΈ° + +### 1단계: λΈŒλΌμš°μ € ν™•μž₯ν”„λ‘œκ·Έλž¨ μ„€μΉ˜ +1. Chrome/Edgeμ—μ„œ Tampermonkey ν™•μž₯ν”„λ‘œκ·Έλž¨ μ„€μΉ˜ +2. μœ„μ˜ 슀크립트 μ½”λ“œλ₯Ό μƒˆ 슀크립트둜 생성 +3. http://192.168.0.151:3000/admin/ μ ‘μ†ν•˜μ—¬ ν•œκΈ€ν™” 확인 + +### 2단계: ν™˜κ²½λ³€μˆ˜ 기반 κ΅¬ν˜„ μ€€λΉ„ +1. tale/headplane μ €μž₯μ†Œ 포크 +2. 둜컬 κ°œλ°œν™˜κ²½ ꡬ성 +3. ν…μŠ€νŠΈ μƒμˆ˜ 파일 생성 및 적용 + +## πŸ“ μ§„ν–‰ 상황 체크리슀트 + +### Phase 1: λΈŒλΌμš°μ € ν™•μž₯ν”„λ‘œκ·Έλž¨ +- [ ] Tampermonkey μ„€μΉ˜ 및 슀크립트 μž‘μ„± +- [ ] 둜그인 νŽ˜μ΄μ§€ ν•œκΈ€ν™” ν…ŒμŠ€νŠΈ +- [ ] 메인 λŒ€μ‹œλ³΄λ“œ ν•œκΈ€ν™” ν…ŒμŠ€νŠΈ +- [ ] λ²ˆμ—­ ν’ˆμ§ˆ 검증 + +### Phase 2: ν™˜κ²½λ³€μˆ˜ 기반 +- [ ] ν”„λ‘œμ νŠΈ 포크 및 클둠 +- [ ] 개발 ν™˜κ²½ ꡬ성 +- [ ] ν…μŠ€νŠΈ μƒμˆ˜ 파일 ꡬ쑰 섀계 +- [ ] μ£Όμš” μ»΄ν¬λ„ŒνŠΈ μˆ˜μ • +- [ ] Docker 이미지 λΉŒλ“œ ν…ŒμŠ€νŠΈ +- [ ] ν”„λ‘œλ•μ…˜ 배포 + +### Phase 3: react-i18next λ„μž… +- [ ] i18n 라이브러리 μ„€μΉ˜ 및 μ„€μ • +- [ ] λ²ˆμ—­ 파일 ꡬ쑰 생성 +- [ ] μ»΄ν¬λ„ŒνŠΈλ³„ κ΅­μ œν™” 적용 +- [ ] μ–Έμ–΄ μ „ν™˜ UI κ΅¬ν˜„ +- [ ] 전체 ν…ŒμŠ€νŠΈ 및 검증 + +## πŸ”— κ΄€λ ¨ 링크 + +- **Headplane GitHub**: https://github.com/tale/headplane +- **React i18next λ¬Έμ„œ**: https://react.i18next.com/ +- **ν˜„μž¬ Headplane UI**: http://192.168.0.151:3000/admin/ + +## πŸ“… 생성일: 2025-09-09 +## πŸ‘€ μž‘μ„±μž: Claude Code Assistant \ No newline at end of file