How To Translate Content With The Translate ViewHelper

You want to make your site available in a wide array of different languages, well, at least 2 — how do you do it? Zend Framework provides Internationalisation (I18N) support and the translate viewHelper to handle this for you quickly and simply. In today’s post, we step through how to set up translations and use them in view templates.


tl;dr

You want to make your site available in a wide array of different languages, well, at least 2 — how do you do it? Zend Framework provides Internationalisation (I18N) support and the translate viewHelper to handle this for you quickly and simply. In today’s post, we step through how to set up translations and use them in view templates.

Getting Started With Translations

In any modern PHP application, especially one built with Zend Framework 2, you’re going to need translations at some point or another. If you’re not familiar with translations, they’re the ability to translate content between a range of languages, such as from English to Czech, English to German, or vice versa.

Let’s put it in to context and assume that you’ve created a website to host video content for your business and decided your initial target audience will be English. It’s a fair assumption to make, given how much tech content is written in English.

Let’s assume that the site’s been live for 6 months and much to your pleasant surprise, it’s rapidly becoming popular. In the second half of the year you notice that it’s rapidly gaining traction in both Germany and the Czech Republic (yes, at one time I could get by in Prague with the amount of Czech I could speak). I’ve picked these two mainly for a bit of contrast from what you might likely expect to see.

Let’s say that these two locale’s now account for 30% of your site’s traffic. It’s fair to say that there’s enough to justify implementing translations for these languages. The question is, you’re not sure just how to do it. You want to make sure that you make it as easy for people in these two countries to use your application as possible.

How Do You Do It?

The answer is translations. Available as part of the ZendI18n classes, translations provide complete support for translating text content from one locale to another.

Whether you’re translating single words, plurals, or phrases, it has you covered. It only has one real dependency, a base locale and PHP’s Intl extension.

In today’s post, I’m going to show you how to implement them, assuming your application’s based on a standard Zend Skeleton project.

How Does It Work?

1. Prepare Translation Sources

First you need to prepare your translation sources. These are, essentially, a map of the text you want to translate with the translation into the locale of choice.

This can be created in a number of formats, including Gettext, PHP Arrays, the INI file format. These are then loaded as a resource and ideally cached for performance reasons.

2. Specify The Active Locale

You then specify which locale to use. There’s a number of ways to do this, which we’ll cover shortly. Based on that, when a translate viewHelper is encountered, it checks which locale is in effect.

If it’s not the original language, a lookup is performed to see if there’s a translation available in the locale in effect. If so, then it’s returned, translating the text. If not, the original text is displayed.

3. Use the Translate ViewHelper

Finally, in your view templates you wrap text which you want to be able to be translated in the translate viewHelper. Below is an example taken from the standard ZF2 Skeleton layout.phtml file.

<a class="navbar-brand" href="<?php echo $this->url('home') ?>">
    <img src="<?php echo $this->basePath('img/zf2-logo.png') ?>" alt="Zend Framework 2"/> 
    <?php echo $this->translate('Skeleton Application') ?>
</a>

In this example, “Skeleton Application” is able to be translated, if desired.

Implementing Translations

Adding The Intl Extension

It’s quite straight-forward to install. However, if you’re on Mac, you might encounter some issues. If you do, check out Rob Allen’s post on it, where he lists how to get around them.

Configuring Translations

Here’s the standard configuration from the ZF2 Skeleton Application module’s module.config.php file:

'translator' => array(
  'locale' => 'en_US',
    'translation\_file\_patterns' => array(
      array(
        'type' => 'gettext',
        'base\_dir' => \\_\_DIR\_\_ . '/../language',
        'pattern' => '%s.mo',
      ),
    ),
),

The translator element handles everything relating to translator configuration. The locale to use is configured, appropriately, in the locale element. This is set to &’en_US’. To see a complete list check out this link.

Next comes &’translation_file_patterns’, which is where you configure which file type to use, where the files can be found and the file pattern to use. Let’s break it down.

Firstly, we’re saying to use the gettext file format, that the files can be found under the language directory, in the root of the Application module directory; and finally, that the file will start with the locale name, e.g. cs_CZ, de_DE, or en_US and have a .mo extension.

Prepare Translation Sources

As I mentioned earlier, Zend Framework 2 supports a number of translation sources, including PHP arrays, INI files, and the Gettext file format. I’m going to start with PHP arrays, then show how to create the maps in the other formats.

PHP Arrays

Let’s say that we’re just going to translate the string “Skeleton Application” from the standard layout file. Here’s what the array would look like in all locales:

In German
<?php
return array(
  'Skeleton Application' => 'das Skelett Applikation'
);
In Czech
<?php
return array(
    "Skeleton Application" => 'Skeleton aplikace'
);

You can see that it’s a simple array, listing the string to convert on the left, and the localisation on the right. This is definitely the simplest to create and maintain. There is no fancy format or overly verbose structure to master.

INI Files

Now let’s look at the INI file format. This one, you might be surprised to learn is more complicated than the PHP array format.

In German
[Skeleton Application]
message = Skeleton Application
translation = das Skelett Applikation
In Czech
[Skeleton Application]
message = Skeleton Application
translation = Skeleton aplikace

The ini file format is a bit more verbose than the PHP array format. For each translation, a group needs to be added, which is defined by the string to translate, in brackets. Then, in each one, you need to specify with the key message, the string to translate, and with translation, the translation to return, when found.

Gettext Files

Regarding Gettext, there’s already a lot of work taken care of in the Zend Skeleton project. As we saw earlier in the article, the configuration’s already in place. Additionally a base set of translations already exists in module/Application/language, where you’ll find translations in 20 languages. Here’s a snippet from de_DE.po:

#: ../view/error/404.phtml:1
msgid "A 404 error occurred"
msgstr "Es trat ein 404 Fehler auf"

The first line shows the file and line of the translation is for, msgid is the text to be translated, and msgstr is the translated string.

Creating the files isn’t as straight-forward as I’d like. But Manuel Stosic has a great post detailing how to use PoEdit to set everything up for you.

Changing Locales On The Fly

Now it’s one thing to setup locales which are set at configuration time, but what about changing them on the fly, say in response to a user choice, a browser setting, a query parameter, or a request header?

These are all possible, but here are two examples. They’re going to assume that you have a route parameter called locale available. To do that, we’ll need to change the default route in module.config.php to the following:

'default' => array(

  'type' => 'Segment',

  'options' => array(

    'route' => '/\[:controller[/:action\]\[/:locale\]]',

    'constraints' => array(

      'controller' => '\[a-zA-Z\]\[a-zA-Z0-9_-\]*',

      'action' => '\[a-zA-Z\]\[a-zA-Z0-9_-\]*',

      'locale' => '[a-zA-Z]{2}_[a-zA-Z]{2}',

    ),

    'defaults' => array(

    'locale' => 'en_US'

    ),

  ),

),

Here I’ve set locale as an optional route parameter, which defaults to ‘en_US’. No, it’s not too sophisticated; but for the following two examples, it’s sufficient.

Changing In Module.php

Module.php is currently my preferred way of doing this kind of operation. In the snippet below, I’ve slightly customised the standard onBootstrap method which comes with the Application module.

I’ve attached a closure to the dispatch event, which checks if the route parameter, locale has been set. If so, it then retrieves the translator service from the ServiceManager and call’s the setLocale method on it.

As a result it will attempt to use the retrieved locale and translate the content of your site accordingly. Be aware though, this will run on every request, in every module. So it’s something you need to use with care.

public function onBootstrap(MvcEvent $e)
 {

  $app = $e->getApplication();

  $app->getEventManager()->attach(

  'dispatch',

  function($e) {

    $routeMatch = $e->getRouteMatch();

    if ($routeMatch->getParam('locale') != '') {

      $this->serviceManager = $e->getApplication()->getServiceManager();

      $translator = $this->serviceManager->get('translator');

      $translator->setLocale($routeMatch->getParam('locale'));

    }

  },
 100

);

}

Changing In A Controller Action

Another way is to change the translator in a specific controller action. The simplest way is to use the getServiceLocator() method, as below:

public function indexAction()
 {

  $this->getServiceLocator()->get('translator')->setLocale(
    $this->params()->fromRoute('locale')

  );

  return new ViewModel();

}

This does the same thing as above, just quicker and with less code. I want to stress that using the getServiceLocator method is not the way to go, unless you’re doing some kind of rapid prototyping, which will ultimately be replaced with constructor injection. I’ve mention it for the sakes of keeping the example concise.

As an experiment, try changing the locale with a custom header.

Setting Up Caching

Now let’s finish up by looking at improving performance by setting up caching with translations. To do so, you use the ‘cache’ key in the translator array and specify a cache configuration. Here’s an example one to get us started:

'cache' => array(
  'adapter' => array(
    'name' => 'Filesystem',
    'options' => array(
      'cache\_dir' => \\_\_DIR\_\_ . '/../../../data/cache',
      'ttl' => '3600'
    )
  ),
  'plugins' => array(
    array(
      'name' => 'serializer',
      'options' => array()
    ),
    'exception_handler' => array(
    'throw_exceptions' => true
    )
  )
),

This sets up a cache object, storing the cache files on the filesystem in a directory called data, off the project root directory. Here’s an example of what you’ll see after the translations have been cached:

|- data
| - cache
| - zfcache-a7
| - zfcache-Zend\_I18n\_Translator\_Messages\_784c47e4851a9d751d165e80e9e72f46.dat

The content’s of the cache file looks like:

C:31:"ZendI18nTranslatorTextDomain":97:{x:i:0;a:1:{s:20:"Skeleton Application";s:17:"Skeleton aplikace";};m:a:1:{s:13:"�*�pluralRule";N;}

Wrapping Up

And that’s how you setup translations and use the translate viewHelper in Zend Framework 2 to provide automatic translations of your site’s content.

Yes, it takes a bit of work to get it started, but if you’re basing your project off of the Zend Skeleton Application repository, a lot of the work is already done for you.

In part two of this series, we’re going to look at the cached and uncached performance of each container, using Siege as the testing tool. That way, you can see the respective performance of each one.

Till then, are you already using translations in your Zend Framework 2 applications? What format are you using? Are you having any issues? Share your experience in the comments.


You might also be interested in these tutorials too...


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.

Join the discussion

comments powered by Disqus