Реалізація автоопреділення мови по GeoIP для сайту
Автоопреділення мови та регіону дозволяє показувати користувачеві контент на його мові без явного вибору. Комбінує GeoIP (визначення країни за IP) та заголовок Accept-Language браузера для точного визначення бажаної мови.
Джерела даних
GeoIP бази:
- MaxMind GeoLite2 — безплатна, вимагає реєстрації, оновлюється щотижня
- MaxMind GeoIP2 — платна, точніша
- ip-api.com — API, безплатно до 45 запитів/хвилину
- ipinfo.io — API з планами
Accept-Language заголовок — браузер сам передає список бажаних мов:
Accept-Language: uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7
Установка MaxMind GeoLite2
# Завантажити базу
mkdir -p /usr/share/GeoIP
wget "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_KEY&suffix=tar.gz" \
-O GeoLite2-Country.tar.gz
tar -xzf GeoLite2-Country.tar.gz -C /usr/share/GeoIP/
// composer require maxmind-db/reader
use MaxMind\Db\Reader;
class GeoIpService
{
private Reader $reader;
public function __construct()
{
$this->reader = new Reader('/usr/share/GeoIP/GeoLite2-Country.mmdb');
}
public function getCountryCode(string $ip): ?string
{
try {
$record = $this->reader->get($ip);
return $record['country']['iso_code'] ?? null;
} catch (\Exception) {
return null;
}
}
}
Визначення мови (комбінована логіка)
class LanguageDetectionService
{
// Маппінг країна → мова
private array $countryToLanguage = [
'RU' => 'ru', 'BY' => 'ru', 'KZ' => 'ru',
'UA' => 'uk',
'DE' => 'de', 'AT' => 'de', 'CH' => 'de',
'US' => 'en', 'GB' => 'en', 'AU' => 'en', 'CA' => 'en',
];
private array $supportedLanguages = ['ru', 'en', 'de', 'uk'];
public function detect(Request $request): string
{
// 1. Явний вибір користувача (вищий пріоритет)
if ($lang = $request->cookie('locale')) {
if (in_array($lang, $this->supportedLanguages)) {
return $lang;
}
}
// 2. Accept-Language заголовок
$acceptLanguage = $request->header('Accept-Language', '');
$preferred = $this->parseAcceptLanguage($acceptLanguage);
foreach ($preferred as $lang) {
$short = substr($lang, 0, 2);
if (in_array($short, $this->supportedLanguages)) {
return $short;
}
}
// 3. GeoIP
$ip = $request->ip();
$countryCode = app(GeoIpService::class)->getCountryCode($ip);
if ($countryCode && isset($this->countryToLanguage[$countryCode])) {
return $this->countryToLanguage[$countryCode];
}
// 4. За замовчуванням
return config('app.locale', 'en');
}
private function parseAcceptLanguage(string $header): array
{
preg_match_all('/([a-z]{1,8}(?:-[a-z]{1,8})*)(?:;q=([0-9.]+))?/i', $header, $matches);
$langs = array_combine($matches[1], array_map(
fn($q) => $q === '' ? 1.0 : (float) $q,
$matches[2]
));
arsort($langs);
return array_keys($langs);
}
}
Middleware для застосування мови
class SetLocale
{
public function handle(Request $request, Closure $next): Response
{
$lang = app(LanguageDetectionService::class)->detect($request);
App::setLocale($lang);
$response = $next($request);
// Зберегти в cookie (без змін Accept-Language)
if (!$request->cookie('locale')) {
$response->withCookie(cookie('locale', $lang, 60 * 24 * 365));
}
return $response;
}
}
Редирект на мовний поддомен або підпапку
// Редирект при першому відвідуванні
if (!$request->cookie('locale') && !$request->is('*/')) {
$lang = $this->detect($request);
$localizedUrl = url("/{$lang}" . $request->getPathInfo());
return redirect($localizedUrl)->withCookie(cookie('locale', $lang, 525600));
}
Кешування GeoIP
GeoIP-запит швидкий (читання файлу), але при високій навантаженню кешуємо в Redis:
$country = Cache::remember("geoip:{$ip}", 3600, fn() =>
app(GeoIpService::class)->getCountryCode($ip)
);
Автоматичне оновлення бази GeoLite2
# Cron: оновлювати базу щотижня
0 4 * * 2 /usr/local/bin/update-geoip.sh
# update-geoip.sh
wget -qO /tmp/GeoLite2.tar.gz "https://download.maxmind.com/..."
tar -xzf /tmp/GeoLite2.tar.gz -C /tmp/
mv /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /usr/share/GeoIP/
Срок реалізації
2–3 дні: настройка MaxMind + middleware + логіка пріоритетів + тестування з VPN з різних країн.







