mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
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:
parent
9f41917fb1
commit
874aaf1d27
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Bernat Foj Capell <bernat@silverstripe.com>
|
||||
* @author Ingo Schommer <FIRSTNAME@silverstripe.com>
|
||||
* @package sapphire
|
||||
* @subpackage misc
|
||||
*/
|
||||
@ -8,11 +9,28 @@ class i18nTextCollector extends Object {
|
||||
|
||||
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
|
||||
*/
|
||||
function __construct($locale = null) {
|
||||
$this->defaultLocale = ($locale) ? $locale : i18n::default_locale();
|
||||
$this->basePath = Director::baseFolder();
|
||||
$this->baseSavePath = Director::baseFolder();
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
@ -24,149 +42,109 @@ class i18nTextCollector extends Object {
|
||||
*
|
||||
* @uses DataObject->collectI18nStatics()
|
||||
*/
|
||||
public function run($module = null) {
|
||||
if(Director::is_cli()) {
|
||||
echo "Collecting text...\n";
|
||||
} else {
|
||||
echo "Collecting text...<br /><br />";
|
||||
}
|
||||
public function run($restrictToModule = null) {
|
||||
Debug::message("Collecting text...", false);
|
||||
|
||||
//Calculate base directory
|
||||
$baseDir = Director::baseFolder();
|
||||
|
||||
// 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
|
||||
if (!isset($module)) {
|
||||
$topLevel = scandir($baseDir);
|
||||
foreach($topLevel as $module) {
|
||||
// we store the master string tables
|
||||
$processed = $this->processModule($baseDir, $module, $includedtpl);
|
||||
if ($processed) $mst[$module] = $processed;
|
||||
}
|
||||
} else {
|
||||
$module = basename($module);
|
||||
$processed = $this->processModule($baseDir, $module, $includedtpl);
|
||||
if ($processed) $mst[$module] = $processed;
|
||||
$modules = (isset($restrictToModule)) ? array(basename($restrictToModule)) : scandir($this->basePath);
|
||||
|
||||
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
|
||||
$entitiesByModule[$module] = $this->processModule($module);
|
||||
}
|
||||
|
||||
// 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
|
||||
*
|
||||
* @param string $baseDir Silverstripe's base directory
|
||||
* @param string $module Module's name
|
||||
* @return string Generated master string table
|
||||
*/
|
||||
protected function processModule($baseDir, $module) {
|
||||
|
||||
// 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);
|
||||
protected function processModule($module) {
|
||||
$entitiesArr = array();
|
||||
|
||||
$mst = '';
|
||||
// Search for calls in code files if these exists
|
||||
if(is_dir("$baseDir/$module/code")) {
|
||||
$fileList = $this->getFilesRecursive("$baseDir/$module/code");
|
||||
foreach($fileList as $file) {
|
||||
if(substr($file,-3) == '.php') $mst .= $this->collectFromCode($file);
|
||||
}
|
||||
Debug::message("Processing Module '{$module}'", false);
|
||||
|
||||
// Search for calls in code files if these exists
|
||||
if(is_dir("$this->basePath/$module/code")) {
|
||||
$fileList = $this->getFilesRecursive("$this->basePath/$module/code");
|
||||
} else if('sapphire' == $module) {
|
||||
// sapphire doesn't have the usual module structure, so we'll scan all subfolders
|
||||
$fileList = $this->getFilesRecursive("$baseDir/$module");
|
||||
foreach($fileList as $file) {
|
||||
// exclude ss-templates, they're scanned separately
|
||||
if(substr($file,-3) == '.php') $mst .= $this->collectFromCode($file);
|
||||
}
|
||||
// sapphire doesn't have the usual module structure, so we'll scan all subfolders
|
||||
$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);
|
||||
$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
|
||||
if(is_dir("$baseDir/$module/templates")) {
|
||||
$includedtpl[$module] = array();
|
||||
$fileList = $this->getFilesRecursive("$baseDir/$module/templates");
|
||||
foreach($fileList as $index => $file) {
|
||||
$mst .= $this->collectFromTemplates($index, $file, $includedtpl[$module]);
|
||||
}
|
||||
}
|
||||
|
||||
// Search for calls in template files if these exists
|
||||
if(is_dir("$this->basePath/$module/templates")) {
|
||||
$fileList = $this->getFilesRecursive("$this->basePath/$module/templates");
|
||||
foreach($fileList as $index => $filePath) {
|
||||
$content = file_get_contents($filePath);
|
||||
// templates use their filename as a namespace
|
||||
$namespace = basename($filePath);
|
||||
$entitiesArr = array_merge($entitiesArr, (array)$this->collectFromTemplate($content, $module, $namespace));
|
||||
}
|
||||
|
||||
return $mst;
|
||||
|
||||
} else return false;
|
||||
}
|
||||
|
||||
// sort for easier lookup and comparison with translated files
|
||||
asort($entitiesArr);
|
||||
|
||||
return $entitiesArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// Evaluate the constructed mst
|
||||
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";
|
||||
}
|
||||
protected function writeMasterStringFile($entitiesByModule) {
|
||||
$php = '';
|
||||
|
||||
// Write each module language file
|
||||
foreach($allmst as $module => $mst) {
|
||||
if($entitiesByModule) foreach($entitiesByModule as $module => $entities) {
|
||||
// Create folder for lang files
|
||||
$langFolder = $baseDir . '/' . $module . '/lang';
|
||||
if(!file_exists($baseDir. '/' . $module . '/lang')) {
|
||||
mkdir($langFolder, Filesystem::$folder_create_mask);
|
||||
touch($baseDir. '/' . $module . '/lang/_manifest_exclude');
|
||||
$langFolder = $this->baseSavePath . '/' . $module . '/lang';
|
||||
if(!file_exists($this->baseSavePath. '/' . $module . '/lang')) {
|
||||
Filesystem::makeFolder($langFolder, Filesystem::$folder_create_mask);
|
||||
touch($this->baseSavePath. '/' . $module . '/lang/_manifest_exclude');
|
||||
}
|
||||
|
||||
|
||||
// Open the English file and write the Master String Table
|
||||
if($fh = fopen($langFolder . '/' . $this->defaultLocale . '.php', "w")) {
|
||||
fwrite($fh, "<?php\n\nglobal \$lang;\n\n" . $mst . "\n?>");
|
||||
fclose($fh);
|
||||
if(Director::is_cli()) {
|
||||
echo "Created file: $langFolder/" . $this->defaultLocale . ".php\n";
|
||||
} else {
|
||||
echo "Created file: $langFolder/" . $this->defaultLocale . ".php<br />";
|
||||
if($entities) foreach($entities as $fullName => $spec) {
|
||||
$php .= $this->langArrayCodeForEntitySpec($fullName, $spec);
|
||||
}
|
||||
|
||||
|
||||
// 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 {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for calls to the underscore function in php files and build our MST
|
||||
*
|
||||
* @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);
|
||||
public function collectFromCode($content, $module) {
|
||||
$entitiesArr = array();
|
||||
|
||||
$regexRule = '_t[[:space:]]*\(' .
|
||||
'[[: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 (isset($callMap["$class--$entity"]))
|
||||
echo "Warning! Redeclaring entity $entity in file $file (previously declared in {$callMap["$class--$entity"]})<br>";
|
||||
|
||||
if (substr($regs[2],0,1) == '"') $regs[2] = addcslashes($regs[2],'\'');
|
||||
$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";
|
||||
// remove parsed content to continue while() loop
|
||||
$content = str_replace($regs[0],"",$content);
|
||||
|
||||
$callMap["$class--$entity"] = $file;
|
||||
}
|
||||
|
||||
return $mst;
|
||||
return $entitiesArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for calls to the underscore function in template files and build our MST
|
||||
* 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);
|
||||
public function collectFromTemplate($content, $module, $fileName) {
|
||||
$entitiesArr = array();
|
||||
|
||||
// Search for included templates
|
||||
preg_match_all('/<' . '% include +([A-Za-z0-9_]+) +%' . '>/', $content, $inc, PREG_SET_ORDER);
|
||||
foreach ($inc as $template) {
|
||||
if (!isset($included[$index])) $included[$index] = array();
|
||||
array_push($included[$index], $template[1]);
|
||||
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');
|
||||
$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 = '';
|
||||
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);
|
||||
|
||||
// Entity redeclaration check
|
||||
if (isset($callMap["$index--$entity"]))
|
||||
echo "Warning! Redeclaring entity $entity in file $file (previously declared in {$callMap["$index--$entity"]})<br>";
|
||||
|
||||
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";
|
||||
// @todo respect template tags (<% _t() %> instead of _t())
|
||||
$regexRule = '_t[[:space:]]*\(' .
|
||||
'[[:space:]]*("[^"]*"|\\\'[^\']*\\\')[[:space:]]*,' . # namespace.entity
|
||||
'[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\')([[:space:]]*,' . # value
|
||||
'[[:space:]]*[^,)]*)?([[:space:]]*,' . # priority (optional)
|
||||
'[[:space:]]*("([^"]|\\\")*"|\'([^\']|\\\\\')*\'))?[[:space:]]*' . # comment (optional)
|
||||
'\)';
|
||||
while(ereg($regexRule,$content,$regs)) {
|
||||
$entitiesArr = array_merge($entitiesArr,(array)$this->entitySpecFromRegexMatches($regs, $fileName));
|
||||
// remove parsed content to continue while() loop
|
||||
$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;
|
||||
}
|
||||
}
|
||||
?>
|
7
templates/Includes/i18nTextCollectorTest_Include.ss
Normal file
7
templates/Includes/i18nTextCollectorTest_Include.ss
Normal 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)
|
@ -0,0 +1,2 @@
|
||||
<% _t('NESTEDINCLUDE','Nested Include Value') %>
|
||||
<% _t('Test.NESTEDINCLUDEWITHNAMESPACE','Nested Include Value with namespace') %>
|
0
tests/i18n/_fakewebroot/i18ntestmodule/_config.php
Normal file
0
tests/i18n/_fakewebroot/i18ntestmodule/_config.php
Normal 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
class i18nTestSubModule extends Object {
|
||||
function __construct() {
|
||||
_t('i18nTestModule.OTHERENTITY', 'Other Entity');
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
?>
|
@ -0,0 +1,2 @@
|
||||
<% _t("i18nTestModule.WITHNAMESPACE", 'Include Entity with Namespace') %>
|
||||
<% _t("NONAMESPACE", 'Include Entity without Namespace') %>
|
@ -0,0 +1,2 @@
|
||||
<% _t('i18nTestModule.LAYOUTTEMPLATE',"Layout Template")%>
|
||||
<% include i18nTestModuleInclude %>
|
@ -0,0 +1 @@
|
||||
<% _t('i18nTestModule.MAINTEMPLATE',"Main Template")%>
|
344
tests/i18nTextCollectorTest.php
Normal file
344
tests/i18nTextCollectorTest.php
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
?>
|
Loading…
Reference in New Issue
Block a user