Vue 3
Comvi i18n integrates seamlessly with Vue 3 via the @comvi/vue plugin. This guide covers installation, setup, and common patterns for Vue applications.
Installation
Section titled “Installation”npm install @comvi/vueBootstrap
Section titled “Bootstrap”-
Create an i18n instance in a separate module:
src/i18n.ts import { createI18n } from "@comvi/vue";export const i18n = createI18n({locale: "en",fallbackLocale: "en",});// Register translations (from API, local files, or both)i18n.registerLoader({en: () => import("./locales/en.json"),de: () => import("./locales/de.json"),"en:admin": () => import("./locales/admin/en.json"),}); -
Install the plugin in your Vue app:
src/main.ts import { createApp } from "vue";import App from "./App.vue";import { i18n } from "./i18n";const app = createApp(App);app.use(i18n);app.mount("#app");The
.use(i18n)call auto-initializes the library if it has not already been initialized. -
Use the composable in components:
src/App.vue <script setup>import { useI18n } from "@comvi/vue";const { t, locale, setLocale } = useI18n();</script><template><div><h1>{{ t("home.title") }}</h1><p>{{ locale }}</p></div></template>
useI18n Composable
Section titled “useI18n Composable”useI18n() returns a reactive object with translation methods and state. Commonly used values:
// Partial — see the API reference for the complete shapeinterface UseI18nReturn { t: TypedTranslationFunction; // returns string tRaw: RawTranslationFunction; // returns TranslationResult locale: Ref<string>; setLocale(locale: string): Promise<void>; isLoading: Readonly<Ref<boolean>>; isInitializing: Readonly<Ref<boolean>>; dir: ComputedRef<"ltr" | "rtl">;
onMissingKey( cb: (key: string, locale: string, namespace: string) => TranslationResult | void, ): () => void; formatNumber(value: number, options?: Intl.NumberFormatOptions): string; formatDate(value: Date | number, options?: Intl.DateTimeFormatOptions): string; formatCurrency(value: number, currency: string, options?: Intl.NumberFormatOptions): string;}See the Vue API reference for the full return type.
Access translations by passing a namespace:
<script setup>import { useI18n } from "@comvi/vue";
const { t: tAdmin } = useI18n("admin");const { t: tDefault } = useI18n(); // uses default namespace</script>
<template> <p>{{ tDefault("common.welcome") }}</p> <p>{{ tAdmin("dashboard.title") }}</p></template>Translation Function
Section titled “Translation Function”The t() function always returns a plain string. Use <T> for rich text rendering, or tRaw() if you need the structured TranslationResult directly:
<script setup>import { useI18n } from "@comvi/vue";
const { t, tRaw } = useI18n();
const plain = t("rich.message"); // stringconst structured = tRaw("rich.message"); // string | Array<string | VirtualNode></script>
<template> <!-- Simple key --> <h1>{{ t("home.title") }}</h1>
<!-- With parameters --> <p>{{ t("home.greeting", { name: "Alice" }) }}</p>
<!-- Fallback if key missing --> <p>{{ t("admin.section", { fallback: "Not found" }) }}</p>
<!-- Specific locale (override current) --> <p>{{ t("home.title", { locale: "de" }) }}</p>
<!-- Specific namespace --> <p>{{ t("dashboard.title", { ns: "admin" }) }}</p></template>Reactivity: Calling t() inside a Vue template, render function, or computed value automatically tracks the current locale and translation cache, so the UI re-renders when either changes.
T Component
Section titled “T Component”The <T> component renders rich text (tags, components) from translations:
<!-- With slot for custom component --><T i18n-key="rich_text.link_text"> <template #link="{ children }"> <a href="https://example.com" class="text-blue-600"> {{ children }} </a> </template></T>
<!-- With parameters --><T i18n-key="welcome.message" :params="{ name: 'Alice', count: 5 }" ns="admin"/>T Props
Section titled “T Props”interface TProps { i18nKey: string; // Translation key (required) params?: Record<string, unknown>; // Parameters for interpolation ns?: string; // Namespace override locale?: string; // Locale override fallback?: string; // Fallback if key missing components?: { [tag: string]: string | Component | { component: Component; props?: object; }; };}Slots vs Components Prop
Section titled “Slots vs Components Prop”The components prop takes precedence over slots when both define the same tag. Slots are convenient for inline Vue template syntax. The components prop is useful when you want to reuse the same handlers across many <T> calls or configure handlers at runtime. String handlers, such as bold: "strong", render as plain HTML tags without instantiating a Vue component.
<!-- Using slots for simple text children --><T i18n-key="legal.tos"> <template #tos="{ children }"> <a href="/terms">{{ children }}</a> </template></T>
<!-- Using components prop for reusable or runtime-configured handlers --><T i18n-key="legal.tos" :components="{ tos: { component: 'a', props: { href: '/terms', class: 'text-blue-600' } } }"/>Locale Switching
Section titled “Locale Switching”Change the locale reactively. setLocale() is asynchronous and resolves after translations are loaded:
<script setup>import { useI18n } from "@comvi/vue";
const { locale, setLocale } = useI18n();
const switchLocale = async (newLocale) => { await setLocale(newLocale); // Translations are now loaded};
// or fire-and-forget (not recommended for UX)const quickSwitch = (newLocale) => { locale.value = newLocale;};</script>
<template> <div> <p>Current: {{ locale }}</p> <button @click="switchLocale('de')">Deutsch</button> <button @click="switchLocale('en')">English</button> </div></template>Reactivity Patterns
Section titled “Reactivity Patterns”Vue’s Composition API makes translations reactive:
<script setup>import { useI18n } from "@comvi/vue";import { watch, computed, ref } from "vue";
const { locale, t } = useI18n();
// Watch locale changeswatch(locale, (newLocale) => { document.documentElement.lang = newLocale; document.documentElement.dir = newLocale === "ar" ? "rtl" : "ltr";});
// Computed keys based on stateconst count = ref(5);const itemsText = computed(() => { return t("items.count", { count: count.value });});</script>
<template> <p>{{ itemsText }}</p></template>Server-Side Rendering (SSR)
Section titled “Server-Side Rendering (SSR)”Comvi Vue integration supports SSR hydration. For most Vue SSR apps, prefer the Nuxt integration because it handles per-request i18n instances, hydration locale, and routing conventions for you.
See the Nuxt guide and the SSR guide for server-rendered applications.
Options API
Section titled “Options API”When the plugin is installed, two global properties are available in all components:
$t(key, params?)— translate a key to a string$tRaw(key, params?)— translate a key to a rawTranslationResult$i18n— the i18n instance
<template> <h1>{{ $t("hello.world") }}</h1> <p>Language: {{ $i18n.locale }}</p></template>Common Patterns
Section titled “Common Patterns”Loading & Error States
Section titled “Loading & Error States”<script setup>import { useI18n } from "@comvi/vue";
const { t, isLoading, isInitializing } = useI18n();</script>
<template> <div v-if="isInitializing" class="spinner">Loading translations...</div> <div v-else-if="isLoading" class="spinner">{{ t("common.loading") }}</div> <div v-else>Content loaded</div></template>Missing Key Handling
Section titled “Missing Key Handling”const { onMissingKey } = useI18n();
onMissingKey((key, locale, namespace) => { console.warn(`Missing translation: ${namespace}:${key} (${locale})`); return `[${key}]`; // Return fallback});Plugins & Loaders
Section titled “Plugins & Loaders”import { createI18n } from "@comvi/vue";import { FetchLoader } from "@comvi/plugin-fetch-loader";import { LocaleDetector } from "@comvi/plugin-locale-detector";
const i18n = createI18n({ locale: "en", fallbackLocale: "en",}) .use( FetchLoader({ cdnUrl: "https://cdn.example.com/translations/", }) ) .use( LocaleDetector({ supportedLocales: ["en", "de", "fr"], order: ["localStorage", "navigator"], caches: ["localStorage"], }) );
i18n.registerLoader({ en: () => import("./locales/en.json"), de: () => import("./locales/de.json"),});