views:

798

answers:

4

Is there an easy way to parse the user's HTTP_ACCEPT_LANGUAGE and set the locale in PHP?

I know the Zend framework has a method to do this, but I'd rather not install the whole framework just to use that one bit of functionality.

The PEAR I18Nv2 package is in beta and hasn't been changed for almost three years, so I'd rather not use that if possible.

Also nice would be if it could figure out if the server was running on Windows or not, since Windows's locale strings are different from the rest of the world's... (German is "deu" or "german" instead of "de".)

+2  A: 

Nice solution is on its way.

Without that you'll need to parse that header. It's a comma-separated list of semicolon-separated locales and attributes.

It can look like this:

en_US, en;q=0.8, fr_CA;q=0.2, *;q=0.1

and then try each locale until setlocale() accepts it. Be prepared that none of them may match.

Don't base on it anything too important or allow users to override it, because some users may have misconfigured browsers.


For Windows locale, perhaps you need to convert ISO 639-1 names to ISO 639-2/3?

porneL
+4  A: 

It's not as easy as it should be (in my humble opinion). First of all you have to extract the locales from the $_SERVER['HTTP_ACCEPT_LANGUAGE'] and sort them by their q values. Afterwards you have to retrieve the appropriate system locale for each of the given locales, which should be no problem on a *nix machine (you only might have to cope with the correct charset) but on Windows you'll have to translate the locales into Windows locales, e.g. de_DE will be German_Germany (again you also have to cope with charset issues if you're using UTF-8 in your app for example). I think you'll have to build a lookup table for this issue - and there are a lot of locales ;-)

No you try one locale after the other (sorted with descending q values) until you find a match using setlocale() (the function will return false if the given locale could not be set).

But then there will be a last obstacle to cope with:

The locale information is maintained per process, not per thread. If you are running PHP on a multithreaded server api like IIS or Apache on Windows you may experience sudden changes of locale settings while a script is running although the script itself never called setlocale() itself. This happens due to other scripts running in different threads of the same process at the same time changing the processwide locale using setlocale().

(see: http://de2.php.net/manual/en/function.setlocale.php)

This means that you could experience sudden locale changes during the execution of a script because another user with a different locale set just hit your webpage.

Therefore the mentioned Zend_Locale does not rely on the PHP function setlocale() (it's only used to retrieve the system locale information) but instead uses a system based on the data provided by the Unicode CLDR Project. This makes the component independent from all those setlocale() issues but this also introduces some other deficiencies such as the lack of support for locale-aware string operations (sorting for example).

Stefan Gehrig
+1  A: 

I know the Zend framework has a method to do this, but I'd rather not install the whole framework just to use that one bit of functionality.

The good news about Zend is, you don't need to install it all. It's a losely coupled framework and you can just make use of Zend_Locale without using any of the other components. Maybe you want to combine it with Zend_Translate.

Check it out

tharkun
+1  A: 

There's http_negotiate_language, but it depends on the http-extension. Alternatively, see the comments on the manual page, for a userland implementation.

troelskn