Language based on HTTP_ACCEPT_LANGUAGE and database languages - php

I have a web site made in 3 languages, but will (maybe) be expanded.
So I created a table on the database where I insert the languages of the Website.
As index.php I have a script. If the URI detected is "/" the script redirects to /language/index.php.
I'd like now to have a script which dinamically redirects based on HTTP_ACCEPT_LANGUAGE.
My problem is that, the following script I made works only with the first foreach run.
$abbr = $link->query("SELECT abbr AS langs FROM LANGS");
while($langs_db = mysqli_fetch_array($abbr)){
$site_langs[] = $langs_db['langs'];
}
$client_lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
foreach($site_langs as $lang){
switch($client_lang){
case $client_lang == $lang:
header("location: http://www.mysite.com/$lang/index.php");
break;
default:
header("location: http://www.mysite.com/$lang/index.php");
break;
}
}
I thought a solution like:
case $client_lang == $lang:
$find "location: http://www.mysite.com/$lang/index.php";
break;
default:
$not_find = "location: http://www.mysite.com/$lang/index.php";
break;
And then:
if($find != ''){
header($find);
}else{
header(not_find);
}
But I'm not sure this it's a good idea...
Thank you!

// Set the default language as a fall back
$default_lang='en';
// Have an array with all languages your site supports
$langs=array('en', 'es', 'pt', /* etc... */);
// Query the browser's first preferred language
$client_lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
// See if the language is supported otherwise redirect use the default one
$client_lang = (in_array($client_lang, $langs) ? $client_lang : $default_lang);
header("Location: /$client_lang/index.php");
Alternatively though you may want to consider gettext:
http://php.net/manual/en/book.gettext.php

Related

How to automatically reflect changes when adding PHP cookie?

My website by default is in English
But I can change the language, when a visitor visits our website with a url of a news item in Spanish:
example.com/es/news/718/url-title-article/
The url given as an example is a news in Spanish since the first folder or directory is named "es" example.com/es/news/718/url-title-article/ -> es
And, that's where the trick comes, since I have a function that allows me to obtain the name of the first folder or directory and if it matches with the given parameters, it should set a cookie with the language of that url, in this case Spanish -> "es".
if($FOLDER_LANG === "es" || $SUBDOMAIN_LANG === "es") {
setcookie ('language', 'es', time()+60*60*24*365, '/', 'example.com');
}
if(isset($_COOKIE['language'])){
if($_COOKIE['language']==='en'){
$WEBSITE_TITLE = " | WebSIte EN";
$LANG = 'en';
$language = "en";
}elseif($_COOKIE['language']==='es'){
$WEBSITE_TITLE = " | WebSite ES";
$LANG = 'es';
$language = "es";
}
} else {
$WEBSITE_TITLE = " | WebSite DEFAULT EN";
$LANG = 'en';
$language = "en";
}
But the problem, is that I have to reload the page to see the changes of the new language added to the site.
So how can you solve it without having to reload the site.
You need to write the logic which determines the language so it doesn't depend on the cookie.
So, before, where you might have had something like:
$language_data = get_i18n_data( $_COOKIES['language'] );
You would instead have something like:
function get_language() {
if($FOLDER_LANG === "es" || $SUBDOMAIN_LANG === "es") {
setcookie ('language', 'es', time()+60*60*24*365, '/', 'example.com');
return 'es'
}
return $_COOKIES['language'];
}
$language_data = get_i18n_data( get_language() );
The reason for refresh being needed is that $LANG and $language is set by an existing cookie and not the new one.
Analysing your code:
if($FOLDER_LANG === "es" || $SUBDOMAIN_LANG === "es") {
setcookie ('language', 'es', time()+60*60*24*365, '/', 'example.com');
}
Here, it's setting the cookie only in the header. It is not accessible until the page is reloaded.
One option is to add the following in the if condition:
$LANG = 'es';
$language = "es";
The rest of your code then reads existing cookies so this is what will load on the same page even if the cookie is set just before it:
A better way in my opinion is to keep it simple and expandable by using variable $language to drive the data:
$language = 'en'; //this is default
if(isset($_COOKIE['language'])){
$language = $_COOKIE['language'];
}
//now check if current $language is different to folder
if($FOLDER_LANG != $language || $SUBDOMAIN_LANG != $language) {
//this is condition depends on your variables. You might need && if both conditions should be met or modify as required
//set the language for this page (so no need for refresh and also set the cookie
$language = $FOLDER_LANG ? $FOLDER_LANG : $SUBDOMAIN_LANG; //sets to folder or sudomain language code if $FOLDER_LANG is empty. You can switch the two folders to priotorise the latter.
//now set the cookie
setcookie ('language', $language, time()+60*60*24*365, '/', 'example.com');
}
//now set the the title and the extra variable $LANG (you probably should choose one variable to use)
$WEBSITE_TITLE = " | WebSite . strtoupper($language)";
$LANG = $language;
Now you can support more languages and also won't need to refresh

Conditional condition in PHP

My project can switch between languages. The items are stored in a database, and using $_GET['lang'] in the database gives back the correct items. For now, only English and French are in use, so it works with this code :
if ($_GET['lang'] == 'fr' OR ($_GET['lang'] == 'en')) {
$header = getTranslation('header', $_GET['lang']);
$footer = getTranslation('footer', $_GET['lang']);
} else {
header('Location: error.php');
}
What I'm looking for is some way to be prepared in case a language is added in the db. The code below approaches what I need (and obviously didn't work).
while ($translations = $languagesList->fetch()) {
if ($_GET['lang'] == $translations['code']) {
$header = getTranslation('header', $_GET['lang']);
$footer = getTranslation('footer', $_GET['lang']);
} else {
header('Location: language.php');
}
}
Is there any way to create a code that would generate multiple if conditions based on the number of languages in the db ?
You should move the else part outside of the loop, as otherwise you will always execute it at some point in the loop iterations. Only when you have iterated through all possibilities, and you still have no match, then you can be sure there to have to navigate to the language page:
$header = null;
while ($translations = $languagesList->fetch()) {
if ($_GET['lang'] == $translations['code']) {
$header = getTranslation('header', $_GET['lang']);
$footer = getTranslation('footer', $_GET['lang']);
break; // No need to waste time to look further.
}
}
if ($header === null) {
header('Location: language.php');
}
But it would be smarter to prepare an SQL statement that gets you the result for the particular language only (with where code = ? and bind $_GET['lang'] to it). Then you don't have to iterate in PHP, which will in general be slower than what the database can provide. Instead you can just see whether you had 0 or 1 records back (Or use select count(*) and check whether that is 0 or not).
Easiest is to have a mapping in an array, check if your language is present there, and if so, do stuff. Something like this;
$languages = array('en', 'fr', 'de', '...');
$default_language = 'en';
$language = in_array($_GET['lang'], $languages) ? $_GET['lang'] : $default_language;
$header = getTranslation('header', $language);
As you can see I've set a default langauge which is used if the language in the query var cannot be found. How to create that array of languages is up to you (a simple query should suffice).

Language redirect works on desktop but not mobile browser

I've put together a small script in PHP that checks for the browser's language settings and redirect them to a language version of the site (WP multisite),
function redirect() {
$language = substr( $_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2 );
switch( $language ) {
case 'sv':
header( 'Location: http://www.example.com/sv/' );
break;
case 'no':
header( 'Location: http://www.example.com/no/' );
break;
case 'da':
header( 'Location: http://www.example.com/da/' );
break;
default:
header( 'Location: http://www.example.com/' );
break;
}
}
if ( strlen($url) < 4 ) {
session_start();
if ( empty($_SESSION[ 'language' ]) ) {
$_SESSION[ 'language' ] = true;
redirect();
}
}
When testing with Mobile Safari or Mobile Chrome the redirect doesn't appear to work. Is there any special output for the accept language for mobile browsers that I need to consider?
Update: After some more debugging I found out this:
Mobile Safari displays the correct language when echoing HTTP_ACCEPT_LANGUAGE but does not redirect.
Mobile Chrome (iOS only, works on Android) doesn't display the correct language (defaults to "en").
iOS parses the http header data in a different order, compare iOS Chrome (en-US,en;q=0.8,sv;q=0.6) and OSX Chrome (sv,en-US;q=0.8,en;q=0.6).
Try this and let us know the output please
function redirect() {
$language = substr( $_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2 );
switch( $language ) {
case 'sv':
header( 'Location: http://www.example.com/sv/' );
break;
case 'no':
header( 'Location: http://www.example.com/no/' );
break;
case 'da':
header( 'Location: http://www.example.com/da/' );
break;
default:
die('Default location');
/* if you get this message on mobile devices, then this line
$language = substr( $_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2 );
is faulty. Perhaps as #chris85 mentioned, HTTP_ACCEPT_LANGUAGE is
not populated so mobile behaves as a default by not redirecting to
other languages. If this is the case, fix that line
and remove the die();*/
header( 'Location: http://www.example.com/' );
break;
}
die(); // leave this one in. It forces the server to flush data to the browser
}
UPDATE to my previous answer
The HTTP_ACCEPT_LANGUAGE is set via headers and will give different values for everyone.
In my case I am in south america on an computer setup in english so my lang headers have english and spanish
settings with a bias towards english.
session_start();
function redirectToLang($langCode){
// use if's instead of switch so that you can
// check exact matches and presence of a substring
if ($langCode == 'sv'){
$langPath = 'sv';
}else if (strpos($langCode, 'en') !== false){ // this would match en, en-CA, en-US
$langPath = 'en';
}else if ($langCode == 'no'){
$langPath = 'no';
}else{
$langPath = 'en';
}
// you should have no output from the server before this line!
// that is no echoes, print_r, var_dumps, headers, etc
header( 'Location: http://www.example.com/' . $langPath .'/' );
die();
}
function parseLang(){
// echo $_SERVER['HTTP_ACCEPT_LANGUAGE']; in my case
// Chrome Mac OS: en,es;q=0.8
// Chrome Android 5.1: en-US;en;q=0.8,es;q=0.6
// IE Windows Phone 8.1: en-US,en;q=0.5
// Safari iOS: en-US
// Chrome iOS: en-US,en;q=0.8
// get the lang and set a default
$lang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : 'en';
// parse the lang code. This can be as simple or as complex as you want
// Simple
$langCode = substr($lang, 0, 2); // in my case 'en'
// Semi Complex (credits to http://www.thefutureoftheweb.com/blog/use-accept-language-header)
$languages = array();
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $lang, $parsed);
if (count($parsed[1])) {
$languages = array_combine($parsed[1], $parsed[4]);
foreach ($languages as $lang => $val) {
if ($val === '') $languages[$lang] = 1;
}
arsort($languages, SORT_NUMERIC);
}
// var_dump($languages); in my case
// array (size=2)
// 'en' => int 1
// 'es' => string '0.8'
$langCode = key($languages); // in my case 'en'
return $langCode;
}
if (!isset($_SESSION['lang'])){
$langCode = parseLang();
$_SESSION['lang'] = $langCode;
redirectToLang($langCode);
}else{
// we already know the language and it is in $_SESSION
// no need to parseLang nor redirect
}
In my case, all devices redirect correctly. My guess is that there is something happening on the logic that calls redirect()
// this part
if ( strlen($url) < 4 ) {
session_start();
if ( empty($_SESSION[ 'language' ]) ) {
$_SESSION[ 'language' ] = true;
redirect();
}
}
and the session var
is bypassing the redirect logic. Try the code above and clear all cookies and sessions from all devices so that the $_SESSION['language'] var you have
set during testing wont mess up the results. Let us know what happens on your end.
I'm quoting..
"A more contemporary method would be to use http_negotiate_language():"
Did you check this one?
Using the PHP HTTP_ACCEPT_LANGUAGE server variable
This works fine on my desktop browsers, and mobile devices. I too was experiencing session problems on devices only and most often, I was relying on a session variable being empty to fulfill the requirements of my condition when in fact the variable was still in existence, or there simply was no session_id() instantiated.
?reset will clear the session.
It also will run the redirect if the language has changed.
<?php
session_start();
if (isset($_REQUEST['reset'])) {
unset($_SESSION);
$_SESSION['PREVIOUS_SESSION'] = '&cleared=1';
}
function redirect($loc) {
$_SESSION[ 'language' ] = true;
$_SESSION['last_language'] = $language;
header( 'Location: ?r='.$loc.$_SESSION['PREVIOUS_SESSION']);
}
$language = substr( $_SERVER["HTTP_ACCEPT_LANGUAGE"],0,2 );
if (( empty($_SESSION[ 'language' ]) ) || ($_SESSION['last_language'] != $language)) {
redirect($language);
}
echo '<pre>';
print_r($_SESSION);
echo '</pre>';
if (!empty($_SESSION['PREVIOUS_SESSION'])) {
unset($_SESSION['PREVIOUS_SESSION']);
}
?>
You should really give us examples of what is the value of $_SERVER["HTTP_ACCEPT_LANGUAGE"] for the three cases.
Anyway, please note that according to the RFC2616 of HTTP/1.1, the choice of a language is much more complicated than just taking the two first chars of the header.
Each language-range MAY be given an associated quality value which
represents an estimate of the user's preference for the languages
specified by that range. The quality value defaults to "q=1". For
example,
Accept-Language: da, en-gb;q=0.8, en;q=0.7
would mean: "I prefer Danish, but will accept British English and
other types of English."
Nothing says that those headers are sorted, nor that the preffered language of the user is the first one in the list. And the language configuration could also not be configured in the browser or OS.
Ideally, to select the best language, you have to parse this header this way:
Split the string on commas
Split every substring found on the semicolon character
When a numeric value is not given, use the default value of 1.0
Sort the result using this numeric value
Compare this list to the list of languages that are available on your website and find the best one.
You really shouldn't rely on getting first two characters. You really need to rely on inspecting the whole string and understanding what the best language selection should be. Those string values have specific meaning, and for example in one of your cases of the "problem" strings, you would actually be doing most appropriate behavior to show en instead of sv. You can obviously write logic to break apart the accept language, investigate the constituent parts, and take appropriate action, but you also might consider using something like:
http_negotiate_language
to do this for you. There are probably dozens of other scripts available from quick google search to really work with this header in a more appropriate fashion than just looking at the two first characters.
Also, you can check out similar question here: Using the PHP HTTP_ACCEPT_LANGUAGE server variable

I Don't want ?lan=AL code in my url php?

I want to change the language without showing the url:
http://myweb.com/?lan=AL
or
http://myweb.com/about-us?lan=AL
How can it be done in background, not to show in url.
This is the code below.
require('_inc_lang/lan_en.php');
require('_inc_lang/lan_al.php');
require('_inc_lang/lan_de.php');
if(!isset($_SESSION['lan'])){
session_start();
}
if(isset($_GET['lan'])){
$_SESSION['lan'] = $_GET['lan'];
}
$lan = isset($_SESSION['lan']) ? $_SESSION['lan'] : 'al';
switch ($lan) {
case 'al':
$TEXT = $TEXT_AL;
break;
case 'de':
$TEXT = $TEXT_DE;
break;
case 'en':
$TEXT = $TEXT_EN;
break;
}
You can do it based on the browser language
<?php
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
switch ($lang){
case "de":
$TEXT = $TEXT_DE;
break;
case "en":
$TEXT = $TEXT_EN;
break;
case "al":
$TEXT = $TEXT_AL;
break;
default:
$TEXT = $TEXT_EN;
}
?>
Change from GET to POST.
<form method="post">
..button/select/whatever have you
</form>
if(isset($_POST) && /* sanitise */)
$_SESSION['lan'] = $_POST['lan'];
Code needs a tidy up but you can do that yourself :) Is this what you are looking for?
Edit:
Absolutely totally must use a link or you will explode? The following SO pages will magically show you how!
Use a normal link to submit a form
How to submit a form with JavaScript by clicking a link?
You're already copying the language choice to the session, so if it is found in the URL, just location: to the version without the URL:
if(isset($_GET['lan'])){
$_SESSION['lan'] = $_GET['lan'];
header("Location: ".$_SERVER['SCRIPT_URI']);
}
Edit: note that I added variable $_SERVER['SCRIPT_URI'] instead of hard coded location; now it will work regardless of where it is called (SCRIPT_URI will give you server/page without query string)

Multilingual Site - Redirection

I am trying to implement a language based redirection. Our site has sub-domains for different languages ie es.domain.com // de.domain.com etc. What i am trying to do is include the following code on all pages
<?php
$lng = array( 'en'=>'www', 'de'=>'de'); // more languages
$defaultLang = 'en'; // Index of the default (and fallback) language
$cookieName = "_dmLang"; // Set up the cookie name
$lang = trim(substr($_GET['lang'], 0, 2));
if (empty($lang)) $lang = trim($_COOKIE[$cookieName]);
if (empty($lang)) $lang = trim(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2));
if (empty($lang) || empty($lng[$lang])) $lang = $defaultLang;
setcookie($cookieName, $lang, time()+3600*24*30);
header("HTTP/1.1 301 Moved Permanently");
header("Location: http://".$lng[$lang].".domain.com" . $_SERVER['REQUEST_URI']);
?>
The language will change when you call /?lang=de
Are there any server/load implications when using this code? Is there a better way to implement this?

Categories