Skip to main content

User-Specific Language Dropdown with Country Flags

This guide documents the implementation of a user-specific language dropdown with country flags that allows each user to set their own language preference, saves it to the database, and applies it across the entire application.

Overview

Dropdown button in header

The language dropdown provides:

  • Real country flag icons for each language
  • User-specific language preferences stored in database
  • Automatic language switching across the entire application
  • Persistent language settings across login sessions
  • Clean, responsive design

Prerequisites

  • Laravel application with user authentication
  • Existing translation files in lang/ directory
  • User table with language field
  • Basic understanding of Laravel middleware and localization

Step 1: Verify Language Constants

This project uses the existing langs configuration in config/constants.php for supported languages.

File: config/constants.php

The configuration should already include:

'langs' => [
'en' => ['full_name' => 'English', 'short_name' => 'English'],
'es' => ['full_name' => 'Spanish - Español', 'short_name' => 'Spanish'],
'sq' => ['full_name' => 'Albanian - Shqip', 'short_name' => 'Albanian'],
'hi' => ['full_name' => 'Hindi - हिंदी', 'short_name' => 'Hindi'],
'nl' => ['full_name' => 'Dutch', 'short_name' => 'Dutch'],
'fr' => ['full_name' => 'French - Français', 'short_name' => 'French'],
'de' => ['full_name' => 'German - Deutsch', 'short_name' => 'German'],
'ar' => ['full_name' => 'Arabic - العَرَبِيَّة', 'short_name' => 'Arabic'],
'tr' => ['full_name' => 'Turkish - Türkçe', 'short_name' => 'Turkish'],
'id' => ['full_name' => 'Indonesian', 'short_name' => 'Indonesian'],
'ps' => ['full_name' => 'Pashto', 'short_name' => 'Pashto'],
'pt' => ['full_name' => 'Portuguese', 'short_name' => 'Portuguese'],
'vi' => ['full_name' => 'Vietnamese', 'short_name' => 'Vietnamese'],
'ce' => ['full_name' => 'Chinese', 'short_name' => 'Chinese'],
'ro' => ['full_name' => 'Romanian', 'short_name' => 'Romanian'],
'lo' => ['full_name' => 'Lao', 'short_name' => 'Lao'],
],

Note:

  • This implementation uses the existing langs key (not supported_languages)
  • Flag codes are defined in the view template (Step 7) for better separation of concerns

Step 2: Add Flag Icons CSS

Add the Flag Icons CSS library to your layout.

File: resources/views/layouts/partials/css.blade.php

{{-- Flag Icons for Language Switcher --}}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.0.0/css/flag-icons.min.css"/>

Step 3: Create Language Middleware

Create middleware to automatically set the application locale based on the user's language preference stored in the session.

Generate Middleware

php artisan make:middleware Language

File: app/Http/Middleware/Language.php

<?php

namespace App\Http\Middleware;

use App;
use Closure;

class Language
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$locale = config('app.locale');
if ($request->session()->has('user.language')) {
$locale = $request->session()->get('user.language');
}
App::setLocale($locale);

return $next($request);
}
}

Key Points:

  • Gets language from session user.language
  • Falls back to config('app.locale') if not set
  • Sets the application locale for the current request

Step 4: Register Middleware

Register the middleware in your HTTP Kernel.

File: app/Http/Kernel.php

protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// ... other middleware
\App\Http\Middleware\Language::class, // Add this line
// ... other middleware
],
];

Or add it to your route groups as shown in routes/web.php:101:

Route::middleware(['setData', 'auth', 'SetSessionData', 'language', 'timezone', 'AdminSidebarMenu', 'CheckUserLogin'])->group(function () {
// Your routes here
});

Step 5: Add Controller Method

Add a method to handle language updates in your user management controller.

File: app/Http/Controllers/ManageUserController.php

