I am working on a symfony application where my goal is no matter what page the user is on it will navigate to the locale version of the page.
For example, if the user navigates to "/" the home page, it will redirect to "/en/"
If they are on "/admin" page it will redirect to "/en/admin", in such a way that the _locale
property is set from the route.
Also it needs to determine the locale if they visit /admin from the users browser since no locale was determined so it knows which page to redirect to.
Currently my default controller looks like below since I'm testing. I'm using the dev mode & profiler to test that translations are working in correct.
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
* @Route("/{_locale}/", name="homepage_locale")
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
This current method will keep the user at "/" if they navigate there, but I want to have it redirect to "/en/". This should work for other pages too, like /admin, or /somepath/pathagain/article1 (/en/admin , /en/somepath/pathagain/article1)
How would I do this?
References I've read that did not help:
Symfony2 Use default locale in routing (one URL for one language)
Symfony2 default locale in routing
::Update::
I have not solved my issue but I've come close as well as learned a few tricks to be more efficient.
DefaultController.php
<?php
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class DefaultController extends Controller
{
/**
* @Route("/", name="home", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
* @Route("/{_locale}/", name="home_locale", requirements={"_locale" = "%app.locales%"})
*/
public function indexAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
/**
* @Route("/admin", name="admin", defaults={"_locale"="en"}, requirements={"_locale" = "%app.locales%"})
* @Route("/{_locale}/admin", name="admin_locale", requirements={"_locale" = "%app.locales%"})
*/
public function adminAction(Request $request)
{
$translated = $this->get('translator')->trans('Symfony is great');
// replace this example code with whatever you need
return $this->render('default/index.html.twig', [
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'translated' => $translated
]);
}
}
?>
Config.yml
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
# Put parameters here that don't need to change on each machine where the app is deployed
# http://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: en
app.locales: en|es|zh
framework:
#esi: ~
translator: { fallbacks: ["%locale%"] }
secret: "%secret%"
router:
resource: "%kernel.root_dir%/config/routing.yml"
strict_requirements: ~
form: ~
csrf_protection: ~
validation: { enable_annotations: true }
#serializer: { enable_annotations: true }
templating:
engines: ['twig']
#assets_version: SomeVersionScheme
default_locale: "%locale%"
trusted_hosts: ~
trusted_proxies: ~
session:
# handler_id set to null will use default session handler from php.ini
handler_id: ~
save_path: "%kernel.root_dir%/../var/sessions/%kernel.environment%"
fragments: ~
http_method_override: true
assets: ~
# Twig Configuration
twig:
debug: "%kernel.debug%"
strict_variables: "%kernel.debug%"
# Doctrine Configuration
doctrine:
dbal:
driver: pdo_mysql
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
# if using pdo_sqlite as your database driver:
# 1. add the path in parameters.yml
# e.g. database_path: "%kernel.root_dir%/data/data.db3"
# 2. Uncomment database_path in parameters.yml.dist
# 3. Uncomment next line:
# path: "%database_path%"
orm:
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
# Swiftmailer Configuration
swiftmailer:
transport: "%mailer_transport%"
host: "%mailer_host%"
username: "%mailer_user%"
password: "%mailer_password%"
spool: { type: memory }
Notice under parameters the value app.locales: en|es|zh
. This is now a value I can reference whenever I create my routes if I plan to support more locales in the future which I do. Those routes are english, spanish, chinese in that order for those curious. In the DefaultController in the annotations the "%app.locales%"
is the part that references the config parameter.
The problem with my current method is going to /admin for example does not redirect the user to /{browsers locale}/admin, which would be the more elegant solution to keep everything organized... but at least the routes work. Still looking for better solution.
****Update****
I think I may have possibly found the answer here as the bottom answer given (Add locale and requirements to all routes - Symfony2), the answer by Athlan. Just not sure how to implement this in symfony 3 as his directions were not clear enough to me.
I think this article might help also (http://symfony.com/doc/current/components/event_dispatcher/introduction.html)
After 12 hours of looking into this I finally found an acceptable solution. Please post revised versions of this solution if you can make it more efficient.
Some things to note, my solution is particular to my need. What it does is force any URL to go to a localized version if it exists.
This requires some conventions to be followed when you create routes.
DefaultController.php
Notice that both routes always start with "/{_locale}/". For this to work every route in your project needs to have this. You just put the real route name afterwards. For me I was okay with this scenario. You can modify my solution to fit your needs easily enough.
The first step is to create a listen on the httpKernal to intercept requests before they go to the routers to render them.
LocaleRewriteListener.php
Finally you set the services.yml to start the listener up.
Services.yml
Also in the config.yml you will want to add the following under parameters:
config.yml
I wanted there to be only one place you define the locales but I wound up having to do 2...but at least they are in the same spot so easy to change.
app.locales is used in default controller
(requirements={"_locale" = "%app.locales%"})
and locale_supported is used in the LocaleRewriteListener. If it detects a locale that is not in the list it will fallback to the default locale, which in this case is the value of locale:en.app.locales is nice with the requirements command because it will cause a 404 for any locales that do not match.
If you are using forms and have a login you will need to do the following to your security.yml
Security.yml
The important change to note here is that
(.*?)/login
will authenticate anonymously so your users can still login. This does mean that routes like..dogdoghere/login could trigger, but the requirements I will show you shortly on the login routes prevent this and will throw 404 errors. I like this solution with the(.*?)
versus[a-z]{2}
incase you wanted to use en_US type locales.SecurityController.php
Note that even these paths use {_locale} in front. I like this however so I can give custom logins for different locales. Just keep that in mind. The only route that does not need the locale is logout which works just fine since its really only an intercept route for the security system. Also notice it uses the requirements which is set from the config.yml, so you only have to edit it in one place for all the routes across your projects.
Hope this helps someone trying to do what I was doing!
NOTE:: To test this easily I use 'Quick Language Switcher' extension for Google Chrome, which changes the accept-language header on all requests.
final function smallResumeOfResearching($localeRewrite, $opinion = 'IMHO') :)
The method, provided by mr. Joseph working great with routes like /{route_name}, or /, but not with routes like /article/slug/other.
If we use modified mr.Joseph's method, provided by https://stackoverflow.com/a/37168304/9451542, we will lost profiler and debugger in dev mode.
If we want more flexible solution, onKernelRequest method can be modified like this (thanks to mr. Joseph, thanks to https://stackoverflow.com/a/37168304/9451542):
note: $this->profilerRoutes = array('_profiler', '_wdt', '_error');
Small improvement for Symfony 3.4:
Be sure, that the getSubscribedEvents() will register LocaleRewriteListener BEFORE RouterListener::onKernelRequest and BEFORE LocaleListener::onKernelRequest. Integer 17 must be greater than RouterListener::onKernelRequest priotity. Otherwise you will got 404.
bin/console debug:event-dispatcher
Service definition in services.yml must be (depends on Symfony configuration):
AppBundle\EventListener\LocaleRewriteListener: arguments: ['@router', '%kernel.default_locale%', '%locale_supported%'] tags: - { name: kernel.event_subscriber, event: kernel.request }
I don't have enough reputation to add a comment to the correct solution. So I'm adding a new answer
You can add "prefix: /{_locale}" at app/config/routing.yml like this:
So you don't need to add it to every route to every action. For the following steps. Thank you very much it's perfect.