LocalePack
ChromeFirefoxEdgeOperaSafariCWS Listing
Vue.jsReact
Next.jsi18nextReact Native
Guides
Home/Guides/vue-i18n locale files
March 24, 2026

vue-i18n locale files: JSON, YAML, pipe plurals, and {named} placeholders

vue-i18n is the standard i18n library for Vue 3 (and Nuxt via @nuxtjs/i18n). It supports JSON and YAML locale files, pipe-separated plurals, named placeholders, and linked messages. Here is how each feature works — and the mistakes that trip people up.

Folder structure

The most common layout is a locales/ directory with one file per locale. Each file contains every translation key for that language:

my-vue-app/
├── src/
│   ├── i18n.ts          ← createI18n() setup
│   └── ...
├── locales/
│   ├── en.json          ← English (source)
│   ├── de.json          ← German
│   ├── fr.json          ← French
│   └── ja.json          ← Japanese
└── package.json

In your i18n.ts setup file, import each locale and pass it to createI18n():

import { createI18n } from 'vue-i18n'
import en from '../locales/en.json'
import de from '../locales/de.json'

const i18n = createI18n({
  locale: 'en',
  fallbackLocale: 'en',
  messages: { en, de },
})

export default i18n
Nuxt projects using @nuxtjs/i18n auto-discover locale files from a configured directory — you typically only set the path in nuxt.config.ts.

Basic JSON format

Locale files can use flat keys or nested objects. vue-i18n resolves nested keys with dot notation:

{
  "hello": "Hello!",
  "nav": {
    "home": "Home",
    "about": "About us",
    "contact": "Contact"
  },
  "footer": {
    "copyright": "© 2026 My App"
  }
}

In a Vue template, use $t() to access any key:

<template>
  <h1>{{ $t('hello') }}</h1>
  <nav>
    <a href="/">{{ $t('nav.home') }}</a>
    <a href="/about">{{ $t('nav.about') }}</a>
  </nav>
  <footer>{{ $t('footer.copyright') }}</footer>
</template>

{named} placeholders

vue-i18n uses single curly braces for named placeholders — not double braces like some other libraries:

{
  "welcome": "Welcome, {name}!",
  "itemsInCart": "You have {count} items in your cart.",
  "orderConfirm": "Order #{orderId} confirmed for {email}."
}

Pass the values as the second argument to $t():

<template>
  <p>{{ $t('welcome', { name: 'Alice' }) }}</p>
  <!-- Output: "Welcome, Alice!" -->

  <p>{{ $t('itemsInCart', { count: 3 }) }}</p>
  <!-- Output: "You have 3 items in your cart." -->
</template>
Single braces only. Writing {{name}} (double braces) will not work — vue-i18n expects {name}. Double braces are Vue template syntax, not vue-i18n syntax.

Pipe-separated plurals

vue-i18n handles pluralization by splitting a single string on the pipe character |. The segments map to plural forms based on the count:

{
  "car": "car | cars",
  "apple": "no apples | one apple | {count} apples",
  "item": "no items | one item | {n} items"
}

How the segments are resolved:

  • •Two segments ("car | cars"): first for count 1, second for everything else.
  • •Three segments ("no apples | one apple | {count} apples"): first for 0, second for 1, third for 2+.

Use $t() with a count parameter or the legacy $tc() helper:

<template>
  <!-- Vue I18n v9+ (Composition API or Legacy) -->
  <p>{{ $t('apple', { count: 0 }) }}</p>
  <!-- Output: "no apples" -->

  <p>{{ $t('apple', { count: 1 }) }}</p>
  <!-- Output: "one apple" -->

  <p>{{ $t('apple', { count: 5 }) }}</p>
  <!-- Output: "5 apples" -->

  <!-- Legacy $tc() — still works but $t() with plural is preferred -->
  <p>{{ $tc('car', 2) }}</p>
  <!-- Output: "cars" -->
</template>
The special placeholder {n} is automatically injected with the count value. You can also use any named placeholder like {count} and pass it explicitly.

YAML support

vue-i18n supports YAML locale files alongside JSON. The structure is identical — only the syntax differs. YAML is popular for its readability and lack of trailing-comma issues:

# locales/en.yaml
hello: Hello!
nav:
  home: Home
  about: About us
  contact: Contact
welcome: "Welcome, {name}!"
car: car | cars
apple: "no apples | one apple | {count} apples"

To use YAML, install @intlify/unplugin-vue-i18n (or the Vite/Webpack plugin) which handles YAML parsing at build time. Nuxt’s @nuxtjs/i18n supports YAML out of the box.

YAML quoting rule: strings containing pipes, colons, or curly braces should be quoted. Without quotes, car: car | cars works, but welcome: Welcome, {name}! will fail because of the braces. Always wrap complex values in double quotes.

Linked locale messages

The @:key syntax lets one message reference another, reducing duplication:

{
  "the_world": "the world",
  "dio": "DIO",
  "greeting": "hello, @:the_world!",
  "villainGreeting": "@:dio says @:greeting"
}

$t('greeting') resolves to "hello, the world!". You can also apply modifiers to change the case of linked text:

{
  "homeAddress": "Home address",
  "missing498": "Please provide @.lower:homeAddress",
  "heading": "@.upper:homeAddress",
  "label": "@.capitalize:homeAddress"
}
  • •@.upper:key — converts the linked value to uppercase.
  • •@.lower:key — converts the linked value to lowercase.
  • •@.capitalize:key — capitalizes the first letter.

SFC <i18n> blocks

Vue Single File Components can embed translations directly using a custom <i18n> block. This keeps component-specific translations co-located with the component:

<template>
  <h1>{{ $t('title') }}</h1>
  <p>{{ $t('description') }}</p>
</template>

<script setup>
// component logic
</script>

<i18n>
{
  "en": {
    "title": "My Component",
    "description": "This component does something useful."
  },
  "de": {
    "title": "Meine Komponente",
    "description": "Diese Komponente macht etwas Nützliches."
  }
}
</i18n>

The <i18n> block also supports YAML by adding lang="yaml":

<i18n lang="yaml">
en:
  title: My Component
  description: This component does something useful.
de:
  title: Meine Komponente
  description: Diese Komponente macht etwas Nützliches.
</i18n>
SFC <i18n> blocks require the @intlify/unplugin-vue-i18n Vite or Webpack plugin. Without it the block is silently ignored.

Common mistakes

Forgetting spaces around pipes

Writing "car|cars" instead of "car | cars". vue-i18n requires a space before and after each pipe for plural splitting. Without spaces the entire string is treated as a single form.

Using double braces instead of single

Writing {{name}} in the locale file. vue-i18n uses {name} (single braces). Double braces are Vue template interpolation, not vue-i18n syntax.

Mixing $t and $tc incorrectly

In vue-i18n v9+, $t() handles pluralization when you pass a count parameter. The legacy $tc() still works but mixing the two in the same project leads to confusion. Pick one and be consistent.

YAML indentation errors

YAML is whitespace-sensitive. Using tabs instead of spaces, or inconsistent indentation, silently breaks nested keys. Always use 2-space indentation and validate with a YAML linter.

Unquoted YAML values with special characters

YAML values containing {, :, or # must be quoted. Without quotes, welcome: Welcome, {name}! is a YAML parse error.

vue-i18n format at a glance

Featurevue-i18n
File pathlocales/en.json or locales/en.yaml
Interpolation{named} single-brace placeholders
PluralsPipe-separated: "car | cars"
Linked messages@:key and @.modifier:key
SFC support<i18n> blocks in .vue files
YAML supportYes, via @intlify/unplugin-vue-i18n
Nested keysYes, dot-notation access

Translate your vue-i18n locale files with LocalePack

LocalePack preserves pipe plurals (car | cars), keeps every {named} placeholder intact, and supports both JSON and YAML. Upload your source locale, select target languages, and download ready-to-use translations. Pay once, no subscription.

Translate vue-i18n files →

Related guides

i18next JSON format: namespaces, plurals, and interpolation

The i18next locale file reference — namespace-based splitting, _plural suffixes, and {{interpolation}} syntax compared to vue-i18n.

Next.js i18n: next-intl vs react-i18next

A side-by-side comparison of the two leading Next.js i18n libraries — useful if your Vue project also has a Next.js counterpart.

← Back to Guides
LocalePack
GuidesPrivacyTermsSupport

© 2025 LocalePack. All rights reserved.

This project was translated with LocalePack logoLocalePack