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:
- Laravel (PHP files) for server-side rendering, emails, SEO
- Vue-i18n (JSON files) for reactive client-side UI
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:
- Updates vue-i18n locale instantly (no reload)
- Persists to Laravel backend via Inertia.js
- Maintains state across navigation
<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
- Best of Both Worlds: Server-side SEO + client-side reactivity
- Single Structure: Same keys in PHP and JSON
- Instant Switching: No page reloads
- Persistent: State maintained across sessions
- Developer-Friendly:
$t('global.auth.login')works everywhere - Scalable: Easy to add languages
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