| |

Vue.js 79 🟢 Vue Application Internationalization (i18n) with vue-i18n

⏱️ Estimated read: 18 min | |

Internationalization is not a feature you add at the end of a project; it is a structural decision that touches every string, date, and number the user sees. vue-i18n is the standard library for this in Vue, and its modern Composition API mode changes how translation state flows through an application. Unlike a simple dictionary lookup, a properly configured i18n layer manages locale switching, plural rules, date and currency formatting, and lazy loading of message bundles—all while keeping the UI reactive.

This chapter covers vue-i18n for Vue 3 in Composition API mode. It examines how createI18n and useI18n work together, how message interpolation and pluralization are expressed, how dates and numbers are formatted through named presets, and how locale bundles are loaded on demand rather than shipped all at once.

Key point: The single configuration option that determines whether vue-i18n works with the Composition API is legacy: false. Omitting it places the instance in Vue 2 compatibility mode, where useI18n() throws and Composition API examples fail in ways that do not clearly point to the cause .


Why vue-i18n exists

The string scattering problem. In a single-language application, text lives wherever it is convenient—inside templates, inside JavaScript constants, inside validation messages. Translating that application means finding every string, extracting it, and replacing it with a lookup. vue-i18n exists to make the lookup the default from the start: messages live in structured files, components call t(), and adding a language means adding a file rather than editing components.

The reactivity problem. A naive translation function returns a string for the current locale, but when the user changes language, nothing re-renders. vue-i18n solves this by making locale a reactive reference and binding t() to it . Changing locale.value triggers re-render of every component that depends on a translated string, without manual event buses or page reloads.

The formatting problem. Dates and numbers are locale-specific in ways that translation strings cannot express. “July 22, 2025” versus “22 juillet 2025” is not a word substitution; it is a formatting rule. vue-i18n delegates this to the platform’s Intl API through d() and n(), so currency symbols, decimal separators, and calendar behavior follow CLDR data rather than hand-written logic .

The bundle size problem. An application supporting ten languages does not need ten message files in the initial JavaScript bundle. vue-i18n provides the primitives for lazy loading: dynamic import() of locale files, registration through setLocaleMessage, and integration with router navigation guards so messages are present before a route renders . Without this, every user downloads every language they will never see.


a. Installation and Instance Configuration

vue-i18n is installed as a runtime dependency and registered as a Vue plugin. The current maintained major version is 11, though v9 and v10 remain in maintenance mode . After installation, createI18n produces the instance, and app.use() connects it to the Vue application.

// src/i18n.ts — creates and configures the i18n instance
import { createI18n } from 'vue-i18n'
import en from './locales/en.json'
import ja from './locales/ja.json'

const i18n = createI18n({
  // legacy: false is required for the Composition API
  // Without it, useI18n() throws at runtime
  legacy: false,

  // globalInjection makes $t available in templates without importing t
  globalInjection: true,

  // locale is the active language; a reactive ref in composition mode
  locale: 'en',

  // fallbackLocale is used when a message key is missing in the active locale
  fallbackLocale: 'en',

  // messages maps locale codes to their message objects
  messages: { en, ja }
})

export default i18n
// src/main.ts — installs i18n as a Vue plugin
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './i18n'

const app = createApp(App)

// app.use registers the i18n instance and its global properties
app.use(i18n)

app.mount('#app')

The legacy option deserves emphasis because it is the most common source of confusion. When legacy: true (the default if omitted), the instance operates in Vue 2 compatibility mode, where useI18n() is not available and the $t signature differs. Setting legacy: false enables the Composition API mode, where locale is a ref and useI18n returns the translation functions .


b. Using Translations in Components

The useI18n composable returns the translation function t along with formatting helpers and the reactive locale reference. In <script setup>, destructuring from a single call is the recommended pattern; calling useI18n multiple times with local scope in the same component triggers a warning and produces unsynchronized instances .

<!-- src/components/Dashboard.vue — uses t() for translation -->
<script setup lang="ts">
import { useI18n } from 'vue-i18n'

// Destructure everything needed from one call
const { t, locale } = useI18n()

