From 9c9e584ae0a0fac75c250cab5b2d6d227d5f92b1 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Thu, 25 Sep 2025 10:31:56 +0200 Subject: [PATCH 1/5] WIP: FEATURE: Introduce interface to extract and reapply translatable fields from valueobjects --- .../NodeTranslationService.php | 22 ++++-- .../TranslatablePropertyName.php | 22 +++++- .../TranslatablePropertyNames.php | 18 ++++- .../TranslatablePropertyNamesFactory.php | 9 ++- .../TranslationObjectConnectorInterface.php | 23 +++++++ Classes/Utility/ArrayFlatteningUtility.php | 54 +++++++++++++++ .../Utility/ArrayFlatteningUtilityTest.php | 67 +++++++++++++++++++ 7 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 Classes/Domain/TranslationObjectConnectorInterface.php create mode 100644 Classes/Utility/ArrayFlatteningUtility.php create mode 100644 Tests/Unit/Utility/ArrayFlatteningUtilityTest.php diff --git a/Classes/ContentRepository/NodeTranslationService.php b/Classes/ContentRepository/NodeTranslationService.php index 94a131d..15ea006 100644 --- a/Classes/ContentRepository/NodeTranslationService.php +++ b/Classes/ContentRepository/NodeTranslationService.php @@ -15,6 +15,7 @@ use Neos\Neos\Utility\NodeUriPathSegmentGenerator; use Sitegeist\LostInTranslation\Domain\TranslatableProperty\TranslatablePropertyNamesFactory; use Sitegeist\LostInTranslation\Domain\TranslationServiceInterface; +use Sitegeist\LostInTranslation\Utility\ArrayFlatteningUtility; /** * @Flow\Scope("singleton") @@ -297,12 +298,18 @@ public function translateNode(NodeInterface $sourceNode, NodeInterface $targetNo if ((trim(strip_tags($propertyValue))) == "") { continue; } - $propertiesToTranslate[$propertyName] = $propertyValue; + if ($connectorName = $translatableProperties->getTranslationObjectConnector($propertyName)) { + $propertiesToTranslate[$propertyName] = $connectorName::extractTranslations($propertyValue); + } else { + $propertiesToTranslate[$propertyName] = $propertyValue; + } unset($properties[$propertyName]); } if (count($propertiesToTranslate) > 0) { - $translatedProperties = $this->translationService->translate($propertiesToTranslate, $targetLanguage, $sourceLanguage); + $propertiesToTranslateDeflated = ArrayFlatteningUtility::deflate($propertiesToTranslate); + $translatedPropertiesDeflated = $this->translationService->translate($propertiesToTranslateDeflated, $targetLanguage, $sourceLanguage); + $translatedProperties = ArrayFlatteningUtility::enflate($translatedPropertiesDeflated); $properties = array_merge($translatedProperties, $properties); } @@ -311,9 +318,14 @@ public function translateNode(NodeInterface $sourceNode, NodeInterface $targetNo if ($propertyName === 'uriPathSegment' && !preg_match('/^[a-z0-9\-]+$/i', $propertyValue)) { $propertyValue = $this->nodeUriPathSegmentGenerator->generateUriPathSegment(null, $propertyValue); } - - if ($targetNode->getProperty($propertyName) !== $propertyValue) { - $targetNode->setProperty($propertyName, $propertyValue); + if (is_array($propertyValue)) { + $connectorName = $translatableProperties->getTranslationObjectConnector($propertyName); + $targetValue = $connectorName::applyTranslations($targetNode->getProperty($propertyName), $propertyValue); + } else { + $targetValue = $propertyValue; + } + if ($targetNode->getProperty($propertyName) !== $targetValue) { + $targetNode->setProperty($propertyName, $targetValue); } } } diff --git a/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php b/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php index 6cb453d..f7d4849 100644 --- a/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php +++ b/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php @@ -4,6 +4,8 @@ namespace Sitegeist\LostInTranslation\Domain\TranslatableProperty; +use Sitegeist\LostInTranslation\Domain\TranslationObjectConnectorInterface; + class TranslatablePropertyName { /** @@ -11,13 +13,31 @@ class TranslatablePropertyName */ protected $name; - public function __construct(string $name) + /** + * @var class-string + */ + protected $translationObjectConnector; + + /** + * @param string $name + * @param class-string|null $translationObjectConnector + */ + public function __construct(string $name, ?string $translationObjectConnector = null) { $this->name = $name; + $this->translationObjectConnector = $translationObjectConnector; } public function getName(): string { return $this->name; } + + /** + * @return class-string|null + */ + public function getTranslationObjectConnector(): ?string + { + return $this->translationObjectConnector; + } } diff --git a/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php b/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php index 78769a2..4666f33 100644 --- a/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php +++ b/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php @@ -4,6 +4,8 @@ namespace Sitegeist\LostInTranslation\Domain\TranslatableProperty; +use Sitegeist\LostInTranslation\Domain\TranslationObjectConnectorInterface; + /** * @implements \IteratorAggregate */ @@ -29,7 +31,21 @@ public function isTranslatable(string $propertyName): bool } /** - * @return \ArrayIterator + * @param string $propertyName + * @return class-string|null + */ + public function getTranslationObjectConnector(string $propertyName): ?string + { + foreach ($this->translatableProperties as $translatableProperty) { + if ($translatableProperty->getName() == $propertyName) { + return $translatableProperty->getTranslationObjectConnector(); + } + } + return null; + } + + /** + * @return \ArrayIterator */ public function getIterator(): \Iterator { diff --git a/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php b/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php index a7ac22e..c41ccce 100644 --- a/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php +++ b/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php @@ -6,6 +6,7 @@ use Neos\Flow\Annotations as Flow; use Neos\ContentRepository\Domain\Model\NodeType; +use Sitegeist\LostInTranslation\Domain\TranslationObjectConnectorInterface; class TranslatablePropertyNamesFactory { @@ -28,6 +29,7 @@ public function createForNodeType(NodeType $nodeType): TranslatablePropertyNames $propertyDefinitions = $nodeType->getProperties(); $translateProperties = []; foreach ($propertyDefinitions as $propertyName => $propertyDefinition) { + $translationObjectConnector = $propertyDefinition['options']['translationObjectConnector'] ?? null; if (array_key_exists('type', $propertyDefinition) && $propertyDefinition['type'] !== 'string') { continue; } @@ -35,14 +37,15 @@ public function createForNodeType(NodeType $nodeType): TranslatablePropertyNames continue; // do not translate (inline-editable) properties explicitly set to: 'automaticTranslation: false' } if ($this->translateInlineEditables && ($propertyDefinitions[$propertyName]['ui']['inlineEditable'] ?? false)) { - $translateProperties[] = new TranslatablePropertyName($propertyName); + $translateProperties[] = new TranslatablePropertyName($propertyName, $translationObjectConnector); continue; } // @deprecated Fallback for renamed setting translateOnAdoption -> automaticTranslation - if ($propertyDefinition['options']['automaticTranslation'] ?? ($propertyDefinition['options']['translateOnAdoption'] ?? false)) { - $translateProperties[] = new TranslatablePropertyName($propertyName); + if ($propertyDefinition[ 'options' ][ 'automaticTranslation' ] ?? ($propertyDefinition[ 'options' ][ 'translateOnAdoption' ] ?? false)) { + $translateProperties[] = new TranslatablePropertyName($propertyName, $translationObjectConnector); continue; } + } $this->firstLevelCache[$nodeType->getName()] = new TranslatablePropertyNames(...$translateProperties); return $this->firstLevelCache[$nodeType->getName()]; diff --git a/Classes/Domain/TranslationObjectConnectorInterface.php b/Classes/Domain/TranslationObjectConnectorInterface.php new file mode 100644 index 0000000..2088a0c --- /dev/null +++ b/Classes/Domain/TranslationObjectConnectorInterface.php @@ -0,0 +1,23 @@ + + */ + public static function extractTranslations(object $object): array; + + /** + * @param T $object + * @param array $translations + * @return T + */ + public static function applyTranslations(object $object, array $translations): object; +} diff --git a/Classes/Utility/ArrayFlatteningUtility.php b/Classes/Utility/ArrayFlatteningUtility.php new file mode 100644 index 0000000..1ccab46 --- /dev/null +++ b/Classes/Utility/ArrayFlatteningUtility.php @@ -0,0 +1,54 @@ +> $array to deflate + * @param string $seperator seperator that is not used in array keys + * @return array + */ + public static function deflate(array $array, string $seperator = '.'): array + { + $result = []; + foreach ($array as $key => $value) { + if (is_string($value)) { + $result[$key] = $value; + } elseif(is_array($value)) { + foreach ($value as $subkey => $subvalue) { + $result[$key . $seperator . $subkey] = $subvalue; + } + } + } + return $result; + } + + /** + * @param array $array to enflate + * @param string $seperator seperator that is not used in array keys + * @return array> + */ + public static function enflate(array $array, string $seperator = '.'): array + { + $result = []; + foreach ($array as $key => $value) { + if (str_contains($key, $seperator)) { + list($mainKey, $subKey) = explode($seperator, $key, 2); + if (array_key_exists($mainKey, $result)) { + $result[$mainKey][$subKey] = $value; + } else { + $result[$mainKey] = [$subKey => $value]; + } + } else { + $result[$key] = $value; + } + } + return $result; + } +} diff --git a/Tests/Unit/Utility/ArrayFlatteningUtilityTest.php b/Tests/Unit/Utility/ArrayFlatteningUtilityTest.php new file mode 100644 index 0000000..775aa14 --- /dev/null +++ b/Tests/Unit/Utility/ArrayFlatteningUtilityTest.php @@ -0,0 +1,67 @@ + [ + [], + [], + '.' + ]; + + yield 'simple array' => [ + ['foo' => 'bar', 'bar' => 'baz'], + ['foo' => 'bar', 'bar' => 'baz'], + '.' + ]; + + yield 'nested array' => [ + ['foo' => ['bar' => 'baz', 'baz' => 'bam'], 'bar' => ['baz' => 'bam']], + ['foo.bar' => 'baz', 'foo.baz' => 'bam', 'bar.baz' => 'bam'], + '.' + ]; + + yield 'mixed array' => [ + ['foo' => ['bar' => 'baz', 'baz' => 'bam'], 'bar' => ['baz' => 'bam'], 'baz' => 'bam'], + ['foo.bar' => 'baz', 'foo.baz' => 'bam', 'bar.baz' => 'bam', 'baz' => 'bam'], + '.' + ]; + + yield 'deeply nested mixed array' => [ + ['foo' => ['bar' => 'baz', 'baz' => 'bam'], 'bar' => ['baz' => 'bam'], 'baz' => 'bam'], + ['foo.bar' => 'baz', 'foo.baz' => 'bam', 'bar.baz' => 'bam', 'baz' => 'bam'], + '.' + ]; + } + + /** + * @dataProvider provideExamples + * @param array> $enflated + * @param array $deflated + * @param string $seperator + * @return void + */ + public function testArrayDeflation(array $enflated, array $deflated, string $seperator): void + { + $this->assertEquals($deflated, ArrayFlatteningUtility::deflate($enflated, $seperator)); + } + + /** + * @dataProvider provideExamples + * @param array> $enflated + * @param array $deflated + * @param string $seperator + * @return void + */ + public function testArrayEnflation(array $enflated, array $deflated, string $seperator): void + { + $this->assertEquals($enflated, ArrayFlatteningUtility::enflate($deflated, $seperator)); + } +} From 97f2be58adcd5de419cdb55797ff442364f6318f Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Thu, 25 Sep 2025 15:38:34 +0200 Subject: [PATCH 2/5] 99776: Make Linter happy --- .../NodeTranslationService.php | 19 ++- .../TranslatablePropertyName.php | 18 +-- .../TranslatablePropertyNames.php | 8 +- .../TranslatablePropertyNamesFactory.php | 34 +++--- ....php => TranslationConnectorInterface.php} | 11 +- Classes/Utility/ArrayFlatteningUtility.php | 33 ++--- .../TranslatablePropertyNamesFactoryTest.php | 113 ++++++++++++++++++ .../Utility/ArrayFlatteningUtilityTest.php | 23 ++-- 8 files changed, 192 insertions(+), 67 deletions(-) rename Classes/Domain/{TranslationObjectConnectorInterface.php => TranslationConnectorInterface.php} (66%) create mode 100644 Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php diff --git a/Classes/ContentRepository/NodeTranslationService.php b/Classes/ContentRepository/NodeTranslationService.php index 15ea006..e78f7d0 100644 --- a/Classes/ContentRepository/NodeTranslationService.php +++ b/Classes/ContentRepository/NodeTranslationService.php @@ -288,26 +288,31 @@ public function translateNode(NodeInterface $sourceNode, NodeInterface $targetNo /** @phpstan-ignore arguments.count */ $properties = (array)$sourceNode->getProperties(true); $propertiesToTranslate = []; + foreach ($properties as $propertyName => $propertyValue) { - if (empty($propertyValue) || !is_string($propertyValue)) { + if (empty($propertyValue)) { continue; } + assert($propertyName !== ''); + assert($propertyValue !== ''); if (!$translatableProperties->isTranslatable($propertyName)) { continue; } - if ((trim(strip_tags($propertyValue))) == "") { + if (is_string($propertyValue) && trim(strip_tags($propertyValue)) === "") { continue; } if ($connectorName = $translatableProperties->getTranslationObjectConnector($propertyName)) { $propertiesToTranslate[$propertyName] = $connectorName::extractTranslations($propertyValue); + unset($properties[$propertyName]); } else { $propertiesToTranslate[$propertyName] = $propertyValue; + unset($properties[$propertyName]); } - unset($properties[$propertyName]); } if (count($propertiesToTranslate) > 0) { $propertiesToTranslateDeflated = ArrayFlatteningUtility::deflate($propertiesToTranslate); + /** @var array $translatedPropertiesDeflated */ $translatedPropertiesDeflated = $this->translationService->translate($propertiesToTranslateDeflated, $targetLanguage, $sourceLanguage); $translatedProperties = ArrayFlatteningUtility::enflate($translatedPropertiesDeflated); $properties = array_merge($translatedProperties, $properties); @@ -319,8 +324,12 @@ public function translateNode(NodeInterface $sourceNode, NodeInterface $targetNo $propertyValue = $this->nodeUriPathSegmentGenerator->generateUriPathSegment(null, $propertyValue); } if (is_array($propertyValue)) { - $connectorName = $translatableProperties->getTranslationObjectConnector($propertyName); - $targetValue = $connectorName::applyTranslations($targetNode->getProperty($propertyName), $propertyValue); + $targetValue = $targetNode->getProperty($propertyName); + if ($connectorName = $translatableProperties->getTranslationObjectConnector($propertyName)) { + if (is_object($targetValue)) { + $targetValue = $connectorName::applyTranslations($targetValue, $propertyValue); + } + } } else { $targetValue = $propertyValue; } diff --git a/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php b/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php index f7d4849..b304b40 100644 --- a/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php +++ b/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php @@ -4,7 +4,7 @@ namespace Sitegeist\LostInTranslation\Domain\TranslatableProperty; -use Sitegeist\LostInTranslation\Domain\TranslationObjectConnectorInterface; +use Sitegeist\LostInTranslation\Domain\TranslationConnectorInterface; class TranslatablePropertyName { @@ -14,18 +14,18 @@ class TranslatablePropertyName protected $name; /** - * @var class-string + * @var class-string>|null */ - protected $translationObjectConnector; + protected $translationConnectorClassName; /** * @param string $name - * @param class-string|null $translationObjectConnector + * @param class-string>|null $translationConnectorClassName */ - public function __construct(string $name, ?string $translationObjectConnector = null) + public function __construct(string $name, ?string $translationConnectorClassName = null) { $this->name = $name; - $this->translationObjectConnector = $translationObjectConnector; + $this->translationConnectorClassName = $translationConnectorClassName; } public function getName(): string @@ -34,10 +34,10 @@ public function getName(): string } /** - * @return class-string|null + * @return class-string>|null */ - public function getTranslationObjectConnector(): ?string + public function getTranslationConnectorClassName(): ?string { - return $this->translationObjectConnector; + return $this->translationConnectorClassName; } } diff --git a/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php b/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php index 4666f33..bbc677c 100644 --- a/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php +++ b/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php @@ -4,7 +4,7 @@ namespace Sitegeist\LostInTranslation\Domain\TranslatableProperty; -use Sitegeist\LostInTranslation\Domain\TranslationObjectConnectorInterface; +use Sitegeist\LostInTranslation\Domain\TranslationConnectorInterface; /** * @implements \IteratorAggregate @@ -32,20 +32,20 @@ public function isTranslatable(string $propertyName): bool /** * @param string $propertyName - * @return class-string|null + * @return class-string>|null */ public function getTranslationObjectConnector(string $propertyName): ?string { foreach ($this->translatableProperties as $translatableProperty) { if ($translatableProperty->getName() == $propertyName) { - return $translatableProperty->getTranslationObjectConnector(); + return $translatableProperty->getTranslationConnectorClassName(); } } return null; } /** - * @return \ArrayIterator + * @return \ArrayIterator */ public function getIterator(): \Iterator { diff --git a/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php b/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php index c41ccce..1f63142 100644 --- a/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php +++ b/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php @@ -6,7 +6,9 @@ use Neos\Flow\Annotations as Flow; use Neos\ContentRepository\Domain\Model\NodeType; -use Sitegeist\LostInTranslation\Domain\TranslationObjectConnectorInterface; +use Sitegeist\LostInTranslation\Domain\TranslatableProperty\TranslatablePropertyName; +use Sitegeist\LostInTranslation\Domain\TranslatableProperty\TranslatablePropertyNames; +use Sitegeist\LostInTranslation\Domain\TranslationConnectorInterface; class TranslatablePropertyNamesFactory { @@ -29,23 +31,23 @@ public function createForNodeType(NodeType $nodeType): TranslatablePropertyNames $propertyDefinitions = $nodeType->getProperties(); $translateProperties = []; foreach ($propertyDefinitions as $propertyName => $propertyDefinition) { - $translationObjectConnector = $propertyDefinition['options']['translationObjectConnector'] ?? null; - if (array_key_exists('type', $propertyDefinition) && $propertyDefinition['type'] !== 'string') { - continue; - } - if (isset($propertyDefinition['options']['automaticTranslation']) && !$propertyDefinition['options']['automaticTranslation']) { - continue; // do not translate (inline-editable) properties explicitly set to: 'automaticTranslation: false' - } - if ($this->translateInlineEditables && ($propertyDefinitions[$propertyName]['ui']['inlineEditable'] ?? false)) { - $translateProperties[] = new TranslatablePropertyName($propertyName, $translationObjectConnector); - continue; - } + $type = $propertyDefinition['type']; + // @deprecated Fallback for renamed setting translateOnAdoption -> automaticTranslation - if ($propertyDefinition[ 'options' ][ 'automaticTranslation' ] ?? ($propertyDefinition[ 'options' ][ 'translateOnAdoption' ] ?? false)) { - $translateProperties[] = new TranslatablePropertyName($propertyName, $translationObjectConnector); - continue; - } + $automaticTranslationIsEnabled = $propertyDefinition[ 'options' ][ 'automaticTranslation' ] + ?? ($propertyDefinition[ 'options' ][ 'translateOnAdoption' ] ?? false); + $isInlineEditable = $propertyDefinition['ui']['inlineEditable'] + ?? false; + $translationConnector = $propertyDefinition['options']['automaticTranslationConnector'] + ?? null; + if ($type === "string" && $this->translateInlineEditables && $isInlineEditable) { + $translateProperties[] = new TranslatablePropertyName($propertyName); + } elseif ($type === "string" && $automaticTranslationIsEnabled) { + $translateProperties[] = new TranslatablePropertyName($propertyName); + } elseif ($translationConnector && $automaticTranslationIsEnabled) { + $translateProperties[] = new TranslatablePropertyName($propertyName, $translationConnector); + } } $this->firstLevelCache[$nodeType->getName()] = new TranslatablePropertyNames(...$translateProperties); return $this->firstLevelCache[$nodeType->getName()]; diff --git a/Classes/Domain/TranslationObjectConnectorInterface.php b/Classes/Domain/TranslationConnectorInterface.php similarity index 66% rename from Classes/Domain/TranslationObjectConnectorInterface.php rename to Classes/Domain/TranslationConnectorInterface.php index 2088a0c..c127ed9 100644 --- a/Classes/Domain/TranslationObjectConnectorInterface.php +++ b/Classes/Domain/TranslationConnectorInterface.php @@ -1,22 +1,23 @@ + * @return array */ public static function extractTranslations(object $object): array; /** * @param T $object - * @param array $translations + * @param array $translations * @return T */ public static function applyTranslations(object $object, array $translations): object; diff --git a/Classes/Utility/ArrayFlatteningUtility.php b/Classes/Utility/ArrayFlatteningUtility.php index 1ccab46..32d4f96 100644 --- a/Classes/Utility/ArrayFlatteningUtility.php +++ b/Classes/Utility/ArrayFlatteningUtility.php @@ -1,4 +1,5 @@ > $array to deflate - * @param string $seperator seperator that is not used in array keys - * @return array + * @param array> $array to deflate + * @return array */ - public static function deflate(array $array, string $seperator = '.'): array + public static function deflate(array $array): array { $result = []; foreach ($array as $key => $value) { + assert($key !== ''); if (is_string($value)) { $result[$key] = $value; - } elseif(is_array($value)) { + } elseif (is_array($value)) { foreach ($value as $subkey => $subvalue) { - $result[$key . $seperator . $subkey] = $subvalue; + $result[$key . self::SEPERATOR . $subkey] = $subvalue; } } } @@ -30,17 +33,19 @@ public static function deflate(array $array, string $seperator = '.'): array } /** - * @param array $array to enflate - * @param string $seperator seperator that is not used in array keys - * @return array> + * @param array $array to enflate + * @return array> */ - public static function enflate(array $array, string $seperator = '.'): array + public static function enflate(array $array): array { $result = []; foreach ($array as $key => $value) { - if (str_contains($key, $seperator)) { - list($mainKey, $subKey) = explode($seperator, $key, 2); - if (array_key_exists($mainKey, $result)) { + assert($key !== ''); + if (str_contains($key, self::SEPERATOR)) { + list($mainKey, $subKey) = explode(self::SEPERATOR, $key, 2); + assert($mainKey !== ''); + assert($subKey !== ''); + if (array_key_exists($mainKey, $result) && is_array($result[$mainKey])) { $result[$mainKey][$subKey] = $value; } else { $result[$mainKey] = [$subKey => $value]; diff --git a/Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php b/Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php new file mode 100644 index 0000000..fe3e5f6 --- /dev/null +++ b/Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php @@ -0,0 +1,113 @@ +translatablePropertyNamesFactory = new TranslatablePropertyNamesFactory(); + $this->inject($this->translatablePropertyNamesFactory, 'translateInlineEditables', true); + } + + public function exampleProvider(): \Generator + { + yield 'empty' => [ + new NodeType('Example', [], []), + new TranslatablePropertyNames(), + ]; + + yield 'ignored' => [ + new NodeType('Example', [], [ + 'properties' => [ + 'stringProperty' => [ + 'type' => 'string', + ] + ] + ]), + new TranslatablePropertyNames(), + ]; + + yield 'inline editable' => [ + new NodeType('Example', [], [ + 'properties' => [ + 'inlineEditableTextProperty' => [ + 'type' => 'string', + 'ui' => [ + 'inlineEditable' => true, + ] + ] + ] + ]), + new TranslatablePropertyNames( + new TranslatablePropertyName('inlineEditableTextProperty'), + ), + ]; + + yield 'automaticTranslation' => [ + new NodeType('Example', [], [ + 'properties' => [ + 'textPropertyWithOptions' => [ + 'type' => 'string', + 'options' => [ + 'automaticTranslation' => true, + ] + ] + ] + ]), + new TranslatablePropertyNames( + new TranslatablePropertyName('textPropertyWithOptions'), + ), + ]; + + yield 'value object property' => [ + new NodeType('Example', [], [ + 'properties' => [ + 'valueObjectProperty' => [ + 'type' => 'Some\Class', + 'options' => [ + 'automaticTranslationConnector' => 'Some\Class\Name', + 'automaticTranslation' => true + ] + ] + ] + ]), + new TranslatablePropertyNames( + new TranslatablePropertyName('valueObjectProperty', 'Some\Class\Name'), + ), + ]; + + yield 'test image property' => [ + new NodeType('Image', [], [ + 'properties' => [ + 'image' => [ + 'type' => 'Sitegeist\Kaleidoscope\ValueObjects\ImageSourceProxy', + 'options' => [ + 'automaticTranslationConnector' => 'Sitegeist\Kaleidoscope\ValueObjects\Connector\ImageSourceProxyLostInTranslationConnector', + 'automaticTranslation' => true + ] + ] + ] + ]), + new TranslatablePropertyNames( + new TranslatablePropertyName('image', 'Sitegeist\Kaleidoscope\ValueObjects\Connector\ImageSourceProxyLostInTranslationConnector'), + ), + ]; + } + + /** + * @dataProvider exampleProvider + */ + public function testDetectionOfTranslatableProperties(NodeType $nodeType, TranslatablePropertyNames $expectedPropertyNames): void { + $this->assertEquals($expectedPropertyNames, $this->translatablePropertyNamesFactory->createForNodeType($nodeType) ); + } +} diff --git a/Tests/Unit/Utility/ArrayFlatteningUtilityTest.php b/Tests/Unit/Utility/ArrayFlatteningUtilityTest.php index 775aa14..cf118d6 100644 --- a/Tests/Unit/Utility/ArrayFlatteningUtilityTest.php +++ b/Tests/Unit/Utility/ArrayFlatteningUtilityTest.php @@ -12,32 +12,27 @@ public function provideExamples(): \Generator { yield 'empty array' => [ [], - [], - '.' + [] ]; yield 'simple array' => [ ['foo' => 'bar', 'bar' => 'baz'], - ['foo' => 'bar', 'bar' => 'baz'], - '.' + ['foo' => 'bar', 'bar' => 'baz'] ]; yield 'nested array' => [ ['foo' => ['bar' => 'baz', 'baz' => 'bam'], 'bar' => ['baz' => 'bam']], - ['foo.bar' => 'baz', 'foo.baz' => 'bam', 'bar.baz' => 'bam'], - '.' + ['foo.bar' => 'baz', 'foo.baz' => 'bam', 'bar.baz' => 'bam'] ]; yield 'mixed array' => [ ['foo' => ['bar' => 'baz', 'baz' => 'bam'], 'bar' => ['baz' => 'bam'], 'baz' => 'bam'], - ['foo.bar' => 'baz', 'foo.baz' => 'bam', 'bar.baz' => 'bam', 'baz' => 'bam'], - '.' + ['foo.bar' => 'baz', 'foo.baz' => 'bam', 'bar.baz' => 'bam', 'baz' => 'bam'] ]; yield 'deeply nested mixed array' => [ ['foo' => ['bar' => 'baz', 'baz' => 'bam'], 'bar' => ['baz' => 'bam'], 'baz' => 'bam'], - ['foo.bar' => 'baz', 'foo.baz' => 'bam', 'bar.baz' => 'bam', 'baz' => 'bam'], - '.' + ['foo.bar' => 'baz', 'foo.baz' => 'bam', 'bar.baz' => 'bam', 'baz' => 'bam'] ]; } @@ -48,9 +43,9 @@ public function provideExamples(): \Generator * @param string $seperator * @return void */ - public function testArrayDeflation(array $enflated, array $deflated, string $seperator): void + public function testArrayDeflation(array $enflated, array $deflated): void { - $this->assertEquals($deflated, ArrayFlatteningUtility::deflate($enflated, $seperator)); + $this->assertEquals($deflated, ArrayFlatteningUtility::deflate($enflated)); } /** @@ -60,8 +55,8 @@ public function testArrayDeflation(array $enflated, array $deflated, string $sep * @param string $seperator * @return void */ - public function testArrayEnflation(array $enflated, array $deflated, string $seperator): void + public function testArrayEnflation(array $enflated, array $deflated): void { - $this->assertEquals($enflated, ArrayFlatteningUtility::enflate($deflated, $seperator)); + $this->assertEquals($enflated, ArrayFlatteningUtility::enflate($deflated)); } } From 0ba456224af3a410de50bdd8fca3ca4769ed00f0 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Thu, 25 Sep 2025 17:54:04 +0200 Subject: [PATCH 3/5] TASK: Make connectors singletons that are retrieved from the object manager for complex cases This will allow to inject dependencies if needed. --- .../NodeTranslationService.php | 8 +- .../TranslatablePropertyName.php | 16 ++-- .../TranslatablePropertyNames.php | 6 +- .../TranslatablePropertyNamesFactory.php | 21 ++++- .../Domain/TranslationConnectorInterface.php | 4 +- Configuration/Settings.yaml | 8 ++ .../TranslatablePropertyNamesFactoryTest.php | 79 +++++++++++-------- 7 files changed, 87 insertions(+), 55 deletions(-) diff --git a/Classes/ContentRepository/NodeTranslationService.php b/Classes/ContentRepository/NodeTranslationService.php index e78f7d0..ca0658b 100644 --- a/Classes/ContentRepository/NodeTranslationService.php +++ b/Classes/ContentRepository/NodeTranslationService.php @@ -301,8 +301,8 @@ public function translateNode(NodeInterface $sourceNode, NodeInterface $targetNo if (is_string($propertyValue) && trim(strip_tags($propertyValue)) === "") { continue; } - if ($connectorName = $translatableProperties->getTranslationObjectConnector($propertyName)) { - $propertiesToTranslate[$propertyName] = $connectorName::extractTranslations($propertyValue); + if ($connector = $translatableProperties->getTranslationObjectConnector($propertyName)) { + $propertiesToTranslate[$propertyName] = $connector->extractTranslations($propertyValue); unset($properties[$propertyName]); } else { $propertiesToTranslate[$propertyName] = $propertyValue; @@ -325,9 +325,9 @@ public function translateNode(NodeInterface $sourceNode, NodeInterface $targetNo } if (is_array($propertyValue)) { $targetValue = $targetNode->getProperty($propertyName); - if ($connectorName = $translatableProperties->getTranslationObjectConnector($propertyName)) { + if ($connector = $translatableProperties->getTranslationObjectConnector($propertyName)) { if (is_object($targetValue)) { - $targetValue = $connectorName::applyTranslations($targetValue, $propertyValue); + $targetValue = $connector->applyTranslations($targetValue, $propertyValue); } } } else { diff --git a/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php b/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php index b304b40..3da51e8 100644 --- a/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php +++ b/Classes/Domain/TranslatableProperty/TranslatablePropertyName.php @@ -14,18 +14,18 @@ class TranslatablePropertyName protected $name; /** - * @var class-string>|null + * @var TranslationConnectorInterface|null */ - protected $translationConnectorClassName; + protected $translationConnector; /** * @param string $name - * @param class-string>|null $translationConnectorClassName + * @param TranslationConnectorInterface|null $translationConnector */ - public function __construct(string $name, ?string $translationConnectorClassName = null) + public function __construct(string $name, ?TranslationConnectorInterface $translationConnector = null) { $this->name = $name; - $this->translationConnectorClassName = $translationConnectorClassName; + $this->translationConnector = $translationConnector; } public function getName(): string @@ -34,10 +34,10 @@ public function getName(): string } /** - * @return class-string>|null + * @return TranslationConnectorInterface|null */ - public function getTranslationConnectorClassName(): ?string + public function getTranslationConnector(): ?TranslationConnectorInterface { - return $this->translationConnectorClassName; + return $this->translationConnector; } } diff --git a/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php b/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php index bbc677c..969ce1f 100644 --- a/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php +++ b/Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php @@ -32,13 +32,13 @@ public function isTranslatable(string $propertyName): bool /** * @param string $propertyName - * @return class-string>|null + * @return TranslationConnectorInterface|null */ - public function getTranslationObjectConnector(string $propertyName): ?string + public function getTranslationObjectConnector(string $propertyName): ?TranslationConnectorInterface { foreach ($this->translatableProperties as $translatableProperty) { if ($translatableProperty->getName() == $propertyName) { - return $translatableProperty->getTranslationConnectorClassName(); + return $translatableProperty->getTranslationConnector(); } } return null; diff --git a/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php b/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php index 1f63142..3f564fa 100644 --- a/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php +++ b/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php @@ -6,8 +6,7 @@ use Neos\Flow\Annotations as Flow; use Neos\ContentRepository\Domain\Model\NodeType; -use Sitegeist\LostInTranslation\Domain\TranslatableProperty\TranslatablePropertyName; -use Sitegeist\LostInTranslation\Domain\TranslatableProperty\TranslatablePropertyNames; +use Neos\Flow\ObjectManagement\ObjectManagerInterface; use Sitegeist\LostInTranslation\Domain\TranslationConnectorInterface; class TranslatablePropertyNamesFactory @@ -18,6 +17,18 @@ class TranslatablePropertyNamesFactory */ protected $translateInlineEditables; + /** + * @Flow\InjectConfiguration(path="nodeTranslation.translationConnectors") + * @var array + */ + protected $translationConnectors; + + /** + * @Flow\Inject + * @var ObjectManagerInterface + */ + protected $objectManager; + /** * @var array */ @@ -38,7 +49,7 @@ public function createForNodeType(NodeType $nodeType): TranslatablePropertyNames ?? ($propertyDefinition[ 'options' ][ 'translateOnAdoption' ] ?? false); $isInlineEditable = $propertyDefinition['ui']['inlineEditable'] ?? false; - $translationConnector = $propertyDefinition['options']['automaticTranslationConnector'] + $translationConnector = $this->translationConnectors[$type] ?? null; if ($type === "string" && $this->translateInlineEditables && $isInlineEditable) { @@ -46,7 +57,9 @@ public function createForNodeType(NodeType $nodeType): TranslatablePropertyNames } elseif ($type === "string" && $automaticTranslationIsEnabled) { $translateProperties[] = new TranslatablePropertyName($propertyName); } elseif ($translationConnector && $automaticTranslationIsEnabled) { - $translateProperties[] = new TranslatablePropertyName($propertyName, $translationConnector); + $translationConnectorInstance = $this->objectManager->get($translationConnector); + assert($translationConnectorInstance instanceof TranslationConnectorInterface); + $translateProperties[] = new TranslatablePropertyName($propertyName, $translationConnectorInstance); } } $this->firstLevelCache[$nodeType->getName()] = new TranslatablePropertyNames(...$translateProperties); diff --git a/Classes/Domain/TranslationConnectorInterface.php b/Classes/Domain/TranslationConnectorInterface.php index c127ed9..baed932 100644 --- a/Classes/Domain/TranslationConnectorInterface.php +++ b/Classes/Domain/TranslationConnectorInterface.php @@ -13,12 +13,12 @@ interface TranslationConnectorInterface * @param T $object * @return array */ - public static function extractTranslations(object $object): array; + public function extractTranslations(object $object): array; /** * @param T $object * @param array $translations * @return T */ - public static function applyTranslations(object $object, array $translations): object; + public function applyTranslations(object $object, array $translations): object; } diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index c70c596..23db989 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -83,3 +83,11 @@ Sitegeist: skipAuthorizationChecks: false excludedNodePaths: [] + + # + # Connectors to translate value object properties + # + # for each value object type a clas implementing the TranslationConnectorInterface + # can be configured to extract and apply translations + # + translationConnectors: [] diff --git a/Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php b/Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php index fe3e5f6..90215bd 100644 --- a/Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php +++ b/Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php @@ -4,19 +4,24 @@ namespace Sitegeist\LostInTranslation\Tests\Unit\Domain; use Neos\ContentRepository\Domain\Model\NodeType; +use Neos\Flow\ObjectManagement\ObjectManagerInterface; use Neos\Flow\Tests\UnitTestCase; use Sitegeist\LostInTranslation\Domain\TranslatableProperty\TranslatablePropertyName; use Sitegeist\LostInTranslation\Domain\TranslatableProperty\TranslatablePropertyNames; use Sitegeist\LostInTranslation\Domain\TranslatableProperty\TranslatablePropertyNamesFactory; +use Sitegeist\LostInTranslation\Domain\TranslationConnectorInterface; class TranslatablePropertyNamesFactoryTest extends UnitTestCase { private TranslatablePropertyNamesFactory $translatablePropertyNamesFactory; + public function setUp(): void { + $this->translatablePropertyNamesFactory = new TranslatablePropertyNamesFactory(); $this->inject($this->translatablePropertyNamesFactory, 'translateInlineEditables', true); + } public function exampleProvider(): \Generator @@ -68,40 +73,6 @@ public function exampleProvider(): \Generator new TranslatablePropertyName('textPropertyWithOptions'), ), ]; - - yield 'value object property' => [ - new NodeType('Example', [], [ - 'properties' => [ - 'valueObjectProperty' => [ - 'type' => 'Some\Class', - 'options' => [ - 'automaticTranslationConnector' => 'Some\Class\Name', - 'automaticTranslation' => true - ] - ] - ] - ]), - new TranslatablePropertyNames( - new TranslatablePropertyName('valueObjectProperty', 'Some\Class\Name'), - ), - ]; - - yield 'test image property' => [ - new NodeType('Image', [], [ - 'properties' => [ - 'image' => [ - 'type' => 'Sitegeist\Kaleidoscope\ValueObjects\ImageSourceProxy', - 'options' => [ - 'automaticTranslationConnector' => 'Sitegeist\Kaleidoscope\ValueObjects\Connector\ImageSourceProxyLostInTranslationConnector', - 'automaticTranslation' => true - ] - ] - ] - ]), - new TranslatablePropertyNames( - new TranslatablePropertyName('image', 'Sitegeist\Kaleidoscope\ValueObjects\Connector\ImageSourceProxyLostInTranslationConnector'), - ), - ]; } /** @@ -110,4 +81,44 @@ public function exampleProvider(): \Generator public function testDetectionOfTranslatableProperties(NodeType $nodeType, TranslatablePropertyNames $expectedPropertyNames): void { $this->assertEquals($expectedPropertyNames, $this->translatablePropertyNamesFactory->createForNodeType($nodeType) ); } + + + public function testPropertiesWithConfiguredConnector(): void + { + $mockTranslationConnector = $this->createMock(TranslationConnectorInterface::class); + + $mockObjectManager = $this->createMock(ObjectManagerInterface::class); + $mockObjectManager + ->expects(self::once()) + ->method('get') + ->with('Example\TranslationConnector') + ->willReturn($mockTranslationConnector); + + $this->inject($this->translatablePropertyNamesFactory, 'objectManager', $mockObjectManager); + $this->inject($this->translatablePropertyNamesFactory, 'translationConnectors', ['Example\Class' => 'Example\TranslationConnector']); + + $nodeType = new NodeType('Example', [], [ + 'properties' => [ + 'objectWithConnector' => [ + 'type' => 'Example\Class', + 'options' => [ + 'automaticTranslation' => true + ] + ], + 'objectWithoutConnector' => [ + 'type' => 'Example\Other\Class', + 'options' => [ + 'automaticTranslation' => true + ] + ] + ] + ]); + + $expectedPropertyNames = new TranslatablePropertyNames( + new TranslatablePropertyName('objectWithConnector', $mockTranslationConnector) + ); + + $this->assertEquals($expectedPropertyNames, $this->translatablePropertyNamesFactory->createForNodeType($nodeType) ); + } + } From a98f14e2158063a125671dbbb3e2dea59e4909f5 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Thu, 25 Sep 2025 18:39:49 +0200 Subject: [PATCH 4/5] TASK: Apply configured connectors by default --- .../TranslatablePropertyNamesFactory.php | 16 ++++-- Configuration/Settings.yaml | 6 +++ .../TranslatablePropertyNamesFactoryTest.php | 54 ++++++++++++++++--- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php b/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php index 3f564fa..45dc483 100644 --- a/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php +++ b/Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php @@ -17,6 +17,12 @@ class TranslatablePropertyNamesFactory */ protected $translateInlineEditables; + /** + * @var bool + * @Flow\InjectConfiguration(path="nodeTranslation.translateTypesWithConnectors") + */ + protected $translateTypesWithConnectors; + /** * @Flow\InjectConfiguration(path="nodeTranslation.translationConnectors") * @var array @@ -46,17 +52,21 @@ public function createForNodeType(NodeType $nodeType): TranslatablePropertyNames // @deprecated Fallback for renamed setting translateOnAdoption -> automaticTranslation $automaticTranslationIsEnabled = $propertyDefinition[ 'options' ][ 'automaticTranslation' ] - ?? ($propertyDefinition[ 'options' ][ 'translateOnAdoption' ] ?? false); + ?? ($propertyDefinition[ 'options' ][ 'translateOnAdoption' ] ?? null); $isInlineEditable = $propertyDefinition['ui']['inlineEditable'] ?? false; $translationConnector = $this->translationConnectors[$type] ?? null; + if ($automaticTranslationIsEnabled === false) { + continue; + } + if ($type === "string" && $this->translateInlineEditables && $isInlineEditable) { $translateProperties[] = new TranslatablePropertyName($propertyName); - } elseif ($type === "string" && $automaticTranslationIsEnabled) { + } elseif ($type === "string" && $automaticTranslationIsEnabled === true) { $translateProperties[] = new TranslatablePropertyName($propertyName); - } elseif ($translationConnector && $automaticTranslationIsEnabled) { + } elseif ($translationConnector && ($this->translateTypesWithConnectors || $automaticTranslationIsEnabled)) { $translationConnectorInstance = $this->objectManager->get($translationConnector); assert($translationConnectorInstance instanceof TranslationConnectorInterface); $translateProperties[] = new TranslatablePropertyName($propertyName, $translationConnectorInstance); diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index 23db989..01fb769 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -84,6 +84,12 @@ Sitegeist: excludedNodePaths: [] + # + # Translate all object properties that have a translationConnector configured + # if this is set to false each property must be enabled via options.automaticTranslation + # + translateTypesWithConnectors: true + # # Connectors to translate value object properties # diff --git a/Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php b/Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php index 90215bd..5889fe2 100644 --- a/Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php +++ b/Tests/Unit/Domain/TranslatablePropertyNamesFactoryTest.php @@ -95,27 +95,67 @@ public function testPropertiesWithConfiguredConnector(): void ->willReturn($mockTranslationConnector); $this->inject($this->translatablePropertyNamesFactory, 'objectManager', $mockObjectManager); + $this->inject($this->translatablePropertyNamesFactory, 'translateTypesWithConnectors', true); $this->inject($this->translatablePropertyNamesFactory, 'translationConnectors', ['Example\Class' => 'Example\TranslationConnector']); $nodeType = new NodeType('Example', [], [ 'properties' => [ - 'objectWithConnector' => [ + 'object' => [ 'type' => 'Example\Class', - 'options' => [ - 'automaticTranslation' => true - ] ], 'objectWithoutConnector' => [ 'type' => 'Example\Other\Class', + ], + 'objectWithConnectorButDisabled' => [ + 'type' => 'Example\Class', 'options' => [ - 'automaticTranslation' => true + 'automaticTranslation' => false, ] - ] + ], + ] + ]); + + $expectedPropertyNames = new TranslatablePropertyNames( + new TranslatablePropertyName('object', $mockTranslationConnector) + ); + + $this->assertEquals($expectedPropertyNames, $this->translatablePropertyNamesFactory->createForNodeType($nodeType) ); + } + + public function testPropertiesWithConfiguredConnectorOptIn(): void + { + $mockTranslationConnector = $this->createMock(TranslationConnectorInterface::class); + + $mockObjectManager = $this->createMock(ObjectManagerInterface::class); + $mockObjectManager + ->expects(self::once()) + ->method('get') + ->with('Example\TranslationConnector') + ->willReturn($mockTranslationConnector); + + $this->inject($this->translatablePropertyNamesFactory, 'objectManager', $mockObjectManager); + $this->inject($this->translatablePropertyNamesFactory, 'translateTypesWithConnectors', false); + $this->inject($this->translatablePropertyNamesFactory, 'translationConnectors', ['Example\Class' => 'Example\TranslationConnector']); + + $nodeType = new NodeType('Example', [], [ + 'properties' => [ + 'object' => [ + 'type' => 'Example\Class', + 'options' => [ + 'automaticTranslation' => true, + ] + ], + 'objectWithoutConnector' => [ + 'type' => 'Example\Other\Class', + ], + 'objectWithoutOptIn' => [ + 'type' => 'Example\Class', + ], ] ]); $expectedPropertyNames = new TranslatablePropertyNames( - new TranslatablePropertyName('objectWithConnector', $mockTranslationConnector) + new TranslatablePropertyName('object', $mockTranslationConnector) ); $this->assertEquals($expectedPropertyNames, $this->translatablePropertyNamesFactory->createForNodeType($nodeType) ); From 07af3ed27be70d49397043379ea212e666593168 Mon Sep 17 00:00:00 2001 From: Martin Ficzel Date: Mon, 29 Sep 2025 17:08:27 +0200 Subject: [PATCH 5/5] TASK: Add test to verify that . is also allowed in subkeys --- Tests/Unit/Utility/ArrayFlatteningUtilityTest.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tests/Unit/Utility/ArrayFlatteningUtilityTest.php b/Tests/Unit/Utility/ArrayFlatteningUtilityTest.php index cf118d6..7f1f298 100644 --- a/Tests/Unit/Utility/ArrayFlatteningUtilityTest.php +++ b/Tests/Unit/Utility/ArrayFlatteningUtilityTest.php @@ -30,10 +30,15 @@ public function provideExamples(): \Generator ['foo.bar' => 'baz', 'foo.baz' => 'bam', 'bar.baz' => 'bam', 'baz' => 'bam'] ]; - yield 'deeply nested mixed array' => [ + yield 'nested mixed array' => [ ['foo' => ['bar' => 'baz', 'baz' => 'bam'], 'bar' => ['baz' => 'bam'], 'baz' => 'bam'], ['foo.bar' => 'baz', 'foo.baz' => 'bam', 'bar.baz' => 'bam', 'baz' => 'bam'] ]; + + yield 'nested with . in subkeys' => [ + ['foo' => ['bar.baz' => "bam", 'bar.bam' => 'blah'], 'bar' => ['baz.bam' => 'blah'], 'baz' => 'bam'], + ['foo.bar.baz' => 'bam', 'foo.bar.bam' => 'blah', 'bar.baz.bam' => 'blah', 'baz' => 'bam'] + ]; } /**