다국어 React 앱을 국제화(i18n)로 구축하는 방법
Source: Dev.to
Internationalization (i18n)이란?
Internationalization (i18n)은 애플리케이션을 설계하고 개발하는 과정으로, 엔지니어링 변경 없이도 다양한 언어와 지역에 쉽게 적용될 수 있도록 합니다.
용어 i18n은 “internationalization”이라는 단어에서 첫 번째 “i”와 마지막 “n” 사이에 18개의 글자가 있기 때문에 붙여졌습니다.
i18n의 핵심 요소
- Translation – 텍스트 문자열을 다른 언어로 변환하기
- Localization – 특정 지역에 맞게 콘텐츠를 조정하기(날짜, 숫자, 통화 등)
- Pluralization – 각 언어별로 단수/복수 형태를 올바르게 처리하기
React 앱에서 i18n이 중요한 이유
- Global Reach – 사용자의 모국어로 서비스를 제공해 새로운 시장으로 확장
- Better User Experience – 사용자는 자신의 언어로 된 앱을 선호합니다(연구에 따르면 75 %가 모국어로 제품을 구매하는 것을 선호)
- Competitive Advantage – 많은 앱이 적절한 국제화를 구현하지 못하고 있어 차별화된 경쟁력을 가질 수 있음
- Legal Requirements – 일부 국가에서는 앱을 현지 언어로 제공하도록 요구함
- SEO Benefits – 다국어 콘텐츠가 다양한 지역에서 검색 순위를 높여줌
- Increased Conversions – 사용자는 콘텐츠가 자신의 언어일 때 행동을 완료할 확률이 높아짐
사전 준비 사항
- React와 React Hooks에 대한 기본 지식
- Node.js와 npm이 설치된 환경
- VS Code와 같은 코드 편집기
프로젝트 설정
Vite를 사용해 새로운 React 애플리케이션을 생성합니다:
npm create vite@latest react-i18n-demo -- --template react
cd react-i18n-demo
필요한 i18n 패키지를 설치합니다:
npm install i18next react-i18next i18next-browser-languagedetector
개발 서버를 실행합니다:
npm run dev
앱이 이제 실행 중이어야 합니다.
Note: 이 튜토리얼은 스타일링 라이브러리를 사용하지 않습니다. 간단히 인라인 스타일을 정의했지만 실제 애플리케이션에서는 CSS를 별도 파일로 분리하거나 Tailwind CSS와 같은 프레임워크를 사용하는 것이 좋습니다.
기본 CSS 교체
/* src/index.css */
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
}
문제점: 하드코딩된 문자열
import { useState } from 'react';
function ProductCard({ product }) {
return (
<>
{/* ❌ Hardcoded English strings */}
<h3>{product.name}</h3>
<p>Price: ${product.price}</p>
<p>In stock: {product.stock} items</p>
{product.stock === 0 && (
<p>Out of stock</p>
)}
{product.stock === 1 && (
<p>Only 1 item left!</p>
)}
{product.stock > 1 && (
<p>In stock</p>
)}
<button>Add to cart</button>
</>
);
}
export default ProductCard;
이 접근 방식의 문제점
- 번역 지원이 없음 – 모든 텍스트가 영어로만 제공
- 유지 보수 어려움 – 텍스트를 변경하려면 여러 컴포넌트를 수정해야 함
- 복수형 처리 미흡 – “1 item” vs “2 items”가 하드코딩됨
- 현지화 부재 – 날짜와 숫자 형식이 고정됨
- 확장성 문제 – 새로운 언어를 추가하려면 모든 컴포넌트를 수정해야 함
해결책: react-i18next
react-i18next는 React 국제화를 위한 가장 인기 있고 강력한 솔루션입니다. 주요 기능은 다음과 같습니다.
- 간단한 번역 API
- 자동 언어 감지
- 복수형 지원
- 중첩 번역
- 날짜·숫자·통화 포맷팅
- 번역 파일의 지연 로딩
- TypeScript 지원
번역 파일 만들기
utils/locales 폴더에 별도의 JavaScript 파일로 번역을 구성합니다:
utils/
locales/
en/
translation.js
es/
translation.js
fr/
translation.js
English (utils/locales/en/translation.js)
export default {
en: {
translation: {
"product.price": "Price",
"product.inStock": "In stock: {{count}} items",
"product.outOfStock": "Out of stock",
"product.onlyOneLeft": "Only 1 item left!",
"product.addToCart": "Add to cart",
"profile.welcome": "Welcome, {{name}}!",
"profile.memberSince": "Member since: {{date}}",
"profile.loyaltyPoints": "You have {{points}} loyalty points",
"profile.orders_zero": "You have no orders",
"profile.orders_one": "You have 1 order",
"profile.orders_other": "You have {{count}} orders",
"profile.editProfile": "Edit profile",
},
},
};
Spanish (utils/locales/es/translation.js)
export default {
es: {
translation: {
"product.price": "Precio",
"product.inStock": "En stock: {{count}} artículos",
"product.outOfStock": "Agotado",
"product.onlyOneLeft": "¡Solo queda 1 artículo!",
"product.addToCart": "Añadir al carrito",
"profile.welcome": "¡Bienvenido, {{name}}!",
"profile.memberSince": "Miembro desde: {{date}}",
"profile.loyaltyPoints": "Tienes {{points}} puntos de fidelidad",
"profile.orders_zero": "No tienes pedidos",
"profile.orders_one": "Tienes 1 pedido",
"profile.orders_other": "Tienes {{count}} pedidos",
"profile.editProfile": "Editar perfil",
},
},
};
French (utils/locales/fr/translation.js)
export default {
fr: {
translation: {
"product.price": "Prix",
"product.inStock": "En stock: {{count}} articles",
"product.outOfStock": "Rupture de stock",
"product.onlyOneLeft": "Plus qu'un article !",
"product.addToCart": "Ajouter au panier",
"profile.welcome": "Bienvenue, {{name}} !",
"profile.memberSince": "Membre depuis : {{date}}",
"profile.loyaltyPoints": "Vous avez {{points}} points de fidélité",
"profile.orders_zero": "Vous n'avez aucune commande",
"profile.orders_one": "Vous avez 1 commande",
"profile.orders_other": "Vous avez {{count}} commandes",
"profile.editProfile": "Modifier le profil",
},
},
};