SlideShare une entreprise Scribd logo
1  sur  18
Télécharger pour lire hors ligne
Internacionalization
Building multi-language applications with
                  CakePHP
Who am I?
●   The argentinean in the Cake team
●   A C++ developer that turned to Java,
    then PHP
●   Working exclusively with CakePHP for 3
    years
Oh, BTW



Tomorrow is my birthday
Why do we need i18n?
●   Expand your audience
●   CakePHP makes it simple
●   Translators don't have to know our
    application
●   Works with files (views, models,
    controllers), and database records
●   Think about your future needs
This could get you killed
●   The switch lover
     switch ($language) {
        case 'en': echo 'My message'; break;
        case 'es': echo 'Mi mensaje'; break;
     }


●   The table lover
     $terms = $this->Term->find('all');
     $terms = Set::combine($terms, '/Term/code', '/Term/text');
     $this->set(compact('terms'));

     // ...

     echo $term['my_message'];
The CakePHP way
●   Methods

    ●   __() -> __('My message')
    ●   __n() -> __n('An element', 'Several
        elements', $count)
    ●   Configure::write('Config.language', 'en')

●   Translate behavior

●   i18n extractor
The CakePHP way
●   Multibyte
    ●   1 letter != 1 byte
         ●   8 bits -> 256
         ●   wchar_t (L'w')
    ●   mb_strlen(), mb_strpos(), mb_substr(), ...
    ●   Multibyte::checkMultibyte() -> ord($char)
        > 128
    ●   Multibyte::utf8($string) -> array (values
        higher than 128 allowed)
    ●   Multibyte::ascii($array) -> string
The CakePHP way

    <p><?php __('Welcome to my page'); ?></p>
    <?php echo $html->link(__('Home', true), '/'); ?>
    <p><?php echo sprintf(__('Your name is %s', true),
   $name); ?></p>




    $ cake i18n extract -output app/locale



                                             locale/eng/LC_MESSAGES
  locale/                                            default.po
default.pot            POEDIT
                                                     default.mo
Let's go to work
●   Our own example
    ●   Modify the view
    ●   Run the extractor
    ●   Look at the generated template file
         ●   Run POEDIT → nplurals=2; plural=(n != 1);
    ●   Look at the translated files
●   Some tips when using POEDIT
Translate Behavior
●   Internationalization for our database
    records

●   All translations in the same table

●   Automatically filters the records to
    fetch them in the current language
Translate Behavior
          class Post extends AppModel {
             public $actsAs = array('Translate' => array(
                 'title', 'body'
             ));
             public $belongsTo = array('User');
          }




CREATE TABLE `posts`(                      CREATE TABLE `posts`(
    `id` INT NOT NULL AUTO_INCREMENT,          `id` INT NOT NULL AUTO_INCREMENT,
    `user_id` INT NOT NULL,                    `user_id` INT NOT NULL,
    `title` VARCHAR(255) NOT NULL,             `title` VARCHAR(255) NOT NULL,
    `body` TEXT,                               `body` TEXT,
    `created` DATETIME,                        `created` DATETIME,
    `modified` DATETIME,                       `modified` DATETIME,
    PRIMARY KEY(`id`)                          PRIMARY KEY(`id`)
);                                         );
Translate Behavior
        mysql> desc i18n;
        +-------------+--------------+------+-----+---------+----------------+
        | Field       | Type         | Null | Key | Default | Extra          |
        +-------------+--------------+------+-----+---------+----------------+
        | id          | int(10)      | NO   | PRI | NULL    | auto_increment |
        | locale      | varchar(6)   | NO   | MUL | NULL    |                |
        | model       | varchar(255) | NO   | MUL | NULL    |                |
        | foreign_key | int(10)      | NO   | MUL | NULL    |                |
        | field       | varchar(255) | NO   | MUL | NULL    |                |
        | content     | text         | YES |      | NULL    |                |
        +-------------+--------------+------+-----+---------+----------------+



mysql> select * from i18n;
+----+--------+-------+-------------+-------+------------------------------------------+
| id | locale | model | foreign_key | field | content                                  |
+----+--------+-------+-------------+-------+------------------------------------------+
| 1 | eng     | Post |            1 | title | Pre-registration opened                  |
| 2 | spa     | Post |            1 | title | Pre-inscripciones abiertas               |
| 3 | eng     | Post |            1 | body | Body for Pre-registration opened          |
| 4 | spa     | Post |            1 | body | Cuerpo para Pre-inscripciones abiertas    |
+----+--------+-------+-------------+-------+------------------------------------------+
4 rows in set (0.00 sec)
Translate Behavior
          $posts = $this->Post->find('all', array(
              'recursive' => -1, 'fields' => array('title', 'body')
          ));
          $posts = Set::combine($posts, '/Post/title', '/Post/body');




 SELECT `I18n__title`.`content`, `I18n__body`.`content`
 FROM `posts` AS `Post`
 LEFT JOIN `i18n` AS `I18n__title` ON (`Post`.`id` = `I18n__title`.`foreign_key` AND
`I18n__title`.`model` = 'Post' AND `I18n__title`.`field` = 'title')
 LEFT JOIN `i18n` AS `I18n__body` ON (`Post`.`id` = `I18n__body`.`foreign_key` AND
`I18n__body`.`model` = 'Post' AND `I18n__body`.`field` = 'body')
 WHERE `I18n__title`.`locale` = 'eng' AND `I18n__body`.`locale` = 'eng'




           array(
              [Pre-registration opened] => Body for Pre-registration opened
              [Site Updates] => Body for Site Updates
           )
Translate Behavior
$this->Post->create();
$this->Post->save(array('Post' => array(
     'user_id' => 1,
     'title' => array('eng' => 'ENG 1', 'spa' => 'spa1'),
     'body' => array('eng' => 'Body for ENG 1', 'spa' => 'Cuerpo para spa1')
)));



          $this->Post->create();
          $this->Post->save(array('Post' => array(
               'user_id' => 1,
               'title' => array('eng' => 'ENG 1'),
               'body' => array('eng' => 'Body for ENG 1')
          )));
          $this->Post->save(array('Post' => array(
               'id' => $this->Post->id,
               'title' => array('spa' => 'spa1'),
               'body' => array('spa' => 'Cuerpo para spa1')
          )));
Changing the language
class AppController extends Controller {
   public $components = array('Cookie');
   public function beforeFilter() {
       $lang = null;
       if (!empty($this->params['url']['lang'])) {
            $lang = $this->params['url']['lang'];
            $this->Cookie->write('CakeFestLanguage', $lang, false, '+365 days');
       } else {
            $lang = $this->Cookie->read('CakeFestLanguage');
       }
       if (empty($lang)) {
            $lang = Configure::read('CakeFest.defaultLanguage');
       }
       Configure::write('Config.language', $lang);
   }

    function beforeRender() {
        $this->set('currentLanguage', Configure::read('Config.language'));
    }
}
Changing the language
 class AppHelper extends Helper {
    Public function url($url = null, $full = false) {
        if (!empty($url) && !is_array($url) && $url[0] == '/') {
              $urlRoute = Router::parse($url);
              if (!empty($urlRoute['controller'])) {
                    $url = array_merge(array_intersect_key($urlRoute,
array_flip(array('admin', 'controller', 'action', 'plugin'))), $urlRoute['pass'],
$urlRoute['named']);
              }
        }
        if (is_array($url)) {
              if (!isset($url['lang']) && Configure::read('Config.language') !=
Configure::read('CakeFest.defaultLanguage')) {
                    $url['lang'] = Configure::read('Config.language');
              }
        }
        return parent::url($url, $full);
    }
}
Caching i18n elements

$this->element('news', array('cache' => array(
   'time' => '+1 day',
   'key' => $currentLanguage
)));




                tmp/cache/views
               element_eng_news
               element_spa_news
And we are done



Questions?

Contenu connexe

Tendances

Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4
Fabien Potencier
 
Dependency Injection with PHP and PHP 5.3
Dependency Injection with PHP and PHP 5.3Dependency Injection with PHP and PHP 5.3
Dependency Injection with PHP and PHP 5.3
Fabien Potencier
 

Tendances (20)

Lithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate FrameworksLithium: The Framework for People Who Hate Frameworks
Lithium: The Framework for People Who Hate Frameworks
 
PHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php frameworkPHP 5.3 and Lithium: the most rad php framework
PHP 5.3 and Lithium: the most rad php framework
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
 
Quebec pdo
Quebec pdoQuebec pdo
Quebec pdo
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
 
Building Lithium Apps
Building Lithium AppsBuilding Lithium Apps
Building Lithium Apps
 
Command Bus To Awesome Town
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome Town
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
 
Doctrine 2
Doctrine 2Doctrine 2
Doctrine 2
 
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo EditionLithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
 
The State of Lithium
The State of LithiumThe State of Lithium
The State of Lithium
 
PHP tips and tricks
PHP tips and tricks PHP tips and tricks
PHP tips and tricks
 
Dependency Injection
Dependency InjectionDependency Injection
Dependency Injection
 
Things I Believe Now That I'm Old
Things I Believe Now That I'm OldThings I Believe Now That I'm Old
Things I Believe Now That I'm Old
 
Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4Dependency injection in PHP 5.3/5.4
Dependency injection in PHP 5.3/5.4
 
Dependency Injection with PHP and PHP 5.3
Dependency Injection with PHP and PHP 5.3Dependency Injection with PHP and PHP 5.3
Dependency Injection with PHP and PHP 5.3
 
Everything About PowerShell
Everything About PowerShellEverything About PowerShell
Everything About PowerShell
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
 
CoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love AffairCoffeeScript - A Rubyist's Love Affair
CoffeeScript - A Rubyist's Love Affair
 

Similaire à Internationalizing CakePHP Applications

Zendcon 2007 Api Design
Zendcon 2007 Api DesignZendcon 2007 Api Design
Zendcon 2007 Api Design
unodelostrece
 
Php tips-and-tricks4128
Php tips-and-tricks4128Php tips-and-tricks4128
Php tips-and-tricks4128
PrinceGuru MS
 
Php Code Audits (PHP UK 2010)
Php Code Audits (PHP UK 2010)Php Code Audits (PHP UK 2010)
Php Code Audits (PHP UK 2010)
Damien Seguy
 
From mysql to MongoDB(MongoDB2011北京交流会)
From mysql to MongoDB(MongoDB2011北京交流会)From mysql to MongoDB(MongoDB2011北京交流会)
From mysql to MongoDB(MongoDB2011北京交流会)
Night Sailer
 
R57shell
R57shellR57shell
R57shell
ady36
 
Taming that client side mess with Backbone.js
Taming that client side mess with Backbone.jsTaming that client side mess with Backbone.js
Taming that client side mess with Backbone.js
Jarod Ferguson
 
PHP and Rich Internet Applications
PHP and Rich Internet ApplicationsPHP and Rich Internet Applications
PHP and Rich Internet Applications
elliando dias
 

Similaire à Internationalizing CakePHP Applications (20)

Extbase and Beyond
Extbase and BeyondExtbase and Beyond
Extbase and Beyond
 
PHP security audits
PHP security auditsPHP security audits
PHP security audits
 
Zendcon 2007 Api Design
Zendcon 2007 Api DesignZendcon 2007 Api Design
Zendcon 2007 Api Design
 
Php tips-and-tricks4128
Php tips-and-tricks4128Php tips-and-tricks4128
Php tips-and-tricks4128
 
Php Code Audits (PHP UK 2010)
Php Code Audits (PHP UK 2010)Php Code Audits (PHP UK 2010)
Php Code Audits (PHP UK 2010)
 
Simple Ways To Be A Better Programmer (OSCON 2007)
Simple Ways To Be A Better Programmer (OSCON 2007)Simple Ways To Be A Better Programmer (OSCON 2007)
Simple Ways To Be A Better Programmer (OSCON 2007)
 
関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
 
Php on the desktop and php gtk2
Php on the desktop and php gtk2Php on the desktop and php gtk2
Php on the desktop and php gtk2
 
Mojolicious
MojoliciousMojolicious
Mojolicious
 
Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2Zend Framework Study@Tokyo #2
Zend Framework Study@Tokyo #2
 
From mysql to MongoDB(MongoDB2011北京交流会)
From mysql to MongoDB(MongoDB2011北京交流会)From mysql to MongoDB(MongoDB2011北京交流会)
From mysql to MongoDB(MongoDB2011北京交流会)
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
 
Blog Hacks 2011
Blog Hacks 2011Blog Hacks 2011
Blog Hacks 2011
 
Get into the FLOW with Extbase
Get into the FLOW with ExtbaseGet into the FLOW with Extbase
Get into the FLOW with Extbase
 
PHP Static Code Review
PHP Static Code ReviewPHP Static Code Review
PHP Static Code Review
 
R57shell
R57shellR57shell
R57shell
 
Taming that client side mess with Backbone.js
Taming that client side mess with Backbone.jsTaming that client side mess with Backbone.js
Taming that client side mess with Backbone.js
 
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you needDutch PHP Conference - PHPSpec 2 - The only Design Tool you need
Dutch PHP Conference - PHPSpec 2 - The only Design Tool you need
 
PHP and Rich Internet Applications
PHP and Rich Internet ApplicationsPHP and Rich Internet Applications
PHP and Rich Internet Applications
 
Let's write secure Drupal code! - DrupalCamp Oslo, 2018
Let's write secure Drupal code! - DrupalCamp Oslo, 2018Let's write secure Drupal code! - DrupalCamp Oslo, 2018
Let's write secure Drupal code! - DrupalCamp Oslo, 2018
 

Plus de Pierre MARTIN (6)

Introduction à CakePHP
Introduction à CakePHPIntroduction à CakePHP
Introduction à CakePHP
 
Using and reusing CakePHP plugins
Using and reusing CakePHP pluginsUsing and reusing CakePHP plugins
Using and reusing CakePHP plugins
 
Building custom APIs
Building custom APIsBuilding custom APIs
Building custom APIs
 
Test and API-driven development of CakePHP Behaviors
Test and API-driven development of CakePHP BehaviorsTest and API-driven development of CakePHP Behaviors
Test and API-driven development of CakePHP Behaviors
 
Recipes for successful CakePHP projects
Recipes for successful CakePHP projectsRecipes for successful CakePHP projects
Recipes for successful CakePHP projects
 
The CakePHP Media Plugin
The CakePHP Media PluginThe CakePHP Media Plugin
The CakePHP Media Plugin
 

Internationalizing CakePHP Applications

  • 2. Who am I? ● The argentinean in the Cake team ● A C++ developer that turned to Java, then PHP ● Working exclusively with CakePHP for 3 years
  • 3. Oh, BTW Tomorrow is my birthday
  • 4. Why do we need i18n? ● Expand your audience ● CakePHP makes it simple ● Translators don't have to know our application ● Works with files (views, models, controllers), and database records ● Think about your future needs
  • 5. This could get you killed ● The switch lover switch ($language) { case 'en': echo 'My message'; break; case 'es': echo 'Mi mensaje'; break; } ● The table lover $terms = $this->Term->find('all'); $terms = Set::combine($terms, '/Term/code', '/Term/text'); $this->set(compact('terms')); // ... echo $term['my_message'];
  • 6. The CakePHP way ● Methods ● __() -> __('My message') ● __n() -> __n('An element', 'Several elements', $count) ● Configure::write('Config.language', 'en') ● Translate behavior ● i18n extractor
  • 7. The CakePHP way ● Multibyte ● 1 letter != 1 byte ● 8 bits -> 256 ● wchar_t (L'w') ● mb_strlen(), mb_strpos(), mb_substr(), ... ● Multibyte::checkMultibyte() -> ord($char) > 128 ● Multibyte::utf8($string) -> array (values higher than 128 allowed) ● Multibyte::ascii($array) -> string
  • 8. The CakePHP way <p><?php __('Welcome to my page'); ?></p> <?php echo $html->link(__('Home', true), '/'); ?> <p><?php echo sprintf(__('Your name is %s', true), $name); ?></p> $ cake i18n extract -output app/locale locale/eng/LC_MESSAGES locale/ default.po default.pot POEDIT default.mo
  • 9. Let's go to work ● Our own example ● Modify the view ● Run the extractor ● Look at the generated template file ● Run POEDIT → nplurals=2; plural=(n != 1); ● Look at the translated files ● Some tips when using POEDIT
  • 10. Translate Behavior ● Internationalization for our database records ● All translations in the same table ● Automatically filters the records to fetch them in the current language
  • 11. Translate Behavior class Post extends AppModel { public $actsAs = array('Translate' => array( 'title', 'body' )); public $belongsTo = array('User'); } CREATE TABLE `posts`( CREATE TABLE `posts`( `id` INT NOT NULL AUTO_INCREMENT, `id` INT NOT NULL AUTO_INCREMENT, `user_id` INT NOT NULL, `user_id` INT NOT NULL, `title` VARCHAR(255) NOT NULL, `title` VARCHAR(255) NOT NULL, `body` TEXT, `body` TEXT, `created` DATETIME, `created` DATETIME, `modified` DATETIME, `modified` DATETIME, PRIMARY KEY(`id`) PRIMARY KEY(`id`) ); );
  • 12. Translate Behavior mysql> desc i18n; +-------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+--------------+------+-----+---------+----------------+ | id | int(10) | NO | PRI | NULL | auto_increment | | locale | varchar(6) | NO | MUL | NULL | | | model | varchar(255) | NO | MUL | NULL | | | foreign_key | int(10) | NO | MUL | NULL | | | field | varchar(255) | NO | MUL | NULL | | | content | text | YES | | NULL | | +-------------+--------------+------+-----+---------+----------------+ mysql> select * from i18n; +----+--------+-------+-------------+-------+------------------------------------------+ | id | locale | model | foreign_key | field | content | +----+--------+-------+-------------+-------+------------------------------------------+ | 1 | eng | Post | 1 | title | Pre-registration opened | | 2 | spa | Post | 1 | title | Pre-inscripciones abiertas | | 3 | eng | Post | 1 | body | Body for Pre-registration opened | | 4 | spa | Post | 1 | body | Cuerpo para Pre-inscripciones abiertas | +----+--------+-------+-------------+-------+------------------------------------------+ 4 rows in set (0.00 sec)
  • 13. Translate Behavior $posts = $this->Post->find('all', array( 'recursive' => -1, 'fields' => array('title', 'body') )); $posts = Set::combine($posts, '/Post/title', '/Post/body'); SELECT `I18n__title`.`content`, `I18n__body`.`content` FROM `posts` AS `Post` LEFT JOIN `i18n` AS `I18n__title` ON (`Post`.`id` = `I18n__title`.`foreign_key` AND `I18n__title`.`model` = 'Post' AND `I18n__title`.`field` = 'title') LEFT JOIN `i18n` AS `I18n__body` ON (`Post`.`id` = `I18n__body`.`foreign_key` AND `I18n__body`.`model` = 'Post' AND `I18n__body`.`field` = 'body') WHERE `I18n__title`.`locale` = 'eng' AND `I18n__body`.`locale` = 'eng' array( [Pre-registration opened] => Body for Pre-registration opened [Site Updates] => Body for Site Updates )
  • 14. Translate Behavior $this->Post->create(); $this->Post->save(array('Post' => array( 'user_id' => 1, 'title' => array('eng' => 'ENG 1', 'spa' => 'spa1'), 'body' => array('eng' => 'Body for ENG 1', 'spa' => 'Cuerpo para spa1') ))); $this->Post->create(); $this->Post->save(array('Post' => array( 'user_id' => 1, 'title' => array('eng' => 'ENG 1'), 'body' => array('eng' => 'Body for ENG 1') ))); $this->Post->save(array('Post' => array( 'id' => $this->Post->id, 'title' => array('spa' => 'spa1'), 'body' => array('spa' => 'Cuerpo para spa1') )));
  • 15. Changing the language class AppController extends Controller { public $components = array('Cookie'); public function beforeFilter() { $lang = null; if (!empty($this->params['url']['lang'])) { $lang = $this->params['url']['lang']; $this->Cookie->write('CakeFestLanguage', $lang, false, '+365 days'); } else { $lang = $this->Cookie->read('CakeFestLanguage'); } if (empty($lang)) { $lang = Configure::read('CakeFest.defaultLanguage'); } Configure::write('Config.language', $lang); } function beforeRender() { $this->set('currentLanguage', Configure::read('Config.language')); } }
  • 16. Changing the language class AppHelper extends Helper { Public function url($url = null, $full = false) { if (!empty($url) && !is_array($url) && $url[0] == '/') { $urlRoute = Router::parse($url); if (!empty($urlRoute['controller'])) { $url = array_merge(array_intersect_key($urlRoute, array_flip(array('admin', 'controller', 'action', 'plugin'))), $urlRoute['pass'], $urlRoute['named']); } } if (is_array($url)) { if (!isset($url['lang']) && Configure::read('Config.language') != Configure::read('CakeFest.defaultLanguage')) { $url['lang'] = Configure::read('Config.language'); } } return parent::url($url, $full); } }
  • 17. Caching i18n elements $this->element('news', array('cache' => array( 'time' => '+1 day', 'key' => $currentLanguage ))); tmp/cache/views element_eng_news element_spa_news
  • 18. And we are done Questions?