Using BeSimpleI18nRoutingBundle

Welcome to BeSimpleI18nRoutingBundle - generate I18N Routes simply and quickly

Installation

Step 1: Download the Bundle

Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle:

1
$ composer require besimple/i18n-routing-bundle "^3.0"

This command requires you to have Composer installed globally, as explained in the installation chapter of the Composer documentation.

Step 2: Enable the Bundle

Then, enable the bundle by adding the following line in the app/AppKernel.php file of your project:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// app/AppKernel.php

// ...
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...

            new BeSimple\I18nRoutingBundle\BeSimpleI18nRoutingBundle(),
        );

        // ...
    }

    // ...
}

Step 3: (optional) Configure the bundle

The bundle comes with a sensible default configuration, which is listed below. If you skip this step, these defaults will be used.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# app/config/config.yml
be_simple_i18n_routing:
    #  if true, enables the annotation route loader
    annotations: true

    locales:
        # the default locale, used when generating routes
        default_locale: null
        # the supported locales used by "filter" and "strict"
        supported: []
        #  if true, filter out any locales not in "supported"
        filter: false
        #  if true, filter out any locales not in "supported"
        strict: false

    attribute_translator:
        #  if true, enables the attribute translator
        enabled: false

        # the type of attribute translator to use
        type: translator
        # When defining type as "service" then
        # add the id parameter with the service id to use (e.g. id: "my_attribute_translator_service_id")
        # and ensure the service implements "\BeSimple\I18nRoutingBundle\Routing\Translator\AttributeTranslatorInterface"
        #
        # When defining type as "doctrine_dbal" then
        # optionally add the connection parameter to set the dbal connection name (e.g. connection: "connection_name")
        # optionally add a caching configuration using the cache parameter:
        #   type: array | apc | xcache | memcache
        #   when the cache type is "memcache" then (optionally) add the connection information:
        #       type: memcache
        #       host: 127.0.0.1
        #       port: 11211
        #       instance_class: Memcache
        #       class: \Doctrine\Common\Cache\MemcacheCache

    # the route name inflector service
    route_name_inflector: 'be_simple_i18n_routing.route_name_inflector.postfix'

Create your localized routes!

Importing Routes

To define internationalized routes, you need to import the routing file using the be_simple_i18n type:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# app/config/routing.yml

my_yaml_i18n_routes:
    type: be_simple_i18n
    resource: "@AppBundle/Resources/config/routing/i18n.yml"

my_xml_i18n_routes:
    type: be_simple_i18n
    resource: "@AppBundle/Resources/config/routing/i18n.xml"

# For annotation support ensure that annotations is true in the configuration
my_annotation_i18n_routes:
    resource: '@AppBundle/Controller/'
    type:     annotation

Defining Routes

  • YAML
    1
    2
    3
    4
    # @AppBundle/Resources/config/routing/i18n.yml
    homepage:
        path:      /blog/{slug}
        defaults:  { _controller: AppBundle:Frontend:index }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- @AppBundle/Resources/config/routing/i18n.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://besim.pl/schema/i18n_routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://besim.pl/schema/i18n_routing http://besim.pl/schema/i18n_routing/routing-1.0.xsd">
    
        <route id="homepage">
            <locale key="en">/welcome</locale>
            <locale key="fr">/bienvenue</locale>
            <locale key="de">/willkommen</locale>
            <default key="_controller">AppBundle:Frontend:index</default>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // app/config/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use BeSimple\I18nRoutingBundle\Routing\RouteGenerator\I18nRouteGenerator;
    
    $generator = new I18nRouteGenerator();
    
    $collection = new RouteCollection();
    $collection->addCollection(
        $generator->generateRoutes(
            'homepage',
            array(
                'en' => '/welcome',
                'fr' => '/bienvenue',
                'de' => '/willkommen'
            ),
            new Route('', array(
                '_controller' => 'AppBundle:Frontend:index'
            ))
        )
    );
    
    return $collection;
    
  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    // @AppBundle/Controller/BlogController.php
    namespace AppBundle\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use BeSimple\I18nRoutingBundle\Routing\Annotation\I18nRoute;
    
    class FrontendController extends Controller
    {
        /**
         * @I18nRoute({ "en": "/welcome/{name}", "fr": "/bienvenue/{name}", "de": "/willkommen/{name}" }, name="homepage")
         */
        public function indexAction($name)
        {
            // ...
        }
    }
    

Using Normal Routes

