Home > i18n > CakePHP | Japanese



Kobu.Com Logo

Contact



CakePHP 2.x Application
Localization Sample



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.

CakePHP 2.x

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.

CakePHP

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.

About This Sample Application

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.

Messages list Write message page

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.

Language dialog of Web browser

Whatever the language setting, the message text itself is displayed in a language it is written.

Message list in English

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.

List of users Add user page

Application Files

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:

ConfigApplication configuration files are placed here; database to use, debug mode or application's top page path are specified.
ControllerController classes are placed here; processing functions (actions) for each URL path is written in a controller class.
ModelModel classes are placed here; they are used to interface database tables.
ViewCore 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.
LocaleLocalization translation files are placed here.
webrootApplication'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.

Creating Database Tables

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.

Models, Controllers and Views

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.

Generating PHP Code with Cake bake Command

CakePHP provides a console command called Cake written in PHP. With the two subcommands of Cake, you can:

Cake bake
Generates prototypes of model, controller and views for operating a specific database table. Controller functions and views are created for adding, deleting, viewing, editing and listing (index) rows of the table.
Cake i18n
Generates a template file (pot) for localization by scanning strings in PHP source files of this application.

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.

Rewriting PHP Code

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.phpSpecified application entry (login page)
Controller/AppController.phpDeclared use of session and authentication components
Controller/MessageController.phpRestriction of messages shown to a normal user. Set the sender to the currently logged in user.
View/Messages/add.ctpCreate a candidate list of receipients. Fix the sender to the logged user.
View/Messages/index.ctpShow senders and receipients with names (not IDs)
View/Messages/view.ctpDitto
View/User/add.ctpLimit role names to admin and user
View/User/index.ctpHide 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.

Association of tables

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.

Generating Localization Templates with Cake i18n command, and Preparing Translation File

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:

Internationalization & Localization

Authentication Component

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:

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:

Running this App

Here is how to test run this sample application by yourself:

See http://book.cakephp.org/2.0/en/installation.html for detail.

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

Notice

Other localization samples provided in this site are:

Localizing Struts2 Application
A web application written in Struts2 tested for localization and conventional (or zero-config) action mapping.
Localizing Android Application
Android application with automatic switching between Japanese and English.

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.