365

I have a website (Flash) localized into a dozen of languages and I want to auto-define a default value depending on the user's browser settings in order to minimize the steps to access the content.

FYI, I cannot use server scripts due to proxy restrictions, so I guess JavaScript or ActionScript would be appropriate to solve the problem.

Questions:

  1. What would be the best method to 'guess' the user's locale?

  2. Are there any existing simple classes/functions that could help me out (no complex localization bundles)? Especially to reduce all possible languages to a smaller number (the translations I have) in a smart way.

  3. To which point can I trust such a solution?

  4. Any other workarounds or suggestions?

Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
asked Mar 23, 2009 at 15:43
1
  • THe only way a browser can share meta data about its user and the request is via URLs and headers. Grab Firefox and take a peek at the headers being sent when a request is made. Its quite interesting to examine typical request and response headers. Commented Jul 6, 2010 at 2:37

11 Answers 11

278

The proper way is to look at the HTTP Accept-Language header sent to the server. This contains the ordered, weighted list of languages the user has configured their browser to prefer.

Unfortunately this header is not available for reading inside JavaScript; all you get is navigator.language, which tells you what localised version of the web browser was installed. This is not necessarily the same thing as the user's preferred language(s). On IE you instead get systemLanguage (OS installed language), browserLanguage (same as language) and userLanguage (user configured OS region), which are all similarly unhelpful.

If I had to choose between those properties, I'd sniff for userLanguage first, falling back to language and only after that (if those didn't match any available language) looking at browserLanguage and finally systemLanguage.

If you can put a server-side script somewhere else on the net that simply reads the Accept-Language header and spits it back out as a JavaScript file with the header value in the string, eg.:

var acceptLanguage= 'en-gb,en;q=0.7,de;q=0.3';

then you could include a <script src> pointing at that external service in the HTML, and use JavaScript to parse the language header. I don't know of any existing library code to do this, though, since Accept-Language parsing is almost always done on the server side.

Whatever you end up doing, you certainly need a user override because it will always guess wrong for some people. Often it's easiest to put the language setting in the URL (eg. http​://www.example.com/en/site vs http​://www.example.com/de/site), and let the user click links between the two. Sometimes you do want a single URL for both language versions, in which case you have to store the setting in cookies, but this may confuse user agents with no support for cookies and search engines.

ashleedawg
21.9k9 gold badges82 silver badges120 bronze badges
answered Mar 23, 2009 at 18:12
Sign up to request clarification or add additional context in comments.

4 Comments

From MDN developer.mozilla.org/en-US/docs/Web/API/NavigatorLanguage/… "The Accept-Language HTTP header in every HTTP request from the user's browser uses the same value for the navigator.languages property except for the extra qvalues (quality values) field (e.g. en-US;q=0.8)."
Update: There is now (2020) an experimental feature supported by all modern browsers that returns an array of language preference: navigator.languages //["en-US", "zh-CN", "ja-JP"] This should work on at least 95% of browsers in 2020.
navigator.languages, according to both MDN and caniuse.com, is now available in all major browsers - try the tiny (~200 bytes) navigator-languages package for the most modern and backwards-compatible approach.
@bora89 Because the accepted answer is from 2009 i.e. 13 years ago. Implementations and documentation tends to change in that span of time you know...
182

On Chrome and Firefox 32+, Navigator.languages contains an array of locales in order of user preference, and is more accurate than navigator.language, however, to make it backward-compatible (Tested Chrome / IE / Firefox / Safari), use:

function getLang() {
 if (navigator.languages !== undefined) 
 return navigator.languages[0]; 
 return navigator.language;
}
answered Jun 30, 2015 at 10:15

8 Comments