/**
* Change user's language preference
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function changeLanguage(Request $request)
{
try {
// Get supported languages from config
$supportedLanguages = array_keys(config('constants.langs', []));

// Validate the language
$request->validate([
'language' => ['required', 'string', \Illuminate\Validation\Rule::in($supportedLanguages)]
]);

$user = auth()->user();
$language = $request->input('language');

// Update user's language preference in database
$user->update(['language' => $language]);

// Update session language
session(['user.language' => $language]);

// Set app locale for immediate effect
app()->setLocale($language);

return redirect()
->back()
->with('status', ['success' => 1, 'msg' => __('lang_v1.language_changed_successfully')]);

} catch (\Exception $e) {
\Log::error('Error changing language: ' . $e->getMessage());

return redirect()
->back()
->with('status', ['success' => 0, 'msg' => __('lang_v1.something_went_wrong')]);
}
}

Key Features:

  • Validates language against supported languages from config
  • Updates user's language in database
  • Updates session user.language
  • Sets app locale for immediate effect
  • Redirects back with status message

Step 6: Add Route

Add a route for the language change endpoint.

File: routes/web.php

// Language change route
Route::post('/change-language', [ManageUserController::class, 'changeLanguage'])
->name('change.language');

Step 7: Create Language Dropdown

Add the language dropdown to your header with flag mapping defined in the view.

File: resources/views/layouts/partials/header.blade.php

{{-- Language Selector --}}
<details class="tw-dw-dropdown tw-relative tw-inline-block tw-text-left">
<summary
class="tw-inline-flex tw-transition-all tw-ring-1 tw-ring-white/10 hover:tw-text-white tw-cursor-pointer tw-duration-200 tw-bg-primary-800 hover:tw-bg-primary-700 tw-py-1.5 tw-px-3 tw-rounded-lg tw-items-center tw-justify-center tw-text-sm tw-font-medium tw-text-white tw-gap-1">
@php
$currentLang = session()->get('user.language', auth()->user()->language ?? config('app.locale', 'en'));
$supportedLanguages = config('constants.langs', []);

// Flag mapping for CSS classes (using flag-icons library)
$flagMapping = [
'en' => 'us', // English -> United States flag
'es' => 'es', // Spanish
'sq' => 'al', // Albanian -> Albania flag
'hi' => 'in', // Hindi -> India flag
'nl' => 'nl', // Dutch
'fr' => 'fr', // French
'de' => 'de', // German
'ar' => 'sa', // Arabic -> Saudi Arabia flag
'tr' => 'tr', // Turkish
'id' => 'id', // Indonesian
'ps' => 'af', // Pashto -> Afghanistan flag
'pt' => 'pt', // Portuguese
'vi' => 'vn', // Vietnamese
'ce' => 'cn', // Chinese -> China flag
'ro' => 'ro', // Romanian
'lo' => 'la', // Lao -> Laos flag
];

// Build languages array with flags
$languages = [];
foreach ($supportedLanguages as $langCode => $langData) {
$languages[$langCode] = [
'name' => $langData['short_name'],
'full_name' => $langData['full_name'],
'flag_code' => $flagMapping[$langCode] ?? 'un' // Default to UN flag
];
}

$currentLanguage = $languages[$currentLang] ?? $languages['en'] ?? ['name' => 'English', 'flag_code' => 'us'];
@endphp

{{-- Current Language Flag --}}
<span class="fi fi-{{ $currentLanguage['flag_code'] }} tw-text-lg tw-mr-2"></span>
<span class="tw-hidden sm:tw-block tw-text-sm tw-font-medium">{{ $currentLanguage['name'] }}</span>
<svg class="tw-w-4 tw-h-4 tw-ml-1" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
</svg>
</summary>

{{-- Language Dropdown menu --}}
<ul class="tw-p-2 tw-w-52 tw-absolute tw-right-0 tw-z-10 tw-mt-2 tw-origin-top-right tw-bg-white dark:tw-bg-gray-800 tw-rounded-lg tw-shadow-lg tw-ring-1 tw-ring-gray-200 dark:tw-ring-gray-700 focus:tw-outline-none"
role="menu" tabindex="-1">
<div class="tw-px-4 tw-py-3 tw-border-b tw-border-gray-200 dark:tw-border-gray-700">
<p class="tw-text-sm tw-text-gray-900 dark:tw-text-white tw-font-medium">@lang('lang_v1.select_language')</p>
</div>
<div class="tw-py-2">
@foreach($languages as $langCode => $langData)
<li>
<a href="#"
onclick="event.preventDefault(); changeLanguage('{{ $langCode }}');"
class="tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100 dark:tw-text-gray-200 dark:hover:tw-bg-gray-600 dark:hover:tw-text-white {{ $currentLang === $langCode ? 'tw-bg-blue-50 dark:tw-bg-blue-900 tw-text-blue-700 dark:tw-text-blue-300' : '' }}">
<span class="fi fi-{{ $langData['flag_code'] }} tw-text-lg tw-mr-3"></span>
<span>{{ $langData['name'] }}</span>
@if($currentLang === $langCode)
<svg class="tw-w-4 tw-h-4 tw-ml-auto tw-text-blue-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/>
</svg>
@endif
</a>
</li>
@endforeach
</div>
</ul>
</details>

Key Features:

  • Gets current language from session, user model, or falls back to app locale
  • Loads languages from config('constants.langs')
  • Defines flag mapping locally for better separation of concerns
  • Combines config data with flag codes to build complete languages array
  • Uses flag-icons library CSS classes (fi fi-{country_code})
  • Highlights currently selected language
  • Calls JavaScript changeLanguage() function on click

Step 8: Add JavaScript Handler

Add JavaScript to handle the language change functionality.

File: resources/views/layouts/partials/javascripts.blade.php

/**
* Change language via form submission
* @param {string} langCode - Language code to switch to
*/
function changeLanguage(langCode) {
try {
// Show loading indicator if Swal is available
if (window.Swal) {
window.Swal.fire({
title: 'Changing Language...',
text: 'Please wait while we update your language preference.',
allowOutsideClick: false,
allowEscapeKey: false,
allowEnterKey: false,
showConfirmButton: false,
didOpen: () => {
window.Swal.showLoading();
}
});
}

// Validate CSRF token exists
const csrfMetaTag = document.querySelector('meta[name="csrf-token"]');
if (!csrfMetaTag) {
console.error('CSRF token meta tag not found');
if (window.Swal) {
window.Swal.fire('Error', 'Security token not found. Please refresh the page and try again.', 'error');
}
return;
}

// Create form and submit
const form = document.createElement('form');
form.method = 'POST';
form.action = '{{ route("change.language") }}';

// Add CSRF token
const csrfToken = document.createElement('input');
csrfToken.type = 'hidden';
csrfToken.name = '_token';
csrfToken.value = csrfMetaTag.getAttribute('content');
form.appendChild(csrfToken);

// Add language parameter
const langInput = document.createElement('input');
langInput.type = 'hidden';
langInput.name = 'language';
langInput.value = langCode;
form.appendChild(langInput);

// Submit form
document.body.appendChild(form);
form.submit();
} catch (error) {
console.error('Error changing language:', error);
if (window.Swal) {
window.Swal.fire('Error', 'An error occurred while changing language. Please try again.', 'error');
}
}
}

