Kohana3: automatically collect internationalization strings

I started implementing i18n for my upcoming KO3 application, and implemented a quick patch so that I don’t need to manually find and type translation strings.

What this code does

What the code below does is it checks whether the translation string exists, if not then it saves it into the translation file with the English equivalent. This updated version of the translation string file is saved into /application/i18n/languagename.php, and the old file is saved with a new name containing the current date and time.

Hope this helps!

How to set it up

First set the language using i18n::lang(‘xy-xx’) in bootstrap.php.

Also, add the following as the last line in bootstrap.php:

// Write the updated language file, if necessary
i18n::write();

Finally, add the file /application/classes/i18n.php which overrides i18n::get():

<?php
/**
 * A patch for the Internationalization (i18n) class.
 *
 * @package    I18n
 * @author Mikito Takada
 */
class I18n extends Kohana_I18n {
   // Cache of missing strings
   protected static $_cache_missing = array();
   /**
    * Returns translation of a string. If no translation exists, the original
    * string will be returned.
    *
    * @param   string   text to translate
    * @return  string
    */
   public static function get($string)
   {
      if ( ! isset(I18n::$_cache[I18n::$lang]))
      {
         // Load the translation table
         I18n::load(I18n::$lang);
      }
      // Return the translated string if it exists
      if(isset(I18n::$_cache[I18n::$lang][$string]))
      {         
         return I18n::$_cache[I18n::$lang][$string];
      } else {
         // Translated string does not exist
         // Store the original string as missing - still makes sense to store the English string so that loading the untranslated file will work.
         I18n::$_cache_missing[I18n::$lang][$string] = $string;
         return $string;
      }
   }
 
   public static function write()
   {
      // something new must be added for anything to happen
      if(!empty(I18n::$_cache_missing)) {
         $contents = '<?php defined(\'SYSPATH\') or die(\'No direct script access.\');
/**
 * Translation file in language: '.I18n::$lang.'
 * Automatically generated from previous translation file.
 */
return '.var_export(array_merge(I18n::$_cache_missing[I18n::$lang], I18n::$_cache[I18n::$lang]), true).';';
 
         // save string to file
         $savepath = APPPATH.'/i18n/';
         $filename = I18n::$lang.'.php';
         // check that the path exists
         if(!file_exists($savepath)) {
            // if not, create directory
            mkdir($savepath, 0777, true);
         }
         // rename the old file - if the file size is different.
         if(file_exists($savepath.$filename) && ( filesize($savepath.$filename) != strlen($contents) ) ) {
            $result = rename($savepath.$filename, $savepath.I18n::$lang.'_'.date('Y_m_d_H_i_s').'.php');
            if(!$result) {
               // Rename failed! Don't write the file.
               return;
            }
         }
         // save the file
         file_put_contents($savepath.$filename, $contents);
      }
   }
}

Caveats and notes

There are two things that have to be taken into account:

  • First, this is would obviously be inefficient for a production site, since actual files are being rewritten on each request that finds new translation strings.
  • Why this is not a problem: My recommendation is that you shouldn’t run this code in production mode, since there is no point and it is very easy to remove the code after developement is completed.
  • Second, this approach is less comprehensive than using something like the gettext tools that are available – those tools scan all of the source code, while my approach depends on run-time detection of new strings. This means that a small percentage of strings will not be found automatically (ex. rare errors that never get triggered).
  • Why this is not a problem: This approach will still get the vast majority of the strings without requiring any manual hunting for strings, so I think it’ll save you quite a bit of time.

Leave a comment