Internationalization (i18n)

Internationalization can be integrated in a Piko Framework project by using a separate package:

composer require piko/i18n

This package will install the I18n component.

In order to use the I18n component, translations have to be stored in PHP files. These files will return an array of key-value pairs. Keys are strings to translate and values are corresponding translated strings.

Example of translation file:

// fr.php
return [
    'Translation test' => 'Test de traduction',
    'Hello {name}' => 'Bonjour {name}',
];

The I18n component can be declared in the application configuration:

return [
    //...
    'components' => [
        'Piko\I18n' => [
            'translations' => [
                'app' => '@app/messages'
            ]
        ],
    ],
];

Once the I18n component correctly configured, it can be invoked in two ways:


/* @var Piko\Application $app */

$i18n = $app->getComponent('Piko\i18n');

echo $i18n->translate('app', 'Translation test') . '<br>'; // Test de traduction
echo $i18n->translate('app', 'Hello {name}', ['name' => 'John']) . '<br>' ; // Bonjour John

// Using the proxy function __() :
use function Piko\I18n\__;
echo __('app', 'Translation test') . '<br>'; // Test de traduction
echo __('app', 'Hello {name}', ['name' => 'John']) . '<br>' ;  // Bonjour John

Dynamic language loading in a Piko based application

A multi language application needs to switch language dynamically.

The I18n component doesn’t offer a ready to use solution but this can be implemented in a Middleware.

A commonly used solution is to extract the first element of the uri path to set the language.

in a Piko application, we can extract the language from the request object in the middleware. We can also prepend the language to each generated url from Router::getUrl method. For that, we have to listen to the router AfterUriBuiltEvent.

Bellow, an example of a I18nMiddleware implementation:

<?php
namespace app\lib;

use Piko\Application;
use Piko\I18n;
use Piko\Router;
use Piko\Router\AfterUriBuiltEvent;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class I18nMiddleware implements MiddlewareInterface
{
    /**
     * @var I18n
     */
    private $i18n;

    public function __construct(Application $app)
    {
        $router = $app->getComponent(Router::class);
        assert($router instanceof Router);
        $i18n = $app->getComponent(I18n::class);
        assert($i18n instanceof I18n);

        $router->on(
            AfterUriBuiltEvent::class,
            fn($event) => $event->uri = '/' . $i18n->language . $event->uri
        );

        $this->i18n = $i18n;
    }

    /**
     * {@inheritDoc}
     * @see \Psr\Http\Server\MiddlewareInterface::process()
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $uri = $request->getUri();
        $uriPath = $uri->getPath();

        /*
        * Extract the language from the uri
        * ex: with /fr/blog uri, it will extract 'fr' and set the i18n language with 'fr'
        */
        if (preg_match('/^\/([a-z]{2})\//', $uriPath, $match)) {
            $lang = $match[1];
            $uriPath = preg_replace('/^\/' . $lang . '\//', '/', $uriPath);
            $request = $request->withUri($uri->withPath($uriPath));
            $this->i18n->language = $lang;
        }

        return $handler->handle($request);
    }
}

The middleware is then initialized in the entry script:

<?php
use Piko\ModularApplication;
use app\lib\I18nMiddleware;

require(__DIR__ . '/../vendor/autoload.php');

$config = require __DIR__ . '/../config/app.php';

$app = new ModularApplication($config);

$app->pipe(new I18nMiddleware($app));
$app->run();