Key Features:

  • Shows loading indicator using SweetAlert2
  • Validates CSRF token exists
  • Creates dynamic form with CSRF token and language parameter
  • Submits to change.language route
  • Error handling with user feedback

Step 9: Add Translation Keys

Add the required translation keys to all language files.

Files: lang/*/lang_v1.php (for each language)

Add these two keys before the closing ]; in each language file:

// English (lang/en/lang_v1.php)
'select_language' => 'Select Language',
'language_changed_successfully' => 'Language changed successfully',

// Spanish (lang/es/lang_v1.php)
'select_language' => 'Seleccionar idioma',
'language_changed_successfully' => 'Idioma cambiado exitosamente',

// Arabic (lang/ar/lang_v1.php)
'select_language' => 'اختر اللغة',
'language_changed_successfully' => 'تم تغيير اللغة بنجاح',

// And so on for all other languages...

Required translations for all 16 languages:

  • English (en): "Select Language", "Language changed successfully"
  • Spanish (es): "Seleccionar idioma", "Idioma cambiado exitosamente"
  • Arabic (ar): "اختر اللغة", "تم تغيير اللغة بنجاح"
  • French (fr): "Sélectionner la langue", "Langue changée avec succès"
  • German (de): "Sprache auswählen", "Sprache erfolgreich geändert"
  • Turkish (tr): "Dil Seç", "Dil başarıyla değiştirildi"
  • Portuguese (pt): "Selecionar idioma", "Idioma alterado com sucesso"
  • Dutch (nl): "Selecteer taal", "Taal succesvol gewijzigd"
  • Indonesian (id): "Pilih Bahasa", "Bahasa berhasil diubah"
  • Hindi (hi): "भाषा चुनें", "भाषा सफलतापूर्वक बदली गई"
  • Vietnamese (vi): "Chọn ngôn ngữ", "Đã thay đổi ngôn ngữ thành công"
  • Romanian (ro): "Selectați limba", "Limba schimbată cu succes"
  • Albanian (sq): "Zgjidhni gjuhën", "Gjuha u ndryshua me sukses"
  • Pashto (ps): "ژبه وټاکئ", "ژبه په بریالیتوب سره بدله شوه"
  • Chinese (ce): "选择语言", "语言更改成功"
  • Lao (lo): "ເລືອກພາສາ", "ປ່ຽນພາສາສໍາເລັດແລ້ວ"

Step 10: Clear Caches

Clear your application caches to ensure all changes take effect:

php artisan config:cache
php artisan view:clear
php artisan route:clear

Testing

  1. Login to your application
  2. Click the language dropdown in the header
  3. Select a different language
  4. Verify the page reloads in the new language
  5. Check that the language preference persists after logout/login

Implementation Differences from Other Approaches

This implementation differs from some common approaches:

  1. Flag Mapping in View: Flag codes are defined in the view template rather than stored in the config file. This provides:

    • Better separation of concerns (display logic stays in view)
    • Easier to update flag mappings without touching config
    • Config file remains focused on language data
  2. Session-Based Language Storage: Uses user.language in session:

    • Language is set from user's database field on login
    • Middleware reads from session for each request
    • More efficient than querying database on every request
  3. Form Submission: Uses actual form submission instead of AJAX:

    • More reliable for language changes
    • Ensures full page reload with new language
    • Simpler error handling

Customization

Adding New Languages

  1. Add language files to lang/ directory
  2. Update config/constants.php langs array
  3. Add corresponding flag code to $flagMapping array in header template
  4. Add the translation keys to all lang/*/lang_v1.php files:
    • 'select_language' - Translated "Select Language"
    • 'language_changed_successfully' - Translated "Language changed successfully"
  5. Add JavaScript language files if needed to public/js/lang/

Styling

Modify the Tailwind CSS classes in the header template to match your application's design system.

Flag Sources

The implementation uses the Flag Icons CSS library. You can also use:

  • Local flag images
  • Different flag icon libraries
  • Custom SVG flags

Troubleshooting

Language Not Changing

  • Check middleware is registered correctly in route groups
  • Verify language files exist in lang/ directory
  • Ensure session is working properly
  • Check browser console for JavaScript errors

Flags Not Displaying

  • Verify Flag Icons CSS is loaded
  • Check flag codes match the library format (2-letter country codes)
  • Ensure internet connection for CDN
  • Inspect browser network tab for failed CSS requests

Database Errors

  • Verify users table has language column
  • Check column type allows sufficient length (VARCHAR(5) recommended)
  • Ensure database permissions are correct

Session Issues

  • Verify session driver is configured properly in .env
  • Check session lifetime settings
  • Clear session cache: php artisan cache:clear

Conclusion

You now have a fully functional user-specific language dropdown with country flags that:

  • Allows each user to set their preferred language
  • Saves preferences to the database
  • Automatically applies language across the application via middleware
  • Uses beautiful country flag icons from flag-icons library
  • Provides a professional, responsive interface
  • Separates display logic (flag codes) from data (language names)

The implementation is scalable and can easily accommodate additional languages as your application grows.

File References

  • Config: config/constants.php:34-51
  • CSS: resources/views/layouts/partials/css.blade.php:3
  • Middleware: app/Http/Middleware/Language.php
  • Controller: app/Http/Controllers/ManageUserController.php:475-509
  • Route: routes/web.php:249-250
  • View: resources/views/layouts/partials/header.blade.php:194-267
  • JavaScript: resources/views/layouts/partials/javascripts.blade.php:224 (changeLanguage function)

💛 Support this project

Premium Login