React 앱을 Tailwind 방식으로 현지화하기: 키도 JSON도 없이, 코드만으로
I’m happy to translate the article for you, but I need the full text of the post (the parts you’d like translated). Could you please paste the article’s content here? I’ll keep the source link at the top and preserve all formatting, code blocks, URLs, and technical terms as requested.
그래서, 앱을 현지화하고 싶으시군요
대부분의 i18n 솔루션은 익숙한 패턴을 따르도록 유도합니다:
- 거대한 JSON/YAML 파일
- 번거롭게 키로 번역을 “명명”하는 것
- 만들어진 문자열 보간 구문
- UI 코드와 번역 파일 사이를 계속 오가야 함
이는 잘 작동하지만, 시간이 지나면 그 절차가 다소 지루하게 느껴질 수 있습니다.
몇 년 전 처음 Tailwind CSS를 접했을 때, 필요한 것만 작성하기 시작하니 실제 UI를 훨씬 빠르게 배포할 수 있다는 사실에 충격을 받았습니다. i18n에서도 같은 느낌을 원했습니다.
소개 react‑scoped‑i18n 🌐
핵심 아이디어는 간단합니다: 전역 번역 파일과 키 대신, 번역이 렌더링되는 컴포넌트 바로 옆에 존재합니다.
How does i18n typically look?
// en.json
{
"profile": {
"header": "Hello, {{name}}"
}
}
// es.json
{
"profile": {
"header": "Hola, {{name}}"
}
}
// Header.tsx
export const Header = () => {
const { t } = useI18n();
return (
<h1>{t("profile.header", { name: "John" })}</h1>
);
};
이 접근 방식은 작동하고 검증되었지만 몇 가지 문제점이 있습니다:
- 추가 도구 없이는 Typescript가 크게 도움이 되지 않습니다.
- 번역 키는 실제로 렌더링되는 내용에 대한 컨텍스트가 거의 없습니다.
- 코드베이스에서 렌더링된 텍스트를 검색해도 해당 컴포넌트를 직접 찾을 수 없습니다.
- 문자열 보간이 커스텀 구문을 사용합니다.
“스코프드” 접근 방식은 어떻게 보이나요?
// Header.tsx
export const Header = () => {
const { t } = useI18n();
const name = "John";
return (
<h1>{t({
en: `Hello, ${name}`,
es: `Hola, ${name}`,
})}</h1>
);
};
키도 없고, 거대한 JSON 파일도 없으며, 커스텀 인터폴레이션 구문도 없습니다—그냥 순수 코드입니다!
Note: 이것은 완전한 i18n 플랫폼을 대체하기 위한 것이 아닙니다. 빠른 현지화가 필요한 코드 중심 앱을 위한 대안입니다.
이 접근 방식의 가장 큰 장점
타입 안전성 🩵
코드일 뿐이므로 모든 것이 타입 안전합니다.
createI18nContext({
languages: ["en", "es", "sl"],
fallbackLanguage: "en", // inferred from `languages` above
});
번역이 누락되면 TypeScript가 컴파일 시점에 표시합니다:
// ❌ Property 'es' is missing in type { en: string; sl: string; }
t({
en: "Hello",
sl: "Pozdravljeni",
});
누락된 번역이나 지원되지 않는 언어는 런타임에서의 놀라움 대신 컴파일 시점 오류가 됩니다. 😄
키 이름 지정이 필요 없음
콘텐츠가 키입니다. 렌더링된 텍스트를 검색하면 바로 해당 컴포넌트로 이동합니다—큰 DX 향상입니다. 😊
추가 빌드 단계 없음
라이브러리는 React Context 생태계 내에서 동작합니다. 설치 후 컨텍스트를 초기화하고, 내보낸 프로바이더로 앱을 감싸면 바로 사용할 수 있습니다.
기본 제공 숫자, 날짜, 시간 및 통화 포맷팅 ‼️
react‑scoped‑i18n은 기본 Intl API를 활용하므로 표준 로케일 식별자(en, en‑GB, es, es‑ES, …)만 사용하면 추가 설정 없이 전체 포맷팅 지원을 받을 수 있습니다.
타입‑안전한 “공유” 번역
앱 전반에 걸쳐 공유되는 문자열은 commons API를 통해 정의합니다:
createI18nContext({
languages: ["en", "es"],
fallbackLanguage: "en",
commons: {
continue: {
en: "Continue",
es: "Continuar",
},
},
});
그런 다음 IDE 자동 완성과 함께 작동하는 타입‑안전한 방식으로 사용합니다:
{t(commons.continue)}
복수형 (ICU‑영감을 받은, ICU‑엄격함은 아님)
tPlural은 각 언어가 실제로 필요한 카테고리만 정의할 수 있게 합니다.
{tPlural(count, {
en: {
one: `You have one apple.`,
many: `You have ${count} apples.`,
},
sl: {
one: `Imaš eno jabolko.`,
two: `Imaš dve jabolki.`, // dual form in Slovenian
many: `Imaš ${count} jabolk.`,
},
})}
특정 값을 ICU 카테고리 외부에서 지정할 수도 있습니다:
{tPlural(count, {
en: {
one: `You have one apple.`,
many: `You have ${count} apples.`,
42: `You have THE PERFECT amount of apples!`,
},
es: {
one: `Tienes una manzana.`,
many: `Tienes ${count} manzanas.`,
},
})}
이것이 적합하지 않을 때
(원본 내용의 나머지를 계속하십시오…)
적합한 경우
이 접근 방식은 설계상 개발자 중심이므로, 워크플로우가 다음에 의존한다면:
- 외부 번역가
- “Crowdin”, “Lokalise” 등 유사한 도구
- 번역 파일을 다루는 비기술 편집자
- 많은 지원 언어
그렇다면 이 방법은 적합하지 않을 가능성이 높습니다.
하지만 번역이 코드 내에서 작성·관리되고, 지원 언어가 소규모~중간 규모라면 개발자 경험이 놀라울 정도로 좋습니다.
궁금하시다면, 이 프로젝트는 오픈 소스입니다. 확인해 보세요:
- GitHub:
- npm:
피드백을 언제든 환영합니다!