ENHANCEMENT Refactored i18nTextCollector and added unit tests

FEATURE Support for i18n entity namespaces in templates

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@64492 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2008-10-17 15:21:33 +00:00
parent 9f41917fb1
commit 874aaf1d27
10 changed files with 614 additions and 183 deletions

View File

@ -1,6 +1,7 @@
<?php <?php
/** /**
* @author Bernat Foj Capell <bernat@silverstripe.com> * @author Bernat Foj Capell <bernat@silverstripe.com>
* @author Ingo Schommer <FIRSTNAME@silverstripe.com>
* @package sapphire * @package sapphire
* @subpackage misc * @subpackage misc
*/ */
@ -8,11 +9,28 @@ class i18nTextCollector extends Object {
protected $defaultLocale; protected $defaultLocale;
/**
* @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;
/**
* @var string $basePath 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.
* @todo Fully support changing of basePath through {@link SSViewer} and {@link ManifestBuilder}
*/
public $baseSavePath;
/** /**
* @param $locale * @param $locale
*/ */
function __construct($locale = null) { function __construct($locale = null) {
$this->defaultLocale = ($locale) ? $locale : i18n::default_locale(); $this->defaultLocale = ($locale) ? $locale : i18n::default_locale();
$this->basePath = Director::baseFolder();
$this->baseSavePath = Director::baseFolder();
parent::__construct(); parent::__construct();
} }
@ -24,149 +42,109 @@ class i18nTextCollector extends Object {
* *
* @uses DataObject->collectI18nStatics() * @uses DataObject->collectI18nStatics()
*/ */
public function run($module = null) { public function run($restrictToModule = null) {
if(Director::is_cli()) { Debug::message("Collecting text...", false);
echo "Collecting text...\n";
} else {
echo "Collecting text...<br /><br />";
}
//Calculate base directory
$baseDir = Director::baseFolder();
// A master string tables array (one mst per module) // A master string tables array (one mst per module)
$mst = array(); $entitiesByModule = array();
// A list of included templates dependencies
$includedtpl = array();
//Search for and process existent modules, or use the passed one instead //Search for and process existent modules, or use the passed one instead
if (!isset($module)) { $modules = (isset($restrictToModule)) ? array(basename($restrictToModule)) : scandir($this->basePath);
$topLevel = scandir($baseDir);
foreach($topLevel as $module) { foreach($modules as $module) {
// Only search for calls in folder with a _config.php file (which means they are modules)
$isValidModuleFolder = (
is_dir("$this->basePath/$module")
&& is_file("$this->basePath/$module/_config.php")
&& substr($module,0,1) != '.'
);
if(!$isValidModuleFolder) continue;
// we store the master string tables // we store the master string tables
$processed = $this->processModule($baseDir, $module, $includedtpl); $entitiesByModule[$module] = $this->processModule($module);
if ($processed) $mst[$module] = $processed;
}
} else {
$module = basename($module);
$processed = $this->processModule($baseDir, $module, $includedtpl);
if ($processed) $mst[$module] = $processed;
} }
// Write the generated master string tables // Write the generated master string tables
$this->writeMasterStringFile($baseDir, $mst, $includedtpl); $this->writeMasterStringFile($entitiesByModule);
echo "Done!\n"; Debug::message("Done!", false);
} }
/** /**
* Build the module's master string table * Build the module's master string table
* *
* @param string $baseDir Silverstripe's base directory
* @param string $module Module's name * @param string $module Module's name
* @return string Generated master string table
*/ */
protected function processModule($baseDir, $module) { protected function processModule($module) {
$entitiesArr = array();
// Only search for calls in folder with a _config.php file (which means they are modules)
if(
is_dir("$baseDir/$module")
&& is_file("$baseDir/$module/_config.php")
&& substr($module,0,1) != '.'
) {
Debug::message("Processing Module '{$module}'", false); Debug::message("Processing Module '{$module}'", false);
$mst = '';
// Search for calls in code files if these exists // Search for calls in code files if these exists
if(is_dir("$baseDir/$module/code")) { if(is_dir("$this->basePath/$module/code")) {
$fileList = $this->getFilesRecursive("$baseDir/$module/code"); $fileList = $this->getFilesRecursive("$this->basePath/$module/code");
foreach($fileList as $file) {
if(substr($file,-3) == '.php') $mst .= $this->collectFromCode($file);
}
} else if('sapphire' == $module) { } else if('sapphire' == $module) {
// sapphire doesn't have the usual module structure, so we'll scan all subfolders // sapphire doesn't have the usual module structure, so we'll scan all subfolders
$fileList = $this->getFilesRecursive("$baseDir/$module"); $fileList = $this->getFilesRecursive("$this->basePath/$module");
foreach($fileList as $file) { }
foreach($fileList as $filePath) {
// exclude ss-templates, they're scanned separately // exclude ss-templates, they're scanned separately
if(substr($file,-3) == '.php') $mst .= $this->collectFromCode($file); if(substr($filePath,-3) == 'php') {
$content = file_get_contents($filePath);
$entitiesArr = array_merge($entitiesArr,(array)$this->collectFromCode($content, $module));
//$entitiesArr = array_merge($entitiesArr, (array)$this->collectFromStatics($filePath, $module));
} }
} }
// Search for calls in template files if these exists // Search for calls in template files if these exists
if(is_dir("$baseDir/$module/templates")) { if(is_dir("$this->basePath/$module/templates")) {
$includedtpl[$module] = array(); $fileList = $this->getFilesRecursive("$this->basePath/$module/templates");
$fileList = $this->getFilesRecursive("$baseDir/$module/templates"); foreach($fileList as $index => $filePath) {
foreach($fileList as $index => $file) { $content = file_get_contents($filePath);
$mst .= $this->collectFromTemplates($index, $file, $includedtpl[$module]); // templates use their filename as a namespace
$namespace = basename($filePath);
$entitiesArr = array_merge($entitiesArr, (array)$this->collectFromTemplate($content, $module, $namespace));
} }
} }
return $mst; // sort for easier lookup and comparison with translated files
asort($entitiesArr);
} else return false; return $entitiesArr;
} }
/** /**
* Write the master string table of every processed module * Write the master string table of every processed module
*
* @param string $baseDir Silverstripe's base directory
* @param array $allmst Module's master string tables
* @param array $includedtpl Templates included by other templates
*/ */
protected function writeMasterStringFile($baseDir, $allmst, $includedtpl) { protected function writeMasterStringFile($entitiesByModule) {
// Evaluate the constructed mst $php = '';
foreach($allmst as $mst) eval($mst);
// Resolve template dependencies
foreach($includedtpl as $tplmodule => $includers) {
// Variable initialization
$stringsCode = '';
$moduleCode = '';
$modulestoinclude = array();
foreach($includers as $includertpl => $allincluded)
foreach($allincluded as $included)
// we will only add code if the included template has localizable strings
if(isset($lang[$this->defaultLocale]["$included.ss"])) {
$module = i18n::get_owner_module("$included.ss");
/* if the module of the included template is not the same as the includer's one
* we will need to load the first one in order to have these included strings in memory
*/
if ($module != $tplmodule) $modulestoinclude[$module] = $included;
// Give the includer name to the included strings in order to be used from the includer template
$stringsCode .= "\$lang['" . $this->defaultLocale . "']['$includertpl'] = " .
"array_merge(\$lang['" . $this->defaultLocale . "']['$includertpl'], \$lang['" . $this->defaultLocale . "']['$included.ss']);\n";
}
// Include a template for every needed module (the module language file will then be autoloaded)
foreach($modulestoinclude as $tpltoinclude) $moduleCode .= "self::include_by_class('$tpltoinclude.ss');\n";
// Add the extra code to the existing module mst
if ($stringsCode) $allmst[$tplmodule] .= "\n$moduleCode$stringsCode";
}
// Write each module language file // Write each module language file
foreach($allmst as $module => $mst) { if($entitiesByModule) foreach($entitiesByModule as $module => $entities) {
// Create folder for lang files // Create folder for lang files
$langFolder = $baseDir . '/' . $module . '/lang'; $langFolder = $this->baseSavePath . '/' . $module . '/lang';
if(!file_exists($baseDir. '/' . $module . '/lang')) { if(!file_exists($this->baseSavePath. '/' . $module . '/lang')) {
mkdir($langFolder, Filesystem::$folder_create_mask); Filesystem::makeFolder($langFolder, Filesystem::$folder_create_mask);
touch($baseDir. '/' . $module . '/lang/_manifest_exclude'); touch($this->baseSavePath. '/' . $module . '/lang/_manifest_exclude');
} }
// Open the English file and write the Master String Table // Open the English file and write the Master String Table
if($fh = fopen($langFolder . '/' . $this->defaultLocale . '.php', "w")) { if($fh = fopen($langFolder . '/' . $this->defaultLocale . '.php', "w")) {
fwrite($fh, "<?php\n\nglobal \$lang;\n\n" . $mst . "\n?>"); if($entities) foreach($entities as $fullName => $spec) {
fclose($fh); $php .= $this->langArrayCodeForEntitySpec($fullName, $spec);
if(Director::is_cli()) {
echo "Created file: $langFolder/" . $this->defaultLocale . ".php\n";
} else {
echo "Created file: $langFolder/" . $this->defaultLocale . ".php<br />";
} }
// test for valid PHP syntax by eval'ing it
try{
//eval($php);
} catch(Exception $e) {
user_error('i18nTextCollector->writeMasterStringFile(): Invalid PHP language file. Error: ' . $e->toString(), E_USER_ERROR);
}
fwrite($fh, "<?php\n\nglobal \$lang;\n\n" . $php . "\n?>");
fclose($fh);
Debug::message("Created file: $langFolder/" . $this->defaultLocale . ".php", false);
} else { } else {
user_error("Cannot write language file! Please check permissions of $langFolder/" . $this->defaultLocale . ".php", E_USER_ERROR); user_error("Cannot write language file! Please check permissions of $langFolder/" . $this->defaultLocale . ".php", E_USER_ERROR);
} }
@ -192,90 +170,156 @@ class i18nTextCollector extends Object {
return $fileList; return $fileList;
} }
/** public function collectFromCode($content, $module) {
* Look for calls to the underscore function in php files and build our MST $entitiesArr = array();
*
* @param string $file Path to the file to be parsed
* @return string Built Master String Table from this file
*/
protected function collectFromCode($file) {
$callMap = array();
$content = file_get_contents($file);
$mst = '';
while (ereg('_t[[:space:]]*\([[:space:]]*("[^"]*"|\\\'[^\']*\\\')[[:space:]]*,[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\')([[:space:]]*,[[:space:]]*[^,)]*)?([[:space:]]*,[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\'))?[[:space:]]*\)', $content, $regs)) {
$entityParts = explode('.',substr($regs[1],1,-1));
$entity = array_pop($entityParts);
$class = implode('.',$entityParts);
if (isset($callMap["$class--$entity"])) $regexRule = '_t[[:space:]]*\(' .
echo "Warning! Redeclaring entity $entity in file $file (previously declared in {$callMap["$class--$entity"]})<br>"; '[[:space:]]*("[^"]*"|\\\'[^\']*\\\')[[:space:]]*,' . # namespace.entity
'[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\')([[:space:]*,' . # value
'[[:space:]]*[^,)]*)?([[:space:]]*,' . # priority (optional)
'[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\'))?[[:space:]]*' . # comment
'\)';
while (ereg($regexRule, $content, $regs)) {
$entitiesArr = array_merge($entitiesArr, (array)$this->entitySpecFromRegexMatches($regs));
if (substr($regs[2],0,1) == '"') $regs[2] = addcslashes($regs[2],'\''); // remove parsed content to continue while() loop
$mst .= '$lang[\'' . $this->defaultLocale . '\'][\'' . $class . '\'][\'' . $entity . '\'] = ';
if ($regs[5]) {
$mst .= "array(\n\t'" . substr($regs[2],1,-1) . "',\n\t" . substr($regs[5],1);
if ($regs[7]) {
if (substr($regs[7],0,1) == '"') $regs[7] = addcslashes($regs[7],'\'');
$mst .= ",\n\t'" . substr($regs[7],1,-1) . '\'';
}
$mst .= "\n);";
} else $mst .= '\'' . substr($regs[2],1,-1) . '\';';
$mst .= "\n";
$content = str_replace($regs[0],"",$content); $content = str_replace($regs[0],"",$content);
$callMap["$class--$entity"] = $file;
} }
return $mst; return $entitiesArr;
} }
/** public function collectFromTemplate($content, $module, $fileName) {
* Look for calls to the underscore function in template files and build our MST $entitiesArr = array();
* Template version - no "class" argument
*
* @param string $index Index used to namespace strings
* @param string $file Path to the file to be parsed
* @param string $included List of explicitly included templates
* @return string Built Master String Table from this file
*/
protected function collectFromTemplates($index, $file, &$included) {
$callMap = array();
$content = file_get_contents($file);
// Search for included templates // Search for included templates
preg_match_all('/<' . '% include +([A-Za-z0-9_]+) +%' . '>/', $content, $inc, PREG_SET_ORDER); preg_match_all('/<' . '% include +([A-Za-z0-9_]+) +%' . '>/', $content, $regs, PREG_SET_ORDER);
foreach ($inc as $template) { foreach($regs as $reg) {
if (!isset($included[$index])) $included[$index] = array(); $includeName = $reg[1];
array_push($included[$index], $template[1]); $includeFileName = "{$includeName}.ss";
$filePath = SSViewer::getTemplateFileByType($includeName, 'Includes');
$includeContent = file_get_contents($filePath);
// @todo Will get massively confused if you include the includer -> infinite loop
$entitiesArr = array_merge($entitiesArr,(array)$this->collectFromTemplate($includeContent, $module, $includeFileName));
} }
$mst = ''; // @todo respect template tags (<% _t() %> instead of _t())
while (ereg('_t[[:space:]]*\([[:space:]]*("[^"]*"|\\\'[^\']*\\\')[[:space:]]*,[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\')([[:space:]]*,[[:space:]]*[^,)]*)?([[:space:]]*,[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\'))?[[:space:]]*\)',$content,$regs)) { $regexRule = '_t[[:space:]]*\(' .
'[[:space:]]*("[^"]*"|\\\'[^\']*\\\')[[:space:]]*,' . # namespace.entity
$entityParts = explode('.',substr($regs[1],1,-1)); '[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\')([[:space:]]*,' . # value
$entity = array_pop($entityParts); '[[:space:]]*[^,)]*)?([[:space:]]*,' . # priority (optional)
'[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\'))?[[:space:]]*' . # comment (optional)
// Entity redeclaration check '\)';
if (isset($callMap["$index--$entity"])) while(ereg($regexRule,$content,$regs)) {
echo "Warning! Redeclaring entity $entity in file $file (previously declared in {$callMap["$index--$entity"]})<br>"; $entitiesArr = array_merge($entitiesArr,(array)$this->entitySpecFromRegexMatches($regs, $fileName));
// remove parsed content to continue while() loop
if (substr($regs[2],0,1) == '"') $regs[2] = addcslashes($regs[2],'\'');
$mst .= '$lang[\'' . $this->defaultLocale . '\'][\'' . $index . '\'][\'' . $entity . '\'] = ';
if ($regs[5]) {
$mst .= "array(\n\t'" . substr($regs[2],1,-1) . "',\n\t" . substr($regs[5],1);
if ($regs[7]) {
if (substr($regs[7],0,1) == '"') $regs[7] = addcslashes($regs[7],'\'\\');
$mst .= ",\n\t'" . substr($regs[7],1,-1) . '\'';
}
$mst .= "\n);";
} else $mst .= '\'' . substr($regs[2],1,-1) . '\';';
$mst .= "\n";
$content = str_replace($regs[0],"",$content); $content = str_replace($regs[0],"",$content);
$callMap["$index--$entity"] = $file;
} }
return $mst; return $entitiesArr;
}
/**
* @todo Fix regexes so the deletion of quotes, commas and newlines from wrong matches isn't necessary
*/
protected function entitySpecFromRegexMatches($regs, $_namespace = null) {
// remove wrapping quotes
$fullName = substr($regs[1],1,-1);
// split fullname into entity parts
$entityParts = explode('.', $fullName);
if(count($entityParts) > 1) {
// templates don't have a custom namespace
$entity = array_pop($entityParts);
// namespace might contain dots, so we explode
$namespace = implode('.',$entityParts);
} else {
$entity = array_pop($entityParts);
$namespace = $_namespace;
}
// remove wrapping quotes
$value = ($regs[2]) ? substr($regs[2],1,-1) : null;
// only escape quotes when wrapped in double quotes, to make them safe for insertion
// into single-quoted PHP code. If they're wrapped in single quotes, the string should
// be properly escaped already
if(substr($regs[2],0,1) == '"') $value = addcslashes($value,'\'');
// remove starting comma and any newlines
$prio = ($regs[5]) ? trim(preg_replace('/\n/','',substr($regs[5],1))) : null;
// remove wrapping quotes
$comment = ($regs[7]) ? substr($regs[7],1,-1) : null;
return array(
"{$namespace}.{$entity}" => array(
$value,
$prio,
$comment
)
);
}
/**
* 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) {
$php = '';
$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;
}
$value = $entitySpec[0];
$prio = (isset($entitySpec[1])) ? addcslashes($entitySpec[1],'\'') : null;
$comment = (isset($entitySpec[2])) ? addcslashes($entitySpec[2],'\'') : null;
$php .= '$lang[\'' . $this->defaultLocale . '\'][\'' . $namespace . '\'][\'' . $entity . '\'] = ';
if ($prio) {
$php .= "array(\n\t'" . $value . "',\n\t" . $prio;
if ($comment) {
$php .= ",\n\t'" . $comment . '\'';
}
$php .= "\n);";
} else {
$php .= '\'' . $value . '\';';
}
$php .= "\n";
return $php;
}
protected function collectFromStatics($filePath) {
$entitiesArr = array();
$classes = ClassInfo::classes_for_file($filePath);
if($classes) foreach($classes as $class) {
if(class_exists($class) && method_exists($class, 'provideI18nStatics')) {
$obj = singleton($class);
$entitiesArr = array_merge($entitiesArr,(array)$obj->provideI18nStatics());
}
}
return $entitiesArr;
}
public function getDefaultLocale() {
return $this->defaultLocale;
}
public function setDefaultLocale($locale) {
$this->defaultLocale = $locale;
} }
} }
?> ?>

View File

@ -0,0 +1,7 @@
<% if Foo %>
<% control Foo %><% end_control %>
<% _t('INCLUDENONAMESPACE', 'Include Value'); %>
<% _t('Test.INCLUDEWITHNAMESPACE', 'Include Value with namespace'); %>
<% include i18nTextCollectorTest_NestedInclude %>
<% end_if %>
_t(in text)

View File

@ -0,0 +1,2 @@
<% _t('NESTEDINCLUDE','Nested Include Value') %>
<% _t('Test.NESTEDINCLUDEWITHNAMESPACE','Nested Include Value with namespace') %>

View File

@ -0,0 +1,20 @@
<?php
class i18nTestModule extends Object {
function __construct() {
_t(
'i18nTestModule.ENTITY',
'Entity with "Double Quotes"',
PR_LOW,
'Comment for entity'
);
parent::__construct();
}
}
class i18nTestModule_Addition extends Object {
function __construct() {
_t('i18nTestModule.ADDITION','Addition');
parent::__construct();
}
}

View File

@ -0,0 +1,9 @@
<?php
class i18nTestSubModule extends Object {
function __construct() {
_t('i18nTestModule.OTHERENTITY', 'Other Entity');
parent::__construct();
}
}
?>

View File

@ -0,0 +1,2 @@
<% _t("i18nTestModule.WITHNAMESPACE", 'Include Entity with Namespace') %>
<% _t("NONAMESPACE", 'Include Entity without Namespace') %>

View File

@ -0,0 +1,2 @@
<% _t('i18nTestModule.LAYOUTTEMPLATE',"Layout Template")%>
<% include i18nTestModuleInclude %>

View File

@ -0,0 +1 @@
<% _t('i18nTestModule.MAINTEMPLATE',"Main Template")%>

View File

@ -0,0 +1,344 @@
<?php
/**
* @package sapphire
* @subpackage tests
*/
class i18nTextCollectorTest extends SapphireTest {
/**
* @var string $tmpBasePath Used to write language files.
* We don't want to store them inside sapphire (or in any web-accessible place)
* in case something goes wrong with the file parsing.
*/
protected $alternateBaseSavePath;
/**
* @var string $alternateBasePath Fake webroot with a single module
* /i18ntestmodule which contains some files with _t() calls.
*/
protected $alternateBasePath;
function setUp() {
parent::setUp();
$this->alternateBasePath = Director::baseFolder() . "/sapphire/tests/i18n/_fakewebroot/";
$this->alternateBaseSavePath = TEMP_FOLDER . '/i18nTextCollectorTest_webroot';
FileSystem::makeFolder($this->alternateBaseSavePath);
// SSViewer and ManifestBuilder don't support different webroots, hence we set the paths manually
global $_CLASS_MANIFEST;
$_CLASS_MANIFEST['i18nTestModule'] = Director::baseFolder() . $this->alternateBasePath . 'i18ntestmodule/code/i18nTestModule.php';
$_CLASS_MANIFEST['i18nTestModule_Addition'] = Director::baseFolder() . $this->alternateBasePath . 'i18ntestmodule/code/i18nTestModule.php';
global $_TEMPLATE_MANIFEST;
$_TEMPLATE_MANIFEST['i18nTestModule.ss'] = array(
'main' => Director::baseFolder() . $this->alternateBasePath . 'i18ntestmodule/templates/i18nTestModule.ss',
'Layout' => Director::baseFolder() . $this->alternateBasePath . 'i18ntestmodule/templates/Layout/i18nTestModule.ss',
);
$_TEMPLATE_MANIFEST['i18nTestModuleInclude.ss'] = array(
'Includes' => Director::baseFolder() . $this->alternateBasePath . 'i18ntestmodule/templates/Includes/i18nTestModuleInclude.ss',
);
}
function tearDown() {
//FileSystem::removeFolder($this->tmpBasePath);
global $_CLASS_MANIFEST;
unset($_CLASS_MANIFEST['i18nTestModule']);
unset($_CLASS_MANIFEST['i18nTestModule_Addition']);
global $_TEMPLATE_MANIFEST;
unset($_TEMPLATE_MANIFEST['i18nTestModule.ss']);
unset($_TEMPLATE_MANIFEST['i18nTestModuleInclude.ss']);
}
function testCollectFromTemplateSimple() {
$c = new i18nTextCollector();
$html = <<<SS
<% _t('Test.SINGLEQUOTE','Single Quote'); %>
SS;
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
'Test.SINGLEQUOTE' => array('Single Quote',null,null)
)
);
$html = <<<SS
<% _t( "Test.DOUBLEQUOTE", "Double Quote and Spaces" ); %>
SS;
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
'Test.DOUBLEQUOTE' => array("Double Quote and Spaces", null, null)
)
);
$html = <<<SS
<% _t("Test.NOSEMICOLON","No Semicolon") %>
SS;
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
'Test.NOSEMICOLON' => array("No Semicolon", null, null)
)
);
}
function testCollectFromTemplateAdvanced() {
$c = new i18nTextCollector();
$html = <<<SS
<% _t(
'NEWLINES',
'New Lines'
) %>
SS;
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
'Test.NEWLINES' => array("New Lines", null, null)
)
);
$html = <<<SS
<% _t(
'Test.PRIOANDCOMMENT',
' Prio and Value with "Double Quotes"',
PR_MEDIUM,
'Comment with "Double Quotes"'
) %>
SS;
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
'Test.PRIOANDCOMMENT' => array(' Prio and Value with "Double Quotes"','PR_MEDIUM','Comment with "Double Quotes"')
)
);
$html = <<<SS
<% _t(
'Test.PRIOANDCOMMENT',
" Prio and Value with 'Single Quotes'",
PR_MEDIUM,
"Comment with 'Single Quotes'"
) %>
SS;
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'Test'),
array(
'Test.PRIOANDCOMMENT' => array(" Prio and Value with \'Single Quotes\'",'PR_MEDIUM',"Comment with 'Single Quotes'")
)
);
}
function testCollectFromCodeSimple() {
$c = new i18nTextCollector();
$php = <<<PHP
_t('Test.SINGLEQUOTE','Single Quote');
PHP;
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
'Test.SINGLEQUOTE' => array('Single Quote',null,null)
)
);
$php = <<<PHP
_t( "Test.DOUBLEQUOTE", "Double Quote and Spaces" );
PHP;
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
'Test.DOUBLEQUOTE' => array("Double Quote and Spaces", null, null)
)
);
}
function testCollectFromCodeAdvanced() {
$c = new i18nTextCollector();
$php = <<<PHP
_t(
'Test.NEWLINES',
'New Lines'
);
PHP;
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
'Test.NEWLINES' => array("New Lines", null, null)
)
);
$php = <<<PHP
_t(
'Test.PRIOANDCOMMENT',
' Value with "Double Quotes"',
PR_MEDIUM,
'Comment with "Double Quotes"'
);
PHP;
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
'Test.PRIOANDCOMMENT' => array(' Value with "Double Quotes"','PR_MEDIUM','Comment with "Double Quotes"')
)
);
$php = <<<PHP
_t(
'Test.PRIOANDCOMMENT',
" Value with 'Single Quotes'",
PR_MEDIUM,
"Comment with 'Single Quotes'"
);
PHP;
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
'Test.PRIOANDCOMMENT' => array(" Value with \'Single Quotes\'",'PR_MEDIUM',"Comment with 'Single Quotes'")
)
);
$php = <<<PHP
_t(
'Test.PRIOANDCOMMENT',
'Value with \'Escaped Single Quotes\''
);
PHP;
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
'Test.PRIOANDCOMMENT' => array("Value with \'Escaped Single Quotes\'",null,null)
)
);
$php = <<<PHP
_t(
'Test.PRIOANDCOMMENT',
"Doublequoted Value with 'Unescaped Single Quotes'"
);
PHP;
$this->assertEquals(
$c->collectFromCode($php, 'mymodule'),
array(
'Test.PRIOANDCOMMENT' => array("Doublequoted Value with \'Unescaped Single Quotes\'",null,null)
)
);
}
/**
* Input for langArrayCodeForEntitySpec() should be suitable for insertion
* into single-quoted strings, so needs to be escaped already.
*/
function testLangArrayCodeForEntity() {
$c = new i18nTextCollector();
$locale = $c->getDefaultLocale();
$this->assertEquals(
$c->langArrayCodeForEntitySpec('Test.SIMPLE', array('Simple Value')),
"\$lang['{$locale}']['Test']['SIMPLE'] = 'Simple Value';\n"
);
$this->assertEquals(
// single quotes should be properly escaped by the parser already
$c->langArrayCodeForEntitySpec('Test.ESCAPEDSINGLEQUOTES', array("Value with \'Escaped Single Quotes\'")),
"\$lang['{$locale}']['Test']['ESCAPEDSINGLEQUOTES'] = 'Value with \'Escaped Single Quotes\'';\n"
);
$this->assertEquals(
$c->langArrayCodeForEntitySpec('Test.DOUBLEQUOTES', array('Value with "Double Quotes"')),
"\$lang['{$locale}']['Test']['DOUBLEQUOTES'] = 'Value with \"Double Quotes\"';\n"
);
$php = <<<PHP
\$lang['$locale']['Test']['PRIOANDCOMMENT'] = array(
'Value with \'Single Quotes\'',
PR_MEDIUM,
'Comment with \'Single Quotes\''
);
PHP;
$this->assertEquals(
$c->langArrayCodeForEntitySpec('Test.PRIOANDCOMMENT', array("Value with \'Single Quotes\'",'PR_MEDIUM',"Comment with 'Single Quotes'")),
$php
);
$php = <<<PHP
\$lang['$locale']['Test']['PRIOANDCOMMENT'] = array(
'Value with "Double Quotes"',
PR_MEDIUM,
'Comment with "Double Quotes"'
);
PHP;
$this->assertEquals(
$c->langArrayCodeForEntitySpec('Test.PRIOANDCOMMENT', array('Value with "Double Quotes"','PR_MEDIUM','Comment with "Double Quotes"')),
$php
);
}
function testCollectFromIncludedTemplates() {
$c = new i18nTextCollector();
$html = <<<SS
<% _t("MainTemplate.MAINVALUE", 'Main Value'); %>
<% if Bar %>
<% include i18nTextCollectorTest_Include %>
<% end_if %>
lonely _t() call that should be ignored
SS;
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'RandomNamespace'),
array(
'i18nTextCollectorTest_NestedInclude.ss.NESTEDINCLUDE' => array('Nested Include Value', null, null),
'Test.NESTEDINCLUDEWITHNAMESPACE' => array('Nested Include Value with namespace', null, null),
'i18nTextCollectorTest_Include.ss.INCLUDENONAMESPACE' => array('Include Value', null, null),
'Test.INCLUDEWITHNAMESPACE' => array('Include Value with namespace', null, null),
'MainTemplate.MAINVALUE' => array('Main Value', null, null),
)
);
}
function testCollectFromFilesystemAndWriteMasterTables() {
$c = new i18nTextCollector();
$c->basePath = $this->alternateBasePath;
$c->baseSavePath = $this->alternateBaseSavePath;
$c->run('i18ntestmodule');
$moduleLangFile = "{$this->alternateBaseSavePath}/i18ntestmodule/lang/" . $c->getDefaultLocale() . '.php';
$this->assertTrue(
file_exists($moduleLangFile),
'Master language file can be written to modules /lang folder'
);
$compareContent = <<<PHP
<?php
global \$lang;
\$lang['en_US']['i18nTestModule']['ADDITION'] = 'Addition';
\$lang['en_US']['i18nTestModule']['ENTITY'] = array(
'Entity with "Double Quotes"',
PR_LOW,
'Comment for entity'
);
\$lang['en_US']['i18nTestModule']['WITHNAMESPACE'] = 'Include Entity with Namespace';
\$lang['en_US']['i18nTestModuleInclude.ss']['NONAMESPACE'] = 'Include Entity without Namespace';
\$lang['en_US']['i18nTestModule']['MAINTEMPLATE'] = 'Main Template';
\$lang['en_US']['i18nTestModule']['OTHERENTITY'] = 'Other Entity';
?>
PHP;
$this->assertEquals(
file_get_contents($moduleLangFile),
$compareContent
);
}
}
?>