Add Vue.js SPA Language Switcher i18n – Extend Your Laravel Vue Project Easily

In this follow-up tutorial, we will extend our previous article “Create Vue.js SPA in Laravel Project – Step-by-Step Guide for Beginners” and add multi-language support to the same project. In this post, you will learn how to set up vue-i18n and build a vue.js spa language switcher i18n feature that changes language instantly—without refreshing the page and without calling backend Laravel translations.

This is a small but very powerful upgrade because users expect multi-language support in real world dashboards, admin panels, POS, SaaS apps, or internal business tools. With vue-i18n, you can fully translate content inside Vue components directly inside frontend.

Why vue-i18n is the best choice for Vue SPAs

vue-i18n is the official internationalization library for Vue. It is:

  • Lightweight
  • Works perfect in SPA
  • No backend integration required
  • Language switching is instant (no request to server)

And the most important:

You can store translations directly inside Vue.

Which is what we want.

Step 1: Check Out the Previous Tutorial First

Before starting this guide, make sure you’ve already completed the setup from the previous post: Create Vue.js SPA in Laravel Project – Step-by-Step Guide for Beginners.
👉 This tutorial builds on top of that project structure, so having it ready will help you continue smoothly.

You can read it here: https://laravelcenter.com/laravel-12-vue-3-session-based-authentication/

Step 2: Install vue-i18n Library

In this step, we install the vue-i18n library which is the main plugin used to power the vue.js spa language switcher i18n feature. vue-i18n provides a simple API for handling multi-language text inside any Vue SPA project.
Official website → https://vue-i18n.intlify.dev/

npm install vue-i18n@next

Step 3: Create translation file

Next, we create a translation file that stores our multi-language strings. These key/value pairs are required to make the vue.js spa language switcher i18n work properly, and this file will later be loaded into Vue when we register the i18n instance.

Create a file /resources/js/i18n.js:

import { createI18n } from 'vue-i18n'

const messages = {
    en: {
        'home': 'Home',
        'blog': 'Blog',
        'about us': 'About Us',
        'contact us': 'Contact Us',
        'message': 'Welcome to Vue.js SPA in Laravel!'
    },
    kh: {
        'home': 'ទំព័រដើម',
        'blog': 'ទំព័រប្លុក',
        'about us': 'អំពីពួកយើង',
        'contact us': 'ទាក់ទងយើង',
        'message': 'សូមស្វាគមន៍​មកកាន់ Vue.js SPA ក្នុង Laravel!'
    },
    jp: {
        'home': 'ホームページ',
        'blog': 'ブログページ',
        'about us': '私たちについて',
        'contact us': 'お問い合わせ',
        'message': 'Laravel の Vue.js SPA へようこそ!'
    }
}

const i18n = createI18n({
    locale: localStorage.getItem('lang') || 'en',
    fallbackLocale: 'en',
    legacy: false,
    messages
})

export default i18n

Step 4: Register i18n in app.js

In this step, we import and configure vue-i18n in the main app.js entry point. This setup allows the vue.js spa language switcher i18n to become active throughout the entire SPA so all components can switch languages instantly.

Open /resources/js/app.js:

import './bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import i18n from './i18n';

createApp(App)
    .use(router)
    .use(i18n)
    .mount('#app');

Step 5: Display Translated Text in Vue Component

Now we update one of our Vue page components and use the $t() helper to display translation strings. This is where we confirm that the vue.js spa language switcher i18n is working correctly and the language content updates in real time.

In this step, we will update our existing Vue pages and integrate the translation keys so the text can be switched dynamically.

Home.vue

<template>
    <div>
        <h2>{{ $t('home') }}</h2>
        <p>{{ $t('message') }}</p>
    </div>
</template>

Blog.vue

<template>
    <div>
        <h2>{{ $t('blog') }}</h2>
        <p>{{ $t('message') }}</p>
    </div>
</template>

About.vue

<template>
    <div>
        <h2>{{ $t('about us') }}</h2>
        <p>{{ $t('message') }}</p>
    </div>
</template>

Contact.vue

<template>
    <div>
        <h2>{{ $t('contact us') }}</h2>
        <p>{{ $t('message') }}</p>
    </div>
</template>

Layout.vue

<template>
    <div>
        <header
            class="text-bg-success d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom">
            <div class="col-md-4 mb-2 mb-md-0 d-flex">
                <a href="/" class="d-inline-flex link-body-emphasis text-decoration-none">
                    <strong class="fs-3 ms-2 text-white">Laravel Center</strong>
                </a>
                <ul class="nav nav-pills col-md-auto mt-2 justify-content-end mb-md-0 ms-md-5">
                    <li class="nav-item" v-for="availableLocale in availableLocales">
                        <button @click="setLang(availableLocale)"
                            :class="['nav-link text-white py-1 px-2 mx-1', { 'active fw-bold bg-dark': locale == availableLocale }]">
                            {{ availableLocale.toUpperCase() }}
                        </button>
                    </li>
                </ul>
            </div>
            <ul class="nav nav-underline col-12 col-md-auto mb-2 justify-content-end mb-md-0">
                <li class="nav-item">
                    <RouterLink to="/" class="nav-link mx-3 text-white" exact-active-class="active">
                        {{ $t('home') }}
                    </RouterLink>
                </li>
                <li class="nav-item">
                    <RouterLink to="/blog" class="nav-link mx-3 text-white" active-class="active">
                        {{ $t('blog') }}
                    </RouterLink>
                </li>
                <li class="nav-item">
                    <RouterLink to="/about" class="nav-link mx-3 text-white" active-class="active">
                        {{ $t('about us') }}
                    </RouterLink>
                </li>
                <li class="nav-item">
                    <RouterLink to="/contact" class="nav-link mx-3 text-white" active-class="active">
                        {{ $t('contact us') }}
                    </RouterLink>
                </li>
            </ul>
        </header>
        <div class="container mt-5">
            <router-view></router-view>
        </div>
    </div>
</template>
<script setup>
import { useI18n } from 'vue-i18n';

// Get the locale ref and availableLocales from the global scope
const { locale, availableLocales } = useI18n({ useScope: 'global' });

const setLang = (lang) => {
    locale.value = lang
    localStorage.setItem('lang', lang)
}
</script>

Test It

Switch language from the dropdown → the welcome text changes instantly.

This confirms vue-i18n is working correctly in our Vue.js SPA.

Why this is better than Laravel translations for SPA

Featurevue-i18n in SPALaravel translations
Page reload required?❌ No✅ Yes
Works offline (PWA)✅ Yes✅ Yes
Setup complexityLowMedium
Best for Vue-only UI✅ Yes❌ No
SEO friendly? (SPA)Not needed (JS)Not relevant

Since our SPA renders text fully on frontend, vue-i18n is the perfect match.

Conclusion

By following these steps, we successfully implemented the vue.js spa language switcher i18n solution inside our Laravel-based Vue SPA. This upgrade improves user experience and adds real-world value for any multilingual project, dashboard, or frontend application.

Senghok
Senghok

Senghok is a web developer who enjoys working with Laravel and Vue.js. He creates easy-to-follow tutorials and guides to help beginners learn step by step. His goal is to make learning web development simple and fun for everyone.

Articles: 44

Newsletter Updates

Enter your email address below and subscribe to our newsletter

Leave a Reply

Your email address will not be published. Required fields are marked *