Drupal 8 multilingual tidbits 19: content translation development

Up to date as of November 11th, 2015.

Now that we covered how content translation workflow works in Drupal 8, its time to look a bit at the API side. In Drupal 7 this meant dealing with scary seemingly infinitely nested arrays with language codes, field names, deltas, etc. Drupal 8 makes this a whole lot simpler.

Translation basics on an entity

Drupal 8's entity API handles entities as full fledged objects. Translations of entities may be requested from this object and returned as a reusable entity object as well, which can be treated the same way as the original entity that we loaded. Here are some quick examples:

<?php
use Drupal\node\Entity\Node;

// Load node 4. In terms of language, this will get us an entity
// in the original submission language.
$node = Node::load(4);

// Get a list of all translations and collect titles.
$titles = [];
$languages = $node->getTranslationLanguages();
foreach (
$languages as $langcode => $language) {
 
// The object returned by getTranslation() behaves the same way as $node.
 
$translation = $node->getTranslation($langcode);
 
$titles[$langcode] = $translation->title;
}

// If the node has no Hungarian translation, add one.
if (!$node->hasTranslation('hu')) {
 
$translation = $node->addTranslation('hu', array('title' => 'Hungarian title'));
 
$translation->save();
}

// If the node has no Spanish translation, this also adds one. If it does
// already have a Spanish translation, it will update the title.
$node->getTranslation('es')->title->value = 'Spanish title';
$node->save();

// Remove the Hungarian translation.
$node->removeTranslation('hu');
$node->save();
?>

Loading the right translation

Drupal 8 also comes with several supporting systems for dealing with content language. For example, a content language is selected for the page to be used for translated content display. You can retrieve that language from the language manager and use to deal with the right translation.

<?php
use Drupal\Core\Language\LanguageInterface;
use
Drupal\taxonomy\Entity\Term;

$term = Term::load(23);
$langcode = \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT);
$translation = $term->getTranslation($langcode);
// Now deal with the right language from here on.
?>

A possible problem with that is getTranslation() as we have seen above will just create a new translation if there is none, so you may as well just get back the original language values without a different translation loaded. That is not a very big problem if you always want to fall back on the original language of an entity when requesting a non-existent translation. If that is not the case however, you need entity language negotiation. That is provided by the entity repository and helps you figure out which translation to load.

<?php
use Drupal\user\Entity\User;

$account = User::load(15);
$langcode = \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT);
// Get the user profile with language fallback, if it did not have a translation for $langcode.
$translation = \Drupal::service('entity.repository')->getTranslationFromContext($account, $langcode);
?>

The entity repository uses the language manager and possibly contributed modules to figure out which language to load for the entity. Ideally $langcode would be that language, but if that is not available, modules get a chance to provide fallback schemes for which other language may be best suitable. See LanguageManager::getFallbackCandidates() as well as hook_language_fallback_candidates_alter() and hook_language_fallback_candidates_OPERATION_alter() for more information.

That's it for a short summary of how the much improved content translation API works in Drupal 8. There is of course a lot more to this topic from creating translatable entity types, new field types to dealing with revisions and entity field queries. For some of those deeper details on how content translations are stored and queried, see Francesco Placella's (unfortunately already slightly outdated) articles in Drupal Watchdog titled Wait, $langcode? What the Heck? and Entity Storage, the Drupal 8 Way.

Next up, I plan to look at complex core use cases where interface, content and configuration translation meet. If you know how the pieces map together, you can really translate practically anything!

Issues to work on

None at the moment.

Comments

David Park's picture

You say that calling $langcode = \Drupal::languageManager()->getLanguage(LanguageInterface::TYPE_CONTENT); would give the language object of the current content language? How is that? In my site i'm calling that and getLanguage will only return a language object if the LanguageInterface::TYPE_CONTENT witch is string "language_content" from the list of all languages that this content is translated with, ie array('en', 'sv', 'und', 'zxx') for a site with Swedish and English. I don't se that the array in getLanguage, that $languages = $this->getLanguages(LanguageInterface::STATE_ALL); is returning will include a object with key "language_content". I guess I'm missing something here?

Gábor Hojtsy's picture

My mistake, sorry for wasting your time. It should be \Drupal::languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT), fixing.

Christophe Jossart's picture

Thanks for this nice introduction, it really helped to get started quickly. I am working with the 8.1.x branch and noticed that $translation->title; becomes $translation->getTitle() (or $translation->get('title')->value;). Probably the same for $node->getTranslation('es')->title->value = 'Spanish title'; that should be replaced by $node->getTranslation('es')->setTitle('Spanish title');.

Nehal Rupani's picture

Hello Gábor,

Thanks for sharing multilingual functionality in detail. I am trying to import nodes automatically from excel file.

if ($node->hasTranslation($fields_values['langcode']) != 1) {
$translation = $node->addTranslation($fields_values['langcode']);

foreach($fields_values as $key => $value) {
$translation->set($key, $value);
}
$translation->save();
}
else {
$node->save();
}
With above code I was able to get node translated in to other language then english but What i found is when try to view those pages in admin/content area all my earlier path alias with english language are gone and it showing me nodes with other language which is not default language of my website. It should show English nodes by default. Do you think anything else is missing in above code.

What I can guess from admin screen is may be i need to add content_translation_source with is field in DB. how to do that while updating node.

Gábor Hojtsy's picture

Adding node translations should not affect path aliases. Check out http://cgit.drupalcode.org/multilingual_demo/tree/multilingual_demo.inst... for an example of creating nodes and translations programatically.

Add new comment