This doesn't work for IE9 and IE10. For them, you have to examine navigator.browserLanguage instead.
OneLiner : function getLang(){ return ( navigator.language || navigator.languages[0] ); }
@ChStark Shouldn't it be return navigator.languages[0] || navigator.language;?
Also, the correct "one-liner" would look like this: return (navigator.languages && navigator.languages.length) ? navigator.languages[0] : navigator.language;. Otherwise you get an exception if navigator.languages is undefined or empty.
For an even more compact version const getLang = () => navigator.language || navigator.browserLanguage || ( navigator.languages || [ "en" ] ) [ 0 ]
|
70

There's a difference between the user's preferred languages and the system/browser locale.

A user can configure preferred languages in the browser, and these will be used for navigator.language(s), and used when requesting resources from a server, to request content according to a list of language priorities.

However, the browser locale will decide how to render number, date, time and currency. This setting is likely the highest ranking language, but there is no guarantee. On Mac and Linux, the locale is decided by the system regardless of the user language preferences. On Windows is can be elected among the languages in the preferred list on Chrome.

By using Intl (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl), developers can override/set the locale to use to render these things, but there are elements that cannot be overridden, such as the <input type="date"> format.

To properly extract this language, the only way I've found is:

(new Intl.NumberFormat()).resolvedOptions().locale

(Intl.NumberFormat().resolvedOptions().locale also seems to work)

This will create a new NumberFormat instance for the default locale and then reading back the locale of those resolved options.

answered Aug 16, 2019 at 18:23

3 Comments

Very important answer to add to the picture. For example, in my Chromium navigator.language gives de-DE (which is my system setting), however all dates and number are formatted en-US (which is my browser setting), which this answer correctly determines.
Yeah I want to thank you as well for this answer. At a quick glance it felt like the question was "How can I determine a users Locale within the browser" and the answers were all of the type "Here's how you find the users preferred language instead, hope that helps".
Do they return the same? Intl.NumberFormat().resolvedOptions().locale and Intl.DateTimeFormat().resolvedOptions().locale
59

This article suggests the following properties of the browser's navigator object:

  • navigator.language (Netscape - Browser Localization)
  • navigator.browserLanguage (IE-Specific - Browser Localized Language)
  • navigator.systemLanguage (IE-Specific - Windows OS - Localized Language)
  • navigator.userLanguage

Roll these into a javascript function and you should be able to guess the right language, in most circumstances. Be sure to degrade gracefully, so have a div containing your language choice links, so that if there is no javascript or the method doesn't work, the user can still decide. If it does work, just hide the div.

The only problem with doing this on the client side is that either you serve up all the languages to the client, or you have to wait until the script has run and detected the language before requesting the right version. Perhaps serving up the most popular language version as a default would irritate the fewest people.

Edit: I'd second Ivan's cookie suggestion, but make sure the user can always change the language later; not everyone prefers the language their browser defaults to.

meyi
8,2521 gold badge17 silver badges29 bronze badges
answered Mar 23, 2009 at 15:53

3 Comments

Thanks Phil for your suggestions. Regarding the article, it's 6 years old ... not sure we can rely on it ?
I suspect that modern browsers all support navigator.language. My browser (FF3 on Ubuntu 8.10) reports 'en-GB'. If someone is still using IE6 - around 20% of people - then it's worth allowing for that. IE6 appeared 8 years ago.
IE 8 doesn't support navigator.language. Perhaps IE 9 will?
58

Combining the multiple ways browsers are using to store the user's language, you get this function:

const getNavigatorLanguage = () => {
 if (navigator.languages && navigator.languages.length) {
 return navigator.languages[0];
 } else {
 return navigator.userLanguage || navigator.language || navigator.browserLanguage || 'en';
 }
}

We first check the navigator.languages array for its first element. Then we get either navigator.userLanguage or navigator.language. If this fails we get navigator.browserLanguage. Finally, we set it to 'en' if everything else failed.


And here's the sexy one-liner:

const getNavigatorLanguage = () => (navigator.languages && navigator.languages.length) ? navigator.languages[0] : navigator.userLanguage || navigator.language || navigator.browserLanguage || 'en';
Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answered Aug 31, 2018 at 9:11

5 Comments

there is apparently also something as navigator.userLanguage (for IE). You could add that to make it complete :)
Why not simply navigator.languages[0] || navigator.userLanguage || navigator.language || navigator.browserLanguage || 'en' ?
@Curse Because navigator.languages can be undefined
@Zenoo Do you know on which browsers ?
(navigator.languages || [])[0] || navigator.userLanguage || navigator.language || navigator.browserLanguage || 'en'
26

I used all the answers and created a single line solution:

const getLanguage = () => navigator.userLanguage || (navigator.languages && navigator.languages.length && navigator.languages[0]) || navigator.language || navigator.browserLanguage || navigator.systemLanguage || 'en';
console.log(getLanguage());
answered Apr 13, 2020 at 20:24

1 Comment

What is the gist of it? What is the idea? Why was it combined exactly this and not some other way? What is thinking behind it?
7

I did a bit of research regarding this & I have summarised my findings so far in below table

enter image description here

So the recommended solution is to write a a server side script to parse the Accept-Language header & pass it to client for setting the language of the website. It's weird that why the server would be needed to detect the language preference of client but that's how it is as of now There are other various hacks available to detect the language but reading the Accept-Language header is the recommended solution as per my understanding.

answered May 22, 2018 at 5:46

1 Comment

That is unreadable. Opening it in a separate tab is only slightly better.
5

This TypeScript function combines all the other solutions:

export function determineLocale(): string {
 // All modern browsers support this. Should match what's used by localeCompare() etc.
 const intl = window.Intl;
 if (intl !== undefined) {
 return intl.NumberFormat().resolvedOptions().locale;
 }
 // Fall back to ranked choice locales, which are configured in the browser but aren't necessarily
 // what's used in functions like localeCompare().
 const languages = navigator.languages as (string[] | undefined);
 if (languages !== undefined && languages.length > 0) {
 return languages[0];
 }
 // Old standard.
 return navigator.language ?? "en-US";
}

On my Mac, the first option (from Intl) is from the system (OS-wide) language that I set in System Preferences. The second (the languages array) is from Chrome's Settings' Language list.

answered Jul 24, 2024 at 0:30

Comments

3

You can also try to get the language from the document should might be your first port of call, then falling back to other means as often people will want their JS language to match the document language.

HTML5:

document.querySelector('html').getAttribute('lang')

Legacy:

document.querySelector('meta[http-equiv=content-language]').getAttribute('content')

No real source is necessarily 100% reliable as people can simply put in the wrong language.

There are language detection libraries that might let you determine the language by content.

answered Aug 5, 2019 at 13:29

Comments

2

I am porting this from JavaScript for detecting browser language preference

My answer.

I've just come up with this. It combines newer JS destructuring syntax with a few standard operations to retrieve the language and locale.

var [lang, locale] = (
 (
 (
 navigator.userLanguage || navigator.language
 ).replace(
 '-', '_'
 )
 ).toLowerCase()
).split('_');

I'd also recommend testing this and combining with other answers, such as https://stackoverflow.com/a/52112155/1548557 which seems like a more robust way to get the language. We also have null coalesce ?? so perhaps || is not the best way. I imagine this to be something like

var [lang, locale] = (
 (
 getBrowserLanguage()
 .replace('-', '_')
 ).toLowerCase()
).split('_');

Note that I've not mandated a default value for lang or locale, which you could do. The world is so anglo-centric. I'd pick English for this, but I didn't feel like it would benefit anyone me coding that in. I also did not call getBrowserLanguage getNavigatorLanguage, which was deliberate. Maybe you use navigator, but maybe something else comes up in future. This really is a preference.

Peter Mortensen
31.4k22 gold badges110 silver badges134 bronze badges
answered Jan 21, 2023 at 20:11

Comments

-1

You said your website has Flash, then, as another option, you can get operation system's language with flash.system.Capabilities.language— see How to determine OS language within browser to guess an operation system locale.

answered Jun 5, 2015 at 8:52

1 Comment

Flash is now dead

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.