Sometimes you have routes that don’t need to be translated. To allow this simply add the routes as followed.

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    # @AppBundle/Resources/config/routing/i18n.yml
    homepage:
        path:  "/{name}"
        defaults: { _controller: AppBundle:Hello:index }
    
    welcome:
        locales:  { en: "/welcome/{name}", fr: "/bienvenue/{name}", de: "/willkommen/{name}" }
        defaults: { _controller: AppBundle:Frontend:welcome }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- @AppBundle/Resources/config/routing/i18n.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://besim.pl/schema/i18n_routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://besim.pl/schema/i18n_routing http://besim.pl/schema/i18n_routing/routing-1.0.xsd">
    
        <route id="hello" pattern="/hello/{name}">
            <default key="_controller">AppBundle:Hello:index</default>
        </route>
            <route id="homepage">
            <locale key="en">/welcome/{name}</locale>
            <locale key="fr">/bienvenue/{name}</locale>
            <locale key="de">/willkommen/{name}</locale>
            <default key="_controller">AppBundle:Frontend:index</default>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // app/config/routing.php
    use BeSimple\I18nRoutingBundle\Routing\RouteGenerator\I18nRouteGenerator;
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    
    $generator = new I18nRouteGenerator();
    
    $collection = new RouteCollection();
    $collection->add('hello', new Route('/hello/{name}', array(
        '_controller' => 'AppBundle:Hello:index',
    )));
    $collection->addCollection(
        $generator->generateRoutes(
            'homepage',
            array('en' => '/welcome/{name}', 'fr' => '/bienvenue/{name}', 'de' => '/willkommen/{name}'),
            new Route('', array(
                '_controller' => 'AppBundle:Frontend:index',
            ))
        )
    );
    
    return $collection;
    
  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // @AppBundle/Controller/BlogController.php
    namespace AppBundle\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use BeSimple\I18nRoutingBundle\Routing\Annotation\I18nRoute;
    
    class FrontendController extends Controller
    {
        /**
         * @I18nRoute("/{name}", name="hello")
         */
        public function helloAction($slug)
        {
            // ...
        }
    
        /**
         * @I18nRoute({ "en": "/welcome/{name}", "fr": "/bienvenue/{name}", "de": "/willkommen/{name}" }, name="homepage")
         */
        public function indexAction($name)
        {
            // ...
        }
    }
    

Prefixing Imported Routes

You can also choose to provide a “prefix” for the imported routes. Combining this with normal routes will automatically localize them.

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    # app/config/routing.yml
    app:
        resource: '@AppBundle/Controller/'
        type: be_simple_i18n
        prefix:
            en: /website
            fr: /site
            de: /webseite
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- app/config/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            http://symfony.com/schema/routing/routing-1.0.xsd">
    
        <import resource="@AppBundle/Resources/config/routing/i18n.xml" type="be_simple_i18n">
            <locale key="en">/english</locale>
            <locale key="de">/german</locale>
            <locale key="fr">/french</locale>
        </import>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // app/config/routing.php
    use BeSimple\I18nRoutingBundle\Routing\RouteGenerator\I18nRouteGenerator;
    use Symfony\Component\Routing\RouteCollection;
    
    $generator = new I18nRouteGenerator();
    
    $app = $loader->import('@AppBundle/Controller/', 'annotation');
    $app = $generator->generateCollection(array(
        'en' => '/english',
        'de' => '/german',
        'fr' => '/french',
    ), $app);
    
    $collection = new RouteCollection();
    $collection->addCollection($app);
    
    return $collection;
    

More Stuff

Route Generation

Using a specify locale

  • Twig
    1
    2
    3
    4
    5
    6
    7
    8
    {{ path('homepage.en') }}
    {{ path('homepage', { 'locale': 'en' }) }}
    
    {{ path('homepage.fr') }}
    {{ path('homepage', { 'locale': 'fr' }) }}
    
    {{ path('homepage.de') }}
    {{ path('homepage', { 'locale': 'de' }) }}
    
  • PHP
    1
    2
    3
    4
    5
    6
    <?php echo $view['router']->generate('homepage.en') ?>
    <?php echo $view['router']->generate('homepage', array('locale' => 'en')) ?>
    <?php echo $view['router']->generate('homepage.fr') ?>
    <?php echo $view['router']->generate('homepage', array('locale' => 'fr')) ?>
    <?php echo $view['router']->generate('homepage.de') ?>
    <?php echo $view['router']->generate('homepage', array('locale' => 'de')) ?>
    

Note

When using the locale to generate the route make sure you use the locale parameter and not _locale.

Using the current locale

  • Twig
    1
    {{ path('homepage') }}
    
  • PHP
    1
    <?php echo $view['router']->generate('homepage') ?>
    

Route Attribute Translation

If the static parts of your routes are translated you get to the point really fast when dynamic parts such as product slugs, category names or other dynamic routing parameters should be translated. The bundle provides 2 implementations.

After configuring the backend you want to use (see below for each one), you can define a to be translated attribute in your route defaults:

1
2
3
4
5
6
7
8
9
product_view:
    locales: { en: "/product/{slug}", de: "/produkt/{slug}" }
    defaults: { _controller: "ShopBundle:Product:view", _translate: "slug" }

product_view2:
    locales: { en: "/product/{category}/{slug}", de: "/produkt/{category}/{slug}" }
    defaults:
        _controller: "ShopBundle:Product:view"
        _translate: ["slug", "category"]

The same goes with generating routes, now backwards:

1
2
{{ path("product_view", {"slug": product.slug, "translate": "slug"}) }}
{{ path("product_view2", {"slug": product.slug, "translate": ["slug", "category]}) }}

The reverse translation is only necessary if you have the “original” values in your templates. If you have access to the localized value of the current locale then you can just pass this and do not hint to translate it with the “translate” key.

Doctrine DBAL Backend

Configure the use of the DBAL backend

1
2
3
4
5
6
# app/config/config.yml
be_simple_i18n_routing:
    attribute_translator:
        type: doctrine_dbal
        connection: default # Doctrine DBAL connection name. Using null (default value) will use the default connection
        cache: apc

The Doctrine Backend has the following table structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
CREATE TABLE routing_translations (
    id INT NOT NULL,
    route VARCHAR(255) NOT NULL,
    locale VARCHAR(255) NOT NULL,
    attribute VARCHAR(255) NOT NULL,
    localized_value VARCHAR(255) NOT NULL,
    original_value VARCHAR(255) NOT NULL,
    UNIQUE INDEX UNIQ_291BA3522C420794180C698FA7AEFFB (route, locale, attribute),
    INDEX IDX_291BA352D951F3E4 (localized_value),
    PRIMARY KEY(id)
) ENGINE = InnoDB;

Lookups are made through the combination of route name, locale and attribute of the route to be translated.

Every lookup is cached in a DoctrineCommonCacheCache instance that you should configure to be APC, Memcache or Xcache for performance reasons.

If you are using Doctrine it automatically registers a listener for SchemaTool to create the routing_translations table for your database backend, you only have to call:

1
2
./app/console doctrine:schema:update --dump-sql
./app/console doctrine:schema:update --force

Translator backend

This implementation uses the Symfony2 translator to translate the attributes. The translation domain will be created using the pattern <route name>_<attribute name>

1
2
3
4
# app/config/config.yml
be_simple_i18n_routing:
    attribute_translator:
        type: translator
Custom backend

If you want to use a different implementation, simply create a service implementing BeSimpleI18nRoutingBundleRoutingTranslatorAttributeTranslatorInterface.

1
2
3
4
5
# app/config/config.yml
be_simple_i18n_routing:
    attribute_translator:
        type: service
        id: my_attribute_translator

Customizing the Router

Route Naming

By default all routes that are imported are named ‘<route_name>.<locale>’ but sometimes you may want to change this behaviour. To do this you can specify a route name inflector service in your configuration as followed.

1
2
3
4
# app/config/config.yml
be_simple_i18n_routing:
    # Service must implement the `BeSimple\I18nRoutingBundle\Routing\RouteGenerator\NameInflector\RouteNameInflectorInterface`
    route_name_inflector: "my_route_name_inflector_service"

There are currently 2 inflectors available by default be_simple_i18n_routing.route_name_inflector.postfix and be_simple_i18n_routing.route_name_inflector.default_postfix.

Default Postfix Inflector

The default postfix inflector changed the behaviour to only add a locale postfix when the locale is not the default locale. For this to work correctly you must configure a default_locale.

1
2
3
4
5
# app/config/config.yml
be_simple_i18n_routing:
    route_name_inflector: 'be_simple_i18n_routing.route_name_inflector.default_postfix'
    locales:
        default_locale: '%kernel.default_locale%'

Route Filtering

During the development of your system you may want to filter out a (not fully) implemented locale. This can be done why configuring the supported locales and enabling filtering.

1
2
3
4
5
# app/config/config.yml
be_simple_i18n_routing:
    locales:
        supported: ['en', 'nl']
        filter: true

The above configuration will only generate routes for the en and nl locale and will ignore any other locales.

Strict Route Locales

During development it can be nice to ensure that all routes are localized. This can be done why configuring the supported locales and enabling strict localization.

1
2
3
4
5
# app/config/config.yml
be_simple_i18n_routing:
    locales:
        supported: ['en', 'nl']
        strict: true

The strict options has 3 settings:

  • true for throwing a exception when a i18n route is found where the locale is unknown or where a locale is missing.
  • null for throwing a exception where a locale is missing.
  • false for disabling strict routes (default)