Переключение языков в Vue

Библиотека i18next используется для интернационализации приложений и предоставляет гибкий механизм управления переводами, переключения языков, форматирования и локализации интерфейса. В экосистеме Vue библиотека часто применяется совместно с адаптером react-i18next для React или напрямую через API i18next. Для Vue обычно используется связка с vue-i18next либо собственный composable-слой.

Структура проекта с поддержкой нескольких языков часто выглядит следующим образом:

src/
├── i18n/
│   ├── index.js
│   ├── locales/
│   │   ├── ru.json
│   │   ├── en.json
│   │   └── kz.json
├── components/
├── App.vue
└── main.js

Установка зависимостей:

npm install i18next

Базовая конфигурация:

// src/i18n/index.js

import i18next from 'i18next'

import ru from './locales/ru.json'
import en from './locales/en.json'
import kz from './locales/kz.json'

i18next.init({
  lng: 'ru',
  fallbackLng: 'en',

  resources: {
    ru: {
      translation: ru
    },
    en: {
      translation: en
    },
    kz: {
      translation: kz
    }
  }
})

export default i18next

Файлы переводов:

// ru.json
{
  "title": "Главная страница",
  "welcome": "Добро пожаловать"
}
// en.json
{
  "title": "Home page",
  "welcome": "Welcome"
}

Использование переводов в компонентах Vue

После настройки экземпляр i18next импортируется в компоненты.

Пример компонента:

<script setup>
import i18next from '@/i18n'
</script>

<template>
  <div>
    <h1>{{ i18next.t('title') }}</h1>
    <p>{{ i18next.t('welcome') }}</p>
  </div>
</template>

Метод t() выполняет поиск перевода по ключу.


Переключение языков

Основная функция переключения языка — changeLanguage().

Простейший пример:

<script setup>
import i18next from '@/i18n'

const switchLanguage = (lang) => {
  i18next.changeLanguage(lang)
}
</script>

<template>
  <div>
    <button @click="switchLanguage('ru')">
      Русский
    </button>

    <button @click="switchLanguage('en')">
      English
    </button>

    <button @click="switchLanguage('kz')">
      Қазақша
    </button>
  </div>
</template>

При вызове метода:

i18next.changeLanguage('en')

библиотека:

  1. меняет активную локаль;
  2. обновляет внутреннее состояние;
  3. начинает использовать переводы выбранного языка.

Реактивное обновление интерфейса

Проблема прямого использования i18next.t() внутри шаблона Vue заключается в отсутствии реактивности. Vue не отслеживает внутренние изменения состояния i18next.

Без дополнительной логики интерфейс может не обновиться автоматически после переключения языка.

Распространённое решение — использовать ref.

Создание composable для переводов

// composables/useI18n.js

import { ref } from 'vue'
import i18next from '@/i18n'

const currentLanguage = ref(i18next.language)

i18next.on('languageChanged', (lng) => {
  currentLanguage.value = lng
})

export function useI18n() {
  const t = (key) => {
    currentLanguage.value
    return i18next.t(key)
  }

  const changeLanguage = (lng) => {
    i18next.changeLanguage(lng)
  }

  return {
    t,
    changeLanguage,
    currentLanguage
  }
}

Использование:

<script setup>
import { useI18n } from '@/composables/useI18n'

const {
  t,
  changeLanguage,
  currentLanguage
} = useI18n()
</script>

<template>
  <div>
    <h1>{{ t('title') }}</h1>

    <p>
      Текущий язык:
      {{ currentLanguage }}
    </p>

    <button @click="changeLanguage('ru')">
      RU
    </button>

    <button @click="changeLanguage('en')">
      EN
    </button>
  </div>
</template>

Теперь Vue реагирует на изменение языка и автоматически перерисовывает компонент.


Сохранение выбранного языка

После обновления страницы язык по умолчанию обычно сбрасывается. Для сохранения выбранной локали применяется localStorage.

Сохранение языка

const changeLanguage = (lng) => {
  localStorage.setItem('language', lng)
  i18next.changeLanguage(lng)
}

Загрузка языка при старте приложения

const savedLanguage =
  localStorage.getItem('language') || 'ru'

i18next.init({
  lng: savedLanguage,
  fallbackLng: 'en',
  resources: {
    ru: {
      translation: ru
    },
    en: {
      translation: en
    }
  }
})

Теперь приложение восстанавливает язык пользователя автоматически.


Автоматическое определение языка браузера

i18next умеет определять язык браузера через плагин i18next-browser-languagedetector.

Установка:

npm install i18next-browser-languagedetector

Настройка:

import i18next from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'

i18next
  .use(LanguageDetector)
  .init({
    fallbackLng: 'en',

    resources: {
      ru: {
        translation: ru
      },
      en: {
        translation: en
      }
    }
  })

Если браузер пользователя работает на русском языке, автоматически будет выбрана локаль ru.


Приоритет выбора языка

Часто применяется следующая схема:

  1. язык из localStorage;
  2. язык браузера;
  3. язык по умолчанию.

Настройка:

detection: {
  order: ['localStorage', 'navigator'],
  caches: ['localStorage']
}

Полная конфигурация:

i18next
  .use(LanguageDetector)
  .init({
    fallbackLng: 'en',

    detection: {
      order: ['localStorage', 'navigator'],
      caches: ['localStorage']
    },

    resources: {
      ru: {
        translation: ru
      },
      en: {
        translation: en
      }
    }
  })

Динамическая загрузка переводов

