Интеграция i18next с Vue 3 через Composition API
позволяет строить гибкую систему интернационализации без привязки к
Options API. Основная идея заключается в создании composable-функций,
предоставляющих доступ к текущему языку, переводам и механизмам смены
локали.
Composition API особенно хорошо сочетается с i18next,
поскольку библиотека изначально построена вокруг независимых
экземпляров, событий и реактивного обновления состояния.
Базовая установка:
npm install i18next
Для интеграции с Vue обычно дополнительно используются:
npm install i18next-browser-languagedetector
npm install i18next-http-backend
Одна из наиболее удобных структур:
src/
├── i18n/
│ ├── index.js
│ ├── locales/
│ │ ├── en/
│ │ │ └── common.json
│ │ └── ru/
│ │ └── common.json
│ └── composables/
│ └── useTranslation.js
Файл i18n/index.js:
import i18next from 'i18next'
import Backend from 'i18next-http-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
i18next
.use(Backend)
.use(LanguageDetector)
.init({
fallbackLng: 'en',
debug: true,
ns: ['common'],
defaultNS: 'common',
interpolation: {
escapeValue: false
},
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json'
}
})
export default i18next
locales/en/common.json
{
"welcome": "Welcome",
"profile": "Profile",
"logout": "Logout"
}
locales/ru/common.json
{
"welcome": "Добро пожаловать",
"profile": "Профиль",
"logout": "Выйти"
}
Composition API предполагает перенос логики в composable-функции.
Файл useTranslation.js:
import { ref, onMounted, onUnmounted } from 'vue'
import i18next from '../index'
const currentLanguage = ref(i18next.language)
export function useTranslation() {
const updateLanguage = (lng) => {
currentLanguage.value = lng
}
onMounted(() => {
i18next.on('languageChanged', updateLanguage)
})
onUnmounted(() => {
i18next.off('languageChanged', updateLanguage)
})
const t = (key, options = {}) => {
return i18next.t(key, options)
}
const changeLanguage = async (lng) => {
await i18next.changeLanguage(lng)
}
return {
t,
currentLanguage,
changeLanguage
}
}
<script setup>
import { useTranslation } from '@/i18n/composables/useTranslation'
const {
t,
currentLanguage,
changeLanguage
} = useTranslation()
</script>
<template>
<div>
<h1>{{ t('welcome') }}</h1>
<p>{{ currentLanguage }}</p>
<button @click="changeLanguage('en')">
English
</button>
<button @click="changeLanguage('ru')">
Русский
</button>
</div>
</template>
i18next сам по себе не является реактивным. Vue не
отслеживает внутренние изменения библиотеки автоматически. Именно
поэтому требуется:
languageChanged;ref;Без этого интерфейс не будет обновляться после смены языка.
Иногда полезно оборачивать переводы в computed.
import { computed } from 'vue'
import { useTranslation } from './useTranslation'
export function usePageTitle() {
const { t } = useTranslation()
const title = computed(() => t('profile'))
return {
title
}
}
i18next поддерживает разделение переводов по
namespace.
Пример:
locales/
├── en/
│ ├── common.json
│ └── auth.json
Инициализация:
i18next.init({
ns: ['common', 'auth'],
defaultNS: 'common'
})
Использование:
t('login', { ns: 'auth' })
Либо:
t('auth:login')
import i18next from '../index'
export function useNamespaceTranslation(namespace) {
const t = (key, options = {}) => {
return i18next.t(`${namespace}:${key}`, options)
}
return {
t
}
}
Использование:
const { t } = useNamespaceTranslation('auth')
При больших приложениях все переводы не загружаются сразу.
await i18next.loadNamespaces(['dashboard'])
После этого namespace становится доступен:
t('dashboard:title')
await i18next.changeLanguage('de')
Поскольку загрузка файлов может быть сетевой операцией, смена языка является асинхронной.
Composition API позволяет удобно организовать индикаторы загрузки.
import { ref } from 'vue'
import i18next from '../index'
export function useLanguageSwitcher() {
const loading = ref(false)
const switchLanguage = async (lng) => {
loading.value = true
try {
await i18next.changeLanguage(lng)
} finally {
loading.value = false
}
}
return {
loading,
switchLanguage
}
}
i18next поддерживает шаблонные переменные.
common.json
{
"hello": "Hello, {{name}}"
}
Использование:
t('hello', {
name: 'Alex'
})
Результат:
Hello, Alex
{
"userInfo": "Name: {{user.name}}, Age: {{user.age}}"
}
t('userInfo', {
user: {
name: 'John',
age: 28
}
})
i18next.init({
interpolation: {
format(value, format) {
if (format === 'uppercase') {
return value.toUpperCase()
}
return value
}
}
})
Перевод:
{
"title": "{{name, uppercase}}"
}
i18next содержит встроенную систему множественных
форм.
{
"item_one": "{{count}} item",
"item_other": "{{count}} items"
}
Использование:
t('item', { count: 1 })
t('item', { count: 10 })
Для русского языка автоматически используются:
onefewmanyotherПример:
{
"message_one": "{{count}} сообщение",
"message_few": "{{count}} сообщения",
"message_many": "{{count}} сообщений"
}
Контекст позволяет учитывать пол, роль или другие параметры.
{
"welcome_male": "Добро пожаловать",
"welcome_female": "Добро пожаловать"
}
t('welcome', {
context: 'male'
})
{
"content": "<strong>Important</strong> message"
}
Использование:
<div v-html="t('content')"></div>
Подход требует осторожности из-за XSS-рисков.
По умолчанию браузерный вывод экранируется Vue, однако
i18next тоже имеет механизм escape.
interpolation: {
escapeValue: true
}
Во Vue обычно устанавливают:
escapeValue: false
Поскольку Vue уже выполняет защиту при обычном выводе через
{{ }}.
Иногда composable объединяют с Pinia.
import { defineStore } from 'pinia'
import i18next from '@/i18n'
export const useLocaleStore = defineStore('locale', {
state: () => ({
current: i18next.language
}),
actions: {
async setLanguage(lng) {
await i18next.changeLanguage(lng)
this.current = lng
}
}
})
Composition API позволяет реагировать на смену языка.
watch(currentLanguage, (newLang) => {
console.log('Language changed:', newLang)
})
Иногда экземпляр i18next внедряется через Dependency
Injection.
import { createApp } from 'vue'
import App from './App.vue'
import i18next from './i18n'
const app = createApp(App)
app.provide('i18next', i18next)
app.mount('#app')
import { inject } from 'vue'
const i18next = inject('i18next')
При серверном рендеринге необходимо:
i18next на каждый
запрос;Пример фабрики:
import i18next from 'i18next'
export function createI18nInstance() {
const instance = i18next.createInstance()
instance.init({
fallbackLng: 'en'
})
return instance
}
Vue 3 поддерживает Suspense, что удобно для асинхронной
загрузки переводов.
<Suspense>
<DashboardPage />
</Suspense>
Компонент может ожидать загрузку namespace:
await i18next.loadNamespaces(['dashboard'])
Composition API удобно комбинируется с Vue Router.
const routes = [
{
path: '/ru/profile',
component: Profile
},
{
path: '/en/profile',
component: Profile
}
]
watch(route, async (to) => {
const lang = to.params.lang
if (lang) {
await i18next.changeLanguage(lang)
}
})
const savedLanguage = localStorage.getItem('language')
if (savedLanguage) {
i18next.changeLanguage(savedLanguage)
}
Сохранение:
await i18next.changeLanguage('ru')
localStorage.setItem('language', 'ru')
i18next-browser-languagedetector умеет определять язык
через:
navigator.language;Настройка:
detection: {
order: ['localStorage', 'navigator'],
caches: ['localStorage']
}
i18next.init({
saveMissing: true
})
Либо:
missingKeyHandler(lng, ns, key) {
console.warn(`Missing translation: ${key}`)
}
fallbackLng: {
'de-CH': ['fr', 'it'],
default: ['en']
}
Composition API особенно хорошо раскрывается вместе с TypeScript.
type TranslationKey =
| 'welcome'
| 'profile'
| 'logout'
export function useTranslation() {
const t = (key: TranslationKey) => {
return i18next.t(key)
}
return {
t
}
}
В больших проектах типы генерируют автоматически.
Пример:
import common from './locales/en/common.json'
type TranslationSchema = typeof common
Можно создать полноценный Vue plugin.
export default {
install(app) {
app.config.globalProperties.$t = i18next.t.bind(i18next)
app.provide('i18next', i18next)
}
}
Подключение:
app.use(i18nPlugin)
<script setup>Composition API особенно удобен в script setup.
<script setup>
import { useTranslation } from '@/i18n/composables/useTranslation'
const { t } = useTranslation()
</script>
<template>
<h1>{{ t('welcome') }}</h1>
</template>
При работе с локализацией важно учитывать:
t;t() вне
реактивного контекстаПлохо:
const title = t('profile')
После смены языка значение не обновится.
Лучше:
const title = computed(() => t('profile'))
Плохо:
import i18next from '@/i18n'
в каждом компоненте.
Лучше использовать composable.
Плохо:
common.json -> 5000 строк
Лучше:
auth.json
dashboard.json
profile.json
Пример теста:
import { useTranslation } from '@/i18n/composables/useTranslation'
describe('translation composable', () => {
it('returns translated string', () => {
const { t } = useTranslation()
expect(t('welcome')).toBe('Welcome')
})
})
Крупные приложения часто разделяют переводы по доменам:
modules/
├── auth/
│ ├── locales/
│ └── components/
├── dashboard/
│ ├── locales/
│ └── composables/
Каждый модуль содержит собственные namespace и composable.
const component = computed(() => {
if (currentLanguage.value === 'ru') {
return RussianComponent
}
return EnglishComponent
})
watchEffect(() => {
document.title = t('pageTitle')
})
Через дополнительный плагин:
npm install i18next-icu
Подключение:
import ICU from 'i18next-icu'
i18next.use(ICU)
Пример:
{
"items": "{count, plural, one {# item} other {# items}}"
}
Подписка:
i18next.on('initialized', () => {
console.log('i18next initialized')
})
Смена языка:
i18next.on('languageChanged', (lng) => {
console.log(lng)
})
Ошибка загрузки:
i18next.on('failedLoading', (lng, ns, msg) => {
console.error(msg)
})
Иногда создают полноценный реактивный слой:
import { reactive } from 'vue'
export const i18nState = reactive({
language: i18next.language
})
i18next.on('languageChanged', (lng) => {
i18nState.language = lng
})
watchEffect(() => {
console.log(t('welcome'))
})
watchEffect автоматически отслеживает реактивные
зависимости.
Vite отлично работает с динамическим импортом переводов.
const messages = await import(`./locales/${lng}/common.json`)
Во время разработки переводы можно обновлять без перезапуска приложения:
i18next.reloadResources()
Основные различия:
| Vue I18n | i18next |
|---|---|
| Тесная интеграция с Vue | Независимая библиотека |
| Vue-ориентированный API | Универсальный API |
| Простая настройка | Более гибкая архитектура |
| Меньше middleware | Большая экосистема |
Composition API раскрывает преимущества i18next в
следующих сценариях: