Content Notice!

This post is really old, and no longer reflect my skill level, views or opinions, it is made available here for archival purposes (it was originally on my old WordPress blog).

Keep that in mind when you read the contents within.

i18n in Slim Framework using Symfony/Translation & Twig

I've been using Slim at work for the past week creating an internal web application that displays some statistics from an API, and I opted to use Twig for rendering the views since it is the templating engine I am most familiar with and in my opinion has the cleanest syntax.

The application is going to be used by multiple people and should support multiple languages, I thought that throwing an i18n(internationalization) component from Packagist was going to be a quick task, but after messing around for an hour I had figured that this might not be as easy as I initially thought.

But I finally figured out the best course of action and I thought I'd share it with all of you so that nobody else needs to reinvent the wheel or wade through piles of shitty documentation... again.

The solution I came up with involved using the Translation component from Symfony along with the a Twig extension called TranslationExtensionBridge (or some other obscurely complex name).

Either way, let's get on with it!

I've put a sample application up on GitHub that you can take a look at or clone & modify.

What we first have to do is to create our composer.json file, this is what it looks like:

{
  "minimum-stability": "stable",
  "require": {
    "slim/slim": "2.6.2",
    "twig/twig": "1.21.1",
    "slim/views": "0.1.3",
    "twig/extensions": "1.2",
    "symfony/translation": "2.7.3",
    "symfony/twig-bridge": "2.7.3"
  }
}

We're pulling in Slim, SlimViews, Twig, Twix Extensions and Symfony translation and Symfony twig bridge.

Before we start messing with composer, we should create our file structure, You can obviously do this however you want, but for this example here is how my project is setup:

/lang
    en_US.php
    no_NB.php
/views
    example.twig
index.php
composer.json

the files inside of /lang are simple PHP files that immediately return an associative array like so:

<?php
return array(
    'welcome' => 'Welcome to my website, this is just a test',
    'random' => 'I am a random text string, I am only using it as an example'
);

Now we're ready to run composer install and watch the fancy green text scroll down our terminal for awhile, when that is completed create your index.php file and pull in the composer autoloader file and use  the ridiculously long symfony namespaces, so we don't have to type them out fully each time.

<?php
// include the composer autoloader
require './vendor/autoload.php';

// Use the ridiculously long Symfony namespaces
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Component\Translation\Loader\PhpFileLoader;
use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\Translation\Translator;

Then we create a Slim application instance.

// Instantiate and setup Slim application instance
$app = new \Slim\Slim(array(
    'view' => new \Slim\Views\Twig(),
    'templates.path' => './views'
));

We then have to create an instance of the Translation class like so:

// First param is the "default language" to use.
$translator = new Translator("no\_NB", new MessageSelector());

// Set a fallback language incase you don't have a translation in the default language
$translator->setFallbackLocales(['en_US']);

// Add a loader that will get the php files we are going to store our translations in
$translator->addLoader('php', new PhpFileLoader());

// Add language files here
$translator->addResource('php', './lang/no_NB.php', 'no_NB'); // Norwegian
$translator->addResource('php', './lang/en_US.php', 'en_US'); // English

What we are doing in the snippet above is creating a new Translator instance, passing it a "default" language as the first param and a MessageSelector instance as the second, what does the MessageSelector do you ask, well I have no fucking clue, it said to use it in the documentation so I did.

Anyways, we then set our fallback languages, notice that it is an array of languages and not a string, this languages will be used if the one you selected as default does not exist or does not have the translation string available.

The method $translator->addLoader() will setup a loader that is used to read in the language files you are going to add next with the $translator->addResource()it should be self explanatory from the code what it does, it grabs a language file and loads it into "memory" so that we can reference strings in it later.

After we've done all that we have to add Twig and the Translation Bridge to the slim view ParserExtensions, you do this like so:

// Get the view
$view = $app->view();
// Add the parserextensions TwigExtension and TranslationExtension to the view
$view->parserExtensions = array(
new Slim\Views\TwigExtension(),
new TranslationExtension($translator)
);

and you should be good to go, let's fire up an example route!

// setup a home route
$app->get("/", function () use ($app) {
    // Render a twig view
    $app->render("example.twig");
});
// Run the slim app
$app->run();

Here is the contents of the example.twig file:

<!DOCTYPE html>

<html>
<head>
    <title>i18n in Slim Framework using Twig and Symfony Translate</title>
    <meta charset="utf-8">
</head>
<body>
<h1>i18n with Slim Framework, Twig and Symfony Translate</h1>

<p>{{ "welcome" | trans }}</p>

<p>{{ "random" | trans }}</p>

</body>
</html>

The {{ "welcome" | trans }} strings will be replaced by the corresponding translation found in your language files.

Try it by yourself, git clone the example application and look through the code!

If you need more documentation on twig, translation and slim framework, take a look at these pages: