I'm trying to make my Zend Framework website in multiple languages, I want it to be easy to increment the number of languages in my website.
I tried following this tutorial, but I'm having some problems:
http://blog.hackix.com/series/working-with-zend_translate-and-poedit/
I got my language files nl_BE and en_US, changed the content in my views by e.g. echo $this->translate("Restricted section");
First, I was able to change the default language by changing it in the Bootstrap.php, but after a couple of tries to make it work without the Bootstrap.php and changing some code, even that doesn't work anymore.
I made a little form, with two images, a Belgian flag and a Brittish flag, if I click the Belgian flag I want to change the language to nl_BE, if I click the Brittish flag, I want to change the language to en_US.
This is my form:
<form action="" method="post">
<input type="image" src="images/belgium.png" value="nl_BE" name="lang" />
<input type="image" src="images/brittain.png" value="en_US" name="lang"/>
</form>
This is my Bootstrap.php file, according to the tutorial I used:
<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap {
protected function _initTranslate()
{
Zend_Loader::loadClass('Zend_Translate');
Zend_Loader::loadClass('Zend_Registry');
// Get current registry
$registry = Zend_Registry::getInstance();
/**
* Set application wide source Locale
* This is usually your source string language;
* i.e. $this->translate('Hi I am an English String');
*/
$locale = new Zend_Locale('en_US');
Zend_Registry::set('Zend_Locale', $locale);
$session = new Zend_Session_Namespace('session');
//$langLocale = isset($session->lang) ? $session->lang : $locale;
$langLocale = $locale;
/**
* Set up and load the translations (all of them!)
* resources.translate.options.disableNotices = true
* resources.translate.options.logUntranslated = true
*/
$translate = new Zend_Translate('gettext',
APPLICATION_PATH . DIRECTORY_SEPARATOR .'languages', 'auto',
array(
'disableNotices' => true, // This is a very good idea!
'logUntranslated' => false, // Change this if you debug
)
);
/**
* Both of these registry keys are magical and makes
* ZF 1.7+ do automagical things.
*/
$registry->set('Zend_Locale', $locale);
$registry->set('Zend_Translate', $translate);
return $registry;
}
}
This is my LangController.php, the Zend_Controller_Plugin_Abstract to handle the change of language, this file is placed in library/App/Controller/Plugin.
<?php
class App_Controller_Plugin_LangSelector extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$registry = Zend_Registry::getInstance();
// Get our translate object from registry.
$translate = $registry->get('Zend_Translate');
$currLocale = $translate->getLocale();
// Create Session block and save the locale
$session = new Zend_Session_Namespace('session');
$lang = $request->getParam('lang','');
// Register all your "approved" locales below.
switch($lang) {
case "nl_BE":
$langLocale = 'nl_BE';
break;
case "en_US":
$langLocale = 'en_US';
break;
default:
/**
* Get a previously set locale from session or set
* the current application wide locale (set in
* Bootstrap)if not.
*/
$langLocale = isset($session->lang) ? $session->lang : $currLocale;
}
$newLocale = new Zend_Locale();
$newLocale->setLocale($langLocale);
$registry->set('Zend_Locale', $newLocale);
$translate->setLocale($langLocale);
$session->lang = $langLocale;
// Save the modified translate back to registry
$registry->set('Zend_Translate', $translate);
}
}
Can someone please help me, tell me what I'm doing wrong? Because I've been trying for hours now and I think I'm really stuck.
Related
I want to put my module in Prestashop market place, and make it standard everyone can use it. This plugin needs to know the admin directory name dynamically to do its service.
I have searched on the Internet a lot of times, but I didn't find a solution to this issue.
You can use _PS_ADMIN_DIR_ witch is set in [your_admin_dir]/index.php:
if (!defined('_PS_ADMIN_DIR_')) {
define('_PS_ADMIN_DIR_', getcwd());
}
This constant is only set when you're on an admin context. Your FrontOffice doesn't have knowledge of this directory and should not for obvious security reason.
There's also a getAdminLink method in class Link:
/**
* Use controller name to create a link
*
* #param string $controller
* #param bool $with_token include or not the token in the url
* #return string url
*/
public function getAdminLink($controller, $with_token = true)
{
$id_lang = Context::getContext()->language->id;
$params = $with_token ? array('token' => Tools::getAdminTokenLite($controller)) : array();
return Dispatcher::getInstance()->createUrl($controller, $id_lang, $params, false);
}
Example:
// Here we create a link to the dashboard without token
$this->context->link->getAdminLink(Tab::getClassNameById(1), false)
I am trying to implement URL based translation in Zend Framework so that my site is SEO friendly. This means that I want URLs like the below in addition to the default routes.
zend.local/en/module
zend.local/en/controller
zend.local/en/module/controller
zend.local/en/controller/action
The above are the ones I have problems with right now; the rest should be OK. I have added a controller plugin that fetches a lang parameter so that I can set the locale and translation object in the preDispatch method. Here are some of my routes (stored in a .ini file):
; Language + module
; Language + controller
resources.router.routes.lang1.type = "Zend_Controller_Router_Route_Regex"
resources.router.routes.lang1.route = "(^[a-zA-Z]{2})/(\w+$)"
resources.router.routes.lang1.defaults.controller = index
resources.router.routes.lang1.defaults.action = index
resources.router.routes.lang1.map.1 = "lang"
resources.router.routes.lang1.map.2 = "module"
; Language + module + controller
; Language + controller + action
resources.router.routes.lang2.type = "Zend_Controller_Router_Route_Regex"
resources.router.routes.lang2.route = "(^[a-zA-Z]{2})/(\w+)/(\w+$)"
resources.router.routes.lang2.defaults.module = default
resources.router.routes.lang2.defaults.action = index
resources.router.routes.lang2.map.1 = "lang"
resources.router.routes.lang2.map.2 = "controller"
resources.router.routes.lang2.map.3 = "action"
As the comments indicate, several URL structures will match the same route, which makes my application interpret the format incorrectly. For instance, the following two URLs will be matched by the lang1 route:
zend.local/en/mymodule
zend.local/en/mycontroller
In the first URL, "mymodule" is used as module name, which is correct. However, in the second URL, "mycontroller" is used as module name, which is not what I want. Here I want it to use the "default" module and "mycontroller" as controller. The same applies for the previous lang2 route. So I don't know how to distinguish between if the URL is of the structure /en/module or /en/controller.
To fix this, I experimented with the code below in my controller plugin.
// Get module names as array
$dirs = Zend_Controller_Front::getInstance()->getControllerDirectory();
$modules = array_keys($dirs);
// Module variable contains a module that does not exist
if (!in_array($request->getModuleName(), $modules)) {
// Try to use it as controller name instead
$request->setControllerName($request->getModuleName());
$request->setModuleName('default');
}
This works fine in the scenarios I tested, but then I would have to do something similar to make the lang2 route work (which possibly involves scanning directories to get the list of controllers). This just seems like a poor solution, so if it is possible, I would love to accomplish all of this with routes only (or simple code that is not so "hacky"). I could also make routes for every time I want /en/controller, for instance, but that is a compromise that I would rather not go with. So, if anyone knows how to solve this, or know of another approach to accomplish the same thing, I am all ears!
I've reproduced your problem here and come out with the following (not using config files though):
Router
/**
* Initializes the router
* #return Zend_Controller_Router_Interface
*/
protected function _initRouter() {
$locale = Zend_Registry::get('Zend_Locale');
$routeLang = new Zend_Controller_Router_Route(
':lang',
array(
'lang' => $locale->getLanguage()
),
array('lang' => '[a-z]{2}_?([a-z]{2})?')
);
$frontController = Zend_Controller_Front::getInstance();
$router = $frontController->getRouter();
// Instantiate default module route
$routeDefault = new Zend_Controller_Router_Route_Module(
array(),
$frontController->getDispatcher(),
$frontController->getRequest()
);
// Chain it with language route
$routeLangDefault = $routeLang->chain($routeDefault);
// Add both language route chained with default route and
// plain language route
$router->addRoute('default', $routeLangDefault);
// Register plugin to handle language changes
$frontController->registerPlugin(new Plugin_Language());
return $router;
}
Plug-in
/**
* Language controller plugin
*/
class Plugin_Language extends Zend_Controller_Plugin_Abstract
{
/**
* #var array The available languages
*/
private $languages = array('en', 'pt');
/**
* Check the URI before starting the route process
* #param Zend_Controller_Request_Abstract $request
*/
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$translate = Zend_Registry::get('Zend_Translate');
$lang = $translate->getLocale();
// Extracts the URI (part of the URL after the project public folder)
$uri = str_replace($request->getBaseUrl() . '/', '', $request->getRequestUri());
$langParam = substr($uri, 0, 3);
// Fix: Checks if the language was specified (if not, set it on the URI)
if((isset($langParam[2]) && $langParam[2] !== '/') || !in_array(substr($langParam, 0, 2), $this->languages)) { {
$request->setRequestUri($request->getBaseUrl() . '/' . $lang . "/" . $uri);
$request->setParam('lang', $lang);
}
}
}
Basically, there's the route chain for applying the language settings within the module default route. As a fix, we ensure that the URI will contain the language if the user left it out.
You'll need to adapt it, as I'm using the Zend_Registry::get('Zend_Locale') and Zend_Registry::get('Zend_Translate'). Change it to the actual keys on your app.
As for the lang route: [a-z]{2}_?([a-z]{2})? it will allow languages like mine: pt_BR
Let me know if it worked for you.
I'm trying to clear a custom placeholder from a viewscript, let's say I have a controller plugin that creates a sidebar:
$bootstrap = Zend_Controller_Front::getInstance()->getParam('bootstrap');
$view = $bootstrap->getResource('view');
$partial = $view->render('partials/_sidebar.phtml');
$view->placeholder('sidebar')->append($partial);
My partial contains my submenu (rendered through Zend_Navigation view helper).
In order to render that sidebar, I have this in my layout:
<?= $this->placeholder('sidebar'); ?>
But what if in some pages I don't want to display my sidebar (login page for example) ? How can I handle these cases?
I thought I could reset/clear my placeholder using $this->placeholder('sidebar')->exchangeArray(array()); but it seems that I can't access my placeholder from a viewscript:
// in /application/modules/default/views/account/login.phtml
<?php $placeholder = $this->placeholder('sidebar');
Zend_Debug::dump($placeholder); ?>
// output:
object(Zend_View_Helper_Placeholder_Container)#217 (8) {
["_prefix":protected] => string(0) ""
["_postfix":protected] => string(0) ""
["_separator":protected] => string(0) ""
["_indent":protected] => string(0) ""
["_captureLock":protected] => bool(false)
["_captureType":protected] => NULL
["_captureKey":protected] => NULL
["storage":"ArrayObject":private] => array(0) {
}
}
Any idea how to do such a thing?
Thanks.
Edit:
My problem was very simple actually, since my plugin was registered and executed in the postDispatch() method, then my viewscript was executed before the plugin and the layout was executed after the plugin.
From now on, what are my options? I can't really declare my sidebar in the preDispatch method because there won't be any script directory set, and therefore I won't be able to determine which view script to execute at this step.
I could also use an action() helper, what do you think? A question has been already asked about it. I still feel like this is not the proper way to do it, and it sounds overkilling to me.
Also, another idea would be to move my plugin into a the preDispatch() method of my controller, but that would lead me to copy/paste on every controller my sidebar, or create a baseController, but I still don't like this idea, I feel like I'm doing it wrong.
Any idea?
You were pretty close actually. You just needed to add the name of your placeholder, sidebar in this case:
$this->placeholder('sidebar')->exchangeArray(array()); should work.
See http://framework.zend.com/manual/en/zend.view.helpers.html#zend.view.helpers.initial.placeholder
EDIT:
That's strange that the above did not work for you. I tested this out on my own ZF website and it worked. Possibly different scenario. I don't know.
Here is another alternative, albeit more involved.
//Get the placeholder object directly
$placeholderObj = $this->getHelper('placeholder')
//This may be why you were getting null in your example above. Since your were using the magic method in the View class to call the placeholder view helper method directly.
//For example you could call the placeholder like so:
$placeholderObj->placeholder('sidebar')->set("Some value");
//Now the alternative way to remove the placeholder value is as follows.
$registry = $placeholderObj->getRegistry()
$registry->deleteContainer('sidebar');
EDIT3
It should work as a Zend_Controller_Plugin using preDispatch hook. I think the only you need to make sure of is that you order that plugin after you setting your view script/helper paths and layout paths. This can be done in the same plugin or in another plugin.
Here is a code example based on something I use on my website. The relevant part is just the stuff in preDispatch where I set the view script paths, etc
class RT_Theme extends Zend_Controller_Plugin_Abstract
{
private $_sitename;
private $_assets;
private $_appPath;
private $_appLibrary;
/**
* Constructor
*
* #param string $sitename
* #param SM_Assets $assets
* #param string $appPath
* #param string $appLibrary
*/
public function __construct($sitename, $assets, $appPath, $appLibrary)
{
$this->_sitename = $sitename;
$this->_assets = $assets;
$this->_appPath = $appPath;
$this->_appLibrary = $appLibrary;
}
/**
* Sets up theme
*
* Sets up theme template directory and assets locations (js, css, images)
*
* #param Zend_Controller_Request_Abstract $request
*/
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$module = $request->getModuleName();
$controller = $request->getControllerName();
$action = $request->getActionName();
$layout = Zend_Layout::getMvcInstance();
$view = $layout->getView();
$view->sitename = $this->_sitename;
$view->headTitle()->setSeparator(' - ')->headTitle($view->sitename);
$layout->setLayoutPath($this->_appPath . "/html/$module/layout/");
$view->setScriptPath($this->_appPath . "/html/$module/view/");
$view->addScriptPath($this->_appPath . "/html/$module/view/_partials/");
$view->addScriptPath($this->_appLibrary . '/RT/View/Partials/');
$view->addHelperPath('RT/View/Helper', 'RT_View_Helper');
$view->addHelperPath($this->_appPath . '/html/view/helpers','My_View_Helper');
$viewRenderer =
Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
$viewRenderer->setView($view);
//Ignore this line. Not relevant to the question
$this->_assets->loadFor($controller, $action);
//Example: Now I can set the placeholder.
$view->placeholder('header_title')->set($view->sitename);
}
}
I have build a CMS using Zend Framework (1.11). In the application I have two modules, one called 'cms' which contains all the CMS logic and an other 'web' which enables a user to build their own website around the CMS. This involves adding controllers/views/models etc all in that module.
The application allows you to serve multiple instances of the app with their own themes. These instances are identified by the hostname. During preDispatch(), a database lookup is done on the hostname. Based on the database field 'theme' the app then loads the required css files and calls Zend_Layout::setLayout() to change the layout file for that specific instance.
I want to extend this functionality to also allow the user to run different web modules based on the 'theme' db field. However, this is where I am stuck. As it is now, the web module serves the content for all the instances of the application.
I need the application to switch to a different web module based on the 'theme' database value (so indirectly the hostname). Any ideas?
Well, in my opinion,
You should write a front controller plugin for the web module, and do it so, that when you need another plugin, you can do so easily.
The front controller plugin should look something like this:
class My_Controller_Plugin_Web extends My_Controller_Plugin_Abstract implements My_Controller_Plugin_Interface
{
public function init()
{
// If user is not logged in - send him to login page
if(!Zend_Auth::getInstance()->hasIdentity()) {
// Do something
return false;
} else {
// You then take the domain name
$domainName = $this->_request->getParam( 'domainName', null );
// Retrieve the module name from the database
$moduleName = Module_fetcher::getModuleName( $domainName );
// And set the module name of the request
$this->_request->setModuleName( $moduleName );
if(!$this->_request->isDispatched()) {
// Good place to alter the route of the request further
// the way you want, if you want
$this->_request->setControllerName( $someController );
$this->_request->setActionName( $someAction );
$this->setLayout( $someLayout );
}
}
}
/**
* Set up layout
*/
public function setLayout( $layout )
{
$layout = Zend_Layout::getMvcInstance();
$layout->setLayout( $layout );
}
}
And the My_Controller_Plugin_Abstract, which is not an actual abstract and which your controller plugin extends,looks like this:
class My_Controller_Plugin_Abstract
{
protected $_request;
public function __construct($request)
{
$this->setRequest($request);
$this->init();
}
private function setRequest($request)
{
$this->_request = $request;
}
}
And in the end the front controller plugin itself, which decides which one of the specific front controller plugins you should execute.
require_once 'Zend/Controller/Plugin/Abstract.php';
require_once 'Zend/Locale.php';
require_once 'Zend/Translate.php';
class My_Controller_Plugin extends Zend_Controller_Plugin_Abstract
{
/**
* before dispatch set up module/controller/action
*
* #param Zend_Controller_Request_Abstract $request
*/
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
// Make sure you get something
if(is_null($this->_request->getModuleName())) $this->_request->setModuleName('web');
// You should use zend - to camelCase convertor when doing things like this
$zendFilter = new Zend_Filter_Word_SeparatorToCamelCase('-');
$pluginClass = 'My_Controller_Plugin_'
. $zendFilter->filter($this->_request->getModuleName());
// Check if it exists
if(!class_exists($pluginClass)) {
throw new Exception('The front controller plugin class "'
. $pluginClass. ' does not exist');
}
Check if it is written correctly
if(!in_array('My_Controller_Plugin_Interface', class_implements($pluginClass))) {
throw new Exception('The front controller plugin class "'
. $pluginClass.'" must implement My_Controller_Plugin_Interface');
}
// If all is well instantiate it , and you will call the construct of the
// quasi - abstract , which will then call the init method of your
// My_Plugin_Controller_Web :)
$specificController = new $pluginClass($this->_request);
}
}
If you have never done this, now is the time. :)
Also, to register your front controller plugin with the application, you should edit the frontController entry in your app configuration. I will give you a json example, i'm sure you can translate it to ini / xml / yaml if you need...
"frontController": {
"moduleDirectory": "APPLICATION_PATH/modules",
"defaultModule": "web",
"modules[]": "",
"layout": "layout",
"layoutPath": "APPLICATION_PATH/layouts/scripts",
// This is the part where you register it!
"plugins": {
"plugin": "My_Controller_Plugin"
}
This might be a tad confusing, feel free to ask for a more detailed explanation if you need it.
I added one library for gettext translation. Added corresponding po and mo files.
And the translation is working fine.
Now when I update my po file, change some translation.. after that when I reload the page, I am getting the old translation, not the new.
Here is the Library code:
/**
* This method overides the original load method. Its duty is loading the domain files by config or by default internal settings.
*
*/
function load_gettext($userlang = false) {
/* I want the super object */
if ($userlang)
$this->gettext_language = $userlang;
else
$this->gettext_language = 'it_IT';
log_message('debug', 'Gettext Class gettext_language was set by parameter:' . $this->gettext_language);
putenv("LANG=$this->gettext_language");
setlocale(LC_ALL, $this->gettext_language);
/* Let's set the path of .po files */
$this->gettext_path = APPPATH . 'language/locale';
log_message('debug', 'Gettext Class path chosen is: ' . $this->gettext_path);
bindtextdomain($this->gettext_domain, $this->gettext_path);
textdomain($this->gettext_domain);
log_message('debug', 'Gettext Class the domain chosen is: ' . $this->gettext_domain);
return true;
}
/**
* Plural forms added by Tchinkatchuk
* http://www.codeigniter.com/forums/viewthread/2168/
*/
/**
* The translator method
*
* #param string $original the original string to translate
* #param array $aParams the plural parameters
* #return the string translated
*/
function _trans($original, $aParams = false) {
if (isset($aParams['plural']) && isset($aParams['count'])) {
$sTranslate = ngettext($original, $aParams['plural'], $aParams['count']);
$sTranslate = $this->replaceDynamically($sTranslate, $aParams);
} else {
$sTranslate = gettext($original);
if (is_array($aParams) && count($aParams))
$sTranslate = $this->replaceDynamically($sTranslate, $aParams);
}
return $sTranslate;
}
This is the usage in a controller:
$this->pos_language->load_gettext('fr_FR');
echo $this->pos_language->_trans('Hello world, good morning');
I think you need to compile your .po files to .mo files. Gettext uses the .mo file, the .po is just a human readable form.
If you haven't done the compilation step, your application is still reading your old .mo files, with the untranslated strings...
This page has some more info about gettext translation: http://wiki.creativecommons.org/Translating_PO_Files
I wrote a helper to integrate php-gettext with Smarty & Code Igniter 2
http://bit.ly/rrITVx
I hope it might help