Директива v-t

При интеграции i18next и vue-i18next во Vue-приложение часто возникает необходимость быстро подставлять переводы прямо в шаблонах компонентов. Для этой задачи используется директива v-t.

Директива v-t позволяет:

  • выводить локализованные строки без вызова методов;
  • упрощать шаблоны;
  • уменьшать количество логики внутри компонентов;
  • автоматически обновлять текст при смене языка.

Базовый пример:

<p v-t="'welcome'"></p>

Если для текущего языка существует ключ:

{
  "welcome": "Добро пожаловать"
}

то итоговый HTML будет выглядеть так:

<p>Добро пожаловать</p>

Подключение директивы

После установки vue-i18next директива обычно регистрируется автоматически вместе с экземпляром i18next.

Пример конфигурации:

import Vue from 'vue'
import i18next from 'i18next'
import VueI18Next from '@panter/vue-i18next'

Vue.use(VueI18Next)

i18next.init({
  lng: 'ru',
  resources: {
    ru: {
      translation: {
        hello: 'Привет'
      }
    }
  }
})

const i18n = new VueI18Next(i18next)

new Vue({
  i18n,
  render: h => h(App)
}).$mount('#app')

После этого директива становится доступной во всех компонентах.


Базовое использование

Простая строка

<span v-t="'hello'"></span>

Результат:

<span>Привет</span>

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

<button v-t="'buttons.save'"></button>

JSON:

{
  "buttons": {
    "save": "Сохранить"
  }
}

Использование в заголовках

<h1 v-t="'page.title'"></h1>

Отличие v-t от $t

В i18next для Vue существует два основных подхода:

Через метод $t

<p>{{ $t('hello') }}</p>

Через директиву v-t

<p v-t="'hello'"></p>

Оба варианта дают одинаковый результат, однако между ними есть важные различия.


Когда удобнее использовать v-t

Минимизация шаблонного кода

<li v-t="'menu.profile'"></li>

вместо:

<li>{{ $t('menu.profile') }}</li>

Работа с DOM-элементами

Директива напрямую изменяет содержимое элемента.

<div v-t="'loading'"></div>

Большое количество переводов

В длинных шаблонах директива делает структуру более компактной.

<ul>
  <li v-t="'menu.home'"></li>
  <li v-t="'menu.about'"></li>
  <li v-t="'menu.contacts'"></li>
</ul>

Передача параметров

v-t поддерживает interpolation — подстановку значений внутрь строки.

Перевод

{
  "greeting": "Привет, {{name}}"
}

Шаблон

<p v-t="{ path: 'greeting', args: { name: userName } }"></p>

Результат

<p>Привет, Алексей</p>

Структура объекта директивы

Директива может принимать объект с параметрами.

Полная форма

<p
  v-t="{
    path: 'message',
    args: { count: 5 },
    locale: 'ru'
  }"
></p>

Свойство path

Определяет ключ перевода.

<p v-t="{ path: 'auth.login' }"></p>

Свойство args

Передаёт параметры интерполяции.

{
  "cart": {
    "items": "Товаров: {{count}}"
  }
}
<p v-t="{ path: 'cart.items', args: { count: total } }"></p>

Свойство locale

Позволяет явно указать язык.

<p
  v-t="{
    path: 'hello',
    locale: 'en'
  }"
></p>

Даже если текущий язык приложения — русский, текст будет выведен на английском.


Работа с pluralization

i18next поддерживает множественные формы.

Переводы

{
  "item_one": "{{count}} товар",
  "item_few": "{{count}} товара",
  "item_many": "{{count}} товаров"
}

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

<p
  v-t="{
    path: 'item',
    args: { count: itemsCount }
  }"
></p>

Использование HTML внутри переводов

Иногда переводы содержат HTML-разметку.

Пример перевода

{
  "warning": "Нажмите <strong>сюда</strong>"
}

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

<div v-html="$t('warning')"></div>

Для v-t подобный подход не подходит напрямую, потому что директива устанавливает текстовое содержимое.

Следовательно:

<div v-t="'warning'"></div>

выведет:

<div>Нажмите <strong>сюда</strong></div>

без интерпретации HTML.


Использование вместе с атрибутами

v-t изменяет только текст внутри элемента. Атрибуты она не локализует.

Неправильно:

<input v-t="'placeholder.name'">

Для атрибутов используется привязка:

<input :placeholder="$t('placeholder.name')">

Локализация title

<button :title="$t('tooltips.save')">
  Save
</button>

Локализация alt

<img
  src="logo.png"
  :alt="$t('logoAlt')"
>

Комбинирование v-t и Vue-выражений

Допустимо использовать директиву вместе с другими возможностями Vue.

<p
  v-if="isAuthorized"
  v-t="'profile.authorized'"
></p>

Использование в циклах

<li
  v-for="item in menu"
  :key="item.id"
  v-t="item.translationKey"
></li>

Динамические ключи

Ключ перевода может вычисляться динамически.

<p v-t="currentKey"></p>
data() {
  return {
    currentKey: 'messages.success'
  }
}

Реактивность

При изменении языка директива автоматически обновляет содержимое элементов.

i18next.changeLanguage('en')

После вызова все элементы с v-t будут перерисованы.


Использование fallback-переводов

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

<p v-t="'unknown.key'"></p>

i18next может:

  • вернуть сам ключ;
  • использовать fallback language;
  • использовать значение по умолчанию.

Настройка fallback language

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

Пространства имён (namespaces)

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

Конфигурация

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

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

<p v-t="'auth:login.title'"></p>

Lazy loading переводов

При больших объёмах локализации переводы загружаются динамически.

i18next.loadNamespaces('dashboard')

