Skip to content
Drupal

Drupal Multilingual: Migrating Translations Without Losing Them

· 8 min read · 1,641 words
Shaker Abady

Written by

Shaker Abady

Founder & CEO, Gspikes

I own and run Gspikes, the agency I founded in 2018. Before going all-in, I led SEO at Chain Reaction — one of the region's largest digital agencies — and shipped production code at Orange and Forbes. Everything published here comes from projects I've personally built, migrated, or ranked.

LinkedIn

Last updated · September 2026

Drupal is the best multilingual CMS most people will ever use, and multilingual is where Drupal migrations go wrong most often. Both things are true, and the reason is the same: the translation model in Drupal 8 and later is fundamentally different from what came before it, and different from what every other platform does. Migrate translations as if they were just more content and you will arrive with twice the nodes, half the links and a set of hreflang tags pointing at pages that do not exist.

This guide is about getting translations across intact — from Drupal 7, from WordPress, from any source — and about the multilingual configuration that has to be right on the other side, because the migration is only half of it.

How Drupal thinks about translation now

Four core modules divide the work. Language defines which languages exist and how the site decides which one a visitor wants. Content Translation lets a single node carry a translation per language: one entity, one ID, one set of relationships, with translatable fields varying by language and untranslatable ones shared. Interface Translation handles the strings in code and templates. Configuration Translation covers everything in configuration that has a label: field names, view titles, menu names, site name.

The idea that matters is the first one. A translation is not a separate piece of content; it is the same content in another language. That is what makes a multilingual Drupal site coherent — one URL structure, one set of references, one revision history — and it is exactly what older systems did not do.

THE MODEL THAT CHANGEDSeparate nodes per language, or one node with translationsThis is the difference the migration has to bridge. Get it wrong and you arrive with duplicates.DRUPAL 7 NODE TRANSLATIONEN node/12own ID, own path, own referencesFR node/57own ID, own path, own referencesAR node/91own ID, own path, own referencesThree nodes tied by a translation-set ID.References point at one language’s copy.DRUPAL 8+ CONTENT TRANSLATIONONE NODE node/12EN translation: title, body, aliasFR translation: title, body, aliasAR translation: title, body, aliasshared: author, references, revisionsOne entity. Translatable fields vary by language;untranslatable fields are shared. Core adds hreflangonly for translations that exist.d7_node_translationStructure of Drupal core’s translation systems; no external data.Free to reuse with attribution: gspikes.com

Why Drupal 7 migrations break here

Drupal 7 had two incompatible models. Core’s node translation created a separate node per language, linked by a translation set ID; every French page was its own node with its own ID, its own references and its own path. The Entity Translation module did it the modern way, with field-level translations on one node, but far fewer sites used it. Many sites, in practice, used both — one for content added before 2013 and one after — and some added the i18n suite on top, which translated taxonomy, menus and variables through mechanisms of its own.

Core’s migration handles the first case: the d7_node_translation migration folds translation-set nodes into translations of a single node, and d7_node_entity_translation handles the second. The mapping is good and it is not magic. Where it stumbles is everywhere the old site was inconsistent: a translation set whose source node was deleted, a node translated in one module and then in the other, references that point at the French node rather than the set, path aliases that assumed one node per language. Every one of those has to be found before the migration runs, because after it runs they are duplicate content.

Migrating translations from anything else

From WordPress, the shape depends on the plugin. WPML and Polylang both store translations as separate posts linked by a relationship table or a taxonomy; the migration source has to join through that relationship so each language row lands on the same destination node. From a headless or custom source, the rule is the same: the source must yield one row per node per language, with a shared identifier that names the node and a language code that names the translation.

In the migration definition, the destination gets translations: true, the process pipeline sets langcode from the source language, and the default language row runs first, in its own migration, so the translation rows have a node to attach to. The pattern in YAML is short; the work is in the source query, and our guide to Migrate Plus and custom source plugins covers building it.

Two things people forget. Untranslatable fields are shared across languages, so decide which fields are which before the migration, not after, because changing a field’s translatability once it has data is a data-loss operation. And references — taxonomy, media, other nodes — must resolve to the shared entity, never to a language-specific copy, or you rebuild the Drupal 7 problem on Drupal 11.

The configuration that has to be right afterwards

Language negotiation. Path prefix (/fr/) is the choice that works with everything else — sitemaps, hreflang, caching, analytics. Domain per language is legitimate for genuinely separate markets and costs you a separate configuration for each. Browser detection and session negotiation are for redirecting a first visit, not for deciding what URL a page lives at.

