Multi-language website with translated URI


#1

Hello to everyone,
I’m new here; I’m from Geneva (speaking french, so sorry for my approximate english!), and I’ve started to learn Slim3 for few months. I would like to have your advices to start a multilingual website with that kind of structure :

http://127.0.0.1/
http://127.0.0.1/en
http://127.0.0.1/fr
http://127.0.0.1/about
http://127.0.0.1/a-propos
http://127.0.0.1/en/about
http://127.0.0.1/fr/a-propos
http://127.0.0.1/login
http://127.0.0.1/se-connecter
http://127.0.0.1/en/login
http://127.0.0.1/fr/se-connecter
http://127.0.0.1/contact
http://127.0.0.1/en/contact
http://127.0.0.1/fr/contact

I saw a tutorial from Brian Nesbitt (http://nesbot.com/2012/6/26/multilingual-site-using-slim) that could help managing this if there is a route with no specific language, but it was done with Slim2 and I really would like to have translated URI, too… So I really wonder if I need something else than the routes themselves to call my controllers. (and maybe the group). I could maybe just detect the language of the browser if the route is not informed (like “http://127.0.0.1/contact”, because “contact” could be english or french…) and redirect to the good route ?

What do you think about it ?
Thank you in advance for your answer.


#2

Hi!

This is a little bit complex but possible.

Add a tiny middleware to detect the browser language:

// Detect the browser language
$app->add(function (Request $request, Response $response, $next) {
    $language = $request->getHeader('accept-language')[0];
    $language = explode(',', $language)[0];
    $language = explode('-', $language)[0];

    if ($language !== 'fr' && $language !== 'en') {
        // default
        $language = 'en';
    }

    $request = $request->withAttribute('accept-language', $language);

    return $next($request, $response);
});

Add your language specific routes:

// Home index, the 'root'
$app->get('/', \App\Action\HomeIndexAction::class)->setName('root');

// languages only
$app->get('/en', \App\Action\EnglishIndexAction::class);
$app->get('/fr', \App\Action\FrenchIndexAction::class);

// ------------------------------------------------------------------------
// All routes without the language argument
// Only required for the redirect
// ------------------------------------------------------------------------
$app->get('/about', \App\Action\AboutEnRedirectAction::class);
$app->get('/a-propos', \App\Action\AboutFrRedirectAction::class);
$app->get('/contact', \App\Action\ContactRedirectAction::class);
$app->get('/login', \App\Action\LoginEnRedirectAction::class);
$app->get('/se-connecter', \App\Action\LoginFrRedirectAction::class);

// ------------------------------------------------------------------------
// The real page routes
// ------------------------------------------------------------------------
$app->get('/en/about', \App\Action\AboutIndexEnAction::class)->setName('about-en');
$app->get('/fr/a-propos', \App\Action\AboutIndexFrAction::class)->setName('about-fr');

$app->get('/en/contact', \App\Action\ContactIndexEnAction::class)->setName('contact-en');
$app->get('/fr/contact', \App\Action\ContactIndexFrAction::class)->setName('contact-fr');

// add more...

For all routes with a redirect use the accept-language attribute from the $request object:

$acceptLanguage = $request->getAttribute('accept-language');

// customize this route name
$routeName = sprintf('contact-%s', $acceptLanguage);
$url = $this->router->pathFor($routeName);

return $response->withRedirect($url);

#3

Thank you for your answer.
I just don’t understand where you would like to put your last few lines of code. Inside the Controller, just before render the view ?

And how can I manage my menu lang in a general template ? Idealy, I would need a fonction to change the current URL with the chosen language… or am I wrong ?


#4

I don’t know your exact routing definitions. My examples are very generic. Put the code with the redirect logic only in the controller (action) methods you have to perform a redirect for example: in /fr /en /about /a-propos and so on… You don’t have to put this logic in the routes like /en/about or /fr/a-propos because that’s the correct page and a redirect is unnecessary there, right?

And how can I manage my menu lang in a general template ?

Do you use Twig? You could pass the translated strings to your general template and Twig will render it. Another approach is to translate the text in Twig with a translation extension. If you use native PHP templates then you just call a helper translation function like __('my text') within the template to translate the text. But without more details about your requirement it’s not easy to give a “good” answer here.


#5

Thank you once again for your complete answers ! :slight_smile:

Yes I use twig. And I could load my lang file inside the controller. Everything is ok for that.

I understood what you did and your generic exemples are good. I just wonder if there is a way to avoid this multiplication of redirections… Because, in your exemple, for the same page, there are 4 page controllers (with the page “about” : AboutEnRedirectAction, AboutFrRedirectAction, AboutIndexEnAction, AboutIndexFrAction) while I’ve got only one twig file… (for exemple : “about.twig”) where I just need to load the good language depending the URL…

Is there maybe a way to pass a parameter (the lang) to the controller directly inside “$app->get” ?

I would imagine it a bit like this (with a third parameter) for the page controller “about” (of course, this doesn’t work) :
$app->get('/about', PagesController::class . ':about', 'en');
$app->get('/a-propos', PagesController::class . ':about', 'fr');
$app->get('en/about', PagesController::class . ':about', 'en');
$app->get('fr/a-propos', PagesController::class . ':about', 'fr');

I also tried this, without success :
$app->get('/fr', call_user_func_array(PagesController::class . ':home', 'fr'));


#6

well… no… I wrote bull••• ! ^^

I have to name routes with lang for twig (to use it with « path for ») :
->setName(‘about-en’);
->setName(‘about-fr’);
So I need 1 pagecontroller per lang…
And redirection for the rest. So you did right ! :wink: