Migrate Translated Content from Drupal 6 to Drupal 8

Apr 12, 2017 · by Jigar Mehta

Now that Drupal 6 has reached end-of-life, many sites are moving to Drupal 8. If you had multilingual content in Drupal 6, this upgrade used to be very difficult—but since Drupal 8.2 there is support for migrating all your translations! In this article, we will discuss how to migrate translated content from Drupal 6 to Drupal 8.

This article would not have been possible without the help of my colleague Dave. Gracias Dave!

The problem

We have a Drupal 6 database containing story nodes about animal hybrids. Some nodes have translations in English, Spanish and French; some are untranslated; and others are language-neutral (non-translatable). Our goal is to migrate the D6 nodes into a D8 website, preserving the translations.

Before we start

The module

To write the migrations, we create a module - in our case, migrate_example_i18n. There's nothing special about the module declaration, except for the dependencies:

  • migrate_plus and migrate_tools provide various features for defining and executing migrations.
  • migrate_source_csv: Will be used for demonstrating migration of translated content from non-Drupal sources in an upcoming article.
  • migrate_drupal: This module provides tools for migrating data from older versions of Drupal. It comes with Drupal 8.x core. Since this migration uses a Drupal 6 site as a source for its data, we need the migrate_drupal module.

How do translations work?

    Before jumping into writing these migrations, it is important to mention that Drupal 6 and Drupal 8 translations work very differently. Here's the difference in a nutshell:

    • Drupal 6: When we translate a node, a new node is created with a different ID. This translated node has a property named tnid, which stores the ID of the original node, linking the two nodes together. For language-neutral or untranslated content, the tnid is set to 0.
    • Drupal 8: When we translate a node, no new node is created! The translation is saved in the fields of the original node, but with a different language code.

    To map between the D6 and D8 translation models, we'll use two migrations:

    • The example_hybrid_base migration will migrate the original content of each node, untranslated.
    • The example_hybrid_i18n migration will migrate in all the translations, and connect each one to the original node from example_hybrid_base..

    We group the two migrations using the example_hybrid migration group to keep things clean and organized. Then we can execute both migrations with drush migrate-import --group=example_hybrid --update.

    Step 1: Base migration

    Let's start with the example_hybrid_base, to migrate all the base data (non-translations) in this migration. Described below are some noteworthy parameters:

    Source

    source:
      plugin: d6_node
      node_type: story
      key: drupal_6
      constants:
        node_article: article
        body_format: full_html
    • plugin: Since we want to import data from a Drupal installation, we need to set the source plugin to d6_node. The d6_node source plugin is introduced by the migrate_drupal, module and it helps us read nodes from a Drupal 6 database without having to write queries manually.
    • node_type: This tells the source plugin that we are interested in just one particular Drupal 6 node type, namely story.
    • key: Our Drupal 6 data doesn't come from our main Drupal 8 database—instead it comes from a secondary database connection. We choose a key to identify each such connection, and we need to tell the source which such key to use. The keys themselves are defined in the $databases variable in our settings.php or settings.local.php. See the example settings.local.php file to see how it's done.
    • constants: We define some hard-coded values under this parameter.
    • translations: Notice there is no translations parameter here. The default value (false) tells the source plugin that we're only interested in migrating non-translations, i.e. content in the base language and language-neutral content.

    Destination

    destination:
      plugin: 'entity:node'
    • plugin: Since we want to create node entities in Drupal 8, we specify this as entity:node. That's it.
    • translations: Again we do not define the translations parameter while migrating base data. Omitting the parameter tells the destination plugin that we are interested in creating fresh nodes for each record, not translations of existing nodes.

    Process

    process:
      type: constants/node_article
      langcode:
        plugin: default_value
        source: language
        default_value: und
      'body/value': body
      'body/format': constants/body_format
      title: title
      field_one_liner: field_one_liner
      sticky: sticky
      status: status
      promote: promote

    This is where we map the old node properties to the new node properties. Most of the properties have been assigned as is, without alteration, however, some noteworthy properties have been discussed below:

    • nidThere is no nid parameter here, because we don't care what nid each new node has in Drupal 8. Drupal can just assign a new nid to each node in the normal way.
    • type: We specify that we want to create article nodes.
    • langcode: The langcode parameter was formerly language in Drupal 6, so we rename it here. Also, if a Drupal 6 node is language-neutral, it will have no value at all here. In that case,  we default to und.
    • body: We can assign this property directly to the body property. However, the Drupal 6 data is treated as plain text in Drupal 8 in that case. So migrating with body: body, the imported nodes in Drupal 8 would show visible HTML markup on your site. To resolve this, we explicitly assign the old body to body/value and specify that the text is in HTML by assigning full_html to body/format. That tells Drupal to treat the body as Full HTML.

    This takes care of the base data. If you run this migration with drush migrate-import example_hybrid_base --update, all Drupal 6 nodes which are in base language or are language-neutral will be migrated into Drupal 8.

    Step 2: Translation migration

    We are halfway through now! All that's missing is migrating translations of the nodes we migrated above. To do this, we create another migration with the ID example_hybrid_i18n:

    source:
      plugin: d6_node
      node_type: story
      translations: true
      # ...
    destination:
      plugin: 'entity:node'
      translations: true
    process:
      nid:
        plugin: migration
        source: tnid
        migration: example_hybrid_base
      langcode: language
      # ...
    migration_dependencies:
      required:
        - example_hybrid_base

    The migration definition remains mostly the same but has the following important differences as compared the base migration:

    • source:
      • translations: We set this to true to make the source plugin read only translations.
    • destination:
      • translations: We set this to true to make the destination plugin create translations for existing nodes instead of creating fresh new nodes for each source record.
    • process:
      • nid: In this case, we do care what the Drupal 8 nid is for each node. It has to match the nid for the untranslated version of this content, so that Drupal can add a translation to the correct node. This section uses the migration process plugin to figure out the right nid. It tells Drupal to check the previously-executed example_hybrid_base migration for a D6 node that has the same tnid as this D6 node. It will then then reuse the resulting nid here.
      • langcode: We define the language in which the translation should be created.
    • migration_dependencies: Since we cannot add translations to nodes that do not yet exist, we tell Drupal that this migration depends on the base migration example_hybrid_base. That way, the base migration will run before this migration.

    That's it! We can run our translation migration with drush migrate-import example_hybrid_i18n --update and the translations will be imported into Drupal 8. Alternatively, we can use the migration group we defined to run both these migrations at once - the base migration will automatically be executed first and then the i18n migration. Here's how the output should look:

    $ drush migrate-import --group=example_hybrid --update
    Processed 8 items (8 created, 0 updated, 0 failed, 0 ignored) - done with 'example_hybrid_base'
    Processed 9 items (9 created, 0 updated, 0 failed, 0 ignored) - done with 'example_hybrid_i18n'

    You can check if everything went alright by clicking the Translate option for any translated node in Drupal 8. If everything went correctly, you should see that the node exists in the original language and has one or more translations.

    Next steps

    Picture of Jigar Mehta

    About the author, Jigar Mehta

    Jigar is a full-stack PHP developer. He has planned and built a wide array of PHP powered applications ranging from school admission systems, reservation systems, PHP integrations with SAP Business One, ERP solutions and SaaS based platforms.

    More Articles by Jigar Mehta »
    Sign up for our training newsletter

    Receive email notifications about upcoming Drupal trainings near you.

    Featured Articles