SvelteKitでi18n(多言語対応)を実装した話

SvelteKitでi18n(国際化・多言語対応)を実装しました。まずシンプルな実装で概念を理解し、本番向けのアプローチも紹介します。


シンプルなi18nの実装

1. 翻訳ファイルを作成

// src/lib/i18n.ts
export const messages = {
  ja: {
    title: 'TODOリスト',
    add: '追加',
    delete: '削除',
    complete: '完了',
    undo: '戻す',
    placeholder: 'タスクを入力',
    welcome: (name: string) => `ようこそ、${name}さん`,
  },
  en: {
    title: 'TODO List',
    add: 'Add',
    delete: 'Delete',
    complete: 'Complete',
    undo: 'Undo',
    placeholder: 'Enter a task',
    welcome: (name: string) => `Welcome, ${name}`,
  },
} as const;

export type Locale = keyof typeof messages;

as const をつけることで、各言語のキーが型として推論されます。関数を使うと変数を含む文字列も型安全に扱えます。

2. 言語状態をグローバルに管理

// src/lib/locale.svelte.ts
import type { Locale } from './i18n';

let locale = $state<Locale>('ja');

export function getLocale() {
  return locale;
}

export function setLocale(l: Locale) {
  locale = l;
}

Svelte 5の $state をモジュールレベルで使うことで、どのコンポーネントからでも同じ言語状態を参照・更新できます。

3. コンポーネントで使う

<script lang="ts">
  import { messages } from '$lib/i18n';
  import { getLocale, setLocale } from '$lib/locale.svelte';

  const m = $derived(messages[getLocale()]);
</script>

<button onclick={() => setLocale('ja')}>日本語</button>
<button onclick={() => setLocale('en')}>English</button>

<h1>{m.title}</h1>
<p>{m.welcome('Nori')}</p>

<input placeholder={m.placeholder} />
<button>{m.add}</button>

$derived(messages[getLocale()]) で言語が変わるたびに m が自動更新されます。


シンプル実装の課題

課題 内容
URLに言語が出ない /en/todo のようなURL対応が必要
SSR時の言語検出 Accept-Language ヘッダーから言語を自動判定できない
翻訳ファイルの管理 規模が大きくなるとTypeScriptファイルでは管理しにくい

本番向け:paraglide-js(推奨)

SvelteKit公式が推奨する Inlang の paraglide-js はこれらの課題を解決します。

npx @inlang/paraglide-sveltekit init

特徴

  • JSONファイルで翻訳を管理 — チームでの分業がしやすい
  • URLベースの言語切り替え/ja/todo / /en/todo のようなルート対応
  • ビルド時コンパイル — 実行時のオーバーヘッドがゼロ
  • Cloudflare Workers対応 — エッジ環境でも動作する
  • TypeScript完全対応 — 翻訳キーが型として補完される

まとめ

  • シンプルな実装は翻訳オブジェクト + $state で言語切り替えができる
  • $derived で言語が変わるたびにUIが自動更新される
  • 関数を使うと変数を含む文字列も型安全に扱える
  • 本番ではURLベースの言語管理ができる paraglide-js が推奨
← トップページに戻る