Using Zend Framework 2 and Zend\I18n\Translator\Translator, I would like to define the language of the translation dynamically in the same action.
Why ? I need to send email to every customers where the content translation language depends of the language defined in the customer profile. This will be executed by a CRON.
I tried this code for example :
/** #var \Zend\I18n\Translator\Translator $translator */
$translator = $this->getServiceLocator()->get('translator');
$translator->setLocale('fr_FR');
AbstractValidator::setDefaultTranslator($translator);
/** #var \Zend\View\Renderer\PhpRenderer $renderer */
$renderer = $this->getServiceLocator()->get('Zend\View\Renderer\PhpRenderer');
$view = new ViewModel();
$view->setTemplate('templatename')->setVariables($data);
var_dump($renderer->render($view));
/** #var \Zend\I18n\Translator\Translator $translator */
$translator = $this->getServiceLocator()->get('translator');
$translator->setLocale('en_EN');
AbstractValidator::setDefaultTranslator($translator);
/** #var \Zend\View\Renderer\PhpRenderer $renderer */
$renderer = $this->getServiceLocator()->get('Zend\View\Renderer\PhpRenderer');
$view = new ViewModel();
$view->setTemplate('templatename')->setVariables($data);
var_dump($renderer->render($view));
But both var_dump show the same content in the same language (in this case fr_FR).
Any idea ?
the problem with that is the dependency injection in zf2. in normal cases you define/init your locale in an zf2 event at the beginning of a dispatch event on every request over the module.php and onBootstrap method.
after the module initiation the Zend\View\HelperPluginManager class are loaded through the ServiceManager and inject into every ViewHelper ($this->translate is a ViewHelper too) instance the translator in the following order.
has the ServiceManager a MvcTranslator instance inject into viewhelper
has the ServiceManager a Zend\I18n\Translator\TranslatorInterface inject into viewhelper
has the ServiceManager a Translator inject into viewhelper
you change the last instance Translator but the viewhelper has a instance of the MvcTranslator
use
$translator = $this->getServiceLocator()->get('MvcTranslator');
and every viewhelper calling internal getTranslator getLocale return the new locale and use it to translate the given string from $this->translate('someString');
I haven't done this yet with zend framework 2 but i found a tutorial that should help you with what you want to achieve.
tutorial
update:
Okay i did not understand correctly. Nonetheless i tried what you are doing and i get the current locale to be en_EN
$translator = $this->getServiceLocator()->get('translator');
$translator->setLocale('fr_FR');
$translator->setLocale('en_EN');
echo $translator->getLocale();die;
so could it be failing to load the English translation and gets the fallback locale(assuming fr_FR) ?
Update 2.
I wanted to try and work something on this since i will soon need it so i tried to create a small test in order to help you with your problem and also learn something about it. But my approach is somewhat different that yours. I hope i understood correctly what you are trying to do.
Controller:
$translator = $this->getServiceLocator()->get('translator');
$translator->setLocale('fr_FR');
$locale = $translator->getLocale('fr_FR');
$textDomain = 'Users\Controller';
$view = new ViewModel(array(
'locale' => $locale,
'textDomain' => $textDomain,
'translator' => $translator,
));
return $view;
View:
<?php echo $translator->translate('Home', $textDomain, $locale); ?>
module.config.php:
'translator' => array(
'locale' => 'en_US',
'translation_file_patterns' => array(
array(
'type' => 'gettext',
'base_dir' => __DIR__ . '/../language',
'pattern' => '%s.mo',
'text_domain' => __NAMESPACE__,
),
),
),
From the instructions i was reading it was stated that text_domain should be unique so i am using NAMESPACE but i am hard coding that in my controller example above because the test was done on a different namespace so ignore the part just keep in mind that you need it.
So from what i was testing i was able to use the locale i set and translate the word HOME to Accueil. Is this what you where looking for?
Related
Hi I'm new to the php world.
I'm wondering What is the best way to handle multilingual routing ?
I'm starting to create a website with Phalcon php.
I have the following routing structure.
$router->add('/{language:[a-z]{2}}/:controller/:action/:params', array(
'controller' => 2,
'action' => 3,
'params' => 4,
));
$router->add('/{language:[a-z]{2}}/:controller/:action', array(
'controller' => 2,
'action' => 3,
));
$router->add('/{language:[a-z]{2}}/:controller', array(
'controller' => 2,
'action' => 'index',
));
$router->add('/{language:[a-z]{2}}', array(
'controller' => 'index',
'action' => 'index',
));
My problem is for instance when I go on mywebsite.com/ I want to change my url in the dispatcher like mywebsite.com/en/ or other language. Is it a good practise to handle it in the beforeDispatchLoop ? I seek the best solutions.
/**Triggered before entering in the dispatch loop.
* At this point the dispatcher don't know if the controller or the actions to be executed exist.
* The Dispatcher only knows the information passed by the Router.
* #param Event $event
* #param Dispatcher $dispatcher
*/
public function beforeDispatchLoop(Event $event, Dispatcher $dispatcher)
{
//$params = $dispatcher->getParams();
$params = array('language' => 'en');
$dispatcher->setParams($params);
return $dispatcher;
}
This code doesn't work at all, my url is not change. The url stay mywebsite.com/ and not mywebsite.com/en/
Thanks in advance.
I try one solution above.
The redirect doesn't seems to work. I even try to hard-coded it for test.
use Phalcon\Http\Response;
//Obtain the standard eventsManager from the DI
$eventsManager = $di->getShared('eventsManager');
$dispatcher = new Phalcon\Mvc\Dispatcher();
$eventsManager->attach("dispatch:beforeDispatchLoop",function($event, $dispatcher)
{
$dispatcher->getDI->get('response')->redirect('/name/en/index/index/');
}
I think you are confusing something. If I understand correctly: you want to redirect users to a valid url if they open a page without specifying a language.
If that's the case you should verify in your event handler whether the language parameter is specified and redirect user to the same url + the default language if its missing. I am also assuming that your beforeDispatchLoop is located in your controller, which is also part of the problem, because your route without a language never matches and you never get into that controller. Instead you need to use it as an event handler with the event manager as per the documentation. Here is how you do the whole thing.
$di->get('eventsManager')->attach("dispatch:beforeDispatchLoop", function(Event $event, Dispatcher $dispatcher)
{
$params = $dispatcher->getParams();
// Be careful here, if it matches a valid route without a language it may go into a massive
// recursion. Really, you probably want to use `beforeExecuteRoute` event or explicitly
// check if `$_SERVER['REQUEST_URI']` contains the language part before redirecting here.
if (!isset($params['language']) {
$dispatcher->getDI->get('response')->redirect('/en' . $_SERVER['REQUEST_URI']);
return false;
}
return $dispatcher;
});
I'm using ZFCUser and need to develop a German project.
Unfortunately the login form is in English and I couldn't find a way to translate the form fields or especially the error messages to English.
Is there maybe a global way for the module to either overwrite the messages or switch the language?
Thanks!
Edit:
This is my translator call in my bootstrap:
$translator = new Translator();
$translator->addTranslationFile(
'phpArray',
'vendor/zendframework/zendframework/resources/languages/de/Zend_Validate.php',
'default',
'de_DE'
);
AbstractValidator::setDefaultTranslator($translator);
Edit II:
My custom translation file:
<?php
return array(
// ZFCUser
"Authentication failed. Please try again." => "test"
);
My factory:
<?php
class CustomTranslatorFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $sl)
{
$translator = new Translator();
$translator->addTranslationFile(
'phpArray',
'vendor/.../TranslationTable.php',
'default',
'de_DE'
);
return $translator;
}
}
Module.php from my User module:
public function getServiceConfig()
{
return array(
'factories' => array(
'translator' => 'Path\To\Translator\CustomTranslatorFactory',
),
);
}
Edit III:
My Translator looks like this. Even when using a PHP file instead of the array just nothing happens. No error, no translation. Any ideas what I'm doing wrong?
$custom_translations = array(
"Authentication failed. Please try again." => "test",
);
$translator = new Translator();
$translator->addTranslationFile(
'phpArray',
'vendor/zendframework/zendframework/resources/languages/de/Zend_Validate.php',
'default',
'de_DE'
);
$translator->addTranslationFile(
'phpArray',
$custom_translations
);
return $translator;
Just for Your knowledge - since yesterday there's a modular de_DE translation available (pl_PL and ja_JP are available too). It covers only ZfcUser messages, so not all validators, but form labels etc. https://github.com/websafe/zf-mod-zfc-user-i18n-de-de Easy installable and available via Packagist too.
I'm creating an answer for that because it's becoming too complex for the comment section.
The validator library does translation of messages on its own. As you've assigned the translator to it, your validation messages are fine.
However, form label translation belongs to another piece of library. Accordingly, they also need a translator assigned to them. As stated above in the comments, you can either do that manually (by invoking $viewHelper->setTranslator($translator)) or let the ViewHelperManager do that for you.
You can easily refactor your code to support the second case.
Create a factory class for your translator.
Register that factory to SM. Use the "translator" key.
If you need it, retrieve your $translation var through SM in future.
Example (uses skipped):
/** One of your modules, should be a base module that's always loaded */
class Module
{
// ...
public function getServiceConfig()
{
return array(
'factories' => array(
'translator' => 'Your\Translator\Factory', // could also be a closure (anonymous function)
)
);
}
}
-
class YourTranslatorFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $sl)
{
$translator = new Translator();
$translator->addTranslationFile(
'phpArray',
'vendor/zendframework/zendframework/resources/languages/de/Zend_Validate.php',
'default',
'de_DE'
);
return $translator;
}
}
If you need to access it, simpyl retrieve it from SM as you're used to ($translator = $sm->get('translator');).
Note: Validators implement the TranslatorAwareInterface. This means that, if you've registered the translator key to SM, they also should be injected into the validators automatically. Thus, you can even skip the static method call.
Note also: This is just an example of how you can do it without changing much. You could also reach this goal just by configuration.
NOTE: This is an old question and the answers here no longer works (since beta5). See this question on how to do it with ZF2 stable version.
I have looked at this example from the manual. Note that this is version 2 of the Zend Framework.
I create this helper:
<?php
namespace Mats\Helper;
use Zend\View\Helper\AbstractHelper;
class SpecialPurpose extends AbstractHelper
{
protected $count = 0;
public function __invoke()
{
$this->count++;
$output = sprintf("I have seen 'The Jerk' %d time(s).", $this->count);
return htmlspecialchars($output, ENT_QUOTES, 'UTF-8');
}
}
?>
and then try to register it like this:
return array(
'di' => array('instance' => array(
'Zend\View\HelperLoader' => array('parameters' => array(
'map' => array(
'specialpurpose' => 'Mats\Helper\SpecialPurpose',
),
)),
)),
);
but when doing this in a view, for instance add.phtml
<?php echo $this->specialPurpose(); ?>
It will crash, saying it cannot find the helper.
However, in the same add.phtml file I can do
<?php $helper = new Mats\Helper\SpecialPurpose(); ?>
and have access to it, so I guess the namespace should be right?
Currently I register it in Module.php, but I have also tried elsewhere.
My goal is to have access to the view helper in all views in my module, without having to create an instance of it in each phtml file, and not having to add it every time in the controller.
How can this be achieved? Thanks.
ZF2 moved to service managers with programmatic factories, while di used as fallback factory.
For view there is view manager now and as service resolution stops as soon as it found factory, helpers configured via di no longer work.
Example how you should register helpers now you can find in ZfcUser module config
Add custom helper is very simple, just add one line to your module config file like this:
return array(
'view_manager' => array(
'helper_map' => array(
'specialPurpose' => 'Mats\Helper\SpecialPurpose',
),
),
);
How I can get access to my module config from the controller?
I am really surprised at how obscure this is, because I had exactly the same problem and could not find a definitive answer. One would think the ZF2 documentation would say something about this. Anyhow, using trial and error, I came across this extremely simple answer:
Inside controller functions:
$config = $this->getServiceLocator()->get('Config');
Inside Module class functions (the Module.php file):
$config = $e->getApplication()->getServiceManager()->get('Config');
whereas $e is an instance of Zend\Mvc\MvcEvent
In general, the config is accessible from anywhere you have access to the global service manager since the config array is registered as a service named Config. (Note the uppercase C.)
This returns an array of the union of application.config.php (global and local) and your module.config.php. You can then access the array elements as you need to.
Even though the OP is quite old now, I hope this saves someone the hour or more it took me to get to this answer.
What exactly do you want to do in your controller with the module configuration? Is it something that can't be done by having the DI container inject a fully configured object into your controller instead?
For example, Rob Allen's Getting Started with Zend Framework 2 gives this example of injecting a configured Zend\Db\Table instance into a controller:
return array(
'di' => array(
'instance' => array(
'alias' => array(
'album' => 'Album\Controller\AlbumController',
),
'Album\Controller\AlbumController' => array(
'parameters' => array(
'albumTable' => 'Album\Model\AlbumTable',
),
),
'Album\Model\AlbumTable' => array(
'parameters' => array(
'config' => 'Zend\Db\Adapter\Mysqli',
)),
'Zend\Db\Adapter\Mysqli' => array(
'parameters' => array(
'config' => array(
'host' => 'localhost',
'username' => 'rob',
'password' => '123456',
'dbname' => 'zf2tutorial',
),
),
),
...
If you need to do additional initialization after the application has been fully bootstrapped, you could attach an init method to the bootstrap event, in your Module class. A blog post by Matthew Weier O'Phinney gives this example:
use Zend\EventManager\StaticEventManager,
Zend\Module\Manager as ModuleManager
class Module
{
public function init(ModuleManager $manager)
{
$events = StaticEventManager::getInstance();
$events->attach('bootstrap', 'bootstrap', array($this, 'doMoarInit'));
}
public function doMoarInit($e)
{
$application = $e->getParam('application');
$modules = $e->getParam('modules');
$locator = $application->getLocator();
$router = $application->getRouter();
$config = $modules->getMergedConfig();
// do something with the above!
}
}
Would either of these approaches do the trick?
for Beta5, you can add function like this in Module.php
public function init(ModuleManager $moduleManager)
{
$sharedEvents = $moduleManager->getEventManager()->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, 'dispatch', function($e) {
$config = $e->getApplication()->getConfiguration();
$controller = $e->getTarget();
$controller->config = $config;
});
}
in controller, you can get config :
print_r($this->config);
To read module-only config your module should just implement LocatorRegisteredInterface
Before:
namespace Application;
class Module
{
// ...
}
After:
namespace Application;
use Zend\ModuleManager\Feature\LocatorRegisteredInterface;
class Module implements LocatorRegisteredInterface
{
// ...
}
That implementation says LocatorRegistrationListener to save module intance in service locator as namespace\Module
Then anywhere you can get access to your module:
class IndexController extends AbstractActionController
{
public function indexAction()
{
/** #var \Application\Module $module */
$module = $this->getServiceLocator()->get('Application\Module');
$moduleOnlyConfig = $module->getConfig();
// ...
}
}
There is a pull request ready now which pulls the module class (so the modules/foo/Module.php Foo\Module class) from the DI container. This gives several advantages, but you are also able to grab that module instance another time if you have access to the Zend\Di\Locator.
If your action controller extends the Zend\Mvc\Controller\ActionController, then your controller is LocatorAware. Meaning, upon instantiation your controller is injected with the locator knowing about modules. So, you can pull the module class from the DIC in your controller. Now, when your module consumes a config file and stores this inside the module class instance, you can create a getter to access that config data from any class with a locator. You probably have already an accessor with your module Foo\Module::getConfig()
While ZF2 is heavily under development and perhaps this code will change later on, this feature is currently covered by this test, with this the most relevant part:
$sharedInstance = $locator->instanceManager()->getSharedInstance('ListenerTestModule\Module');
$this->assertInstanceOf('ListenerTestModule\Module', $sharedInstance);
So with $sharedInstance your module class, you can access the config from there. I expect a shorthand for this feature soon, but this can only be done after PR #786 has been merged in ZF2 master.
You need to implements ServiceLocatorAwareInterface from your model. And then you can set setServiceLocator() and getServiceLocator() which give you direct access to the service manager. Take a look at this code sample https://gist.github.com/ppeiris/7308289
I created the module with controller plugin and view helper for reading a config in controllers and views. GitHub link __ Composer link
Install it via composer
composer require tasmaniski/zf2-config-helper
Register new module "ConfigHelper" in your config/application.config.php file
'modules' => array(
'...',
'ConfigHelper'
),
Use it in controller and view files
echo $this->configHelp('key_from_config'); // read specific key from config
$config = $this->configHelp(); // return config object Zend\Config\Config
echo $config->key_from_config;
you can also access any config value anywhere by this hack/tricks
$configReader = new ConfigReader();
$configData = $configReader->fromFile('./config.ini');
$config = new Config($configData, true);
I have a questiom regarding the Zend Framework 2:
I have
library/System and library/Zend. the system is my custom library, which I want to configure de aplication (routes, modules, etc., and redirect user to correct module, controller and/or action).
I don't want to do this inside each application/modules/ModuleName/Module.php file. So, my library/System can do everything related to application configuration.
As said in the comments above: register to the bootstrap-event and add new routes there:
<?php
namespace Application;
use Zend\Module\Manager,
Zend\EventManager\StaticEventManager;
class Module
{
public function init(Manager $moduleManager)
{
$events = StaticEventManager::getInstance();
$events->attach('bootstrap', 'bootstrap', array($this, 'initCustom'), 100);
}
public function initCustom($e)
{
$app = $e->getParam('application');
$r = \Zend\Mvc\Router\Http\Segment::factory(array(
'route' => '/test',
'defaults' => array(
'controller' => 'test'
)
)
);
$app->getRouter()->addRoute('test',$r);
}
}
$app = $e->getParam('application'); does return an instance of Zend\Mvc\Application. Have a look there to see which additional parts you can get there. The bootstrap event is fired before the actual dispatching does happen.
Note that the ZendFramework 1 routes are not always compatible to the ZendFramework 2 ones.
Update to comments
public function initCustom($e)
{
$app = $e->getParam('application');
// Init a new router object and add your own routes only
$app->setRouter($newRouter);
}
Update to new question
<?php
namespace Application;
use Zend\Module\Manager,
Zend\EventManager\StaticEventManager;
class Module
{
public function init(Manager $moduleManager)
{
$events = StaticEventManager::getInstance();
$events->attach('bootstrap', 'bootstrap', array($this, 'initCustom'), 100);
}
public function initCustom($e)
{
$zendApplication = $e->getParam('application');
$customApplication = new System\Application();
$customApplication->initRoutes($zendApplication->getRouter());
// ... other init stuff of your custom application
}
}
This only happens in one zf2 module (named Application which can be the only one as well). This doesn't fit your needs? You could:
extend a custom module autoloader
extend Zend\Mvc\Application for your own logic
make your code zf2-compatible