콘텐츠로 이동

Worked example: how-to

Generated by skills/document-author with type=how-to. Demonstrates the canonical Diátaxis how-to format — direct, problem-driven, no fluff.


---
title: How to add Korean copy to existing English-only components
description: Translate component copy to Korean while preserving design-ai conventions for honorific level, IME handling, and Daum Postcode integration.
type: how-to
audience: Developers shipping a Korean version of an English product
last_updated: 2026.05.08
---

# How to add Korean copy to existing English-only components

When you need to: ship a Korean locale for a product currently in English only.

## TL;DR

```ts
// 1. Wrap copy in a t() function
<button>{t("Save")}</button>

// 2. Use Korean-aware t() with honorific level
import { t } from "@/lib/i18n";
t("Save");  // → "저장" (consumer) or "저장하기" (formal)

Set LOCALE=ko and HONORIFIC=informal (해요체) or formal (합쇼체) per app configuration. Default formal for fintech / B2B; informal for consumer.

Steps

1. Inventory the copy

Find every literal string in your components. Tools:

# Quick grep
grep -r "Save\|Cancel\|Submit" src/

# Better: use react-i18next's extractor
i18next-scanner --config i18next-scanner.config.js

Output: a JSON file mapping every English string to a key.

2. Pick honorific level for your product

Use knowledge/i18n/korean-product-conventions.md to decide:

  • Consumer mobile / friendly: 해요체 (~해요)
  • B2B / formal / fintech / banking: 합쇼체 (~합니다)
  • Government / legal / healthcare: 합쇼체 always

Decision sticks for the entire app — don't mix.

3. Translate using design-ai conventions

For UI strings, use the canonical Korean per knowledge/i18n/korean-typography.md:

English 해요체 합쇼체
Save 저장 저장하기
Cancel 취소 취소
Submit 제출 / 등록 제출하기
Delete 삭제 삭제하기
Sign in 로그인 로그인
Sign up 가입 회원가입
Loading 로딩 중 불러오는 중
Try again 다시 시도 다시 시도해 주세요

Don't direct-translate — Korean has different defaults. Example: "Submit" rarely means 제출 in Korean apps; it usually means 등록 or 저장.

4. Replace English in components

For each component:

- <Button>Save</Button>
+ <Button>{t("save")}</Button>

Where t() reads from your locale JSON.

5. Handle Korean IME for input components

If your component has typeahead / autocomplete / mention features, IME composition must be respected. Update existing components:

+ const [isComposing, setIsComposing] = useState(false);

- <input onChange={(e) => onChange(e.target.value)} />
+ <input
+   onChange={(e) => !isComposing && onChange(e.target.value)}
+   onCompositionStart={() => setIsComposing(true)}
+   onCompositionEnd={(e) => {
+     setIsComposing(false);
+     onChange(e.target.value);
+   }}
+ />

Cite examples/component-input.md for the full pattern.

6. Replace address inputs with Daum Postcode

Korean users won't tolerate free-form address. Replace any <AddressInput> with the Daum Postcode-integrated version:

# Install
npm install react-daum-postcode
import { DaumPostcode } from "react-daum-postcode";
// ...use the Daum widget; see knowledge/i18n/korean-product-conventions.md

Cite examples/component-address-input.md.

7. Apply Korean typography

Add Pretendard to your font stack:

:root {
  --font-family-base: 'Pretendard', -apple-system, BlinkMacSystemFont,
                      'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
}

Bump body line-height to 1.6 (Korean +10% adjustment) per knowledge/i18n/korean-typography.md:

body { line-height: 1.6; }

Contrast check: translated Korean body text must keep at least 4.5:1 against the page background, and focus indicators on locale switchers or copy controls must keep 3:1 per knowledge/a11y/contrast.md. Screen reader output must announce the active locale and any language switcher state via aria-current or equivalent text.

8. Add Korean-specific patterns

If your app has: - Payment: replace generic with Toss/KakaoPay/NaverPay buttons. See knowledge/i18n/korean-payments.md. - Phone auth: add SMS verification flow with InputOTP — see examples/component-input-otp.md. - Identity verification: integrate PASS — see examples/component-pass-auth.md. - Money display: use KRWAmount or AmountInput — see examples/component-krw-amount.md.

Variations

Just translation, no Korean-specific UX

If you're only translating words (e.g., a documentation site), skip steps 5–8. The result will work but won't feel native.

Multi-locale (en + ko in one app)

Use a locale switcher in settings. Persist to localStorage + send via Accept-Language header.

const locale = useLocale();  // "en" | "ko"

Korean-only product

Skip the en stage entirely. Don't ship english-strings-with-Korean-fallback — just write Korean directly.

Common pitfalls

Pitfall Fix
Mixed 합쇼체 + 해요체 in same screen Pick one. Document in style guide.
Translation breaks layout (Korean longer or shorter) Use flex-grow not fixed widths; allow wrap.
Phone validation regex assumes US format Update to Korean: /^01[0-9]-?\d{3,4}-?\d{4}$/
English error messages slip through Add a CI check via tools/audit/korean-copy-check.py.
Date format mm/dd/yyyy Switch to yyyy.MM.dd (KR convention).

See also


Why this is a good how-to example

  • Problem stated up front ("when you need to…").
  • TL;DR with code snippet — for the reader who'll only read 5 seconds.
  • Numbered steps with concrete actions.
  • Variations section for adjacent cases.
  • Common pitfalls save debug time.
  • No motivation/background — that's an explanation doc.
  • Imperative voice throughout ("install", "replace", "switch").

Cite knowledge/patterns/technical-writing.md for voice rules.

Cross-reference