2025-12-02 06:27:21 +00:00

5.3 KiB

다국어 지원 (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 대신)

페이지 생성 시

// ❌ 잘못된 방식
"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.tst() 함수를 사용합니다:

// 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} 플레이스홀더 사용
  • 일본어는 어린이 친화적 표현 (한자 최소화, ひらがな 우선)

일본어 번역 주의사항

  • 한자 최소화: 초등학생 대상이므로 가능한 한 히라가나 사용
  • 정중한 표현: です/ます 체 사용
  • 예시:
    // ❌ 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 설정

관련 문서


© 2024 BlueNovaLab. All rights reserved.