How to properly internationalize a Vue application using i18next

So first of all: “Why i18next?”

Let’s get into it…

Prerequisites

Getting started

import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
import LanguageDetector from 'i18next-browser-languagedetector'

i18next
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
resources: {
en: {
translation: {
// here we will place our translations...
}
}
}
});

export default function (app) {
app.use(I18NextVue, { i18next })
return app
}
import { createApp } from 'vue'
import i18n from './i18n'
import App from './App.vue'

i18n(createApp(App)).mount('#app')
<template>
<div class="hello">
<h1>{{ $t('welcome') }}</h1>
<p v-html="$t('descr')"></p>
</div>
</template>

<script>
export default {
name: 'TranslationShowCase'
}
</script>
import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
import LanguageDetector from 'i18next-browser-languagedetector'

i18next
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
resources: {
en: {
translation: {
welcome: 'Welcome to Your Vue.js App',
descr: 'For a guide and recipes on how to configure / customize '
+ 'this project,<br>check out the '
+ '<a href="https://cli.vuejs.org" target="_blank" '
+ 'rel="noopener">vue-cli documentation</a>.'
}
}
}
});

export default function (app) {
app.use(I18NextVue, { i18next })
return app
}

Language Switcher

<template>
<div class="hello">
<h1>{{ $t('welcome') }}</h1>
<p v-html="$t('descr')"></p>
<hr />
<div>
<div v-if="languages">
<span v-for="(lng, index) in Object.keys(languages)" :key="lng">
<a v-if="$i18next.resolvedLanguage !== lng" v-on:click="$i18next.changeLanguage(lng)">
{{ languages[lng].nativeName }}
</a>
<strong v-if="$i18next.resolvedLanguage === lng">
{{ languages[lng].nativeName }}
</strong>
<span v-if="index < (Object.keys(languages).length - 1)">&nbsp;|&nbsp;</span>
</span>
</div>
</div>
</div>
</template>

<script>
export default {
name: 'TranslationShowCase',
data () {
return {
languages: {
en: { nativeName: 'English' },
de: { nativeName: 'Deutsch' }
}
}
}
}
</script>
import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
import LanguageDetector from 'i18next-browser-languagedetector'

i18next
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
resources: {
en: {
translation: {
welcome: 'Welcome to Your Vue.js App',
descr: 'For a guide and recipes on how to configure / customize '
+ 'this project,<br>check out the '
+ '<a href="https://cli.vuejs.org" target="_blank" '
+ 'rel="noopener">vue-cli documentation</a>.'
}
},
de: {
translation: {
welcome: 'Willkommen zu Deiner Vue.js App',
descr: 'Eine Anleitung und Rezepte zum Konfigurieren/Anpassen '
+ 'dieses Projekts findest du<br>in der '
+ '<a href="https://cli.vuejs.org" target="_blank" '
+ 'rel="noopener">vue-cli-Dokumentation</a>.'
}
}
}
});

export default function (app) {
app.use(I18NextVue, { i18next })
return app
}

How to get the current language?

i18next.language vs. i18next.languages vs. i18next.resolvedLanguage

/* language */
i18next.language;
// Is set to the current detected or set language.
/* language */
i18next.languages;
// Is set to an array of language codes that will be used to look up the translation value.
// When the language is set, this array is populated with the new language codes.
// Unless overridden, this array is populated with less-specific versions of that code for fallback purposes, followed by the list of fallback languages
// initialize with fallback languages
i18next.init({
fallbackLng: ["es", "fr", "en-US", "dev"]
});
// change the language
i18next.changeLanguage("en-US-xx");
// new language and its more generic forms, followed by fallbacks
i18next.languages; // ["en-US-xx", "en-US", "en", "es", "fr", "dev"]
// change the language again
i18next.changeLanguage("de-DE");
// previous language is not retained
i18next.languages; // ["de-DE", "de", "es", "fr", "en-US", "dev"]
/* resolvedLanguage */
i18next.resolvedLanguage;
// Is set to the current resolved language.
// It can be used as primary used language,
// for example in a language switcher.

Interpolation and Pluralization

Formatting

Context

Separate translations from code

import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
import LanguageDetector from 'i18next-browser-languagedetector'
import Backend from 'i18next-http-backend'
i18next
// i18next-http-backend
// loads translations from your server
// https://github.com/i18next/i18next-http-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en'
});
export default function (app) {
app.use(I18NextVue, { i18next })
return app
}
import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
import LanguageDetector from 'i18next-browser-languagedetector'
import Backend from 'i18next-http-backend'
export const
i18nextPromise = i18next
// i18next-http-backend
// loads translations from your server
// https://github.com/i18next/i18next-http-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en'
});
export default function (app) {
app.use(I18NextVue, { i18next })
return app
}
<template>
<img alt="Vue logo" src="./assets/logo.png">
<TranslationShowCase />
</template>
<script>
import TranslationShowCase from './components/TranslationShowCase.vue'
import { i18nextPromise } from './i18n.js'
export default {
name: 'App',
components: {
TranslationShowCase
},
// used in combination with Suspense.
// useful when translations are not in-memory...
async setup() {
await i18nextPromise
return {}
}
}
</script>
<template>
<Suspense>
<template #default>
<App />
</template>
<template #fallback>
<div>
<img alt="Vue logo" src="./assets/logo.png">
<h1>Loading...</h1>
</div>
</template>
</Suspense>
</template>
<script>
import App from './App.vue'
export default {
name: 'Suspenser',
components: {
App
}
}
</script>
import { createApp } from 'vue'
import i18n from './i18n'
import App from './Suspenser.vue'
i18n(createApp(App)).mount('#app')

Better translation management

