API Entity Translation
In Drupal 8, field language is no longer exposed via the public API. Instead, fields are attached to language-aware objects from which they "inherit" their language.
The key benefits here are:
- We no longer need to worry about field portability—this is handled internally by the entity object.
// Determine the $active_langcode somehow. $translation = $entity->getTranslation($active_langcode); $value = $translation->field_foo->value;
- We no longer need to pass the active language explicitly—we can just pass the translation object, which implements EntityInterface and is essentially a clone of the original object with a different internal language. In many cases, the code doesn’t need to know the language explicitly.
// Instantiate the proper translation object just once and pass it around
// wherever it is needed. This is typically taken care of by core
// subsystems and in many common cases an explicit retrieval of the
// translation object is not needed.
$langcode = Drupal::languageManager()->getLanguage(Language::TYPE_CONTENT);
$translation = $entity->getTranslation($langcode);
entity_do_stuff($translation);
function entity_do_stuff(EntityInterface $entity) {
$value = $entity->field_foo->value;
// do stuff
}
- We now have a reusable entity language negotiation API that can determine the most appropriate translation for a given context:
// Simplified code to generate a renderable array for an entity.
function viewEntity(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
// The EntityManagerInterface::getTranslationFromContext() method will
// apply entity language negotiation logic to the whole entity object
// and will return the proper translation object for the given context.
// The $langcode parameter is optional and indicates the language of the
// current context. If it is not specified the current content language
// is used, which is the desired behavior during the rendering phase.
// Note that field values are left alone in the process, so empty values
// will just not be displayed.
$langcode = NULL;
$translation = $this->entityManager->getTranslationFromContext($entity, $langcode);
$build = entity_do_stuff($translation, 'full');
return $build;
}
We can also specify an optional $context parameter that describes the context in which the translation will be used:
// Simplified token replacements generation code.
function node_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
// If no language is specified for this context we just default to the
// default entity language.
if (!isset($options['langcode'])) {
$langcode = Language::LANGCODE_DEFAULT;
}
// We pass a $context parameter describing the operation being performed.
// The default operation is 'entity_view'.
$context = array('operation' => 'node_tokens');
$translation = \Drupal::service('entity.repository')->getTranslationFromContext($data['node'], $langcode, $context);
$items = $translation->get('body');
// do stuff
return $replacements;
}
The logic used to determine which translation object to return can be altered by modules. See LanguageManager::getFallbackCandidates() for more details.
Field data is shared across all translation objects. Changing the value of an untranslated field affects all translations.
$entity->langcode->value = 'en';
$translation = $entity->getTranslation('it');
$en_value = $entity->field_foo->value; // 'bar'
$it_value = $translation->field_foo->value; // 'bella'
$entity->field_untranslatable->value = 'baz';
$translation->field_untranslatable->value = 'zio';
$value = $entity->field_untranslatable->value; // 'zio'
You can instantiate a translation object from the original or from another translation using EntityInterface::getTranslation(). If the active language is needed, use EntityInterface::language(). The original (untranslated) entity can be retrieved via EntityInterface::getUntranslated().
$entity->langcode->value = 'en';
$translation = $entity->getTranslation('it');
$langcode = $translation->language()->id; // 'it'
$untranslated_entity = $translation->getUntranslated();
$langcode = $untranslated_entity->language()->id; // 'en'
$identical = $entity === $untranslated_entity; // TRUE
$entity_langcode = $translation->getUntranslated()->language()->id; // 'en'
EntityInterface now includes several methods to facilitate working with entity translations. To act on every available translation, use EntityInterface::getTranslationLanguages():
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
$translation = $entity->getTranslation($langcode);
entity_do_stuff($translation);
}
You can also add, remove, or check for the existence of translations:
if (!$entity->hasTranslation('fr')) {
$translation = $entity->addTranslation('fr', array('field_foo' => 'bag'));
}
// Equivalent to:
$translation = $entity->getTranslation('fr');
$translation->field_foo->value = 'bag';
// Accessing a removed translation throws an exception.
$translation = $entity->getTranslation('it');
$entity->removeTranslation('it');
$value = $translation->field_foo->value; // throws InvalidArgumentException
When translations are added to or removed from the storage, the following hooks are triggered:
hook_entity_translation_insert()hook_entity_translation_delete()
You can still retrieve a field’s language directly via its method:
$langcode = $translation->field_foo->getLangcode();