feat: localize all UI text to Korean

Translate all user-facing strings to Korean across 25 files:
- UI screens: title, nickname, lobby, class select, waiting, game,
  shop, result, help, leaderboard, achievements, codex, stats
- Game logic: combat logs, events, achievements, mutations, emotes,
  lobby errors, session messages
- Keep English for: class names, monster names, item names, relic names

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 23:47:27 +09:00
parent 206ac522c5
commit f28160d4da
26 changed files with 286 additions and 286 deletions

View File

@@ -33,7 +33,7 @@ func (s *AchievementsScreen) View(ctx *Context) string {
}
func renderAchievements(playerName string, achievements []store.Achievement, width, height int) string {
title := styleHeader.Render("── Achievements ──")
title := styleHeader.Render("── 업적 ──")
var content string
unlocked := 0
@@ -49,9 +49,9 @@ func renderAchievements(playerName string, achievements []store.Achievement, wid
content += styleSystem.Render(" "+a.Description) + "\n"
}
progress := fmt.Sprintf("\n %s", styleGold.Render(fmt.Sprintf("%d/%d Unlocked", unlocked, len(achievements))))
progress := fmt.Sprintf("\n %s", styleGold.Render(fmt.Sprintf("%d/%d 해금", unlocked, len(achievements))))
footer := styleSystem.Render("\n[A] Back")
footer := styleSystem.Render("\n[A] 뒤로")
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
lipgloss.JoinVertical(lipgloss.Center, title, "", content, progress, footer))

View File

