Skip to content

Instantly share code, notes, and snippets.

@geerteltink
Last active June 4, 2016 09:28
Show Gist options
  • Save geerteltink/1df70d2c072021633405a31e48f9138c to your computer and use it in GitHub Desktop.
Save geerteltink/1df70d2c072021633405a31e48f9138c to your computer and use it in GitHub Desktop.
PSR-7 Localization POC. A symfony translator is used but the zend-i18n translator has a similar setup.
<?php
namespace App\Core\I18n;
use Zend\Expressive\Helper\UrlHelper;
class I18nUrlHelper extends UrlHelper
{
public function __invoke($route = null, array $params = [])
{
if ($this->getRouteResult()) {
$matchedParams = $this->getRouteResult()->getMatchedParams();
$params['locale'] = $params['locale'] ?? $matchedParams['locale'] ?? 'en';
}
return parent::__invoke($route, $params);
}
}
<?php
namespace App\Core\I18n;
use Interop\Container\ContainerInterface;
use Zend\Expressive\Helper\Exception\MissingRouterException;
use Zend\Expressive\Router\RouterInterface;
class I18nUrlHelperFactory
{
/**
* Create a UrlHelper instance.
*
* @param ContainerInterface $container
*
* @return I18nUrlHelper
* @throws \Interop\Container\Exception\NotFoundException
* @throws \Interop\Container\Exception\ContainerException
* @throws \Zend\Expressive\Helper\Exception\MissingRouterException
*/
public function __invoke(ContainerInterface $container)
{
if (!$container->has(RouterInterface::class)) {
throw new MissingRouterException(sprintf(
'%s requires a %s implementation; none found in container',
I18nUrlHelper::class,
RouterInterface::class
));
}
return new I18nUrlHelper($container->get(RouterInterface::class));
}
}
<?php
namespace App\Core\I18n;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Zend\Diactoros\Response\RedirectResponse;
class LocalizationMiddleware
{
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
{
$acceptedLanguages = ['en', 'nl', 'fr', 'de', 'es'];
$locale = $request->getAttribute('locale');
// If there is no or an invalid locale
if (!$locale || !in_array(substr(trim($locale), 0, 2), $acceptedLanguages, true)) {
// Try the user preferred locales
$userLocales = $request->getServerParams()['HTTP_ACCEPT_LANGUAGE'] ?? '';
foreach (explode(',', $userLocales) as $userLocale) {
if (in_array(substr(trim($userLocale), 0, 2), $acceptedLanguages, true)) {
$locale = $userLocale;
break;
}
}
}
// Fallback to first accepted language
if (!$locale) {
$locale = $acceptedLanguages[0];
}
// Only the language is needed
$locale = substr(trim($locale), 0, 2);
if ($request->getUri()->getPath() === '/') {
// Redirect and set the locale for the homepage
return new RedirectResponse('/' . $locale);
}
// Set current locale
$this->translator->setLocale($locale);
// Get response first, apply content language later
if (null !== $next) {
$response = $next($request->withAttribute('locale', $locale), $response);
}
// Append the preferred language if it's different from the locale
if ($acceptedLanguages[0] !== $locale) {
$locale .= ', ' . $acceptedLanguages[0];
}
// Add the content language to the response
return $response->withHeader('Content-Language', $locale);
}
}
<?php
namespace App\Core\I18n;
use Interop\Container\ContainerInterface;
use Symfony\Component\Translation\Loader\ArrayLoader;
use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\Translation\Translator;
class TranslatorFactory
{
public function __invoke(ContainerInterface $container)
{
$config = $container->get('config');
$config['i18n']['default_locale'] = 'en';
$translator = new Translator($config['i18n']['default_locale'], new MessageSelector());
$translator->addLoader('array', new ArrayLoader());
$translator->setFallbackLocales([$config['i18n']['default_locale']]);
$translator->addResource('array', [
'Hello World!' => 'Bonjour!',
], 'fr');
$translator->addResource('array', [
'Hello World!' => 'Hallo!',
], 'nl');
return $translator;
}
}
<?php
namespace App\Core\I18n;
use Interop\Container\ContainerInterface;
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Component\Translation\TranslatorInterface;
class TwigTranslationExtensionFactory
{
public function __invoke(ContainerInterface $container)
{
return new TranslationExtension($container->get(TranslatorInterface::class));
}
}
<?php
namespace App\Frontend\Action;
use App\Core\Action\AbstractAction;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Expressive\Template\TemplateRendererInterface;
class HomePageAction extends AbstractAction
{
private $template;
private $translator;
public function __construct(TemplateRendererInterface $template, TranslatorInterface $translator)
{
$this->template = $template;
$this->translator = $translator;
}
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
{
return new HtmlResponse($this->translator->trans('Hello World!'));
}
}
{
"require": {
"twig/extensions": "^1.3",
"symfony/translation": "^3.1",
"symfony/twig-bridge": "^3.1"
},
}
<?php
use App\Domain;
use Zend\Expressive;
return [
'dependencies' => [
'invokables' => [
],
'factories' => [
Expressive\Helper\UrlHelper::class => App\Core\I18n\I18nUrlHelperFactory::class,
App\Core\I18n\LocalizationMiddleware::class => App\Core\I18n\LocalizationMiddlewareFactory::class,
Symfony\Component\Translation\TranslatorInterface::class => App\Core\I18n\TranslatorFactory::class,
Symfony\Bridge\Twig\Extension\TranslationExtension::class =>
App\Core\I18n\TwigTranslationExtensionFactory::class,
],
],
'middleware_pipeline' => [
'routing' => [
'middleware' => [
Expressive\Container\ApplicationFactory::ROUTING_MIDDLEWARE,
Expressive\Helper\UrlHelperMiddleware::class,
App\Core\I18n\LocalizationMiddleware::class,
Expressive\Container\ApplicationFactory::DISPATCH_MIDDLEWARE,
],
'priority' => 1,
],
],
'twig' => [
'cache_dir' => 'data/cache/twig',
'assets_url' => '/assets/',
'assets_version' => null,
'extensions' => [
Symfony\Bridge\Twig\Extension\TranslationExtension::class,
],
],
];
<?php
namespace App\Core\I18n;
use Interop\Container\ContainerInterface;
use Symfony\Component\Translation\TranslatorInterface;
class LocalizationMiddlewareFactory
{
public function __invoke(ContainerInterface $container)
{
return new LocalizationMiddleware(
$container->get(TranslatorInterface::class)
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment