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