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

Multilingual routing

Hi,

I've just started out with Phalcon, the first PHP framework I've ever used. I'm currently developing on a site which will be in both English and Swedish. The English site will feature four different pages being: Home, About, Contact and Extras. The Swedish site will be exactly the same but with: Hem, Om, Kontakt and Extra instead. I want the url to look like this:

https://baseurl/sv/hem https://baseurl/sv/om https://baseurl/sv/kontakt https://baseurl/sv/extra

https://baseurl/en/home https://baseurl/en/about https://baseurl/en/contact https://baseurl/en/extras

I want to use one controller per site and language meaning that both: https://baseurl/sv/home and https://baseurl/en/home goes to the same controller.

What would be the best way to achive this? I've looked into routing and tested this piece of code:

$router->add(
    "/sv/om/:action",
    array(
        "language" => "sv",
        "controller" => "about",
        "action"     => 2,
    )
);

Even though this would work just fine, you would have to add every single controller, I would instead prefer to have some sort of configuration file like the following array:

$sites = array(
    "hem" => "home",
    "om" => "about",
    "kontakt" => "contact",
    "extra" => "extras"
);

To handle this instead. I've also looked into using convert for the route but I don't know how to implement that.

Much help is appreciated! Also great work with this awesome framework, I'm looking forward to dig deeper in to this.



39.3k
Accepted
answer

You can do something like this:

// This will get you straight to home
$router->add(
    '/{language:[a-z]{2}}',
    array(
        'controller' => 'index',
        'action'     => 'index',
    )
);

$router->add(
    '/{language:[a-z]{2}}/{pageSlug:(home|about|contact|extras|hem|om|kontakt|extra)}',
    array(
        'controller' => 'index',
        'action'     => 'page',
    )
);

Then in your index controller you have the following actions

class IndexController extends \Phalcon\Mvc\Controller
{
    public function indexAction()
    {
        $lang   = $this->dispatcher->getParam('language');
        $lang   = ($lang != 'sv' && $lang != 'en') ? 'en' : $lang;
        $action = ($lang == 'sv') ? 'hem' : 'home';
        $this->response->redirect($lang . '/' . $action);
    }

    public function pageAction()
    {
        $lang   = $this->dispatcher->getParam('language');
        $page   = $this->dispatcher->getParam('pageSlug');
        $action = '';
        switch ($page) {
            case 'home':
            case 'hem':
                $action = 'home';
                break;
            case 'about':
            case 'om':
                $action = 'about';
                break;
            case 'contact':
            case 'kontakt':
                $action = 'contact';
                break;
            case 'extras':
            case 'extra':
                $action = 'extras';
                break;
        }

        // Forward to the relevant action
        $this->dispatcher->forward(
            array(
                'controller' => 'index',
                'action'     => $action
            )
        );
    }

    public function homeAction()
    {
        // Home stuff goes here
    }

    public function aboutAction()
    {
        // About stuff goes here
    }

    public function contactAction()
    {
        // Contact stuff goes here
    }

    public function extrasAction()
    {
        // Home stuff goes here
    }
}
edited Mar '14

Thank you so much for your reply Nikolaos!

$this->dispatcher->forward

was exactly what I needed!

My router now looks like this:

 # /public/index.php
$router->add("(/.+)?", array(
    'controller' => 'index',
    'action' => 'routing'
));

$router->add("/([a-z]{2})(/?)", array(
    'redirectLanguage' => 1,
    'controller' => 'index',
    'action' => 'routing'
));

$router->add("/([a-z]{2})/:controller(/?)", array(
    'redirectLanguage' => 1,
    'redirectController' => 2,
    'controller' => 'index',
    'action' => 'routing'
));

$router->add("/([a-z]{2})/:controller/:action(/?)", array(
    'redirectLanguage' => 1,
    'redirectController' => 2,
    'redirectAction' => 3,
    'controller' => 'index',
    'action' => 'routing'
));

Everything is sent to the routing action in my index controller which looks as following:

<?php
 # /app/controllers/IndexController.php

class IndexController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {

    }

    public function routingAction() 
    {   
        // Get entered language.
        $language   = $this->dispatcher->getParam('redirectLanguage');

        // Get entered controller.
        $controller   = $this->dispatcher->getParam('redirectController');

        // Get entered action.
        $action = $this->dispatcher->getParam('redirectAction');

        // Load global config.
        $config = $this->config;

        // Check if the language is available.
        if (!array_key_exists($language, (array) $config->sites)) {

            $this->response->redirect('');

        } else {

            if (!array_key_exists($controller, (array) $config->sites->$language)) {

                reset($config->sites->$language);
                $this->response->redirect($language . '/' . key($config->sites->$language) );

            } else {

                // Forward to the relevant action
                $this->dispatcher->forward(
                    array(
                        'controller' => $config->sites->$language->$controller,
                        'action'     => $action
                    )
                );

            }

        }

    }

}

This takes an array from my config file which looks like this

<?php
 # /app/config/config.php

$settings = array(

    "sites" => array(

        "sv" => array(
            "hem" => "home",
            "om" => "about",
            "kontakt" => "contact",
            "extra" => "extras"
        ),
        "en" => array(
            "home" => "home",
            "about" => "about",
            "contact" => "contact",
            "extras" => "extras"
        )

    )

);

It might not be the best way to do it but it works as it should.

Once again thank you so much for your fast reply, I went ahead and donated some to Phalcon!



26.3k

@Krokodilen

Hi, I know the topic is solved but I am analyzing the code here and have a question.

What is the goal of (/?) and (/.+)? in your routes? Could you explain it please?

I assume it is some substitution of simple /, am I right? What is the difference?

(/?) Allows for a trailing slash in the end of the URL, that way domain.com/en/about/ will still lead to the AboutController. Looking back at my code I realize that the regex grouping isn't necessary and simply /? will work as well. Using only / would require a slash to be trailing the URL, adding ? makes it optional. (/.+)? Will match a slash and any following text, once again the grouping isn't necessary and /.+ will work as fine.

I hope this explained everything, otherwise reply and I will try to explain it better.

Cheers, Jens.



26.3k

Thank you for your explanation. I think now it's clear for me. I checked some docs and found that Phalcon can remove this slashes automatically by:


$router->removeExtraSlashes(true);

It's here, https://docs.phalcon.io/en/latest/reference/routing.html#dealing-with-extra-trailing-slashes

May be helpfull sometime in the future. Thanks!