i18n — Korean as the First Priority
i18n — Korean as the First Priority
Internationalization (i18n) is the work of making an app behave naturally across multiple languages and regions.
1. Terminology and standards
- i18n (internationalization) — the work of not tying code to a specific language or region.
- l10n (localization) — adding region-specific translation and formatting on top.
- locale — an identifier combining language and region (BCP 47 standard).
ko,ko-KR,en-US,zh-Hant-TW, etc.
Standards:
- BCP 47 — language tag standard.
ko-KR. - CLDR (Unicode Common Locale Data Repository) — date, number, unit, and plural rule data for each locale. Maintained by the Unicode Consortium.
- ICU MessageFormat — notation for handling variables, plurals, and gender inside messages.
- Intl API — ECMAScript standard built-in objects.
Intl.NumberFormat,Intl.DateTimeFormat,Intl.RelativeTimeFormat,Intl.PluralRules,Intl.Collator,Intl.Segmenter(2021+).
Browsers and Node already have Intl, so libraries often add only a thin layer on top.
2. Libraries in the React ecosystem
| Library | First release | Author | Notes |
|---|---|---|---|
| react-i18next | 2014 (i18next) | Jan Mühlemann | React binding for i18next (JS). Most widely adopted. |
| react-intl (FormatJS) | 2014 | Yahoo (now OpenJS Foundation) | First-class ICU MessageFormat support. |
| LinguiJS | 2017 | Tomáš Roch | ICU + macros + compiled catalogs. |
| next-intl | 2022 | Jan Amann | Specialized for Next.js App Router. |
| Tolgee | 2021 | Tolgee | Emphasizes in-context editing. |
Because Next.js App Router does not assume i18n in routing itself, tools like next-intl grew up. They simplify locale branching by combining the [locale] dynamic segment with middleware.
3. Message catalogs
Two common shapes.
JSON / NS split (i18next style):
// public/locales/ko/common.json
{ "save": "저장", "cancel": "취소" }
import { useTranslation } from "react-i18next"
const { t } = useTranslation("common")
t("save") // "저장"
ICU MessageFormat (FormatJS / Lingui / next-intl):
{ "items": "{count, plural, =0 {항목 없음} other {#개의 항목}}" }
t("items", { count: 3 }) // "3개의 항목"
ICU is a standard with good portability between tools. There is more learning curve than simple key-value pairs.
4. Key naming conventions
Two common branches.
① Dot notation (namespaces): auth.login.title, cart.item.removed
② Natural-language keys: use "이 항목을 정말 삭제할까요?" itself as the key (Lingui macros)
Pros and cons differ. Dot notation is easy to search and rarely conflicts, but the meaning is hard to grasp from the key alone. Natural-language keys are clear in meaning, but the key changes when the text changes (build tools compensate).
5. Dates and numbers — using Intl
new Intl.NumberFormat("ko-KR", { style: "currency", currency: "KRW" })
.format(123456) // "₩123,456"
new Intl.DateTimeFormat("ko-KR", { dateStyle: "long" })
.format(new Date()) // "2026년 4월 25일"
new Intl.RelativeTimeFormat("ko", { numeric: "auto" })
.format(-1, "day") // "어제"
date-fns (2014) and dayjs (2018) also have their own locale packages. In new code, Intl alone is increasingly reported to be sufficient.
6. Korean particle handling
In Korean, the particle attached to a noun changes depending on whether the final consonant exists.
- With consonant: 책을, 사람이, 학교과 (→ "와")
- Without consonant: 사과를, 친구가, 학교와
Workarounds like {name} 을(를) 삭제했습니다 are unnatural. Common paths:
- A helper that determines presence of the final consonant by code point (
(ch - 0xAC00) % 28 !== 0) and picks the right particle. - Rewriting the message to avoid the particle (for example, "삭제 완료: {name}").
- Libraries: es-hangul, hangul-js.
7. Korean numbers, plurals, and collation
Korean groups digits by 만 (ten thousand) ("1만 2345" can read more naturally than "12,345"). It conflicts with the English-world thousand-comma convention, so the choice depends on UI.
Unlike English, Korean barely distinguishes singular and plural (사과 1개 and 사과 3개 use the same word form). One frequent confusion is that ICU's plural category effectively reduces to a single other in Korean.
Collation (sorting) follows 가나다 order. Intl.Collator("ko-KR") handles it by default.
["하늘", "가을", "나무"].sort(new Intl.Collator("ko-KR").compare)
// ["가을", "나무", "하늘"]
8. Fonts and IME
Pretendard (Hyungjin Gil, 2021) has effectively become one of the de facto standards in Korean UI (variable font, OFL license). When Hanja or Japanese glyphs are mixed in, font fallback must be defined well to avoid invisible misses. Line-height tuned to 1.4 for English can feel cramped in Korean. 1.6 to 1.8 is often recommended.
While Korean is being typed, keydown fires during the composition stage, so patterns like auto-search and immediate submit behave unexpectedly. Use compositionstart and compositionend events to detect composition.
const [composing, setComposing] = useState(false)
<input
onCompositionStart={() => setComposing(true)}
onCompositionEnd={() => setComposing(false)}
onKeyDown={(e) => {
if (composing) return
if (e.key === "Enter") submit()
}}
/>
9. Minimal next-intl example
// messages/ko.json
{ "Home": { "title": "안녕하세요", "items": "{count}개" } }
// app/[locale]/page.tsx
import { useTranslations } from "next-intl"
export default function Page() {
const t = useTranslations("Home")
return <h1>{t("title")}</h1>
}
10. Common pitfalls
Hardcoding misses — when some UI text is embedded directly in code, it is discovered late. Reinforce with ESLint rules (react/jsx-no-literals) or extraction tools.
Translation key consistency check — when a new language is added, missing keys only surface at runtime. Add a key set difference check to CI.
Time zone — new Date(...) depends on the client's time zone. When server render and client render produce different results, hydration mismatch happens. Handle in UTC and convert only at display time.
Branching 0/1 by English standard — the 1 item / 2 items branch has no meaning in Korean. In Korean catalogs, unifying ICU plural's =0, one, other into a single other is more natural.
RTL possibility — if Arabic or Hebrew may be added later, look ahead at CSS logical properties (margin-inline-start) and dir="rtl" compatibility.
Closing thoughts
The cost of i18n grows quickly the later you adopt it. Even for a Korean-only app, key separation is recommended from the start. For particle handling, message rewriting is the simplest path rather than relying on a library.
Next
- material3-tokens
- forms-zod
We refer to Unicode CLDR, ICU MessageFormat, BCP 47, MDN Intl, react-i18next, FormatJS, LinguiJS, next-intl, Pretendard, and es-hangul.