После загрузки namespace директива начнёт использовать новые переводы автоматически.


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

v-t особенно популярна в проектах на Vue 2, где директивы активно применяются для работы с DOM.

Пример:

<template>
  <section>
    <h1 v-t="'home.title'"></h1>
    <p v-t="'home.description'"></p>
  </section>
</template>

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

В Vue 3 чаще используется Composition API и функция t, однако v-t по-прежнему может применяться в совместимых библиотеках.


Производительность

Директива может быть немного эффективнее интерполяции через {{ }}, поскольку обновляет DOM напрямую.

Особенно заметно это при:

  • больших таблицах;
  • длинных списках;
  • интерфейсах с сотнями переводимых элементов.

Ограничения директивы v-t

Невозможность рендеринга HTML

<div v-t="'html.content'"></div>

HTML будет экранирован.


Не подходит для атрибутов

Для placeholder, title, aria-label и других атрибутов требуется $t.


Меньшая гибкость

Иногда интерполяция удобнее:

<p>{{ $t(dynamicKey) }}</p>

Использование вместе с computed-свойствами

computed: {
  statusKey() {
    return `statuses.${this.status}`
  }
}
<p v-t="statusKey"></p>

Организация переводов

Плохая структура

{
  "title1": "Главная",
  "title2": "Контакты"
}

Хорошая структура

{
  "pages": {
    "home": {
      "title": "Главная"
    },
    "contacts": {
      "title": "Контакты"
    }
  }
}

Использование констант для ключей

export const I18N_KEYS = {
  SAVE_BUTTON: 'buttons.save',
  DELETE_BUTTON: 'buttons.delete'
}
<button v-t="I18N_KEYS.SAVE_BUTTON"></button>

Ошибки при использовании v-t

Отсутствующий ключ

<p v-t="'missing.key'"></p>

Результат:

<p>missing.key</p>

Неверная структура объекта

Неправильно:

<p v-t="{ key: 'hello' }"></p>

Правильно:

<p v-t="{ path: 'hello' }"></p>

Отсутствие namespace

<p v-t="'login.title'"></p>

если перевод находится в auth, ключ не будет найден.

Правильно:

<p v-t="'auth:login.title'"></p>

Использование в крупных приложениях

В enterprise-проектах директива часто применяется:

  • в меню;
  • в таблицах;
  • в формах;
  • в модальных окнах;
  • в административных панелях;
  • в системах аналитики;
  • в CRM-интерфейсах.

Практический пример компонента

<template>
  <div class="profile">
    <h1 v-t="'profile.title'"></h1>

    <p
      v-t="{
        path: 'profile.messages',
        args: { count: messagesCount }
      }"
    ></p>

    <button v-t="'buttons.logout'"></button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      messagesCount: 12
    }
  }
}
</script>

Сравнение подходов

Подход Преимущества Недостатки
v-t Компактность, прямое обновление DOM Не работает с HTML и атрибутами
$t() Гибкость, поддержка атрибутов Более многословный синтаксис

Рекомендации по использованию

Использовать v-t, когда:

  • нужен простой текстовый перевод;
  • шаблон перегружен интерполяциями;
  • требуется компактный синтаксис;
  • переводится содержимое DOM-элемента.

Использовать $t, когда:

  • нужен перевод атрибутов;
  • используется HTML;
  • требуется сложная логика;
  • перевод участвует в вычислениях.

Часто используемые шаблоны

Кнопки

<button v-t="'buttons.submit'"></button>

Пункты меню

<li v-t="'menu.settings'"></li>

Заголовки страниц

<h2 v-t="'pages.dashboard.title'"></h2>

Сообщения статуса

<span v-t="'status.loading'"></span>

Совместимость с SSR

При серверном рендеринге директива работает корректно, если:

  • язык определён до рендера;
  • namespace загружены заранее;
  • экземпляр i18next синхронизирован между сервером и клиентом.

Интеграция с TypeScript

Для повышения надёжности можно типизировать ключи переводов.

type TranslationKeys =
  | 'buttons.save'
  | 'buttons.cancel'
  | 'profile.title'
<button v-t="translationKey"></button>

Архитектурные рекомендации

Разделение переводов по модулям

locales/
├── ru/
│   ├── auth.json
│   ├── dashboard.json
│   └── common.json
└── en/
    ├── auth.json
    ├── dashboard.json
    └── common.json

Использование единых соглашений

Хороший стиль:

{
  "buttons": {
    "save": "Сохранить"
  }
}

Плохой стиль:

{
  "SaveButtonText": "Сохранить"
}

Отладка переводов

Для диагностики удобно включать режим debug.

i18next.init({
  debug: true
})

i18next начнёт выводить в консоль:

  • отсутствующие ключи;
  • загруженные namespace;
  • смену языка;
  • ошибки загрузки ресурсов.

Использование fallback-значений

<p>
  {{ $t('unknown.key', 'Текст по умолчанию') }}
</p>

Для v-t подобный fallback обычно настраивается глобально через конфигурацию i18next.


Совместное использование с v-if

<p
  v-if="hasError"
  v-t="'errors.network'"
></p>

Совместное использование с v-show

<p
  v-show="isLoading"
  v-t="'loading'"
></p>

Совместное использование с компонентами

<BaseButton v-t="'buttons.ok'" />

Корректная работа зависит от того, как компонент обрабатывает содержимое слота и DOM-элементы.


Внутренний принцип работы

Во время монтирования директива:

  1. получает ключ перевода;
  2. обращается к экземпляру i18next;
  3. получает локализованную строку;
  4. записывает её в textContent элемента;
  5. подписывается на изменение языка.

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