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. 翻訳辞書の定義

as const で TypeScript オブジェクトをアサーションし、型システムがキー名とパラメータ型を推論できるようにします:

// messages/ja-JP.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 jaJP from "./messages/ja-JP";
import enUS from "./messages/en-US";

export const {
  getTranslations,
  getFormatter,
  isSupportedLanguage,
  languages,
  defaultLanguage,
} = createI18n({
  defaultLanguage: "ja-JP",
  messages: {
    "ja-JP": jaJP,
    "en-US": enUS,
  },
});

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

3. 翻訳関数の取得と呼び出し

import { getTranslations } from "./i18n";

const t = getTranslations("ja-JP", "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 jaJP from "./messages/ja-JP.json";
import enUS from "./messages/en-US.json";

export const { getTranslations } = createI18n({
  defaultLanguage: "ja-JP",
  messages: { "ja-JP": jaJP, "en-US": enUS },
});

動的インポート(await import)

Top-Level Await をサポートするモダンな実行環境(Astro、Vite、Node 22+、Bun など)では、await import(...) を使用して辞書ファイルを動的に読み込むことができます:

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

const jaJP = (await import("./messages/ja-JP.ts")).default;
const enUS = (await import("./messages/en-US.ts")).default;

export const { getTranslations } = createI18n({
  defaultLanguage: "ja-JP",
  messages: { "ja-JP": jaJP, "en-US": enUS },
});

言語ごとのコード分割(Locale Code Splitting)

多言語プロジェクトにおいて、すべての辞書が同一のクライアントバンドルに含まれるのを防ぐため、ローダーマップを定義してアクティブな言語のみをオンデマンドで読み込むことが可能です:

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

const loaders = {
  "ja-JP": () => import("./messages/ja-JP.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})といった静的型チェックが完全に維持されます。