Path aliases per language. Pathauto generates an alias per translation, and the pattern can be different per language. Check the aliases the migration produced; a translation without an alias is served at /fr/node/123, which is technically fine and looks like a broken site.

hreflang. With Content Translation enabled, core adds the alternate-language link tags on each translated node — but only for translations that exist. Anything that generates those tags from a pattern rather than from real translations will produce links to pages that 404. That is exactly what happened on our own site this month: a helper built Arabic alternates for every English post whether or not the Arabic existed, and a crawl found a hundred dead URLs. Generate from data, never from patterns.

Fallback. Decide what a visitor sees when a page has no translation in their language: the source language, or nothing. Drupal’s default shows the original; make it a decision rather than an accident, and never let a fallback page carry a canonical to itself.

Right-to-left. Arabic, Hebrew, Persian and Urdu need dir="rtl" on the document, which Drupal sets from the language’s direction setting, and a theme that respects it — mirrored layouts, mirrored icons, logical CSS properties. A theme that was never tested in RTL will look broken in ways the migration report cannot show you.

TRANSLATION AT VOLUMESites reporting the Translation Management Tool installedThe standard once translation becomes a process rather than a task. Latest week highlighted.11,66116 Aug12,28023 Aug12,65430 Aug12,8626 Sep12,78813 SepSource: drupal.org project usage statistics for tmgmt, weeks starting 16 Aug to 13 Sep 2026Free to reuse with attribution: gspikes.com

What else has to move

Menus. A menu link per language in the old site becomes one menu link with translated titles. Migrate the links first, then the translations, and check the menu in each language for links that only exist in one.

Taxonomy. Same rule as nodes — one term, translated — and the same trap: if the source had separate terms per language, they must merge on migration or every French node is tagged with French-only terms nobody else can see.

Views. A view rendering “content” without a language filter shows every translation as a separate row. Set the rendering language to the interface language and filter on translation language, and check every listing on the site in every language after launch.

Media and files. Usually shared, occasionally translated — a brochure PDF per language, an image with text in it. Mark the media field translatable only when the asset genuinely differs.

Sitemaps. One sitemap per language with hreflang inside it, which Simple XML Sitemap does out of the box and which nothing else does for you.

Editorial workflow

Once the content is across, someone has to keep it translated. For a site with a handful of languages and an in-house editor, core is enough: the Translate tab on every node shows what exists and what is outdated. For real volume — many languages, external translators, or machine translation with human review — the Translation Management Tool (TMGMT) is the standard, with job queues, translator integrations and a review step. It is the difference between translation being a task and being a process, and it is worth setting up before launch rather than after the first backlog.

BEFORE LAUNCHMultilingual migration checklistPrint it. Tick it. The migration is not done until every box is.Which D7 model was in use: node translation, entity translation, bothTranslation sets with a deleted source node found and resolvedField translatability decided before data landsReferences resolve to the shared entity, never a language copyLanguage negotiation set: path prefix unless there is a reasonPathauto alias per translation, checkedhreflang generated from real translations onlyFallback behaviour decided and canonical checkedRTL tested in the theme, not assumedTen fully-translated and ten single-language nodes walkedChecklist from Gspikes engagements; no external data.Free to reuse with attribution: gspikes.com

The test before launch

Pick ten nodes that exist in every language. For each, visit every translation, check the URL, the alias, the hreflang tags in the source, the menu, and the references on the page. Then pick ten nodes that exist in only one language and check what the other languages show. That is an hour, and it catches nearly everything on this page.

Gspikes builds multilingual Drupal for organisations in the Gulf and the US, and we run English and Arabic on our own platform — including the mistakes above, which is how we know them. If you are migrating a site with translations, send us the language list and we will tell you which model it is coming from and what it will take. More on our Drupal development page, the Drupal 7 to 11 checklist, and the Drupal SEO guide for the hreflang and sitemap side.

Field Notes

Get the next one by email

Research on Drupal, WordPress and search. Measured, not guessed. New pieces straight to your inbox.

Confirmed opt-in. One click to leave, any time.

Put senior Drupal engineers on it

Migrations, custom modules, integrations, and support with real SLAs — with a fixed-price quote from a senior engineer within 3 days.

Want this applied to your business?

A senior strategist reviews your situation and sends an honest plan within one business day.

Get a Free Quote