В больших приложениях хранение всех переводов в одном бандле увеличивает размер JavaScript-файлов.

Оптимальное решение — ленивое подключение локалей.

Асинхронная загрузка

async function loadLanguage(lang) {
  const translations = await import(
    `./locales/${lang}.json`
  )

  i18next.addResourceBundle(
    lang,
    'translation',
    translations.default
  )

  await i18next.changeLanguage(lang)
}

Использование:

await loadLanguage('en')

Переключение языка через выпадающий список

Распространённый интерфейс — <select>.

<script setup>
import { useI18n } from '@/composables/useI18n'

const {
  currentLanguage,
  changeLanguage
} = useI18n()
</script>

<template>
  <sel ect
    :value="currentLanguage"
    @change="changeLanguage($event.target.value)"
  >
    <option value="ru">
      Русский
    </option>

    <option value="en">
      English
    </option>

    <option value="kz">
      Қазақша
    </option>
  </select>
</template>

Синхронизация языка с URL

Во многих приложениях язык хранится в адресной строке:

/ru/products
/en/products

Это полезно для:

  • SEO;
  • генерации ссылок;
  • серверного рендеринга;
  • разделения локализованных страниц.

Получение языка из маршрута

import { useRoute } fr om 'vue-router'

const route = useRoute()

const lang = route.params.lang

i18next.changeLanguage(lang)

Переключение языка при изменении маршрута

router.beforeEach((to, from, next) => {
  const lang = to.params.lang

  if (lang) {
    i18next.changeLanguage(lang)
  }

  next()
})

Создание глобального плагина Vue

Чтобы не импортировать i18next в каждый компонент, создаётся глобальный плагин.

// plugins/i18n.js

import i18next from '@/i18n'

export default {
  install(app) {
    app.config.globalProperties.$t =
      i18next.t.bind(i18next)

    app.config.globalProperties.$i18n =
      i18next
  }
}

Подключение:

import { createApp } from 'vue'
import App from './App.vue'

import i18nPlugin from './plugins/i18n'

const app = createApp(App)

app.use(i18nPlugin)

app.mount('#app')

Использование:

<template>
  <h1>{{ $t('title') }}</h1>
</template>

Обработка отсутствующих переводов

Если ключ отсутствует:

i18next.t('unknown_key')

будет возвращён сам ключ:

unknown_key

Для отслеживания ошибок локализации используется параметр:

saveMissing: true

Также можно подключить собственный обработчик:

missingKeyHandler: function (
  lng,
  ns,
  key
) {
  console.log('Отсутствует перевод:', key)
}

Fallback-языки

Если перевод отсутствует в текущей локали, i18next может использовать запасной язык.

Пример:

fallbackLng: 'en'

Сценарий:

// ru.json
{
  "title": "Главная"
}
// en.json
{
  "title": "Home",
  "profile": "Profile"
}

Вызов:

i18next.t('profile')

вернёт:

Profile

поскольку ключ отсутствует в русском переводе.


Namespace в i18next

В крупных проектах переводы разделяются по namespace.

Пример:

locales/
├── ru/
│   ├── common.json
│   ├── auth.json
│   └── profile.json

Настройка:

i18next.init({
  ns: ['common', 'auth'],
  defaultNS: 'common'
})

Использование:

i18next.t('auth:login')

Переключение языка и асинхронные операции

changeLanguage() возвращает Promise.

await i18next.changeLanguage('en')

Это важно при:

  • динамической загрузке переводов;
  • SSR;
  • обновлении роутов;
  • изменении заголовков страницы.

Пример:

const switchLanguage = async (lang) => {
  await i18next.changeLanguage(lang)

  document.title =
    i18next.t('title')
}

Обновление HTML-атрибута lang

После переключения языка желательно обновлять атрибут:

<html lang="ru">

Реализация:

i18next.on('languageChanged', (lng) => {
  document.documentElement.lang = lng
})

Это важно для:

  • SEO;
  • accessibility;
  • экранных дикторов;
  • поисковых систем.

Поддержка RTL-языков

Для арабского и иврита требуется направление текста справа налево.

const rtlLanguages = ['ar', 'he']

i18next.on('languageChanged', (lng) => {
  document.documentElement.dir =
    rtlLanguages.includes(lng)
      ? 'rtl'
      : 'ltr'
})

Типичные ошибки при переключении языков

Отсутствие реактивности

Ошибка:

{{ i18next.t('title') }}

без отслеживания состояния языка.

Решение — использование ref, composable или интеграции с реактивной системой Vue.


Потеря языка после обновления страницы

Причина — отсутствие сохранения в localStorage.

Решение:

localStorage.setItem('language', lng)

Несовпадение ключей

Ошибка:

i18next.t('titles')

при наличии:

{
  "title": "Главная"
}

Рекомендуется использовать строгую структуру ключей:

{
  "pages": {
    "home": {
      "title": "Главная"
    }
  }
}

Архитектура локализации в крупных Vue-приложениях

Часто применяется следующая структура:

src/
├── i18n/
│   ├── index.js
│   ├── plugins/
│   ├── composables/
│   ├── locales/
│   │   ├── ru/
│   │   ├── en/
│   │   └── kz/
│   └── helpers/

Где:

  • locales/ — переводы;
  • composables/ — реактивные хуки;
  • helpers/ — функции локализации;
  • plugins/ — интеграция с Vue.

Подобная архитектура упрощает:

  • масштабирование;
  • поддержку новых языков;
  • lazy loading;
  • SSR;
  • модульную локализацию.