From 2b3ea20d58993e150201183f71d0b479033d12f2 Mon Sep 17 00:00:00 2001 From: Martin Giessing <mgp@novicell.dk> Date: Thu, 11 Jan 2024 10:36:31 +0100 Subject: [PATCH] Added multilingual support for the base tranform_api module --- .../Transform/Field/LanguageCodeTransform.php | 35 +++++ .../Transform/Field/LanguageNameTransform.php | 126 ++++++++++++++++++ src/FieldTransformBase.php | 11 +- .../Block/SystemMainTransformBlock.php | 23 +++- .../Field/EntityReferenceLinksTransform.php | 7 + src/Plugin/Transform/Type/Entity.php | 31 ++++- src/Transform/EntityTransform.php | 12 +- 7 files changed, 233 insertions(+), 12 deletions(-) create mode 100644 modules/transform_api_language/src/Plugin/Transform/Field/LanguageCodeTransform.php create mode 100644 modules/transform_api_language/src/Plugin/Transform/Field/LanguageNameTransform.php diff --git a/modules/transform_api_language/src/Plugin/Transform/Field/LanguageCodeTransform.php b/modules/transform_api_language/src/Plugin/Transform/Field/LanguageCodeTransform.php new file mode 100644 index 0000000..977e59a --- /dev/null +++ b/modules/transform_api_language/src/Plugin/Transform/Field/LanguageCodeTransform.php @@ -0,0 +1,35 @@ +<?php + +namespace Drupal\transform_api_language\Plugin\Transform\Field; + +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\transform_api\FieldTransformBase; + +/** + * Transform field plugin for language field types. + * + * @FieldTransform( + * id = "language_code", + * label = @Translation("Language code"), + * field_types = { + * "language" + * } + * ) + */ +class LanguageCodeTransform extends FieldTransformBase { + + /** + * {@inheritdoc} + */ + public function transformElements(FieldItemListInterface $items, $langcode): array { + $values = []; + /** @var \Drupal\Core\Field\FieldItemInterface $item */ + foreach ($items as $item) { + if (isset($item->getValue()['value'])) { + $values[] = $item->getValue()['value'] ?? NULL; + } + } + return $values; + } + +} diff --git a/modules/transform_api_language/src/Plugin/Transform/Field/LanguageNameTransform.php b/modules/transform_api_language/src/Plugin/Transform/Field/LanguageNameTransform.php new file mode 100644 index 0000000..ea73859 --- /dev/null +++ b/modules/transform_api_language/src/Plugin/Transform/Field/LanguageNameTransform.php @@ -0,0 +1,126 @@ +<?php + +namespace Drupal\transform_api_language\Plugin\Transform\Field; + +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\transform_api\FieldTransformBase; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Transform field plugin for language field types. + * + * @FieldTransform( + * id = "language_name", + * label = @Translation("Language name"), + * field_types = { + * "language" + * } + * ) + */ +class LanguageNameTransform extends FieldTransformBase { + + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + + /** + * Constructs a LanguageNameTransform instance. + * + * @param string $plugin_id + * The plugin_id for the transform. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition + * The definition of the field to which the transform is associated. + * @param array $settings + * The transform settings. + * @param string $label + * The transform label display setting. + * @param string $transform_mode + * The transform mode. + * @param array $third_party_settings + * Any third party settings. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. + */ + public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $transform_mode, array $third_party_settings, LanguageManagerInterface $language_manager) { + parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $transform_mode, $third_party_settings); + + $this->languageManager = $language_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $plugin_id, + $plugin_definition, + $configuration['field_definition'], + $configuration['settings'], + $configuration['label'], + $configuration['transform_mode'], + $configuration['third_party_settings'], + $container->get('language_manager') + ); + } + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + $settings = parent::defaultSettings(); + $settings['native_language'] = FALSE; + return $settings; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $form = parent::settingsForm($form, $form_state); + $form['native_language'] = [ + '#title' => $this->t('Display in native language'), + '#type' => 'checkbox', + '#default_value' => $this->getSetting('native_language'), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = parent::settingsSummary(); + if ($this->getSetting('native_language')) { + $summary[] = $this->t('Displayed in native language'); + } + return $summary; + } + + /** + * {@inheritDoc} + */ + public function transformElements(FieldItemListInterface $items, $langcode) { + $values = []; + /** @var \Drupal\Core\Field\FieldItemInterface $item */ + foreach ($items as $item) { + // The 'languages' cache context is not necessary because the language is + // either displayed in its configured form (loaded directly from config + // storage by LanguageManager::getLanguages()) or in its native language + // name. That only depends on transform settings and no language + // condition. + $languages = $this->getSetting('native_language') ? $this->languageManager->getNativeLanguages(LanguageInterface::STATE_ALL) : $this->languageManager->getLanguages(LanguageInterface::STATE_ALL); + $values[] = $item->language && isset($languages[$item->language->getId()]) ? $languages[$item->language->getId()]->getName() : ''; + } + return $values; + } + +} diff --git a/src/FieldTransformBase.php b/src/FieldTransformBase.php index 93404dc..30c6c2c 100644 --- a/src/FieldTransformBase.php +++ b/src/FieldTransformBase.php @@ -2,11 +2,13 @@ namespace Drupal\transform_api; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\PluginSettingsBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\TypedData\TranslatableInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -140,8 +142,15 @@ abstract class FieldTransformBase extends PluginSettingsBase implements FieldTra '#third_party_settings' => $this->getThirdPartySettings(), '#collapse' => !$this->fieldDefinition->getFieldStorageDefinition()->isMultiple(), ]; + $transformation = array_merge($info, $elements); - return array_merge($info, $elements); + if ($this->fieldDefinition->isTranslatable()) { + $cacheMetadata = CacheableMetadata::createFromRenderArray($transformation); + $cacheMetadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_CONTENT]); + $cacheMetadata->applyTo($transformation); + } + + return $transformation; } /** diff --git a/src/Plugin/Transform/Block/SystemMainTransformBlock.php b/src/Plugin/Transform/Block/SystemMainTransformBlock.php index d1826b0..6cef7b8 100644 --- a/src/Plugin/Transform/Block/SystemMainTransformBlock.php +++ b/src/Plugin/Transform/Block/SystemMainTransformBlock.php @@ -5,6 +5,8 @@ namespace Drupal\transform_api\Plugin\Transform\Block; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Language\LanguageManagerInterface; use Drupal\transform_api\Transform\EntityTransform; use Drupal\transform_api\Transform\RouteTransform; use Drupal\transform_api\TransformBlockBase; @@ -45,6 +47,13 @@ class SystemMainTransformBlock extends TransformBlockBase { */ protected RequestStack $requestStack; + /** + * The language manager service. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + private LanguageManagerInterface $languageManager; + /** * Constructs a SystemMainTransformBlock object. * @@ -60,12 +69,15 @@ class SystemMainTransformBlock extends TransformBlockBase { * The configuration factory. * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack * The request stack. + * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager + * The language manager service. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, RouterInterface $router, ConfigFactoryInterface $configFactory, RequestStack $requestStack) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, RouterInterface $router, ConfigFactoryInterface $configFactory, RequestStack $requestStack, LanguageManagerInterface $languageManager) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->router = $router; $this->configFactory = $configFactory; $this->requestStack = $requestStack; + $this->languageManager = $languageManager; } /** @@ -75,7 +87,8 @@ class SystemMainTransformBlock extends TransformBlockBase { return new static($configuration, $plugin_id, $plugin_definition, $container->get('router'), $container->get('config.factory'), - $container->get('request_stack') + $container->get('request_stack'), + $container->get('language_manager') ); } @@ -93,9 +106,13 @@ class SystemMainTransformBlock extends TransformBlockBase { (str_starts_with($match['_route'], 'entity.')) && (str_ends_with($match['_route'], '.canonical'))) { $cacheMetadata->addCacheableDependency($route_entity); + $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(); + if ($route_entity->hasTranslation($langcode)) { + $route_entity = $route_entity->getTranslation($langcode); + } $transform = [ '#collapse' => TRUE, - 'content' => new EntityTransform($route_entity->getEntityTypeId(), $route_entity->id(), 'full'), + 'content' => EntityTransform::createFromEntity($route_entity, 'full'), ]; } else { diff --git a/src/Plugin/Transform/Field/EntityReferenceLinksTransform.php b/src/Plugin/Transform/Field/EntityReferenceLinksTransform.php index 92659b4..112b989 100644 --- a/src/Plugin/Transform/Field/EntityReferenceLinksTransform.php +++ b/src/Plugin/Transform/Field/EntityReferenceLinksTransform.php @@ -7,6 +7,8 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\TypedData\TranslatableInterface; use Drupal\transform_api\FieldTransformBase; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -78,6 +80,11 @@ class EntityReferenceLinksTransform extends FieldTransformBase { if (!empty($ids)) { $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple($ids); foreach ($entities as $entity) { + if ($entity instanceof TranslatableInterface) { + if ($entity->hasTranslation($langcode)) { + $entity = $entity->getTranslation($langcode); + } + } $elements[] = $this->transformEntity($entity); $metadata->addCacheableDependency($entity); } diff --git a/src/Plugin/Transform/Type/Entity.php b/src/Plugin/Transform/Type/Entity.php index d28aaf5..eed6d7c 100644 --- a/src/Plugin/Transform/Type/Entity.php +++ b/src/Plugin/Transform/Type/Entity.php @@ -7,6 +7,9 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\TypedData\TranslatableInterface; use Drupal\transform_api\Configs\EntityTransformDisplayInterface; use Drupal\transform_api\Entity\EntityTransformDisplay; use Drupal\transform_api\EventSubscriber\TransformationCache; @@ -64,6 +67,13 @@ class Entity extends TransformationTypeBase { */ protected ModuleHandlerInterface $moduleHandler; + /** + * The language manager service. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + private LanguageManagerInterface $languageManager; + /** * Construct an entity transform type plugin. * @@ -83,14 +93,17 @@ class Entity extends TransformationTypeBase { * The repository for transform modes. * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler * The module handler. + * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager + * The language manager service. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entityTypeManager, TransformationCache $transformationCache, FieldTransformManager $fieldTransformManager, EntityTransformRepositoryInterface $entityTransformRepository, ModuleHandlerInterface $moduleHandler) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entityTypeManager, TransformationCache $transformationCache, FieldTransformManager $fieldTransformManager, EntityTransformRepositoryInterface $entityTransformRepository, ModuleHandlerInterface $moduleHandler, LanguageManagerInterface $languageManager) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->entityTypeManager = $entityTypeManager; $this->transformationCache = $transformationCache; $this->fieldTransformManager = $fieldTransformManager; $this->entityTransformRepository = $entityTransformRepository; $this->moduleHandler = $moduleHandler; + $this->languageManager = $languageManager; } /** @@ -102,7 +115,8 @@ class Entity extends TransformationTypeBase { $container->get('transform_api.transformation_cache'), $container->get('plugin.manager.transform_api.field_transform'), $container->get('transform_api.entity_display.repository'), - $container->get('module_handler') + $container->get('module_handler'), + $container->get('language_manager') ); } @@ -142,8 +156,13 @@ class Entity extends TransformationTypeBase { $storage = $this->entityTypeManager->getStorage($entity_type_id); /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ $entity = $storage->load($id); - if (!empty($langcode)) { - $entity = $entity->getTranslation($langcode); + if ($entity instanceof TranslatableInterface) { + if (empty($langcode)) { + $langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(); + } + if ($entity->hasTranslation($langcode)) { + $entity = $entity->getTranslation($langcode); + } } if (!empty($entity)) { return $this->prepareAndTransformEntity($entity, $transform_mode); @@ -297,8 +316,10 @@ class Entity extends TransformationTypeBase { $transformation['id'] = $entity->id(); $transformation['label'] = $entity->label(); $transformation['transform_mode'] = $display->getMode(); - $transformation['langcode'] = $entity->language()->getId(); $transformation['#entity'] = $entity; + if ($entity instanceof TranslatableInterface && $entity->isTranslatable()) { + $cacheMetadata->addCacheContexts(['languages:' . LanguageInterface::TYPE_CONTENT]); + } if ($entity instanceof FieldableEntityInterface && !empty($display)) { $cacheMetadata->addCacheableDependency($display); foreach ($display->getComponents() as $field_name => $definition) { diff --git a/src/Transform/EntityTransform.php b/src/Transform/EntityTransform.php index 34f4f0c..d7bcd97 100644 --- a/src/Transform/EntityTransform.php +++ b/src/Transform/EntityTransform.php @@ -3,6 +3,7 @@ namespace Drupal\transform_api\Transform; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Language\LanguageInterface; use Drupal\transform_api\Configs\EntityTransformDisplayInterface; use Drupal\transform_api\Repository\EntityTransformRepositoryInterface; @@ -41,12 +42,15 @@ class EntityTransform extends PluginTransformBase { * One or more ids of entities. * @param string $transform_mode * (Optional) The transform mode to use for transformation. + * @param string $langcode + * (Optional) The language to use for transformation. */ - public function __construct($entity_type, $ids, $transform_mode = EntityTransformRepositoryInterface::DEFAULT_DISPLAY_MODE) { + public function __construct($entity_type, $ids, $transform_mode = EntityTransformRepositoryInterface::DEFAULT_DISPLAY_MODE, $langcode = NULL) { $this->values = [ 'entity_type' => $entity_type, 'ids' => $ids, 'transform_mode' => $transform_mode, + 'langcode' => $langcode ?? \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId() ]; if (is_array($ids)) { foreach ($ids as $id) { @@ -165,7 +169,7 @@ class EntityTransform extends PluginTransformBase { * @return EntityTransform */ public static function createFromEntity(EntityInterface $entity, $transform_mode = EntityTransformRepositoryInterface::DEFAULT_DISPLAY_MODE) { - return new self($entity->getEntityTypeId(), $entity->id(), $transform_mode); + return new self($entity->getEntityTypeId(), $entity->id(), $transform_mode, $entity->language()->getId()); } /** @@ -177,11 +181,13 @@ class EntityTransform extends PluginTransformBase { public static function createFromMultipleEntities(array $entities, $transform_mode = EntityTransformRepositoryInterface::DEFAULT_DISPLAY_MODE) { $ids = []; $entityTypeId = ''; + $langcode = NULL; foreach ($entities as $entity) { $ids[] = $entity->id(); $entityTypeId = $entity->getEntityTypeId(); + $langcode = $entity->language()->getId(); } - return new self($entityTypeId, $ids, $transform_mode); + return new self($entityTypeId, $ids, $transform_mode, $langcode); } } -- GitLab