FEATURE Collect i18n Entities from dynamic PHP code - useful to translate statics. Added DataObject->provideI18nEntities() and i18nTextCollector->collectFromEntityProviders(). See #1625

FEATURE Making DataObject attributes translatable through i18n class, e.g. $db and all relation statics. Use DataObject->fieldLabels() to access translated attributes.
ENHANCEMENT Ignoring entity-names with $ signs (most likely dynamic properties) in i18nTextCollector->collectFromCode()


git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@64881 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2008-10-29 21:07:17 +00:00
parent 076fcde635
commit 84db28eb10
8 changed files with 232 additions and 26 deletions

View File

@ -92,7 +92,7 @@ class i18nTextCollector extends Object {
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));
$entitiesArr = array_merge($entitiesArr, (array)$this->collectFromEntityProviders($filePath, $module));
}
}
@ -144,7 +144,7 @@ class i18nTextCollector extends Object {
fwrite($fh, "<?php\n\nglobal \$lang;\n\n" . $php . "\n?>");
fclose($fh);
Debug::message("Created file: $langFolder/" . $this->defaultLocale . ".php", false);
//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);
}
@ -247,6 +247,13 @@ class i18nTextCollector extends Object {
$namespace = $_namespace;
}
// 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}.
if(strpos('$', $entity) !== FALSE) return false;
// remove wrapping quotes
$value = ($regs[2]) ? substr($regs[2],1,-1) : null;
@ -309,14 +316,14 @@ class i18nTextCollector extends Object {
return $php;
}
function collectFromStatics($filePath) {
function collectFromEntityProviders($filePath) {
$entitiesArr = array();
$classes = ClassInfo::classes_for_file($filePath);
if($classes) foreach($classes as $class) {
if(class_exists($class) && method_exists($class, 'i18nCollectStatics')) {
if(class_exists($class) && method_exists($class, 'provideI18nEntities')) {
$obj = singleton($class);
$entitiesArr = array_merge($entitiesArr,(array)$obj->i18nCollectStatics());
$entitiesArr = array_merge($entitiesArr,(array)$obj->provideI18nEntities());
}
}

View File

@ -292,8 +292,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
*
* @return string User friendly translated singular name of this DataObject
*/
function i18n_singular_name()
{
function i18n_singular_name() {
$name = (!empty($this->add_action)) ? $this->add_action : $this->singular_name();
return _t($this->class.'.SINGULARNAME', $name);
}
@ -2818,6 +2817,60 @@ class DataObject extends ViewableData implements DataObjectInterface {
* view of this object.
*/
public static $summary_fields = null;
/**
* Collect all static properties on the object
* which contain natural language, and need to be translated.
* The full entity name is composed from the class name and a custom identifier.
*
* @return array A numerical array which contains one or more entities in array-form.
* Each numeric entity array contains the "arguments" for a _t() call as array values:
* $entity, $string, $priority, $context.
*/
public function provideI18nEntities() {
$entities = array();
$db = $this->uninherited('db', true);
if($db) foreach($db as $name => $type) {
$entities["{$this->class}.db_{$name}"] = array(
$name,
PR_MEDIUM,
'Name of the object property, mainly used for automatically generating forms'
);
}
$has_many = $this->uninherited('has_many', true);
if($has_many) foreach($has_many as $name => $class) {
$entities["{$this->class}.has_many_{$name}"] = array(
$name,
PR_MEDIUM,
'Name of an object relation, mainly used for automatically generating forms'
);
}
$many_many = $this->uninherited('many_many', true);
if($many_many) foreach($many_many as $name => $class) {
$entities["{$this->class}.many_many_{$name}"] = array(
$name,
PR_MEDIUM,
'Name of an object relation, mainly used for automatically generating forms'
);
}
$entities["{$this->class}.SINGULARNAME"] = array(
$this->uninherited('singular_name', true),
PR_MEDIUM,
'Singular name of the object, used in dropdowns and to generally identify a single object in the interface'
);
$entities["{$this->class}.PLURALNAME"] = array(
$this->uninherited('plural_name', true),
PR_MEDIUM,
'Pural name of the object, used in dropdowns and to generally identify a collection of this object in the interface'
);
return $entities;
}
}

View File

@ -1,7 +0,0 @@
<% 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

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

View File

@ -0,0 +1,20 @@
<?php
/**
* @package sapphire
* @subpackage tests
*/
class i18nTextCollectorTestMyObject extends DataObject implements TestOnly {
static $db = array(
'FirstProperty' => 'Varchar',
'SecondProperty' => 'Int'
);
static $has_many = array(
'Relation' => 'Group'
);
static $singular_name = "My Object";
static $plural_name = "My Objects";
}
?>

View File

@ -0,0 +1,20 @@
<?php
/**
* @package sapphire
* @subpackage tests
*/
class i18nTextCollectorTestMySubObject extends i18nTextCollectorTestMyObject implements TestOnly {
static $db = array(
'SubProperty' => 'Varchar',
);
static $has_many = array(
'SubRelation' => 'Group'
);
static $singular_name = "My Sub Object";
static $plural_name = "My Sub Objects";
}
?>

View File

@ -10,4 +10,69 @@ class i18nTest extends SapphireTest {
$this->assertTrue(isset($translations['de_DE']), 'Checking for de_DE translation');
}
function testDataObjectFieldLabels() {
global $lang;
$oldLocale = i18n::get_locale();
i18n::set_locale('de_DE');
$obj = new i18nTest_Object();
$lang['en_US']['i18nTest_Object']['db_MyProperty'] = 'MyProperty';
$lang['de_DE']['i18nTest_Object']['db_MyProperty'] = 'Mein Attribut';
$this->assertEquals(
$obj->fieldLabel('MyProperty'),
'Mein Attribut'
);
$lang['en_US']['i18nTest_Object']['has_one_HasOneRelation'] = 'HasOneRelation';
$lang['de_DE']['i18nTest_Object']['has_one_HasOneRelation'] = 'Eins zu eins';
$this->assertEquals(
$obj->fieldLabel('HasOneRelation'),
'Eins zu eins'
);
$lang['en_US']['i18nTest_Object']['has_many_HasManyRelation'] = 'HasManyRelation';
$lang['de_DE']['i18nTest_Object']['has_many_HasManyRelation'] = 'Viel zu eins';
$this->assertEquals(
$obj->fieldLabel('HasManyRelation'),
'Viel zu eins'
);
$lang['en_US']['i18nTest_Object']['many_many_ManyManyRelation'] = 'ManyManyRelation';
$lang['de_DE']['i18nTest_Object']['many_many_ManyManyRelation'] = 'Viel zu viel';
$this->assertEquals(
$obj->fieldLabel('ManyManyRelation'),
'Viel zu viel'
);
$lang['en_US']['i18nTest_Object']['db_MyUntranslatedProperty'] = 'MyUntranslatedProperty';
$this->assertEquals(
$obj->fieldLabel('MyUntranslatedProperty'),
'My Untranslated Property'
);
i18n::set_locale($oldLocale);
}
}
class i18nTest_Object extends DataObject implements TestOnly {
static $db = array(
'MyProperty' => 'Varchar',
'MyUntranslatedProperty' => 'Text'
);
static $has_one = array(
'HasOneRelation' => 'Member'
);
static $has_many = array(
'HasManyRelation' => 'Member'
);
static $many_many = array(
'ManyManyRelation' => 'Member'
);
}
?>

View File

@ -21,26 +21,26 @@ class i18nTextCollectorTest extends SapphireTest {
function setUp() {
parent::setUp();
$this->alternateBasePath = Director::baseFolder() . "/sapphire/tests/i18n/_fakewebroot/";
$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';
$_CLASS_MANIFEST['i18nTestModule'] = $this->alternateBasePath . '/i18ntestmodule/code/i18nTestModule.php';
$_CLASS_MANIFEST['i18nTestModule_Addition'] = $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',
'main' => $this->alternateBasePath . '/i18ntestmodule/templates/i18nTestModule.ss',
'Layout' => $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss',
);
$_TEMPLATE_MANIFEST['i18nTestModuleInclude.ss'] = array(
'Includes' => Director::baseFolder() . $this->alternateBasePath . 'i18ntestmodule/templates/Includes/i18nTestModuleInclude.ss',
'Includes' => $this->alternateBasePath . '/i18ntestmodule/templates/Includes/i18nTestModuleInclude.ss',
);
$_TEMPLATE_MANIFEST['i18nTestModule.ss'] = array(
'main' => Director::baseFolder() . $this->alternateBasePath . 'i18ntestmodule/templates/i18nTestModule.ss',
'Layout' => Director::baseFolder() . $this->alternateBasePath . 'i18ntestmodule/templates/Layout/i18nTestModule.ss',
'main' => $this->alternateBasePath . '/i18ntestmodule/templates/i18nTestModule.ss',
'Layout' => $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss',
);
}
@ -289,7 +289,7 @@ PHP;
function testCollectFromIncludedTemplates() {
$c = new i18nTextCollector();
$templateFilePath = $this->alternateBasePath . 'i18ntestmodule/templates/Layout/i18nTestModule.ss';
$templateFilePath = $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss';
$html = file_get_contents($templateFilePath);
$this->assertEquals(
$c->collectFromTemplate($html, 'mymodule', 'RandomNamespace'),
@ -360,5 +360,55 @@ PHP;
$compareContent
);
}
function testCollectFromEntityProvidersInCustomObject() {
$c = new i18nTextCollector();
$filePath = Director::baseFolder() . '/sapphire/tests/i18n/i18nTextCollectorTestMyObject.php';
$matches = $c->collectFromEntityProviders($filePath);
$this->assertEquals(
array_keys($matches),
array(
'i18nTextCollectorTestMyObject.PLURALNAME',
'i18nTextCollectorTestMyObject.SINGULARNAME',
'i18nTextCollectorTestMyObject.db_FirstProperty',
'i18nTextCollectorTestMyObject.db_SecondProperty',
'i18nTextCollectorTestMyObject.has_many_Relation',
)
);
$this->assertEquals(
'FirstProperty',
$matches['i18nTextCollectorTestMyObject.db_FirstProperty'][0]
);
$this->assertEquals(
'My Object',
$matches['i18nTextCollectorTestMyObject.SINGULARNAME'][0]
);
}
function testCollectFromEntityProvidersInCustomSubClass() {
$c = new i18nTextCollector();
$filePath = Director::baseFolder() . '/sapphire/tests/i18n/i18nTextCollectorTestMySubObject.php';
$matches = $c->collectFromEntityProviders($filePath);
$this->assertEquals(
array_keys($matches),
array(
'i18nTextCollectorTestMySubObject.PLURALNAME',
'i18nTextCollectorTestMySubObject.SINGULARNAME',
'i18nTextCollectorTestMySubObject.db_SubProperty',
'i18nTextCollectorTestMySubObject.has_many_SubRelation',
)
);
$this->assertEquals(
'SubProperty',
$matches['i18nTextCollectorTestMySubObject.db_SubProperty'][0]
);
$this->assertEquals(
'My Sub Object',
$matches['i18nTextCollectorTestMySubObject.SINGULARNAME'][0]
);
}
}
?>