This is an example of an internationalized (I18N) and localizable (L10N) CakePHP 2.x Application.
Download: CakePHP 2.x application source files - cake-i18n.zip
Highlights of the sample CakePHP 2.x application are:
I describe an overview of the sample, list files that comprise the application followed by explanation of the topics shown above.
According to Wikipedia:
CakePHP is an open source web application framework. It is written in PHP, modeled after the concepts of Ruby on Rails, and distributed under the MIT License.
According to Wikipedia again, CakePHP was born in 2005. Its v1.2.0 supporting PHP v4 or v5 was released in 2008. v2.2.0 only supporting PHP v5 was released in 2011.
The latest stable version as of 2012 december is v2.2.4.
Convension is a fashon these days in writing web applications.
CakePHP uses convention in a great amount.
URL paths and request variable names are determined by naming convention used in the database and PHP code.
To assist building of such convention-based web applications, CakePHP provides a convenient utility called Cake
to generate a part of a web application that operates on a database table based on the table definition.
CakePHP uses GNU gettext library to provide its localization framework, a common style in C language world.
This is a simple message exchange application. You can create and send a message, show the list of messages and delete a message.
You may think that this applicatioin is made of the two tutorial code published on the official CakePHP site with some additional flavors.
Click the screen image to see only that image.
This application supports two languages: English and Japanese. If you set the most preferred language to Japanese, UI is displayed in Japanese. If you set it to other than Japanese (say to English) the UI is displayed in English.
Whatever the language setting, the message text itself is displayed in a language it is written.
This application requires registration of users. On registration, a role (or priviledge) is selected: administrator or normal user. A user with administration role can see and delete any message. A normal user can see and delete a message he or she sent or received. Messages related only to others do not appear on the message list. An administrator can see the list of users, add or delete a registered user.
A way to set up a CakePHP application is different from that of an ordinary PHP application. It's not that you install Apache, install PHP, install CakPHP somewhere then write an application elsewhere. You install a cakePHP package within the Apache document root and add application-specifiec files there. It's like copying a blank template then adding files to it in Java Struts application building.
At first, let's take a look at major files are in this sample application.
<apache-document-root> cake-i18n/ app/ Config/ core.php database.php routes.php Controller/ AppController.php MessagesController.php UsersController.php Locale/ jpn/ LC_MESSAGES/ default.po Model Message.php User.php View Layouts default.ctp Messages add.ctp index.ctp view.ctp Users add.ctp index.ctp login.ctp view.ctp webroot css cake.generic.css
The cake-i18n
is the root of this application that is immediately below the Apache document root.
It is the same location as the CakePHP package is unpacked.
You will add application files below app
according to some rules.
In summary:
Config | Application configuration files are placed here; database to use, debug mode or application's top page path are specified. |
Controller | Controller classes are placed here; processing functions (actions) for each URL path is written in a controller class. |
Model | Model classes are placed here; they are used to interface database tables. |
View | Core part of a result page associated with a function in a controller. An entire layout design file (defualts to default.ctp) is placed in Layouts folder. |
Locale | Localization translation files are placed here. |
webroot | Application's static files are placed here. For example, css/cake.generic.css is the default stylesheet. |
The next URL is an entry point of this application assuming the Apache server name is <server>
:
http://<server>/cake-i18n/
The rest of this page follows application building steps and describes how to create these files.
Building of a CakePHP application starts with data definition. First, you have to design and create database tables for application use.
This sample application uses two tables: messages
table for holding text messages and users
table for holding application users.
The following SQL statement creates the messages
table:
CREATE TABLE messages ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, sender_id INT UNSIGNED, receipient_id INT UNSIGNED, title VARCHAR(50), body TEXT, created DATETIME DEFAULT NULL, modified DATETIME DEFAULT NULL );
The next SQL statement is for creating the other users
table:
CREATE TABLE users ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50), password VARCHAR(50), role VARCHAR(20), created DATETIME DEFAULT NULL, modified DATETIME DEFAULT NULL );
Specify database connection to use in Config/database.php
.
public $default = array( 'datasource' => 'Database/Mysql', 'persistent' => false, 'host' => 'localhost', 'login' => 'root', 'password' => 'test', 'database' => 'test', 'encoding' => 'utf8' );
CakePHP uses this information to know what tables and columns are in the database.
As you see, the character code used for this application is UTF-8 throughout database and web app.
Then you create a model class for each table.
The model class for messages
is app/Model/Message.php
while that of users
table is app/Model/User.php
.
If you use a model class, you can do the following operations without writing a single function:
$this->Message->findAll(); $this->Message->findAllBySenderId(3); $this->User->findById(3); $this->User->findAllByRole('Admin');
The first function call reads all rows from the messages
table.
The second reads all rows whose sender_id
is three.
The third reads a row whose id
is three from the users
table.
The last function reads all rows whose role
is 'admin'.
If you rely on a model to do update to the database, timestamps such as created
and modified
are automatically updated.
Next you create controllers.
You define functions associated with URL paths of this application in a controller class.
For example, function index()
in a controller class called app/Controller/MessagesController.php
will be called for messages/index
(or just messages/
).
add()
in MessagesController.php
will be called for messages/add
.
Finally you create view pages.
The view page for the result returned from index()
in MessagesController.php
associated with messages/index
is app/View/Messages/index.ctp
.
app/View/Messages/add.ctp
corresponds to messages/add
.
http://<server>/cake-i18n/messages/index -> MessagesController::index() -> View/Messages/index.ctp (uses Messages.php -> messages table) http://<server>/cake-i18n/messages/add -> MessagesController::add() -> View/Messages/add.ctp (uses Messages.php -> messages table)
In this way, association of components in an application is done based on names of tables and columns in the database and names of folders, files and functions in your PHP code. You don't have to do special setup for such association.
CakePHP provides a console command called Cake written in PHP. With the two subcommands of Cake, you can:
You can run the Bake subcommand by passing a parameter "bake" to the Cake utility:
Here are files generated for the messages
table of this application:
app/Model/Message.php app/Controller/MessagesController.php app/View/Messages/index.ctp app/View/Messages/add.ctp app/View/Messages/delete.ctp app/View/Messages/view.ctp app/View/Messages/edit.ctp
These provide all the necessary processings of the table rows although operations are limited to this single table. Especially the page listing includes paging function (pagination) from the start. Page navigation buttons are provided. You can click a table header to change order of rows being listed. You can specify initial order of the listing.
Few applications are complete with just operations to a single table. However, it is helpful if you don't have to write basic functions of each piece of data that comprise your application. You can complete your application by adding exceptional things such as association with other data and initial values for an HTML form, etc.
You finish the desired application by adding modifications to PHP code generated by Bake command. Here are major modifications I made to the generated code:
Config/routes.php | Specified application entry (login page) |
Controller/AppController.php | Declared use of session and authentication components |
Controller/MessageController.php | Restriction of messages shown to a normal user. Set the sender to the currently logged in user. |
View/Messages/add.ctp | Create a candidate list of receipients. Fix the sender to the logged user. |
View/Messages/index.ctp | Show senders and receipients with names (not IDs) |
View/Messages/view.ctp | Ditto |
View/User/add.ctp | Limit role names to admin and user |
View/User/index.ctp | Hide passwords (actually they are hash values and safe to show) |
I added comments starting from "mods" at modifications to the code. If you looked at the sample code, you know how less I needed to add modifications.
There is one important modification: association of models, that is tables. The message table contains IDs of a sender and receipient. These IDs point to rows in the user table. At first, the message listing and addition pages showed these IDs. It is convenient if names are displayed.
If you tell CakePHP about the relationship between messages
and users
table, read in a row from the messages
table, related rows from the users
table are also read in.
With this setup, if you read a message data using the model:
$message = $this->Message->findById(10); // read a message whose id is ten
The read data, that is a value of variable ##$message# will contain:
array 'Message' id => 10 sender_id = 3 receipient_id = 4 title => 'Hello' body => 'Hello, World' 'Sender' id => 3 username => 'obama' 'Receipient' id => 4 username => 'abe'
CakePHP's model association is powerful in that it allows multi-level linking of tables (defualt is one level).
It seems the Bake utility allows us to specify model association. I didn't test it because I don't know how to use the feature. It's wonderful if I can do it.
Another function of Cake utility is to scan all application source files and extracts strings that need to be translated.
You see the following if you look at a PHP code generated by Bake command:
[View/Messages/index.ctp] <?php echo $this->Form->postLink(__('Delete'), array('action' => 'delete', $message['Message']['id']), null, __('Are you sure you want to delete # %s?', $message['Message']['id'])); ?> [app/Controller/MessagesController.php] $this->Session->setFlash(__('Message deleted'));
You see a function with two underscores.
This is a function to output a localized string.
For __('Delete')
, you will see a button with its name "Delete" if you do nothing.
If you prepare a Japanese translation file, add an entry for "Delete", and the user's browser's preferred language is Japanese, then the button will be shown with the Japanese name.
If you run the Cake utility by passing "i18n" parameter, the entire PHP code in an application is scanned, strings such as above will be extracted and a number of files with POT
extention will be created:
app Locale cake.pot cake_console.pot cake_dev.pot default.pot
Strings of the application are stored in default.pot
.
This is a template for translation into different languages.
You can see the following items in the file:
#: View/Messages/edit.ctp:19 #: View/Messages/index.ctp:32 #: View/Users/edit.ctp:18 #: View/Users/index.ctp:25 #: View/Scaffolds/form.ctp:31 #: View/Scaffolds/index.ctp:52 #: View/Scaffolds/view.ctp:133 msgid "Delete" msgstr "" #: Controller/MessagesController.php:140 msgid "Message deleted" msgstr ""
Translate them into Japanese:
msgid "Delete" msgstr "japanese word for delete" msgid "Message deleted" msgstr "japanese sentence for the above"
You are done localization if you put the translated file in the following location.
The extention of the file must be PO
, not POT
which is for the template.
app/ Locale/ jpn/ LC_MESSAGES/ default.po
By the way, you see other file than defaput.pot
among the automatically generated translation templates.
These include strings used in the CakePHP side.
CakePHP allows you to prepare different translation files for messages of different "domains."
The default layout file contains the following line:
[View/Layouts/default.ctp] $cakeDescription = __d('cake_dev', 'CakePHP: the rapid development php framework');
The ordinaray __()
function retrieves a message from default.po
.
__d()
function will take a message from the cake_dev
domain in this case.
That is, you have to prepare cake_dev.po
and place the translation for the string in the file.
This application does nothing for specification of a language to use. CakePHP automatically switches a language by checking a preferred language setting sent from the browser (Accept-Language header).
You can, however, explicitly specify and fix the language setting:
Configure::write('Config.language', 'en'); // or 'ja' for japanese
You can, for example, determine a language at the start of a session and continue to use it during the session.
[At the beginning of a session] $this->Session->write('Config.language', 'en'); [Rest of the session] class AppController extends Controller { public function beforeFilter() { Configure::write('Config.language', $this->Session->read('Config.language')); } }
I checked the following documents in the CakePHP 2.x site for learning this part:
To implement authentication function in a Web application, you normally store login information extracted from a file or database into a session and use that information when necessary. CakePHP provides a component that allows you to do authentication simply and consistently.
With the authentication component, you only have to do little things to make authentication work if you prepare user information according to certain rules:
users
)
User.php
that references the table.
UserController.php
. It's easy if you use Bake. You have to prepare a login function and its view.
public $components = array('Auth', parameters);
users/login
)
The Auth component assumes values of passwords stored in the database are hashed (not plain text).
Therefore, you have to add some code to do hashing on writing a password in User.php
.
public function beforeSave($options = array()) { if (isset($this->data[$this->alias]['password'])) { $this->data[$this->alias]['password'] = AuthComponent::password($this->data[$this->alias]['password']); } return true; }
The application is not usable unless you register the first user, an administrator. You can't add a user unless you are logged in. Therefore, you need some setup such as allowing addition while in Dev mode.
[Controller/UsersController.php] public function beforeFilter() { parent::beforeFilter(); if (Configure::read('debug') > 0) $this->Auth->allow('add'); }
If a user logs in, the row containing the information about the user will be read and stored in the session. Check against a protected page is automatically done by CakePHP. If you want to add authorization function based on a role, you can do it by referencing the user information in the session.
$user = $this->Auth->user(); if ($user['role'] === 'admin') { ...
I checked the following documents in the CakePHP 2.x site for learning this part:
Here is how to test run this sample application by yourself:
See http://book.cakephp.org/2.0/en/installation.html for detail.
cake-i18n
.
cake-i18n
.
app/Config/database.php
. See required tables.
http://<server>/cake-i18n/add
and add users.
http:/<server>//cake-i18n/
.
If you use Apache, I recommend you to enable mod_write
.
Also make index.php
recognizable as an index page.
Set the character code to UTF-8 for PHP, MySQL and CakePHP.
I used the following environment:
Apache 2.2
PHP 5.3
MySQL 5.1
CakePHP 2.2
Other localization samples provided in this site are:
Kobu.Com has a lot of experiences in authoring software and documentation usable world-wide.
Please feel free to contact us.
Copyright © 2012 Kobu.Com. All rights reserved.
Presented by: Kobu.Com
Author: ARAI Bunkichi
Written: 2012 Dec 29
The published sample code is a prototype and is not complete.
Please refrain from duplicating the sample code and its document in another place.
This page is link-free. We welcome your questions and comments.