Skip to content

Commit ad25460

Browse files
authored
Merge pull request #75 from sitegeist/feature/translateValueObjects
Feature: Translate ValueObjects via TranslationConnectorInterface
2 parents f76366c + 07af3ed commit ad25460

File tree

9 files changed

+432
-19
lines changed

9 files changed

+432
-19
lines changed

Classes/ContentRepository/NodeTranslationService.php

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Neos\Neos\Utility\NodeUriPathSegmentGenerator;
1616
use Sitegeist\LostInTranslation\Domain\TranslatableProperty\TranslatablePropertyNamesFactory;
1717
use Sitegeist\LostInTranslation\Domain\TranslationServiceInterface;
18+
use Sitegeist\LostInTranslation\Utility\ArrayFlatteningUtility;
1819

1920
/**
2021
* @Flow\Scope("singleton")
@@ -287,22 +288,33 @@ public function translateNode(NodeInterface $sourceNode, NodeInterface $targetNo
287288
/** @phpstan-ignore arguments.count */
288289
$properties = (array)$sourceNode->getProperties(true);
289290
$propertiesToTranslate = [];
291+
290292
foreach ($properties as $propertyName => $propertyValue) {
291-
if (empty($propertyValue) || !is_string($propertyValue)) {
293+
if (empty($propertyValue)) {
292294
continue;
293295
}
296+
assert($propertyName !== '');
297+
assert($propertyValue !== '');
294298
if (!$translatableProperties->isTranslatable($propertyName)) {
295299
continue;
296300
}
297-
if ((trim(strip_tags($propertyValue))) == "") {
301+
if (is_string($propertyValue) && trim(strip_tags($propertyValue)) === "") {
298302
continue;
299303
}
300-
$propertiesToTranslate[$propertyName] = $propertyValue;
301-
unset($properties[$propertyName]);
304+
if ($connector = $translatableProperties->getTranslationObjectConnector($propertyName)) {
305+
$propertiesToTranslate[$propertyName] = $connector->extractTranslations($propertyValue);
306+
unset($properties[$propertyName]);
307+
} else {
308+
$propertiesToTranslate[$propertyName] = $propertyValue;
309+
unset($properties[$propertyName]);
310+
}
302311
}
303312

304313
if (count($propertiesToTranslate) > 0) {
305-
$translatedProperties = $this->translationService->translate($propertiesToTranslate, $targetLanguage, $sourceLanguage);
314+
$propertiesToTranslateDeflated = ArrayFlatteningUtility::deflate($propertiesToTranslate);
315+
/** @var array<non-empty-string, string> $translatedPropertiesDeflated */
316+
$translatedPropertiesDeflated = $this->translationService->translate($propertiesToTranslateDeflated, $targetLanguage, $sourceLanguage);
317+
$translatedProperties = ArrayFlatteningUtility::enflate($translatedPropertiesDeflated);
306318
$properties = array_merge($translatedProperties, $properties);
307319
}
308320

@@ -311,9 +323,18 @@ public function translateNode(NodeInterface $sourceNode, NodeInterface $targetNo
311323
if ($propertyName === 'uriPathSegment' && !preg_match('/^[a-z0-9\-]+$/i', $propertyValue)) {
312324
$propertyValue = $this->nodeUriPathSegmentGenerator->generateUriPathSegment(null, $propertyValue);
313325
}
314-
315-
if ($targetNode->getProperty($propertyName) !== $propertyValue) {
316-
$targetNode->setProperty($propertyName, $propertyValue);
326+
if (is_array($propertyValue)) {
327+
$targetValue = $targetNode->getProperty($propertyName);
328+
if ($connector = $translatableProperties->getTranslationObjectConnector($propertyName)) {
329+
if (is_object($targetValue)) {
330+
$targetValue = $connector->applyTranslations($targetValue, $propertyValue);
331+
}
332+
}
333+
} else {
334+
$targetValue = $propertyValue;
335+
}
336+
if ($targetNode->getProperty($propertyName) !== $targetValue) {
337+
$targetNode->setProperty($propertyName, $targetValue);
317338
}
318339
}
319340
}

Classes/Domain/TranslatableProperty/TranslatablePropertyName.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,40 @@
44

55
namespace Sitegeist\LostInTranslation\Domain\TranslatableProperty;
66

7+
use Sitegeist\LostInTranslation\Domain\TranslationConnectorInterface;
8+
79
class TranslatablePropertyName
810
{
911
/**
1012
* @var string
1113
*/
1214
protected $name;
1315

14-
public function __construct(string $name)
16+
/**
17+
* @var TranslationConnectorInterface<object>|null
18+
*/
19+
protected $translationConnector;
20+
21+
/**
22+
* @param string $name
23+
* @param TranslationConnectorInterface<object>|null $translationConnector
24+
*/
25+
public function __construct(string $name, ?TranslationConnectorInterface $translationConnector = null)
1526
{
1627
$this->name = $name;
28+
$this->translationConnector = $translationConnector;
1729
}
1830

1931
public function getName(): string
2032
{
2133
return $this->name;
2234
}
35+
36+
/**
37+
* @return TranslationConnectorInterface<object>|null
38+
*/
39+
public function getTranslationConnector(): ?TranslationConnectorInterface
40+
{
41+
return $this->translationConnector;
42+
}
2343
}

Classes/Domain/TranslatableProperty/TranslatablePropertyNames.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Sitegeist\LostInTranslation\Domain\TranslatableProperty;
66

7+
use Sitegeist\LostInTranslation\Domain\TranslationConnectorInterface;
8+
79
/**
810
* @implements \IteratorAggregate<int, TranslatablePropertyName>
911
*/
@@ -28,6 +30,20 @@ public function isTranslatable(string $propertyName): bool
2830
return false;
2931
}
3032

33+
/**
34+
* @param string $propertyName
35+
* @return TranslationConnectorInterface<object>|null
36+
*/
37+
public function getTranslationObjectConnector(string $propertyName): ?TranslationConnectorInterface
38+
{
39+
foreach ($this->translatableProperties as $translatableProperty) {
40+
if ($translatableProperty->getName() == $propertyName) {
41+
return $translatableProperty->getTranslationConnector();
42+
}
43+
}
44+
return null;
45+
}
46+
3147
/**
3248
* @return \ArrayIterator<int, TranslatablePropertyName>
3349
*/

Classes/Domain/TranslatableProperty/TranslatablePropertyNamesFactory.php

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
use Neos\Flow\Annotations as Flow;
88
use Neos\ContentRepository\Domain\Model\NodeType;
9+
use Neos\Flow\ObjectManagement\ObjectManagerInterface;
10+
use Sitegeist\LostInTranslation\Domain\TranslationConnectorInterface;
911

1012
class TranslatablePropertyNamesFactory
1113
{
@@ -15,6 +17,24 @@ class TranslatablePropertyNamesFactory
1517
*/
1618
protected $translateInlineEditables;
1719

20+
/**
21+
* @var bool
22+
* @Flow\InjectConfiguration(path="nodeTranslation.translateTypesWithConnectors")
23+
*/
24+
protected $translateTypesWithConnectors;
25+
26+
/**
27+
* @Flow\InjectConfiguration(path="nodeTranslation.translationConnectors")
28+
* @var array<class-string, class-string>
29+
*/
30+
protected $translationConnectors;
31+
32+
/**
33+
* @Flow\Inject
34+
* @var ObjectManagerInterface
35+
*/
36+
protected $objectManager;
37+
1838
/**
1939
* @var array<string, TranslatablePropertyNames>
2040
*/
@@ -28,20 +48,28 @@ public function createForNodeType(NodeType $nodeType): TranslatablePropertyNames
2848
$propertyDefinitions = $nodeType->getProperties();
2949
$translateProperties = [];
3050
foreach ($propertyDefinitions as $propertyName => $propertyDefinition) {
31-
if (array_key_exists('type', $propertyDefinition) && $propertyDefinition['type'] !== 'string') {
51+
$type = $propertyDefinition['type'];
52+
53+
// @deprecated Fallback for renamed setting translateOnAdoption -> automaticTranslation
54+
$automaticTranslationIsEnabled = $propertyDefinition[ 'options' ][ 'automaticTranslation' ]
55+
?? ($propertyDefinition[ 'options' ][ 'translateOnAdoption' ] ?? null);
56+
$isInlineEditable = $propertyDefinition['ui']['inlineEditable']
57+
?? false;
58+
$translationConnector = $this->translationConnectors[$type]
59+
?? null;
60+
61+
if ($automaticTranslationIsEnabled === false) {
3262
continue;
3363
}
34-
if (isset($propertyDefinition['options']['automaticTranslation']) && !$propertyDefinition['options']['automaticTranslation']) {
35-
continue; // do not translate (inline-editable) properties explicitly set to: 'automaticTranslation: false'
36-
}
37-
if ($this->translateInlineEditables && ($propertyDefinitions[$propertyName]['ui']['inlineEditable'] ?? false)) {
64+
65+
if ($type === "string" && $this->translateInlineEditables && $isInlineEditable) {
3866
$translateProperties[] = new TranslatablePropertyName($propertyName);
39-
continue;
40-
}
41-
// @deprecated Fallback for renamed setting translateOnAdoption -> automaticTranslation
42-
if ($propertyDefinition['options']['automaticTranslation'] ?? ($propertyDefinition['options']['translateOnAdoption'] ?? false)) {
67+
} elseif ($type === "string" && $automaticTranslationIsEnabled === true) {
4368
$translateProperties[] = new TranslatablePropertyName($propertyName);
44-
continue;
69+
} elseif ($translationConnector && ($this->translateTypesWithConnectors || $automaticTranslationIsEnabled)) {
70+
$translationConnectorInstance = $this->objectManager->get($translationConnector);
71+
assert($translationConnectorInstance instanceof TranslationConnectorInterface);
72+
$translateProperties[] = new TranslatablePropertyName($propertyName, $translationConnectorInstance);
4573
}
4674
}
4775
$this->firstLevelCache[$nodeType->getName()] = new TranslatablePropertyNames(...$translateProperties);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sitegeist\LostInTranslation\Domain;
6+
7+
/**
8+
* @template T of object
9+
*/
10+
interface TranslationConnectorInterface
11+
{
12+
/**
13+
* @param T $object
14+
* @return array<non-empty-string, string>
15+
*/
16+
public function extractTranslations(object $object): array;
17+
18+
/**
19+
* @param T $object
20+
* @param array<non-empty-string, string> $translations
21+
* @return T
22+
*/
23+
public function applyTranslations(object $object, array $translations): object;
24+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Sitegeist\LostInTranslation\Utility;
6+
7+
/**
8+
* Utility to deflate and enflate an array of named strings that may be nested by using a seperator
9+
* that must never be used in the array keys
10+
*/
11+
class ArrayFlatteningUtility
12+
{
13+
private const SEPERATOR = '.';
14+
15+
/**
16+
* @param array<non-empty-string, string|array<non-empty-string, string>> $array to deflate
17+
* @return array<non-empty-string, string>
18+
*/
19+
public static function deflate(array $array): array
20+
{
21+
$result = [];
22+
foreach ($array as $key => $value) {
23+
assert($key !== '');
24+
if (is_string($value)) {
25+
$result[$key] = $value;
26+
} elseif (is_array($value)) {
27+
foreach ($value as $subkey => $subvalue) {
28+
$result[$key . self::SEPERATOR . $subkey] = $subvalue;
29+
}
30+
}
31+
}
32+
return $result;
33+
}
34+
35+
/**
36+
* @param array<non-empty-string, string> $array to enflate
37+
* @return array<non-empty-string, string|array<non-empty-string, string>>
38+
*/
39+
public static function enflate(array $array): array
40+
{
41+
$result = [];
42+
foreach ($array as $key => $value) {
43+
assert($key !== '');
44+
if (str_contains($key, self::SEPERATOR)) {
45+
list($mainKey, $subKey) = explode(self::SEPERATOR, $key, 2);
46+
assert($mainKey !== '');
47+
assert($subKey !== '');
48+
if (array_key_exists($mainKey, $result) && is_array($result[$mainKey])) {
49+
$result[$mainKey][$subKey] = $value;
50+
} else {
51+
$result[$mainKey] = [$subKey => $value];
52+
}
53+
} else {
54+
$result[$key] = $value;
55+
}
56+
}
57+
return $result;
58+
}
59+
}

Configuration/Settings.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,17 @@ Sitegeist:
8383
skipAuthorizationChecks: false
8484

8585
excludedNodePaths: []
86+
87+
#
88+
# Translate all object properties that have a translationConnector configured
89+
# if this is set to false each property must be enabled via options.automaticTranslation
90+
#
91+
translateTypesWithConnectors: true
92+
93+
#
94+
# Connectors to translate value object properties
95+
#
96+
# for each value object type a clas implementing the TranslationConnectorInterface
97+
# can be configured to extract and apply translations
98+
#
99+
translationConnectors: []

0 commit comments

Comments
 (0)