Skip to content

Commit 38ef33f

Browse files
authored
Move language_choose_code() into new LangChooser class (#1081)
1 parent 85eb93c commit 38ef33f

File tree

3 files changed

+310
-159
lines changed

3 files changed

+310
-159
lines changed

include/langchooser.inc

Lines changed: 9 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -28,170 +28,20 @@
2828
2929
*/
3030

31+
use phpweb\LangChooser;
32+
33+
require_once __DIR__ . '/../src/autoload.php';
34+
3135
// Default STRIPPED_URI
3236
$_SERVER['STRIPPED_URI'] = htmlspecialchars($_SERVER['REQUEST_URI'], ENT_QUOTES, 'UTF-8');
3337

3438
// The code is encapsulated in a function,
3539
// so the variable namespace is not polluted
36-
list($LANG, $EXPL_LANG, $UA_LANGS) = language_choose_code();
40+
list($LANG, $EXPL_LANG) = (new LangChooser($LANGUAGES, $INACTIVE_ONLINE_LANGUAGES, myphpnet_language(), default_language() ?: ''))->chooseCode(
41+
$_REQUEST['lang'] ?? null,
42+
$_SERVER['REQUEST_URI'],
43+
$_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? null,
44+
);
3745

3846
// Compatibility
3947
if ($EXPL_LANG == '') { unset($EXPL_LANG); }
40-
41-
function language_choose_code()
42-
{
43-
// Contains all the languages picked up by the
44-
// process in priority order (without repeating codes)
45-
$languages = [];
46-
47-
// Default values for languages
48-
$explicitly_specified = ''; $selected = '';
49-
50-
// Specified for the request (GET/POST parameter)
51-
if (!empty($_REQUEST['lang']) && is_string($_REQUEST['lang'])) {
52-
$explicitly_specified = language_add(htmlspecialchars($_REQUEST['lang'], ENT_QUOTES, 'UTF-8'), $languages);
53-
}
54-
55-
// Specified in a shortcut URL (eg. /en/echo or /pt_br/echo)
56-
if (preg_match("!^/(\\w{2}(_\\w{2})?)/!", htmlspecialchars($_SERVER['REQUEST_URI'],ENT_QUOTES, 'UTF-8'), $flang)) {
57-
58-
// Put language into preference list
59-
$rlang = language_add($flang[1], $languages);
60-
61-
// Set explicity specified language
62-
if (empty($explicitly_specified)) {
63-
$explicitly_specified = $rlang;
64-
}
65-
66-
// Drop out langauge specification from URL, as this is already handled
67-
$_SERVER['STRIPPED_URI'] = preg_replace(
68-
"!^/$flang[1]/!", "/", htmlspecialchars($_SERVER['REQUEST_URI'], ENT_QUOTES, 'UTF-8'),
69-
);
70-
71-
}
72-
73-
// Specified in a manual URL (eg. manual/en/ or manual/pt_br/)
74-
if (preg_match("!^/manual/(\\w{2}(_\\w{2})?)(/|$)!", htmlspecialchars($_SERVER['REQUEST_URI'], ENT_QUOTES, 'UTF-8'), $flang)) {
75-
76-
$flang = language_add($flang[1], $languages);
77-
78-
// Set explicity specified language
79-
if (empty($explicitly_specified)) {
80-
$explicitly_specified = $flang;
81-
}
82-
}
83-
84-
// Honor the users own language setting (if available)
85-
if (myphpnet_language()) {
86-
language_add(myphpnet_language(), $languages);
87-
}
88-
89-
// Specified by the user via the browser's Accept Language setting
90-
// Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
91-
$browser_langs = []; $parsed_langs = [];
92-
93-
// Check if we have $_SERVER['HTTP_ACCEPT_LANGUAGE'] set and
94-
// it no longer breaks if you only have one language set :)
95-
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
96-
$browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']);
97-
98-
// Go through all language preference specs
99-
foreach ($browser_accept as $value) {
100-
// The language part is either a code or a code with a quality
101-
// We cannot do anything with a * code, so it is skipped
102-
// If the quality is missing, it is assumed to be 1 according to the RFC
103-
if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($value), $found)) {
104-
$quality = (isset($found[3]) ? (float) $found[3] : 1.0);
105-
$browser_langs[] = [$found[1], $quality];
106-
}
107-
unset($found);
108-
}
109-
}
110-
111-
// Order the codes by quality
112-
usort($browser_langs, "language_accept_order");
113-
114-
// For all languages found in the accept-language
115-
foreach ($browser_langs as $langdata) {
116-
117-
// Translation table for accept-language codes and phpdoc codes
118-
switch ($langdata[0]) {
119-
case "pt-br":
120-
$langdata[0] = 'pt_br';
121-
break;
122-
case "zh-cn":
123-
$langdata[0] = 'zh';
124-
break;
125-
case "zh-hk":
126-
$langdata[0] = 'hk';
127-
break;
128-
case "zh-tw":
129-
$langdata[0] = 'tw';
130-
break;
131-
}
132-
133-
// We do not support flavors of languages (except the ones above)
134-
// This is not in conformance to the RFC, but it here for user
135-
// convinience reasons
136-
if (preg_match("!^(.+)-!", $langdata[0], $match)) {
137-
$langdata[0] = $match[1];
138-
}
139-
140-
// Add language to priority order
141-
$parsed_langs[] = language_add($langdata[0], $languages);
142-
}
143-
144-
// Language preferred by this mirror site
145-
language_add(default_language(), $languages);
146-
147-
// Last default language is English
148-
language_add("en", $languages);
149-
150-
// Try to find out what language is available on this mirror.
151-
// As most of the language dependant operations involve manual
152-
// page display (lookup, search, shortcuts), we will check for
153-
// the index file of manuals.
154-
/*
155-
foreach ($languages as $language) {
156-
if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/manual/$language/index.php")) {
157-
$selected = $language;
158-
break;
159-
}
160-
}
161-
*/
162-
$selected = $languages[0];
163-
164-
// Return with all found data
165-
return [$selected, $explicitly_specified, $parsed_langs];
166-
}
167-
168-
// Add a language to the possible languages' list
169-
function language_add($langcode, &$langs)
170-
{
171-
global $LANGUAGES, $INACTIVE_ONLINE_LANGUAGES;
172-
173-
// Make language code lowercase, html encode special chars and remove slashes
174-
$langcode = strtolower(htmlspecialchars($langcode));
175-
176-
// The Brazilian Portuguese code needs special attention
177-
if ($langcode == 'pt_br') { $langcode = 'pt_BR'; }
178-
179-
// Append language code in priority order if it is not
180-
// there already and supported by the PHP site. Try to
181-
// lower number of file_exists() calls to the minumum...
182-
if (!in_array($langcode, $langs, false) && isset($LANGUAGES[$langcode])
183-
&& !isset($INACTIVE_ONLINE_LANGUAGES[$langcode])) {
184-
$langs[] = $langcode;
185-
}
186-
187-
// Return with language code
188-
return $langcode;
189-
}
190-
191-
// Order the array of compiled
192-
// accept-language codes by quality value
193-
function language_accept_order($a, $b)
194-
{
195-
if ($a[1] == $b[1]) { return 0; }
196-
return ($a[1] > $b[1]) ? -1 : 1;
197-
}

src/LangChooser.php

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
3+
namespace phpweb;
4+
5+
class LangChooser
6+
{
7+
private readonly string $preferredLanguage;
8+
9+
private readonly string $defaultLanguage;
10+
11+
/**
12+
* @param array<string, string> $availableLanguages
13+
* @param array<string, string> $inactiveLanguages
14+
*/
15+
public function __construct(
16+
private readonly array $availableLanguages,
17+
private readonly array $inactiveLanguages,
18+
string $preferredLanguage,
19+
string $defaultLanguage,
20+
)
21+
{
22+
$this->defaultLanguage = $this->normalize($defaultLanguage);
23+
$this->preferredLanguage = $this->normalize($preferredLanguage);
24+
}
25+
26+
/**
27+
* @return array{string, string}
28+
*/
29+
public function chooseCode(
30+
string|array|null $langParam,
31+
string $requestUri,
32+
?string $acceptLanguageHeader,
33+
): array
34+
{
35+
// Default values for languages
36+
$explicitly_specified = '';
37+
38+
// Specified for the request (GET/POST parameter)
39+
if (is_string($langParam)) {
40+
$langCode = $this->normalize(htmlspecialchars($langParam, ENT_QUOTES, 'UTF-8'));
41+
$explicitly_specified = $langCode;
42+
if ($this->isAvailableLanguage($langCode)) {
43+
return [$langCode, $explicitly_specified];
44+
}
45+
}
46+
47+
// Specified in a shortcut URL (eg. /en/echo or /pt_br/echo)
48+
if (preg_match("!^/(\\w{2}(_\\w{2})?)/!", htmlspecialchars($requestUri,ENT_QUOTES, 'UTF-8'), $flang)) {
49+
// Put language into preference list
50+
$rlang = $this->normalize($flang[1]);
51+
52+
// Set explicitly specified language
53+
if (empty($explicitly_specified)) {
54+
$explicitly_specified = $rlang;
55+
}
56+
57+
// Drop out language specification from URL, as this is already handled
58+
$_SERVER['STRIPPED_URI'] = preg_replace(
59+
"!^/$flang[1]/!", "/", htmlspecialchars($requestUri, ENT_QUOTES, 'UTF-8'),
60+
);
61+
62+
if ($this->isAvailableLanguage($rlang)) {
63+
return [$rlang, $explicitly_specified];
64+
}
65+
}
66+
67+
// Specified in a manual URL (eg. manual/en/ or manual/pt_br/)
68+
if (preg_match("!^/manual/(\\w{2}(_\\w{2})?)(/|$)!", htmlspecialchars($requestUri, ENT_QUOTES, 'UTF-8'), $flang)) {
69+
$flang = $this->normalize($flang[1]);
70+
71+
// Set explicitly specified language
72+
if (empty($explicitly_specified)) {
73+
$explicitly_specified = $flang;
74+
}
75+
76+
if ($this->isAvailableLanguage($flang)) {
77+
return [$flang, $explicitly_specified];
78+
}
79+
}
80+
81+
// Honor the users own language setting (if available)
82+
if ($this->isAvailableLanguage($this->preferredLanguage)) {
83+
return [$this->preferredLanguage, $explicitly_specified];
84+
}
85+
86+
// Specified by the user via the browser's Accept Language setting
87+
// Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
88+
$browser_langs = [];
89+
90+
// Check if we have $_SERVER['HTTP_ACCEPT_LANGUAGE'] set and
91+
// it no longer breaks if you only have one language set :)
92+
if (isset($acceptLanguageHeader)) {
93+
$browser_accept = explode(",", $acceptLanguageHeader);
94+
95+
// Go through all language preference specs
96+
foreach ($browser_accept as $value) {
97+
// The language part is either a code or a code with a quality
98+
// We cannot do anything with a * code, so it is skipped
99+
// If the quality is missing, it is assumed to be 1 according to the RFC
100+
if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($value), $found)) {
101+
$quality = (isset($found[3]) ? (float) $found[3] : 1.0);
102+
$browser_langs[] = [$found[1], $quality];
103+
}
104+
unset($found);
105+
}
106+
}
107+
108+
// Order the codes by quality
109+
usort($browser_langs, fn ($a, $b) => $b[1] <=> $a[1]);
110+
111+
// For all languages found in the accept-language
112+
foreach ($browser_langs as $langdata) {
113+
114+
// Translation table for accept-language codes and phpdoc codes
115+
switch ($langdata[0]) {
116+
case "pt-br":
117+
$langdata[0] = 'pt_br';
118+
break;
119+
case "zh-cn":
120+
$langdata[0] = 'zh';
121+
break;
122+
case "zh-hk":
123+
$langdata[0] = 'hk';
124+
break;
125+
case "zh-tw":
126+
$langdata[0] = 'tw';
127+
break;
128+
}
129+
130+
// We do not support flavors of languages (except the ones above)
131+
// This is not in conformance to the RFC, but it here for user
132+
// convenience reasons
133+
if (preg_match("!^(.+)-!", $langdata[0], $match)) {
134+
$langdata[0] = $match[1];
135+
}
136+
137+
$lang = $this->normalize($langdata[0]);
138+
if ($this->isAvailableLanguage($lang)) {
139+
return [$lang, $explicitly_specified];
140+
}
141+
}
142+
143+
// Language preferred by this mirror site
144+
if ($this->isAvailableLanguage($this->defaultLanguage)) {
145+
return [$this->defaultLanguage, $explicitly_specified];
146+
}
147+
148+
// Last default language is English
149+
return ["en", $explicitly_specified];
150+
}
151+
152+
private function normalize(string $langCode): string
153+
{
154+
// Make language code lowercase, html encode special chars and remove slashes
155+
$langCode = strtolower(htmlspecialchars($langCode));
156+
157+
// The Brazilian Portuguese code needs special attention
158+
if ($langCode == 'pt_br') {
159+
return 'pt_BR';
160+
}
161+
return $langCode;
162+
}
163+
164+
private function isAvailableLanguage(string $langCode): bool
165+
{
166+
return isset($this->availableLanguages[$langCode]) && !isset($this->inactiveLanguages[$langCode]);
167+
}
168+
}

0 commit comments

Comments
 (0)