Skip to content

Instantly share code, notes, and snippets.

@suin
Created December 26, 2012 07:21
Show Gist options
  • Save suin/4378670 to your computer and use it in GitHub Desktop.
Save suin/4378670 to your computer and use it in GitHub Desktop.
AcceptLanguage negotiation algorithm
<?php
/**
* Return accept languages
* @return array
*
* HTTP content negotiation (section 12) uses short "floating point" numbers to indicate the
* relative importance ("weight") of various negotiable parameters. A weight is normalized to
* a real number in the range 0 through 1, where 0 is the minimum and 1 the maximum value.
* If a parameter has a quality value of 0, then content with this parameter is `not acceptable'
* for the client. HTTP/1.1 applications MUST NOT generate more than three digits after the
* decimal point. User configuration of these values SHOULD also be limited in this fashion.
*
* qvalue = ( "0" [ "." 0*3DIGIT ] )
* | ( "1" [ "." 0*3("0") ] )
*
* "Quality values" is a misnomer, since these values merely represent relative degradation in desired quality.
*
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9
*/
public function getAcceptLanguages()
{
if ( isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) === false )
{
return [];
}
if ( preg_match_all('/(?P<langcode>[A-Za-z-]+)(?:;q=(?P<quality>[0-9\.]+))?/', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches) == 0 )
{
return [];
}
$qualities = $matches['quality'];
$languages = $matches['langcode'];
$languages = array_map('strtolower', $languages);
foreach ( $qualities as $k => $quality )
{
if ( $quality === '' or $quality > 1 )
{
$qualities[$k] = 1;
}
else
{
$qualities[$k] = ceil($quality * 1000) / 1000;
}
}
$acceptLanguages = array_combine($languages, $qualities);
$acceptLanguages = array_filter($acceptLanguages); // remove quality=0
return $acceptLanguages;
}
/**
* Negotiate language
* @param string[] $serverLanguages Language codes must be lower case
* @param float[] $clientLanguages A Hush which keys are language codes and values are qualities
* @return string
*/
public function negotiateLanguage(array $serverLanguages, array $clientLanguages)
{
// Set up client language list.
$orderPriority = 1;
foreach ( $clientLanguages as $language => $clientPriority )
{
$clientLanguages[$language] = ( ( 1 - $clientPriority ) * 10000 ) + $orderPriority;
$orderPriority += 1;
if ( strpos($language, '-') > 0 )
{
$primaryLanguage = substr($language, 0, strpos($language, '-'));
if ( isset($clientLanguages[$primaryLanguage]) === false )
{
// The priority will be increased by 1000 for partial matches.
$clientLanguages[$primaryLanguage] = $clientLanguages[$language] + 1000;
}
}
}
// Here, the nearer to zero the priority is, the higher the quality is.
asort($clientLanguages);
// Set up server language list.
$serverLanguages = array_combine($serverLanguages, $serverLanguages);
foreach ( $serverLanguages as $serverLanguage )
{
if ( strpos($serverLanguage, '-') > 0 )
{
$primaryLanguage = substr($serverLanguage, 0, strpos($serverLanguage, '-'));
if ( isset($serverLanguages[$primaryLanguage]) === false )
{
$serverLanguages[$primaryLanguage] = $serverLanguage;
}
}
}
// Negotiate.
foreach ( $clientLanguages as $language => $priority )
{
if ( isset($serverLanguages[$language]) === true )
{
return $serverLanguages[$language];
}
}
return reset($serverLanguages);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment