lionbridge_content_api_test-8.x-4.0/src/Plugin/tmgmt/Translator/LionbridgeTranslator.php

src/Plugin/tmgmt/Translator/LionbridgeTranslator.php
<?php

namespace Drupal\lionbridge_translation_provider\Plugin\tmgmt\Translator;

use Drupal\Component\Serialization\Json;
use Drupal\tmgmt\Entity\Job;
use Drupal\Core\Url;
use Drupal\lionbridge_translation_provider\LionbridgeConnector;
use Drupal\tmgmt\JobInterface;
use Drupal\tmgmt\TMGMTException;
use Drupal\tmgmt\TranslatorInterface;
use Drupal\tmgmt\TranslatorPluginBase;
use Drupal\tmgmt\Translator\AvailableResult;
use Drupal\tmgmt\Translator\TranslatableResult;

/**
 * Lionbridge provider.
 *
 * @TranslatorPlugin(
 *   id = "lionbridge",
 *   label = @Translation("Lionbridge translator"),
 *   description = @Translation("Lionbridge translator service."),
 *   ui = "Drupal\lionbridge_translation_provider\LionbridgeTranslatorUi",
 *   default_settings = {
 *     "po_number" = "123456"
 *   },
 * )
 */
class LionbridgeTranslator extends TranslatorPluginBase {

  /**
   * Translation keys.
   *
   * @var array
   */
  protected $translationKeys;

