Skip to content

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

AttributeWhat it meansWhat it does NOT mean
친근한 (Warm)Conversational, approachableOverly casual, slangy, using 해체
명확한 (Clear)One meaning per sentence, no ambiguityVerbose, over-explaining
간결한 (Concise)Every word earns its placeTerse, robotic, dropping needed context
존중하는 (Respectful)Acknowledges user's time and autonomyPatronizing, commanding, guilt-tripping
공감하는 (Empathetic)Matches the user's emotional stateFake 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 해요체

AppSpeech LevelRationale
토스 (Toss)해요체"금융이나 IT 업계에서 쓰던 어려운 용어와 딱딱한 뉘앙스를 쉽고 친절하게 고쳐"
카카오톡해요체Messaging context demands conversational warmth
카카오뱅크해요체 (financial confirmations mix 합니다체)Banking trust needs warmth without losing reliability
당근마켓해요체Neighborhood-friendly, "이웃과 대화하는 느낌"
배달의민족해요체 + playful wordplayHumor 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:

ContextExampleReason
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:

AppPrimary CTA styleExample
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:

PrincipleTwoMore application
Prevention is bestDisable buttons when action is impossible; gray out past dates
Appropriate componentToast for transient errors; Dialog for blocking errors
Self-directed solutionsAlways include what the user can do next
User-friendly language"모임을 만들지 못했어요" not "세션 생성 실패"
Reduce frictionProvide a retry button, not just text
Minimize negative emotion"앗" softens the blow; never blame the user

Error verb choices:

PatternToneWhen to use
~지 못했어요Soft, empatheticDefault — the system couldn't do it
~에 실패했어요Slightly harderAcceptable for auth failures where user might have caused it
~할 수 없었어요NeutralGeneral inability — "불러올 수 없었어요"
~이(가) 안 됐어요Too colloquialAvoid 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

ContextEmoji allowed?Example
Celebrations (toast, success)Limited"참석 완료!" (no emoji needed; ! suffices)
Empty statesNoUse illustration/icon, not emoji
Error messagesNo"앗" is the softener, not an emoji
NotificationsNoClean text only
Informal nudgesSparinglyAchievement milestones may use sport emoji
Marketing / onboardingSparinglyTagline can have subtle accent

Industry reference:

AppEmoji usage
TossAlmost zero — relies on illustration + animation
KakaoBankZero in-app; emoticons in KakaoTalk integration
당근마켓Minimal — uses warm illustration style instead
배민Uses custom 배민체 font for personality, not emoji
NaverZero 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 toneEnglish equivalent appEnglish tone
해요체 conversational warmthStripe (clear, respectful)"Your payment was successful"
Concise, one-task-per-screenNotion (minimal, functional)"No pages here yet. Create your first page."
Empathetic errorsSlack (friendly acknowledgment)"Something went wrong. Try again?"
Celebratory completionsDuolingo (encouraging)"Nice! Session created."
Suggestive CTAsAirbnb (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:

KoreanEnglishNotes
~하기 (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:

typescript
// 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

typescript
// 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:

typescript
// Current (generic):
scoreSubmitFailed: '앗, 점수를 등록하지 못했어요. 다시 시도해 주세요.';

// Better (specific):
scoreSubmitFailed: '앗, 점수를 등록하지 못했어요. 인터넷 연결을 확인하고 다시 시도해 주세요.';

// Current (generic):
invalidInviteCode: '초대 코드가 맞지 않아요. 다시 확인해 주세요.';

// Better (specific):
invalidInviteCode: '초대 코드가 맞지 않아요. 관리자에게 코드를 다시 받아 보세요.';

8. Quick Reference Card

Sentence Ending Cheat Sheet

ContextEndingExample
Success toast~됐어요 / ~완료!"저장됐어요", "참석 완료!"
Primary CTA button~하기"참석하기", "모임 만들기"
Secondary buttonBare noun"확인", "취소", "저장"
Suggestion (empty state body)~해 보세요"첫 모임을 만들어 보세요"
Confirmation dialog~할까요? / ~겠어요?"삭제할까요?", "나가시겠어요?"
Error title~지 못했어요"불러오지 못했어요"
Error guidance~해 주세요"다시 시도해 주세요"
Status labelNoun / ~중"진행 중", "모집 중", "대기"
Notification description~드려요"알려 드려요"
Loading~는 중..."불러오는 중..."
A11y hint~합니다"설정 화면으로 이동합니다"

Do / Don't Summary

DODON'T
해요체 for all conversational UI합니다체 in user-facing messages
"~지 못했어요" for errors"~에 실패했습니다"
"~해 보세요" for suggestions"~하세요" (imperative commands)
"~할까요?" for confirmations"~하시겠습니까?"
Pure Korean where possibleUnnecessary 한자어
State consequence neutrallyFear-based warnings ("주의!", "경고!")
Guide toward next actionDead-end error messages
! for celebrationsEmoji 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.ts entry 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:

  1. Predictable Hint — text hints at what happens next
  2. Weed Cutting — remove words that add no meaning
  3. Remove Empty Sentences — no decorative/redundant text
  4. Focus on Key Message — sometimes omitting info is kinder
  5. Easy to Speak — avoid 한자어 and 문어체; use words you'd say aloud
  6. Suggest Over Force — offer choices, never guilt or scare
  7. Universal Words — no jargon, slang, memes, or industry terms
  8. 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

Markdown remains the source of truth. Run yarn docs:check before handoff.