codezero/browser-locale
Parse the browser’s Accept-Language header to get a visitor’s preferred locales in order. Returns primary locale or full locale list with language/region parts. Works in vanilla PHP and integrates with Laravel via the container.
Installation:
composer require codezero/browser-locale
Laravel 5.5+ auto-registers the service provider; no manual configuration needed.
First Use Case: Retrieve the primary locale in a Laravel controller or middleware:
use CodeZero\BrowserLocale\BrowserLocale;
$browserLocale = app(BrowserLocale::class);
$locale = $browserLocale->getLocale();
if ($locale) {
app()->setLocale($locale->language); // Set Laravel's locale
// Or use $locale->locale (e.g., "en-US") for granular logic
}
Where to Look First:
getLocale(): For the highest-priority locale (e.g., en-US).getLocales(): For all locales sorted by preference weight.LocaleFilter, LanguageFilter, etc., for specific data extraction (e.g., just languages or countries).Create a middleware to set the app locale dynamically:
namespace App\Http\Middleware;
use Closure;
use CodeZero\BrowserLocale\BrowserLocale;
class SetLocaleMiddleware
{
public function handle($request, Closure $next)
{
$browserLocale = app(BrowserLocale::class);
$locale = $browserLocale->getLocale();
if ($locale) {
app()->setLocale($locale->language);
}
return $next($request);
}
}
Register in app/Http/Kernel.php:
protected $middleware = [
\App\Http\Middleware\SetLocaleMiddleware::class,
];
Use getLocales() to implement fallback chains (e.g., en-US → en → en-GB):
$locales = $browserLocale->getLocales();
$supportedLocales = ['en-US', 'en', 'fr', 'de'];
foreach ($locales as $locale) {
if (in_array($locale->locale, $supportedLocales)) {
app()->setLocale($locale->language);
break;
}
}
Extract specific data (e.g., languages or countries) for reporting or conditional rendering:
$languageFilter = new \CodeZero\BrowserLocale\Filters\LanguageFilter();
$languages = $browserLocale->filter($languageFilter);
// Example: Show a "Welcome back" message in the user's language
if (in_array('nl', $languages)) {
return view('welcome_nl');
}
Implement a custom filter for niche use cases (e.g., extract regions only for EU compliance):
use CodeZero\BrowserLocale\Filters\Filter;
class RegionFilter implements Filter
{
public function filter(array $locales): array
{
return array_unique(array_filter($locales, function ($locale) {
return in_array($locale->country, ['DE', 'FR', 'IT']);
}));
}
}
Usage:
$regionFilter = new RegionFilter();
$regions = $browserLocale->filter($regionFilter);
app(BrowserLocale::class) over new BrowserLocale() for testability and dependency injection.$cacheKey = 'browser_locale_' . md5($_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '');
$locales = cache()->remember($cacheKey, now()->addHours(1), function () use ($browserLocale) {
return $browserLocale->getLocales();
});
BrowserLocale in tests:
$this->app->instance(BrowserLocale::class, Mockery::mock(BrowserLocale::class));
$_SERVER["HTTP_ACCEPT_LANGUAGE"] directly:
$browserLocale = new BrowserLocale($_SERVER["HTTP_ACCEPT_LANGUAGE"]);
null returns (e.g., if the header is missing or malformed).null or empty arrays).null when using getLocale():
$locale = $browserLocale->getLocale();
if (!$locale) {
// Fallback to default or redirect to language selector
}
Locale Weight Misinterpretation:
weight property (e.g., 0.8) indicates preference strength, not support status. A weight of 0.4 doesn’t mean the locale is invalid—it’s just less preferred.getLocales() to iterate through all locales in order of preference.Duplicate Locales:
en-US,en). The package deduplicates results automatically, but be aware of the original order.getLocales() to see the full sorted list, including weights.Country-Only Locales:
en). The CountryFilter will skip these, which may be unexpected.LanguageFilter or implement a custom filter.Laravel Locale Conflicts:
app()->setLocale($locale->language), ensure the locale exists in your config/app.php under supportedLocales. Otherwise, Laravel may fall back to the default.$supported = config('app.supportedLocales', []);
if (in_array($locale->language, $supported)) {
app()->setLocale($locale->language);
}
Middleware Order:
Inspect Raw Input:
Dump $_SERVER["HTTP_ACCEPT_LANGUAGE"] to verify the input string:
dd($_SERVER["HTTP_ACCEPT_LANGUAGE"] ?? 'Header missing');
Example output: en-US,en;q=0.8,fr;q=0.6.
Check Filter Outputs: Use filters to debug what’s being parsed:
$languageFilter = new \CodeZero\BrowserLocale\Filters\LanguageFilter();
$countriesFilter = new \CodeZero\BrowserLocale\Filters\CountryFilter();
dd($browserLocale->filter($languageFilter), $browserLocale->filter($countriesFilter));
Test Edge Cases: Manually test with malformed strings:
$browserLocale = new BrowserLocale('en-US,,fr;q=invalid,nl');
dd($browserLocale->getLocales());
Expected: The package ignores invalid q values and empty entries.
Laravel Logs: Enable debug logging to trace middleware execution:
\Log::debug('Browser locale:', [
'raw' => $_SERVER["HTTP_ACCEPT_LANGUAGE"] ?? null,
'parsed' => $browserLocale->getLocales(),
]);
Custom Parsing Logic:
Extend the BrowserLocale class to add pre-processing (e.g., normalize the input string):
class ExtendedBrowserLocale extends BrowserLocale
{
public function __construct($acceptLanguage)
{
// Pre-process: replace 'und' with 'en' for testing
$acceptLanguage = str_replace('und', 'en', $acceptLanguage);
parent::__construct($acceptLanguage);
}
}
Database Integration: Store parsed locales in the database for analytics:
$locales = $browserLocale->getLocales();
foreach ($locales as $locale) {
UserLocale::updateOrCreate(
['user_id' => auth()->id()],
['locale' => $locale->locale, 'weight' => $locale->weight]
);
}
API Headers:
Use the package to set Accept-Language headers in API responses:
$response->header('Accept-Language', implode(',', array_map(
fn($locale) => $locale->locale,
$browserLocale->getLocales()
)));
Geolocation Fallback: Combine with a geolocation service for fallback logic:
$browserLocales = $browserLocale->getLocales();
$
How can I help you explore Laravel packages today?