首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 多語言自動導向:基於 Accept-Language 與 GeoIP 的語言檢測策略

     編輯:PHP 相關知識

網站多語言進入點自動導向需平衡使用者偏好、SEO、快取與使用者控制權。本文提供完整檢測策略:HTTP Header、GeoIP、Cookie 覆寫、URL 語言前綴。

語言檢測優先順序

  1. URL 語言前綴:/en/, /zh-TW/ (最高優先,SEO 友善、可快取)
  2. 使用者明確選擇:Cookie/Session 儲存偏好
  3. 瀏覽器 Accept-Language:HTTP Header 自動檢測
  4. GeoIP 國家代碼:IP 定位備援
  5. 網站預設語言:最後兜底

核心實作:LanguageDetector 類別

<?php
class LanguageDetector {
    private array $supportedLanguages = ['en', 'zh-TW', 'zh-CN', 'ja', 'ko'];
    private string $defaultLanguage = 'en';
    private string $cookieName = 'preferred_lang';

    public function detect(): string {
        // 1. URL 前綴 (由路由層處理,這裡假設已解析)
        if (!empty($_GET['_lang']) && $this->isSupported($_GET['_lang'])) {
            return $this->setPreference($_GET['_lang']);
        }

        // 2. Cookie 偏好
        if (!empty($_COOKIE[$this->cookieName]) && $this->isSupported($_COOKIE[$this->cookieName])) {
            return $_COOKIE[$this->cookieName];
        }

        // 3. Session 偏好
        if (session_status() === PHP_SESSION_ACTIVE) {
            if (!empty($_SESSION[$this->cookieName]) && $this->isSupported($_SESSION[$this->cookieName])) {
                return $_SESSION[$this->cookieName];
            }
        }

        // 4. Accept-Language 解析
        $acceptLang = $this->parseAcceptLanguage();
        if ($acceptLang) return $this->setPreference($acceptLang);

        // 5. GeoIP 國家代碼映射
        $geoLang = $this->geoIpToLanguage();
        if ($geoLang) return $this->setPreference($geoLang);

        // 6. 預設語言
        return $this->defaultLanguage;
    }

    private function parseAcceptLanguage(): ?string {
        $header = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';
        if (!$header) return null;

        // 解析 q-value 權重
        // 範例: "zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7"
        preg_match_all('/([a-z]{1,8}(?:-[a-z]{1,8})?)\s*(?:;\s*q\s*=\s*(1|0\.\d+))?/i', $header, $matches);
        
        $langs = [];
        foreach ($matches[1] as $i => $lang) {
            $q = isset($matches[2][$i]) && $matches[2][$i] !== '' ? (float)$matches[2][$i] : 1.0;
            $langs[] = ['lang' => strtolower($lang), 'q' => $q];
        }
        
        // 依權重排序
        usort($langs, fn($a, $b) => $b['q'] <=> $a['q']);

        // 匹配支援語言 (精確 > 語言代碼)
        foreach ($langs as $item) {
            $lang = $item['lang'];
            // 精確匹配
            if (in_array($lang, $this->supportedLanguages, true)) return $lang;
            // 語言代碼匹配 (zh-TW → zh)
            $primary = explode('-', $lang)[0];
            foreach ($this->supportedLanguages as $supported) {
                if (str_starts_with($supported, $primary . '-')) return $supported;
            }
        }
        return null;
    }

    private function geoIpToLanguage(): ?string {
        // 需整合 MaxMind GeoIP2 或 IP2Location
        // 這裡示範邏輯
        $countryCode = $this->getCountryCodeFromIp($_SERVER['REMOTE_ADDR']);
        if (!$countryCode) return null;

        $map = [
            'TW' => 'zh-TW', 'HK' => 'zh-TW', 'MO' => 'zh-TW',
            'CN' => 'zh-CN', 'SG' => 'zh-CN', 'MY' => 'zh-CN',
            'JP' => 'ja', 'KR' => 'ko',
            'US' => 'en', 'GB' => 'en', 'AU' => 'en', 'CA' => 'en',
        ];
        return $map[$countryCode] ?? null;
    }

    private function getCountryCodeFromIp(string $ip): ?string {
        // 整合 GeoIP2 資料庫
        // $reader = new GeoIp2\Database\Reader('/path/to/GeoLite2-Country.mmdb');
        // return $reader->country($ip)->country->isoCode;
        return null; // 佔位
    }

    private function isSupported(string $lang): bool {
        return in_array($lang, $this->supportedLanguages, true);
    }

    private function setPreference(string $lang): string {
        setcookie($this->cookieName, $lang, [
            'expires' => time() + 365 * 86400,
            'path' => '/',
            'httponly' => true,
            'secure' => true,
            'samesite' => 'Lax',
        ]);
        if (session_status() === PHP_SESSION_ACTIVE) {
            $_SESSION[$this->cookieName] = $lang;
        }
        return $lang;
    }
}
?>

中介層整合 (單一入口點)

<?php
// public/index.php 或前端控制器
require 'vendor/autoload.php';

$detector = new LanguageDetector();
$lang = $detector->detect();

// 重導向至語言前綴 URL (避免重複內容 SEO 問題)
$currentUri = $_SERVER['REQUEST_URI'];
if (!preg_match('/^\/(?:' . implode('|', $detector->getSupportedLanguages()) . ')\//', $currentUri)) {
    $redirectUrl = '/' . $lang . $currentUri;
    header('Location: ' . $redirectUrl, true, 302); // 302 暫時重導
    exit;
}

// 設定全域語言常數
define('CURRENT_LANG', $lang);
// 載入語言包、設定 locale 等...
?>

JavaScript 版本 (SPA/前端渲染)

<script>
function detectLanguage() {
    // 1. URL 路徑
    const pathLang = window.location.pathname.match(/^\/(en|zh-TW|zh-CN|ja|ko)\//);
    if (pathLang) return pathLang[1];

    // 2. Cookie
    const cookieLang = document.cookie.match(/preferred_lang=([^;]+)/);
    if (cookieLang) return decodeURIComponent(cookieLang[1]);

    // 3. navigator.language / navigator.languages
    const supported = ['en', 'zh-TW', 'zh-CN', 'ja', 'ko'];
    const prefs = navigator.languages || [navigator.language || navigator.userLanguage];
    
    for (const lang of prefs) {
        const lower = lang.toLowerCase();
        if (supported.includes(lower)) return lower;
        const primary = lower.split('-')[0];
        const match = supported.find(s => s.startsWith(primary + '-'));
        if (match) return match;
    }

    return 'en'; // 預設
}

const userLang = detectLanguage();
// 切換 i18n 語言包、更新 UI...

SEO 關鍵點

項目做法
hreflang<link rel="alternate" hreflang="zh-TW" href="https://example.com/zh-TW/page">
Canonical每頁指定標準 URL (含語言前綴)
Sitemap包含所有語言版本 URL
重導向302 (非 301),避免快取錯誤語言版本
快取Vary: Accept-Language, Cookie 區分快取

使用者控制權:語言切換器

<?php
function renderLanguageSwitcher(string $currentLang, array $supported): string {
    $html = '
'; foreach ($supported as $lang) { $nativeNames = [ 'en' => 'English', 'zh-TW' => '繁體中文', 'zh-CN' => '简体中文', 'ja' => '日本語', 'ko' => '한국어' ]; $active = $lang === $currentLang ? ' active' : ''; $url = '/' . $lang . $_SERVER['REQUEST_URI']; $html .= ''; $html .= $nativeNames[$lang] ?? $lang; $html .= ''; } $html .= '
'; return $html; } ?>

多語言導向核心原則:URL 為唯一真實來源,Accept-Language/GeoIP 僅作初次訪問預設,使用者明確選擇優先,並提供可見、可切換的語言選單。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved