This is a Layer 2 document. See CLAUDE.md for project overview.
Voice & Tone Guide (Korean UX Writing)
Status: Active Applies to: All user-facing strings in src/config/i18n/ko.ts and en.ts
Current vocabulary override: top-level navigation is
홈 / 클럽 / 경기 / 기록 / 프로필. Use일정for scheduled sessions,경기for played/scored matches,클럽for clubs, and번개/번개 찾기for pickup discovery. Older examples in this historical guide that use모임for scheduled sessions should be interpreted as일정unless the text is describing an umbrella concept.
1. TwoMore's Voice Identity
TwoMore speaks like a reliable club mate (믿음직한 클럽 동료) — someone who played with you last Saturday, remembers your schedule, and texts you reminders without being annoying. Not a corporate announcer. Not a chatbot trying too hard to be cute.
Voice Attributes
| Attribute | What it means | What it does NOT mean |
|---|---|---|
| 친근한 (Warm) | Conversational, approachable | Overly casual, slangy, using 해체 |
| 명확한 (Clear) | One meaning per sentence, no ambiguity | Verbose, over-explaining |
| 간결한 (Concise) | Every word earns its place | Terse, robotic, dropping needed context |
| 존중하는 (Respectful) | Acknowledges user's time and autonomy | Patronizing, commanding, guilt-tripping |
| 공감하는 (Empathetic) | Matches the user's emotional state | Fake cheerfulness during errors |
2. Korean Speech Level: 해요체 (Polite Conversational)
The Standard: 해요체 Throughout
TwoMore uses 해요체 as the primary speech level for all user-facing text. This matches the industry standard set by Toss, KakaoTalk, 당근마켓, and Naver.
해요체 (TwoMore default): "모임이 만들어졌어요"
합니다체 (too formal): "모임이 생성되었습니다"
해체 (too casual): "모임 만들었어"Why 해요체
| App | Speech Level | Rationale |
|---|---|---|
| 토스 (Toss) | 해요체 | "금융이나 IT 업계에서 쓰던 어려운 용어와 딱딱한 뉘앙스를 쉽고 친절하게 고쳐" |
| 카카오톡 | 해요체 | Messaging context demands conversational warmth |
| 카카오뱅크 | 해요체 (financial confirmations mix 합니다체) | Banking trust needs warmth without losing reliability |
| 당근마켓 | 해요체 | Neighborhood-friendly, "이웃과 대화하는 느낌" |
| 배달의민족 | 해요체 + playful wordplay | Humor lives in word choice, not formality level |
| 네이버 | 해요체 (Naver Pay, Maps) / 합니다체 (legal, settings) | Modern products are 해요체; legacy pages still 합니다체 |
When 합니다체 Is Acceptable
Only in these narrow contexts:
| Context | Example | Reason |
|---|---|---|
| Accessibility hints (screenreader) | "설정 화면으로 이동합니다" | VoiceOver convention; formal = predictable |
| Legal text (terms, privacy) | "개인정보를 수집합니다" | Legal requirement |
| System-level alerts | "OTA 업데이트는 빌드된 앱에서만 동작합니다" | Platform-level, not conversational |
Never mix 합니다체 into conversational UI. A single 합니다 in a 해요체 flow feels jarring.
3. Sentence Patterns by Context
3.1 Confirmations & Success (완료/성공)
Pattern: Declarative past tense — ~됐어요, ~었어요
GOOD: "모임이 만들어졌어요!" (It's been created!)
GOOD: "참석 완료!" (RSVP confirmed!)
GOOD: "점수가 저장됐어요" (Score saved)
GOOD: "코드가 복사됐어요!" (Code copied!)
BAD: "모임이 성공적으로 생성되었습니다" (합니다체 + 한자어 '성공적')
BAD: "저장 완료되었습니다" (합니다체)Toss's principle: Find Hidden Emotion — celebrate completions. A loan repayment cleared? "축하해요!" A session created? Add the ! to share the excitement.
3.2 Calls to Action (CTA Buttons)
Pattern: ~하기 (verb stem + 하기) for primary actions
PRIMARY BUTTONS:
"시작하기" (Start)
"참석하기" (Attend)
"모임 만들기" (Create session)
"공유하기" (Share)
"기록하기" (Log it)
SECONDARY/SHORT BUTTONS:
"확인" (Confirm)
"취소" (Cancel)
"저장" (Save)
"삭제" (Delete)
"다음" (Next)
"완료" (Done)Rules:
- Primary CTA (bottom of screen, main action): use
~하기form — "참석하기", "모임 만들기" - Secondary/inline buttons: bare noun — "확인", "취소", "저장"
- Destructive actions: bare noun, never
~하기— "삭제", "나가기" (exception: 나가기 is a verb phrase) - Never use
~하세요(imperative) on buttons — it sounds like a command - Never use
~해요on buttons — it sounds like narration, not an action
Industry comparison:
| App | Primary CTA style | Example |
|---|---|---|
| Toss | ~하기 | "송금하기", "확인하기" |
| KakaoBank | ~하기 | "계좌 개설하기" |
| 당근마켓 | ~하기 | "글쓰기", "채팅하기" |
| 배민 | ~하기 (+ playful) | "주문하기", "찜하기" |
| Naver Pay | ~하기 | "결제하기" |
3.3 Suggestions & Guidance
Pattern: ~해 보세요 (try doing ~) or ~해 볼까요? (shall we try ~?)
GOOD: "클럽에 가입하고 테니스를 시작해 보세요!" (gentle suggestion)
GOOD: "첫 모임을 만들어 보세요" (encouraging nudge)
GOOD: "초대코드로 가입하거나 모집 중인 번개를 찾아보세요"
BAD: "클럽에 가입하세요" (too commanding — 하세요 = imperative)
BAD: "모임을 만드세요" (sounds like an order)
BAD: "클럽에 가입해야 해요" (obligation — 해야 해요 = "must")Toss's principle: Suggest Over Force — "권유와 강요는 다릅니다" (suggestion and coercion are different). Offer choices; never guilt or scare.
3.4 Confirmation Dialogs (할까요?)
Pattern: ~할까요? (shall we ~?) for destructive or important actions
GOOD: "이 클럽을 나가시겠어요?" (Are you sure you want to leave?)
GOOD: "클럽을 삭제할까요?" (Delete the club?)
GOOD: "라운드 2를 생성할까요?" (Generate round 2?)
GOOD: "8명으로 3라운드를 생성할까요?" (Generate 3 rounds with 8 players?)
BAD: "삭제하시겠습니까?" (합니다체, too formal)
BAD: "삭제해요" (too casual for a destructive dialog)
BAD: "정말 삭제하시겠어요? 되돌릴 수 없어요!" (fear-mongering)For destructive actions, state the consequence neutrally:
GOOD: "클럽을 삭제하면 모든 데이터가 사라져요. 정말 삭제하시겠어요?"
BAD: "주의! 이 작업은 되돌릴 수 없습니다!!"3.5 Empty States
Pattern: Declarative situation + suggestive CTA
STRUCTURE:
Title: "아직 [thing]이(가) 없어요" (There's no [thing] yet)
Body: "[action]하면 여기에 표시돼요" (When you [action], it'll show here)
CTA: "[action]하기" or "~해 보세요"
EXAMPLES:
Title: "예정된 모임이 없어요"
Body: "클럽에서 모임을 만들거나 공개 모임에 참가해 보세요!"
CTA: [모임 찾기] [모임 만들기]
Title: "아직 경기 기록이 없어요"
Body: "경기 결과가 쌓이면 여기에 표시돼요. 첫 모임에 참가해 보세요!"
CTA: [모임 참가하기]
BAD:
Title: "데이터 없음" (robotic)
Body: "표시할 항목이 없습니다" (합니다체 + generic)당근마켓's pattern: empty states feel like a friendly nudge from a neighbor, not a system message. "아직 동네 이웃이 없어요. 관심사를 설정하면 이웃을 추천해 드려요!" — same energy TwoMore should have.
3.6 Error Messages
Pattern: Empathize + Explain + Guide
STRUCTURE:
"앗, [what went wrong]. [what to do next]."
EXAMPLES:
"앗, 모임을 만들지 못했어요. 다시 시도해 주세요."
"인터넷 연결을 확인해 주세요."
"초대 코드가 맞지 않아요. 다시 확인해 주세요."
BAD:
"Error: Session creation failed" (English, technical)
"세션 생성에 실패했습니다 (500)" (합니다체, error code leak)
"오류가 발생했습니다. 관리자에게 문의하세요" (unhelpful, formal)Toss's 6 Error Principles applied to TwoMore:
| Principle | TwoMore application |
|---|---|
| Prevention is best | Disable buttons when action is impossible; gray out past dates |
| Appropriate component | Toast for transient errors; Dialog for blocking errors |
| Self-directed solutions | Always include what the user can do next |
| User-friendly language | "모임을 만들지 못했어요" not "세션 생성 실패" |
| Reduce friction | Provide a retry button, not just text |
| Minimize negative emotion | "앗" softens the blow; never blame the user |
Error verb choices:
| Pattern | Tone | When to use |
|---|---|---|
~지 못했어요 | Soft, empathetic | Default — the system couldn't do it |
~에 실패했어요 | Slightly harder | Acceptable for auth failures where user might have caused it |
~할 수 없었어요 | Neutral | General inability — "불러올 수 없었어요" |
~이(가) 안 됐어요 | Too colloquial | Avoid in production copy |
Preferred: ~지 못했어요 (couldn't do X) as the default error verb. It puts responsibility on the system, not the user.
3.7 Notifications & Alerts
Pattern: Informative ~드려요 or factual ~이에요
GOOD: "새 모임이 열리면 알려 드려요"
GOOD: "모임 24시간·2시간 전에 알려 드려요"
GOOD: "미납 회비가 있으면 알려 드려요"
BAD: "새 세션 생성 시 알림을 전송합니다" (합니다체, 한자어)
BAD: "모임 알림 ON" (English mixed in)3.8 Loading States
Pattern: ~는 중... (in progress) or ~고 있어요
GOOD: "불러오는 중..."
GOOD: "새 버전을 다운로드하고 있어요"
BAD: "로딩 중..." (English loanword 로딩 is acceptable but 불러오는 is warmer)
BAD: "데이터 로드 중" (too technical)4. Word Choice Rules
4.1 Pure Korean Over 한자어
Following Toss's Easy to Speak principle — if you can't say it naturally in conversation, rewrite it.
| Avoid (한자어/technical) | Prefer (순우리말/conversational) |
|---|---|
| 생성 | 만들기 |
| 삭제하시겠습니까 | 삭제할까요 |
| 성공적으로 완료 | ~됐어요 |
| 일시적 오류 발생 | 앗, 잠깐 문제가 생겼어요 |
| 세션 | 모임 |
| 인원 | 명/사람 |
| 등록 | 추가/넣기 |
| 조회 | 보기/찾기 |
| 설정 완료 | 설정됐어요 |
| 전송 | 보내기 |
| 변경사항 적용 | 바뀐 내용 저장 |
4.2 Toss's Universal Words Principle
Avoid words that depend on age, education, or industry background:
BAD: "RSVP 마감" → GOOD: "참석 신청 마감" (but RSVP is known in tennis culture — acceptable)
BAD: "Elo 레이팅 변동" → GOOD: "실력 점수 변화" (for general users; Elo is fine for tennis enthusiasts)
BAD: "로테이션 알고리즘" → GOOD: "경기 배정 방식"Domain exception: Tennis-specific terms (Elo, RSVP, 복식, 단식, 타이브레이크) are acceptable because the audience knows them. But always provide context on first encounter.
4.3 Weed Cutting (잡초 제거)
Toss's principle: remove words that add no meaning on small screens.
BAD: "앞으로 이 기능은 여기서 이용하실 수 있어요"
GOOD: "이 기능은 여기서 이용할 수 있어요"
BAD: "현재 진행 중인 모임이 없습니다"
GOOD: "진행 중인 모임이 없어요"
BAD: "성공적으로 참석 처리가 완료되었습니다"
GOOD: "참석 완료!"5. Emoji Policy
TwoMore: Minimal, Purposeful Emoji
| Context | Emoji allowed? | Example |
|---|---|---|
| Celebrations (toast, success) | Limited | "참석 완료!" (no emoji needed; ! suffices) |
| Empty states | No | Use illustration/icon, not emoji |
| Error messages | No | "앗" is the softener, not an emoji |
| Notifications | No | Clean text only |
| Informal nudges | Sparingly | Achievement milestones may use sport emoji |
| Marketing / onboarding | Sparingly | Tagline can have subtle accent |
Industry reference:
| App | Emoji usage |
|---|---|
| Toss | Almost zero — relies on illustration + animation |
| KakaoBank | Zero in-app; emoticons in KakaoTalk integration |
| 당근마켓 | Minimal — uses warm illustration style instead |
| 배민 | Uses custom 배민체 font for personality, not emoji |
| Naver | Zero in serious products (Pay, Maps); some in Cafe |
Rule: If you need an emoji to convey tone, the copy itself is weak. Rewrite the text instead.
6. Korean / English Tone Mapping
How TwoMore Korean maps to English equivalents
| TwoMore Korean tone | English equivalent app | English tone |
|---|---|---|
| 해요체 conversational warmth | Stripe (clear, respectful) | "Your payment was successful" |
| Concise, one-task-per-screen | Notion (minimal, functional) | "No pages here yet. Create your first page." |
| Empathetic errors | Slack (friendly acknowledgment) | "Something went wrong. Try again?" |
| Celebratory completions | Duolingo (encouraging) | "Nice! Session created." |
| Suggestive CTAs | Airbnb (inviting) | "Try hosting a game" |
English i18n tone rules
When writing en.ts, the English should feel like Stripe meets Notion — clean, respectful, slightly warm but never corporate:
GOOD: "Session created!" (matches "모임이 만들어졌어요!")
GOOD: "Couldn't create the session. Try again." (matches "앗, 모임을 만들지 못했어요")
GOOD: "No upcoming sessions" (matches "예정된 모임이 없어요")
GOOD: "Join a session to get started" (matches "모임에 참가해 보세요!")
BAD: "Session creation successful" (too formal, passive)
BAD: "Error: Failed to create session" (too technical)
BAD: "Oopsie! Something broke!" (too cutesy)
BAD: "There are currently no sessions" (verbose)English button labels:
| Korean | English | Notes |
|---|---|---|
| ~하기 (primary CTA) | Verb phrase | "Create session", "Join club", "Start" |
| Bare noun (secondary) | Bare noun or short verb | "Save", "Cancel", "Delete" |
| ~해 보세요 (suggestion) | "Try ~ing" or infinitive | "Try creating a session" |
| ~할까요? (confirmation) | "~?" question | "Delete this club?" |
7. Codebase Audit: Current Issues
Analysis of src/config/i18n/ko.ts (as of 2026-03-28):
7.1 Inconsistent Error Verb Forms
The codebase mixes two error patterns:
// Pattern A: ~지 못했어요 (PREFERRED — system couldn't do it)
sessionCreateFailed: '앗, 모임을 만들지 못했어요. 다시 시도해 주세요.';
sessionStartFailed: '앗, 모임을 시작하지 못했어요. 다시 시도해 주세요.';
// Pattern B: ~에 실패했어요 (LESS PREFERRED — sounds like a failure report)
rsvpFailed: '앗, 참석 응답에 실패했어요. 다시 시도해 주세요.';
joinClubFailed: '앗, 클럽 가입에 실패했어요. 다시 시도해 주세요.';
leaveClubFailed: '앗, 클럽 탈퇴에 실패했어요. 다시 시도해 주세요.';
profileSaveFailed: '앗, 프로필 저장에 실패했어요. 다시 시도해 주세요.';Fix: Standardize all errors to Pattern A (~지 못했어요):
rsvpFailed-> "앗, 참석 응답을 처리하지 못했어요. 다시 시도해 주세요."joinClubFailed-> "앗, 클럽에 가입하지 못했어요. 다시 시도해 주세요."leaveClubFailed-> "앗, 클럽을 나가지 못했어요. 다시 시도해 주세요."profileSaveFailed-> "앗, 프로필을 저장하지 못했어요. 다시 시도해 주세요."
7.2 합니다체 Leaking Into Conversational UI
// These are accessibility hints — 합니다체 is CORRECT here:
prevMonthHint: '이전 달로 이동합니다'; // OK (a11y)
settingsHint: '설정 화면으로 이동합니다'; // OK (a11y)
// But this is a user-facing message, NOT an a11y hint — should be 해요체:
devModeOtaOnly: 'OTA 업데이트는 빌드된 앱에서만 동작합니다.';
// FIX: "OTA 업데이트는 빌드된 앱에서만 동작해요."
pinMax: '최대 4개까지 선택 가능합니다';
// FIX: "최대 4개까지 선택할 수 있어요"
// Mixed in a single message — jarring:
staleDataMessage: '최신 정보를 불러오지 못했어요. 이전 데이터를 표시합니다.';
// FIX: "최신 정보를 불러오지 못했어요. 이전 데이터를 보여 드릴게요."7.3 Repetitive Error Template
All errors follow an identical template: "앗, [X]하지 못했어요. 다시 시도해 주세요." While consistent, this becomes meaningless when users see it multiple times. Where possible, add specific guidance:
// Current (generic):
scoreSubmitFailed: '앗, 점수를 등록하지 못했어요. 다시 시도해 주세요.';
// Better (specific):
scoreSubmitFailed: '앗, 점수를 등록하지 못했어요. 인터넷 연결을 확인하고 다시 시도해 주세요.';
// Current (generic):
invalidInviteCode: '초대 코드가 맞지 않아요. 다시 확인해 주세요.';
// Better (specific):
invalidInviteCode: '초대 코드가 맞지 않아요. 관리자에게 코드를 다시 받아 보세요.';8. Quick Reference Card
Sentence Ending Cheat Sheet
| Context | Ending | Example |
|---|---|---|
| Success toast | ~됐어요 / ~완료! | "저장됐어요", "참석 완료!" |
| Primary CTA button | ~하기 | "참석하기", "모임 만들기" |
| Secondary button | Bare noun | "확인", "취소", "저장" |
| Suggestion (empty state body) | ~해 보세요 | "첫 모임을 만들어 보세요" |
| Confirmation dialog | ~할까요? / ~겠어요? | "삭제할까요?", "나가시겠어요?" |
| Error title | ~지 못했어요 | "불러오지 못했어요" |
| Error guidance | ~해 주세요 | "다시 시도해 주세요" |
| Status label | Noun / ~중 | "진행 중", "모집 중", "대기" |
| Notification description | ~드려요 | "알려 드려요" |
| Loading | ~는 중... | "불러오는 중..." |
| A11y hint | ~합니다 | "설정 화면으로 이동합니다" |
Do / Don't Summary
| DO | DON'T |
|---|---|
| 해요체 for all conversational UI | 합니다체 in user-facing messages |
| "~지 못했어요" for errors | "~에 실패했습니다" |
| "~해 보세요" for suggestions | "~하세요" (imperative commands) |
| "~할까요?" for confirmations | "~하시겠습니까?" |
| Pure Korean where possible | Unnecessary 한자어 |
| State consequence neutrally | Fear-based warnings ("주의!", "경고!") |
| Guide toward next action | Dead-end error messages |
! for celebrations | Emoji for emphasis |
| Concise (remove every unnecessary word) | Filler words ("현재", "앞으로", "성공적으로") |
9. Writing Checklist (for every new i18n string)
Before adding a string to ko.ts, verify:
- [ ] Speech level: Is it 해요체? (Only a11y hints use 합니다체)
- [ ] Speakable: Can you say it naturally in conversation?
- [ ] Weed-free: Is every word necessary? Remove filler.
- [ ] User-centered: Written from user's perspective, not system's?
- [ ] Next step: Does it tell the user what to do next?
- [ ] Positive framing: "~할 수 있어요" not "~할 수 없어요" (where possible)?
- [ ] Consistent pattern: Matches the sentence ending chart above?
- [ ] No 합니다 leak: Double-check no formal endings snuck in
- [ ] English parity: Does
en.tsentry match the tone? (Stripe/Notion feel)
10. Reference: App-by-App Deep Dive
Toss (토스) — The Benchmark
Core Values: Clear, Concise, Casual, Respectful, Emotional
8 Writing Principles:
- Predictable Hint — text hints at what happens next
- Weed Cutting — remove words that add no meaning
- Remove Empty Sentences — no decorative/redundant text
- Focus on Key Message — sometimes omitting info is kinder
- Easy to Speak — avoid 한자어 and 문어체; use words you'd say aloud
- Suggest Over Force — offer choices, never guilt or scare
- Universal Words — no jargon, slang, memes, or industry terms
- Find Hidden Emotion — go beyond info delivery; empathize with the user's feeling
Error philosophy: "좋은 에러 메시지 = 상황 설명 + 이유 + 해결책". Reframe negatively: "~할 수 있어요" instead of "안 돼요".
TwoMore takeaway: We already follow Toss closely. Key gap is error verb consistency and removing filler words.
카카오 (KakaoTalk, KakaoBank)
KakaoTalk: Pure 해요체. "메시지를 보냈어요", "사진이 저장됐어요". Minimal, functional — the message content is the hero, not the UI text.
KakaoBank: 해요체 for most interactions, but financial confirmations sometimes use 합니다체 for gravitas: "출금 이체가 완료되었습니다" alongside "이체할까요?" — the confirmation question is 해요체, the result is 합니다체. This mix works in finance but is NOT recommended for TwoMore (we are not a financial app).
KakaoBank error pattern: "이체하지 못했어요. [specific reason]." Always tells you why and what to do.
당근마켓 (Karrot)
Tone: "이웃" (neighbor) — the warmest of all major Korean apps. Every interaction feels like chatting with someone in your apartment complex.
Patterns:
- Empty states: "아직 동네 이웃이 없어요" (neighborly)
- Notifications: "회원님의 글에 댓글이 달렸어요" (uses 회원님, polite but warm)
- Listings: minimal text, photo-first, location chip
- Success: "글이 올라갔어요!" (conversational past tense)
TwoMore takeaway: We can adopt 당근's neighborhood warmth for club-internal communication. When a member RSVPs, it should feel like a friend confirming — "참석 완료!" not "참석이 등록되었습니다".
배달의민족 (Baemin)
Famous for: Quirky, witty Korean copywriting that makes mundane food delivery feel delightful. They commission custom fonts (배민체) and write copy that people screenshot and share.
How they balance humor + clarity:
- Humor lives in marketing, splash screens, and push notifications — never in critical flows
- Order confirmation, payment errors, and status updates are straightforward 해요체
- They never sacrifice clarity for a joke during a transaction
Example pattern:
- Marketing: "오늘 뭐 먹지?" (playful 해체 in headline only)
- Order flow: "주문이 접수됐어요" (standard 해요체)
- Error: "주문에 실패했어요. 다시 시도해 주세요." (empathetic 해요체)
TwoMore takeaway: We can add personality in onboarding, splash screens, and achievement celebrations. But session creation, score entry, and dues management must remain clear and straightforward.
네이버 (Naver)
Mixed approach: Naver's modern products (Naver Pay, Naver Maps, Naver Cafe) use 해요체. Legacy products and settings pages still have 합니다체. They are migrating toward 해요체 across the board.
Naver Pay: "결제가 완료됐어요" (해요체), "포인트가 적립됐어요" Naver Maps: "목적지까지 30분 걸려요" (해요체, conversational) Naver Cafe: "게시글이 등록됐어요" (해요체)
TwoMore takeaway: Naver's trajectory confirms that 해요체 is the future standard. We should never introduce 합니다체 into new strings.
11. Templates for Common Scenarios
Session Lifecycle
Created: "모임이 만들어졌어요!"
RSVP open: "참석 신청을 받고 있어요"
RSVP closed: "참석 신청이 마감됐어요"
Started: "모임이 시작됐어요!"
In progress: "지금 진행 중"
Ended: "모임이 종료됐어요. 수고했어요!"
Cancelled: "모임이 취소됐어요"Member Actions
Joined club: "환영해요! 클럽에 가입됐어요"
Left club: "클럽을 나갔어요"
RSVP yes: "참석 완료!"
RSVP cancel: "참석을 취소했어요"
Score saved: "점수가 저장됐어요"Admin Actions
Dues created: "회비가 생성됐어요"
Member removed: "멤버를 내보냈어요"
Matches generated: "경기 배정 완료!"
Settings saved: "설정이 저장됐어요"Last updated: 2026-03-28Based on: Toss tech blog principles, KakaoBank/KakaoTalk patterns, 당근마켓 tone analysis, 배민 copywriting analysis, Naver product audit, and TwoMore codebase audit of ko.ts