Интеграция библиотеки интернационализации в приложение на Vue 3 обычно строится вокруг связки:
i18nextreactive-обёртки для Vuei18next-vueУстановка зависимостей:
npm install i18next i18next-vue
Дополнительно часто используются:
npm install i18next-http-backend
npm install i18next-browser-languagedetector
Структура проекта:
src/
├── locales/
│ ├── ru/
│ │ └── common.json
│ └── en/
│ └── common.json
├── i18n/
│ └── index.js
├── App.vue
└── main.js
Файл src/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
| Параметр | Назначение |
|---|---|
fallbackLng |
Язык по умолчанию |
debug |
Режим отладки |
ns |
Пространства имён |
defaultNS |
Namespace по умолчанию |
backend.loadPath |
Путь к JSON-файлам |
interpolation |
Настройки подстановки |
Файл main.js:
import { createApp } from 'vue'
import App from './App.vue'
import i18next from './i18n'
import I18NextVue from 'i18next-vue'
const app = createApp(App)
app.use(I18NextVue, { i18next })
app.mount('#app')
После подключения глобально становятся доступны:
$t$i18nextlocales/ru/common.json
{
"title": "Главная страница",
"welcome": "Добро пожаловать",
"description": "Многоязычное приложение Vue 3"
}
locales/en/common.json
{
"title": "Home page",
"welcome": "Welcome",
"description": "Vue 3 multilingual application"
}
Компонент:
<template>
<div>
<h1>{{ $t('title') }}</h1>
<p>{{ $t('welcome') }}</p>
<p>{{ $t('description') }}</p>
</div>
</template>
Метод $t() возвращает перевод по ключу.
Во Vue 3 чаще применяется Composition API.
<script setup>
import { useTranslation } from 'i18next-vue'
const { t } = useTranslation()
</script>
<template>
<div>
<h1>{{ t('title') }}</h1>
</div>
</template>
useTranslation| Свойство | Назначение |
|---|---|
t |
Функция перевода |
i18next |
Экземпляр i18next |
ready |
Состояние загрузки |
<script setup>
import { useTranslation } from 'i18next-vue'
const { i18next } = useTranslation()
const switchToRussian = () => {
i18next.changeLanguage('ru')
}
const switchToEnglish = () => {
i18next.changeLanguage('en')
}
</script>
<template>
<div>
<button @click="switchToRussian">
Русский
</button>
<button @click="switchToEnglish">
English
</button>
</div>
</template>
Плагин i18next-browser-languagedetector определяет язык
на основе:
langПример конфигурации:
i18next
.use(LanguageDetector)
.init({
detection: {
order: [
'querystring',
'cookie',
'localStorage',
'navigator'
],
caches: ['localStorage']
}
})
В крупных приложениях переводы разделяются на модули.
Структура:
locales/
├── en/
│ ├── common.json
│ ├── auth.json
│ └── dashboard.json
└── ru/
├── common.json
├── auth.json
└── dashboard.json
Конфигурация:
i18next.init({
ns: ['common', 'auth', 'dashboard'],
defaultNS: 'common'
})
Использование:
<template>
<div>
{{ $t('login', { ns: 'auth' }) }}
</div>
</template>
В больших приложениях переводы желательно загружать динамически.
Пример backend-конфигурации:
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json'
}
Переводы будут подгружаться только при необходимости.
i18next поддерживает динамические значения.
{
"greeting": "Привет, {{name}}!"
}
<template>
<div>
{{ $t('greeting', { name: 'Алексей' }) }}
</div>
</template>
Результат:
Привет, Алексей!
{
"content": "Нажмите <strong>сюда</strong>"
}
<template>
<div v-html="$t('content')"></div>
</template>
v-htmlИспользование v-html может приводить к XSS-уязвимостям.
Нельзя вставлять непроверенный пользовательский HTML.
i18next поддерживает множественные формы.
{
"item_one": "{{count}} элемент",
"item_few": "{{count}} элемента",
"item_many": "{{count}} элементов"
}
<template>
<div>
{{ $t('item', { count: 1 }) }}
{{ $t('item', { count: 3 }) }}
{{ $t('item', { count: 10 }) }}
</div>
</template>
Контекст используется для разделения вариантов.
{
"friend_male": "Друг",
"friend_female": "Подруга"
}
t('friend', {
context: 'female'
})
i18next поддерживает Intl API.
i18next.init({
interpolation: {
format(value, format, lng) {
if (format === 'currency') {
return new Intl.NumberFormat(lng, {
style: 'currency',
currency: 'USD'
}).format(value)
}
return value
}
}
})
{
"price": "Цена: {{value, currency}}"
}
<template>
<div>
{{ $t('price', { value: 1999 }) }}
</div>
</template>
После вызова:
i18next.changeLanguage('ru')
все компоненты Vue автоматически перерисовываются.
Это достигается через реактивную интеграцию
i18next-vue.
Иногда перевод требуется в обычных JavaScript-модулях.
import i18next from '@/i18n'
const message = i18next.t('welcome')
console.log(message)
/ru/about
/en/about
Пример конфигурации маршрутов:
const routes = [
{
path: '/:lang/about',
component: AboutPage
}
]
Синхронизация языка:
router.beforeEach((to, from, next) => {
const lang = to.params.lang
if (lang) {
i18next.changeLanguage(lang)
}
next()
})
router.afterEach((to) => {
document.title = i18next.t(to.meta.title)
})
Маршрут:
{
path: '/about',
component: AboutPage,
meta: {
title: 'about.title'
}
}
Переводы могут использоваться внутри store.
import { defineStore } from 'pinia'
import i18next from '@/i18n'
export const useUserStore = defineStore('user', {
actions: {
showMessage() {
console.log(i18next.t('welcome'))
}
}
})
При асинхронной загрузке переводов полезен Suspense.
<Suspense>
<template #default>
<AppContent />
</template>
<template #fallback>
Loading...
</template>
</Suspense>
i18next.init({
saveMissing: true,
missingKeyHandler(lng, ns, key) {
console.warn(`Отсутствует перевод: ${key}`)
}
})
fallbackLng: {
'de-CH': ['fr', 'it'],
default: ['en']
}
Если перевод отсутствует на швейцарском немецком, будет использоваться французский или итальянский.
Пример через localStorage:
detection: {
caches: ['localStorage']
}
Дополнительно можно использовать chained backend.
При серверном рендеринге требуется отдельная инициализация i18next на каждый запрос.
Типичная проблема:
Hydration mismatch
Причина — различие языков между сервером и клиентом.
export type TranslationKeys =
| 'title'
| 'welcome'
| 'description'
function translate(key: TranslationKeys) {
return i18next.t(key)
}
Новый формат pluralization:
{
"cart": {
"one": "{{count}} товар",
"few": "{{count}} товара",
"many": "{{count}} товаров"
}
}
Использование:
t('cart', { count: 5 })
Не следует хранить все переводы в одном JSON-файле.
Загрузка только необходимых переводов уменьшает initial bundle.
debug: process.env.NODE_ENV === 'development'
preload: ['en', 'ru']
404 locales/en/common.json
Причины:
loadPathОшибка:
missingKey
Причина:
t('login')
при отсутствии namespace auth.
Проблема:
const title = i18next.t('title')
Переменная не обновится после смены языка.
Правильно:
<template>
{{ t('title') }}
</template>
На сервере отсутствуют:
windownavigatorlocalStorageНеобходимо разделять серверную и клиентскую конфигурацию.
Типичная структура:
src/
├── i18n/
│ ├── index.js
│ ├── config.js
│ ├── detectors/
│ ├── formatters/
│ └── plugins/
├── locales/
│ ├── en/
│ ├── ru/
│ └── de/
└── modules/
├── auth/
├── dashboard/
└── profile/