Skip to content
Comvi

@comvi/vue

View .md

The Vue 3 binding. A VueI18n wraps a @comvi/core instance in Vue reactivity and is the plugin you pass to app.use().

  • Values: createI18n, createI18nFromCore, VueI18n, T, useI18n, useI18nLoader, useI18nPlugins, I18N_INJECTION_KEY, loader, attachLoader, plugins, definePlugin, icuCompiler, devtools.
  • Types: VueI18nOptions, VueI18nCoreOptions, AnyVueI18n, I18nInstance (alias of VueI18n), UseInstanceIdentityError, UseI18nReturn, UseI18nLoaderReturn, UseI18nPluginsReturn, ComponentMap, DevtoolsOptions, I18n, every @comvi/core type.

There is no TProps type; @comvi/core/tags is not re-exported.

function createI18n<D>(options: VueI18nOptions<D>): VueI18n<D, I18n<D>>
type VueI18nOptions<D> = I18nOptions<D> & { ssrLocale?: string }

Builds a core i18n instance and wraps it. VueI18nOptions is every core I18nOptions plus:

Option Type Default Meaning
ssrLocale string none The locale the server rendered with. It paints on the first render when that locale’s catalogs are already available, inline or loaded earlier; with createI18nFromCore on an instance that still has to load them, the switch lands when the load completes
src/i18n.ts
import { createI18n, loader, plugins } from '@comvi/vue';
export const i18n = createI18n({ locale: 'en', ssrLocale: 'en' })
.use(loader({ en: () => import('./locales/en.json') }))
.use(plugins());
function createI18nFromCore<D, C extends WrapperI18nInstance<D>>(core: C, options?: VueI18nCoreOptions): VueI18n<D, C>
interface VueI18nCoreOptions { ssrLocale?: string }

Wraps an instance built elsewhere, keeping its exact type as core. It constructs nothing itself.

src/i18n-from-core.ts
import { createI18n as createCore } from '@comvi/core';
import { createI18nFromCore, loader } from '@comvi/vue';
const core = createCore({ locale: 'en' }).use(loader({ en: () => import('./locales/en.json') }));
export const i18n = createI18nFromCore(core, { ssrLocale: 'en' });
Member Type
core C, the wrapped i18n instance, readonly
locale Ref<string> getter; the setter takes a string
isLoading, isInitializing, isInitialized Readonly<Ref<boolean>>
cacheRevision Readonly<Ref<number>>
dir ComputedRef<'ltr' | 'rtl'>
messagesCache ComputedRef<ReadonlyMap<string, FlattenedMessages>>
defaultParams ComputedRef<DefaultParamsSnapshot<D>>
loadedLocales, activeNamespaces ComputedRef<string[]>
defaultNamespace ComputedRef<string>
t(key, params?): string
tRaw(key, params?): TranslationResult
setLocale(locale: string): Promise<void>
hasTranslation(key: string, opts?: { locale?: string; namespace?: string; checkFallbacks?: boolean }): ComputedRef<boolean>
hasLocale(locale: string, namespace?: string): ComputedRef<boolean>
hasTranslationNow(key: string, opts?): boolean
hasLocaleNow(locale: string, namespace?: string): boolean
use<T extends WrapperI18nInstance<D>>(installer: (core: C) => T): VueI18n<D, T>
init(): Promise<this>
destroy(): Promise<void>
install(app: App): void
Method Meaning
setLocale(l) Resolves once the translations loaded
hasTranslation, hasLocale Allocate a computed(); call inside setup() or an effectScope
hasTranslationNow, hasLocaleNow Plain booleans, safe anywhere
use(installer) Runs installer(this.core) and returns the same wrapper, retyped. An installer returning another object throws with code: 'E_USE_INSTANCE_IDENTITY'. Compose before app.use(i18n); a later .use warns once in development
init() Initializes the wrapped instance
destroy() Resolves once plugin cleanups finished; a cleanup rejection goes to onError with source 'plugin-cleanup'; repeat calls return the same promise
install(app) Idempotent per app. Starts init() fire-and-forget, provides the instance under I18N_INJECTION_KEY and registers $t, $tRaw, $i18n. On the server, await i18n.init() before renderToString

The .use() deadline is app.use(i18n), because install starts init() and init() drains the plugin queue once. A plugin queued after that waits for the next init(), which never comes in a normal app.

Pass-throughs to the wrapped instance: addMessages, clearMessages, on, setFallbackLocale, setDefaultParams, reportError, formatNumber, formatDate, formatCurrency, formatRelativeTime. Put .use() on the wrapper, not on .core: the latter runs the installer but leaves the static type of core unchanged.

