2008-10-16 22:42:41 +02:00
< ? php
/**
2008-11-01 14:26:08 +01:00
* SilverStripe - variant of the " gettext " tool :
* Parses the string content of all PHP - files and SilverStripe templates
* for ocurrences of the _t () translation method . Also uses the { @ link i18nEntityProvider }
* interface to get dynamically defined entities by executing the
* { @ link provideI18nEntities ()} method on all implementors of this interface .
*
* Collects all found entities ( and their natural language text for the default locale )
* into language - files for each module in an array notation . Creates or overwrites these files ,
2012-03-24 04:38:57 +01:00
* e . g . framework / lang / en_US . php .
2008-11-01 14:26:08 +01:00
*
* The collector needs to be run whenever you make new translatable
* entities available . Please don ' t alter the arrays in language tables manually .
*
2010-11-18 20:00:13 +01:00
* Usage through URL : http :// localhost / dev / tasks / i18nTextCollectorTask
2009-11-10 22:50:27 +01:00
* Usage through URL ( module - specific ) : http :// localhost / dev / tasks / i18nTextCollectorTask / ? module = mymodule
* Usage on CLI : sake dev / tasks / i18nTextCollectorTask
* Usage on CLI ( module - specific ) : sake dev / tasks / i18nTextCollectorTask module = mymodule
2008-11-01 14:26:08 +01:00
*
2008-10-16 22:42:41 +02:00
* @ author Bernat Foj Capell < bernat @ silverstripe . com >
2008-10-17 17:21:33 +02:00
* @ author Ingo Schommer < FIRSTNAME @ silverstripe . com >
2012-04-12 08:02:46 +02:00
* @ package framework
2008-11-01 14:26:08 +01:00
* @ subpackage i18n
* @ uses i18nEntityProvider
* @ uses i18n
2008-10-16 22:42:41 +02:00
*/
2009-11-10 22:50:27 +01:00
class i18nTextCollector extends Object {
2008-10-16 22:42:41 +02:00
protected $defaultLocale ;
2008-10-17 17:21:33 +02:00
/**
* @ var string $basePath The directory base on which the collector should act .
* Usually the webroot set through { @ link Director :: baseFolder ()} .
* @ todo Fully support changing of basePath through { @ link SSViewer } and { @ link ManifestBuilder }
*/
public $basePath ;
2012-04-14 00:16:30 +02:00
public $baseSavePath ;
2008-10-17 17:21:33 +02:00
/**
2012-04-14 00:16:30 +02:00
* @ var i18nTextCollector_Writer
2008-10-17 17:21:33 +02:00
*/
2012-04-14 00:16:30 +02:00
protected $writer ;
2008-10-17 17:21:33 +02:00
2008-10-16 22:42:41 +02:00
/**
* @ param $locale
*/
function __construct ( $locale = null ) {
2012-04-14 00:16:30 +02:00
$this -> defaultLocale = ( $locale ) ? $locale : i18n :: get_lang_from_locale ( i18n :: default_locale ());
2008-10-17 17:21:33 +02:00
$this -> basePath = Director :: baseFolder ();
$this -> baseSavePath = Director :: baseFolder ();
2008-10-16 22:42:41 +02:00
parent :: __construct ();
}
2012-04-14 00:16:30 +02:00
public function setWriter ( $writer ) {
$this -> writer = $writer ;
}
public function getWriter () {
2012-04-14 01:01:59 +02:00
if ( ! $this -> writer ) $this -> writer = new i18nTextCollector_Writer_RailsYaml ();
2012-04-14 00:16:30 +02:00
return $this -> writer ;
}
2008-10-16 22:42:41 +02:00
/**
* This is the main method to build the master string tables with the original strings .
* It will search for existent modules that use the i18n feature , parse the _t () calls
* and write the resultant files in the lang folder of each module .
*
* @ uses DataObject -> collectI18nStatics ()
2009-01-05 07:19:48 +01:00
*
* @ param array $restrictToModules
2008-10-16 22:42:41 +02:00
*/
2009-01-05 07:19:48 +01:00
public function run ( $restrictToModules = null ) {
2008-10-20 14:39:49 +02:00
//Debug::message("Collecting text...", false);
2008-10-16 22:42:41 +02:00
2012-07-25 01:39:39 +02:00
$modules = scandir ( $this -> basePath );
2010-11-18 20:00:13 +01:00
$themeFolders = array ();
2009-01-05 07:19:48 +01:00
2008-10-16 22:42:41 +02:00
// A master string tables array (one mst per module)
2008-10-17 17:21:33 +02:00
$entitiesByModule = array ();
2008-10-16 22:42:41 +02:00
2010-11-18 20:00:13 +01:00
foreach ( $modules as $index => $module ){
if ( $module != 'themes' ) continue ;
else {
$themes = scandir ( $this -> basePath . " /themes " );
if ( count ( $themes )){
foreach ( $themes as $theme ) {
if ( is_dir ( $this -> basePath . " /themes/ " . $theme ) && substr ( $theme , 0 , 1 ) != '.' && is_dir ( $this -> basePath . " /themes/ " . $theme . " /templates " )){
$themeFolders [] = 'themes/' . $theme ;
}
}
}
$themesInd = $index ;
}
}
if ( isset ( $themesInd )) {
unset ( $modules [ $themesInd ]);
}
$modules = array_merge ( $modules , $themeFolders );
2008-10-17 17:21:33 +02:00
foreach ( $modules as $module ) {
2010-11-18 20:00:13 +01:00
// Only search for calls in folder with a _config.php file (which means they are modules, including themes folder)
2008-10-17 17:21:33 +02:00
$isValidModuleFolder = (
is_dir ( " $this->basePath / $module " )
&& is_file ( " $this->basePath / $module /_config.php " )
&& substr ( $module , 0 , 1 ) != '.'
2010-11-18 20:00:13 +01:00
) || (
substr ( $module , 0 , 7 ) == 'themes/'
&& is_dir ( " $this->basePath / $module " )
2008-10-17 17:21:33 +02:00
);
2010-11-18 20:00:13 +01:00
2008-10-17 17:21:33 +02:00
if ( ! $isValidModuleFolder ) continue ;
// we store the master string tables
2009-01-05 07:19:48 +01:00
$processedEntities = $this -> processModule ( $module );
2010-10-15 03:19:58 +02:00
2009-01-05 07:19:48 +01:00
if ( isset ( $entitiesByModule [ $module ])) {
$entitiesByModule [ $module ] = array_merge_recursive ( $entitiesByModule [ $module ], $processedEntities );
} else {
$entitiesByModule [ $module ] = $processedEntities ;
}
// extract all entities for "foreign" modules (fourth argument)
foreach ( $entitiesByModule [ $module ] as $fullName => $spec ) {
2012-04-27 10:11:38 +02:00
if ( isset ( $spec [ 2 ]) && $spec [ 2 ] && $spec [ 2 ] != $module ) {
$othermodule = $spec [ 2 ];
2009-01-05 07:19:48 +01:00
if ( ! isset ( $entitiesByModule [ $othermodule ])) $entitiesByModule [ $othermodule ] = array ();
2012-04-27 10:11:38 +02:00
unset ( $spec [ 2 ]);
2009-01-05 07:19:48 +01:00
$entitiesByModule [ $othermodule ][ $fullName ] = $spec ;
unset ( $entitiesByModule [ $module ][ $fullName ]);
}
2010-10-15 03:19:58 +02:00
}
2008-10-16 22:42:41 +02:00
}
2009-01-05 07:19:48 +01:00
2012-07-25 01:39:39 +02:00
// Restrict modules we update to just the specified ones (if any passed)
if ( $restrictToModules && count ( $restrictToModules )) {
foreach ( array_diff ( array_keys ( $entitiesByModule ), $restrictToModules ) as $module ) {
unset ( $entitiesByModule [ $module ]);
}
}
2012-04-14 00:16:30 +02:00
// Write each module language file
if ( $entitiesByModule ) foreach ( $entitiesByModule as $module => $entities ) {
$this -> getWriter () -> write ( $entities , $this -> defaultLocale , $this -> baseSavePath . '/' . $module );
}
2008-10-16 22:42:41 +02:00
}
/**
2012-05-18 02:32:12 +02:00
* Builds a master string table from php and . ss template files for the module passed as the $module param
2012-06-20 23:59:16 +02:00
* @ see collectFromCode () and collectFromTemplate ()
2008-10-16 22:42:41 +02:00
*
2012-05-18 02:32:12 +02:00
* @ param string $module A module 's name or just ' themes '
* @ return array $entities An array of entities found in the files that comprise the module
* @ todo Why the type juggling for $this -> collectFromBlah () ? They always return arrays .
2008-10-16 22:42:41 +02:00
*/
2008-10-17 17:21:33 +02:00
protected function processModule ( $module ) {
2012-04-14 00:16:30 +02:00
$entities = array ();
2008-10-16 22:42:41 +02:00
2008-10-17 17:21:33 +02:00
// Search for calls in code files if these exists
2012-05-01 22:05:54 +02:00
$fileList = array ();
2008-10-17 17:21:33 +02:00
if ( is_dir ( " $this->basePath / $module /code " )) {
$fileList = $this -> getFilesRecursive ( " $this->basePath / $module /code " );
2012-03-24 04:38:57 +01:00
} else if ( $module == FRAMEWORK_DIR || substr ( $module , 0 , 7 ) == 'themes/' ) {
// framework doesn't have the usual module structure, so we'll scan all subfolders
2008-10-17 17:21:33 +02:00
$fileList = $this -> getFilesRecursive ( " $this->basePath / $module " );
}
foreach ( $fileList as $filePath ) {
// exclude ss-templates, they're scanned separately
if ( substr ( $filePath , - 3 ) == 'php' ) {
$content = file_get_contents ( $filePath );
2012-04-14 00:16:30 +02:00
$entities = array_merge ( $entities ,( array ) $this -> collectFromCode ( $content , $module ));
$entities = array_merge ( $entities , ( array ) $this -> collectFromEntityProviders ( $filePath , $module ));
2008-10-16 22:42:41 +02:00
}
2008-10-17 17:21:33 +02:00
}
// Search for calls in template files if these exists
2012-05-03 17:45:25 +02:00
if ( is_dir ( " $this->basePath / $module / " )) {
$dummy = array ();
$fileList = $this -> getFilesRecursive ( " $this->basePath / $module / " , $dummy , 'ss' );
2008-10-17 17:21:33 +02:00
foreach ( $fileList as $index => $filePath ) {
$content = file_get_contents ( $filePath );
// templates use their filename as a namespace
$namespace = basename ( $filePath );
2012-04-14 00:16:30 +02:00
$entities = array_merge ( $entities , ( array ) $this -> collectFromTemplate ( $content , $module , $namespace ));
2008-10-16 22:42:41 +02:00
}
2008-10-17 17:21:33 +02:00
}
// sort for easier lookup and comparison with translated files
2012-04-14 00:16:30 +02:00
ksort ( $entities );
2008-10-17 17:21:33 +02:00
2012-04-14 00:16:30 +02:00
return $entities ;
2008-10-16 22:42:41 +02:00
}
2012-04-16 07:24:48 +02:00
2012-05-18 02:32:12 +02:00
/**
* Extracts translatables from . php files .
*
* @ param string $content The text content of a parsed template - file
* @ param string $module Module 's name or ' themes '
* @ return array $entities An array of entities representing the extracted translation function calls in code
*/
2012-04-18 07:23:57 +02:00
public function collectFromCode ( $content , $module ) {
$entities = array ();
2012-04-16 07:24:48 +02:00
2012-04-18 07:23:57 +02:00
$tokens = token_get_all ( " <?php \n " . $content );
$inTransFn = false ;
$inConcat = false ;
$finalTokenDueToArray = false ;
$currentEntity = array ();
foreach ( $tokens as $token ) {
if ( is_array ( $token )) {
list ( $id , $text ) = $token ;
if ( $inTransFn && $id == T_ARRAY ) {
//raw 'array' token found in _t function, stop processing the tokens for this _t now
$finalTokenDueToArray = true ;
2012-04-16 07:24:48 +02:00
}
2012-04-18 07:23:57 +02:00
if ( $id == T_STRING && $text == '_t' ) {
// start definition
$inTransFn = true ;
} elseif ( $inTransFn && $id == T_VARIABLE ) {
// Dynamic definition from provideEntities - skip
$inTransFn = false ;
$inConcat = false ;
$currentEntity = array ();
} elseif ( $inTransFn && $id == T_CONSTANT_ENCAPSED_STRING ) {
// Fixed quoting escapes, and remove leading/trailing quotes
if ( preg_match ( '/^\'/' , $text )) {
$text = str_replace ( " \ ' " , " ' " , $text );
$text = preg_replace ( '/^\'/' , '' , $text );
$text = preg_replace ( '/\'$/' , '' , $text );
} else {
$text = str_replace ( '\"' , '"' , $text );
$text = preg_replace ( '/^"/' , '' , $text );
$text = preg_replace ( '/"$/' , '' , $text );
}
2012-04-16 07:24:48 +02:00
2012-04-18 07:23:57 +02:00
if ( $inConcat ) {
$currentEntity [ count ( $currentEntity ) - 1 ] .= $text ;
} else {
$currentEntity [] = $text ;
}
2012-04-16 07:24:48 +02:00
}
2012-04-18 07:23:57 +02:00
} elseif ( $inTransFn && $token == '.' ) {
$inConcat = true ;
} elseif ( $inTransFn && $token == ',' ) {
$inConcat = false ;
} elseif ( $inTransFn && ( $token == ')' || $finalTokenDueToArray )) {
// finalize definition
$inTransFn = false ;
$inConcat = false ;
$entity = array_shift ( $currentEntity );
$entities [ $entity ] = $currentEntity ;
$currentEntity = array ();
$finalTokenDueToArray = false ;
2012-04-14 00:16:30 +02:00
}
2008-10-16 22:42:41 +02:00
}
2012-04-16 07:24:48 +02:00
2012-04-18 07:23:57 +02:00
foreach ( $entities as $entity => $spec ) {
// call without master language definition
if ( ! $spec ) {
unset ( $entities [ $entity ]);
continue ;
2012-04-16 07:24:48 +02:00
}
2012-04-18 07:23:57 +02:00
unset ( $entities [ $entity ]);
$entities [ $this -> normalizeEntity ( $entity , $module )] = $spec ;
2012-04-16 07:24:48 +02:00
}
2012-04-18 07:23:57 +02:00
ksort ( $entities );
2012-04-16 07:24:48 +02:00
2012-04-18 07:23:57 +02:00
return $entities ;
2008-10-16 22:42:41 +02:00
}
2012-05-18 02:32:12 +02:00
/**
* Extracts translatables from . ss templates ( Self referencing )
*
* @ param string $content The text content of a parsed template - file
* @ param string $module Module 's name or ' themes '
* @ param string $fileName The name of a template file when method is used in self - referencing mode
* @ return array $entities An array of entities representing the extracted template function calls
*
* @ todo Why the type juggling for $this -> collectFromTemplate () ? It always returns an array .
*/
2012-04-14 00:16:30 +02:00
public function collectFromTemplate ( $content , $fileName , $module ) {
$entities = array ();
2008-10-16 22:42:41 +02:00
// Search for included templates
2008-10-17 17:21:33 +02:00
preg_match_all ( '/<' . '% include +([A-Za-z0-9_]+) +%' . '>/' , $content , $regs , PREG_SET_ORDER );
foreach ( $regs as $reg ) {
$includeName = $reg [ 1 ];
$includeFileName = " { $includeName } .ss " ;
$filePath = SSViewer :: getTemplateFileByType ( $includeName , 'Includes' );
2009-05-25 08:59:21 +02:00
if ( ! $filePath ) $filePath = SSViewer :: getTemplateFileByType ( $includeName , 'main' );
if ( $filePath ) {
$includeContent = file_get_contents ( $filePath );
2012-04-14 00:16:30 +02:00
$entities = array_merge ( $entities ,( array ) $this -> collectFromTemplate ( $includeContent , $module , $includeFileName ));
2009-05-25 08:59:21 +02:00
}
2008-10-17 17:21:33 +02:00
// @todo Will get massively confused if you include the includer -> infinite loop
2008-10-16 22:42:41 +02:00
}
2012-04-16 07:24:48 +02:00
// use parser to extract <%t style translatable entities
$translatables = i18nTextCollector_Parser :: GetTranslatables ( $content );
$entities = array_merge ( $entities ,( array ) $translatables );
// use the old method of getting _t() style translatable entities
2012-04-14 00:16:30 +02:00
// Collect in actual template
2012-07-18 14:58:53 +02:00
if ( preg_match_all ( '/(_t\([^\)]*?\))/ms' , $content , $matches )) {
foreach ( $matches [ 1 ] as $match ) {
$entities = array_merge ( $entities , $this -> collectFromCode ( $match , $module ));
2012-04-14 00:16:30 +02:00
}
}
2012-02-27 22:14:02 +01:00
2012-04-14 00:16:30 +02:00
foreach ( $entities as $entity => $spec ) {
unset ( $entities [ $entity ]);
$entities [ $this -> normalizeEntity ( $entity , $module )] = $spec ;
2008-10-17 17:21:33 +02:00
}
2012-04-14 00:16:30 +02:00
ksort ( $entities );
2012-05-03 17:45:25 +02:00
2012-04-14 00:16:30 +02:00
return $entities ;
2008-10-17 17:21:33 +02:00
}
2008-11-02 00:16:45 +01:00
/**
* @ uses i18nEntityProvider
*/
2012-08-09 10:59:15 +02:00
function collectFromEntityProviders ( $filePath , $module = null ) {
2012-04-14 00:16:30 +02:00
$entities = array ();
2012-08-09 12:46:40 +02:00
// HACK Ugly workaround to avoid "Cannot redeclare class PHPUnit_Framework_TestResult" error
// when running text collector with PHPUnit 3.4. There really shouldn't be any dependencies
// here, but the class reflection enforces autloading of seemingly unrelated classes.
// The main problem here is the CMSMenu class, which iterates through test classes,
// which in turn trigger autoloading of PHPUnit.
$phpunitwrapper = PhpUnitWrapper :: inst ();
$phpunitwrapper -> init ();
2008-11-01 19:56:39 +01:00
$classes = ClassInfo :: classes_for_file ( $filePath );
if ( $classes ) foreach ( $classes as $class ) {
// Not all classes can be instanciated without mandatory arguments,
// so entity collection doesn't work for all SilverStripe classes currently
// Requires PHP 5.1+
2012-08-06 14:02:47 +02:00
if ( class_exists ( $class ) && in_array ( 'i18nEntityProvider' , class_implements ( $class ))) {
2008-11-02 01:29:11 +01:00
$reflectionClass = new ReflectionClass ( $class );
if ( $reflectionClass -> isAbstract ()) continue ;
2009-02-02 00:49:53 +01:00
2008-11-01 19:56:39 +01:00
$obj = singleton ( $class );
2012-04-14 00:16:30 +02:00
$entities = array_merge ( $entities ,( array ) $obj -> provideI18nEntities ());
2008-11-01 19:56:39 +01:00
}
}
2012-04-14 00:16:30 +02:00
ksort ( $entities );
return $entities ;
2008-11-01 19:56:39 +01:00
}
2008-10-17 17:21:33 +02:00
/**
2012-05-18 02:32:12 +02:00
* Normalizes enitities with namespaces .
*
* @ param string $fullName
* @ param string $_namespace
* @ return string | boolean FALSE
2008-10-17 17:21:33 +02:00
*/
2012-04-14 00:16:30 +02:00
protected function normalizeEntity ( $fullName , $_namespace = null ) {
2008-10-17 17:21:33 +02:00
// split fullname into entity parts
$entityParts = explode ( '.' , $fullName );
if ( count ( $entityParts ) > 1 ) {
// templates don't have a custom namespace
2008-10-16 22:42:41 +02:00
$entity = array_pop ( $entityParts );
2008-10-17 17:21:33 +02:00
// namespace might contain dots, so we explode
$namespace = implode ( '.' , $entityParts );
} else {
$entity = array_pop ( $entityParts );
$namespace = $_namespace ;
}
2008-10-29 22:07:17 +01:00
// If a dollar sign is used in the entity name,
// we can't resolve without running the method,
// and skip the processing. This is mostly used for
// dynamically translating static properties, e.g. looping
// through $db, which are detected by {@link collectFromEntityProviders}.
2012-04-15 21:49:51 +02:00
if ( $entity && strpos ( '$' , $entity ) !== FALSE ) return false ;
2008-10-29 22:07:17 +01:00
2012-04-14 00:16:30 +02:00
return " { $namespace } . { $entity } " ;
2008-10-17 17:21:33 +02:00
}
2008-11-01 19:56:39 +01:00
/**
2012-05-18 02:32:12 +02:00
* Helper function that searches for potential files ( templates and code ) to be parsed
2008-11-01 19:56:39 +01:00
*
* @ param string $folder base directory to scan ( will scan recursively )
2012-05-18 02:32:12 +02:00
* @ param array $fileList Array to which potential files will be appended
* @ param string $type Optional , " php " or " ss "
* @ return array $fileList An array of files
2008-11-01 19:56:39 +01:00
*/
2012-05-03 17:45:25 +02:00
protected function getFilesRecursive ( $folder , & $fileList = null , $type = null ) {
2008-11-01 19:56:39 +01:00
if ( ! $fileList ) $fileList = array ();
$items = scandir ( $folder );
$isValidFolder = (
! in_array ( '_manifest_exclude' , $items )
&& ! preg_match ( '/\/tests$/' , $folder )
);
if ( $items && $isValidFolder ) foreach ( $items as $item ) {
if ( substr ( $item , 0 , 1 ) == '.' ) continue ;
2012-05-03 17:45:25 +02:00
if ( substr ( $item , - 4 ) == '.php' && ( ! $type || $type == 'php' )) {
$fileList [ substr ( $item , 0 , - 4 )] = " $folder / $item " ;
}
else if ( substr ( $item , - 3 ) == '.ss' && ( ! $type || $type == 'ss' )) {
$fileList [ $item ] = " $folder / $item " ;
}
else if ( is_dir ( " $folder / $item " )) $this -> getFilesRecursive ( " $folder / $item " , $fileList , $type );
2008-11-01 19:56:39 +01:00
}
return $fileList ;
2008-10-17 17:21:33 +02:00
}
public function getDefaultLocale () {
return $this -> defaultLocale ;
}
public function setDefaultLocale ( $locale ) {
$this -> defaultLocale = $locale ;
2008-10-16 22:42:41 +02:00
}
}
2012-04-14 00:16:30 +02:00
/**
* Allows serialization of entity definitions collected through { @ link i18nTextCollector }
* into a persistent format , usually on the filesystem .
*/
interface i18nTextCollector_Writer {
/**
* @ param Array $entities Map of entity names ( incl . namespace ) to an numeric array ,
* with at least one element , the original string , and an optional second element , the context .
* @ param String $locale
* @ param String $path The directory base on which the collector should create new lang folders and files .
* Usually the webroot set through { @ link Director :: baseFolder ()} . Can be overwritten for testing or export purposes .
* @ return Boolean success
*/
function write ( $entities , $locale , $path );
}
/**
* Legacy writer for 2. x style persistence .
*/
class i18nTextCollector_Writer_Php implements i18nTextCollector_Writer {
public function write ( $entities , $locale , $path ) {
$php = '' ;
$eol = PHP_EOL ;
// Create folder for lang files
$langFolder = $path . '/lang' ;
if ( ! file_exists ( $langFolder )) {
Filesystem :: makeFolder ( $langFolder , Filesystem :: $folder_create_mask );
touch ( $langFolder . '/_manifest_exclude' );
}
// Open the English file and write the Master String Table
$langFile = $langFolder . '/' . $locale . '.php' ;
if ( $fh = fopen ( $langFile , " w " )) {
if ( $entities ) foreach ( $entities as $fullName => $spec ) {
$php .= $this -> langArrayCodeForEntitySpec ( $fullName , $spec , $locale );
}
// test for valid PHP syntax by eval'ing it
try {
eval ( $php );
} catch ( Exception $e ) {
throw new LogicException ( 'i18nTextCollector->writeMasterStringFile(): Invalid PHP language file. Error: ' . $e -> toString ());
}
fwrite ( $fh , " < " . " ?php { $eol } { $eol } global \$ lang; { $eol } { $eol } " . $php . " { $eol } " );
fclose ( $fh );
} else {
throw new LogicException ( " Cannot write language file! Please check permissions of $langFolder / " . $locale . " .php " );
}
return true ;
}
/**
* Input for langArrayCodeForEntitySpec () should be suitable for insertion
* into single - quoted strings , so needs to be escaped already .
*
* @ param string $entity The entity name , e . g . CMSMain . BUTTONSAVE
*/
public function langArrayCodeForEntitySpec ( $entityFullName , $entitySpec , $locale ) {
$php = '' ;
$eol = PHP_EOL ;
$entityParts = explode ( '.' , $entityFullName );
if ( count ( $entityParts ) > 1 ) {
// templates don't have a custom namespace
$entity = array_pop ( $entityParts );
// namespace might contain dots, so we implode back
$namespace = implode ( '.' , $entityParts );
} else {
user_error ( " i18nTextCollector::langArrayCodeForEntitySpec(): Wrong entity format for $entityFullName with values " . var_export ( $entitySpec , true ), E_USER_WARNING );
return false ;
}
2012-04-15 21:49:51 +02:00
2012-04-14 00:16:30 +02:00
$value = $entitySpec [ 0 ];
$comment = ( isset ( $entitySpec [ 1 ])) ? addcslashes ( $entitySpec [ 1 ], '\'' ) : null ;
$php .= '$lang[\'' . $locale . '\'][\'' . $namespace . '\'][\'' . $entity . '\'] = ' ;
$php .= ( count ( $entitySpec ) == 1 ) ? var_export ( $entitySpec [ 0 ], true ) : var_export ( $entitySpec , true );
$php .= " ; $eol " ;
return $php ;
}
}
2012-04-14 01:01:59 +02:00
/**
* Writes files compatible with { @ link i18nRailsYamlAdapter } .
*/
class i18nTextCollector_Writer_RailsYaml implements i18nTextCollector_Writer {
public function write ( $entities , $locale , $path ) {
$content = '' ;
// Create folder for lang files
$langFolder = $path . '/lang' ;
if ( ! file_exists ( $langFolder )) {
Filesystem :: makeFolder ( $langFolder , Filesystem :: $folder_create_mask );
touch ( $langFolder . '/_manifest_exclude' );
}
// Open the English file and write the Master String Table
$langFile = $langFolder . '/' . $locale . '.yml' ;
if ( $fh = fopen ( $langFile , " w " )) {
fwrite ( $fh , $this -> getYaml ( $entities , $locale ));
fclose ( $fh );
} else {
throw new LogicException ( " Cannot write language file! Please check permissions of $langFile " );
}
return true ;
}
public function getYaml ( $entities , $locale ) {
2012-04-17 23:34:57 +02:00
// Use the Zend copy of this script to prevent class conflicts when RailsYaml is included
require_once 'thirdparty/zend_translate_railsyaml/library/Translate/Adapter/thirdparty/sfYaml/lib/sfYamlDumper.php' ;
2012-04-14 01:01:59 +02:00
// Unflatten array
$entitiesNested = array ();
foreach ( $entities as $entity => $spec ) {
// Legacy support: Don't count *.ss as namespace
$entity = preg_replace ( '/\.ss\./' , '___ss.' , $entity );
$parts = explode ( '.' , $entity );
$currLevel = & $entitiesNested ;
while ( $part = array_shift ( $parts )) {
$part = str_replace ( '___ss' , '.ss' , $part );
if ( ! isset ( $currLevel [ $part ])) $currLevel [ $part ] = array ();
$currLevel = & $currLevel [ $part ];
}
$currLevel = $spec [ 0 ];
}
// Write YAML
$yamlHandler = new sfYaml ();
// TODO Dumper can't handle YAML comments, so the context information is currently discarded
return $yamlHandler -> dump ( array ( $locale => $entitiesNested ), 99 );
}
2012-04-16 07:24:48 +02:00
}
/**
* Parser that scans through a template and extracts the parameters to the _t and <% t calls
*/
class i18nTextCollector_Parser extends SSTemplateParser {
static $entities = array ();
static $currentEntity = array ();
function Translate__construct ( & $res ) {
self :: $currentEntity = array ( null , null , null ); //start with empty array
}
function Translate_Entity ( & $res , $sub ) {
self :: $currentEntity [ 0 ] = $sub [ 'text' ]; //entity
}
function Translate_Default ( & $res , $sub ) {
self :: $currentEntity [ 1 ] = $sub [ 'String' ][ 'text' ]; //value
}
function Translate_Context ( & $res , $sub ) {
self :: $currentEntity [ 2 ] = $sub [ 'String' ][ 'text' ]; //comment
}
function Translate__finalise ( & $res ) {
// set the entity name and the value (default), as well as the context (comment)
// priority is no longer used, so that is blank
self :: $entities [ self :: $currentEntity [ 0 ]] = array ( self :: $currentEntity [ 1 ], null , self :: $currentEntity [ 2 ]);
}
/**
* Parses a template and returns any translatable entities
*/
static function GetTranslatables ( $template ) {
self :: $entities = array ();
// Run the parser and throw away the result
$parser = new i18nTextCollector_Parser ( $template );
if ( substr ( $template , 0 , 3 ) == pack ( " CCC " , 0xef , 0xbb , 0xbf )) $parser -> pos = 3 ;
$parser -> match_TopTemplate ();
return self :: $entities ;
}
2012-04-14 01:01:59 +02:00
}