
이 글은 React에서 다국어화(internationalization, i18n)를 구현하는 방법을 다룹니다.
다국어화란?
다국어화는 애플리케이션을 여러 언어와 지역에서 사용할 수 있도록 설계하고 구현하는 과정입니다. 'i18n'이라는 용어는 'internationalization'의 첫 글자 'i'와 마지막 글자 'n' 사이에 18개의 글자가 있다고 해서 만들어진 약어입니다.
환경 설정하기
필요한 패키지 설치
가장 널리 사용되는 React 다국어화 라이브러리인 react-intl을 사용하겠습니다.
npm install react-intl
# 또는
yarn add react-intl
기본 설정
src/App.js 파일을 열고 다국어 설정을 위한 기본 구조를 추가합니다. 먼저 애플리케이션의 최상위에서 IntlProvider를 설정해야 합니다.

// App.js
import React, { useState } from 'react';
import { FormattedMessage, IntlProvider } from 'react-intl';
// 메시지 정의
const messages = {
ko: {
'app.welcome': '안녕하세요!',
'app.description': 'React 다국어화 예제입니다.',
'button.click': '클릭하세요',
},
en: {
'app.welcome': 'Hello!',
'app.description': 'This is a React i18n example.',
'button.click': 'Click me',
},
};
function App() {
const [locale, setLocale] = useState('ko');
return (
<IntlProvider
locale={locale}
messages={messages[locale]}
defaultLocale="ko"
>
<div>
<button onClick={() => setLocale('ko')}>한국어</button>
<button onClick={() => setLocale('en')}>English</button>
</div>
<FormattedMessage id="app.welcome" />
</IntlProvider>
);
}
export default App;
기본 사용 방법
가장 기본적인 텍스트 번역은 FormattedMessage 컴포넌트를 사용합니다.
<FormattedMessage id="app.welcome" />
다른 방법으로는 useIntl 훅을 사용할 수 있습니다.
import React from 'react';
import { useIntl } from 'react-intl';
function MyComponent() {
const intl = useIntl();
const handleClick = () => {
// 알림창에 번역된 메시지 표시
alert(intl.formatMessage({ id: 'button.click' }));
};
return (
<button
title={intl.formatMessage({ id: 'button.tooltip' })}
onClick={handleClick}
>
{intl.formatMessage({ id: 'button.click' })}
</button>
);
}
심화 기능
텍스트에 변수 사용하기
문장에 10개, 홍길동님 같이 서버에서 전달받은 데이터를 사용해야 하는 경우가 있습니다. 이런 경우, 메시지 파일에 변수를 사용할 수 있습니다.
const messages = {
ko: {
'text.name': '{name}님',
'text.count': '{count}개',
},
...
};
<FormattedMessage id="text.name" values={{ name: '홍길동' }} /> // 홍길동님
<FormattedMessage id="text.count" values={{ count: 10 }} /> // 10개
HTML 태그와 스타일링
작업을 하다 보면 요구사항 중 텍스트에 링크를 걸어야 하거나 특정 텍스트에 강조 또는 색상 변경 같은 요구사항이 있습니다.
이를 해결하기 위해 두 가지 방법이 있습니다.

values 속성에 함수 사용하기
const messages = {
ko: {
'text.with.bold': '이것은 <bold>굵은 텍스트</bold>가 포함된 문장입니다.',
'text.with.link': '<link>여기를 클릭</link>하면 구글로 이동합니다.',
'text.mixed': '<bold>중요:</bold> <link>문서</link>를 확인하세요.',
},
};
<div>
{/* 굵은 텍스트 */}
<p>
<FormattedMessage
id="text.with.bold"
values={{
bold: chunks => <strong>{chunks}</strong>,
count: 10,
}}
/>
</p>
{/* 링크 */}
<p>
<FormattedMessage
id="text.with.link"
values={{
link: chunks => (
<a
href="https://google.com"
target="_blank"
rel="noopener noreferrer"
>
{chunks}
</a>
),
}}
/>
</p>
{/* 혼합 사용 */}
<p>
<FormattedMessage
id="text.mixed"
values={{
bold: chunks => (
<strong style={{ color: 'red' }}>{chunks}</strong>
),
link: chunks => (
<a href="/docs" className="doc-link">
{chunks}
</a>
),
}}
/>
</p>
</div>
richTextElements 사용하기
richTextElements 속성은 미리 특정 태그에 대해 추상화하여 자동으로 변환해주는 속성입니다.

