Auto detect user locale with Zend\Http\Request headers and ext/intl

With the recent beta5 release of Zend Framework 2, a completely new Zend\I18n component is shipped. The component is (without any misunderstanding about the work to get this done) a kind-of helper layer on top of ext/intl, a php extension for all kind of i18n tasks.

Since this Zend\I18n release I was thinking about using the Accept-Language header a browser sends with a request. When there is no Accept-Language header, or all the accepted languages are not in a list of supported languages, a default locale is set. It is really useful here to use the Zend\Http\Header\AcceptLanguage object here, as it returns a Zend\Stdlib\PriorityQueue. You can iterate the queue, having the best-choice language first until you get to the least-best match for a language.

To make this happen, I created a listener in a module for the bootstrap event, to register a default locale as fast as possible. For example the Module class of your Application module:

namespace Application;

use Locale;
use Zend\EventManager\EventInterface;
use Zend\ModuleManager\Feature;

class Module implements
    Feature\BootstrapListenerInterface
{
    public function onBootstrap(Event $e)
    {
        $default   = 'en-GB';
        $supported = array('en-GB', 'en-US', 'en-GB', 'nl-NL', 'nl-NL');
        $app       = $e->getApplication();
        $headers   = $app->getRequest()->getHeaders();

        if ($headers->has('Accept-Language')) {
            $locales = $headers->get('Accept-Language')->getPrioritized();

            // Loop through all locales, highest priority first
            foreach ($locales as $locale) {
                if (!!($match = Locale::lookup($supported, $locale))) {
                    // The locale is one of our supported list
                    Locale::setDefault($match);
                    break;
                }
            }

            if (!$match) {
                // Nothing from the supported list is a match
                Locale::setDefault($default);
            }
        } else {
            Locale::setDefault($default);
        }
    }
}

Perhaps later on I will update it to a decent listener in a separate class. I could then use a factory to inject a default locale and a list of supported locales from the configuration. But for now, it is simple and “good enough”.