function useI18n<D extends DefaultTranslationParams = {}, NS extends string | undefined = undefined>(ns?: NS): UseI18nReturn<D, NS>

Throws when app.use(i18n) did not run.

Returned Type Meaning
t NamespaceBoundTranslateFn<NS, D, string> Translate; scoped to ns
tRaw NamespaceBoundTranslateFn<NS, D, TranslationResult> Rich-text half
locale Ref<string> Assigning .value switches without a promise
setLocale (locale: string) => Promise<void> Resolves once loaded
messagesCache ComputedRef<ReadonlyMap<string, FlattenedMessages>> Reactive cache view
cacheRevision Readonly<Ref<number>> Messages-cache revision; config changes have their own tracking
isLoading, isInitializing, isInitialized Readonly<Ref<boolean>> Loading flags
dir ComputedRef<'ltr' | 'rtl'> Text direction
addMessages, setFallbackLocale, setDefaultParams, clearMessages as on the instance Mutations
defaultParams ComputedRef<DefaultParamsSnapshot<D>> Shallow snapshot
loadedLocales, activeNamespaces ComputedRef<string[]> Reactive lists
defaultNamespace ComputedRef<string> Reactive default namespace
hasTranslation, hasLocale (…) => ComputedRef<boolean> Reactive checks; call in setup()
formatNumber, formatDate, formatCurrency, formatRelativeTime bound, trailing locale? Formatters
on, reportError as on the instance Events and errors
destroy () => Promise<void> Tear down the wrapper
src/components/Greeting.vue
<script setup lang="ts">
import { useI18n } from '@comvi/vue';
const { t, locale, setLocale } = useI18n();
</script>
<template>
<button @click="setLocale(locale === 'en' ? 'de' : 'en')">
{{ t('greeting', { name: 'Alice' }) }}
</button>
</template>
function useI18nLoader(): UseI18nLoaderReturn // AcquiredLoaderApi
function useI18nPlugins(): UseI18nPluginsReturn // AcquiredPluginsApi

useI18nLoader() returns addActiveNamespace, addActiveNamespaces, removeActiveNamespace, removeActiveNamespaces, reloadMessages, onLoadError. useI18nPlugins() returns onMissingKey. Each throws, in development and production, when the injected instance lacks the capability. Cached per instance.

src/components/Dashboard.vue
<script setup lang="ts">
import { onMounted } from 'vue';
import { useI18n, useI18nLoader } from '@comvi/vue';
const { t } = useI18n('dashboard');
const { addActiveNamespace } = useI18nLoader();
onMounted(() => void addActiveNamespace('dashboard'));
</script>
<template>
<h1>{{ t('page.title') }}</h1>
</template>

Renders a translation with <tag> markup without v-html. Named slots and components are both tag handlers; components wins a name collision.

Prop Type Meaning
i18nKey string Required
params Record<string, unknown> Interpolation params; any undeclared attribute is a param too and wins a collision
ns, locale, fallback string Overrides; fallback wins over the default slot
markers boolean false turns the in-context editor’s markers off for this element when they cause a problem; it is then not editable in context
components ComponentMap Tag name to a tag name, a component, or { tag | component, props }; tag wins
default slot Rendered when the key is missing, no fallback was given and onMissingKey returned nothing

Write direct params in camelCase. class, style and listeners on <T> become params, so put them on a wrapping element. A slot handler that throws is reported as reportError(error, { source: 'translation', tagName }) and degrades to <span> with the children, on the server and the client alike; a component in components that throws is not caught.

src/components/Legal.vue
<script setup lang="ts">
import { T } from '@comvi/vue';
</script>
<template>
<T i18nKey="legal.tos">
<template #link="{ children }">
<a href="/terms">{{ children }}</a>
</template>
</T>
<T i18nKey="notice" :components="{ bold: 'strong' }" />
<T i18nKey="maybe.missing">Default fallback text</T>
</template>

install(app) registers $t(key, params?), $tRaw(key, params?) and $i18n, typed AnyVueI18n, which erases D and the type of core.

const I18N_INJECTION_KEY: InjectionKey<AnyVueI18n>

What install(app) provides under. inject(I18N_INJECTION_KEY) gives the wrapper with a capability-free core, so composing from inside a component is a type error.

  • Vue: setup, loading, plurals, tags and typing for a Vue 3 app.
  • @comvi/nuxt: module options, the setup hook and server helpers.
  • @comvi/core: the i18n instance, the capabilities and every type.