@@ -69,10 +69,10 @@ var classOptions = []struct {
name string
desc string
}{
{entity.ClassWarrior, "Warrior", "HP:120 ATK:12 DEF:8 Skill: Taunt (draw enemy fire)"},
{entity.ClassMage, "Mage", "HP:70 ATK:20 DEF:3 Skill: Fireball (AoE damage)"},
{entity.ClassHealer, "Healer", "HP:90 ATK:8 DEF:5 Skill: Heal (restore 30 HP)"},
{entity.ClassRogue, "Rogue", "HP:85 ATK:15 DEF:4 Skill: Scout (reveal rooms)"},
{entity.ClassWarrior, "Warrior", "HP:120 ATK:12 DEF:8 스킬: Taunt (적의 공격을 끌어옴)"},
{entity.ClassMage, "Mage", "HP:70 ATK:20 DEF:3 스킬: Fireball (광역 피해)"},
{entity.ClassHealer, "Healer", "HP:90 ATK:8 DEF:5 스킬: Heal (HP 30 회복)"},
{entity.ClassRogue, "Rogue", "HP:85 ATK:15 DEF:4 스킬: Scout (주변 방 탐색)"},
}
func renderClassSelect(state classSelectState, width, height int) string {
@@ -90,7 +90,7 @@ func renderClassSelect(state classSelectState, width, height int) string {
descStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("240"))
header := headerStyle.Render("── Choose Your Class ──")
header := headerStyle.Render("── 직업을 선택하세요 ──")
list := ""
for i, opt := range classOptions {
marker := " "
@@ -103,7 +103,7 @@ func renderClassSelect(state classSelectState, width, height int) string {
marker, style.Render(opt.name), descStyle.Render(opt.desc))
}
menu := "[Up/Down] Select [Enter] Confirm"
menu := "[Up/Down] 선택 [Enter] 확인"
return lipgloss.JoinVertical(lipgloss.Left,
header,

View File

@@ -71,10 +71,10 @@ func (s *CodexScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
}
func (s *CodexScreen) View(ctx *Context) string {
title := styleHeader.Render("-- Codex --")
title := styleHeader.Render("-- 도감 --")
// Tab headers
tabNames := []string{"Monsters", "Items", "Events"}
tabNames := []string{"몬스터", "아이템", "이벤트"}
var tabs []string
for i, name := range tabNames {
if i == s.tab {
@@ -117,7 +117,7 @@ func (s *CodexScreen) View(ctx *Context) string {
}
completion := lipgloss.NewStyle().Foreground(colorCyan).
Render(fmt.Sprintf("Discovered: %d/%d (%.0f%%)", count, total, pct))
Render(fmt.Sprintf("발견: %d/%d (%.0f%%)", count, total, pct))
// Sort discovered keys for consistent display
discoveredKeys := make([]string, 0, len(discovered))
@@ -151,7 +151,7 @@ func (s *CodexScreen) View(ctx *Context) string {
}
}
footer := styleSystem.Render("[Tab/Left/Right] Switch Tab [Esc] Back")
footer := styleSystem.Render("[Tab/Left/Right] 탭 전환 [Esc] 뒤로")
content := lipgloss.JoinVertical(lipgloss.Center,
title,

View File

@@ -386,7 +386,7 @@ func renderMap(floor *dungeon.Floor) string {
}
total := len(floor.Rooms)
header := headerStyle.Render(fmt.Sprintf("── Catacombs B%d: %s ── %d/%d Rooms ──", floor.Number, theme.Name, explored, total))
header := headerStyle.Render(fmt.Sprintf("── 카타콤 B%d: %s ── %d/%d ──", floor.Number, theme.Name, explored, total))
return header + "\n" + dungeon.RenderFloor(floor, floor.CurrentRoom, true)
}
@@ -401,16 +401,16 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
hpBar := renderHPBar(p.HP, p.MaxHP, 20)
status := ""
if p.IsDead() {
status = " [DEAD]"
status = " [사망]"
}
sb.WriteString(fmt.Sprintf("%s (%s) %s %d/%d%s Gold: %d",
sb.WriteString(fmt.Sprintf("%s (%s) %s %d/%d%s 골드: %d",
p.Name, p.Class, hpBar, p.HP, p.MaxHP, status, p.Gold))
// Show inventory count
itemCount := len(p.Inventory)
relicCount := len(p.Relics)
if itemCount > 0 || relicCount > 0 {
sb.WriteString(fmt.Sprintf(" Items:%d Relics:%d", itemCount, relicCount))
sb.WriteString(fmt.Sprintf(" 아이템:%d 유물:%d", itemCount, relicCount))
}
sb.WriteString("\n")
}
@@ -439,7 +439,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
sb.WriteString("\n")
// Action bar
sb.WriteString(styleAction.Render("[1]Attack [2]Skill [3]Item [4]Flee [5]Wait [Tab]Target [/]Chat"))
sb.WriteString(styleAction.Render("[1]공격 [2]스킬 [3]아이템 [4]도주 [5]대기 [Tab]대상 [/]채팅"))
sb.WriteString("\n")
// Timer
@@ -448,7 +448,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
if remaining < 0 {
remaining = 0
}
sb.WriteString(styleTimer.Render(fmt.Sprintf(" Timer: %.1fs", remaining.Seconds())))
sb.WriteString(styleTimer.Render(fmt.Sprintf(" 타이머: %.1f", remaining.Seconds())))
sb.WriteString("\n")
}
@@ -458,15 +458,15 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
var skillDesc string
switch p.Class {
case entity.ClassWarrior:
skillDesc = "Skill: Taunt — enemies attack you for 2 turns"
skillDesc = "스킬: Taunt — 2턴간 적의 공격을 끌어옴"
case entity.ClassMage:
skillDesc = "Skill: Fireball — AoE 0.8x dmg to all enemies"
skillDesc = "스킬: Fireball — 전체 적에게 0.8배 피해"
case entity.ClassHealer:
skillDesc = "Skill: Heal — restore 30 HP to an ally"
skillDesc = "스킬: Heal — 아군 HP 30 회복"
case entity.ClassRogue:
skillDesc = "Skill: Scout — reveal neighboring rooms"
skillDesc = "스킬: Scout — 주변 방 공개"
}
skillDesc += fmt.Sprintf(" (%d uses left)", p.SkillUses)
skillDesc += fmt.Sprintf(" (남은 횟수: %d)", p.SkillUses)
sb.WriteString(styleSystem.Render(skillDesc))
sb.WriteString("\n")
break
@@ -484,7 +484,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
r := state.Floor.Rooms[n]
status := r.Type.String()
if r.Cleared {
status = "Cleared"
status = "클리어"
}
marker := " "
style := normalStyle
@@ -492,7 +492,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
marker = "> "
style = selectedStyle
}
sb.WriteString(style.Render(fmt.Sprintf("%sRoom %d: %s", marker, n, status)))
sb.WriteString(style.Render(fmt.Sprintf("%s %d: %s", marker, n, status)))
sb.WriteString("\n")
}
}
@@ -504,7 +504,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
branches := entity.GetBranches(p.Class)
sb.WriteString("\n")
skillStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("213")).Bold(true)
sb.WriteString(skillStyle.Render(fmt.Sprintf(" Skill Point Available! (%d unspent)", p.Skills.Points-p.Skills.Allocated)))
sb.WriteString(skillStyle.Render(fmt.Sprintf(" 스킬 포인트 사용 가능! (미사용: %d)", p.Skills.Points-p.Skills.Allocated)))
sb.WriteString("\n")
for i, branch := range branches {
key := "["
@@ -513,7 +513,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
}
nextNode := p.Skills.Allocated
if p.Skills.BranchIndex >= 0 && p.Skills.BranchIndex != i {
sb.WriteString(fmt.Sprintf(" [%s] %s (locked)\n", key, branch.Name))
sb.WriteString(fmt.Sprintf(" [%s] %s (잠김)\n", key, branch.Name))
} else if nextNode < 3 {
node := branch.Nodes[nextNode]
sb.WriteString(fmt.Sprintf(" [%s] %s -> %s\n", key, branch.Name, node.Name))
@@ -522,7 +522,7 @@ func renderHUD(state game.GameState, targetCursor int, moveCursor int, fingerpri
break
}
}
sb.WriteString("[Up/Down] Select [Enter] Move [Q] Quit")
sb.WriteString("[Up/Down] 선택 [Enter] 이동 [Q] 종료")
}
if state.Phase == game.PhaseCombat {
@@ -550,19 +550,19 @@ func renderCombatLog(log []string) string {
func colorizeLog(msg string) string {
switch {
case strings.Contains(msg, "fled"):
case strings.Contains(msg, "도주"):
return styleFlee.Render(msg)
case strings.Contains(msg, "co-op"):
case strings.Contains(msg, "협동"):
return styleCoop.Render(msg)
case strings.Contains(msg, "healed") || strings.Contains(msg, "Heal") || strings.Contains(msg, "Blessing"):
case strings.Contains(msg, "회복") || strings.Contains(msg, "Heal") || strings.Contains(msg, "치유") || strings.Contains(msg, "부활"):
return styleHeal.Render(msg)
case strings.Contains(msg, "dmg") || strings.Contains(msg, "hit") || strings.Contains(msg, "attacks") || strings.Contains(msg, "Trap"):
case strings.Contains(msg, "피해") || strings.Contains(msg, "공격") || strings.Contains(msg, "Trap") || strings.Contains(msg, "함정"):
return styleDamage.Render(msg)
case strings.Contains(msg, "Taunt") || strings.Contains(msg, "scouted"):
case strings.Contains(msg, "Taunt") || strings.Contains(msg, "정찰"):
return styleStatus.Render(msg)
case strings.Contains(msg, "gold") || strings.Contains(msg, "Gold") || strings.Contains(msg, "found"):
case strings.Contains(msg, "골드") || strings.Contains(msg, "Gold") || strings.Contains(msg, "발견"):
return styleGold.Render(msg)
case strings.Contains(msg, "defeated") || strings.Contains(msg, "cleared") || strings.Contains(msg, "Descending"):
case strings.Contains(msg, "처치") || strings.Contains(msg, "클리어") || strings.Contains(msg, "내려갑니다") || strings.Contains(msg, "정복"):
return styleSystem.Render(msg)
default:
return msg
@@ -600,14 +600,14 @@ func renderHPBar(current, max, width int) string {
func renderPartyPanel(players []*entity.Player, submittedActions map[string]string) string {
var sb strings.Builder
sb.WriteString(styleHeader.Render(" PARTY") + "\n\n")
sb.WriteString(styleHeader.Render(" 아군") + "\n\n")
for _, p := range players {
nameStr := stylePlayer.Render(fmt.Sprintf(" ♦ %s", p.Name))
classStr := styleSystem.Render(fmt.Sprintf(" (%s)", p.Class))
status := ""
if p.IsDead() {
status = styleDamage.Render(" [DEAD]")
status = styleDamage.Render(" [사망]")
}
sb.WriteString(nameStr + classStr + status + "\n")
@@ -637,7 +637,7 @@ func renderPartyPanel(players []*entity.Player, submittedActions map[string]stri
sb.WriteString(styleHeal.Render(fmt.Sprintf(" ✓ %s", action)))
sb.WriteString("\n")
} else if !p.IsOut() {
sb.WriteString(styleSystem.Render(" ... Waiting"))
sb.WriteString(styleSystem.Render(" ... 대기중"))
sb.WriteString("\n")
}
sb.WriteString("\n")
@@ -647,7 +647,7 @@ func renderPartyPanel(players []*entity.Player, submittedActions map[string]stri
func renderEnemyPanel(monsters []*entity.Monster, targetCursor int) string {
var sb strings.Builder
sb.WriteString(styleHeader.Render(" ENEMIES") + "\n\n")
sb.WriteString(styleHeader.Render(" ") + "\n\n")
for i, m := range monsters {
if m.IsDead() {
@@ -667,7 +667,7 @@ func renderEnemyPanel(monsters []*entity.Monster, targetCursor int) string {
hpBar := renderHPBar(m.HP, m.MaxHP, 12)
taunt := ""
if m.TauntTarget {
taunt = styleStatus.Render(fmt.Sprintf(" [TAUNTED %dt]", m.TauntTurns))
taunt = styleStatus.Render(fmt.Sprintf(" [도발됨 %d]", m.TauntTurns))
}
sb.WriteString(fmt.Sprintf(" %s[%d] %s %s %d/%d%s\n\n",
marker, i, styleEnemy.Render(m.Name), hpBar, m.HP, m.MaxHP, taunt))

View File

@@ -26,38 +26,38 @@ func (s *HelpScreen) View(ctx *Context) string {
}
func renderHelp(width, height int) string {
title := styleHeader.Render("── Controls ──")
title := styleHeader.Render("── 조작법 ──")
sections := []struct{ header, body string }{
{"Lobby", ` [C] Create room [J] Join by code
[Enter] Join selected room
[D] Daily Challenge [H] Hard Mode toggle
[Q] Back to title`},
{"Exploration", ` [Up/Down] Select room
[Enter] Move to room
[[] / []] Allocate skill point (branch 1/2)
[/] Chat
[Q] Quit`},
{"Combat (10s per turn)", ` [1] Attack [2] Skill
[3] Use Item [4] Flee
[5] Defend [Tab] Switch Target
[/] Chat`},
{"Shop", ` [1-3] Buy item
[Q] Leave shop`},
{"Classes", ` Warrior 120HP 12ATK 8DEF Taunt (draw fire 2t)
Mage 70HP 20ATK 3DEF Fireball (AoE 0.8x)
Healer 90HP 8ATK 5DEF Heal (restore 30HP)
Rogue 85HP 15ATK 4DEF Scout (reveal rooms)`},
{"Multiplayer", `Up to 4 players per room
Co-op bonus: +10% dmg when 2+ target same enemy
Class combos trigger bonus effects
All players ready → game starts`},
{"Tips", `Skills have 3 uses per combat
Items are limited to 10 per player
Dead players revive next floor at 30% HP
Bosses appear at floors 5, 10, 15, 20
Skill points: 1 per floor clear (max 3)
Weekly mutations rotate gameplay modifiers`},
{"로비", ` [C] 방 만들기 [J] 코드로 참가
[Enter] 선택한 방 참가
[D] 일일 도전 [H] 하드 모드 전환
[Q] 타이틀로 돌아가기`},
{"탐험", ` [Up/Down] 방 선택
[Enter] 방으로 이동
[[] / []] 스킬 포인트 배분 (분기 1/2)
[/] 채팅
[Q] 종료`},
{"전투 (턴당 10초)", ` [1] 공격 [2] 스킬
[3] 아이템 사용 [4] 도주
[5] 방어 [Tab] 대상 변경
[/] 채팅`},
{"상점", ` [1-3] 아이템 구매
[Q] 상점 나가기`},
{"직업", ` Warrior 120HP 12ATK 8DEF Taunt (2턴간 적 공격 유도)
Mage 70HP 20ATK 3DEF Fireball (광역 0.8)
Healer 90HP 8ATK 5DEF Heal (HP 30 회복)
Rogue 85HP 15ATK 4DEF Scout (주변 방 공개)`},
{"멀티플레이", `방당 최대 4명
협동 보너스: 2명 이상이 같은 적 공격 시 피해 +10%
직업 콤보로 추가 효과 발동
모든 플레이어 준비 완료 시 게임 시작`},
{"", `스킬은 전투당 3회 사용 가능
아이템은 플레이어당 10개 제한
사망한 플레이어는 다음 층에서 HP 30%로 부활
보스는 5, 10, 15, 20층에 등장
스킬 포인트: 층 클리어당 1포인트 (최대 3)
주간 변이가 게임플레이를 변경`},
}
var content string
@@ -69,7 +69,7 @@ func renderHelp(width, height int) string {
content += bodyStyle.Render(s.body) + "\n\n"
}
footer := styleSystem.Render("[H] Back")
footer := styleSystem.Render("[H] 뒤로")
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
lipgloss.JoinVertical(lipgloss.Center, title, "", content, footer))

View File

@@ -43,10 +43,10 @@ func (s *LeaderboardScreen) View(ctx *Context) string {
}
func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRecord, tab, width, height int) string {
title := styleHeader.Render("── Leaderboard ──")
title := styleHeader.Render("── 리더보드 ──")
// Tab header
tabs := []string{"Floor", "Gold", "Daily"}
tabs := []string{"층수", "골드", "일일"}
var tabLine string
for i, t := range tabs {
if i == tab {
@@ -60,7 +60,7 @@ func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRec
switch tab {
case 0: // By Floor
content += styleCoop.Render(" Top by Floor") + "\n"
content += styleCoop.Render(" 층수 순위") + "\n"
for i, r := range byFloor {
if i >= 10 {
break
@@ -75,7 +75,7 @@ func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRec
r.Floor, styleGold.Render(fmt.Sprintf("%dg", r.Score)))
}
case 1: // By Gold
content += styleCoop.Render(" Top by Gold") + "\n"
content += styleCoop.Render(" 골드 순위") + "\n"
for i, r := range byGold {
if i >= 10 {
break
@@ -90,9 +90,9 @@ func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRec
r.Floor, styleGold.Render(fmt.Sprintf("%dg", r.Score)))
}
case 2: // Daily
content += styleCoop.Render(fmt.Sprintf(" Daily Challenge — %s", time.Now().Format("2006-01-02"))) + "\n"
content += styleCoop.Render(fmt.Sprintf(" 일일 도전 — %s", time.Now().Format("2006-01-02"))) + "\n"
if len(daily) == 0 {
content += " No daily runs yet today.\n"
content += " 오늘 일일 도전 기록이 없습니다.\n"
}
for i, r := range daily {
if i >= 20 {
@@ -105,7 +105,7 @@ func renderLeaderboard(byFloor, byGold []store.RunRecord, daily []store.DailyRec
}
}
footer := styleSystem.Render("\n[Tab] Switch Tab [L] Back")
footer := styleSystem.Render("\n[Tab] 탭 전환 [L] 뒤로")
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
lipgloss.JoinVertical(lipgloss.Center, title, tabLine, "", content, footer))

View File

@@ -54,9 +54,9 @@ func (s *LobbyScreen) refreshLobby(ctx *Context) {
rooms := ctx.Lobby.ListRooms()
s.rooms = make([]roomInfo, len(rooms))
for i, r := range rooms {
status := "Waiting"
status := "대기중"
if r.Status == game.RoomPlaying {
status = "Playing"
status = "진행중"
}
players := make([]playerInfo, len(r.Players))
for j, p := range r.Players {
@@ -108,7 +108,7 @@ func (s *LobbyScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
// Normal lobby key handling
if isKey(key, "c") {
if ctx.Lobby != nil {
code := ctx.Lobby.CreateRoom(ctx.PlayerName + "'s Room")
code := ctx.Lobby.CreateRoom(ctx.PlayerName + "의 방")
ctx.Lobby.JoinRoom(code, ctx.PlayerName, ctx.Fingerprint)
ctx.RoomCode = code
return NewClassSelectScreen(), nil
@@ -135,7 +135,7 @@ func (s *LobbyScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
} else if isKey(key, "d") {
// Daily Challenge: create a private solo daily session
if ctx.Lobby != nil {
code := ctx.Lobby.CreateRoom(ctx.PlayerName + "'s Daily")
code := ctx.Lobby.CreateRoom(ctx.PlayerName + "의 일일 도전")
if err := ctx.Lobby.JoinRoom(code, ctx.PlayerName, ctx.Fingerprint); err == nil {
ctx.RoomCode = code
room := ctx.Lobby.GetRoom(code)
@@ -200,14 +200,14 @@ func renderLobby(state lobbyState, width, height int) string {
Border(lipgloss.RoundedBorder()).
Padding(0, 1)
header := headerStyle.Render(fmt.Sprintf("── Lobby ── %d online ──", state.online))
menu := "[C] Create Room [J] Join by Code [D] Daily Challenge [Up/Down] Select [Enter] Join [Q] Back"
header := headerStyle.Render(fmt.Sprintf("── 로비 ── %d명 접속중 ──", state.online))
menu := "[C] 방 만들기 [J] 코드로 참가 [D] 일일 도전 [Up/Down] 선택 [Enter] 참가 [Q] 뒤로"
if state.hardUnlocked {
hardStatus := "OFF"
if state.hardMode {
hardStatus = "ON"
}
menu += fmt.Sprintf(" [H] Hard Mode: %s", hardStatus)
menu += fmt.Sprintf(" [H] 하드 모드: %s", hardStatus)
}
roomList := ""
@@ -234,11 +234,11 @@ func renderLobby(state lobbyState, width, height int) string {
}
}
if roomList == "" {
roomList = " No rooms available. Create one!"
roomList = " 방이 없습니다. 새로 만드세요!"
}
if state.joining {
inputStr := state.codeInput + strings.Repeat("_", 4-len(state.codeInput))
roomList += fmt.Sprintf("\n Enter room code: [%s] (Esc to cancel)\n", inputStr)
roomList += fmt.Sprintf("\n 방 코드 입력: [%s] (Esc로 취소)\n", inputStr)
}
return lipgloss.JoinVertical(lipgloss.Left,

View File

@@ -39,7 +39,7 @@ func (s *NicknameScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
gs := NewGameScreen()
gs.gameState = ctx.Session.GetState()
ctx.Session.TouchActivity(ctx.Fingerprint)
ctx.Session.SendChat("System", ctx.PlayerName+" reconnected!")
ctx.Session.SendChat("System", ctx.PlayerName+" 재접속!")
return gs, gs.pollState()
}
}
@@ -66,7 +66,7 @@ func (s *NicknameScreen) View(ctx *Context) string {
}
func renderNickname(input string, width, height int) string {
title := styleHeader.Render("── Enter Your Name ──")
title := styleHeader.Render("── 이름을 입력하세요 ──")
display := input
if display == "" {
@@ -81,8 +81,8 @@ func renderNickname(input string, width, height int) string {
Padding(0, 2).
Render(stylePlayer.Render(display))
hint := styleSystem.Render(fmt.Sprintf("(%d/12 characters)", len(input)))
footer := styleAction.Render("[Enter] Confirm [Esc] Cancel")
hint := styleSystem.Render(fmt.Sprintf("(%d/12 글자)", len(input)))
footer := styleAction.Render("[Enter] 확인 [Esc] 취소")
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
lipgloss.JoinVertical(lipgloss.Center, title, "", inputBox, hint, "", footer))

View File

@@ -56,30 +56,30 @@ func renderResult(state game.GameState, rankings []store.RunRecord) string {
// Title
if state.Victory {
sb.WriteString(styleHeal.Render(" ✦ VICTORY ✦ ") + "\n\n")
sb.WriteString(styleSystem.Render(" You conquered the Catacombs!") + "\n\n")
sb.WriteString(styleHeal.Render(" ✦ 승리 ✦ ") + "\n\n")
sb.WriteString(styleSystem.Render(" 카타콤을 정복했습니다!") + "\n\n")
} else {
sb.WriteString(styleDamage.Render(" ✦ DEFEAT ✦ ") + "\n\n")
sb.WriteString(styleSystem.Render(fmt.Sprintf(" Fallen on floor B%d", state.FloorNum)) + "\n\n")
sb.WriteString(styleDamage.Render(" ✦ 패배 ✦ ") + "\n\n")
sb.WriteString(styleSystem.Render(fmt.Sprintf(" B%d층에서 쓰러졌습니다", state.FloorNum)) + "\n\n")
}
// Player summary
sb.WriteString(styleHeader.Render("── Party Summary ──") + "\n\n")
sb.WriteString(styleHeader.Render("── 파티 요약 ──") + "\n\n")
totalGold := 0
for _, p := range state.Players {
status := styleHeal.Render("Alive")
status := styleHeal.Render("생존")
if p.IsDead() {
status = styleDamage.Render("Dead")
status = styleDamage.Render("사망")
}
sb.WriteString(fmt.Sprintf(" %s (%s) %s Gold: %d Items: %d Relics: %d\n",
sb.WriteString(fmt.Sprintf(" %s (%s) %s 골드: %d 아이템: %d 유물: %d\n",
stylePlayer.Render(p.Name), p.Class, status, p.Gold, len(p.Inventory), len(p.Relics)))
totalGold += p.Gold
}
sb.WriteString(fmt.Sprintf("\n Total Gold: %s\n", styleGold.Render(fmt.Sprintf("%d", totalGold))))
sb.WriteString(fmt.Sprintf("\n 총 골드: %s\n", styleGold.Render(fmt.Sprintf("%d", totalGold))))
// Rankings
if len(rankings) > 0 {
sb.WriteString("\n" + styleHeader.Render("── Top Runs ──") + "\n\n")
sb.WriteString("\n" + styleHeader.Render("── 최고 기록 ──") + "\n\n")
for i, r := range rankings {
medal := " "
switch i {
@@ -90,11 +90,11 @@ func renderResult(state game.GameState, rankings []store.RunRecord) string {
case 2:
medal = styleGold.Render("🥉")
}
sb.WriteString(fmt.Sprintf(" %s %s Floor B%d Score: %d\n", medal, r.Player, r.Floor, r.Score))
sb.WriteString(fmt.Sprintf(" %s %s B%d 점수: %d\n", medal, r.Player, r.Floor, r.Score))
}
}
sb.WriteString("\n" + styleAction.Render(" [Enter] Return to Lobby") + "\n")
sb.WriteString("\n" + styleAction.Render(" [Enter] 로비로 돌아가기") + "\n")
return sb.String()
}

View File

@@ -27,13 +27,13 @@ func (s *ShopScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
idx := int(key.String()[0] - '1')
switch ctx.Session.BuyItem(ctx.Fingerprint, idx) {
case game.BuyOK:
s.shopMsg = "Purchased!"
s.shopMsg = "구매 완료!"
case game.BuyNoGold:
s.shopMsg = "Not enough gold!"
s.shopMsg = "골드가 부족합니다!"
case game.BuyInventoryFull:
s.shopMsg = "Inventory full!"
s.shopMsg = "인벤토리가 가득 찼습니다!"
default:
s.shopMsg = "Cannot buy that!"
s.shopMsg = "구매할 수 없습니다!"
}
s.gameState = ctx.Session.GetState()
}
@@ -76,23 +76,23 @@ func renderShop(state game.GameState, width, height int, shopMsg string) string
Foreground(lipgloss.Color("196")).
Bold(true)
header := headerStyle.Render("── Shop ──")
header := headerStyle.Render("── 상점 ──")
// Show current player's gold
goldLine := ""
for _, p := range state.Players {
inventoryCount := len(p.Inventory)
goldLine += goldStyle.Render(fmt.Sprintf(" %s — Gold: %d Items: %d/10", p.Name, p.Gold, inventoryCount))
goldLine += goldStyle.Render(fmt.Sprintf(" %s — 골드: %d 아이템: %d/10", p.Name, p.Gold, inventoryCount))
goldLine += "\n"
}
items := ""
for i, item := range state.ShopItems {
label := itemTypeLabel(item)
items += fmt.Sprintf(" [%d] %s %s — %d gold\n", i+1, item.Name, label, item.Price)
items += fmt.Sprintf(" [%d] %s %s — %d 골드\n", i+1, item.Name, label, item.Price)
}
menu := "[1-3] Buy [Q] Leave Shop"
menu := "[1-3] 구매 [Q] 상점 나가기"
parts := []string{header, "", goldLine, items, "", menu}
if shopMsg != "" {

View File

@@ -33,22 +33,22 @@ func (s *StatsScreen) View(ctx *Context) string {
}
func renderStats(playerName string, stats store.PlayerStats, width, height int) string {
title := styleHeader.Render("── Player Statistics ──")
title := styleHeader.Render("── 플레이어 통계 ──")
var content string
content += stylePlayer.Render(fmt.Sprintf(" %s", playerName)) + "\n\n"
content += fmt.Sprintf(" Total Runs: %s\n", styleGold.Render(fmt.Sprintf("%d", stats.TotalRuns)))
content += fmt.Sprintf(" Best Floor: %s\n", styleGold.Render(fmt.Sprintf("B%d", stats.BestFloor)))
content += fmt.Sprintf(" Total Gold: %s\n", styleGold.Render(fmt.Sprintf("%d", stats.TotalGold)))
content += fmt.Sprintf(" Victories: %s\n", styleHeal.Render(fmt.Sprintf("%d", stats.Victories)))
content += fmt.Sprintf(" 총 플레이: %s\n", styleGold.Render(fmt.Sprintf("%d", stats.TotalRuns)))
content += fmt.Sprintf(" 최고 층: %s\n", styleGold.Render(fmt.Sprintf("B%d", stats.BestFloor)))
content += fmt.Sprintf(" 총 골드: %s\n", styleGold.Render(fmt.Sprintf("%d", stats.TotalGold)))
content += fmt.Sprintf(" 승리 횟수: %s\n", styleHeal.Render(fmt.Sprintf("%d", stats.Victories)))
winRate := 0.0
if stats.TotalRuns > 0 {
winRate = float64(stats.Victories) / float64(stats.TotalRuns) * 100
}
content += fmt.Sprintf(" Win Rate: %s\n", styleSystem.Render(fmt.Sprintf("%.1f%%", winRate)))
content += fmt.Sprintf(" 승률: %s\n", styleSystem.Render(fmt.Sprintf("%.1f%%", winRate)))
footer := styleSystem.Render("[S] Back")
footer := styleSystem.Render("[S] 뒤로")
return lipgloss.Place(width, height, lipgloss.Center, lipgloss.Center,
lipgloss.JoinVertical(lipgloss.Center, title, "", content, "", footer))

View File

@@ -44,7 +44,7 @@ func (s *TitleScreen) Update(msg tea.Msg, ctx *Context) (Screen, tea.Cmd) {
gs := NewGameScreen()
gs.gameState = ctx.Session.GetState()
ctx.Session.TouchActivity(ctx.Fingerprint)
ctx.Session.SendChat("System", ctx.PlayerName+" reconnected!")
ctx.Session.SendChat("System", ctx.PlayerName+" 재접속!")
return gs, gs.pollState()
}
}
@@ -101,7 +101,7 @@ func renderTitle(width, height int) string {
subtitle := lipgloss.NewStyle().
Foreground(colorGray).
Render("⚔ A Cooperative Dungeon Crawler ⚔")
Render("⚔ 협동 던전 크롤러 ⚔")
server := lipgloss.NewStyle().
Foreground(colorCyan).
@@ -110,7 +110,7 @@ func renderTitle(width, height int) string {
menu := lipgloss.NewStyle().
Foreground(colorWhite).
Bold(true).
Render("[Enter] Start [H] Help [S] Stats [A] Achievements [L] Leaderboard [C] Codex [Q] Quit")
Render("[Enter] 시작 [H] 도움말 [S] 통계 [A] 업적 [L] 리더보드 [C] 도감 [Q] 종료")
content := lipgloss.JoinVertical(lipgloss.Center,
logo,

View File

@@ -83,7 +83,7 @@ func (s *WaitingScreen) View(ctx *Context) string {
notReadyStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("240"))
header := headerStyle.Render(fmt.Sprintf("── Waiting Room [%s] ──", ctx.RoomCode))
header := headerStyle.Render(fmt.Sprintf("── 대기실 [%s] ──", ctx.RoomCode))
playerList := ""
if ctx.Lobby != nil {
@@ -92,7 +92,7 @@ func (s *WaitingScreen) View(ctx *Context) string {
for _, p := range room.Players {
status := notReadyStyle.Render("...")
if p.Ready {
status = readyStyle.Render("READY")
status = readyStyle.Render("준비 완료")
}
cls := p.Class
if cls == "" {
@@ -103,11 +103,11 @@ func (s *WaitingScreen) View(ctx *Context) string {
}
}
menu := "[Enter] Ready"
menu := "[Enter] 준비"
if s.ready {
menu = "Waiting for other players..."
menu = "다른 플레이어를 기다리는 중..."
}
menu += " [Esc] Leave"
menu += " [Esc] 나가기"
return lipgloss.JoinVertical(lipgloss.Left,
header,