Can Yiic message extract string from a table?

Trying to figure out how to do automation of extracting strings from tables.

yiic message can only extract strings from php files with Yii::t() command. I tried putting variables in like Yii:t(‘default’, $data->name) where $data is a model, but yiic message won’t retrieve it.

I search and keep coming up with CDbMessageSource. But that’s not what I want. It just stores the messages and translation into a db instead of a file.

Can anyone help?

[color="#006400"] /* moved to proper forum */[/color]

Oops! Sorry if I posted to a wrong forum, I thought I posted it in the General Discussions. Where did I post it into?

I don’t yet have the time to post the full code, but I think I found a workaround, so far it’s working alright

Let’s say I have a Product Model class.

Here is what I did:

*edit yiic message config file to exclude view files that are using Yii::t() to output strings from Product table fields;

*subclass CActiveRecord to a new class and make the Product model class extends the new class instead of CActiveRecord

in the new class (declared in the components folder):

*add a property array transltionaFields[] to store fields that need translation


*modify beforeSave event to pull old data from the fields and save to an array (if the record is not a new one)


*

modeName = strtolower($this->getModelName());

(in afterSave())

*modfiy afterSave event to append 

<?php echo Yii::t($modelName,$this->field); ?>

of all data of the translationFields[] to a php file whose name is $modelName AFTER I find and delete the entries of the old data (compare to the old data array) from the file. This is so that only the newest data is kept.

*then run yiic message in afterSave to scan the resulting file

This way, every time a new record is created or and old record is modified, there will always be a php file for yiic message to collect all data for translation. Then all my client has to do is to get the yiic message files and fill in the translations to have the site go multi-lingual

Not sure if this is the best way, but it is for me for the moment. I’m pretty new to Yii, so not sure if there is any significant downside to this…

I discovered CActiveRecordBehavior can be used.

So my model now stayed as an extension to CActiveRecord class. Just need to the below code to add behavior:




   	function behaviors() {

          parent::behaviors();

            return array(

            // Classname => path to Class

            'ActiveRecordMultilingualBehavior'=> array(

                'class' => 'application.behaviors.ActiveRecordMultilingualBehavior',

                'languageFields' =>array('location', 'title','description'),

                'staticStrings' => array(),

              )

            );

        }

Here is the behavior class code:







<?php

/* Scans the database entry and output a fake view files (db_strings.php in this case)

 * with <?php echo Yii::t('model class name', $data) ?> entries and run

 * yiic message so that yiic meessga can scan the fake view file and 

 * produce corresponding message files for translation

 */


class ActiveRecordMultilingualBehavior extends CActiveRecordBehavior

{

        //store old model value in beforeSave

        protected $formerStrings = array();

                

        protected $modelName;

        

        /*@type array

         * stores fields that need translation

         */

        public $languageFields;

        

        /*@type array

         * stores static strings that needs translation where in some cases

         * view files have to be excluded from yii messsage scan.

         */

        public $staticStrings;

        

        

        public function beforeSave($event) {

          $this->modelName = strtolower(get_class($this->Owner));

          //if it's not a rew record, save the old values to delete in afterSave()

          if(!$this->Owner->isNewRecord){

            $current = $this->Owner->findByPk($this->Owner->id);

            

            foreach($this->languageFields as $field){

              $string = '<?php echo Yii::t(\''.$this->modelName.'\', \''.CHtml::encode($current->$field).'\'); ?>'.PHP_EOL;

              $current->$field = CHtml::encode($current->$field);

              $this->formerStrings[]= $string;

            }


          }

          parent::beforeSave($event);

        }


        public function afterSave($event) {

          parent::afterSave($event);

          /**

           * the db_string.php is a fake view file of the class 

           * for yiic message to crawl through to produce a message file

           */

           $dbStringFile = Yii::app()->themeManager->basePath. DIRECTORY_SEPARATOR .Yii::app()->theme->getName().'/views/'.$this->modelName.'/db_strings.php';

          //get the db_string file into an array          

          if (file_exists($dbStringFile)){ 

            $lines = file($dbStringFile);

          } else {

            $lines = array();

          }

          

          $lines = $this->mergeMultilines($lines);


          //remove old strings if they exists (this forces update even if 

          //there is no change

          foreach($this->formerStrings as $formerString){

            foreach($lines as $key => $line){

               if ($line == $formerString){

                 unset($lines[$key]);

               }

             }

             //$lines = str_replace($formerString, '', $lines);

          }

          /* staticString is needed because the view file with Yii::t() function that uses

           * a variable for the second parameter sometimes need to be excluded from the yiic message

           * scan */

          foreach($this->staticStrings as $static){

            $lines = str_replace('<?php echo Yii::t(\''.$this->modelName.'\', \''.$static.'\'); ?>'.PHP_EOL, '', $lines);

            $string = '<?php echo Yii::t(\''.$this->modelName.'\', \''.$static.'\'); ?>'.PHP_EOL;

            array_push($lines, $string);

          }

          //add all data that need translation

          foreach($this->languageFields as $field){

            $string = '<?php echo Yii::t(\''.$this->modelName.'\', \''.$this->Owner->$field.'\'); ?>'.PHP_EOL;

            array_push($lines, $string);

          }

          //sort the array

//          asort($lines);

          $newStrings = implode('', $lines);

          //output

          $handle = fopen($dbStringFile, 'w') or die("Error! Can't open file");

          fwrite($handle, $newStrings);         

          fclose($handle);

          

          //run yiic message command

          $this->runMessageTool();

          

        }

        

        public function beforeDelete($event) {

          $this->modelName = strtolower(get_class($this->Owner));

          //load fake view file

           $dbStringFile = Yii::app()->themeManager->basePath. DIRECTORY_SEPARATOR .Yii::app()->theme->getName().'/views/'.$this->modelName.'/db_strings.php';

          //get the db_string file into an array          

          if (file_exists($dbStringFile)){ 

            $lines = file($dbStringFile);

           //gather current record data before delete

           $current = $this->Owner->findByPk($this->Owner->id);

           foreach($this->languageFields as $field){

              $string = '<?php echo Yii::t(\''.$this->modelName.'\', \''.$current->$field.'\'); ?>'.PHP_EOL;

              $this->formerStrings[]= $string;

           }

           //merge multilines

           $lines = $this->mergeMultilines($lines);

           //remove current data entries from the fake view file

           foreach($this->formerStrings as $formerString){

             foreach($lines as $key => $line){

               if ($line == $formerString){

                 unset($lines[$key]);

               }

             }

           }

          } else {

            $lines = array();

          }

//          asort($lines);

          $newStrings = implode('', $lines);

          //output

          $handle = fopen($dbStringFile, 'w') or die("Error! Can't open file");

          fwrite($handle, $newStrings);         

          fclose($handle);

          

          //run yiic message command

          $this->runMessageTool();

          

          parent::beforeDelete($event);

        }

        

        private function mergeMultilines($lines){

          foreach($lines as $key => $line){

            /* if the entry is the start of a multiline entry (does not end with ?>\n)

             * keep it to concatenate into the next line until finding

             * the end of the multiline entry

             */

            if(preg_match('/\?>$/', $line)!=1){

             //append to $multilineArray and starting from second line

             $multilineArray[] = $line;

             //remove the copied element in $lines

             unset($lines[$key]);

            }

            

            /*if end of multiline entry 

             * (more than 1 mulitline entry in the array and the line ends with ?>)

             */            

            if(isset($multilineArray) && (count($multilineArray))>=1 && (preg_match("/\?>$/", $line)==1)){

              //append the line into the multilineArray

              $multilineArray[]=$line;

              //replace the line with the $multiline implode into a single string

              $multilineString = implode('', $multilineArray);

              //update $lines

              $lines = str_replace($line, $multilineString, $lines);

              //reset multilineArray for the next entry

              $multilineArray = array();

            }

            

          }

          return $lines;

        }

        

        private function runMessageTool() {

            $runner = new CConsoleCommandRunner();

            $commandPath = Yii::getFrameworkPath() . DIRECTORY_SEPARATOR . 'cli' . DIRECTORY_SEPARATOR . 'commands';

            $runner->addCommands($commandPath);

            $args = array('yiic', 'message', Yii::app()->getBasePath(). DIRECTORY_SEPARATOR .'messages'. DIRECTORY_SEPARATOR .'multiline_config.php');

            ob_start();

            $runner->run($args);

        }

}?>

So far seems to work. Not extensively tested yet.

Well, this turned out to be useless… it seems if the translation key is multiline, Yii refuse to translate. It does work if the key is only a single line and the translation is a multiline one…

On further testing I found something strange. I setup a dummy Model Multiline with just a description field (TEXT) with only one record. In a view file, I have a static Yii::t multi-line entry and a dynamic Yii::t echo statement. The data in the description is:




line 1

line2

I put together a view file with a static Yii::t command:




<?php echo Yii::t('messages', 'line 1

line2');?>

And a dynamic Yii::t multi-line command:


<?php echo Yii::t('messages', $data->description); ?>

where $data->description pulls




line 1

line2

out from the database.

These two Yiic::t are on the same view file, when running through the yiic message with the controller behavior I mentioned before. I get a fake db_string.php file as intended that looks like this:




<p><?php echo Yii::t('multiline', 'record2

line2'); ?></p>

and the multiline.php message file looks like this:




<?php

/**

 * Message translations.

 *

 * This file is automatically generated by 'yiic message' command.

 * It contains the localizable messages extracted from source code.

 * You may modify this file by translating the extracted messages.

 *

 * Each array element represents the translation (value) of a message (key).

 * If the value is empty, the message is considered as not translated.

 * Messages that no longer need translation will have their translations

 * enclosed between a pair of '@@' marks.

 *

 * Message string can be used with plural forms format. Check i18n section

 * of the guide for details.

 *

 * NOTE, this file must be saved in UTF-8 encoding.

 *

 * @version $Id: $

 */

return array (

  'record2

line2' => '',

);

If I enter a value, for example, in Chinese like below and perform a language switch:




return array (

  'record2

line2' => '測試',

)

Then the static one translation works but not the dynamic one.

I then trigger another yiic message by running an update on the model. The db_strings.php file stays the same, but the multiline.php message file now becomes:




<?php

/**

 * Message translations.

 *

 * This file is automatically generated by 'yiic message' command.

 * It contains the localizable messages extracted from source code.

 * You may modify this file by translating the extracted messages.

 *

 * Each array element represents the translation (value) of a message (key).

 * If the value is empty, the message is considered as not translated.

 * Messages that no longer need translation will have their translations

 * enclosed between a pair of '@@' marks.

 *

 * Message string can be used with plural forms format. Check i18n section

 * of the guide for details.

 *

 * NOTE, this file must be saved in UTF-8 encoding.

 *

 * @version $Id: $

 */

return array (

  'record2

line2' => '',

  'record2

line2' => '@@@@',

);

I checked for invisible characters and both strings keys are identical! Why is one being identified as not used? What’s going on?

To narrow down the problem, I started over (clean db_strings.php and multiline.php message file). I removed the static Yii::t entry from the view and run update to trigger the yiic message. And sure enough, after two updates, I got the same result (two duplicated entries and one identified as not used).

Can anyone help? I’m really stumped. It seems so close yet I just don’t get it…