bundles/Alko/AutoTranslateBundle/EventSubscriber/WorkflowTranslationSubscriber.php line 57

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Alko\AutoTranslateBundle\EventSubscriber;
  4. use Alko\AutoTranslateBundle\Message\TranslateObjectMessage;
  5. use Alko\AutoTranslateBundle\Service\TranslationService;
  6. use Pimcore\Model\DataObject\AbstractObject;
  7. use Psr\Log\LoggerInterface;
  8. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  9. use Symfony\Component\Messenger\MessageBusInterface;
  10. use Symfony\Component\Workflow\Event\TransitionEvent;
  11. /**
  12.  * Listens to workflow transitions and triggers AI translation
  13.  *
  14.  * Hooks into the existing translation_workflow to automatically
  15.  * translate content when PM starts the translation process.
  16.  */
  17. class WorkflowTranslationSubscriber implements EventSubscriberInterface
  18. {
  19.     private TranslationService $translationService;
  20.     private MessageBusInterface $messageBus;
  21.     private LoggerInterface $logger;
  22.     private bool $asyncMode;
  23.     private bool $enabled;
  24.     public function __construct(
  25.         TranslationService $translationService,
  26.         MessageBusInterface $messageBus,
  27.         LoggerInterface $logger,
  28.         bool $asyncMode true,
  29.         bool $enabled true
  30.     ) {
  31.         $this->translationService $translationService;
  32.         $this->messageBus $messageBus;
  33.         $this->logger $logger;
  34.         $this->asyncMode $asyncMode;
  35.         $this->enabled $enabled;
  36.     }
  37.     /** Subscribe to the "to_translate" workflow transition event */
  38.     public static function getSubscribedEvents(): array
  39.     {
  40.         return [
  41.             'workflow.translation_workflow.transition.to_translate' => ['onStartTranslation'0],
  42.         ];
  43.     }
  44.     /**
  45.      * Handle the to_translate transition
  46.      *
  47.      * This is triggered when a PM clicks "To translate the element" in the workflow.
  48.      * We intercept this to auto-translate content BEFORE notifications are sent.
  49.      */
  50.     public function onStartTranslation(TransitionEvent $event): void
  51.     {
  52.         if (!$this->enabled) {
  53.             $this->logger->debug('AI Translation is disabled, skipping auto-translation');
  54.             return;
  55.         }
  56.         $subject $event->getSubject();
  57.         if (!$subject instanceof AbstractObject) {
  58.             $this->logger->debug('Workflow subject is not a DataObject, skipping');
  59.             return;
  60.         }
  61.         $className $subject->getClassName();
  62.         // Check if this entity type is supported
  63.         if (!$this->translationService->isEntitySupported($className)) {
  64.             $this->logger->debug('Entity type not configured for auto-translation', [
  65.                 'class' => $className,
  66.                 'object_id' => $subject->getId()
  67.             ]);
  68.             return;
  69.         }
  70.         $this->logger->info('Translation workflow triggered - starting AI auto-translation', [
  71.             'object_id' => $subject->getId(),
  72.             'class' => $className,
  73.             'async_mode' => $this->asyncMode,
  74.             'transition' => $event->getTransition()->getName()
  75.         ]);
  76.         try {
  77.             if ($this->asyncMode) {
  78.                 // Queue for asynchronous processing
  79.                 $this->queueForAsyncTranslation($subject);
  80.             } else {
  81.                 // Synchronous translation (blocks the workflow transition)
  82.                 $this->performSyncTranslation($subject);
  83.             }
  84.         } catch (\Exception $e) {
  85.             $this->logger->error('AI auto-translation failed', [
  86.                 'object_id' => $subject->getId(),
  87.                 'error' => $e->getMessage(),
  88.                 'trace' => $e->getTraceAsString()
  89.             ]);
  90.             // Translation failure should not block the workflow; manual translation remains as fallback
  91.         }
  92.     }
  93.     /**
  94.      * Queue object for asynchronous translation via Symfony Messenger
  95.      */
  96.     private function queueForAsyncTranslation(AbstractObject $object): void
  97.     {
  98.         $message = new TranslateObjectMessage(
  99.             $object->getId(),
  100.             $object->getClassName(),
  101.             $this->translationService->getTargetLanguages()
  102.         );
  103.         $this->messageBus->dispatch($message);
  104.         $this->logger->info('Object queued for async translation', [
  105.             'object_id' => $object->getId(),
  106.             'class' => $object->getClassName(),
  107.             'language_count' => count($this->translationService->getTargetLanguages())
  108.         ]);
  109.     }
  110.     /**
  111.      * Perform synchronous translation (blocks until complete)
  112.      */
  113.     private function performSyncTranslation(AbstractObject $object): void
  114.     {
  115.         $startTime microtime(true);
  116.         $results $this->translationService->translateObject($object);
  117.         $duration microtime(true) - $startTime;
  118.         $successCount 0;
  119.         $failureCount 0;
  120.         $totalTokens 0;
  121.         foreach ($results as $result) {
  122.             if ($result->isSuccess()) {
  123.                 $successCount++;
  124.                 $totalTokens += $result->getTokensUsed();
  125.             } else {
  126.                 $failureCount++;
  127.             }
  128.         }
  129.         $this->logger->info('Synchronous translation completed', [
  130.             'object_id' => $object->getId(),
  131.             'duration_seconds' => round($duration2),
  132.             'success_count' => $successCount,
  133.             'failure_count' => $failureCount,
  134.             'total_tokens' => $totalTokens
  135.         ]);
  136.     }
  137. }