const messages = {
ko: {
'welcome.message': '<b>환영합니다!</b> <link>가이드</link>를 확인하세요.',
'important.note': '<highlight>중요한</highlight> 공지사항입니다.',
},
...
};
<IntlProvider
...
defaultRichTextElements={{
b: chunks => <strong>{chunks}</strong>,
link: chunks => <a href="/docs">{chunks}</a>,
highlight: chunks => <span style={{ color: 'red' }}>{chunks}</span>,
}}
>
<FormattedMessage id="welcome.message" />
<FormattedMessage id="important.note" />
</IntlProvider>
ICU MessageFormat 이해하기
ICU MessageFormat은 국제화 메시지를 위한 표준 형식입니다.
ICU MessageFormat 기본 문법
기본 구조는 다음과 같습니다: {변수명, 타입, 형식}
복수형 처리 (Plural)
복수형 처리는 다음과 같은 형식으로 할 수 있습니다:
const messages = {
ko: {
'items.count': '{count, plural, =0 {항목이 없습니다} =1 {항목 1개} other {항목 #개}}'
},
en: {
'items.count': '{count, plural, =0 {No items} =1 {One item} other {# items}}'
}
};
// 사용 예시
function ItemCount({ count }) {
return (
<FormattedMessage
id="items.count"
values={{ count }}
/>
);
}
// 결과
// count=0: "항목이 없습니다" / "No items"
// count=1: "항목 1개" / "One item"
// count=5: "항목 5개" / "5 items"
선택형 처리 (Select)
선택형 처리는 다음과 같은 형식으로 할 수 있습니다:
const messages = {
ko: {
'user.greeting': '{gender, select, male {안녕하세요, {name}님!} female {안녕하세요, {name}님!} other {안녕하세요!}}'
},
en: {
'user.greeting': '{gender, select, male {Hello, Mr. {name}!} female {Hello, Ms. {name}!} other {Hello!}}'
}
};
function UserGreeting({ gender, name }) {
return (
<FormattedMessage
id="user.greeting"
values={{ gender: 'male', name: '홍길동' }}
/>
);
}
// 결과
// gender=male: "안녕하세요, 홍길동님!" / "Hello, Mr. 홍길동!"
// gender=female: "안녕하세요, 홍길동님!" / "Hello, Ms. 홍길동!"
// gender=other: "안녕하세요!" / "Hello!"
other 조건과 누락된 조건 처리
react-intl에서 ICU MessageFormat을 사용할 때, other 조건이 없으면 메시지가 제대로 표시되지 않습니다:
// ❌ 잘못된 사용 - 메시지가 제대로 표시되지 않음
const messages = {
ko: {
'user.greeting': '{gender, select, male {안녕하세요, {name}님!} female {안녕하세요, {name}님!}}'
}
};
// ✅ 올바른 사용
const messages = {
ko: {
'user.greeting': '{gender, select, male {안녕하세요, {name}님!} female {안녕하세요, {name}님!} other {안녕하세요!}}'
}
};
또한, ICU에 없는 조건을 입력한다면, other에 있는 값이 표시됩니다.
// 예시: gender가 'unknown'인 경우
<FormattedMessage
id="user.greeting"
values={{ gender: 'unknown', name: '홍길동' }}
/>
// 결과: "안녕하세요!"
추가 고려사항
키 컨벤션
번역 키를 효과적으로 관리하기 위한 명명 규칙을 알아보겠습니다.
1. 명확하고 일관된 이름 사용
| 텍스트 | ❌ 잘못된 예 | ✅ 좋은 예 |
| 이름 | form.name1 | form.name_person |
| 이름 | form.name2 |
2. 중첩 구조 활용
{
"header": {
"title": "환영합니다",
"logo_alt": "회사 로고"
},
"error": {
"required": "필수 입력 항목입니다",
"invalid_url": "올바른 URL을 입력해주세요"
},
"footer": {
"copyright": "© 2024 회사명. All rights reserved",
"contact": {
"phone": "02-123-4567",
"email": "info@company.com"
}
}
}
3. common 카테고리 활용
여러 페이지에서 공통으로 사용되는 문자열은 common 카테고리에 저장합니다:
| 텍스트 | ❌ 잘못된 예 | ✅ 좋은 예 |
| 취소 | cancel | common.cancel |
| 크레딧 | credits | common.credits |
| 기본 | standard | common.pricing_plan.standard |
4. 명명 규칙
- 도메인.기능.요소 형식 사용
- 예:
auth.login.button,common.errors.required - 이유: 번역가가 문맥을 쉽게 이해할 수 있도록 도와줍니다. 예를 들어
auth.login.button은 인증 페이지의 로그인 버튼이라는 것을 명확히 알 수 있습니다.
- 예:
- 일관된 케이스 사용
- 스네이크 케이스(
snake_case) 추천 - 예:
user_profile.settings.button - 이유: 일관된 케이스 사용은 코드의 가독성을 높이고, 키를 찾고 관리하기 쉽게 만듭니다. 스네이크 케이스는 단어 사이의 구분이 명확하여 특히 번역 키와 같은 긴 문자열에 적합합니다.
- 스네이크 케이스(
- 의미 있는 이름 사용
- 약어 사용 지양
- 예:
rg_frm_fnam_lb→registration_form.label.first_name - 이유: 의미 있는 이름은 코드의 의도를 명확히 전달하고, 유지보수를 쉽게 만듭니다. 약어는 시간이 지나면 의미를 잊기 쉽고, 새로운 팀원이 코드를 이해하는 데 어려움을 줄 수 있습니다.
- 명확한 컨텍스트 제공
- 번역가가 이해하기 쉬운 이름 사용
- 예:
form.name_personvsform.name_company - 이유: 같은 단어라도 문맥에 따라 번역이 달라질 수 있습니다. 예를 들어 '이름'이라는 단어는 사람의 이름과 회사명에서 다른 번역이 필요할 수 있습니다. 명확한 컨텍스트를 제공하면 정확한 번역을 보장할 수 있습니다.
이러한 규칙을 따르면 번역 키를 더 효율적으로 관리하고, 번역가와 개발자 간의 협업을 원활하게 할 수 있습니다. 특히 대규모 프로젝트에서는 이러한 일관된 규칙이 코드의 품질과 유지보수성을 크게 향상시킵니다.
추천 확장 프로그램
다국어 처리를 더 효율적으로 할 수 있도록 도와주는 VS Code 확장 프로그램을 소개합니다.
i18n-ally
i18n-ally는 Lokalise에서 만든 VS Code 확장 프로그램으로, 다국어 처리를 위한 다양한 기능을 제공합니다.
주요 기능:
- 인라인 번역 표시
- 코드에서 번역 키를 사용할 때 실제 번역된 텍스트를 인라인으로 표시
- 예:
<FormattedMessage id="welcome.message" />→ "안녕하세요!"
- 키 자동완성
- 번역 키 입력 시 자동완성 지원
- 사용 가능한 모든 키를 목록으로 표시
- 번역 관리
- 모든 언어의 번역을 한 곳에서 관리
- 누락된 번역 확인 및 추가
- 번역 리뷰 시스템 지원

- 번역 추출
- 코드에서 하드코딩된 텍스트를 번역 키로 자동 변환
- 새로운 번역 키 생성 및 관리
- 문제 감지
- 누락된 번역 키 감지
- 잘못된 키 사용 감지
- 번역 불일치 감지
이 확장 프로그램을 사용하면 다국어 처리가 더욱 효율적이고 안정적으로 이루어질 수 있습니다. 특히 대규모 프로젝트에서 번역 관리와 일관성 유지에 큰 도움이 됩니다.
마무리
다국어 처리는 단순히 텍스트를 번역하는 것을 넘어서는 작업입니다. 사용자에게 일관된 경험을 제공하는 것이 중요합니다. react-intl은 이러한 일관성을 유지하는 데 큰 도움이 됩니다.
예를 들어, 날짜 포맷팅을 생각해보면:
- 한국: "2024년 3월 15일"
- 미국: "March 15, 2024"
- 일본: "2024年3月15日"
이처럼 각 지역마다 다른 형식을 사용하는데, react-intl의 FormattedDate 컴포넌트를 사용하면 로케일에 맞는 형식으로 자동 변환됩니다.
<FormattedDate
value={new Date()}
year="numeric"
month="long"
day="numeric"
/>
또한 단위 변환도 중요한 부분입니다:
- 거리: km vs miles
- 무게: kg vs pounds
- 온도: °C vs °F
FormattedNumber를 사용하면 이러한 단위 변환도 자동으로 처리할 수 있습니다.
<FormattedNumber
value={100}
style="unit"
unit="kilometer"
unitDisplay="long"
/>
// 한국: "100킬로미터"
// 미국: "100 miles"
이처럼 react-intl은 단순한 텍스트 번역을 넘어, 각 지역의 문화와 관습을 반영한 일관된 사용자 경험을 제공할 수 있게 해줍니다. 이는 글로벌 서비스를 제공하는 데 있어 매우 중요한 요소입니다.
앞으로도 다국어 처리와 관련된 새로운 요구사항이 생길 수 있지만, react-intl의 다양한 기능을 활용하면 효과적으로 대응할 수 있을 것입니다. 특히 날짜, 숫자, 단위 등과 같은 복잡한 포맷팅을 일관되게 처리할 수 있다는 점이 큰 장점입니다.
참고
'Frontend > Tech Insight' 카테고리의 다른 글
| Feature-based vs Layer-based 어떤 구조를 사용해야 할까? (0) | 2025.12.14 |
|---|---|
| React Custom Hook은 어떻게 작동할까? (2) | 2025.08.10 |
| Tailwind에서 조건부 클래스와 충돌 방지를 동시에 해결하기 (0) | 2025.05.20 |
| React에서 DOMPurify로 XSS 공격 방어하기 (0) | 2025.04.24 |
| Bun: 빠르고 혁신적인 JavaScript 런타임Bun이란? (0) | 2025.02.16 |