Skip to content

@comvi/vue API Reference

@comvi/vue provides Vue 3 composables, a <T> component for safe rich-text interpolation, and a Vue plugin for global access. It builds on top of @comvi/core and re-exports all core APIs.

Creates a Vue i18n instance. Returns a VueI18n instance that is both a standard i18n object and a Vue plugin compatible with app.use().

import { createI18n } from '@comvi/vue';
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
});

Accepts all core I18nOptions plus:

OptionTypeDefaultDescription
ssrLanguagestringExplicit hydration locale for SSR (prevents mismatch)

The returned instance supports chaining:

const i18n = createI18n({ locale: 'en' })
.use(FetchLoader({ cdnUrl: '...' }))
.use(LocaleDetector({ order: ['cookie', 'navigator'] }));

Install the i18n instance as a Vue plugin with app.use(i18n) to provide useI18n() composable and $t, $i18n globals.

src/main.ts
import { createApp } from 'vue';
import { i18n } from './i18n';
import App from './App.vue';
const app = createApp(App);
app.use(i18n);
app.mount('#app');

app.use(i18n) auto-calls init() if !isInitialized && !isInitializing.

After installation, every component has access to $t() and $i18n (Options API) and useI18n() (Composition API).

The primary composable for translating strings and reading i18n state. Reactive state (locale, isLoading, isInitializing, dir, translationCache) is returned as Vue refs/computeds; the rest are methods bound to the i18n instance.

<script setup lang="ts">
import { useI18n } from '@comvi/vue';
const { t, locale, setLocale, isLoading, isInitializing } = useI18n();
</script>
<template>
<div v-if="isLoading">Loading translations...</div>
<div v-else>
<h1>{{ t('hello.world') }}</h1>
<p>{{ t('greeting', { name: 'Alice' }) }}</p>
</div>
</template>
useI18n(ns?: string)

Pass an optional namespace string to load and scope all t() calls to that namespace.

interface UseI18nReturn {
t: TypedTranslationFunction; // returns string
tRaw: RawTranslationFunction; // returns TranslationResult
locale: Ref<string>;
setLocale(locale: string): Promise<void>;
translationCache: Readonly<Ref<Readonly<ReadonlyMap<string, FlattenedTranslations>>>>;
isLoading: Readonly<Ref<boolean>>;
isInitializing: Readonly<Ref<boolean>>;
dir: ComputedRef<"ltr" | "rtl">;
addTranslations(translations: Record<string, Record<string, TranslationValue>>): void;
addActiveNamespace(namespace: string): Promise<void>;
setFallbackLocale(locales: string | string[]): void;
onMissingKey(
cb: (key: string, locale: string, namespace: string) => TranslationResult | void,
): () => void;
onLoadError(cb: (locale: string, namespace: string, error: Error) => void): () => void;
clearTranslations(locale?: string, namespace?: string): void;
reloadTranslations(locale?: string, namespace?: string): Promise<void>;
hasLocale(locale: string, namespace?: string): boolean;
hasTranslation(key: string, locale?: string, namespace?: string, checkFallbacks?: boolean): boolean;
getLoadedLocales(): string[];
getActiveNamespaces(): string[];
getDefaultNamespace(): string;
on<E extends I18nEvent>(event: E, cb: (payload: I18nEventData[E]) => void): () => void;
reportError(error: unknown, context?: ErrorReportContext): 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;
formatRelativeTime(
value: number,
unit: Intl.RelativeTimeFormatUnit,
options?: Intl.RelativeTimeFormatOptions,
): string;
destroy(): void;
}

t() returns a plain string. tRaw() returns TranslationResult (string or array of strings/VirtualNodes) for advanced renderers.

Key properties:

  • locale is a reactive ref; setting .value triggers language switch (fire-and-forget, use setLocale() to await)
  • setLocale() is asynchronous and resolves after translations are loaded
  • isLoading, isInitializing, and translationCache are readonly refs
  • dir is a computed ref
  • All pass-through methods mirror the i18n instance

You can set locale.value directly or call setLocale(). Both load translations first and then switch — the only difference is that setLocale() returns a promise you can await:

<script setup lang="ts">
import { useI18n } from '@comvi/vue';
const { locale, setLocale } = useI18n();
// Option 1: Direct assignment (fire-and-forget)
function switchDirect(lang: string) {
locale.value = lang;
}
// Option 2: Async — same behavior, but you can await the result
async function switchAsync(lang: string) {
await setLocale(lang);
// Translations are guaranteed to be loaded here
}
</script>

Pass a namespace to scope all t() calls within a component:

<script setup lang="ts">
import { useI18n } from '@comvi/vue';
const { t } = useI18n('dashboard');
</script>
<template>
<!-- Resolves keys from the 'dashboard' namespace -->
<h1>{{ t('page.title') }}</h1>
</template>

The <T> component renders translations that contain rich content (HTML tags, Vue components, or links) safely, without v-html. It uses named slots for tag interpolation.

import { T } from '@comvi/vue';
interface TProps {
i18nKey: string;
params?: Record<string, unknown>;
ns?: string;
locale?: string;
fallback?: string;
components?: {
[tag: string]: string | Component | { component: Component; props?: object };
}
}
  • i18nKey — required
  • components — map of tag names to Vue components/HTML tags. The components prop takes precedence over slots when both define the same tag.
<template>
<!-- Translation: "Hello, {name}!" -->
<T i18n-key="greeting" :params="{ name: 'Alice' }" />
</template>

Use named slots to replace tagged sections in translation values with Vue components or elements:

<template>
<!-- Translation: "Read our <link>terms of service</link>" -->
<T i18n-key="legal.tos">
<template #link="{ children }">
<RouterLink to="/terms">{{ children }}</RouterLink>
</template>
</T>
</template>
<template>
<!-- Translation: "This is <bold>important</bold> and <italic>urgent</italic>" -->
<T i18n-key="notice">
<template #bold="{ children }">
<strong>{{ children }}</strong>
</template>
<template #italic="{ children }">
<em>{{ children }}</em>
</template>
</T>
</template>

As an alternative to slots, use the components prop. This 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:

<template>
<T
i18n-key="legal.tos"
:components="{
link: { component: 'a', props: { href: '/terms' } },
bold: 'strong',
}"
/>
</template>

The Vue injection key used internally by app.use(i18n) to provide the i18n instance through Vue’s dependency injection system. You only need this if you are manually providing the instance with provide/inject instead of using the plugin.

import { I18N_INJECTION_KEY } from '@comvi/vue';
import { provide, inject } from 'vue';
// Provide manually (rare — use app.use(i18n) instead)
provide(I18N_INJECTION_KEY, i18nInstance);
// Inject manually
const i18n = inject(I18N_INJECTION_KEY);

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 raw TranslationResult
  • $i18n — the i18n instance
<template>
<h1>{{ $t('hello.world') }}</h1>
<p>Language: {{ $i18n.locale }}</p>
</template>

@comvi/vue re-exports everything from @comvi/core for convenience. You do not need to install @comvi/core separately:

  • createI18n (Vue-enhanced version)
  • All event types
  • All plugin types (I18nPlugin, I18nPluginFactory)
  • All type utilities (I18nInstance, I18nEvent, I18nEventData, etc.)

See the @comvi/core API Reference for the full list.