Multiple languages in a CakePHP 2.* application in 5 steps
Written on 1 August 2012, 10:21pm
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'));
Written by Dorin Moise (Published articles: 272)
- Likes (23)
-
Share
- Comments (83)
Comments (83)
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.
Thank you! 🙂
You are perfectly right about the visibility modifier. I fixed it in the post.
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.
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.
Thank you both for the observations.
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
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.
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.
@Marco
This sounds like a very nice and useful addition. However, please note that you should Never Trust The Browser To Know The Right Locale
Please let us know your solution after implementing it.
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!
@Daniela
Can you please paste the code somewhere to have a look?
Thanks.
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:
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…
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?
Ok, I think I got it. Here is the complete code. Keep in mind that all the code goes within the CakePHP AppController class:
ignore the saveUserAgent thing, its not relevant.
Thanks for sharing! In which CakePHP version did you test it?
PS: I formatted your code.
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!!!
Hi Marco,
what is the value of
$this->allowedLanguages
???Thanks
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!!!
Hi Marco,
About the CakePHP version, please note that the latest stable version at this time is 2.2.2.
About formatting the code – it’s a WordPress plugin doing this. The name is SyntaxHighlighter Evolved: http://www.viper007bond.com/wordpress-plugins/syntaxhighlighter/
Thanks for this post!!
Slight change suggestion for the helper.
This fixes errors for href=”#” (used for postlinks).
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
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.:
or:
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…Are you sure you implemented the steps 1-4 as described in this post?
Yes I followed this steps.
Here’s my AppController:
Hope u can help me.
Thanks
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.Are you using PHP 5.4? This version does not allow use String indexes in arrays.
I have the same problem :'(
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:
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?
Just noticed, that the “Config.language” is not being updated.
The “Config.Session” and the Cookie are being updated.
Any idea why?
Hey Gerd,
the function above _setLanguage() doesn’t update the config language, so u have to add this line:
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:
to your beforeFilter() Method and it should work…
Regards
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.
My folder structure looks like this: Views/Pages/de: Views/Pages/en
You can also change it in the beforeFilter() Method.
Regards,
Alex
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.
sdf
Can u post your beforeFilter() Method?
Does the folder “eng” really exist in your path?
This one isn’t working for me:
This one is working fine:
I tried your code and it works fine for me and can’t find any error here.
What do u get, if u try?
I think the problem must be in this path.
I found the soultion, thanks to you Alex!
View in capital letters was not accepted by my server. Now it’s working.
Thanks again for your help!
Is there a way to remove
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?
@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.
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?
@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
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?
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
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 🙂
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.
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?
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.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
seems like your session and/or cookie is not updated.
Debug your Config.language..
To avoid duplicate content
in AppController.php You can add default language:
and set routes:
extra ‘|’ to allow empty values for language
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.
Forgot to mention. I used it on Cakephp 2.2.5
just found this:
http://translations.cakephp.org/
https://github.com/cakephp/localized
I followed your step but when i click on link of language it display only header of page non whole page
Some more details would help.
I’m a little bit in trouble. I’m a newbie in CakePHP.
I put in layout links:
and have a route
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?
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:
I hope this helps you. Is there a better solution? Or are there problems associated with mine?
Thanks in advance,
Sjoerd
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
AppController
try to add the below line after the if in setLanguage function
Hi,
What about forms? Do I need special code to automatically add language to form url like custom HtmlHelper?
Cheers
You don’t need to do anything special for the forms as long as you use MyHtmlHelper
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)
Thx, a lot! Took me the whole morning trying to figure out how to get a prefix working.
And two small additions I made to the setLanguage function:
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.
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
Thanks, It great sample easy and useful for my work.
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?
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.
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:
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!!!
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.
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!
> 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.
Multumesc pentru tutorial!
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:
It is not work in my projects! suggest me!
is there an Update for Cakephp 3.x coming?
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?
I have the same problem. How did you fixed it ?