Skip to content
Go back

Scalable Multi-Language System - Bridging Laravel and Vue-i18n

Published:  at  05:00 PM

Seamless language switching across unlimited languages demonstrated with Portuguese and English

Modern full-stack applications face a translation challenge: maintaining separate files for Laravel (backend) and Vue.js (frontend) creates duplication and sync issues. The Geoglify project solves this with a dual translation system that bridges both worlds.

The Problem

Building Laravel + Vue.js SPAs with Inertia.js requires translations in two places:

Common issues: duplication, sync drift, persistence challenges, and determining initial language.

The Solution

A synchronized dual system with matching structure:

Backend: lang/en/global.php  ↔️  Frontend: resources/js/translations/en.json

Core Architecture

1. Parallel Translation Files

Both use identical key structures:

// Laravel: lang/en/global.php
return ['auth' => ['login_title' => 'Sign In']];
// Vue: translations/en.json
{"global": {"auth": {"login_title": "Sign In"}}}

2. Language Switcher Component

A reusable Vue component with country flags that:

<script setup>
import { useI18n } from 'vue-i18n';
import { router } from '@inertiajs/vue3';

const { locale } = useI18n();

const changeLocale = (newLocale) => {
  locale.value = newLocale;
  router.post('/locale/set', { locale: newLocale }, {
    preserveState: true,
    preserveScroll: true
  });
};
</script>

3. Laravel Backend Persistence

// LocaleController
public function set(Request $request)
{
    $locale = $request->input('locale');
    session(['locale' => $locale]);
    app()->setLocale($locale);
    return back();
}

4. Inertia.js Integration

Share locale with every Vue request:

// HandleInertiaRequests middleware
public function share(Request $request): array
{
    return [
        'locale' => session('locale', app()->getLocale()),
    ];
}

Initialize Vue with server locale:

// app.js
const locale = props.initialPage.props.locale || 'en';
i18n.global.locale.value = locale;

Key Benefits

Automated Synchronization

The key to maintaining consistency is automation. Instead of manually duplicating translations, use a sync script:

// lang/generate-translations.php
$langDir = __DIR__ . '/../lang';
$outputDir = __DIR__ . '/../resources/js/translations';

// Create output directory if it doesn't exist
if (!is_dir($outputDir)) {
    mkdir($outputDir, 0755, true);
}

// Read all folders inside lang (each folder is a locale)
$locales = array_filter(scandir($langDir), function($dir) use ($langDir) {
    return $dir !== '.' && $dir !== '..' && is_dir($langDir . '/' . $dir);
});

foreach ($locales as $locale) {
    $translations = [];
    $langPath = $langDir . '/' . $locale;

    // Read all PHP files in the folder
    $files = glob($langPath . '/*.php');
    foreach ($files as $file) {
        $filename = basename($file, '.php');
        $content = require $file;
        if (is_array($content)) {
            $translations[$filename] = $content;
        }
    }

    // Save as JSON
    $jsonFile = $outputDir . '/' . $locale . '.json';
    file_put_contents($jsonFile, json_encode($translations, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));

    echo "Generated: $jsonFile\n";
}

Add to package.json:

{
  "scripts": {
    "i18n": "php lang/generate-translations.php",
  }
}

Usage:

# One-time generation
npm run i18n

Now you maintain translations only in PHP files and automatically generate JSON! The watch command even rebuilds automatically when you edit PHP translations during development.

Implementation Tips

Flag Icons: Use country-flag-icons for lightweight SVG flags:

import US from 'country-flag-icons/string/3x2/US';
const usFlag = `data:image/svg+xml;base64,${btoa(US)}`;

Performance: Load only necessary translations. Consider lazy-loading for large apps.

Expanding to Multiple Languages

The system easily scales beyond English and Portuguese. To add a new language (e.g., Spanish):

1. Add Laravel translations:

# Create lang/es/global.php with Spanish translations

**2. Generate es.json

npm run i18n

3. Update i18n.js:

import es from './translations/es.json';

const i18n = createI18n({
    messages: { en, pt, es }  // Add new language
});

4. Add to Language Switcher:

<v-list-item @click="changeLocale('es')">
  <img :src="esFlag" width="20" />
  Español
</v-list-item>

That’s it! The architecture supports unlimited languages with the same synchronized structure.

Conclusion

You don’t have to choose between Laravel localization and Vue-i18n. With thoughtful architecture, both work in harmony: Laravel handles server concerns while Vue delivers reactive UX. The result? Applications that truly speak your users’ language.


Stack: Laravel 12, Vue.js 3, Vuetify 3, Inertia.js, vue-i18n, country-flag-icons
Repository: Geoglify on GitHub



Next Post
My Swiss Army Knife Tech Stack