How to create translations programmatically? I have a batch in which i'm importing nodes from REST webservices.
My application has two languages, FR and EN (default is FR).
So, I'm creating the node in FR, then, I loop through all other languages (just EN for now) and try to do as following :

$languages = \Drupal::languageManager()-&gt;getLanguages();
unset($languages['fr']);

foreach ($languages AS $langcode =&gt; $language) {
  if ($node-&gt;hasTranslation($langcode)) {
    $node_translation = $node-&gt;getTranslation($langcode);
  } else {
    $node_translation = $node-&gt;addTranslation($langcode, $node-&gt;toArray());
  }

  $node_translation-&gt;status = 1;
  $node_translation-&gt;title = 'title en';
  $node_translation-&gt;subtitle = 'subtitle en';
  $node_translation-&gt;save();
}


And it crashes with a 500 error, reporting :

 - Drupal\Core\Entity\EntityStorageException : Invalid translation language (und) specified. dans Drupal\Core\Entity\Sql\SqlContentEntityStorage-&gt;save() (ligne 805 de /.../web/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).
 - InvalidArgumentException : Invalid translation language (und) specified. dans Drupal\Core\Entity\ContentEntityBase-&gt;getTranslation() (ligne 783 de /.../web/core/lib/Drupal/Core/Entity/ContentEntityBase.php).


I'm pulling my hair out on this issue for days and can not understand why the error message tells me $langcode equals 'und' whereas a var_dump of it just before this line returns me 'en'.



Edit :
I tried to hardcode and remove the foreach. So just after the $node->save() :

$node_translation = $node-&gt;addTranslation('en', $node-&gt;toArray());
$node_translation-&gt;set('title', 'title en');
$node_translation-&gt;set('field_sub_title', 'subtitle en');
$node_translation-&gt;save();


This time, the error is the following : 

InvalidArgumentException : Invalid translation language (en) specified.
dans Drupal\Core\Entity\ContentEntityBase-&gt;addTranslation() (ligne 863
de /var/www/drupalvm/drupal/web/core/lib/Drupal/Core/Entity/ContentEntityBase.php).
After several days, I finally found the solution. Obviously, there is a bug with Drupal (here 8.4.0).

The hasTranslation() method don't work properly. Instead, you should use  EntityManager::getTranslationFromContext() (will be deprecated in Drupal 9 though).

You can also write a new method like this one to replace it:

/**
 * Check if a node as a translation for the given language.
 *
 * @param object $node
 *   The node object.
 * @param string $langcode
 *   The langcode.
 *
 * @return bool
 *   True (exists) or false (does not exist).
 */
function has_translation($node, $langcode) {
  $existing_translation = \Drupal::service('entity.repository')-&gt;getTranslationFromContext($node, $langcode);
  return ($existing_translation-&gt;langcode-&gt;value === $langcode) ? TRUE : FALSE;
}


So, my corrected code is the following:

if (has_translation($node, $langcode)) {
  $node_translation = $node-&gt;getTranslation($langcode);
  $node_translation-&gt;title = 'title en';
  $node_translation-&gt;field_sub_title = 'subtitle en';
  $node_translation-&gt;status = NodeInterface::PUBLISHED;
}
else {
  $node-&gt;addTranslation($langcode, $values);
}

$node-&gt;save();