0
votes

I'm trying to utilize Symfony 4's multilang (or multi-locale) routing pattern.

My application is truly international and supports over 25 different languages, though the translations come incrementally and many routes are not translated to some languages yet.

In such case I want them to fall back into english-default.

My config/packages/translation.yaml looks like this:

framework:
    default_locale: en
    translator:
        default_path: '%kernel.project_dir%/translations'
        fallbacks:
            - en

My routes are defined within routes.yaml file. For example:

about_index:
    path:
        en: /about-us
        pl: /o-nas
    controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
    defaults:
        template: About/index.html.twig

Now, whenever I open the site with either pl or en locale - everything works as expected, but when for example I set it to de, I get "Unable to generate a URL for the named route "about_index" as such route does not exist." error.

How do I force Symfony to fallback to en paths whenever the route in desired locale does not yet exist?

2

2 Answers

0
votes

So, after quite a bit of investigation it appears there's no way to make it work like that with Symfony's default methods.

I went for the "workaround" approach and extended Symfony's Twig Bridge's Routing extension with my own Twig function, autopath():

namespace App\Twig;

use Symfony\Bridge\Twig\Extension\RoutingExtension;
use Twig\TwigFunction;

// auto-wired services
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Extends Symfony's Twig Bridge's routing extension providing more flexible localization.
 *
 * @author Kyeno
 */
class KyAutoPathExtension extends RoutingExtension
{
    private $router;
    private $request;

    public function __construct(UrlGeneratorInterface $router, RequestStack $requestStack)
    {
        $this->router = $router;
        $this->request = $requestStack->getCurrentRequest();

        parent::__construct($router);
    }

    /**
     * {@inheritdoc}
     *
     * @return TwigFunction[]
     */
    public function getFunctions()
    {
        return [
            new TwigFunction('autopath', [$this, 'getPathAuto'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']])
        ];
    }

    /**
     * @param string $name
     * @param array  $parameters
     * @param bool   $relative
     *
     * @return string
     */
    public function getPathAuto($name, $parameters = [], $relative = false)
    {
        // obtain current and default locales from request object
        $localeRequested = $this->request->getLocale();
        $localeDefault = $this->request->getDefaultLocale();

        // build localized route name
        // NOTE: Symfony does NOT RECOMMEND this way in their docs, but it's the fastest that popped in my mind
        foreach([sprintf('%s.%s', $name, $localeRequested), sprintf('%s.%s', $name, $localeDefault)] as $nameLocalized) {

            // if such route exists, link to it and break the loop
            if($this->router->getRouteCollection()->get($nameLocalized)) {

                return $this->router->generate($nameLocalized, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH);
            }
        }

        // when no matches found, attempt relying on Symfony Twig Bridge's original path() function
        // (and likely fail with exception, unless they fix/allow it)
        return parent::getPath($name, $parameters, $relative);
    }
}
0
votes

Works on SF 4.4, using annotations : you can declare double @Route annotations, one with localized routes, the other without localized. The non-localized route will be used if not matching the first annotation.

 * @Route({
 *      "fr": "/bonjour",
 *      "de": "/guten-tag"
 * }, name="hello_path")
 * @Route("/hello", name="hello_path")