9

I have a PHP back-end to which several types of devices communicate via a public API. Requests normally contain a required language for the response (e.g. 'en' or 'fr' or 'ru', etc.) - but not the full locale. This has worked fine for everything I needed over the last couple of years. However now I need to include date information in the response - and need a locale to format dates.

How can I get a locale from the language? I do understand that a language is not enough to uniquely identify a locale, but I need at least something. If I can get at least one locale, e.g. en_GB for 'en' or 'ru_RU' for 'ru', etc. - that would be sufficient.

hakre
  • 193,403
  • 52
  • 435
  • 836
Aleks G
  • 56,435
  • 29
  • 168
  • 265

3 Answers3

6

Having thought more about the problem and the particular setup I have, I came up with this solution, which seems to work. Note that I don't have control over what languages I need to support: there are translation files dropped into a predefined place and system locales installed by someone else. During runtime, I need to support a particular language if corresponding translation file exists and and system locale is installed. This got me to this solution:

function getLocale($lang)
{
    $locs = array();
    exec('locale -a', $locs);

    $locale = 'en_GB';
    foreach($locs as $l)
    {
        $regex = "/$lang\_[A-Z]{2}$/";
        if(preg_match($regex, $l) && file_exists(TRANSROOT . "/$lang.php"))
        {
            $locale = $l;
            break;
        }
    }

    return $locale;
}

I'm defaulting to en_GB if I cannot resolve a locale, because I know for certain that en_GB is installed (we're located in the UK as are our servers).

Jake N
  • 10,535
  • 11
  • 66
  • 112
Aleks G
  • 56,435
  • 29
  • 168
  • 265
  • Didn't see your answer was coming up with something similar, but wrapped into a `System` class encapsulating the `System->getLocales()` method. But it works identically. – hakre Dec 31 '11 at 14:24
1

Do I understand you right that you need just a map from language (like en) to locale (like en_GB)? If so, you can compile one for the languages you use:

$localeMap = array(
    'en' => 'en_GB',
    'fr' => 'fr_FR',
    # ...  
);

# Usage:

$locale = $localeMap[$lang];

But this is so trivial that I'm unsure if I properly understood your question.

If you are unsure what the locale is for a language, just take the language itself which should be a valid locale, just without a country:

l => ll

As long as the language is in the two letter format looks fine to me (ISO standard 639, "Code for the representation of names of languages"), See as well Tags for the Identification of Languages (RFC 1766).

$locale = isset($localeMap[$lang]) ? $localeMap[$lang] : $lang;

However this can differ depending which kind of locale format the function you're using is expecting.


class System
{
    /**
     * @return array
     */
    public function getLocales()
    {
        $locales = array();
        exec('locale -a', $locales, $retCode);
        !$retCode || $locales = array();
        return $locales;
    }
    /**
     * @return string something matching 
     * @note Improve by creating your system aware locale class to move
     *       away the responsibility to map system locales onto strings
     *       of certain kinds and to deal with the different locale types.
     */
    public function getLocaleByLang($lang)
    {
        ...
    }
}


$lang = ...
$system = new System;
$locale = $system->getLocaleByLang($lang);
hakre
  • 193,403
  • 52
  • 435
  • 836
  • Yes, this was my first thought, however I don't know in advance all the languages I use, as the translations are provided by a third party and locales are installed on the system by a system administrator. So, in theory, I would have to dynamically enumerate available locales at runtime and pick one up at that stage. Which, in itself, may not be such a bad idea, actually... – Aleks G Dec 20 '11 at 09:30
  • Which kind of locale format are you using exactly? For which purpose/in which functions? See as well [Tags for the Identification of Languages (RFC 1766)](http://www.ietf.org/rfc/rfc1766.txt) – hakre Dec 20 '11 at 11:26
  • Thanks for clarifications. My languages are in the standard two-letter format. I need the locale for `setlocale` before calling `strftime` – Aleks G Dec 20 '11 at 12:11
1

Perhaps you could use $_SERVER['HTTP_ACCEPT_LANGUAGE']; instead of the supplied language in order to get the locale. Quite often, this will contain the actual locale with a hyphen instead of an underscore.

Example $_SERVER['HTTP_ACCEPT_LANGUAGE']:

en-ca,en;q=0.8,fr-ca;q=0.5,fr;q=0.3

Just get everything before the first , and you're all set. This is also better than populating an array of locales because it lets the browser tell the server what language it prefers, not the other way around, however an array as in hakre's answer should be used as a backup system in case an invalid/empty/missing $_SERVER['HTTP_ACCEPT_LANGUAGE'] is provided.

Something else you may want to look into are the locale class and the perhaps the locale::acceptFromHttp method

Also, you may want to consider modifying your API to allow users to explicitly set their locale, just make sure it will gracefully fall back to whatever system you choose to implement now.

Mike
  • 23,542
  • 14
  • 76
  • 87
  • Thanks for your answer. This could be a nice trick, however it won't work for all cases. Imagine a web site where there's a language selection at the top. I may be sitting in England at a computer with only English as the 'accepted_language', however I switch the site to French. I would be expecting results in French, however both `$_SERVER['HTTP_ACCEPT_LANGUAGE']` and `Locale::acceptFromHttp` method would return `en_GB`. Of course, if everything was as simple as a website, I wouldn't have a problem. – Aleks G Dec 20 '11 at 09:30