Component Interpolation

Basic Usage

Sometimes, we need to localize with a locale message that was included in a HTML tag or component. For example:

<p>I accept xxx <a href="/term">Terms of Service Agreement</a></p>

In the above message, if you use $t, you will probably try to compose the following locale messages:

const messages = {
  en: {
    term1: 'I Accept xxx\'s',
    term2: 'Terms of Service Agreement'
  }
}

And your localized template may look like this:

<p>{{ $t('term1') }}<a href="/term">{{ $t('term2') }}</a></p>

Output:

<p>I accept xxx <a href="/term">Terms of Service Agreement</a></p>

This is very cumbersome, and if you configure the <a> tag in a locale message, there is a possibility of XSS vulnerabilities due to localizing with v-html="$t('term')".

You can avoid it using the Translation component (i18n-t). For example the below.

Template:

<div id="app">
  <!-- ... -->
  <i18n-t keypath="term" tag="label" for="tos">
    <a :href="url" target="_blank">{{ $t('tos') }}</a>
  </i18n-t>
  <!-- ... -->
</div>

JavaScript:

import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'

const i18n = createI18n({
  locale: 'en',
  messages: {
    en: {
      tos: 'Term of Service',
      term: 'I accept xxx {0}.'
    },
    ja: {
      tos: '利用規約',
      term: '私は xxx の{0}に同意します。'
    }
  }
})

const app = createApp({
  data: () => ({ url: '/term' })
})

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

The following output:

<div id="app">
  <!-- ... -->
  <label for="tos">
    I accept xxx <a href="/term" target="_blank">Term of Service</a>.
  </label>
  <!-- ... -->
</div>

About the above example, see the example

The children of translation component are interpolated with locale message of keypath prop. In the above example,

<a :href="url" target="_blank">{{ $t('tos') }}</a>

Is interpolated with term locale message.

In the above example, the component interpolation follows the list interpolation. The children of translation component are interpolated by their order of appearance.

You can choose the root node element type by specifying a tag prop. If omitted, it defaults to Fragments.

Slots syntax usage

It’s more convenient to use the named slots syntax. For example the below:

Template:

<div id="app">
  <!-- ... -->
  <i18n-t keypath="info" tag="p">
    <template v-slot:limit>
      <span>{{ changeLimit }}</span>
    </template>
    <template v-slot:action>
      <a :href="changeUrl">{{ $t('change') }}</a>
    </template>
  </i18n-t>
  <!-- ... -->
</div>

JavaScript:

import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'

const i18n = createI18n({
  locale: 'en',
  messages: {
    en: {
      info: 'You can {action} until {limit} minutes from departure.',
      change: 'change your flight',
      refund: 'refund the ticket'
    }
  }
})

const app = createApp({
  data: () => ({
    changeUrl: '/change',
    refundUrl: '/refund',
    changeLimit: 15,
    refundLimit: 30
  })
})

app.use(i18)
app.mount('#app')

Outputs:

<div id="app">
  <!-- ... -->
  <p>
    You can <a href="/change">change your flight</a> until <span>15</span> minutes from departure.
  </p>
  <!-- ... -->
</div>

You can also use the following slots shorthand in templates:

<div id="app">
  <!-- ... -->
  <i18n-t keypath="info" tag="p">
    <template #limit>
      <span>{{ changeLimit }}</span>
    </template>
    <template #action>
      <a :href="changeUrl">{{ $t('change') }}</a>
    </template>
  </i18n-t>
  <!-- ... -->
</div>

LIMITATION

⚠️ In translation component, slots props are not supported.

Pluralization Usage

You can use pluralization in Component interpolation by use plural prop. For example the below.

Template:

<div id="app">
  <!-- ... -->
  <i18n-t keypath="message.plural" locale="en" :plural="count">
    <template #n>
      <b>{{ count }}</b>
    </template>
  </i18n-t>
  <!-- ... -->
</div>

JavaScript:

const { createApp, ref } = Vue
const { createI18n } = VueI18n

const i18n = createI18n({
  legacy: false,
  locale: 'en',
  messages: {
    en: {
      message: {
        plural: 'no bananas | {n} banana | {n} bananas'
      }
    }
  }
})

const app = createApp({
  setup() {
    const count = ref(2)
    return { count }
  }
})
app.use(i18n)
app.mount('#app')

The following output:

<div id="app" data-v-app="">
  <!-- ... -->
  <b>2</b> bananas
  <!-- ... -->
</div>