5.3 KiB
5.3 KiB
다국어 지원 (i18n) 가이드
라온누리 프로젝트의 다국어 지원 시스템 가이드입니다.
필수 적용 규칙
모든 새로운 UI 및 페이지는 다국어 지원이 필수입니다.
기본 규칙
- 하드코딩 텍스트 금지: 모든 사용자 대면 텍스트는 번역 파일(
messages/ko.json,messages/en.json,messages/ja.json)에 저장 - 번역 키 사용:
useTranslations('namespace')훅으로 번역 텍스트 사용 - 타입 안전 Link:
import {Link} from '@/i18n/routing'사용 (next/link 대신) - 타입 안전 Router:
import {useRouter} from '@/i18n/routing'사용 (next/navigation 대신)
페이지 생성 시
// ❌ 잘못된 방식
"use client";
import {useRouter} from "next/navigation";
export default function NewPage() {
return <h1>새 페이지</h1>; // 하드코딩 금지!
}
// ✅ 올바른 방식
"use client";
import {useRouter} from "@/i18n/routing"; // next-intl router
import {useTranslations} from "next-intl";
export default function NewPage() {
const t = useTranslations('newPage');
return <h1>{t('title')}</h1>;
}
// messages/ko.json에 추가:
// "newPage": { "title": "새 페이지" }
// messages/en.json에 추가:
// "newPage": { "title": "New Page" }
// messages/ja.json에 추가:
// "newPage": { "title": "新しいページ" }
컴포넌트 생성 시
// 공통 컴포넌트는 namespace를 props로 받거나 고정
"use client";
import {useTranslations} from "next-intl";
export function MyComponent() {
const t = useTranslations('components.myComponent');
return (
<Button>{t('submit')}</Button>
);
}
Server Component에서 번역 사용
Server Component에서는 getTranslations() 함수를 사용합니다:
// ✅ Server Component
import {getTranslations} from "next-intl/server";
export default async function WritingDetailPage({
params,
}: {
params: Promise<{locale: string; writingId: string}>;
}) {
const {writingId} = await params;
const t = await getTranslations('interaction');
return (
<Container>
<BackButton label={t('back')} />
<Heading>{writing.title}</Heading>
</Container>
);
}
서비스 레이어에서 번역 사용
React 훅을 사용할 수 없는 곳에서는 src/utils/i18n.ts의 t() 함수를 사용합니다:
// src/services/firebaseAuth.ts
import { t } from "@/utils/i18n";
export function getErrorMessage(code: string): string {
switch (code) {
case "auth/invalid-email":
return t("errors.auth.invalidEmail");
case "auth/weak-password":
return t("errors.auth.weakPassword", { min: 8 });
default:
return t("errors.auth.unknown");
}
}
파일 위치: src/utils/i18n.ts
detectLocale(): URL path 우선 → navigator.language fallbackt(): nested key 지원, 파라미터 치환
체크리스트 (페이지/컴포넌트 생성 시)
- 모든 사용자 대면 텍스트를 번역 키로 추출
messages/ko.json,messages/en.json,messages/ja.json에 번역 추가useTranslations훅 사용 (Client Component)getTranslations함수 사용 (Server Component)- next-intl의 Link/Router 사용
- 파라미터가 있는 경우
{name}플레이스홀더 사용 - 일본어는 어린이 친화적 표현 (한자 최소화, ひらがな 우선)
일본어 번역 주의사항
- 한자 최소화: 초등학생 대상이므로 가능한 한 히라가나 사용
- 정중한 표현: です/ます 체 사용
- 예시:
// ❌ Bad "login": "ログイン" // ✅ Good "login": "ログインする"
언어별 파일
| 언어 | 파일 | 라인 수 | 키 개수 |
|---|---|---|---|
| 한국어 | messages/ko.json |
407줄 | 220+ 키 |
| 영어 | messages/en.json |
407줄 | 220+ 키 |
| 일본어 | messages/ja.json |
407줄 | 220+ 키 |
언어 전환 UI
LocaleSwitcher 컴포넌트 (src/components/layout/LocaleSwitcher.tsx):
- 국기 이모지 (🇰🇷 🇺🇸 🇯🇵)
- 현재 언어 체크 표시
- Portal 사용 (z-index 이슈 방지)
설정 파일
middleware.ts
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
export default createMiddleware(routing);
export const config = {
matcher: ["/", "/(ko|en|ja)/:path*"],
};
i18n/routing.ts
import { defineRouting } from "next-intl/routing";
export const routing = defineRouting({
locales: ["ko", "en", "ja"],
defaultLocale: "ko",
localePrefix: "always",
});
next.config.ts
import createNextIntlPlugin from "next-intl/plugin";
const withNextIntl = createNextIntlPlugin();
export default withNextIntl({
// ... other config
});
라우팅 구조
/ko/* - 한국어 페이지
/en/* - 영어 페이지
/ja/* - 일본어 페이지
- 브라우저 언어 자동 감지 (Accept-Language 헤더)
- NEXT_LOCALE 쿠키 저장
- localeDetection: true 설정
관련 문서
- next-intl Documentation
- CLAUDE.md - 개발 가이드
- TECH_STACK.md - 기술 스택
© 2024 BlueNovaLab. All rights reserved.