# @comvi/vue

API reference for @comvi/vue: createI18n, createI18nFromCore, the VueI18n class, useI18n, useI18nLoader, useI18nPlugins, <T>, I18N_INJECTION_KEY and the Options API globals.

Canonical: https://comvi.io/docs/i18n/api/vue

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

## Exports

- 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.

## `createI18n(options)`

```ts
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`](/docs/i18n/api/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`

```ts

export const i18n = createI18n({ locale: 'en', ssrLocale: 'en' })
.use(loader({ en: () => import('./locales/en.json') }))
.use(plugins());
```

## `createI18nFromCore(core, options?)`

```ts
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`

```ts

const core = createCore({ locale: 'en' }).use(loader({ en: () => import('./locales/en.json') }));

export const i18n = createI18nFromCore(core, { ssrLocale: 'en' });
```

## `VueI18n`

### Reactive state

| 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>` |

### Methods

```ts
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.

## `useI18n(ns?)`

```ts
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`

```vue
<script setup lang="ts">

const { t, locale, setLocale } = useI18n();
</script>

<template>
<button @click="setLocale(locale === 'en' ? 'de' : 'en')">
    {{ t('greeting', { name: 'Alice' }) }}
</button>
</template>
```

## Capability composables

```ts
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`

```vue
<script setup lang="ts">

const { t } = useI18n('dashboard');
const { addActiveNamespace } = useI18nLoader();

onMounted(() => void addActiveNamespace('dashboard'));
</script>

<template>
<h1>{{ t('page.title') }}</h1>
</template>
```

## `<T>`

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`

```vue
<script setup lang="ts">
</script>

<template>

    <template #link="{ children }">
      <a href="/terms">{{ children }}</a>
    </template>

<T i18nKey="maybe.missing">Default fallback text</T>
</template>
```

## Options API globals

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

## `I18N_INJECTION_KEY`

```ts
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.

## Read next

- [Vue](/docs/i18n/vue): setup, loading, plurals, tags and typing for a Vue 3 app.
- [`@comvi/nuxt`](/docs/i18n/api/nuxt): module options, the setup hook and server helpers.
- [`@comvi/core`](/docs/i18n/api/core): the i18n instance, the capabilities and every type.
