Skip to main content

Migrate from Polylang

Polylang keeps one post for each language. easyi18n keeps one post and shows its translation under the language URL. The two cannot run together, so a site moves from one to the other in a single step: a WP-CLI command that keeps every translated address working and retires the extra posts.

What the migration moves​

For each post that has translations in Polylang, the command:

  • gives the post in the default language the slug of each published translation, so /es/sobre-nosotros/ keeps answering and now shows that post;
  • moves the translated posts to the trash;
  • keeps a page tree in place, so a translated child page keeps its address under its translated parent;
  • carries the menu Polylang assigned to each theme location in each language, and serves it under that language's URLs;
  • redirects the other address forms Polylang could use, such as /language/es/…, to the address easyi18n serves, with a 301.

It does not move the translated text. After the migration a translated address shows the post in its source language until you translate and publish it in easyi18n. During that time the page is marked noindex and is left out of the alternate links, as Pages without a translation describes. The translations Polylang held stay in the trashed posts, where you can read or copy them until WordPress empties the trash.

Translated categories and tags are counted and not migrated. Their names are translated in easyi18n as strings. The strings you translated in Polylang's own screen are not migrated either.

Before you start​

The command refuses to run, and writes nothing, unless all of this holds. It names the first condition that fails.

RefusalWhat it needs
polylang-activePolylang is deactivated. Do not delete it: its data is what the command reads
polylang-optionPolylang's settings are still in the database
default-langPolylang's default language is the base language of the easyi18n project
langsEvery language that has a published translation is a target language of the project
url-modePretty permalinks, and Polylang set the language from the content or from a directory in the URL. Subdomains and separate domains are not supported
url-mode-changedPolylang's URL settings are the ones the first run recorded
content-enabledTranslate content is off in the plugin. Switch it on after the migration
trash-disabledThe trash is on. With EMPTY_TRASH_DAYS set to 0, WordPress deletes instead of trashing
schemaThe plugin's database tables are up to date. Load any page of the site and try again
lockedNo import or other migration is running. Try again in a few minutes

So the order is: connect the project and verify that its languages match Polylang's, take a backup of the database, deactivate Polylang, then run the command.

Run it​

The command reports by default and writes only with --apply:

wp easyi18n polylang-migrate
wp easyi18n polylang-migrate --apply

Both print a report. First a census of what the site holds:

url mode: default_lang=en force_lang=1 hide_default=1 rewrite=1 media_support=0 pretty_permalinks=true (accepted)
language en: 42 published (page 12, post 30); 3 in no group (post 3)
language es: 40 published (page 12, post 28); 0 in no group
translated term groups: 9 (not migrated)
menu locations carried: primary (en, es)

Then one line for each group of translations it leaves alone, with the reason, and a total. The count of menu items is printed by --apply only:

skipped group 118: slug-taken
40 groups, 39 migrated, 4 menu items repointed.

Read the dry run before applying. A skipped group is not touched: its posts stay as they are. Fix the cause and run the command again; it picks up where it left off and does not redo a migrated group.

ReasonMeaning
malformedPolylang's record of the group cannot be read
post-typeA post of the group is of a type easyi18n does not translate
base-not-publishedThe post in the default language is not published
base-passwordThe post in the default language has a password
tree-mismatch, child-language, sibling-nameThe page tree of a translation does not line up with the tree of the default language
trashed-unmarkedA translation was already in the trash before the migration. Restore it or delete it permanently
slug-takenThe translated slug is reserved, too long, or already used by another post
assign-refused, trash-failedA write failed while applying. With trash-failed the address already works and the translated post is still published

The dry run cannot foresee a failed write, so it can announce more migrated groups than --apply then reports.

After the migration​

  1. Open a few translated addresses. Each shows its post, in the source language for now.
  2. Switch on Translate content, translate and publish. The slugs the migration carried stay as they are.
  3. Replace Polylang's language switcher with the plugin's.
  4. Keep Polylang installed and deactivated until you are sure. The redirects and the menus do not depend on it: the migration copied what they need.

Do not change Polylang's URL settings after the first --apply. The redirects follow the settings recorded at that moment.

What cannot be undone​

WordPress empties the trash after EMPTY_TRASH_DAYS, 30 days unless the site changed it. That deletes each retired post for good, with its comments and with the menu items that pointed at it. Copy what you need out of the trash before then.

Until then the migration can be reversed by hand, because nothing of Polylang's is deleted. It is a database task: the slugs the migration gave to the default-language posts have to be removed before a retired post is restored, and a post restored from the Trash screen comes back as a draft.