Bu örnekte symfony ile çok dilli uygulama yapmayı öğreneceğiz. İngilizce en ve Türkçe tr dillerinde sabit ve değişken mesajlar kullanacağız. Mesaj kodlarını insanların okuyabilecekleri mesajlara çevirmek için, twig şablonlarında trans fonksiyonunu, controller içinde ise translator servisini kullanacağız.


Temel bilgi


Cache


Yaptığınız değişikliklerden sonra, sık sık önbelleği temizlemeniz gerekecek. Bu tüm ortamlar için geçerlidir.


Message listesi


Kullanımda olan InanzzzApplicationBundle paketindeki en dili için mevcut olan tüm mesajları $ php bin/console debug:translation en InanzzzApplicationBundle komutu ile listeleyebilirsiniz.


URL yapısı


Aşağıda da gördüğümüz gibi URL en ve tr parametrelerini kullanıyor.


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

URL doğrulama


Her URL _locale parametresini en veya tr olarak barındırmak zorundadır. Eğer bir kullanıcı içinde en veya tr parametresi olmayan bir URL'ye ulaşmaya çalışırsa, event listener isteğin yolunu kesip en veya tr ekleme işlemini yapıp, kullanıcı yönlendirme işlemini yapacaktır. Birazdan bu işlemin nasıl yapıldığını göreceksiniz.


Konfigürasyon


Config.yml


parameters:
locale: en
default_locale: en
valid_locales: en|tr

framework:
translator: { fallbacks: ["%locale%"] }

Routing.yml


inanzzz_application:
resource: "@InanzzzApplicationBundle/Controller/"
type: annotation
prefix: /{_locale}/
requirements:
_locale: "%valid_locales%"
defaults:
_locale: "%default_locale%"

Routing_dev.yml


_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

Mesajlar


Messages.en.xlf


#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
---------- ---------- ------------------------ ------------------------------------------

Messages.tr.xlf


#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
---------- ---------- ------------------------ -----------------------------------------

Controller


DefaultController


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'
);
}
}

TranslatorController


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'

Şablonlar


Default


#src/Inanzzz/ApplicationBundle/Resources/views/Default/index.html.twig

{% extends '::base.html.twig' %}

{% block body %}
<h3>{{ 'application.greeting'|trans }}</h3>
{% endblock %}

Translator


#src/Inanzzz/ApplicationBundle/Resources/views/Translator/index.html.twig

{% extends '::base.html.twig' %}

{% block body %}
<h3>{{ page_header }}</h3>
<p>{{ user_greeting }}</p>
{% endblock %}

LocaleRedirectListener


Eğer URL içinde en veya tr yoksa, uygulama 404 NotFoundHttpException hatası verecektir. Bu gibi durumlarda event listener devreye girip, aşağıdaki işlemleri yapacaktır.


  1. Öncelikle NotFoundHttpException hatasını yakalar.

  2. Bu hataya sebep olan şeyin kullanıcı olduğuna emin olur.

  3. URL'yi parçalara ayırır.

  4. URL parçalarında en ve tr parametrelerinin eksik olup olmadığını kontrol eder.

  5. Yönlendirmek için yeni bir URL yaratır.

    1. Kullanıcının kullandığı tarayıcının dilini tespit eder.

    2. Eğer tarayıcı dili geçerli diller listesindeyse (en veya tr) onu kullanır, değilse uygulamanın varsayılan dilini (en) kullanır.

    3. Dili (en veya tr) parçalanan URL'nin başına ekler.

    4. Yeni URL'yi geri verir.

  6. Kullanıcının isteğini doğrulanan URL'ye yönlendirir.

Eğer URL içinde en veya tr varsa ve de uygulama 404 NotFoundHttpException hatası veriyorsa, event listener hiçbir şey olmamış gibi davranır çünkü, kullanıcı mevcut olmayan bir sayfaya ulaşmak istiyor demektir ki bu da normal birşeydir.


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 }

Notlar