We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Locale on routes

I am looking for a quick and reliable solution to simplify my implementation of passing locale / language on routes. Currently I do it like this:

I have a Locale class object that is registered as a service on DI which makes it available in View and Controller. Locale object is configured like this:

Locale::__constructor('<DEFAULT ID>', array(
    '<ID>' => array(
        'locale' => '<LOCALE>',
        'additional' => array(<LIST OF ADDITIONAL LOCALES>)
    )
));
  • ID is a string that identifies locale configuration, it also appears as a slug on the URL: https://site.com/<ID>/blog/article/123. It can be any string like: en, en-au, english etc.
  • LOCALE is actuale locale, en_AU for example.
  • LIST OF ADDITIONAL LOCALES is an array of additional locales that are resolved to the LOCALE
Locale::__constructor('en', array(
    'en => array(
        'locale' => 'en_AU',
        'additional' => array('en_US', 'en')
    ),
    'ru' => array(
        'locale' => 'ru_RU',
        'additional' => array('ru_RU', 'uk_AU', 'mo_RU')
    ),
));

Each route than has "language" parameter:

$router->addGet('/' . $language_segment . '/about', 'Index::about')->setName('about');
$router->addGet('/' . $language_segment . '/contact', 'Index::contact')->setName('contact');
$router->addGet('/' . $language_segment . '/terms-of-service', 'Index::termsOfService')->setName('terms-of-service');

... where $language_segment is {language:en|ru|<ID>}. This is how a route knows about language. Aspecial case is required for handling "/" URL: corresponding controller simply redirects to the URL with the supported or default language that is detected from $_SERVER['HTTP_ACCEPT_LANGUAGE'].

Finally, I setup "dispatch:beforeDispatchLoop" event listener:

$eventsManager->attach('dispatch:beforeDispatchLoop', function(
        Phalcon\Events\Event $event,
        Phalcon\Mvc\Dispatcher $dispatcher,
        Phalcon\Mvc\Dispatcher\Exception $exception = null) use($di) {

        $locale = $di->get('locale');

        // if route has been resolved, then $dispatcher SHOULD have "language" on the array
        // of parameters. Its value is set as default (request) language.

        if (( $language = $dispatcher->getParam('language') ) !== null) {
            $locale->setLocale($language);
        } else {

            // in case of 404, exception etc, request language is detected from the URL
            // *I don't like hardcoded regexp below*

            $url = ( isset($_GET['_url']) ? $_GET['_url'] : $_SERVER['REQUEST_URI'] );

            if ( preg_match('#^/(' . implode('|', $locale->listLanguages()) . ')/#', $url, $matches) ) {
                $locale->setLocale($matches[1]);
            }
        }

        // ... otherwise, default Locale language remains intact
    });

And that's it. In the View I pass value returned by $view->locale->getLanguage() as a parameter to Phalcon\Mvc\Url to create a URL for the current language.

There is also a special implementation in the controller action that handles 404, if locale is not present on the URL, it tries to redirect to the same URL prepended with language.


So do you see anyway to simplify it, e.g., keep everything in one place and avoid working with raw URL using hardcoded regexp when extracting languages? I want to make it universal and release this thing as a component.



2.0k

Did you look at https://github.com/phalcon/website ? its the source code for phalcon website which uses different languages.

Also have you considered making your locale class to extend Phalcon\DI\Injectable ? that way you can easily access other components like url in your class and then just use your class methods at few places in controller/routes.

Maybe I am just pointing out the oblivious here, but might help :)

Thanks for pointing it out. I didn't see Phalcon website code, but language support is implemented in almost the same way there. The only difference, they don't use any Locale object for language detection and setup language in ControllerBase instead of dispatcher. Also they don't handle 404 and failures in different languages, which is required for me.

I though about implementing Phalcon\DI\Injectable. It might have helped me to implement language aware routes and URL, but ... The problem is in loosely coupled framework there is no centralized logic you can hook into or extend. You should put workarounds in a number of places. It makes it harder to setup, but I am staying optimistic and looking for a simpler and better solution.