// t() accepts a key path and an optional values object
// The values object fills named placeholders in the message
const greeting = t('dashboard.welcome', { name: 'Alice' })
</script>

<template>
  <div>
    <!-- In templates, t is available from the destructured setup -->
    <h1>{{ t('dashboard.title') }}</h1>

    <!-- Named interpolation: {name} in the message is replaced -->
    <p>{{ greeting }}</p>

    <!-- locale is a ref; assigning to .value switches language reactively -->
    <select v-model="locale">
      <option value="en">English</option>
      <option value="ja">日本語</option>
    </select>
  </div>
</template>

Message files are nested JSON objects. The key path passed to t() traverses the nesting. Interpolation uses single curly braces around the placeholder name, unlike some other i18n libraries that use double braces .

// src/locales/en.json — nested message structure
{
  "dashboard": {
    "title": "Dashboard",
    "welcome": "Welcome back, {name}"
  }
}

Pluralization uses pipe-separated forms within a single message string. The number of forms determines the selection rule: with two forms, the first is singular and the second is plural; with three, the first handles zero, the second singular, and the third plural .

// Plural forms for a message with a count placeholder
{
  "messages": "no messages | one message | {count} messages"
}
<!-- Passing the count selects the correct form -->
<p>{{ t('messages', { count: messageCount }) }}</p>

For languages with more than three plural categories—Russian, Polish, Arabic, Welsh—the default pipe syntax is insufficient. These require a custom pluralRules function registered per locale, which returns the index of the form to use based on the count .


c. Number and Date Formatting, Lazy Loading

Translation is only part of localization. Dates and numbers carry locale-specific conventions that string substitution cannot handle. vue-i18n exposes d() for dates and n() for numbers, both backed by Intl and both accepting named format presets defined on the instance.

// i18n.ts — named format definitions per locale
const i18n = createI18n({
  legacy: false,
  locale: 'en',
  numberFormats: {
    en: {
      // Currency format for US dollars
      currency: { style: 'currency', currency: 'USD' }
    },
    ja: {
      // Currency format for Japanese yen
      currency: { style: 'currency', currency: 'JPY' }
    }
  },
  datetimeFormats: {
    en: {
      // Short date format for English
      short: { year: 'numeric', month: 'short', day: 'numeric' }
    },
    ja: {
      // Short date format for Japanese
      short: { year: 'numeric', month: 'long', day: 'numeric' }
    }
  }
})
<script setup lang="ts">
const { d, n } = useI18n()

// The second argument selects the named format
// The output adapts to the active locale automatically
const formattedDate = d(new Date(), 'short')
const formattedPrice = n(4999, 'currency')
</script>

Lazy loading addresses the bundle size problem. Instead of importing every locale file at startup, the application imports only the active locale and loads others on demand. The pattern combines dynamic import() with setLocaleMessage and is typically triggered from a router navigation guard so messages are present before the target route renders .

// src/i18n/lazy.ts — loads a locale bundle only when needed
import { nextTick } from 'vue'
import type { I18n } from 'vue-i18n'

export async function loadLocale(i18n: I18n, locale: string) {
  // Dynamic import creates a separate chunk per locale file
  const messages = await import(`../locales/${locale}.json`)

  // Register the loaded messages for this locale
  i18n.global.setLocaleMessage(locale, messages.default)

  // Set the active locale; the ref update triggers re-render
  i18n.global.locale.value = locale

  // Update the HTML lang attribute for accessibility and browser behavior
  document.querySelector('html')?.setAttribute('lang', locale)

  // Wait for the DOM to update after the locale change
  return nextTick()
}
// router/index.ts — loads locale before navigation completes
router.beforeEach(async (to, from, next) => {
  const paramsLocale = to.params.locale as string

  // Only load if the locale bundle is not already present
  if (!i18n.global.availableLocales.includes(paramsLocale)) {
    await loadLocale(i18n, paramsLocale)
  }

  next()
})

The lang attribute on the <html> element is not decorative. It drives screen reader pronunciation, browser translation prompts, and font selection for CJK text . Setting it whenever the locale changes is part of a complete i18n implementation.


Complete Example Session

