Getting Started
This page gets a Vue 3 or Nuxt app rendering a translation string that contains real components. For the reasoning behind the library, see Motivation.
Installation
npm install @tanzlate/vueyarn add @tanzlate/vuepnpm add @tanzlate/vue@tanzlate/vue pulls in @tanzlate/vanilla and @tanzlate/core itself — you don't install those separately. vue is a peer dependency (^3.5.0), so your app's copy is the one that gets used.
Without a framework, install @tanzlate/vanilla on its own.
1. Translation resources
Keys are nested namespace → context → key. The namespace is set once in the config, the context is what you pass to useI18n():
// locales/en.json
{
"app_namespace": {
"home": {
"welcome": "Welcome to tanzlate with <LangSwitcher>switch language</LangSwitcher>",
"description": "A simple <b>example</b> of tanzlate in a Nuxt app"
}
}
}2. Wrap the app in I18nProvider
<!-- app.vue -->
<template>
<Suspense>
<I18nProvider :i18next-config="i18nConfig">
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</I18nProvider>
</Suspense>
</template>
<script setup lang="ts">
import type { Createi18nConfigParams } from '@tanzlate/vanilla';
import { I18nProvider } from '@tanzlate/vue';
import DE_TRANSLATION from './i18n/locales/de.json';
import EN_TRANSLATION from './i18n/locales/en.json';
const i18nConfig: Createi18nConfigParams = {
namespace: 'app_namespace',
lng: 'en',
fallbackLng: 'en',
supportedLanguages: ['en', 'de'],
resources: {
en: EN_TRANSLATION,
de: DE_TRANSLATION,
},
};
</script>I18nProvider needs an outer <Suspense> here
Given i18next-config, it awaits the i18next init, so the parent has to provide a <Suspense> boundary. (If you build the context yourself and pass i18n-context instead, it stays synchronous and needs no boundary.) It also renders a <div class="i18n-provider"> around your children, which can matter for layout.
3. Translate
useI18n(context) returns tanz, the translation function for that context.
<template>
<!-- plain string, no tags -->
<h1>{{ tanz('welcome') }}</h1>
<!-- tags in the string become real components -->
<Tanzlate :t-z="tanz" i18n-key="description" />
</template>
<script setup lang="ts">
import { Tanzlate, useI18n } from '@tanzlate/vue';
const { tanz } = useI18n('home');
</script>tanz() vs <Tanzlate>
tanz() returns a plain string. If the value contains tags, {{ tanz('description') }} prints them literally as text — that is correct behavior, not a bug. Any key whose value contains tags has to go through <Tanzlate>, which parses the string and builds the nodes.
4. Interpolate components
Register a PascalCase component once, then pass only its props per usage:
import { registerComponent } from '@tanzlate/vue';
import ColoredLabel from '~/components/ColoredLabel.vue';
registerComponent('ColoredLabel', ColoredLabel);<Tanzlate
:t-z="tanz"
i18n-key="componentInterpolationExample"
:components="{
ColoredLabel: { color: '#3178c6', label: 'Blue Label' },
'ColoredLabel-1': { color: labelColor, label: 'Red Label' },
a: { href: 'https://tanzlate.dev' },
}"
/>Three things are happening there, and they are the heart of the library:
registerComponentresolves the tag — once per app, covering every string that uses it.:componentssupplies props only, per usage. It never resolves a component.- The
-Nsuffix lets one component appear several times in a string with different props.
Lowercase HTML tags (<a>, <b>, <strong>) need no registration.
Full details in Component Interpolation and Component Registry.
5. Switch language
<script setup lang="ts">
import { useI18n } from '@tanzlate/vue';
const { lang, changeLanguage } = useI18n('home');
</script>
<template>
<button @click="changeLanguage(lang === 'en' ? 'de' : 'en')">
{{ lang }}
</button>
</template>lang is a Ref<string>, and translations re-render on change.
No global $t
tanzlate does not install a global $t or $i18next. Every component asks for the context it needs via useI18n(), which keeps keys traceable to the component that uses them.