# 다국어 지원 (i18n) 가이드 라온누리 프로젝트의 다국어 지원 시스템 가이드입니다. --- ## 필수 적용 규칙 **모든 새로운 UI 및 페이지는 다국어 지원이 필수입니다.** --- ## 기본 규칙 1. **하드코딩 텍스트 금지**: 모든 사용자 대면 텍스트는 번역 파일(`messages/ko.json`, `messages/en.json`, `messages/ja.json`)에 저장 2. **번역 키 사용**: `useTranslations('namespace')` 훅으로 번역 텍스트 사용 3. **타입 안전 Link**: `import {Link} from '@/i18n/routing'` 사용 (next/link 대신) 4. **타입 안전 Router**: `import {useRouter} from '@/i18n/routing'` 사용 (next/navigation 대신) --- ## 페이지 생성 시 ```typescript // ❌ 잘못된 방식 "use client"; import {useRouter} from "next/navigation"; export default function NewPage() { return

새 페이지

; // 하드코딩 금지! } // ✅ 올바른 방식 "use client"; import {useRouter} from "@/i18n/routing"; // next-intl router import {useTranslations} from "next-intl"; export default function NewPage() { const t = useTranslations('newPage'); return

{t('title')}

; } // messages/ko.json에 추가: // "newPage": { "title": "새 페이지" } // messages/en.json에 추가: // "newPage": { "title": "New Page" } // messages/ja.json에 추가: // "newPage": { "title": "新しいページ" } ``` --- ## 컴포넌트 생성 시 ```typescript // 공통 컴포넌트는 namespace를 props로 받거나 고정 "use client"; import {useTranslations} from "next-intl"; export function MyComponent() { const t = useTranslations('components.myComponent'); return ( ); } ``` --- ## Server Component에서 번역 사용 Server Component에서는 `getTranslations()` 함수를 사용합니다: ```typescript // ✅ 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 ( {writing.title} ); } ``` --- ## 서비스 레이어에서 번역 사용 React 훅을 사용할 수 없는 곳에서는 `src/utils/i18n.ts`의 `t()` 함수를 사용합니다: ```typescript // 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 fallback - `t()`: nested key 지원, 파라미터 치환 --- ## 체크리스트 (페이지/컴포넌트 생성 시) - [ ] 모든 사용자 대면 텍스트를 번역 키로 추출 - [ ] `messages/ko.json`, `messages/en.json`, `messages/ja.json`에 번역 추가 - [ ] `useTranslations` 훅 사용 (Client Component) - [ ] `getTranslations` 함수 사용 (Server Component) - [ ] next-intl의 Link/Router 사용 - [ ] 파라미터가 있는 경우 `{name}` 플레이스홀더 사용 - [ ] 일본어는 어린이 친화적 표현 (한자 최소화, ひらがな 우선) --- ## 일본어 번역 주의사항 - **한자 최소화**: 초등학생 대상이므로 가능한 한 히라가나 사용 - **정중한 표현**: です/ます 체 사용 - **예시**: ```json // ❌ 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 ```typescript 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 ```typescript import { defineRouting } from "next-intl/routing"; export const routing = defineRouting({ locales: ["ko", "en", "ja"], defaultLocale: "ko", localePrefix: "always", }); ``` ### next.config.ts ```typescript 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](https://next-intl-docs.vercel.app/) - [CLAUDE.md](./CLAUDE.md) - 개발 가이드 - [TECH_STACK.md](./TECH_STACK.md) - 기술 스택 --- © 2024 BlueNovaLab. All rights reserved.