// ============================================
// PART 1: PROJECT STRUCTURE
// ============================================
// src/
// ├── locales/
// │   ├── en.json
// │   └── ja.json
// ├── i18n/
// │   ├── index.ts          → createI18n instance
// │   └── lazy.ts           → lazy loading helper
// ├── components/
// │   └── Dashboard.vue     → uses useI18n
// ├── router/
// │   └── index.ts          → navigation guard loads locale
// └── main.ts               → installs i18n plugin


// ============================================
// PART 2: I18N INSTANCE CONFIGURATION
// ============================================
// src/i18n/index.ts
import { createI18n } from 'vue-i18n'
import en from '../locales/en.json'

const i18n = createI18n({
  // Required for Composition API: useI18n() will throw without this
  legacy: false,
  // Makes $t available in templates without explicit import
  globalInjection: true,
  // Active locale; in composition mode this is a ref
  locale: 'en',
  // Used when a key is missing in the active locale
  fallbackLocale: 'en',
  // Only English is bundled at startup; Japanese loads on demand
  messages: { en },
  numberFormats: {
    en: { currency: { style: 'currency', currency: 'USD' } },
    ja: { currency: { style: 'currency', currency: 'JPY' } }
  },
  datetimeFormats: {
    en: { short: { year: 'numeric', month: 'short', day: 'numeric' } },
    ja: { short: { year: 'numeric', month: 'long', day: 'numeric' } }
  }
})

export default i18n


// ============================================
// PART 3: MESSAGE FILES
// ============================================
// src/locales/en.json
{
  "dashboard": {
    "title": "Dashboard",
    "welcome": "Welcome back, {name}",
    "messages": "no messages | one message | {count} messages",
    "price": "Total: {amount}"
  }
}

// src/locales/ja.json
{
  "dashboard": {
    "title": "ダッシュボード",
    "welcome": "おかえりなさい、{name}さん",
    "messages": "メッセージなし | 1件のメッセージ | {count}件のメッセージ",
    "price": "合計: {amount}"
  }
}


// ============================================
// PART 4: LAZY LOADING HELPER
// ============================================
// src/i18n/lazy.ts
import { nextTick } from 'vue'
import type { I18n } from 'vue-i18n'

export async function loadLocale(i18n: I18n, locale: string) {
  // Dynamic import creates a separate chunk for this locale
  const messages = await import(`../locales/${locale}.json`)
  i18n.global.setLocaleMessage(locale, messages.default)
  i18n.global.locale.value = locale
  // lang attribute affects screen readers and font selection
  document.querySelector('html')?.setAttribute('lang', locale)
  return nextTick()
}


// ============================================
// PART 5: COMPONENT USING TRANSLATION
// ============================================
// src/components/Dashboard.vue
<script setup lang="ts">
import { useI18n } from 'vue-i18n'

// Single destructured call: multiple calls with local scope trigger warnings
const { t, d, n, locale } = useI18n()

// Simulated data values
const userName = 'Alice'
const messageCount = 3
const lastUpdated = new Date()
const totalPrice = 4999
</script>

<template>
  <div>
    <h1>{{ t('dashboard.title') }}</h1>
    <p>{{ t('dashboard.welcome', { name: userName }) }}</p>
    <p>{{ t('dashboard.messages', { count: messageCount }) }}</p>
    <p>{{ t('dashboard.price', { amount: n(totalPrice, 'currency') }) }}</p>
    <p>{{ d(lastUpdated, 'short') }}</p>
  </div>
</template>


// ============================================
// PART 6: ROUTER GUARD FOR LOCALE LOADING
// ============================================
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import i18n from '../i18n'
import { loadLocale } from '../i18n/lazy'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      // Locale is a URL parameter so the language is shareable and bookmarkable
      path: '/:locale?',
      component: () => import('../components/Dashboard.vue')
    }
  ]
})

router.beforeEach(async (to) => {
  const paramsLocale = (to.params.locale as string) || 'en'

  // Load the locale bundle before the route renders
  if (!i18n.global.availableLocales.includes(paramsLocale)) {
    await loadLocale(i18n, paramsLocale)
  }
})

export default router