  /**
   * {@inheritdoc}
   */
  public function requestTranslation(JobInterface $job) {
    if ($job->isUnprocessed()) {
      try {
        $this->submitJob($job);

        if (!$job->isRejected()) {
          // @todo: Unprocessed Jobs should not trigger success message.
          $job->submitted(t('Job has been submitted.'));
        }
      }
      catch (TMGMTException $e) {
        watchdog_exception('lionbridge_translation_provider', $e);
        $job->rejected('Job has been rejected with following error: @error',
          ['@error' => $e->getMessage()], 'error');
      }
    }

    if ($job->isActive()) {
      try {
        $this->fetchJob($job);
      }
      catch (TMGMTException $e) {
        watchdog_exception('lionbridge_translation_provider', $e);
        $job->addMessage('Translation could not be completed: @error',
          ['@error' => $e->getMessage()], 'error');
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function checkAvailable(TranslatorInterface $translator) {
    if ($translator->getSetting('access_key_id') && $translator->getSetting('access_key')) {
      return AvailableResult::yes();
    }

    return AvailableResult::no(t('Access key ID and access key are not set.'));
  }


  /**
   * {@inheritdoc}
   */
  public function checkTranslatable(TranslatorInterface $translator, JobInterface $job) {
    if ($job->isUnprocessed() || $job->isRejected() || $job->isAborted()) {
      return TranslatableResult::no(t("Please use the ContentAPI connector."));
    }
    else {
      return TranslatableResult::yes();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getSupportedRemoteLanguages(TranslatorInterface $translator) {
    if (!$translator->checkAvailable()->getSuccess()) {
      return parent::getSupportedRemoteLanguages($translator);
    }

    $api_client = new LionbridgeConnector($translator);
    $locales = $api_client->listLocales();
    $languages = [];
    foreach ($locales['Locale'] as $language) {
      $languages[$language['Code']] = $language['Code'];
    }

    return $languages;
  }

  /**
   * Submits a job to Lionbridge translation service.
   *
   * This does the work of actually submitting the translation job to the
   * Lionbridge translation service. There are 3 major parts to submitting a
   * translations job. Each part has been broken out into an individual
   * function to make the code more readable.
   *
   * 1. Create a JSON encoded file that contains all the translatable content
   *    and send it to Lionbridge. If this is successful, an array containing
   *    the details of the uploaded file is returned.
   *
   * 2. Create a project using the settings from the job settings page and the
   *    AssetID of the array returned from the successful file upload. If there
   *    were more than one file uploaded, multiple file asset ID's could be
   *    used in a project, however, in this model there is only 1 asset ID for
   *    a given project.
   *
   * 3. Generate a quote for the translation job using the Project ID from the
   *    array returned from a successful project creation request.
   *
   * NOTE: Translation Memory update job files are XML encoded.
   *
   * @param \Drupal\tmgmt\JobInterface $job
   *   The job object.
   *
   * @throws TMGMTException
   */
  public function submitJob(JobInterface $job) {
    $translator = $job->getTranslator();
    $api_client = new LionbridgeConnector($translator);
    $items = $job->getItems();
    $file_assets = [];
    $source_language = $translator->mapToRemoteLanguage($job->getSourceLangcode());
    $target_language = $translator->mapToRemoteLanguage($job->getTargetLangcode());

    if ($translator->getSetting('notification_url')) {
      $project_complete_url = $translator->getSetting('notification_url');
    }
    else {
      $project_complete_url = Url::fromRoute('lionbridge_translation_provider.lionbridge_translation_provider_project_complete_callback', [], [
        'query' => ['secret' => $job->getSetting('secret')],
        'absolute' => TRUE,
      ])->toString();
    }

    $service_type = $translator->getSetting('service_type');

    switch ($service_type) {
      case 'tm_update':
        // If service type is an update, then we send a xml file with the
        // content that was translated and updated locally.
        // Load the entity and check if there is a translation already.
        $job_item = reset($items);
        $storage = \Drupal::entityTypeManager()->getStorage($job_item->getItemType());
        $entity = $storage->load($job_item->getItemId());

        // If there is translated data, load it.
        if ($entity->hasTranslation($job->getTargetLangcode())) {
          // The $job_item already has the data with the original content to be
          // translated, here we load the source plugin and the already
          // translated entity and format the translated entity just like it is
          // already formatted in the job.
          $source_plugin = $job_item->getSourcePlugin();
          $translated_entity = $entity->getTranslation($job->getTargetLangcode());
          $translated_data = $source_plugin->extractTranslatableData($translated_entity);
        }
        else {
          throw new TMGMTException('Source is required to have translated content for language requested.');
        }

        // Get the item path, e.g. node/15, taxonomy/1, etc.
        $item_path = $entity->toUrl()->getInternalPath();

        // Build data to be added to the xml file.
        $update_data = [
          'theme' => 'lionbridge_update_content',
          'source_language' => $source_language,
          'target_language' => $target_language,
          'item_path' => $item_path,
        ];

        $file_name = $this->generateFileName($job->label(), $source_language, $target_language);

        $file_asset = $this->generateUpdateFile($file_name, $update_data, $job->getData(), $translated_data, $api_client);
        if (isset($file_asset['Errors']['Error'])) {
          throw new TMGMTException($file_asset['Errors']['Error']['DetailedMessage']);
        }

        $file_assets[] = $file_asset['AssetID'];

        break;

      default:
        // If service is of type translation, then we send a json file with the
        // translation and a xml file for a new project.
        // This is the name of the JSON file that will be sent to Lionbridge.
        $file_name = $this->generateFileName($job->label(), $source_language, $target_language);

        $file_asset = $this->generateFile($file_name, $source_language, $job->getData(), $api_client);
        if (isset($file_asset['Errors']['Error'])) {
          throw new TMGMTException($file_asset['Errors']['Error']['DetailedMessage']);
        }

        $file_assets[] = $file_asset['AssetID'];

        break;
    }

    // Create a new project with the file asset, send it to Lionbridge. This
    // will create a new project and return the project XML in the form of an
    // array.
    $service_id = $translator->getSetting('service');

    $project_data = [
      'theme' => 'lionbridge_add_project',
      'project_title' => $job->getSetting('project_title') . ' to ' . $target_language,
      'service_id' => $service_id,
      'source_language' => $source_language,
      'target_languages' => [$target_language],
      'file_assets' => $file_assets,
    ];

    $project = $this->addProject($project_data, $api_client);

    // Check if there are errors in the project.
    if (isset($project['Errors']['Error']) && !empty($project['Errors']['Error'])) {
      throw new TMGMTException($project['Errors']['Error']['DetailedMessage']);
    }

    // Get a quote for the project.
    $quote = $this->generateQuote($project['ProjectID'], $project_complete_url, $api_client);

    if (isset($quote['Errors']) && !empty($quote['Errors'])) {
      throw new TMGMTException($quote['Errors']['Error']['DetailedMessage']);
    }

    // Set the job reference to the Quote ID.
    $job->set('reference', $quote['QuoteID']);

    // Add project ID to `tmgmt_remote` table.
    foreach ($this->translationKeys as $translation_key) {
      $items[$translation_key['tjiid']]->addRemoteMapping(
        $translation_key['data_item_key'],
        $project['ProjectID']
      );
    }

  }

  /**
   * Gets status of job items from Lionbridge and downloads translated items.
   *
   * @param \Drupal\tmgmt\JobInterface $job
   *   The translation job object.
   *
   * @return bool
   *   Completion indicator.
   *
   * @throws TMGMTException
   */
  public function fetchJob(JobInterface $job) {
    $translator = $job->getTranslator();
    $api_client = new LionbridgeConnector($translator);
    if ($job->getReference()) {
      $quote = $api_client->getQuote($job->getReference());
      if (!$quote || isset($quote['Error'])) {
        throw new TMGMTException('Could not get quote.');
      }

      $translation_complete = FALSE;
      $service_type = $translator->getSetting('service_type');

      switch ($quote['Status']) {
        case LionbridgeConnector::QUOTE_STATUS_ERROR:
          $job->addMessage('An error occurred with this quote.');
          $this->abortTranslation($job);
          break;

        case LionbridgeConnector::QUOTE_STATUS_PENDING:
          // If this is a pending quote, add a message and a link to approve.
          $job->addMessage(
            'Quote ready for approval: <a href=":url" target="_blank">View on Lionbridge</a>', [
              ':url' => Url::fromUri($api_client->getEndpoint() . '/project/' . $quote['QuoteID'] . '/details')->toString(),
            ]
          );
          break;

        case LionbridgeConnector::QUOTE_STATUS_CALCULATING:
          $job->addMessage('Calculating quote...');
          break;

        case LionbridgeConnector::QUOTE_STATUS_AUTHORIZED:
          if ($service_type == 'tm_update') {
            $job->addMessage('Translation Memory Update in progress.');
          }
          else {
            $job->addMessage('Translation in progress.');
          }
          break;

        case LionbridgeConnector::QUOTE_STATUS_COMPLETE:
          // If it's a TM update, complete job items.
          if ($service_type == 'tm_update') {
            foreach ($job->getItems() as $job_item) {
              $variables = [
                '@source' => $job_item->getSourceLabel(),
                '@language' => $job->getTargetLanguage()->getName(),
              ];
              $job_item->accepted('Translation Memory Update of "@source" for language @language is finished.', $variables);
            }
          }
          else {
            // Get the file which contains translations for all job items.
            // Loop through the remotes, populate translated data.
            $asset_id = $quote['Projects']['Project']['Files']['File']['AssetID'];
            $data_json = $api_client->getFileTranslation($asset_id, $translator->mapToRemoteLanguage($job->getTargetLangcode()));
            $data = Json::decode($data_json);

            if (!$data) {
              $data_error_message = json_last_error_msg();
              throw new TMGMTException($data_error_message);
            }
            // Batch process the storing of the translated data.
            batch_set(_lionbridge_translation_provider_batch($job, $data));
          }

          $translation_complete = TRUE;
          break;

      }
      return $translation_complete;
    }
  }

  /**
   * {@inheritdoc}
   *
   * @todo: Jobs with authorized Quotes should not be able to be aborted.
   */
  public function abortTranslation(JobInterface $job) {
    $translator = $job->getTranslator();
    $api_client = new LionbridgeConnector($translator);
    $quote = $api_client->getQuote($job->getReference());

    // If the status is Pending reject the quote on Lionbridge.
    if ($quote && empty($quote['Errors']) && $quote['Status'] == LionbridgeConnector::QUOTE_STATUS_PENDING) {
      $api_client->rejectQuote($quote['QuoteID']);
    }

    $job->aborted();
    return TRUE;
  }

  /**
   * Sends a file to the Lionbridge translation service.
   */
  protected function addFile($translation, LionbridgeConnector $api_client) {
    return $api_client->addFile(
      $translation['source_language'],
      $translation['fileName'],
      $translation['content_type'],
      $translation['file_content']
    );
  }

  /**
   * Sends a create project request to the Lionbridge translation service.
   */
  protected function addProject($project_data, LionbridgeConnector $api_client) {
    return $api_client->addProject(
      $project_data['project_title'],
      $project_data['service_id'],
      $project_data['source_language'],
      $project_data['target_languages'],
      $project_data['file_assets']
    );
  }

  /**
   * Send Memory Update in xml to Lionbridge.
   *
   * @param string $file_name
   *    The file name.
   * @param array $update_data
   *    The data to be used in render array to form file content.
   * @param array $entity_data
   *    The source entity data.
   * @param array $translated_entity_data
   *    The translated entity data.
   * @param LionbridgeConnector $api_client
   *   The API client.
   *
   * @return array
   *   The information about the file returned from the API client.
   */
  public function generateUpdateFile($file_name, $update_data, $entity_data, $translated_entity_data, LionbridgeConnector $api_client) {
    // Get the content to be translated and the already translated content and
    // build an array of "content_corrections" that will be sent to Lionbridge.
    $content_corrections = [];

    foreach (['source_content' => reset($entity_data), 'target_content' => $translated_entity_data] as $key => $data_to_flat) {
      if ($data_to_flat == NULL) {
        $data_flat = NULL;
      }
      else {
        $data_flat = array_filter(\Drupal::service('tmgmt.data')->flatten($data_to_flat), [\Drupal::service('tmgmt.data'), 'filterData']);
      }
      foreach ($data_flat as $flat_key => $value) {
        $flat_key = str_replace(['[', ']'], ':', $flat_key);
        $content_corrections[$flat_key][$key] = trim(str_replace(['“', '”'], '\"', htmlspecialchars($value['#text'])));
      }
    }

    foreach ($content_corrections as $data_item_key => $value) {
      // Preserve the original data.
      $this->translationKeys[] = [
        'tjiid' => key($entity_data),
        'data_item_key' => $data_item_key,
      ];
    }

    // Compose render array with the translation update properties and values.
    $update_xml = [
      '#theme' => $update_data['theme'],
      '#source_language' => $update_data['source_language'],
      '#target_language' => $update_data['target_language'],
      '#item_path' => $update_data['item_path'],
      '#content_corrections' => $content_corrections,
    ];

    // Generate update file content.
    $file_content = \Drupal::service('renderer')->render($update_xml)->__toString();

    $file_data = [
      'source_language' => $update_data['source_language'],
      'fileName' => urlencode($file_name . '.xml'),
      'content_type' => 'text/xml',
      'file_content' => $file_content,
    ];

    return $this->addFile($file_data, $api_client);
  }

  /**
   * Gets a quote from the Lionbridge translation service.
   */
  protected function generateQuote($project_id, $notification_url, LionbridgeConnector $api_client) {
    return $api_client->generateQuote($project_id, $notification_url);
  }

  /**
   * Create JSON encoded file.
   *
   * @param string $file_name
   *   The lionbridge connector.
   * @param string $source_language
   *   The source language.
   * @param mixed $data
   *   The data to be flattened.
   * @param LionbridgeConnector $api_client
   *   The API client.
   *
   * @return array
   *   The created file.
   */
  protected function generateFile($file_name, $source_language, $data, LionbridgeConnector $api_client) {
    $translatable_content = [];

    $data_flat = array_filter(\Drupal::service('tmgmt.data')->flatten($data), array(\Drupal::service('tmgmt.data'), 'filterData'));

    foreach ($data_flat as $key => $value) {
      list($tjiid, $data_item_key) = explode('][', $key, 2);

      // Preserve the original data.
      $this->translationKeys[] = [
        'tjiid' => $tjiid,
        'data_item_key' => $data_item_key,
      ];

      // This is the actual text sent to Lionbridge for translation.
      $translatable_content[$tjiid][$data_item_key] = str_replace(array('“', '”'), '\"', $value['#text']);
    }

    $file_content = json_encode($translatable_content, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

    $translation = [
      'source_language' => $source_language,
      'fileName' => urlencode($file_name . '.json'),
      'content_type' => 'application/json',
      'file_content' => $file_content,
    ];

    return $this->addFile($translation, $api_client);
  }

  /**
   * Create a file name.
   *
   * @param string $file_name
   *   The file name.
   * @param string $source_language
   *   The source language code.
   * @param string $target_language
   *   The target language code.
   *
   * @return string
   *   The generated file name.
   */
  protected function generateFileName($file_name, $source_language, $target_language) {
    $length = 0;
    $file_name_string = '';
    $string = strtolower($file_name);

    if (preg_match_all('/(\w+)/is', $string, $matches)) {
      foreach ($matches[0] as $match) {
        $length += strlen($match);

        if ($length < 45) {
          $file_name_string .= $match . '-';
        }
      }

      $file_name_string .= $source_language . '-' . $target_language;
      return $file_name_string;
    }

    // Something went wrong, just return $string.
    return $string;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc