Adding multilingualism to single language site without i18n and/or TranslateBehaviour
Notice!
This tutorial is for CakePHP 2.x!Basic premises
In CakePHP you have wonderful things like i18n and l10n, and TranslateBehaviour. Problem is this is only useful if you design your app from the beginning to do the translation as you go on. This presumes having separate tables for translated content and so on. But what if you want to use your contents table for all language content, your news table for all the news in all the languages and so on. Then, what you do is just add language_id to all the tables from where you pull content, and have a languages table for mapping your languages to names, and of course for adding new languages.
DB/Model setup
First you should create your content table, containing a language_id field. And of course the languages table. Like so:
CREATE TABLE `contents` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(50) DEFAULT NULL,
`body` varchar(250) DEFAULT NULL,
`language_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
CREATE TABLE `languages` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`value` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `value` (`value`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
Create a model and the associations like. Content > belongsTo > Language / Language > hasMany > Content. Example below.
Content.php
class Content extends AppModel {
public $displayField = 'title';
public $validate = array(
'title' => array(
'notempty' => array(
'rule' => array('notempty'),
),
),
'body' => array(
'notempty' => array(
'rule' => array('notempty'),
),
),
);
public $belongsTo = array(
'Language' => array(
'className' => 'Language',
'foreignKey' => 'language_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
Language.php
class Language extends AppModel {
public $displayField = 'value';
public $hasMany = array(
'Content' => array(
'className' => 'Content',
'foreignKey' => 'language_id',
'dependent' => false)
);
}
<code>
<p> Well that takes care of db/model part, lets go on to Controller.</p>
<h2>Controller setup</h2>
<p>The controller setup is pretty easy, but depends on how is your site configured. I do all my frontend content from PagesController coupled with extensive use of elements, and of course routing to various actions defined. For ease of use I'll base this on the basic PagesController you get when you download/unpack CakePHP. But I'm getting ahead of myself. There is one thing you must configure before you do anything. You must configure your app to use Session component. This is paramount as this all is based on a very simple trick using sessions :)
So, let's go... In AppController you simply add the following code
<code>
<?php
class AppController extends Controller {
public $components = array('Session');
}
That's it, yes. Only that, a bit more code is contained in the controller that serves the front page where you put the selectors for the language. Basically links to controller actions only. I put the links in default.ctp layout file because the top menu is the same for all the pages. So let's look at how to config PagesController to make the language switching possible.
<?php
class PagesController extends AppController{
public function display(){
$path = func_get_args();
$count = count($path);
if (!$count) {
$this->redirect('/');
}
$page = $subpage = $title_for_layout = null;
if (!empty($path[0])) {
$page = $path[0];
}
if (!empty($path[1])) {
$subpage = $path[1];
}
if (!empty($path[$count - 1])) {
$title_for_layout = Inflector::humanize($path[$count - 1]);
}
$this->set(compact('page', 'subpage', 'title_for_layout'));
$this->render(implode('/', $path));
}
public function setLangEng(){
$this->Session->write('lang','2');
$this->redirect(array('controller'=>'pages','action'=>'display','home'));
}
public function setLangGer(){
$this->Session->write('lang','1');
$this->redirect(array('controller'=>'pages','action'=>'display','home'));
}
}
The only custom code here is setLangEng() and setLangGer() method that only write a session variable for the language used. And of course you have to configure the routes for those actions to map correctly. That goes in your routes.php, and goes something like this.
//rest of the routes.php you leave alone, just add these two lines after
// Router::connect('/eng', array('controller' => 'pages', 'action' => 'display','home'));
Router::connect('/eng', array('controller' => 'pages', 'action' => 'setLangEng'));
Router::connect('/ger', array('controller' => 'pages', 'action' => 'setLangGer'));
And the last, but not the least piece of magic happens in ContentsController where you set the content to get for each language based on language_id ('lang' session variable) you set in the PagesController.php. So you open the ContentsController.php, and add a few methods of your own, like so...
<?php
class ContentsController extends AppController {
//basic index, add, view, edit ...
public function getcont(){
if($this->Session->read('lang')==1)
{
$this->paginate = array(
'conditions' => array('Content.language_id' => 1),
'limit' => 3,
'fields'=>array('News.body')
);
$content=$this->paginate('Content');
return $content;
}
if($this->Session->read('lang')==2)
{
$this->paginate = array(
'conditions' => array('Content.language_id' => 2),
'limit' => 3,
'fields'=>array('News.body')
);
$content=$this->paginate('Content');
return $content;
}
}
Finishing it up
All you have to do now is to setup links for the language selection, and of course, get the content. I do so in elements. But first the links... Open your /app/View/Layouts/default.ctp and put the following in in the place you want the language selection menu to be.
<a href="/ger">Ger</a>
<a href="/eng">Eng</a>
As you see i used plain HTML links, but that is because I like it more that way, because the designer that works on the front end can then find their way around more easily.
The last thing is to call up the content in the view. Because of the way it is set up it will be the same view for every language, just the content (or whatever you set up this way) will change depending on the session variable 'lang'. So the view would look something like this (I'll use the requestAction because I'm calling it from a different controller).
//rest of your view code
<div class="content-box six-cols">
<?php $contents = $this -> requestAction('/contents/getcont');?>
<?php foreach($contents as $content): ?>
<h2><?php echo $content['Content']['title'];?></h2>
<p><?php echo $content['Content']['body'];?></p>
<?php endforeach; ?>
</div>
So, this is about it... This is my first CakePHP writeup, all before were, how do I do this, or how do I do that. All the other solutions I've come accross for adding multiple languages had meant I had to do massive refactoring, so I settled for this solution. It works just fine. So if you like it, shout :)