// ============================================
// PART 7: LANGUAGE SWITCHER
// ============================================
// src/components/LanguageSwitcher.vue
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'

const { locale } = useI18n()
const router = useRouter()

async function switchLocale(newLocale: string) {
  // Navigate to the same page under the new locale prefix
  // The router guard will load the messages if needed
  await router.push(`/${newLocale}`)
}
</script>

<template>
  <select :value="locale" @change="switchLocale(($event.target as HTMLSelectElement).value)">
    <option value="en">English</option>
    <option value="ja">日本語</option>
  </select>
</template>


// ============================================
// PART 8: INTERPOLATION WITH COMPONENTS
// ============================================
// Message with a link placeholder
// src/locales/en.json
{
  "notice": "Read our <link>terms and conditions</link>."
}

// Template using i18n-t for safe component interpolation
<template>
  <i18n-t keypath="notice" tag="p">
    <template #link>
      <a href="/terms">terms and conditions</a>
    </template>
  </i18n-t>
</template>


// ============================================
// PART 9: CUSTOM PLURAL RULE
// ============================================
// For languages with more than three plural forms, define a rule
// This example is for Russian, which has four categories
const i18n = createI18n({
  legacy: false,
  pluralRules: {
    ru(choice: number, choicesLength: number) {
      if (choice === 0) return 0
      const teen = choice > 10 && choice < 20
      const endsWithOne = choice % 10 === 1
      if (!teen && endsWithOne) return 1
      if (!teen && choice % 10 >= 2 && choice % 10 <= 4) return 2
      return choicesLength < 4 ? 2 : 3
    }
  }
})


// ============================================
// PART 10: HTML LANG ATTRIBUTE UPDATE
// ============================================
// In the loadLocale function from Part 4:
document.querySelector('html')?.setAttribute('lang', locale)
// This ensures screen readers use correct pronunciation
// and browsers offer appropriate translation prompts

The ten parts trace the full path from instance creation through lazy loading to component usage. The critical configuration is legacy: false, which enables the Composition API mode. The lazy loading pattern keeps the initial bundle small by loading locale files only when the router navigates to a locale that has not yet been loaded.


Quick Reference

Instance Configuration Options

OptionPurposeRequired for Composition API
legacyEnables Vue 2 compatibility modeMust be false
globalInjectionMakes $t available in templatesOptional
localeActive language codeYes
fallbackLocaleLanguage used when key is missingRecommended
messagesInitial locale message bundlesYes
numberFormatsNamed number format presetsOptional
datetimeFormatsNamed date format presetsOptional

Composition API Functions

FunctionPurposeReturns
useI18n()Access i18n context{ t, d, n, locale, ... }
t(key, values)Translate a messageString
d(date, format)Format a dateString
n(number, format)Format a numberString
localeActive locale (reactive ref)Ref<string>

Message Syntax

FeatureSyntaxExample
Named interpolation{name}"Hello {name}"
Pluralizationsingular | plural"car | cars"
Three-form pluralzero | one | many"no items | one item | {count} items"
Linked message@:key.path"See @:dashboard.title"

Lazy Loading Functions

FunctionLocationPurpose
setLocaleMessagei18n.globalRegister loaded messages
availableLocalesi18n.globalCheck if locale is loaded
import()Module scopeDynamic chunk loading

Best Practices

✅ Do This:

// Set legacy: false explicitly
createI18n({ legacy: false })                                  // ✅
// Destructure from a single useI18n call
const { t, d, n, locale } = useI18n()                          // ✅
// Use named formats for numbers and dates
n(price, 'currency')                                           // ✅
// Load locale messages in a router guard
router.beforeEach(async () => { await loadLocale(i18n, locale) }) // ✅
// Update the HTML lang attribute on locale change
document.querySelector('html')?.setAttribute('lang', locale)   // ✅
<!-- Use i18n-t for messages containing components -->
<i18n-t keypath="notice"><template #link><a href="/terms" /></template></i18n-t> <!-- ✅ -->

❌ Don’t Do This:

// Omit legacy: false and expect useI18n to work
createI18n({ locale: 'en' })                                   // ❌
// Call useI18n multiple times with local scope
const { t } = useI18n({ messages: {} }); const { locale } = useI18n() // ❌
// Format numbers manually with string concatenation
`$${price.toFixed(2)}`                                         // ❌
// Import all locale files at startup
import en from './locales/en'; import ja from './locales/ja'   // ❌
// Forget to set HTML lang attribute
// No call to setAttribute('lang', locale)                     // ❌
<!-- Use v-html for translations containing user data -->
<p v-html="$t('message')"></p>                                 <!-- ❌ -->

Common Pitfalls

PitfallWhy It HappensFix
useI18n() throws an errorlegacy: true (default) is activeSet legacy: false in createI18n
Locale change does not re-renderlocale mutated outside the refAssign to locale.value in composition mode
Plural form is wrong for some numbersDefault rule handles only 2–3 formsRegister a custom pluralRules function
Missing message renders the key pathKey absent from active and fallback localesCheck message file structure and fallback
Lazy loading fails silentlyDynamic import path is incorrectVerify file path and setLocaleMessage call
Multiple useI18n calls cause warningsLocal scope creates separate instancesDestructure once and reuse the returned object

Real-World Examples

1. Basic Setup with legacy: false

const i18n = createI18n({ legacy: false, locale: 'en', messages: { en } })

2. Using t in Script Setup

const { t } = useI18n()
const title = t('page.title')

3. Named Interpolation

{ "welcome": "Hello, {name}" }
<p>{{ t('welcome', { name: user.name }) }}</p>

4. Plural with Count

{ "items": "no items | one item | {count} items" }
<p>{{ t('items', { count: itemCount }) }}</p>

5. Date Format Preset

datetimeFormats: { en: { short: { year: 'numeric', month: 'short' } } }

6. Number Format Preset

numberFormats: { en: { currency: { style: 'currency', currency: 'USD' } } }

7. Lazy Load Locale

const messages = await import(`./locales/${locale}.json`)
i18n.global.setLocaleMessage(locale, messages.default)

8. Router Guard Loads Locale

router.beforeEach(async (to) => {
  await loadLocale(i18n, to.params.locale)
})

9. Component Interpolation with i18n-t

<i18n-t keypath="notice" tag="p">
  <template #link><a href="/terms">terms</a></template>
</i18n-t>

10. HTML Lang Attribute

document.querySelector('html')?.setAttribute('lang', locale)

Visual

The i18n Instance and Component Connection

┌──────────────────────────────────────────────────────────────┐
│  CREATEI18N INSTANCE                                         │
│                                                              │
│  ┌─────────────────┐                                        │
│  │ legacy: false   │  Enables Composition API mode          │
│  │ messages: {en}  │  Initial bundle                        │
│  │ locale: 'en'    │  Reactive ref (in composition mode)    │
│  │ fallbackLocale  │  Used when key is missing              │
│  └────────┬────────┘                                        │
│           │                                                  │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │ app.use(i18n)   │  Registers plugin globally             │
│  └────────┬────────┘                                        │
│           │                                                  │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │ useI18n()       │  Returns { t, d, n, locale }           │
│  │ in component    │                                        │
│  └────────┬────────┘                                        │
│           │                                                  │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │ locale.value    │  Changing this triggers re-render      │
│  │ changes         │  of all components using t()           │
│  └─────────────────┘                                        │
│                                                              │
│  The instance is the single source of truth.                 │
│  Components never hold their own message copies.             │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Lazy Loading Flow