For sure!

  • How do you integrate any translation services / agency?
  • How do you keep track of new or removed content?
  • How you handle proper versioning?
  • How you deploy translation changes without deploying your complete application?
  • and a lot more…

How does this look like?

import I18NextVue from 'i18next-vue'
import i18next from 'i18next'
import Backend from 'i18next-locize-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
const locizeOptions = {
projectId: '94c21299-0cf5-4ad3-92eb-91f36fc3f20f',
apiKey: 'bc8586d9-fceb-489c-86ac-2985393ed955', // YOU should not expose your apps API key to production!!!
version: 'latest'
}
export const
i18nextPromise = i18next
// i18next-locize-backend
// loads translations from your project, saves new keys to it (saveMissing: true)
// https://github.com/locize/i18next-locize-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
backend: locizeOptions
})
export default function (app) {
app.use(I18NextVue, { i18next })
return app
}
<template>
<div class="hello">
<h1>{{ $t('welcome') }}</h1>
<p v-html="$t('descr')"></p>
<i>{{ $t('new.key', 'this will be added automatically') }}</i>
<hr />
<div>
<div v-if="languages">
<span v-for="(lng, index) in Object.keys(languages)" :key="lng">
<a v-if="$i18next.resolvedLanguage !== lng" v-on:click="$i18next.changeLanguage(lng)">
{{ languages[lng].nativeName }}
</a>
<strong v-if="$i18next.resolvedLanguage === lng">
{{ languages[lng].nativeName }}
</strong>
<span v-if="index < (Object.keys(languages).length - 1)">&nbsp;|&nbsp;</span>
</span>
</div>
</div>
</div>
</template>
<script>
import i18next from 'i18next'
export default {
name: 'TranslationShowCase',
data () {
return {
languages: []
}
},
async mounted () {
this.languages = await i18next.services.backendConnector.backend.getLanguages()
}
}
</script>

save missing translations

import I18NextVue from 'i18next-vue'
import i18next from 'i18next'
import Backend from 'i18next-locize-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
const locizeOptions = {
projectId: '94c21299-0cf5-4ad3-92eb-91f36fc3f20f',
apiKey: 'bc8586d9-fceb-489c-86ac-2985393ed955', // YOU should not expose your apps API key to production!!!
version: 'latest'
}
export const
i18nextPromise = i18next
// i18next-locize-backend
// loads translations from your project, saves new keys to it (saveMissing: true)
// https://github.com/locize/i18next-locize-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
backend: locizeOptions,
saveMissing: true
})
export default function (app) {
app.use(I18NextVue, { i18next })
return app
}
<i>{{ $t('new.key', 'this will be added automatically') }}</i>

👀 but there’s more…

import I18NextVue from 'i18next-vue'
import i18next from 'i18next'
import Backend from 'i18next-locize-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
import LastUsed from 'locize-lastused'
import { locizePlugin } from 'locize'
const locizeOptions = {
projectId: '94c21299-0cf5-4ad3-92eb-91f36fc3f20f',
apiKey: 'bc8586d9-fceb-489c-86ac-2985393ed955', // YOU should not expose your apps API key to production!!!
version: 'latest'
}
export const
i18nextPromise = i18next
// locize-lastused
// sets a timestamp of last access on every translation segment on locize
// -> safely remove the ones not being touched for weeks/months
// https://github.com/locize/locize-lastused
.use(LastUsed)
// locize-editor
// InContext Editor of locize
.use(locizePlugin)
// i18next-locize-backend
// loads translations from your project, saves new keys to it (saveMissing: true)
// https://github.com/locize/i18next-locize-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: true,
fallbackLng: 'en',
saveMissing: true,
backend: locizeOptions,
locizeLastUsed: locizeOptions
})
export default function (app) {
app.use(I18NextVue, { i18next })
return app
}

📦 Let’s prepare for production 🚀

VUE_APP_LOCIZE_PROJECTID=94c21299-0cf5-4ad3-92eb-91f36fc3f20f
VUE_APP_LOCIZE_VERSION=latest
VUE_APP_LOCIZE_APIKEY=bc8586d9-fceb-489c-86ac-2985393ed955
VUE_APP_LOCIZE_VERSION=production
import I18NextVue from 'i18next-vue'
import i18next from 'i18next'
import Backend from 'i18next-locize-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
import LastUsed from 'locize-lastused'
import { locizePlugin } from 'locize'
const isProduction = process.env.NODE_ENV === 'production'const locizeOptions = {
projectId: process.env.VUE_APP_LOCIZE_PROJECTID,
apiKey: process.env.VUE_APP_LOCIZE_APIKEY, // YOU should not expose your apps API key to production!!!
version: process.env.VUE_APP_LOCIZE_VERSION
}
if (!isProduction) {
// locize-lastused
// sets a timestamp of last access on every translation segment on locize
// -> safely remove the ones not being touched for weeks/months
// https://github.com/locize/locize-lastused
i18next.use(LastUsed);
}
export const
i18nextPromise = i18next
// locize-editor
// InContext Editor of locize
.use(locizePlugin)
// i18next-locize-backend
// loads translations from your project, saves new keys to it (saveMissing: true)
// https://github.com/locize/i18next-locize-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
debug: !isProduction,
fallbackLng: 'en',
saveMissing: !isProduction,
backend: locizeOptions,
locizeLastUsed: locizeOptions
})
export default function (app) {
app.use(I18NextVue, { i18next })
return app
}

🎉🥳 Congratulations 🎊🎁

👍

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Adriano Raiano

Adriano Raiano

46 Followers

founder of locize.com; Software Architect, Bachelor in Computer Science #serverless #nodejs #cqrs #ddd always in search for #innovative and #disruptive stuff