Multilanguage website with locale url

Hello
I would like to create a middleware to add in the App for the locale in the url without change existing route.
Example:
$app->get(’/’, function (Request $request, Response $response) use ($container) {
//code here
//URI is https://www.example.com/
or
//URI is https://www.example.com/en/
or
//URI is https://www.example.com/de/
}
$app->get(’/about’, function (Request $request, Response $response) use ($container) {
//code here
//URI is https://www.example.com/en/about
or
//URI is https://www.example.com/de/about
}
When locale is in the URL, a locale option in the container was set.

What is the best solution for this without editing pattern in the routes?

I don’t know if this is the way to go, but it works for me. First of all, wrap all your routes up in a group that takes an optional parameter named lang, something like below

$app->group('/{lang}', function() use($container) {

    $this->get('/', 'HomeController:index')->setName('home');
    $this->get('/signup', AuthSignUpController::class.':getSignUp')->setName('auth.signup'); 
    $this->post('/signup', AuthSignUpController::class.':postSignUp');
    $this->get('/signin', AuthSignInController::class.':getSignIn')->setName('auth.signin');
    $this->post('/signin', AuthSignInController::class.':postSignIn');
});

Then in my middleware I check if the request has a parameter named lang, and I check if the value of lang is something valid.

/**
 * Checks if Uri has invalid or missing language prefix, and redirects to valid route
 * @package App\Middleware
 */
class UriLanguagePrefixerMiddleware
{

    private $router;
    private $view;
    private $localization;
    private $translator;
    private $requestedLanguage;

    public function __construct(Router $router, Twig $view, $localization, Translator $translator)
    {
        $this->router = $router;
        $this->view = $view;
        $this->localization = $localization;
        $this->translator = $translator;
    }

    /**
     * Automatically invoked during any request to the site
     * @param Request | \Slim\Http\Request $request
     * @param Response | \Slim\Http\Response $response
     * @param $next
     * @return mixed
     */
    public function __invoke(Request $request, Response $response, callable $next)
    {
        $validLanguages = $this->localization['languages'];

        // running this middleware (NON-route attached) requires settings -> determineRouteBeforeAppMiddleware = true
        $route = $request->getAttribute('route');
        if (null == $route) {
            $routeName = 'auth.signin';
            $requestedLanguage = null;
        } else {
            $routeName = $route->getName();
            $requestedLanguage = explode('/', $route->getArgument('lang'))[0];
        }

        if (null == $requestedLanguage || !array_key_exists($requestedLanguage, $validLanguages)) {

            $_SESSION['lang'] = key($validLanguages); // first key of language array in settings = de_DE
            $requestedLanguage = $_SESSION['lang'];

            return $response->withRedirect($this->router->pathFor($routeName, [
                'lang' => $requestedLanguage,
            ]));

        }
        $this->view->getEnvironment()->addGlobal('lang', $requestedLanguage);
        $this->translator->setLocale($requestedLanguage);
        $response = $next($request, $response);
        return $response;
    }
}

localization comes from my settings array by the way

$app = new \Slim\App([
    'settings' => [
        'displayErrorDetails' => true,
        'devMode' => true,
        'determineRouteBeforeAppMiddleware' => true,

        'localization' => [

            'languages' => [
                'de_DE' => 'Deutsch',
                'en_US' => 'English'
            ],

            'locales' => [
                'de' => 'de_DE',
                'en' => 'en_US'
            ],
         ],
]
]);
1 Like

Route groups are good to organize routes into logical groups.

Example:

use Slim\App;

// ...

// https://www.example.com/
$app->get('/', \App\Action\HomeIndexAction::class)->setName('root');

// Language group
$app->group('/{language:(?:en|de)}', function () {
    /** @var App $this */

    // https://www.example.com/en
    // https://www.example.com/de
    $this->get('', \App\Action\LanguageIndexAction::class);

    // https://www.example.com/en/about
    // https://www.example.com/de/about
    $this->get('/about', \App\Action\AboutAction::class);
});

To get the url’s language use the $request variable:

$language = $request->getAttribute('language'); // "en" or "de"
1 Like

Yes, I know group already. I need something before the routing, outside the get/map logic because my app is modular, anyone can add routes outside my routes file, so all the routes outside my routes file can’t be added to this rule.

I have found a solution to this problem, I have created a middleware that replace the SLIM $request object (leave the URI part intact). This is the code: https://github.com/BrainStormDevel/perseo/blob/master/Core/PerSeo/MiddleWare/Language.php
The solution is not perfect, because this code will break the container and, for now, I don’t know why.

As an option, you can consider the approach as here (Simple translation switching, using the Eloquent translations component from Laravel.)

1 Like

The approch I interested in was a language args invoked before any route. So, the unique solution I’ve found was this Middleware.