@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.
createI18n(options)
Section titled “createI18n(options)”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',});Options
Section titled “Options”Accepts all core I18nOptions plus:
| Option | Type | Default | Description |
|---|---|---|---|
ssrLocale | string | — | Explicit hydration locale for SSR (prevents mismatch) |
The returned instance supports chaining:
const i18n = createI18n({ locale: 'en' }) .use(FetchLoader({ cdnUrl: '...' })) .use(LocaleDetector({ order: ['cookie', 'navigator'] }));Plugin Installation
Section titled “Plugin Installation”Install the i18n instance as a Vue plugin with app.use(i18n) to provide useI18n() composable and $t, $i18n globals.
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).
useI18n(ns?)
Section titled “useI18n(ns?)”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>Signature
Section titled “Signature”useI18n(ns?: string)Pass an optional namespace string to load and scope all t() calls to that namespace.
Returned Values
Section titled “Returned Values”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:
localeis a reactive ref; setting.valuetriggers a language switch asynchronously — usesetLocale()toawaititsetLocale()is a hook-level helper that resolves after translations are loaded; it is distinct from the corei18n.setLocaleAsync()method (see Core API)isLoading,isInitializing, andtranslationCacheare readonly refsdiris a computed ref that reflects the text direction of the current locale ("ltr"or"rtl")- All pass-through methods mirror the i18n instance
Checking Translation and Locale Existence
Section titled “Checking Translation and Locale Existence”useI18n() exposes two pairs of existence-check methods. Choose based on whether you need reactivity:
| Method | Returns | Use 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?) | boolean | Event handlers, loops, watchers, or any call site outside a reactive scope |
hasLocaleNow(locale, namespace?) | boolean | Same — 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 changesconst showWelcome = hasTranslation('home.welcome');const deLoaded = hasLocale('de');
// Imperative — safe anywhere; plain boolean, no reactive allocationfunction 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 }): booleanhasLocaleNow(locale: string, namespace?: string): booleanChanging Language
Section titled “Changing Language”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 loadingfunction switchDirect(lang: string) { locale.value = lang;}
// Option 2: Async — same behavior, lets you await completionasync function switchAsync(lang: string) { await setLocale(lang); // Translations are guaranteed to be loaded here}</script>Namespace Scoping
Section titled “Namespace Scoping”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><T> Component
Section titled “<T> Component”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— requiredraw— whentrue, prevents post-processors (such as the in-context editor marker injector) from adding invisible marker characterscomponents— map of tag names to Vue components/HTML tags. Thecomponentsprop takes precedence over slots when both define the same tag.
Basic Usage
Section titled “Basic Usage”<template> <!-- Translation: "Hello, {name}!" --> <T i18nKey="greeting" :params="{ name: 'Alice' }" /></template>Rich Text with Slots
Section titled “Rich Text with Slots”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>Rich Text with Components Prop
Section titled “Rich Text with Components Prop”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>I18N_INJECTION_KEY
Section titled “I18N_INJECTION_KEY”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 manuallyconst i18n = inject(I18N_INJECTION_KEY);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>Re-exports from @comvi/core
Section titled “Re-exports from @comvi/core”@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.