Setting Up i18n for Localization in Vue.js
Written on: December, 2024
•5 min readLocalization and internationalization are essential for creating multilingual websites. They help you reach a broader audience by providing content in different languages. In this blog post, I'll show you how to set up i18n in a Vue.js project using the Vue I18n library. By following these steps, you can easily localize your Vue.js applications and provide a better user experience for international users.
Steps
- Setting up Vue.js Project
- Installing Vue I18n
- Setting Up i18n
- Integrating i18n with Vue
- Creating Language Files
- Configuring i18n as a global plugin
- Using i18n in Vue Components
- Switching Between Languages
- Unit Testing Components
- Test Suite
First, ensure you have a Vue.js project set up. If not, I recommend using my Vue.js starter template to quickly scaffold a new project. You can find the template on GitHub at the following link:
https://github.com/Aldikrasniqi/vue-starter.gitNext, install the Vue I18n library in your project. Run the following command in your terminal to add Vue I18n as a dependency:
https://vue-i18n.intlify.dev/guide/installation.html1npm install vue-i18n
In the src/i18n.ts file, create a function called messages that dynamically loads language JSON files from the src/locales folder. After that, create an i18n instance which accepts the legacy option (a boolean that indicates whether to use legacy API or composition API), a locale option (the default language of the application), a fallbackLocale option (the fallback language if the translation is not found in the current language), and a messages option (the language files loaded in the messages function).
1import { createI18n } from 'vue-i18n'
2
3const messages = Object.fromEntries(
4 Object.entries(import.meta.glob('./locales/*.json', { eager: true })).map(([key, value]) => {
5 const locale = key.match(/\.\/locales\/(.*)\.json$/)?.[1]
6 return [locale, (value as any).default]
7 })
8)
9
10export const i18n = createI18n({
11 legacy: false, // Use Composition API
12 locale: 'en', // Default locale
13 fallbackLocale: 'en', // Fallback locale
14 messages
15})
16
In the src/main.ts file, the i18n instance is registered with the Vue application:
1import { createPinia } from 'pinia'
2import { i18n } from './i18n'
3import { createApp } from 'vue'
4import App from './App.vue'
5import lang from './plugins/lang'
6import router from './router'
7import './assets/index.css'
8
9const app = createApp(App)
10
11app.use(createPinia())
12app.use(router)
13app.use(i18n)
14app.use(lang)
15app.mount('#app')
16
Create language files for each supported language in the src/locales folder. Each language file should contain key-value pairs of translation strings.
1{
2 "home": "This is the home page",
3 "about": "This is the about page"
4}
5
In the src/plugins folder, create a file named lang.ts to configure i18n as a global plugin. This file exports a function that installs the i18n instance on the Vue app.
1// src/plugins/lang.ts
2import { i18n } from '@/i18n'
3
4export default {
5 install: (app: any) => {
6 app.config.globalProperties.$t = i18n.global.t
7 app.config.globalProperties.$locale = i18n.global.locale
8
9 const savedLocale = localStorage.getItem('locale') || 'en'
10 i18n.global.locale.value = savedLocale
11 }
12}
You can now use i18n in your Vue components to display localized text. Use the $t method to access translation strings based on their keys.

To switch between languages, create a language switcher component that changes the locale of the i18n instance. This component can be placed in the header or footer of your application.
1<script lang="ts" setup>
2import { useGlobalI18n } from '@/composables/useGlobalI18n'
3import { languages } from '@/utils/general'
4import { ref } from 'vue'
5
6const { locale } = useGlobalI18n()
7const dropdownVisible = ref(false)
8const currentLocale = ref(locale.value)
9
10function changeLanguage(lang: string) {
11 locale.value = lang
12 currentLocale.value = lang
13 dropdownVisible.value = false
14}
15
16function toggleDropdown() {
17 dropdownVisible.value = !dropdownVisible.value
18}
19</script>
20
21<template>
22 <div class="relative">
23 <button
24 class=""
25 @click="toggleDropdown"
26 >
27 <span>{{ languages.find(lang => lang.code === locale)?.label }}</span>
28 <svg
29 xmlns="http://www.w3.org/2000/svg"
30 class="w-5 h-5 ml-2"
31 fill="none"
32 viewBox="0 0 24 24"
33 stroke="currentColor"
34 stroke-width="2"
35 >
36 <path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" />
37 </svg>
38 </button>
39 <div
40 v-show="dropdownVisible"
41 class="absolute right-0 mt-2 w-36 bg-white border border-gray-200 rounded-lg shadow-lg dark:bg-gray-800 dark:border-gray-700"
42 >
43 <ul>
44 <li
45 v-for="language in languages"
46 :key="language.code"
47 @click="changeLanguage(language.code)"
48 class="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 cursor-pointer"
49 >
50 {{ language.label }}
51 </li>
52 </ul>
53 </div>
54 </div>
55</template>
For testing, I use the Vitest library, a simple and lightweight testing library for Vue.js applications. It provides a clean and easy-to-use API for writing unit tests for Vue components. If you don't have it installed, you can install it with the following command: npm install @vitest/vue. First, create a test file for the component you want to test. In the following code, I've imported the AboutView component, mocked some language data, and created an instance of i18n with the locale set to 'en' and mocked languages.
1import { describe, it, expect } from 'vitest'
2import { mount } from '@vue/test-utils'
3import { createI18n } from 'vue-i18n'
4import AboutView from '@/views/AboutView.vue'
5
6// Mock translations
7const messages = {
8 en: { about: 'About Us' },
9 sv: { about: 'Om Oss' }
10}
11
12const i18n = createI18n({
13 locale: 'en',
14 messages
15})
I've created a test suite with the describe function and a test case with the it function. In the test case, I've mounted the AboutView component with the i18n instance and checked if the component renders the translated text correctly. Finally, I've run the test suite with the test function.
1describe('About Component', () => {
2 it('renders translated text', () => {
3 const wrapper = mount(AboutView, {
4 global: {
5 plugins: [i18n]
6 }
7 })
8
9 expect(wrapper.text()).toContain('About Us')
10 })
11
12 it('updates text when locale changes', async () => {
13 const wrapper = mount(AboutView, {
14 global: {
15 plugins: [i18n]
16 }
17 })
18
19 expect(wrapper.text()).toContain('About Us')
20
21 i18n.global.locale = 'sv'
22 await wrapper.vm.$nextTick()
23
24 // Verify the updated text
25 expect(wrapper.text()).toContain('Om Oss')
26 })
27})
28