┌──────────────────────────────────────────────────────────────┐
│  ROUTER NAVIGATION TO /ja/page                               │
│                                                              │
│  ┌─────────────────┐                                        │
│  │ beforeEach      │                                        │
│  │ guard fires     │                                        │
│  └────────┬────────┘                                        │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │ Is 'ja' in      │────► YES: skip loading                 │
│  │ availableLocales│                                        │
│  └────────┬────────┘                                        │
│           │ NO                                               │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │ await import(   │  Creates separate JS chunk             │
│  │ '../locales/    │  Downloaded only for this locale       │
│  │ ja.json')       │                                        │
│  └────────┬────────┘                                        │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │ setLocaleMessage│  Registers messages for 'ja'           │
│  │ ('ja', messages)│                                        │
│  └────────┬────────┘                                        │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │ locale.value =  │  Triggers reactive update              │
│  │ 'ja'            │                                        │
│  └────────┬────────┘                                        │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │ next()          │  Navigation completes; route renders   │
│  └─────────────────┘                                        │
│                                                              │
│  Initial bundle contains only 'en'. 'ja' loads on demand.   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Plural Form Selection

┌──────────────────────────────────────────────────────────────┐
│  MESSAGE: "no items | one item | {count} items"              │
│                                                              │
│  t('items', { count: 0 })                                    │
│  ┌─────────────────┐                                        │
│  │ Three forms?    │────► Yes: index 0 = "no items"         │
│  └─────────────────┘                                        │
│                                                              │
│  t('items', { count: 1 })                                    │
│  ┌─────────────────┐                                        │
│  │ Three forms,    │────► index 1 = "one item"              │
│  │ count is 1      │                                        │
│  └─────────────────┘                                        │
│                                                              │
│  t('items', { count: 5 })                                    │
│  ┌─────────────────┐                                        │
│  │ Three forms,    │────► index 2 = "5 items"               │
│  │ count is not 0  │                                        │
│  │ or 1            │                                        │
│  └─────────────────┘                                        │
│                                                              │
│  For languages with more than three forms (Russian, Arabic), │
│  a custom pluralRules function returns the correct index.    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Date and Number Formatting Path

┌──────────────────────────────────────────────────────────────┐
│  COMPONENT CALLS d() OR n()                                  │
│                                                              │
│  ┌─────────────────┐                                        │
│  │ d(new Date(),   │                                        │
│  │   'short')      │                                        │
│  └────────┬────────┘                                        │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │ Look up 'short' │  From datetimeFormats for active locale│
│  │ in active locale│                                        │
│  └────────┬────────┘                                        │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │ Delegate to     │  Intl.DateTimeFormat                   │
│  │ Intl API        │  Uses CLDR data per locale             │
│  └────────┬────────┘                                        │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │ Return formatted│  en: "Jul 22, 2025"                    │
│  │ string          │  ja: "2025年7月22日"                    │
│  └─────────────────┘                                        │
│                                                              │
│  Formatting is not string substitution.                      │
│  It follows locale-specific rules from CLDR.                 │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
Packagevue-i18n (v11 current, v9/v10 maintenance)
Composition API flaglegacy: false in createI18n
Translation functiont(key, values) from useI18n()
Date formattingd(date, formatName)
Number formattingn(number, formatName)
PluralizationPipe-separated forms: one | many
Lazy loadingDynamic import() + setLocaleMessage
HTML lang attributeMust be updated on locale change
Component interpolation<i18n-t> with named slots
Multiple formsCustom pluralRules per locale

Key takeaways:

  • legacy: false is mandatory for Composition API. Without it, useI18n() throws and the instance runs in Vue 2 compatibility mode. This is the first configuration to verify when examples fail.
  • Destructure once from useI18n. Calling useI18n multiple times with local scope in the same component creates unsynchronized instances and triggers warnings. Destructure all needed functions from a single call.
  • Dates and numbers go through d() and n(). These delegate to Intl and named format presets, producing correct output per locale without manual formatting logic.
  • Lazy loading keeps the bundle proportional to the user’s language. Import only the active locale at startup, load others through dynamic import() in a router guard, and register them with setLocaleMessage.
  • Pluralization with pipes handles two to three forms. Languages with more categories require a custom pluralRules function that returns the correct form index.
  • The lang attribute on <html> matters. It affects screen reader pronunciation, browser translation prompts, and CJK font selection. Update it whenever the locale changes.

Remember: vue-i18n is not a dictionary that you query. It is a reactive layer that sits between your components and the user’s locale. The instance holds the active language, the message bundles, and the formatting rules. Components call t(), d(), and n(), and when the locale changes, everything that depends on those calls re-renders automatically. The configuration that makes this work is legacy: false. The configuration that keeps it performant is lazy loading. The configuration that makes it correct across languages is the message structure, plural rules, and format presets. Get those right, and internationalization becomes a property of the application rather than a task on the backlog.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!