【Nuxt 3 & Vue I18n】SSR環境下での言語切り替えエラー「TypeError: Cannot read properties of undefined (reading ‘locale’)」

Nuxt 3で多言語対応サイトを構築中、言語切り替えボタンを実装した際、予期せぬエラーや表示の不整合にハマったので備忘録として残します。

この記事でわかること

  • vue-i18nのlocaleエラーの原因と対策
  • SSR環境下でのリアクティブな言語切り替えの実装方法
  • Nuxt 3における正しいi18nプラグインの書き方

[現象] 言語切り替え時に発生するエラー

動的ルーティングや非同期コンポーネントを使用している環境で、言語を切り替えようとするとコンソールに以下のエラーが表示される、またはUIのテキストが前の言語のまま変わらないという現象が発生しました。

TypeError: Cannot read properties of undefined (reading 'locale')
    at Proxy.getLocale (vue-i18n.esm-bundler.js:XXXX:XX)

[環境]

  • Framework: Nuxt 3.x
  • Library: vue-i18n v9.x
  • Rendering: SSR (Server Side Rendering)

[原因] ライフサイクルとハイドレーションの不整合

主な原因は、Nuxt 3のSSR環境下で vue-i18n のインスタンスが初期化される前に、コンポーネントが locale プロパティにアクセスしようとしていることです。

特に、以下の要因が重なると問題が顕著になります:

  • 非同期処理のタイミング: SSRでのデータ取得とクライアントサイドでの再ハイドレーション時に、言語状態の一貫性が保たれていない。
  • リアクティビティの欠如: watchlocale を監視する際、オブジェクトの深い階層やComposition APIの参照を正しくハンドルできていない。
  • 動的ルートの遅延: useRouter による遷移と i18n の状態更新が同期していない。

[解決策] watchEffectを用いたプラグインの実装

この問題を解決するには、Nuxt 3のプラグイン内で watchEffect を使用し、言語の変更を確実に検知してUIに反映させる構成にするのが最も安定します。

Before: 問題が発生しやすい実装

// 監視が不十分で、SSR時や非同期コンポーネントでエラーになりやすい
watch(() => i18n.global.locale, (newLocale) => {
  console.log('Locale changed to:', newLocale)
})

After: 安定した実装(plugins/i18n.ts)

import { watchEffect } from 'vue'
import { createI18n } from 'vue-i18n'

export default defineNuxtPlugin(({ vueApp }) => {
  const i18n = createI18n({
    legacy: false, // Composition API モードを有効化
    globalInjection: true,
    locale: 'ja',
    messages: {
      ja: { message: { hello: 'こんにちは' } },
      en: { message: { hello: 'Hello' } }
    }
  })

  vueApp.use(i18n)

  // watchEffectを使用して言語変更をリアクティブに追跡
  if (process.client) {
    watchEffect(() => {
      const locale = i18n.global.locale.value
      // ここでlocalStorageへの保存や、htmlのlang属性更新などを行うと確実
      document.querySelector('html')?.setAttribute('lang', locale)
    })
  }
})

コンポーネント側(pages/index.vue)

<template>
  <div>
    <p>{{ $t('message.hello') }}</p>
    <button @click="changeLocale('ja')">日本語</button>
    <button @click="changeLocale('en')">English</button>
  </div>
</template>

<script setup>
import { useI18n } from '#imports'

const { locale } = useI18n()

const changeLocale = (newLocale) => {
  locale.value = newLocale
}
</script>

[まとめ]

Nuxt 3 + vue-i18nの環境で言語切り替えがうまくいかない場合は、以下の3点を確認してください。

  1. legacy: false が設定されているか(Composition APIを使用する場合)
  2. watchEffect 等で locale.value の変更を明示的に監視しているか
  3. SSRとクライアントサイドで locale の初期値に乖離がないか

正しく設定すれば、SSR特有のハイドレーションエラーを回避し、スムーズな言語切り替え体験を提供できます。

タイトルとURLをコピーしました