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.

Verified against @comvi/vue@0.3.0.

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
ssrLocalestringExplicit 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, loadedLocales, activeNamespaces, defaultNamespace) 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>;
/** Reactive — allocates a computed(); call inside setup() or an effectScope */
hasLocale(locale: string, namespace?: string): ComputedRef<boolean>;
/** Reactive — allocates a computed(); call inside setup() or an effectScope */
hasTranslation(key: string, opts?: { locale?: string; namespace?: string; checkFallbacks?: boolean }): ComputedRef<boolean>;
/** Imperative (non-reactive) — plain boolean, safe to call in loops, event handlers, or outside a reactive scope */
hasLocaleNow(locale: string, namespace?: string): boolean;
/** Imperative (non-reactive) — plain boolean, safe to call in loops, event handlers, or outside a reactive scope */
hasTranslationNow(key: string, opts?: { locale?: string; namespace?: string; checkFallbacks?: boolean }): boolean;
loadedLocales: ComputedRef<string[]>;
activeNamespaces: ComputedRef<string[]>;
defaultNamespace: ComputedRef<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 a language switch asynchronously — use setLocale() to await it
  • setLocale() is a hook-level helper that resolves after translations are loaded; it is distinct from the core i18n.setLocaleAsync() method (see Core API)
  • isLoading, isInitializing, and translationCache are readonly refs
  • dir is a computed ref that reflects the text direction of the current locale ("ltr" or "rtl")
  • All pass-through methods mirror the i18n instance

useI18n() exposes two pairs of existence-check methods. Choose based on whether you need reactivity:

MethodReturnsUse when
hasTranslation(key, opts?)ComputedRef<boolean>Inside setup() / template — re-evaluates automatically when locale or cache changes
hasLocale(locale, namespace?)ComputedRef<boolean>Inside setup() / template — re-evaluates when the translation cache changes
hasTranslationNow(key, opts?)booleanEvent handlers, loops, watchers, or any call site outside a reactive scope
hasLocaleNow(locale, namespace?)booleanSame — imperative check with no reactive overhead

The reactive versions (hasTranslation, hasLocale) allocate a computed() on each call and must be called inside a component’s setup() or an effectScope so Vue can track and dispose them. Calling them inside a v-for loop or a click handler leaks the computed().

The imperative versions (hasTranslationNow, hasLocaleNow) read the cache directly and return a plain boolean. Use them wherever a reactive context is unavailable or undesirable.

<script setup lang="ts">
import { useI18n } from '@comvi/vue';
const { hasTranslation, hasTranslationNow, hasLocale, hasLocaleNow } = useI18n();
// Reactive — safe in setup(); re-evaluates when cache or locale changes
const showWelcome = hasTranslation('home.welcome');
const deLoaded = hasLocale('de');
// Imperative — safe anywhere; plain boolean, no reactive allocation
function buildMenu(keys: string[]) {
return keys.filter((k) => hasTranslationNow(k));
}
function isLocaleReady(locale: string) {
return hasLocaleNow(locale, 'admin');
}
</script>

Signatures:

hasTranslation(key: string, opts?: { locale?: string; namespace?: string; checkFallbacks?: boolean }): ComputedRef<boolean>
hasLocale(locale: string, namespace?: string): ComputedRef<boolean>
hasTranslationNow(key: string, opts?: { locale?: string; namespace?: string; checkFallbacks?: boolean }): boolean
hasLocaleNow(locale: string, namespace?: string): boolean

You can set locale.value directly or call setLocale(). Both trigger an async locale switch — the difference is whether you await the result:

<script setup lang="ts">
import { useI18n } from '@comvi/vue';
const { locale, setLocale } = useI18n();
// Option 1: Direct assignment — no await, UI updates after translations finish loading
function switchDirect(lang: string) {
locale.value = lang;
}
// Option 2: Async — same behavior, lets you await completion
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;
raw?: boolean;
components?: {
[tag: string]: string | Component | { component: string | Component; props?: Record<string, unknown> };
}
}
  • i18nKey — required
  • raw — when true, prevents post-processors (such as the in-context editor marker injector) from adding invisible marker characters
  • 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 i18nKey="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 i18nKey="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 i18nKey="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
i18nKey="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.