mrkeyoor.com_
Sat 08 Aug 22:01 UTC
npmWeb Frontendupdated 08 Aug 2026

vue-i18n

Vue I18n is the main internationalization plugin for Vue 3. It connects locale messages to components through the useI18n composable, formats plurals, numbers and dates, supports global and component-local message scopes, and can lazy-load translation files. Version 11 includes both the older Vue-style API and the Composition API, but new code should explicitly choose Composition mode because the legacy API is deprecated and scheduled for removal in version 12.

Verdict

The default choice for a Vue 3 application that needs serious localization, provided you opt into Composition mode and wire production compilation deliberately. Do not start new code on the legacy API, and do not assume development behavior proves dynamically loaded messages will compile in production.

API stability3/5The Composition API centered on createI18n, useI18n, t, d and n is consistent across recent releases, and version 11 retains compatibility shims. The migration guide is also explicit that legacy mode and v-t are deprecated and will disappear in version 12, while $tc already disappeared in version 11. Existing legacy applications therefore face real, documented migration work even though new Composition-mode code has a steadier path.
Docs4/5The official site has separate guides for composition scope, fallback chains, plural rules, date and number formatting, lazy loading, TypeScript schemas, bundler optimization and migrations. It documents production-only failures such as an incomplete precompile include pattern. Some pages still mix legacy $t examples with Composition examples and refer across multiple versioned sections, so readers must keep track of which API mode a snippet assumes.
Maintenance5/5Version 11.4.8 was published on July 26, 2026, and the repository was pushed to in August 2026. The README labels version 11 stable, explains that an npm deprecation mark was accidental, and publishes a maintenance policy that puts versions 9 and 10 into maintenance mode. The repository currently reports 89 open issues and pull requests, but releases and main-branch work are plainly active rather than frozen.
Ecosystem5/5The package records 3,601,155 downloads for the measured week and is part of the broader Intlify project. That project supplies a Vite and webpack plugin, an ESLint plugin, CLI tooling, locale-message utilities and lower-level compiler packages. Vue integration covers app installation, composables, injected template helpers, built-in formatting components and single-file component blocks, which is much more complete than a thin translation-function wrapper.

Use it if

  • You are building a Vue 3 application and want translations to react automatically when the active locale changes
  • You need plural rules, locale fallback, date formatting and number formatting behind one Vue-aware API
  • You want component-local messages or i18n blocks in single-file components as well as a shared global catalog
  • You need TypeScript to check locale names and translation resource shapes from a master message file
Skip it if

Setup reality

Install vue-i18n alongside Vue 3, but check the runtime first: version 11.4.8 declares Node >=22 and vue ^3.0.0 as a peer. Create one i18n instance, pass legacy: false for the Composition API, register it with app.use(i18n), then call useI18n at the top of <script setup>. Omitting legacy: false selects the older mode by default in version 11, which is exactly the API the migration guide marks for removal in version 12. Large catalogs need a loading strategy because the package does not split locale files on its own. Dynamic import, setLocaleMessage and a router guard are the usual combination, and you should also update the document lang attribute. Production bundling adds another moving part: @intlify/unplugin-vue-i18n precompiles files matched by its include pattern and defaults to a runtime-only production build. A bad include glob can make {name} placeholders work in development but stay unexpanded in production. If messages come from an API or database rather than build-time files, set runtimeOnly: false so the message compiler remains. Single-file <i18n> blocks also require that plugin. Date, number and plural behavior depends on the host's Intl implementation, so older or restricted runtimes may need polyfills. For SSR, create an i18n instance per request instead of mutating one process-wide locale, or concurrent users can leak locale state into one another.

Patterns

Create a Vue 3 i18n instanceinstall-composition-mode

// src/i18n.js
import { createI18n } from 'vue-i18n'

export const i18n = createI18n({
  legacy: false,
  locale: 'en',
  fallbackLocale: 'en',
  messages: {
    en: { greeting: 'Hello, {name}!' },
    fr: { greeting: 'Bonjour, {name} !' },
  },
})

// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import { i18n } from './i18n.js'

createApp(App).use(i18n).mount('#app')

Set legacy: false explicitly. Version 11 defaults to legacy mode, and that mode is deprecated for removal in version 12.

Translate with named interpolationtranslate-message

<script setup>
import { useI18n } from 'vue-i18n'

const { t } = useI18n()
</script>

<template>
  <h1>{{ t('greeting', { name: 'Ada' }) }}</h1>
</template>

Call useI18n at the top of <script setup>. Do not pass untrusted HTML through v-html; use component interpolation when markup is required.

Select a plural formpluralize-message

const messages = {
  en: { apples: 'no apples | one apple | {count} apples' },
}

const { t } = useI18n()
console.log(t('apples', 0))
console.log(t('apples', 1))
console.log(t('apples', 12))

