Htaccess cookie+browser language redirection

245 Views Asked by At

We are trying to solve language redirection "the right way" over at https://guestbell.com/. Some idioms first:

a) Each route has a starting URL parameter that identifies the language. e.g. https://guestbell.com/en for English and https://guestbell.com/es for Spanish. There are also https://guestbell.com/en/pricing etc.

b) You can also omit this parameter, e.g. https://guestbell.com/pricing . The language is then detected (cookie, browser-language, qs param or URL param) and added to the URL. Page is SPA in react, the detection is done by i18next library.

c) Every possible page is pre-rendered in HTML files that are served via static server.

Note that because the routes are pre-rendered, routes like https://guestbell.com/pricing doesn't in fact exist in the folder structure (because it's impossible to guess the language prior to front end detection)

What works so far:

  1. You navigate to guestbell.com
  2. You are redirected to https via htaccess
  3. If the file is found, serve it.
  4. If the file is not found, serve a PHP file that is written as follows:
<?php
$cookieName = "i18next";
$path = rtrim(strtok($_SERVER["REQUEST_URI"], '?'), '/');
$supportedLangs = [
  'en',
  'es',
];
$defaultLang = $supportedLangs[0];
$lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
if(isset($_COOKIE[$cookieName])) {
  $lang = $_COOKIE[$cookieName];
}

$finalLang = $lang;
if (!in_array($lang, $supportedLangs)) {
  $finalLang = $defaultLang;
}

$newPath = $finalLang . $path . '.html';
if (file_exists($newPath) || empty($path)) {
  $newPath = $finalLang . $path;
  header("Location: $newPath", true, 302);
} else {
  $newPath = $finalLang . '/404';
  header("Location: $newPath", true, 302);
}
?>

As you can see, it attempts to detect via cookie or browser language (we know that by this point, the URL param is not present)

This approach works fine but there is one issue.

When navigating to guestbell.com (as most people would), this results into 2 redirects:

  1. HTTP => HTTPS
  2. / => /en

Ideally, I would like to eliminate this added overhead and do it in one redirect. The only way (that I can imagine at the moment) is to do it via htaccess. The issue is I have no idea if this is possible.

This is the current htaccess for completion sake:

ErrorDocument 404 /404.html
#This is extremely important as it disables rewriting route from en => en/ and then 403-ing on directory 
#https://stackoverflow.com/questions/28171874/mod-rewrite-how-to-prioritize-files-over-folders
DirectorySlash Off

# BEGIN WWW omit
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
    RewriteRule ^ %{REQUEST_SCHEME}://%1%{REQUEST_URI} [R=301,L]
</IfModule>
# END WWW omit

# BEGIN HTTPS redirect
<IfModule BEGIN HTTPS redirectfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /

    RewriteCond %{ENV:HTTPS} !=on
    RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI} [R,L]
</IfModule>
# END HTTPS redirect

# BEGIN Omit extension
<ifModule mod_rewrite.c>
    #remove html file extension-e.g. https://example.com/file.html will become https://example.com/file
    RewriteEngine on 
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^([^\.]+)$ $1.html [NC,L]
</ifModule>
# END Omit extension

# BEGIN File detection
<ifModule mod_rewrite.c>
    RewriteEngine On  
    # If an existing asset or directory is requested go to it as it is
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]  
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d  
    RewriteRule ^ - [L]

    # If the requested resource doesn't exist, use index.php - that file then takes care of language redirection
    RewriteRule ^ /index.php
</ifModule>
# END File detection

# BEGIN Compress text files
<ifModule mod_deflate.c>
  <filesMatch "\.(css|js|x?html?|php)$">
    SetOutputFilter DEFLATE
  </filesMatch>
</ifModule>
# END Compress text files

# BEGIN Cache
<ifModule mod_headers.c>
  <filesMatch "\\.(ico|pdf|flv|jpg|jpeg|png|gif|swf|svg|mp4)$">
    Header set Cache-Control "max-age=31536000, public"
  </filesMatch>

  <filesMatch "\\.(css)$">
    Header set Cache-Control "max-age=31536000, public"
  </filesMatch>

  <filesMatch "\\.(js)$">
    Header set Cache-Control "max-age=31536000, private"
  </filesMatch>

  <filesMatch "\\.(xml|txt)$">
    Header set Cache-Control "max-age=2592000, public, must-revalidate"
  </filesMatch>

  <filesMatch "\\.(html|htm|php)$">
    Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
  </filesMatch>

  <filesMatch "sw.js$">
    Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
  </filesMatch>
</ifModule>
# END Cache

An alternative would be to leave the language detection to front-end in such cases, and thus losing the prerendering altogether. I don't like this too much as majority of people would navigate to root of the page instead of /en and therefore lose performance. But what worries me is that the performance will be lost anyways due to multiple redirect.

My question stands: Is it possible to do cookie and browser-language redirection combined with HTTP => HTTPS inside htaccess? If so, could you provide any help in achieving such functionality? If not, could you share the best way of achieving this, or optionally verify that our approach using PHP is "good enough"?

Many thanks.

0

There are 0 best solutions below