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

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
langskey (notsupported_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.languageroute - 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
- Login to your application
- Click the language dropdown in the header
- Select a different language
- Verify the page reloads in the new language
- Check that the language preference persists after logout/login
Implementation Differences from Other Approaches
This implementation differs from some common approaches:
-
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
-
Session-Based Language Storage: Uses
user.languagein 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
-
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
- Add language files to
lang/directory - Update
config/constants.phplangsarray - Add corresponding flag code to
$flagMappingarray in header template - Add the translation keys to all
lang/*/lang_v1.phpfiles:'select_language'- Translated "Select Language"'language_changed_successfully'- Translated "Language changed successfully"
- 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
languagecolumn - 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