The same t function handles pluralization and supplies count and n implicitly. The old $tc helper is not part of version 11.

Change the active locale reactivelyswitch-locale

<script setup>
import { watch } from 'vue'
import { useI18n } from 'vue-i18n'

const { locale } = useI18n({ useScope: 'global' })

watch(locale, (value) => {
  document.documentElement.lang = value
})
</script>

<template>
  <select v-model="locale">
    <option value="en">English</option>
    <option value="fr">Français</option>
  </select>
</template>

locale is a ref in Composition mode. Updating the HTML lang attribute is separate and matters to screen readers and search engines.

Define an explicit fallback chainconfigure-fallback

const i18n = createI18n({
  legacy: false,
  locale: 'de-CH',
  fallbackLocale: {
    'de-CH': ['fr', 'it'],
    default: ['en'],
  },
  messages,
})

Regional locales also fall back implicitly, such as de-CH to de. Add ! to a locale code when that implicit chain is unwanted.

Keep messages local to one componentscope-component-messages

<script setup>
import { useI18n } from 'vue-i18n'

const { t } = useI18n({
  useScope: 'local',
  messages: {
    en: { title: 'Billing settings' },
    fr: { title: 'Paramètres de facturation' },
  },
})
</script>

<template><h2>{{ t('title') }}</h2></template>

Local scope isolates keys but inherits the global locale by default. Repeating large catalogs in components can make translation management harder.

Format currency with a named formatformat-currency

const i18n = createI18n({
  legacy: false,
  locale: 'en-US',
  numberFormats: {
    'en-US': { money: { style: 'currency', currency: 'USD' } },
    'fr-FR': { money: { style: 'currency', currency: 'EUR' } },
  },
})

const { n } = useI18n()
console.log(n(1234.5, 'money'))

Number formatting delegates to Intl.NumberFormat, so available options and locale data depend on the JavaScript runtime.

Format a date with a named formatformat-date

const i18n = createI18n({
  legacy: false,
  locale: 'en-US',
  datetimeFormats: {
    'en-US': { short: { year: 'numeric', month: 'short', day: 'numeric' } },
  },
})

const { d } = useI18n()
console.log(d(new Date('2026-08-08T12:00:00Z'), 'short'))

The displayed day can change with the user's time zone. Pass an appropriate timeZone option when the product needs a fixed zone.

Put a Vue component inside a sentenceinterpolate-component

<i18n-t keypath="terms.accept" tag="p">
  <template #link>
    <RouterLink to="/terms">{{ t('terms.link') }}</RouterLink>
  </template>
</i18n-t>

// messages.en
{
  terms: {
    accept: 'I accept the {link}.',
    link: 'terms of service',
  },
}

Use i18n-t instead of putting translated HTML into v-html. Named slots let translators move the component placeholder safely.

Load a locale catalog on demandlazy-load-locale

import { nextTick } from 'vue'
import { i18n } from './i18n.js'

const loaders = {
  fr: () => import('./locales/fr.json'),
  ja: () => import('./locales/ja.json'),
}

export async function setLocale(locale) {
  if (!i18n.global.availableLocales.includes(locale)) {
    const mod = await loaders[locale]()
    i18n.global.setLocaleMessage(locale, mod.default)
  }
  i18n.global.locale.value = locale
  document.documentElement.lang = locale
  await nextTick()
}

Use a fixed loader map instead of importing an unchecked user string. With the runtime-only production build, every imported catalog must be precompiled.

Precompile locale files with Viteconfigure-vite-plugin

// vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'

export default defineConfig({
  plugins: [
    vue(),
    VueI18nPlugin({
      include: fileURLToPath(new URL('./src/locales/**', import.meta.url)),
      runtimeOnly: true,
    }),
  ],
})

The include pattern must match every build-time catalog. Set runtimeOnly: false if messages arrive as strings from an API at runtime.

Type-check locales from a master catalogtype-message-schema

import { createI18n } from 'vue-i18n'
import enUS from './locales/en-US.json'
import jaJP from './locales/ja-JP.json'

type MessageSchema = typeof enUS
type AppLocale = 'en-US' | 'ja-JP'

export const i18n = createI18n<[MessageSchema], AppLocale>({
  legacy: false,
  locale: 'en-US',
  messages: {
    'en-US': enUS,
    'ja-JP': jaJP,
  },
})

Using one locale as the schema catches missing resource shapes during type checking, but translator-edited JSON still needs CI to run TypeScript.

Alternatives

PackageRegistryPick it when
i18next-vuenpmYou already use i18next on the server or in other frontends and want Vue bindings over the same catalogs
fluent-vuenpmYou prefer Mozilla Fluent's translator-oriented message syntax and are comfortable adopting its catalog format
typesafe-i18nnpmCompile-time key safety and generated TypeScript APIs matter more than deep Vue-specific component integration