04/02/2017 - SYMFONY
This example is going to teach us how to creating a multilingual application with symfony. We will be using static and dynamic messages in English en
and Turkish tr
. To translate message codes to human readable strings, we will be using trans
function in twig templates and translator
service in controller.
You'll often need to clear cache to see changes you did. This applies to all environments.
You can use $ php bin/console debug:translation en InanzzzApplicationBundle
command to list all messages for given language en
and bundle InanzzzApplicationBundle
.
As you can see below, URLs contain en
and tr
parameter.
http://myapp.dev/app_dev.php/en/
http://myapp.dev/app_dev.php/tr/
http://myapp.dev/app_dev.php/en/translator
http://myapp.dev/app_dev.php/tr/translator
Every URL must contain _locale
parameters which are en
and tr
. If a user tries to access an URL without en
or tr
parameter, our event listener will intercept the request to automatically add en
or tr
parameter to the current URL and redirect user to it. Shortly, you'll see how it is done.
parameters:
locale: en
default_locale: en
valid_locales: en|tr
framework:
translator: { fallbacks: ["%locale%"] }
inanzzz_application:
resource: "@InanzzzApplicationBundle/Controller/"
type: annotation
prefix: /{_locale}/
requirements:
_locale: "%valid_locales%"
defaults:
_locale: "%default_locale%"
_wdt:
resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml"
prefix: /{_locale}/_wdt
_profiler:
resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml"
prefix: /{_locale}/_profiler
_errors:
resource: "@TwigBundle/Resources/config/routing/errors.xml"
prefix: /{_locale}/_error
_main:
resource: routing.yml
#src/Inanzzz/ApplicationBundle/Resources/translations/messages.en.xlf
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="application.greeting">
<source>application.greeting</source>
<target>Welcome to our multilingual application!</target>
</trans-unit>
<trans-unit id="user.greeting">
<source>user.greeting</source>
<target>Hello %name% %surname%!</target>
</trans-unit>
<trans-unit id="page.translator.header">
<source>page.translator.header</source>
<target>Translator</target>
</trans-unit>
</body>
</file>
</xliff>
$ php bin/console debug:translation en InanzzzApplicationBundle
---------- ---------- ------------------------ ------------------------------------------
State Domain Id Message Preview (en)
---------- ---------- ------------------------ ------------------------------------------
messages application.greeting Welcome to our multilingual application!
unused messages user.greeting Hello %name% %surname%!
unused messages page.translator.header Translator
---------- ---------- ------------------------ ------------------------------------------
#src/Inanzzz/ApplicationBundle/Resources/translations/messages.tr.xlf
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="tr" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="application.greeting">
<source>application.greeting</source>
<target>Çok dilli web uygulamamıza hoşgeldiniz!</target>
</trans-unit>
<trans-unit id="user.greeting">
<source>user.greeting</source>
<target>Merhabe %name% %surname%!</target>
</trans-unit>
<trans-unit id="page.translator.header">
<source>page.translator.header</source>
<target>Çevirmen</target>
</trans-unit>
</body>
</file>
</xliff>
$ php bin/console debug:translation tr InanzzzApplicationBundle
---------- ---------- ------------------------ -----------------------------------------
State Domain Id Message Preview (tr)
---------- ---------- ------------------------ -----------------------------------------
messages application.greeting Çok dilli web uygulamamıza hoşgeldiniz!
unused messages user.greeting Merhabe %name% %surname%!
unused messages page.translator.header Çevirmen
---------- ---------- ------------------------ -----------------------------------------
namespace Inanzzz\ApplicationBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* @Route("", service="inanzzz_application.controller.default")
*/
class DefaultController
{
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
/**
* @Method({"GET"})
* @Route("/")
*
* @return Response
*/
public function indexAction()
{
return $this->templating->renderResponse(
'InanzzzApplicationBundle:Default:index.html.twig'
);
}
}
namespace Inanzzz\ApplicationBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @Route("/translator", service="inanzzz_application.controller.translator")
*/
class TranslatorController
{
private $templating;
private $translator;
public function __construct(
EngineInterface $templating,
TranslatorInterface $translator
) {
$this->templating = $templating;
$this->translator = $translator;
}
/**
* @Method({"GET"})
* @Route("/")
*
* @return Response
*/
public function indexAction()
{
return $this->templating->renderResponse(
'InanzzzApplicationBundle:Translator:index.html.twig',
[
'page_header' => $this->translator->trans('page.translator.header'),
'user_greeting' => $this->translator->trans(
'user.greeting',
[
'%name%' => 'Inanzzz',
'%surname%' => 'Zzznani',
]
)
]
);
}
}
services:
inanzzz_application.controller.default:
class: Inanzzz\ApplicationBundle\Controller\DefaultController
arguments:
- '@templating'
inanzzz_application.controller.translator:
class: Inanzzz\ApplicationBundle\Controller\TranslatorController
arguments:
- '@templating'
- '@translator'
#src/Inanzzz/ApplicationBundle/Resources/views/Default/index.html.twig
{% extends '::base.html.twig' %}
{% block body %}
<h3>{{ 'application.greeting'|trans }}</h3>
{% endblock %}
#src/Inanzzz/ApplicationBundle/Resources/views/Translator/index.html.twig
{% extends '::base.html.twig' %}
{% block body %}
<h3>{{ page_header }}</h3>
<p>{{ user_greeting }}</p>
{% endblock %}
If the URL doesn't contain en
or tr
, application throws a 404 NotFoundHttpException
. In such case this event listener kicks in and handles it as shown below.
NotFoundHttpException
.en
and tr
are missing in extracted URL parts.en
or tr
) then use it otherwise use application default language en
.en
or tr
) to beginning of the extracted URL parts.If the application throws a 404 NotFoundHttpException
error even if the URL has language part than this event listener will ignore redirection because the user is actually trying to access a non-existent URL which is fine.
namespace Inanzzz\ApplicationBundle\EventListener;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class LocaleRedirectListener
{
private $validLocales;
public function __construct($validLocales)
{
$this->validLocales = explode('|', $validLocales);
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
// 1
if (!$event->getException() instanceof NotFoundHttpException) {
return;
}
// 2
if ($event->getRequestType() != HttpKernelInterface::MASTER_REQUEST) {
return;
}
// 3
$request = $event->getRequest();
$uriParts = explode('/', $request->getPathInfo());
// 4
if ($this->isLocaleMissing($uriParts)) {
// 5
$response = new RedirectResponse($this->constructRedirectUri($request));
// 6
$event->setResponse($response);
}
}
private function isLocaleMissing(array $uriParts)
{
return !isset($uriParts[1]) || !$uriParts[1] || !in_array($uriParts[1], $this->validLocales);
}
private function constructRedirectUri(Request $request)
{
// 5.1
$browserLanguage = $request->getPreferredLanguage($this->validLocales);
// 5.2
$locale = in_array($browserLanguage, $this->validLocales)
? $browserLanguage
: $request->getDefaultLocale();
// 5.3
$find = str_replace('/', '\/', $request->getPathInfo());
// 5.4
return preg_replace(
'/'.$find.'$/',
'/'.$locale.'/'.trim($request->getPathInfo(), '/'),
$request->getUri()
);
}
}
services:
inanzzz_application.listener.locale_redirect:
class: Inanzzz\ApplicationBundle\EventListener\LocaleRedirectListener
arguments:
- "%valid_locales%"
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }