ts-intl
챕터:개요 및 설치

개요 및 설치

ts-intl은 런타임 의존성이 없고, 특정 프레임워크에 종속되지 않으며, 엄격한 타입 안전성을 제공하는 국제화(i18n) 라이브러리입니다.

next-intl과 유사한 직관적인 API 설계를 제공하면서도 Next.js나 특정 UI 프레임워크에 의존하지 않아, 모던 TypeScript 개발 환경 어디서나 유연하게 사용할 수 있습니다.

핵심 설계 원칙

  • 런타임 의존성 제로: 런타임 외부 의존성이 전혀 없으며, 빌드 단계의 코드 생성 과정도 필요하지 않습니다.
  • 프레임워크 무관: Astro, React, Vue, Svelte, Node.js, Bun 및 브라우저 환경을 모두 지원합니다.
  • 엄격한 타입 안전성: 번역 키, 동적 보간 매개변수, 네임스페이스 및 다국어 딕셔너리 간 구조 정합성에 대해 완전한 정적 타입 검사를 제공합니다.

설치

선호하는 패키지 매니저를 사용하여 @aaakul/ts-intl을 설치합니다:

# pnpm
pnpm add @aaakul/ts-intl

# npm
npm install @aaakul/ts-intl

# yarn
yarn add @aaakul/ts-intl

# bun
bun add @aaakul/ts-intl

빠른 시작

1. 다국어 메시지 딕셔너리 정의

TypeScript 객체에 as const 단언을 적용하여 타입 시스템이 키 이름과 매개변수 타입을 자동으로 추론할 수 있도록 합니다:

// messages/ko-KR.ts
export default {
  common: {
    title: "시스템 대시보드",
    greeting: "안녕하세요, {name: string}님!",
    items: {
      one: "총 1개의 항목",
      other: "총 {count}개의 항목",
    },
  },
} as const;
// messages/en-US.ts
export default {
  common: {
    title: "System Dashboard",
    greeting: "Hello, {name: string}!",
    items: {
      one: "1 item in total",
      other: "{count} items in total",
    },
  },
} as const;

2. i18n 인스턴스 초기화

// i18n.ts
import { createI18n } from "@aaakul/ts-intl";
import koKR from "./messages/ko-KR";
import enUS from "./messages/en-US";

export const {
  getTranslations,
  getFormatter,
  isSupportedLanguage,
  languages,
  defaultLanguage,
} = createI18n({
  defaultLanguage: "ko-KR",
  messages: {
    "ko-KR": koKR,
    "en-US": enUS,
  },
});

export type Language = (typeof languages)[number];

3. 번역 함수 가져오기 및 호출

import { getTranslations } from "./i18n";

const t = getTranslations("ko-KR", "common");

// 1. 정적 키
const title = t("title");
// 추론된 타입: (key: "title") => string
// 출력: "시스템 대시보드"

// 2. 매개변수 보간
const greeting = t("greeting", { name: "개발자" });
// 추론된 타입: (key: "greeting", params: { name: string }) => string
// 출력: "안녕하세요, 개발자님!"

// 3. 복수형 규칙 처리
const items = t("items", { count: 5 });
// 추론된 타입: (key: "items", params: { count: number }) => string
// 출력: "총 5개의 항목"

지원하는 딕셔너리 포맷

TypeScript 정적 정의 (권장)

as const를 사용하여 딕셔너리를 정의하면 컴파일 타임 키 유효성 검사 및 매개변수 타입 추출을 완벽하게 지원합니다:

export default {
  auth: {
    login: "로그인",
    welcome: "다시 오신 것을 환영합니다, {username: string}님!",
  },
} as const;

JSON 딕셔너리 포맷

참고: JSON 포맷 역시 키 자동 완성 및 오타 검사를 지원합니다. 단, JSON 자체는 as const를 지원하지 않으므로 템플릿 변수 매개변수는 유연한 타입으로 추론됩니다.

import koKR from "./messages/ko-KR.json";
import enUS from "./messages/en-US.json";

export const { getTranslations } = createI18n({
  defaultLanguage: "ko-KR",
  messages: { "ko-KR": koKR, "en-US": enUS },
});

동적 온디맨드 임포트 (await import)

Top-Level Await를 지원하는 모던 런타임 환경(Astro, Vite, Node 22+, Bun 등)에서는 await import(...)를 통해 딕셔너리 파일을 동적으로 가져올 수 있습니다:

import { createI18n } from "@aaakul/ts-intl";

const koKR = (await import("./messages/ko-KR.ts")).default;
const enUS = (await import("./messages/en-US.ts")).default;

export const { getTranslations } = createI18n({
  defaultLanguage: "ko-KR",
  messages: { "ko-KR": koKR, "en-US": enUS },
});

로케일 분할 (Locale Code Splitting)

다국어 프로젝트에서 모든 언어의 딕셔너리가 단일 클라이언트 번들에 포함되는 것을 방지하기 위해, 로더 맵(Loader Map)을 정의하여 필요한 로케일만 동적으로 분할 로드할 수 있습니다:

import { createI18n } from "@aaakul/ts-intl";

const loaders = {
  "ko-KR": () => import("./messages/ko-KR.ts"),
  "en-US": () => import("./messages/en-US.ts"),
} as const;

export type SupportedLanguage = keyof typeof loaders;

export async function loadI18n<L extends SupportedLanguage>(lang: L) {
  const messages = (await loaders[lang]()).default;
  return createI18n({
    defaultLanguage: lang,
    messages: { [lang]: messages } as Record<L, typeof messages>,
  });
}

타입 안전성 보장: 딕셔너리 파일이 export default { ... } as const; 또는 JSON 포맷으로 내보내지는 한, await import를 통한 동적 임포트 환경에서도 모든 계층의 네임스페이스, 리터럴 키 자동 완성, 매개변수 타입 검증({param: type})이 온전하게 유지됩니다.