In Drupal 8 there is only one unified way of translating content to different languages. However, in Drupal 7, there were two different ways to do it:

  • Using core's Content Translation module: this will create separate nodes per language.
  • Using the contributed Entity Translation module: this maintains a single node and the translation happens at a field level.

Entity Translation is what is closer to Drupal 8's implementation of translation. Evolving Web has written blog posts about both methods in the past: content translation and entity translation.

However, that was a while ago, and there are updated ways to do proceed with entity translation migration. We'll go over those updated methods in this article.

Before We Start

The Problem

Our imaginary client has provided a database dump for their Drupal 7 site containing some nodes. These nodes might have translations in English, Spanish and French, but there could also be some non-translatable nodes. We need to migrate those nodes to Drupal 8 while preserving the translations.

Setting up the Migration

Create the migration module

We need to create a module for our migrations. In this example, we're naming it migrate_example_entity_translation

We then need to add the following modules as dependencies in the module declaration:

Create a migration group

To group the migrations, we also need to create a migration group. To do so, we’ll create a fairly simple configuration file so that the group gets created when the module is installed. The file’s contents should be as follows:

id: entity_translation

label: Entity Translation Group

source_type: Drupal 7

shared_configuration:

source:

key: migrate_d7

Define a new database connection

Next, you need to load the Drupal 7 database into your Drupal 8 installation. To do so, you need to define a new database connection in your settings.php file like this:

$databases['migrate_d7']['default'] = array(

'driver' => 'mysql',

'database' => 'migrate_d7',

'username' => 'user',

'password' => 'password',

'host' => 'db',

'prefix' => '',

);

And then you can import the database dump into this new database connection using your preferred method.

Writing the Migrations

Next thing to do is to write the actual migrations. Per our requirements, we need to write two different migrations: one for the base nodes and one for the translations.

Since Drupal 8.1.x, migrations are plugins that should be stored in a migrations folder inside any module. You can still make them configuration entities as part of the migrate_plus module but I personally prefer to follow the core recommendation because it's easier to develop (you can make an edit and just rebuild cache to update it).

Write the base nodes migration

The first migration to write is the base nodes migration. This will be just a simple migration without anything special related to entity translation.

The full migration file will look like this:

id: example_creature_base
label: Creature base data
migration_group: entity_translation
migration_tags:
- node
- Drupal 7
source:
plugin: d7_node
node_type: article
destination:
plugin: entity:node
process:
type:
plugin: default_value
default_value: article
title: title
status: status
langcode: language
created: created
changed: changed
promote: promote
sticky: sticky
revision_log: log
field_one_liner: field_one_liner
body/value: body/value
body/format:
plugin: default_value
default_value: full_html

Write the translations migration

Now we should write the translations migration. We start by creating the migration file. In this case, we'll name it example_creature_translations.yml. The source section of this migration will look like this:

source:
plugin: d7_node_entity_translation
node_type: article

In the plugin, we're using d7_node_entity_translation; this is a plugin already included in core to handle this type of migration.

The destination for this plugin will be pretty similar to the destination for the base migration. It will look like this:

destination:
plugin: entity:node
translations: true
destination_module: content_translation

Now it's time to write the process section for this migration. It will be pretty similar to the base migration. You only need to keep in mind that you need to migrate two new properties: content_translation_source and content_translation_outdated. So, your process section will look like this:

process:
nid:
plugin: migration_lookup
migration: example_creature_base
source: entity_id
type:
plugin: default_value
default_value: article
title: title
status: status
langcode: language
created: created
changed: changed
promote: promote
sticky: sticky
revision_log: log
field_one_liner: field_one_liner
body/value: body/value
body/format:
plugin: default_value
default_value: full_html
content_translation_source: source
content_translation_outdated: translate

Finally you can setup migration dependencies to ensure your migrations run in the right order:

migration_dependencies:
required:
- example_creature_base

You can look at the full migration file in the code samples repo.

Running the Migrations

Since we have set dependencies, we can instruct Drupal to run the migration group and it will run the migrations in the right order.

To do so, execute drush mim --group=entity_translation and the output will look like this:

[notice] Processed 9 items (9 created, 0 updated, 0 failed, 0 ignored) - done with 'example_creature_base'
[notice] Processed 9 items (9 created, 0 updated, 0 failed, 0 ignored) - done with 'example_creature_translations'

You can also run drush ms to see current migration status:

--------------------- ----------------------------------- -------- ------- ---------- ------------- ---------------------

Group Migration ID Status Total Imported Unprocessed Last Imported

 --------------------- ----------------------------------- -------- ------- ---------- ------------- ---------------------

Entity Translation example_creature_base Idle 9 9 0 2020-11-09 15:54:06

Entity Translation example_creature_tran Idle 9 9 0 2020-11-09 15:54:07

Next Steps

+ more awesome articles by Evolving Web