SvelteKitでi18nを頑張っていたら言語切り替えにつまづいてどうにか解消した話

## この記事の概要

練習がてらSvelteKitでサイトを作っていて、練習なので外部ライブラリに頼らずi18n対応をしてみていました。

その際、ヘッダーのリンクから日本語のパス、英語のパスへそれぞれ飛べるようにしようとしたらつまづきました。

~~正しいアプローチなのか分からないのですが、ひとまず解消できた方法を記事にします。~~

:::note alert
~~そもそものSvelteKitの扱い、i18nの実装方法、回避した方法、どれもあまり正しさに自信がありません。備忘録の側面が強いので、鵜呑みにはしないでください。間違いがあれば指摘いただけると助かります。~~
:::

:::note
追記：ちゃんとした方法を教えていただきましたので、そちらの内容で記事を更新しました。
:::

## 環境

| 依存関係 | バージョン |
| --- | --- |
| svelte | 5.1.13 |
| @sveltejs/kit | 2.8.0 |

## やっていたこと

今回の話に関係のある部分でいうと、このようなディレクトリ構造のプロジェクトです。

```
.
├── src
│   ├── lib
│   │   └── components
│   │       └── Header.svelte
│   └── routes
│       ├── +page.server.ts
│       └── [lang]
│           ├── +layout.svelte
│           ├── +layout.ts
│           ├── +page.svelte
│           ├── +page.ts
│           ├── foo
│           │   ├── +page.svelte
│           │   └── +page.ts
.           .
.           .
.           .
```

`+page.ts`では以下のようなイメージで、それぞれの言語と`load`関数を定義していました。

```typescript:+page.ts
const i18nText = {
  en: {
    title: "English title"
    description: "English description",
  },
  ja: {
    title: "日本語のタイトル"
    description: "日本語の説明文",
  },
};

export function load({ params }: { params: { lang: "en" | "ja" } }) {
  return {
    lang: params.lang,
    i18nText: i18nText[params.lang],
  };
}
```

それを、各ページにてこのようなイメージで使っていました。

```svelte:+page.svelte
<script lang="ts">
  let { data } = $props();
  const { i18nText } = data;
</script>

<h1>{i18nText.title}</h1>
<p>{i18nText.description}</p>
```

というわけで、このサイトでは次のようなURLでアクセスできるわけです。

| ページ種別 | 英語版URL | 日本語版URL |
| --- | --- | --- |
| トップページ | `example.com/en` | `example.com/ja` |
| `/foo`のページ | `example.com/en/foo` | `example.com/ja/foo` |

ここで、全ページ共通で使うコンポーネントに以下のようにリンクを設定していました。
（全体的なコードは簡略化してあります）

```svelte:Header.svelte
<header>
  <div>
    <a href="/">TOP</a>
    <a href="/en">English</a>
    <a href="/ja">日本語</a>
  </div>
</header>
```

`/en`と`/ja`自体は固定なのだから、それぞれのリンクをクリックすれば英語版トップ、日本語版トップへ飛ぶだろうと踏んでいましたが、上手くいきませんでした。

URLは目的のものに変わるのですが、画面に描画されている内容が変わらない、という状態でした。

## 解消した方法

`data`を`$derived`で囲うのみでした。

```diff_svelte:+page.svelte
 <script lang="ts">
   let { data } = $props();
-  const { i18nText } = data;
+  const { i18nText } = $derived(data);
 </script>

 <h1>{i18nText.title}</h1>
 <p>{i18nText.description}</p>
```

当初、`$derived`関連は以下のようにだけ理解していました。

- リアクティブな変数を`$state`で宣言する
- `$state`で宣言した変数から派生するものは`$derived`で宣言する

しかし今回の多言語用テキストも`$derived`での派生の対象だったようで、囲ってあげれば無事に動きました。

https://svelte.dev/docs/svelte/$derived

↓ちゃんとしたやり方教えていただいた様子

https://x.com/shamokit_y2323/status/1882009240094884172

## 旧：解消した方法

:::note alert
当初はこちらの方法で対応していましたが、いいやり方ではないです。一応ログとしてだけ残してありますが、上の方法を参考にしてください。
:::

<details><summary>詳細</summary>

リンクのそれぞれに`data-sveltekit-reload`をつけました。

```diff_svelte:Header.svelte
 <header>
   <div>
     <a href="/">TOP</a>
     <div>
-      <a href="/en">English</a>
-      <a href="/ja">日本語</a>
+      <a href="/en" data-sveltekit-reload>English</a>
+      <a href="/ja" data-sveltekit-reload>日本語</a>
     </div>
   </div>
 </header>
```

ブラウザのdevtoolsのnetworkを見ると分かるように、通常SvelteKitのアプリケーション内で移動しても、ページ自体の読み込みはなされません。
しかし`data-sveltekit-reload`をつけると、完全な読み込み（？）が発生します。

https://svelte.dev/docs/kit/link-options#data-sveltekit-reload

</details>