Skip to content
fluenti

v-t Directive

The v-t directive is the primary API for translating Vue template content. Unlike runtime directives, v-t is a compile-time nodeTransform — the Vite plugin rewrites your template during Vue’s SFC compilation, so v-t never reaches the browser. The result is zero runtime overhead for translation lookups.

Add v-t to any element to translate its text content:

<h1 v-t>Welcome to Fluenti</h1>

At build time this is transformed into:

<h1>{{ $t({ id: 'a1b2c3', message: 'Welcome to Fluenti' }) }}</h1>

The message ID is a deterministic hash of the source text. Translators see the original English string in the catalog.

Vue template expressions inside v-t elements are extracted as ICU placeholders:

<p v-t>Hello {{ name }}, welcome back!</p>
<p v-t>You have {{ count }} items in your cart.</p>

The transform extracts {{ name }} as {name} and {{ count }} as {count} in the ICU message. At runtime, the values are passed to $t() automatically.

Property access uses the last segment as the placeholder name:

<p v-t>Welcome, {{ user.profile.displayName }}!</p>
<!-- ICU message: "Welcome, {displayName}!" -->

Complex expressions (function calls, arithmetic) receive positional placeholders:

<p v-t>Total: {{ formatPrice(total) }}</p>
<!-- ICU message: "Total: {0}" -->

Use the argument syntax to set a custom message ID instead of the auto-generated hash:

<p v-t:nav.home>Home</p>
<p v-t:app.checkout.title>Checkout</p>

Output: <p>{{ $t({ id: 'nav.home', message: 'Home' }) }}</p>

This is useful when you want stable, human-readable IDs in your catalogs.

Use modifiers to translate element attributes instead of text content:

<img v-t.alt src="/hero.jpg" alt="Welcome banner image" />
<input v-t.placeholder placeholder="Search products..." />
<button v-t.title title="Save your changes">💾</button>

Each modifier names the attribute to translate. The static attribute value becomes the source message, and the transform replaces it with a :attr="$t(...)" binding.

You can combine text translation and attribute translation on the same element:

<a v-t v-t.title href="/terms" title="Read our terms">Terms of Service</a>

This translates both the text content (Terms of Service) and the title attribute (Read our terms) independently.

Use the .plural modifier with a bound count value. Separate plural forms with pipes:

<p v-t.plural="count">no items | one item | {{ count }} items</p>

The transform generates an ICU plural message:

{count, plural, =0 {no items} one {one item} other {{count} items}}

The first form maps to =0 (exact zero), the second to one, and the third to other. For languages with more plural categories, translators add the appropriate forms in the catalog.

Child HTML elements are preserved and indexed as placeholders:

<p v-t>Read the <a href="/terms">terms</a> and <strong>conditions</strong></p>

The extracted message uses indexed tags: Read the <0>terms</0> and <1>conditions</1>. At runtime, the translated message is rendered with the original elements restored via v-html and the $vtRich() helper.

Both v-t and t`…` are compile-time APIs, but they serve different contexts:

v-tt`…`
Where<template> block<script setup>
OutputInline $t() call in templatePlain string value
Rich textAutomatic (child elements preserved)Use <Trans> instead
Attributesv-t.alt, v-t.placeholder, etc.Bind via :alt="t\…`“`
ReactivityTemplate re-evaluation handles itUse computed() for reactive script values

Use v-t when the translation is directly in the template. Use t`…` when you need the translated string in <script setup> — for example, in a computed, a watch, or a variable passed to a composable.

The v-t directive is processed by transformVtDirectives in the Vite plugin’s pre phase, before Vue’s own SFC compiler runs. It parses the SFC template AST, collects all v-t usages, and replaces them with $t() calls using offset-based string splicing. Because the transform runs at build time, there is no directive registration, no runtime resolution, and no performance penalty.

For the full compilation pipeline, see How It Works.