diff --git a/src/i18n/TextCollection/i18nTextCollector.php b/src/i18n/TextCollection/i18nTextCollector.php index dc8fc811d..6becafcf4 100644 --- a/src/i18n/TextCollection/i18nTextCollector.php +++ b/src/i18n/TextCollection/i18nTextCollector.php @@ -5,6 +5,9 @@ namespace SilverStripe\i18n\TextCollection; use Exception; use LogicException; use SilverStripe\Core\ClassInfo; +use SilverStripe\Core\Config\Config; +use SilverStripe\Core\Config\Configurable; +use SilverStripe\Core\Extension; use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Manifest\ClassLoader; use SilverStripe\Core\Manifest\Module; @@ -12,10 +15,12 @@ use SilverStripe\Core\Manifest\ModuleLoader; use SilverStripe\Dev\Debug; use SilverStripe\Control\Director; use ReflectionClass; +use SilverStripe\Forms\FormField; use SilverStripe\i18n\i18n; use SilverStripe\i18n\i18nEntityProvider; use SilverStripe\i18n\Messages\Reader; use SilverStripe\i18n\Messages\Writer; +use SilverStripe\ORM\DataObject; /** * SilverStripe-variant of the "gettext" tool: @@ -482,6 +487,7 @@ class i18nTextCollector if ($extension === 'php') { $entities = array_merge( $entities, + $this->collectFromORM($filePath), $this->collectFromCode($content, $filePath, $module), $this->collectFromEntityProviders($filePath, $module) ); @@ -881,7 +887,7 @@ class i18nTextCollector $classes = ClassInfo::classes_for_file($filePath); foreach ($classes as $class) { // Skip non-implementing classes - if (!class_exists($class ?? '') || !is_a($class, i18nEntityProvider::class, true)) { + if (!class_exists($class) || !is_a($class, i18nEntityProvider::class, true)) { continue; } @@ -907,6 +913,47 @@ class i18nTextCollector return $entities; } + /** + * Extracts translations for ORM fields + * + * @param string $filePath + * @return array + */ + public function collectFromORM($filePath) + { + $entities = []; + $classes = ClassInfo::classes_for_file($filePath); + foreach ($classes as $class) { + // Skip non-implementing classes + if (!class_exists($class)) { + continue; + } + + // Skip abstract classes + $reflectionClass = new ReflectionClass($class); + if ($reflectionClass->isAbstract()) { + continue; + } + + $provided = []; + // add labels for ORM fields + if (is_a($class, DataObject::class, true) || is_a($class, Extension::class, true)) { + foreach (['db', 'has_one', 'has_many', 'belongs_to', 'many_many', 'belongs_many_many'] as $type) { + if ($config = Config::inst()->get($class, $type, Config::UNINHERITED)) { + foreach ($config as $name => $spec) { + // add type in translation identifier as used in DataObject::fieldLabels() + $provided["{$class}.{$type}_{$name}"] = FormField::name_to_label($name); + } + } + } + } + $entities = array_merge($entities, $provided); + } + + ksort($entities); + return $entities; + } + /** * Normalizes entities with namespaces. * diff --git a/tests/php/i18n/i18nTextCollectorTest.php b/tests/php/i18n/i18nTextCollectorTest.php index df33ced47..4d1f14c65 100644 --- a/tests/php/i18n/i18nTextCollectorTest.php +++ b/tests/php/i18n/i18nTextCollectorTest.php @@ -3,12 +3,15 @@ namespace SilverStripe\i18n\Tests; use SilverStripe\Assets\Filesystem; +use SilverStripe\Core\Config\Config; use SilverStripe\Core\Manifest\ModuleLoader; use SilverStripe\Dev\SapphireTest; use SilverStripe\i18n\i18n; -use SilverStripe\i18n\TextCollection\i18nTextCollector; use SilverStripe\i18n\Messages\YamlWriter; +use SilverStripe\i18n\Tests\i18nTest\TestDataObject; use SilverStripe\i18n\Tests\i18nTextCollectorTest\Collector; +use SilverStripe\i18n\TextCollection\i18nTextCollector; +use SilverStripe\Security\Member; class i18nTextCollectorTest extends SapphireTest { @@ -709,6 +712,43 @@ PHP; ); } + public function testCollectFromORM() + { + // note: Disable _fakewebroot manifest for this test + $this->popManifests(); + + // update config because test can't load config from actual class + Config::inst()->update(TestDataObject::class, 'db', [ + 'MyProperty' => 'Varchar', + 'MyUntranslatedProperty' => 'Text' + ]); + Config::inst()->update(TestDataObject::class, 'has_one', [ + 'HasOneRelation' => Member::class + ]); + Config::inst()->update(TestDataObject::class, 'has_many', [ + 'HasManyRelation' => Member::class + ]); + Config::inst()->update(TestDataObject::class, 'many_many', [ + 'ManyManyRelation' => Member::class + ]); + + $c = i18nTextCollector::create(); + + // Collect from MyObject.php + $filePath = __DIR__ . '/i18nTest/TestDataObject.php'; + $matches = $c->collectFromORM($filePath); + $this->assertEquals( + [ + 'SilverStripe\i18n\Tests\i18nTest\TestDataObject.db_MyProperty' => 'My property', + 'SilverStripe\i18n\Tests\i18nTest\TestDataObject.db_MyUntranslatedProperty' => 'My untranslated property', + 'SilverStripe\i18n\Tests\i18nTest\TestDataObject.has_one_HasOneRelation' => 'Has one relation', + 'SilverStripe\i18n\Tests\i18nTest\TestDataObject.has_many_HasManyRelation' => 'Has many relation', + 'SilverStripe\i18n\Tests\i18nTest\TestDataObject.many_many_ManyManyRelation' => 'Many many relation', + ], + $matches + ); + } + /** * Test that duplicate keys are resolved to the appropriate modules */