We can not imagine a modern web application without a multi language support. I will show you how easy it is to setup usage of multiple languages in Zend Framework and how to setup some basic language routes (handy for SEO stuff).
The Zend Framework offers many ways for you to store your translated strings. It could be an array, a CSV or XML file or you could use gettext which we will be using today.
Why GetText?
There are many reasons I choose GetText. First, there is a handy program for editing GetText files called PoEdit (a bit on it later). So, when you have a handy program, it is easy to send it to translators, who do not have to mess with some arrays or XML files.
Second, Apache web server is caching the compiled .mo translation files, which is making them really fast.
Let’s start.
First you need an example application, it doesn’t need to be more complicated than a clean installation of ZF with an IndexController and a matching view, and of course the bootstrap.
So, open your Bootstrap.php file and add this method to the play:
[code lang=”php”]
protected function _initTranslate()
{
$translate = new Zend_Translate(‘gettext’,
APPLICATION_PATH . “/langs/”,
null,
array(‘scan’ => Zend_Translate::LOCALE_DIRECTORY));
$registry = Zend_Registry::getInstance();
$registry->set(‘Zend_Translate’, $translate);
$translate->setLocale(‘en’);
}
[/code]
So, what did we just do? We created the Zend_Translate object and pass in the directory containing all of our different languages. After that we saved our Zend_Translate object to the registry so we don’t need to create our object over and over again in our application.
Now, let’s go in our view and add some translations. Open up your index view and translate some strings that you find there:
[code lang=”php”]
translate(“Hello World!”); ?>
translate(“This is a Zend_Translate demonstration”); ?>
[/code]
Simply by using translate method in our view, all the GetText magic is going to happen later.
Now, go to PoEdit website and download PoEdit program and install it. Launch PoEdit.
Create a new catalog (File -> new catalog). Choose the language you want to translate the application to. We are going to use German, GERMANY and utf-8 for both character sets. Leave the plural forms blank.
On the paths tab, set the base path to the directory containing your application file, and add “.” as a path.
Click on the Keyword tab and add translate to keywords list. This way, we are telling PoEdit to scan for our translate methods.
Click OK and save a file in folder /application/langs/de_DE/LC_MESSAGES/ which you must create as messages.po.
Now, PoEdit does not scan PHTML files by default. So, go to Files — Preferences — Parser — PHP — Edit and add *.phtml on the list of extensions. Parser command line should look like this:
[code lang=”sql”]
xgettext –force-po -o %o %C %K %F -L php
[/code]
PoEdit will now automatically scan all source files inside the path you specified earlier and extract all strings that are passed to gettext() or _() or translate(). Click OK.
Now translate our two strings by clicking on them one by one and entering its german translation in lower box. Click Save. This will create messages.mo which will be used by our application later.
If you test the application now, it should be working and you should see default English strings displayed. That is because we set the Locale to en in our Bootstrap.php.
The idea is to have automatic routing of languages, so you won’t have to do it over and over again. So, open your Bootstrap.php again and insert our Routes in:
[code lang=”php”]
public function _initRoutes()
{
$this->bootstrap(‘FrontController’);
$this->_frontController = $this->getResource(‘FrontController’);
$router = $this->_frontController->getRouter();
$langRoute = new Zend_Controller_Router_Route(
‘:lang/’,
array(
‘lang’ => ‘de’,
)
);
$defaultRoute = new Zend_Controller_Router_Route(
‘:controller/:action’,
array(
‘module’=>’default’,
‘controller’=>’index’,
‘action’=>’index’
)
);
$defaultRoute = $langRoute->chain($defaultRoute);
$router->addRoute(‘langRoute’, $langRoute);
$router->addRoute(‘defaultRoute’, $defaultRoute);
}
[/code]
Now, the changing of Locale and translation strings are done in our Controller Plugin. So create one:
[code lang=”php”]
class App_Controller_Plugin_Language
extends Zend_Controller_Plugin_Abstract
{
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$lang = $request->getParam(‘lang’, null);
$translate = Zend_Registry::get(‘Zend_Translate’);
if ($translate->isAvailable($lang)) {
$translate->setLocale($lang);
} else {
$translate->setLocale(‘en’);
}
// Set language to global param so that our language route can
// fetch it nicely.
$front = Zend_Controller_Front::getInstance();
$router = $front->getRouter();
$router->setGlobalParam(‘lang’, $lang);
}
}
[/code]
This plugin is pretty trivial, you can play with it as you like, you can add COOKIE or SESSION support or whatever you like.
Now, back to our Bootstrap.php to init the plugin:
[code lang=”php”]
protected function _initLanguage()
{
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new App_Controller_Plugin_Language());
}
[/code]
Now, test you application with ‘en’ or ‘de’ suffixes (myapp.home/de or myapp.home/en). You should see the transaltion is working perfectly.
Have you got any suggestions to improve this tutorial? Any new ideas for translating content with Zend Framework?
Great article. Complete flow in one article.
Pingback: CodeForest.net: Mehrsprachige Unterstützung in Zend Framework | PHP Boutique
It’s right gettext seems to be a good method to provide translations, but be aware it’s not thread safe. Therefore we switched to a simple array construction: the arrays can be exported to any other format pretty easily. Also with a proper cache set to Zend_Translate, all adapters perform equally.
@Jurian To my understanding PHP’s gettext() is not threaded safe, we can utilize *nix gettext . For years the gettext has been used.
Great article. I too was also planning to write one for a long time, but never happened 🙂 .
Change the default route to something like this:
$defaultRoute = new Zend_Controller_Router_Route(
':module/:controller/:action',
array(
'module'=>':module',
'controller'=>'index',
'action'=>'index'
)
);
I hope this helps.
Just found this article and liked the solution. But I also had same module based problem, and I might have found a way to add module support for this solution.
In my case I have a module named webshop and there i don’t need language routes. Instead I just need it to work on a single language. So for the default module Zvonko’s original code worked like a charm, but webshop module wouldn’t open.
Leaving all the original routes from your solution I added one more route for the webshop module. And the result worked. Finally I had 3 routes that looked like this:
$langRoute = new Zend_Controller_Router_Route(
':lang/',
array(
'lang' => 'hr'
)
);
$defaultRoute = new Zend_Controller_Router_Route(
':controller/:action',
array(
'module'=>'default',
'controller'=>'index',
'action'=>'index'
)
);
$webshopRoute = new Zend_Controller_Router_Route(
'/webshop/:controller/:action',
array(
'module' => 'webshop',
'controller' => 'index',
'action' => 'index'
)
);
$defaultRoute = $langRoute->chain($defaultRoute);
$router->addRoute('langRoute', $langRoute);
$router->addRoute('defaultRoute', $defaultRoute);
$router->addRoute('webshopRoute', $webshopRoute);
With this setup, the default module continued to work with languages chained, and the webshop module started to work as intended.
So I am guessing one could add one route for each module, and those that are chained with langRoute will work with languages and those that are not chained will work as intended.
Hope I wasn’t too confusing.
Cheers
Comments are closed.