cmb69 / polyglot_xh

Advanced multilingual websites for CMSimple_XH
GNU General Public License v3.0
3 stars 0 forks source link

Add automatic language selection #2

Open cmb69 opened 9 years ago

cmb69 commented 9 years ago

The language of the visitor can be detected by sniffing the Accept-Language header. It seems to be reasonable to automatically select the language based on this information. Some kind of overriding should be possible, though.

See http://www.cmsimpleforum.com/viewtopic.php?f=5&t=5905&p=34437#p34437 and http://forum.cmsimple-xh.dk/?f=1&t=754.

cmb69 commented 7 years ago

A potential solution has been worked out in a PM thread in the forum. A rough outline:

The actual main language has to be created as second language. All requests to the installation folder are redirected according to the sent Accept-Language header field (defaulting to the main language) using the 300 Multiple Choices status code. Since some browsers (most notably Chrome) ignore the Location, a manual language selection is presented (perhaps in a minimal template). This requires the languagemenu to be adjusted (to avoid duplicate links to the main language), and also perhaps further adjustments. The language selection also sets apprialte hreflang="x-default" links.

The following code may serve as base for the actual implementation:

<?php

if ($sl === $cf['language']['default']) {
    $temp = XH_secondLanguages();
    array_unshift($temp, 'en'); // explicitly add "default" language
    $temp = new LanguagePicker($temp);
    $temp = $temp->pickLanguage($_SERVER['HTTP_ACCEPT_LANGUAGE']);

    $temp = CMSIMPLE_URL . "$temp/";
    if ($_SERVER['QUERY_STRING'] !== '') {
        $temp .= "?{$_SERVER[QUERY_STRING]}";
    }
    header("Location: $temp", true, 302);
    exit;
}

/**
 * see <https://tools.ietf.org/html/rfc7231#section-5.3.5>
 */
class LanguagePicker
{
    /**
     * @var array
     */
    private $availableLanguages;

    public function __construct(array $availableLanguages)
    {
        $this->availableLanguages = $availableLanguages;
    }

    /**
     * @param string $acceptLanguage
     * @return string
     */
    public function pickLanguage($acceptLanguage)
    {
        $acceptedLanguages = $this->parseAcceptLanguageHeader($acceptLanguage);
        foreach ($acceptedLanguages as $language) {
            if ($language === '*') {
                return $this->availableLanguages[0];
            } elseif (in_array($language, $this->availableLanguages) !== false) {
                return $language;
            }
        }
        return $this->availableLanguages[0];
    }

    /**
     * @param string $acceptLanguage
     * @return array
     */
    private function parseAcceptLanguageHeader($acceptLanguage)
    {
        $languages = array();
        $languageRanges = explode(',', $acceptLanguage);
        foreach ($languageRanges as $languageRange) {
            $parts = explode(';', trim($languageRange));
            if (count($parts) === 1) {
                $language = $parts[0];
                $weight = 1.0;
            } else {
                list($language, $weight) = $parts;
                list(, $weight) = explode('=', $weight);
                $weight = (float) $weight;
            }
            $parts = explode('-', $language);
            $language = trim($parts[0]);
            $languages[] = compact('language', 'weight');
        }
        $this->sortLanguages($languages);
        foreach ($languages as &$language) {
            $language = $language['language'];
        }
        $languages = array_unique($languages);
        return $languages;
    }

    /**
     * Can't use PHP sort functions, since we require a stable sort.
     *
     * @return void
     */
    private function sortLanguages(array &$array)
    {
        for ($i = 1; $i < count($array); $i++) {
            $current = $array[$i];
            $j = $i;
            while ($j > 0 && $array[$j-1]['weight'] < $current['weight']) {
                $array[$j] = $array[$j-1];
                $j--;
            }
            $array[$j] = $current;
        }
    }
}
cmb69 commented 6 years ago

FTR: ext/intl offers Locale::acceptFromHttp which might be interesting.