Locale Switching for Region Specific Content in Apache

Published

We launched a website a few months ago that featured region specific content for users in North America, Europe, India, and China.

Our strategy was to redirect users to the appropriate site given their location, which we referred to this as the user’s “default locale”.

Locale Switcher deployed as part of the CasellaSolutions.com website

Locale switcher created for the Casella Solutions site

Once the user had been presented with their default locale, they could choose a “preferred locale” using a locale switcher on the website (an example shown above).

Below is the resulting order of precedence used by the web server (Apache) when determining which locale to serve to the user:

  1. User’s Preferred Locale – detected by the presence of a “preferred locale” cookie on requests
  2. User’s Default Locale – determined by examining the “viewer location” request header attached by our CDN
  3. Default Site – served if no locale cookie or pertinent location header was present

While this was the best option given our situation, other alternatives for Content Negotiation exist, with some detailed below.

Determining a User’s Default Locale

1. The browser’s “Accept-Language” header

The “Accept-Language” header included on requests specifies which languages are preferred by the user’s browser (presumably derived from the operating system’s locale settings). This header can be examined by Apache rewrite rules or other dynamic scripts to assess which content to display to the user.

Screen grab of the Chrome Developer Tools with a request selected and the "accept-language" header highlighted.

2. Location Headers provided by CDNs

Many of the major CDNs can attach headers to inbound HTTP requests that specify the country of origin for the request (including Amazon CloudFront1 and Cloudflare2). These headers can be read by Apache or other parts of the infrastructure when determining which version of content to serve to the user.

Below is an example of Apache rewrite rules that redirect users based on the “CLOUDFRONT-VIEWER-COUNTRY” header. The UK site is the default locale, with US and CN specific redirects employed for those locations.

# Default - redirect to UK if location is not US or CN
RewriteCond %{REQUEST_URI} ^(/)?$
RewriteCond %{HTTP:CLOUDFRONT-VIEWER-COUNTRY} !^$
RewriteCond %{HTTP:CLOUDFRONT-VIEWER-COUNTRY} ^!US$
RewriteCond %{HTTP:CLOUDFRONT-VIEWER-COUNTRY} ^!CN$
RewriteRule ^.*$ /uk/en.html [R=302,L]

# redirect to United States site if browsing from US
RewriteCond %{REQUEST_URI} ^(/)?$
RewriteCond %{HTTP:CLOUDFRONT-VIEWER-COUNTRY} !^$
RewriteCond %{HTTP:CLOUDFRONT-VIEWER-COUNTRY} ^US$
RewriteRule ^.*$ /us/en.html [R=302,L]

# redirect to Chinese site if browsing from CN
RewriteCond %{REQUEST_URI} ^(/)?$
RewriteCond %{HTTP:CLOUDFRONT-VIEWER-COUNTRY} !^$
RewriteCond %{HTTP:CLOUDFRONT-VIEWER-COUNTRY} ^CN$
RewriteRule ^.*$ /cn/en.html [R=302,L]

Notice that temporary 302 redirects are used here; while preferred for SEO purposes, 301 redirects are cached by browsers and will prevent users from being redirected correctly if their location or preferences change (e.g. cookie value changes when selecting a different preferred locale).

3. IP Address Geo-location Services

Efforts can be taken to map a user’s IP address to their geographical location, revealing which country they are browsing from. The Maxmind Geo IP database is the defacto open source tool for accomplishing these lookups. It has its own Apache module available, along with open source libraries to facilitate it’s use in several back-end languages such as PHP or NodeJS.

Other services exist, with some examples listed in this thread.

Enforcing the User’s Preferred Locale

One approach for implementing “sticky” Locale Switching is to use JavaScript to set a cookie containing the user’s “preferred locale”. This cookie will then be read by Apache, and requests will be directed to the correct locale as needed.

For example, if the user selects China as their preferred locale, but then makes a request for the US homepage, Apache will see the cookie specifying CN as the preferred locale and automatically redirect the user to the Chinese homepage.

Here is an example JavaScript snippet that can be used to set the user’s preferred locale into a “preferred_locale_code” cookie:

// determine expiration date
var date = new Date()
date.setTime(date.getTime() + (180*24*60*60*1000)) // expires in 180 days

// set cookie containing preferred locale
document.cookie = "preferred_locale_code="+ preferredLocaleString + "; expires=" + date.toUTCString() + "; path=/"

Once the cookie is set, it will be passed to the web server, which will redirect requests to the preferred locale if needed. Here is a set of Apache rules to perform redirection based on the “preferred_locale_code” cookie:

# redirect to UK based off set cookie

  # request made to the domain root
  RewriteCond %{REQUEST_URI} ^/$
  RewriteCond %{HTTP_COOKIE} preferred_locale_code=([A-Z][A-Z])
  RewriteCond %1 ^UK$
  RewriteRule (.*) /uk/en.html [R=302,L]

  # with path
  RewriteCond %{REQUEST_URI} ^/(?!uk).*/en.*
  RewriteCond %{HTTP_COOKIE} preferred_locale_code=([A-Z][A-Z])
  RewriteCond %1 ^UK$
  RewriteRule ^(.*)/en(.*) /uk/en$2 [R=302,L]

# redirect to US based off set cookie

  # request made to the domain root
  RewriteCond %{REQUEST_URI} ^/$
  RewriteCond %{HTTP_COOKIE} preferred_locale_code=([A-Z][A-Z])
  RewriteCond %1 ^US$
  RewriteRule (.*) /us/en.html [R=302,L]

  # with path
  RewriteCond %{REQUEST_URI} ^/(?!us).*/en.*
  RewriteCond %{HTTP_COOKIE} preferred_locale_code=([A-Z][A-Z])
  RewriteCond %1 ^US$
  RewriteRule ^(.*)/en(.*) /us/en$2 [R=302,L]

# redirect to CN based off set cookie

  # request made to the domain root
  RewriteCond %{REQUEST_URI} ^/$
  RewriteCond %{HTTP_COOKIE} preferred_locale_code=([A-Z][A-Z])
  RewriteCond %1 ^CN$
  RewriteRule (.*) /cn/en.html [R=302,L]

  # with path
  RewriteCond %{REQUEST_URI} ^/(?!cn).*/en.*
  RewriteCond %{HTTP_COOKIE} preferred_locale_code=([A-Z][A-Z])
  RewriteCond %1 ^CN$
  RewriteRule ^(.*)/en(.*) /cn/en$2 [R=302,L]

To give these rules precedence, place them ahead of any other redirect logic (e.g. rules detecting a user’s default locale from location headers).

If using a CDN or reverse-proxy, make sure it is configured to pass the “preferred_local_code” cookie on through the web server, otherwise the redirection logic will not work!

Wrapping Up

With these Apache rules in place, we were able to route the user to either their preferred locale, or direct them to the appropriate default site. This solution worked well for us because our locale-specific sites all had the same content structure. However, these rules would be problematic for sites with drastically different content, as Apache would try to redirect to non-existent pages when locale switching.

References

  1. Deliver Custom Content With CloudFront
  2. What does Cloudflare IP Geolcation do?