manager = $manager; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static($container->get('content_translation.manager')); } /** * Populates target values with the source values. * * @param \Drupal\Core\Entity\ContentEntityInterface $entity * The entity being translated. * @param \Drupal\Core\Language\LanguageInterface $source * The language to be used as source. * @param \Drupal\Core\Language\LanguageInterface $target * The language to be used as target. */ public function prepareTranslation(ContentEntityInterface $entity, LanguageInterface $source, LanguageInterface $target) { /* @var \Drupal\Core\Entity\ContentEntityInterface $source_translation */ $source_translation = $entity->getTranslation($source->getId()); $target_translation = $entity->addTranslation($target->getId(), $source_translation->toArray()); // Make sure we do not inherit the affected status from the source values. if ($entity->getEntityType()->isRevisionable()) { $target_translation->setRevisionTranslationAffected(NULL); } /** @var \Drupal\user\UserInterface $user */ $user = $this->entityManager()->getStorage('user')->load($this->currentUser()->id()); $metadata = $this->manager->getTranslationMetadata($target_translation); // Update the translation author to current user, as well the translation // creation time. $metadata->setAuthor($user); $metadata->setCreatedTime(REQUEST_TIME); } /** * Builds the translations overview page. * * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The route match. * @param string $entity_type_id * (optional) The entity type ID. * @return array * Array of page elements to render. */ public function overview(RouteMatchInterface $route_match, $entity_type_id = NULL) { /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $entity = $route_match->getParameter($entity_type_id); $account = $this->currentUser(); $handler = $this->entityManager()->getHandler($entity_type_id, 'translation'); $manager = $this->manager; $entity_type = $entity->getEntityType(); // Start collecting the cacheability metadata, starting with the entity and // later merge in the access result cacheability metadata. $cacheability = CacheableMetadata::createFromObject($entity); $languages = $this->languageManager()->getLanguages(); $original = $entity->getUntranslated()->language()->getId(); $translations = $entity->getTranslationLanguages(); $field_ui = $this->moduleHandler()->moduleExists('field_ui') && $account->hasPermission('administer ' . $entity_type_id . ' fields'); $rows = []; $show_source_column = FALSE; if ($this->languageManager()->isMultilingual()) { // Determine whether the current entity is translatable. $translatable = FALSE; foreach ($this->entityManager->getFieldDefinitions($entity_type_id, $entity->bundle()) as $instance) { if ($instance->isTranslatable()) { $translatable = TRUE; break; } } // Show source-language column if there are non-original source langcodes. $additional_source_langcodes = array_filter(array_keys($translations), function ($langcode) use ($entity, $original, $manager) { $source = $manager->getTranslationMetadata($entity->getTranslation($langcode))->getSource(); return $source != $original && $source != LanguageInterface::LANGCODE_NOT_SPECIFIED; }); $show_source_column = !empty($additional_source_langcodes); foreach ($languages as $language) { $language_name = $language->getName(); $langcode = $language->getId(); $add_url = new Url( "entity.$entity_type_id.content_translation_add", [ 'source' => $original, 'target' => $language->getId(), $entity_type_id => $entity->id(), ], [ 'language' => $language, ] ); $edit_url = new Url( "entity.$entity_type_id.content_translation_edit", [ 'language' => $language->getId(), $entity_type_id => $entity->id(), ], [ 'language' => $language, ] ); $delete_url = new Url( "entity.$entity_type_id.content_translation_delete", [ 'language' => $language->getId(), $entity_type_id => $entity->id(), ], [ 'language' => $language, ] ); $operations = [ 'data' => [ '#type' => 'operations', '#links' => [], ], ]; $links = &$operations['data']['#links']; if (array_key_exists($langcode, $translations)) { // Existing translation in the translation set: display status. $translation = $entity->getTranslation($langcode); $metadata = $manager->getTranslationMetadata($translation); $source = $metadata->getSource() ?: LanguageInterface::LANGCODE_NOT_SPECIFIED; $is_original = $langcode == $original; $label = $entity->getTranslation($langcode)->label(); $link = isset($links->links[$langcode]['url']) ? $links->links[$langcode] : ['url' => $entity->urlInfo()]; if (!empty($link['url'])) { $link['url']->setOption('language', $language); $row_title = $this->l($label, $link['url']); } if (empty($link['url'])) { $row_title = $is_original ? $label : $this->t('n/a'); } // If the user is allowed to edit the entity we point the edit link to // the entity form, otherwise if we are not dealing with the original // language we point the link to the translation form. $update_access = $entity->access('update', NULL, TRUE); $translation_access = $handler->getTranslationAccess($entity, 'update'); $cacheability = $cacheability ->merge(CacheableMetadata::createFromObject($update_access)) ->merge(CacheableMetadata::createFromObject($translation_access)); if ($update_access->isAllowed() && $entity_type->hasLinkTemplate('edit-form')) { $links['edit']['url'] = $entity->urlInfo('edit-form'); $links['edit']['language'] = $language; } elseif (!$is_original && $translation_access->isAllowed()) { $links['edit']['url'] = $edit_url; } if (isset($links['edit'])) { $links['edit']['title'] = $this->t('Edit'); } $status = ['data' => [ '#type' => 'inline_template', '#template' => '{% if status %}{{ "Published"|t }}{% else %}{{ "Not published"|t }}{% endif %}{% if outdated %} {{ "outdated"|t }}{% endif %}', '#context' => [ 'status' => $metadata->isPublished(), 'outdated' => $metadata->isOutdated(), ], ]]; if ($is_original) { $language_name = $this->t('@language_name (Original language)', ['@language_name' => $language_name]); $source_name = $this->t('n/a'); } else { $source_name = isset($languages[$source]) ? $languages[$source]->getName() : $this->t('n/a'); $delete_access = $entity->access('delete', NULL, TRUE); $translation_access = $handler->getTranslationAccess($entity, 'delete'); $cacheability = $cacheability ->merge(CacheableMetadata::createFromObject($delete_access)) ->merge(CacheableMetadata::createFromObject($translation_access)); if ($entity->access('delete') && $entity_type->hasLinkTemplate('delete-form')) { $links['delete'] = [ 'title' => $this->t('Delete'), 'url' => $entity->urlInfo('delete-form'), 'language' => $language, ]; } elseif ($translation_access->isAllowed()) { $links['delete'] = [ 'title' => $this->t('Delete'), 'url' => $delete_url, ]; } } } else { // No such translation in the set yet: help user to create it. $row_title = $source_name = $this->t('n/a'); $source = $entity->language()->getId(); $create_translation_access = $handler->getTranslationAccess($entity, 'create'); $cacheability = $cacheability ->merge(CacheableMetadata::createFromObject($create_translation_access)); if ($source != $langcode && $create_translation_access->isAllowed()) { if ($translatable) { $links['add'] = [ 'title' => $this->t('Add'), 'url' => $add_url, ]; } elseif ($field_ui) { $url = new Url('language.content_settings_page'); // Link directly to the fields tab to make it easier to find the // setting to enable translation on fields. $links['nofields'] = [ 'title' => $this->t('No translatable fields'), 'url' => $url, ]; } } $status = $this->t('Not translated'); } if ($show_source_column) { $rows[] = [ $language_name, $row_title, $source_name, $status, $operations, ]; } else { $rows[] = [$language_name, $row_title, $status, $operations]; } } } if ($show_source_column) { $header = [ $this->t('Language'), $this->t('Translation'), $this->t('Source language'), $this->t('Status'), $this->t('Operations'), ]; } else { $header = [ $this->t('Language'), $this->t('Translation'), $this->t('Status'), $this->t('Operations'), ]; } $build['#title'] = $this->t('Translations of %label', ['%label' => $entity->label()]); // Add metadata to the build render array to let other modules know about // which entity this is. $build['#entity'] = $entity; $cacheability ->addCacheTags($entity->getCacheTags()) ->applyTo($build); $build['content_translation_overview'] = [ '#theme' => 'table', '#header' => $header, '#rows' => $rows, ]; return $build; } /** * Builds an add translation page. * * @param \Drupal\Core\Language\LanguageInterface $source * The language of the values being translated. Defaults to the entity * language. * @param \Drupal\Core\Language\LanguageInterface $target * The language of the translated values. Defaults to the current content * language. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The route match object from which to extract the entity type. * @param string $entity_type_id * (optional) The entity type ID. * * @return array * A processed form array ready to be rendered. */ public function add(LanguageInterface $source, LanguageInterface $target, RouteMatchInterface $route_match, $entity_type_id = NULL) { $entity = $route_match->getParameter($entity_type_id); // @todo Exploit the upcoming hook_entity_prepare() when available. // See https://www.drupal.org/node/1810394. $this->prepareTranslation($entity, $source, $target); // @todo Provide a way to figure out the default form operation. Maybe like // $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default'; // See https://www.drupal.org/node/2006348. // Use the add form handler, if available, otherwise default. $operation = $entity->getEntityType()->hasHandlerClass('form', 'add') ? 'add' : 'default'; $form_state_additions = []; $form_state_additions['langcode'] = $target->getId(); $form_state_additions['content_translation']['source'] = $source; $form_state_additions['content_translation']['target'] = $target; $form_state_additions['content_translation']['translation_form'] = !$entity->access('update'); return $this->entityFormBuilder()->getForm($entity, $operation, $form_state_additions); } /** * Builds the edit translation page. * * @param \Drupal\Core\Language\LanguageInterface $language * The language of the translated values. Defaults to the current content * language. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The route match object from which to extract the entity type. * @param string $entity_type_id * (optional) The entity type ID. * * @return array * A processed form array ready to be rendered. */ public function edit(LanguageInterface $language, RouteMatchInterface $route_match, $entity_type_id = NULL) { $entity = $route_match->getParameter($entity_type_id); // @todo Provide a way to figure out the default form operation. Maybe like // $operation = isset($info['default_operation']) ? $info['default_operation'] : 'default'; // See https://www.drupal.org/node/2006348. // Use the edit form handler, if available, otherwise default. $operation = $entity->getEntityType()->hasHandlerClass('form', 'edit') ? 'edit' : 'default'; $form_state_additions = []; $form_state_additions['langcode'] = $language->getId(); $form_state_additions['content_translation']['translation_form'] = TRUE; return $this->entityFormBuilder()->getForm($entity, $operation, $form_state_additions); } }