Vue, vue-i18n and translations in Sleekdocs

vue-i18n has no loader of its own, but a few lines of fetch are enough to load a Vue app's texts from the files Sleekdocs Translate deploys. Translators can then change texts without a new build of the app.

What you need

  • A Vue 3 app. This guide uses Vite, Vue 3.5 and vue-i18n 11.
  • A Sleekdocs Translate project with at least one language deployed to an environment.

Sleekdocs deploys each language to its own file on the CDN, one per environment:

https://cdn.sleekdocs.com/<project>/<environment>/<language>.json

The environment is dev, test or prod, and the language is its code, such as sv. Sleekdocs Translate shows the address of your own project when you deploy.

Create the app and install vue-i18n

Skip the first two lines if you already have an app.

npm create vite@latest my-app -- --template vue-ts
cd my-app
npm install vue-i18n

One address per environment

Vite reads .env.development when you run npm run dev and .env.production when you run npm run build:

# .env.development
VITE_TRANSLATIONS_URL=https://cdn.sleekdocs.com/<project>/dev/

# .env.production
VITE_TRANSLATIONS_URL=https://cdn.sleekdocs.com/<project>/prod/

Plain texts, not vue-i18n's message syntax

vue-i18n reads every text as a small template language: @ links to another message, | separates plural forms, and { starts a placeholder. A translator who writes Contact us at support@example.com stops the app with a compile error, and Save | Cancel silently shows only Save.

Texts that come from translators are safer as plain text. A message compiler of a few lines fills in {name} placeholders and leaves everything else as it is written.

Configure vue-i18n and load the texts

Create src/i18n.ts:

import { createI18n, type MessageCompiler, type MessageContext } from 'vue-i18n';

// Texts are plain: {name} is filled in, and @ and | are just characters.
const plainText: MessageCompiler = (message) => (ctx: MessageContext) =>
  String(message).replace(/\{(\w+)\}/g, (_, name) => String(ctx.named(name)));

export const i18n = createI18n({
  legacy: false,
  locale: 'en',
  fallbackLocale: 'en',
  messageCompiler: plainText,
  messages: {},
});

export async function setLanguage(lang: string) {
  if (!i18n.global.availableLocales.includes(lang)) {
    const response = await fetch(`${import.meta.env.VITE_TRANSLATIONS_URL}${lang}.json`);
    if (!response.ok) {
      throw new Error(`No texts for ${lang}: ${response.status}`);
    }
    i18n.global.setLocaleMessage(lang, await response.json());
  }
  i18n.global.locale.value = lang;
  document.documentElement.lang = lang;
}

setLanguage fetches each language once and switches to it. A deployed file only holds the texts that are translated, so fallbackLocale is your project's primary language: a key that's missing in Swedish is shown in English.

In src/main.ts, load the first language before the app mounts:

import { createApp } from 'vue';
import App from './App.vue';
import { i18n, setLanguage } from './i18n';

await setLanguage('en');
createApp(App).use(i18n).mount('#app');

Use the texts

<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { setLanguage } from './i18n';

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

<template>
  <h1>{{ t('checkout.title') }}</h1>
  <p>{{ t('cart.items', { count: 3 }) }}</p>
  <button @click="setLanguage('en')">English</button>
  <button @click="setLanguage('sv')">Svenska</button>
</template>

With a text like You have {count} items, the second line shows You have 3 items. vue-i18n finds checkout.title in both flat and nested files, so either of the project's file formats works; Flat or nested JSON for translation files? explains the difference.

New texts reach the app without a build

When a translator publishes a language and deploys it, the next start of the app reads the new file. The CDN lets browsers reuse a file for up to an hour; pass { cache: 'no-cache' } to fetch to make the browser check for a newer file each time. Deploy translations without rebuilding your app describes publishing, deploying and rolling back.

Try it

Create a team, import the JSON files your app already has, and deploy them to development. Every new team gets 14 days of Pro. Get started, or read how Sleekdocs Translate works.