Multiple languages in a CakePHP 2.* application in 5 steps

Written on 1 August 2012, 10:21pm

Tagged with: , ,

Below is a solution that I implemented in a CakePHP project needing internationalization (i18n).
It involves showing the language in the URL (so the URLs will look like app.com/:lang/:controller/:action/...) and storing it in the Session + Cookies.
It is basically the implementation described on http://nuts-and-bolts-of-cakephp.com. I only added some minor changes to make it work under CakePHP 2.* (the solution in the link above was implemented in November 2008, for CakePHP 1.3).
The implementation below was tested on CakePHP 2.2 (1 August 2012).
Feel free to add your comments/questions in the form below.

Comments inline:


// Step 1: app/Config/routes.php
Router::connect('/:language/:controller/:action/*',
                       array(),
                       array('language' => '[a-z]{3}'));

//Step 2: app/Config/core.php
Configure::write('Config.language', 'eng');

//Step 3: create app/View/Helper/MyHtmlHelper.php
App::uses('HtmlHelper', 'View/Helper');
class MyHtmlHelper extends HtmlHelper {
	public function url($url = null, $full = false) {
        if(!isset($url['language']) && isset($this->params['language'])) {
          $url['language'] = $this->params['language'];
        }
        return parent::url($url, $full);
   }
}

//Step 4: app/Controller/AppController.php
class AppController extends Controller {
	public $components = array('Cookie','Session');
	//set an alias for the newly created helper: Html<->MyHtml
	public $helpers = array('Html' => array('className' => 'MyHtml'));

	public function beforeFilter() {
          $this->_setLanguage();
        }

	private function _setLanguage() {
	//if the cookie was previously set, and Config.language has not been set
	//write the Config.language with the value from the Cookie
	    if ($this->Cookie->read('lang') && !$this->Session->check('Config.language')) {
	        $this->Session->write('Config.language', $this->Cookie->read('lang'));
	    } 
	    //if the user clicked the language URL 
	    else if ( 	isset($this->params['language']) && 
		($this->params['language'] !=  $this->Session->read('Config.language'))
	    		) {
	    	//then update the value in Session and the one in Cookie
	        $this->Session->write('Config.language', $this->params['language']);
	        $this->Cookie->write('lang', $this->params['language'], false, '20 days');
	    }
	}

	//override redirect
	public function redirect( $url, $status = NULL, $exit = true ) {
		if (!isset($url['language']) && $this->Session->check('Config.language')) {
			$url['language'] = $this->Session->read('Config.language');
		}
		parent::redirect($url,$status,$exit);
	}
}

//add the links to the languages:
//Step 5: app/View/...
echo $this->Html->link('English', array('language'=>'eng')); 
echo $this->Html->link('Français', array('language'=>'fre')); 

Update August 3rd 2012: Added ‘private’ visibility for the _setLanguage function
Update September 28th 2012: Modified the first step (app/Config/routes.php), adding more restrictive rules for the languages. Before, if the user somehow loaded an URL like /css/foo/bar – then ‘css’ was incorrectly considered a language.


	Router::connect('/:language/:controller/:action/*',
                       array(),
                       array('language' => 'eng|fre'));

	Router::connect('/:language/:controller',
                       array('action' => 'index'),
                       array('language' => 'eng|fre'));	

	Router::connect('/:language',
                       array('controller' => 'welcome', 'action' => 'index'),
                       array('language' => 'eng|fre'));

Comments (83)

  1. teknoid — August 3, 2012 at 12:55

    Hey, great job on refactoring this for 2.x… I’ve added link to your solution in the main post.

    Just being picky, but you forgot private/protected visibility before setLanguage() method.. that’ll be more php5-like.

    cheers.

    Reply

    • Dorin M — August 3, 2012 at 13:36

      Thank you! 🙂
      You are perfectly right about the visibility modifier. I fixed it in the post.

      Reply

  2. mark — August 6, 2012 at 20:14

    to be picky, there shouldn’t be any private visibilities in the first place in a framework context. use protected instead.

    also I dont think you need to create an MyHtml helper, overriding url() in AppHelper should do the trick.
    Especially because Cake2 and aliasing is buggy concerning aliasing. I opened a ticket and submitted a solution. But it will not be accepted. Therefore you should avoid aliasing in this context as it can be avoided easily.

    Reply

    • Nasko — September 28, 2012 at 11:49

      An additional benefit of overriding the url() method in AppHelper would be that then it would also work for FormHelper::postLink(), which is not the case with MyHtmlHelper(). Overriding the method in MyHtmlHelper() leaves out links generated by $this->Form->postLink().
      @Tomas: If you implement the overriding in AppHelper, you won’t need the change you mention in your comment, because FormHelper::postLink() would work out of the box as it also derives the run() method from AppHelper.

      Reply

      • Dorin M — September 28, 2012 at 13:31

        Thank you both for the observations.

  3. Marco Hernandez — August 21, 2012 at 05:42

    Thanks for the info, it was very helpfull, however the _setLanguage function relies on the session, and if not, the cookies, and if not, the default language… What about the browser language (HTTP_ACCEPT_LANGUAGE)? It may not seem like much but adding this would allow the site to determine the language the first time the user enters as opposed to having to first click on the propper language. Im trying to integrate this into the code but im not sure how it is done. Any feedback is appreciated! Thanks

    Reply

  4. Marco Hernandez — August 21, 2012 at 05:49

    This is what I believe the _setlanguage code should do (somewhat):
    1. Check the URL if the language is specified and use it
    2. If URL does not specify language, check the session and use it
    3. If the Session does not specify language, check the cookie and use it
    4. If the Cookie does not specify language, use the default language
    Ill post it once I get it done propperly.

    Reply

  5. Marco Hernandez — August 21, 2012 at 05:50

    woops got it wrong, here is the update:

    4. If the Cookie does not specify language, check the HTTP header language and use it.
    5. If the HTTP header language is not specified, use the default language.

    Reply

  6. Daniela — August 29, 2012 at 19:18

    Hello! I hope I can help.
    I am implementing the code and only works if I change the language from the core.php file, when I give the links I click nothing happens! would know what could be the reason? I’ve done everything as you say and I can not find the problem. Thanks for your help
    ___________________________________________________________
    Hola! espero me puedan ayudar.
    Estoy implementando el código y solo funciona si cambio el idioma desde el archivo core.php, cuando le doy doy click a los links no pasa nada! sabrían cuál puede ser la razón? he hecho todo como decís y no encuentro el problema. Gracias por su ayuda!

    Reply

    • Dorin M — August 29, 2012 at 19:57

      @Daniela
      Can you please paste the code somewhere to have a look?
      Thanks.

      Reply

      • Daniela — September 6, 2012 at 19:15

        I’ve put everything as it is in the code example, in the default view (view/Layouts/default.ctp) I have put the links to switch languages ​​as well:

        
        echo $this->Html->link('English', array('language' => 'eng'));
        echo '';
        echo $this->Html->link('Español', array('language' => 'spa'));
        

        I also tried to put in another view but does not work (view/personas/index.ctp)
        It is as if the parameter was empty, I know I can not be doing wrong
        help me please
        _________________________________________
        Lo he puesto todo tal cual está en el cĂłdigo de ejemplo, en la vista default he puesto los links para cambiar de idioma, tambiĂ©n he tratado poniĂ©ndolos en otra vista pero igual no funcionan es como si el parámetro estuviera vacĂ­o, no sĂ© que estarĂ© haciendo mal…

        • Dorin M — September 28, 2012 at 13:38

          Daniela,
          First of all, view/Layouts/default.ctp is not a view; it is the default layout.
          What is the exact error you are getting?

  7. Marco Hernandez — August 29, 2012 at 21:09

    Ok, I think I got it. Here is the complete code. Keep in mind that all the code goes within the CakePHP AppController class:

    
         //Get the Browser Language
         private function _getRequestLanguage() {
    		if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"])) {
    			return $this->_parseRequestLanguage($_SERVER["HTTP_ACCEPT_LANGUAGE"]);
    		} else {
    			return $this->_parseRequestLanguage(NULL);
    		}
    	}
            //Retrieves the browserlanguage if available or use the default one
    	private function _parseRequestLanguage($http_accept, $deflang = "spa") {
    		if(isset($http_accept) && strlen($http_accept) > 1)  {
    			// Split possible languages into array
    			$x = explode(",",$http_accept);
    			foreach ($x as $val) {
    				//check for q-value and create associative array. No q-value means 1 by rule
    				if(preg_match("/(.*);q=([0-1]{0,1}\.\d{0,4})/i",$val,$matches)) {
    					$lang[$matches[1]] = (float)$matches[2];
    				} else {
    					$lang[$val] = 1.0;
    				}
    			}
    			
    			//return default language (highest q-value)
    			$qval = 0.0;
    			foreach ($lang as $key => $value) {
    				foreach ($this->allowedLanguages as $akey => $avalue) {
    					if (preg_match($akey, $key, $matches)) {
    						if ($value > $qval) {
    							$qval = (float)$value;
    							$deflang = $this->allowedLanguages[$akey];
    						}
    					}
    				}
    			}
    		}
    		return strtolower($deflang);
    	}
    	
            //Sets the current language
            //Checks in order of Url, Session, Cookie, HTTP Request, Default
    	private function _setLanguage() {
    		
    		$lang = '';
    		if (isset($this->params['language']) && !empty($this->params['language'])) {
    			$lang = $this->params['language'];
    		}
    		
    		if (empty($lang)) {
    			$lang = $this->Session->read('Config.language');
    		}
    		
    		if (empty($lang)) {
    			$lang = $this->Cookie->read('Config.language');
    		}
    		
    		if (empty($lang)) {
    			$lang = $this->_getRequestLanguage();
    			$this->set('AcceptLanguage', $lang);
    		}
    		
    		if (empty($lang)) {
    			$lang = Configure::read('Config.language');
    		}
    	 
    		$this->Session->write('Config.language', $lang);
    		$this->Cookie->write('lang', $lang, false, '20 days');
    	}
    	
            //CakePHP beforeFilter
    	public function beforeFilter() {
    		$this->_setLanguage();
    		$this->UserAgent->saveUserAgent();
    	}
    

    Reply

    • Marco Hernandez — August 29, 2012 at 21:10

      ignore the saveUserAgent thing, its not relevant.

      Reply

    • Dorin M — August 30, 2012 at 19:43

      Thanks for sharing! In which CakePHP version did you test it?
      PS: I formatted your code.

      Reply

    • sa7booch — December 11, 2013 at 09:42

      Hi Marco,

      what is the value of $this->allowedLanguages ???

      Thanks

      Reply

  8. Marco Hernandez — August 30, 2012 at 20:00

    hello. it’s for the latest CakePHP 2.1, and its currently working live in http://marco.webdoit.freeiz.com. How did you format it? I wanna know!!!

    Reply

  9. Tomas — September 3, 2012 at 04:10

    Thanks for this post!!
    Slight change suggestion for the helper.
    This fixes errors for href=”#” (used for postlinks).

    params['language'])) {
    			if($url == '#') {
    				$url = array();
    			}
    			$url['language'] = $this->params['language'];
            }
            return parent::url($url, $full);
       }
    }
    

    Reply

  10. Alex — October 10, 2012 at 16:30

    Hey, thanks for your tutorial, it’s great.
    But I am having a problem with my pages views.
    I have created different views for the languages e.g. de_about.ctp, en_about.ctp
    But how can I render them in my PagesController?

    Thanks

    Reply

    • Dorin M — October 10, 2012 at 16:42

      Alex, have a look at the Cake manual: http://book.cakephp.org/2.0/en/core-libraries/internationalization-and-localization.html


      (…) if you find you want to translate long paragraphs, or even whole pages – you should consider implementing a different solution. e.g.:

      
      // App Controller Code.
      public function beforeFilter() {
          $locale = Configure::read('Config.language');
          if ($locale && file_exists(VIEWS . $locale . DS . $this->viewPath)) {
              // e.g. use /app/View/fre/Pages/tos.ctp instead of /app/View/Pages/tos.ctp
              $this->viewPath = $locale . DS . $this->viewPath;
          }
      }
      

      or:

      
      // View code
      echo $this->element(Configure::read('Config.language') . '/tos');
      

      Reply

      • Alex — October 10, 2012 at 17:09

        Thanks, but it doesn’t work for me.
        And with Configure::read('Config.language'); I just get the default language, but if I have en as default and de in the url I want to view the de view…

        • Dorin M — October 11, 2012 at 15:24

          Are you sure you implemented the steps 1-4 as described in this post?

  11. Alex — October 12, 2012 at 09:08

    Yes I followed this steps.

    Here’s my AppController:

    
    class AppController extends Controller {
    	public $components = array('Cookie','Session');
    	//set an alias for the newly created helper: Html<->MyHtml
    	public $helpers = array('Html' => array('className' => 'MyHtml'));
    
    	public function beforeFilter() {
              $this->_setLanguage();
              
                $locale = Configure::read('Config.language');
            
                if ($locale && file_exists('VIEWS' . $locale . DS . $this->viewPath)) {
                    // e.g. use /app/View/fre/Pages/tos.ctp instead of /app/View/Pages/tos.ctp
                    $this->viewPath = $locale . DS . $this->viewPath;
                }
              
              $this->layout = 'main';
            }
    
    	private function _setLanguage() {
    	//if the cookie was previously set, and Config.language has not been set
    	//write the Config.language with the value from the Cookie
    	    if ($this->Cookie->read('lang') && !$this->Session->check('Config.language')) {
    	        $this->Session->write('Config.language', $this->Cookie->read('lang'));
    	    } 
    	    //if the user clicked the language URL 
    	    else if ( 	isset($this->params['language']) && 
    		($this->params['language'] !=  $this->Session->read('Config.language'))
    	    		) {
    	    	//then update the value in Session and the one in Cookie
    	        $this->Session->write('Config.language', $this->params['language']);
    	        $this->Cookie->write('lang', $this->params['language'], false, '20 days');
    	    }
    	}
    
    	//override redirect
    	public function redirect( $url, $status = NULL, $exit = true ) {
    		if (!isset($url['language']) && $this->Session->check('Config.language')) {
    			$url['language'] = $this->Session->read('Config.language');
    		}
    		parent::redirect($url,$status,$exit);
    	}
    }
    
    //routes.php
    Router::connect('/:language/about',array('controller' => 'pages', 'action' => 'display', 'about'),array('language' => '[a-z]{2}'));
    Router::connect('/:language/:controller/:action/*',array(),array('language' => '[a-z]{2}'));
    
    //core.php
    Configure::write('Config.language', 'de');
    
    //MyHtmlHelper.php
    App::uses('HtmlHelper', 'View/Helper');
    class MyHtmlHelper extends HtmlHelper {
    	public function url($url = null, $full = false) {
            if(!isset($url['language']) && isset($this->params['language'])) {
              $url['language'] = $this->params['language'];
            }
            return parent::url($url, $full);
       }
    }
    

    Hope u can help me.

    Thanks

    Reply

  12. Alex — October 13, 2012 at 19:03

    Ok, now I got it but I have 2 new problems…
    1, I am always getting this error
    Illegal string offset 'language' [APP\View\Helper\MyHtmlHelper.php, line 7]
    2, My Config.language is always “de” (my default).
    My de View is loaded (pages/de/someView.ctp), but if I change the language I get also my de view, because the config.lang will not changed.

    Reply

    • lsri8088 — February 4, 2014 at 16:47

      Are you using PHP 5.4? This version does not allow use String indexes in arrays.
      I have the same problem :'(

      Reply

      • Zechs — April 4, 2014 at 16:46

        I debuged $url and noticed that sometimes it’s an array and some other times it is a string. I’m not sure exactly why, I didn’t bother to dig farther as I already had the info I needed.
        PHP is warning you because you are trying to add a key to a string, which is a non sense… so don’t blame PHP, PHP is smart !
        So the trick is simply to check if $url is actually an array before adding a key to it:

        //Step 3: create app/View/Helper/MyHtmlHelper.php
        App::uses('HtmlHelper', 'View/Helper');
        class MyHtmlHelper extends HtmlHelper {
        	public function url($url = null, $full = false) {
        		if(is_array($url)){
        			if(!isset($url['language']) && isset($this->params['language'])) {
        			  $url['language'] = $this->params['language'];
        			}
        		}
                return parent::url($url, $full);
           }
        }
        

  13. Gerd — October 14, 2012 at 12:03

    Hi!
    Thanks for the tutorial, anyhow I have an issue:

    I made a clean cake install (2.2.2) and updated the files as said in the tutorial.

    Debugging the Session and Cookie, the language is set and gets changed when I change the language.

    But I don’t understand how to call different .ctp files then.

    I copied the content of my VIEW folder to VIEW/ENG and VIEW/FRE (making small changes so I see when the eng-view or fre-view is called).

    But no matter what language I choose, I always get the original view displayed (from VIEW folder), where it should be e.g. VIEW/FRE.

    Do I need to make some other changes or where do I have to store the view-files for french language?

    Reply

  14. Gerd — October 14, 2012 at 12:24

    Just noticed, that the “Config.language” is not being updated.
    The “Config.Session” and the Cookie are being updated.

    Any idea why?

    Reply

    • Alex — October 14, 2012 at 13:41

      Hey Gerd,

      the function above _setLanguage() doesn’t update the config language, so u have to add this line:

      Configure::write('Config.language', $this->Session->read('Config.language'));

      in your function and then the config.lang will be changed.
      I had also problems with my different lang views, but now it works.
      Just add this function:

      $locale = Configure::read('Config.language');
                
                if ($locale && file_exists(APP.'VIEW'.DS.$this->viewPath.DS.$locale)) {
                  $this->viewPath = $this->viewPath. DS . $locale;
                }

      to your beforeFilter() Method and it should work…

      Regards

      Reply

      • Gerd — October 14, 2012 at 14:21

        Thank you VERY MUCH!!

        Just a quick question:
        How is your folder structure in APP/VIEW?

        On my Items, I’m still getting the app/view/items/index.ctp displayed, even when changing the language.

        • Alex — October 14, 2012 at 14:24

          My folder structure looks like this: Views/Pages/de: Views/Pages/en
          You can also change it in the beforeFilter() Method.

          Regards,
          Alex

          • Gerd — October 14, 2012 at 15:01

            Thank you Alex, you really made my day!!

            But it’s only working when I remove the “file exists” check.

            When I run “debug(file_exists(APP.’VIEW’.DS.$this->viewPath.DS.’eng’));” in my view, it returns false.

        • manish patel — April 20, 2013 at 10:16

          sdf

  15. Alex — October 14, 2012 at 15:06

    Can u post your beforeFilter() Method?
    Does the folder “eng” really exist in your path?

    Reply

    • Gerd — October 14, 2012 at 15:12

      This one isn’t working for me:

      public function beforeFilter() {
                $this->_setLanguage();
      	  $locale = Configure::read('Config.language');
      
      if ($locale && file_exists(APP.'VIEW'.DS.$this->viewPath.DS.$locale)) {
        $this->viewPath = $this->viewPath. DS . $locale;
      }#if
      }#beforefilter
      

      This one is working fine:

      
      public function beforeFilter() {
               $this->_setLanguage();
      	 $locale = Configure::read('Config.language');
      
      if ($locale) {
        $this->viewPath = $this->viewPath. DS . $locale;
      }#if
      }#beforefilter
      

      Reply

      • Alex — October 14, 2012 at 15:23

        I tried your code and it works fine for me and can’t find any error here.
        What do u get, if u try?

        debug(APP.’VIEW’.DS.$this->viewPath.DS.$locale));

        I think the problem must be in this path.

        • Gerd — October 14, 2012 at 16:13

          I found the soultion, thanks to you Alex!

          ($locale  && file_exists(APP.'View'.DS.$this->viewPath.DS.$locale))

          View in capital letters was not accepted by my server. Now it’s working.

          Thanks again for your help!

  16. Gerd — October 16, 2012 at 12:26

    Is there a way to remove

    Configure::write('Config.language', 'eng');

    I’d like the user to fall back to the welcome page to choose the language, if none is submitted in the url or stored in Cookie/Session.

    Any suggestion?

    Reply

    • Dorin M — October 18, 2012 at 09:12

      @Gerd
      I believe the place to start is the _setLanguage() method: if there is no language in the URL/Cokie/Session – then prompt the user to select one. The line in the configuration file must stay because Cake must have a default language (see response to @Cameron below).
      Remember:
      -Config.language sets the default language for your application
      -Session/Cookie stores the language that the user chose.
      I hope this helps.

      Reply

  17. Cameron — October 17, 2012 at 22:54

    Maybe I’m missing something… but why are you setting: `Configure::write(‘Config.language’, ‘eng’);` to create the language constant when as far as I can see in the code you never use it? I see you read sessions and cookies but never read the setting?

    Reply

    • Dorin M — October 18, 2012 at 08:57

      @Cameron:
      This configuration setting is automatically used by Cake:
      http://book.cakephp.org/2.0/en/core-libraries/internationalization-and-localization.html#localization-in-cakephp

      To change or set the language for your application, all you need to do is the following:

      Configure::write('Config.language', 'fre');

      This tells Cake which locale to use […]
      To set the language for the current user, you can store the setting in the Session object, like this:

      $this->Session->write('Config.language', 'fre');

      Reply

      • Cameron — October 18, 2012 at 11:53

        Okay, that been said then. How does your code use that setting then? As according to the Cake docs config settings don’t create sessions so I don’t see how it gets used?

        • Dorin M — October 18, 2012 at 12:16

          The code described in the post handles the case when an user wants to use the application in another language than the default language. The default language is the one in the config file.
          So it is normal that the code does not use the configuration ‘Config.language’ setting.
          To better understand this, I suggest you to create the following test case:

          -create a simple ‘hello world’ cake app
          -create 2 subfolders in locale/ – eng and fre. Populate them with the appropriate .po files
          -the app will simply output __(‘Hello world’).
          -no language selection is available
          Now, if you don’t add anything in the core.php, the app will display the English translation of ‘Hello world’.
          BUT, if you add in the config the line

          Configure::write('Config.language', 'fre');

          then the app will display the French translation of ‘Hello world’.

          That’s where the default config language comes into play.
          If you want to present your application in French instead of English, add that line in core.php.
          But if you want to allow the user to dynamically select the language, then you need the code in post.

          I hope it is clear 🙂

          • Cameron — October 18, 2012 at 14:30

            Ah so that config is used automatically by CakePHP to change the locale texts stored in the po files. I was getting confused thinking it would be handling the url stuff above. Thanks for the info.

  18. kostas — November 7, 2012 at 18:35

    Hi…
    in any tutorial about cakephp lang switching always a major point is missed…

    Dublicated content in search engines…

    eg.:
    default lang =eng

    site.com/
    site.com/eng
    site.com/fre

    in the 2 first urls we pass dublicated content …
    how can we avoid this and merge the defult lang in the first url?
    site.com/ display eng but ommit the lang parameter?

    so engines to see site.com/ => english and site.com/fre => frensh ???

    —–

    and also (i don’t know if this must happen or i have missed something?)
    url rewrite does not working in sub-level
    eg.: site.com/posts/view/5 when we are at the above url the links to switch pointing to => site.com/eng/posts/view and site.com/fre/posts/view
    they don’t point to the correct => site.com/eng/posts/view/5 ..etc….
    they missed the id…

    is that they way they work?

    Reply

    • Gerd — November 7, 2012 at 18:48

      No, there won’t be duplicated content indexed by search enginges, because accessing your site.com/example will redirect to site.com/eng/example

      On your sub-level (eg.: site.com/posts/view/5 ) would be eg.: site.com/eng/posts/view/5

      If you’d like to have a link to another language, you have to put: array('language' => 'fre') in your cakephp link.

      Reply

      • kostas — November 7, 2012 at 19:27

        No it is dublicated content thats true… and effects all site…

        i will use a live example(link from above commnets) to show you….

        visit => http://marco.webdoit.freeiz.com/eng/#yo
        and => http://marco.webdoit.freeiz.com/#yo

        same page in 2 diff links =>dublicated content

        —-

        i have the links … if you read my previous comment you will see that they work till i reach at the post and open(view) it…. then the link-switch miss the id. and redirects me wrong only there at this level… above working as expected.

        P.s. using 2.2.3 cakephp

        • Gerd — December 5, 2012 at 16:59

          seems like your session and/or cookie is not updated.
          Debug your Config.language..

    • lagunawebdesign — July 25, 2013 at 17:37

      To avoid duplicate content

      in AppController.php You can add default language:

      
      _setLanguage(){
      ... 
      if (!isset($this->params['language'])) {
      	        $this->Session->write('Config.language', 'eng');
      	    } 
      ...
      }
      

      and set routes:

      
      Router::connect('/:language/:controller/:action/*',
                            array(),
                            array('language' => '|pol|fre'));
      

      extra ‘|’ to allow empty values for language

      Reply

  19. Sisay — February 2, 2013 at 07:22

    Thanks a million. It worked for me perfectly. In the entries in .pot files, I used single quote (as a result of my reluctance) and suffured a lot, before I figured it out.

    Reply

    • Sisay — February 2, 2013 at 07:24

      Forgot to mention. I used it on Cakephp 2.2.5

      Reply

  20. gerd — February 3, 2013 at 12:54
  21. jo — February 14, 2013 at 05:42

    I followed your step but when i click on link of language it display only header of page non whole page

    Reply

    • Dorin M — February 14, 2013 at 09:34

      Some more details would help.

      Reply

  22. Sergey — May 13, 2013 at 14:02

    I’m a little bit in trouble. I’m a newbie in CakePHP.
    I put in layout links:

    echo $this->Html->image('LV.gif', array('alt' => 'LV','title'=>'LV','url'=>array('language'=>'lv')));
    echo $this->Html->image('RU.gif', array('alt' => 'RU','title'=>'RU','url'=>array('language'=>'ru')));
    

    and have a route

    Router::connect('/:language/test', array('controller' => 'pages', 'action' => 'display', 'test'), array('language' => 'ru|lv|RU|LV'));
    

    But links rendered from page /ru/test are:
    /lv/pages/display
    /ru/pages/display

    If I change ‘url’=>array(‘language’=>’lv’) to ‘url’=>array(‘language’=>’lv’,’test’) all work as expected in this page, but because this are general switching in layout.ctp, how can I pass actual parameters from request?

    Reply

    • Sjoerd — August 2, 2013 at 23:53

      I’m not sure I fully understood your question. Do you want to pass unnamed request parameters to your change-language buttons? I had that problem today and cheated like this:

      echo $this->Html->link('Français', array_merge(array('language'=>'fre'),array_values($this->params['pass'])));

      I hope this helps you. Is there a better solution? Or are there problems associated with mine?

      Thanks in advance,
      Sjoerd

      Reply

  23. kalaipriya — May 27, 2013 at 07:37

    Hi,
    I am using the same method for multilanguage specfied here, but my view takes the selected language only second time. can any one help me.
    my Apphelper code is

    App::uses('Helper', 'View');
    
    class AppHelper extends Helper {
    
          public function url($url = null, $full = false) {
           
            if(!isset($url['language']) && isset($this->params['language'])) {
              $url['language'] = $this->params['language'];          
            }
    
            return parent::url($url, $full);
       }
    }
    

    AppController

    
    function beforeRender() {
    		
    	  if(isset($this->data['language']))
    	  {
              $this->param['language']=$this->data['language'];}
                    //Setting the Language of the page
                    if ( isset($this->data['language']) && $this->data['language'] != '' ) {
                            $language = $this->data['language'];
                            if ( $this->Session->read('Config.language') != $language ) {
                                    
                                    $this->Session->write('Config.language', $language);
                                   
                            }
                    }
                    $this->set('lang', $this->Session->read('Config.language'));
            }
    function beforeFilter(){
                    $this->_setLanguage(); 
    ....
    }
    
    public function _setLanguage() {
    
        //if the cookie was previously set, and Config.language has not been set
        //write the Config.language with the value from the Cookie
            if ($this->Cookie->read('lang') && !$this->Session->check('Config.language')) {
                $this->Session->write('Config.language', $this->Cookie->read('lang'));
            }
            //if the user clicked the language URL
            else if (   isset($this->params['language']) &&
            ($this->params['language'] !=  $this->Session->read('Config.language'))
                    ) {
                //then update the value in Session and the one in Cookie
                $this->Session->write('Config.language', $this->params['language']);	    
                $this->Cookie->write('lang', $this->params['language'], false, '20 days');
            }
        }
    
        //override redirect
    
        public function redirect( $url, $status = NULL, $exit = true ) {
    
            if (!isset($url['language']) && $this->Session->check('Config.language')) {
    
                $url['language'] = $this->Session->read('Config.language');
            }
            parent::redirect($url,$status,$exit);
        }
    

    Reply

    • Dimitris — July 28, 2013 at 16:44

      try to add the below line after the if in setLanguage function

      Configure::write('Config.language', $this->Session->read('Config.language'));

      Reply

  24. lagunawebdesign — July 25, 2013 at 17:27

    Hi,
    What about forms? Do I need special code to automatically add language to form url like custom HtmlHelper?
    Cheers

    Reply

    • Dorin M — August 3, 2013 at 09:18

      You don’t need to do anything special for the forms as long as you use MyHtmlHelper

      Reply

  25. Dimitris — July 28, 2013 at 16:37

    Hello,

    Great article thanks!
    Just a small think that i want to add in case someone has similar problem, in case that you use prefixes then the below routing need to be added in routes.php file. (i don’t know if its the best practice but it work in my case)

    
    Router::connect('/:language/:prefix/:controller/:action/*',
    	array(),
    	array('language' => 'eng|gre|rus|ger'));
    

    Reply

    • Sebastian — September 9, 2013 at 13:43

      Thx, a lot! Took me the whole morning trying to figure out how to get a prefix working.

      Reply

      • Sebastian — September 9, 2013 at 14:34

        And two small additions I made to the setLanguage function:

        if(!isset($url['language']) && isset($this->params['language']) && is_array($url)) {
            $url['language'] = $this->params['language'];
            if (isset($this->params['prefix'])) {
                $url['prefix'] = $this->params['prefix'];
            }
        }
        

        At first I prevent the function adding the language to String urls, which would display a lot of warnings during debugging, for example with DebugKit.
        Next I add the prefix to the url, if it exists. Otherwise my links oddly wouldn’t contain those anymore.

  26. Mooky — September 5, 2013 at 14:50

    Hi 🙂
    nice article, but got some trouble with it :s
    doesn’t work properly :/(i use cakephph v2.3)
    i’ve followed all your 5 step, althought use the update for the routes.php
    but when i click on my lick to swith language, it only reirect me to my home page and don’t change my language :/

    if i switch in the core.php i can switch language and use the right translation but if i use the link in my view, it only rediret me to my home page :/

    do you have any ideas about why doesn’t work correctly ? 🙂

    regards

    Reply

  27. pTucky — September 23, 2013 at 06:01

    Thanks, It great sample easy and useful for my work.

    Reply

  28. Andreas — October 9, 2013 at 14:08

    If you combine your tutorial with the simple authentication tutorial from cakephp (http://book.cakephp.org/2.0/en/tutorials-and-examples/blog-auth-example/auth.html) you get a redirection problem when trying to login. Couldn’t find out why will now, do know anything about that?

    Reply

    • Justin — January 17, 2014 at 09:06

      I have it working in that I’m not getting any errors with the login form. However after login it redirects to example.com/dashboard and not example.com/eng/dashboard. It works fine if I manually edit the URL and insert the lang code… I suspect this code does work with the Auth Component? e.g. $this->Auth->loginRedirect

      I think our issues are related.

      Reply

  29. igarcia — January 5, 2014 at 14:50

    Finally, the url function in the AppHelper had to be improved. In my particular case, the majority of the URLs were string not array, so the function was not doing anything.

    This has been my trick:

    public function url($url = null, $full = false) {
            
            if(is_array($url)){
     	    	if(!isset($url['language']) && isset($this->params['language'])) {
    	 			$url['language'] = $this->params['language'];
    	 		}
            }elseif(is_string($url) && isset($this->params['language']) && !strstr($url, $this->params['language'])){
    	        if(!file_exists(WWW_ROOT.$url))
    		        $url = DS.$this->params['language'].$url;
            }
            return parent::url($url, $full);
       }
    

    Reply

  30. Litu — March 16, 2014 at 20:19

    Hi,
    I’m new i Cakephp. I followed your step in my app but I’ve no output what I need. WHat’s the problem . I’ve no getting any error.
    I want to use two language in my application those are danish and English. Please help me!!!

    Reply

  31. CakeLover — September 29, 2014 at 15:55

    Hello, I am trying to internationalize my cakephp website. But I am having trouble with the URL. I am able to translate the website with CakePHP, but i am having issues with the URL.

    The native language is spanish (ESP), and the other languages would be English and Portuguese. Now I want the native language to not have a url prefix, but the other languages I do.

    Now the url language prefix works with pages that have their own controller, but for the static pages it isnt working.

    Reply

  32. Emerson — December 2, 2014 at 18:14

    I’m having the same issue as Kostas (~ ). My routes are working but URLs with a missing language parameter are NOT redirected to the default language URL.

    So for example, I am able to direct my browser to http://example.com/ OR http://example.com/eng . It is not clear to me if this is the intended behavior or not. If it is , I would love some advice on how to change it so that any request that is missing a language param would redirect to the same controller/action + the default language prefix. EX: http://example.com/ => http://example.com/eng

    PS: This post remains the definitive source on how to localized URLs in CakePHP. Kudos!

    Reply

    • Dorin Moise — December 8, 2014 at 22:40

      > It is not clear to me if this is the intended behavior or not.
      Yes it is
      > If it is , I would love some advice on how to change it
      I guess you would have to play with the routes.

      Reply

  33. mircea — December 14, 2014 at 10:37

    Multumesc pentru tutorial!

    Reply

  34. rao5s.vn — September 7, 2015 at 11:31

    I think you should update a bit in function url,
    add condition is_array($url) because if url is a string(has format /controller/action)
    so it’ll error "Illegal string offset 'language'"
    and not need create new helper, you should add function url into AppHelper to can use for all Application
    Example:

    class AppHelper extends Helper{
        public function url($url = null, $full = false) {
            if (!isset($url['language']) && isset($this->params['language']) && is_array($url)) {
                $url['language'] =  $this->params['language'];
            }
            return parent::url($url, $full);
        }
    }
    

    Reply

  35. MyatBhoneKhaugn — February 25, 2016 at 09:50

    It is not work in my projects! suggest me!

    Warning (2): Illegal string offset 'language' [APP/View/Helper/MyHtmlHelper.php, line 8]
    

    Reply

  36. Drizzt — July 15, 2016 at 14:55

    is there an Update for Cakephp 3.x coming?

    Reply

  37. ryu — June 15, 2017 at 05:40

    Well, everything is working well but i have one problem in appending url. For eg. If I have a url localhost/Post/edit/5. When I clicked the language link the url becomes like localhost/Post/edit/language:eng. The param 5 is gone. Can you help me with that?

    Reply

    • Marc — August 10, 2017 at 02:35

      I have the same problem. How did you fixed it ?

      Reply

Leave a response