Browser language detection and redirect

Hi there,

I am lost with old posts and guides which don’t match the current Cloudflare dashboard. Can someone please help on how I can solve the below issue?

I have 2 languages on my website (example.com) hosted with Cloudflare pages.

example.com/
example.com/fr

I want the Cloudflare to detect the preferred browser language and redirect the user to the appropriate language. For example, if someone has French setup as their preferred language, they go to /fr, else they go to the main homepage.

How can I do this step-by-step code if you can please provide it?

Thanks

Hi,

I’m using Cloudflare workers to create a page and detecting browser language this way:

addEventListener("fetch", (event) => {
  event.respondWith(
    handleRequest(event.request).catch(
      (err) => new Response(err.stack, { status: 500 })
    )
  );
});

const html = `<script>

var userLang = Intl.DateTimeFormat().resolvedOptions().locale;
var urls = {
    'fr-FR': '//toto.com/fr/',
    'fr':    '//toto.com/fr/',
    'de-DE': '//toto.com/de/',
    'de': '//toto.com/de/',
};
var defaultUrl = '//toto.com/en/';

var url = urls[userLang] ?? defaultUrl;

window.location.href = url;
</script>
`

async function handleRequest(request) {
  return new Response(html, {
    headers: {
      "content-type": "text/html;charset=UTF-8",
    },
  })
}

addEventListener("fetch", event => {
  return event.respondWith(handleRequest(event.request))
})
1 Like

Hi @Thibault

Thanks for the detailed response, i was reading a blog of cloudflare showing how can one simply use the Cloudflare redirects which have multiple options like browser language etc.

Using workers need an expert level i was thinking to use the basic Cloudflare redirects in Cloudflare dashboard.

Are you aware of this option and do you know how can i make it work with the existing updated Cloudflare dashboard.

Thanks

I did this code long time ago so maybe they have introduced new ways to do it directly within dashboard but I’m not aware of this… Sorry!

Also I would think if it’s in the dashboard it should be easy to do it?

1 Like

Hi @Thibault

It seems easy but i am lost on what exactly i need to target? the code for targeting browser language.

Thanks

The blog post you mentioned seems to clearly explain it in Localized redirects.

Hi @Thibault

I am unable to understand that blog as the dashboard now looks different and the options seems to be changed and i am unable to understand how to do this now.

Thanks

You can use a Dynamic Redirect.

The example below will redirect all requests to example.com/fr/index.html where Accept-Language: fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5 (for example).

(Accept-Language is what the browser creates and sends as a HTTP request header; we parse this as a zero-based array, e.g. http.request.accepted_languages[1] would be “en” in the above example.

1 Like

Hi @smarsh

Thanks for the great response. I have a follow up question, what are the q= values?

Accept language someone might have english and french. Is it possible to say if prefered language french then redirect to french. Also will the above example work for homepage only or all pages? For example

if prefered language french then redirect:

example.com/ >> to >> example.com/fr/
example.com/contact/ >> to >> example.com/fr/contact/

Thanks again for taking the time to help.

q= is basically the priority/preference set by the browser. In my example above, the user has set their preferred language to French ( fr;q=0.9 ).
If you implement the rule in my screenshot, then it will do as you want i.e.

  1. User preferred language: French
    Request to: example.com/ redirects to example.com/fr/
    Request to example.com/contact redirects to example.com/fr/contact
1 Like

Thanks @smarsh your are a legend :man_superhero:

1 Like

Hi @smarsh

if i use the code as shown in the image i recieve this error:

'(starts_with(http.request.accepted_languages[0],"en") and not starts_with(http.request.uri.path,"/en")) or (starts_with(http.request.accepted_languages|0],"fr") and not starts_with(http.request.uri.path,"/fr"))' is not a valid value for expression because the expression is invalid: Filter parsing error (2:14): (starts_with(http.request.accepted_languages|0],"fr") and not starts_with(http.request.uri.path,"/fr")) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid type of argument #0: expected value of type {Type(Bytes)}, but got Array(Bytes)

1 thing i forgot to mention is that the english language do not have /en it is the default language so it starts with / and only the secondary languages like french spanish have a name after slash.

Thanks again for taking the time to help

I suspect its a formatting error, as i get the same when i copy/paste your code.
Try this, its working for me in my rule:

(starts_with(http.request.accepted_languages[0],"en") and not starts_with(http.request.uri.path, "/en")) or
(starts_with(http.request.accepted_languages[0],"fr") and not starts_with(http.request.uri.path, "/fr"))
1 Like

For that then, just remove the ‘en’ parts and replace with ‘es’ etc. That way traffic with a preference of English dont get redirected.

1 Like

Thanks @smarsh

This worked but a new issue comes up the website is not redirecting to /fr but instead redirecting to /fr-FR if i change vpn location to canada for example it redirect to /fr-CA

What can i do to remove the -country code thanks

You could choose to create one dynamic redirect per language and hardcode the language in the expression, i.e.

You can extend the expression to add more variants of ‘fr-’ if you like.

2 Likes

Thanks @smarsh

Perfect solution for now. But this is going to eat my dynamic redirect limit very fast as just french and spanish combined have a total of = 25 redirect :stuck_out_tongue:

// Spanish 20
es-AR
es-BO
es-CL
es-CO
es-CR
es-DO
es-EC
es-ES
es-GT
es-HN
es-MX
es-NI
es-PA
es-PE
es-PR
es-PY
es-SV
es-US
es-UY
es-VE

// French 5
fr-BE
fr-CA
fr-CH
fr-FR
fr-LU
fr-MC

I hope in the future there maybe a even better solution to save some manual work

Thanks alot for your great help

Should all “start with” a string like which contain fr- or es-? :thinking:
Therefore, you could catch them “all in one” maybe, or that’s just my imagination if there could be done something with contains? :sweat_smile:
Or you have a case where each of them is actually different in writing/spelling and has to have it’s own page?

1 Like

Ah, yes fair point! starts_with(fr will cover all variants of fr-

2 Likes

any-key

Where i put the starts_with(fr value