Migration
PerfLocale ships with importers for the three most common multilingual plugins. Each one reads the source plugin’s data and writes equivalent rows into PerfLocale’s own tables - your original plugin’s data is left untouched, so you can re-run or abandon a migration at any point without losing work.
Supported sources
- WPML - posts, terms, strings, menus, WooCommerce order languages.
- Polylang - posts, terms, menus, the site title and tagline, date and time formats.
- TranslatePress - posts (reconstructed from the string dictionary), term names, menus, strings, the site title and tagline, translated slugs, WooCommerce order languages.
Imports from qTranslate-X, Multisite Language Switcher, GTranslate, or other multilingual plugins are not currently bundled. If you’re on one of those, you can export via WPML’s importer first (many of them offer a WPML-compatible bridge) then run the PerfLocale WPML importer.
Before you start
- Back up your database. The importer doesn’t modify the source plugin’s data, but it does write new rows into PerfLocale’s own tables, and a backup is always the right baseline before a structural change.
- Deactivate the source plugin first. PerfLocale refuses to start while WPML, Polylang or TranslatePress is active - you will see the admin notice “PerfLocale: Cannot run while WPML is active. Please deactivate one of the two multilingual plugins to avoid conflicts. Your WPML translations stay in the database: after deactivating it, import them from Settings → Export & Import, or with
wp perflocale migrate.” (on multisite the same notice appears in Network Admin when the other plugin was network-activated) and a matching critical result in Tools → Site Health, and neither the importer screen nor thewp perflocale migratecommand will exist until you do. Deactivating is safe: it leaves every row in the database untouched, and the importer reads those tables directly rather than calling the other plugin. Deactivate WPML’s add-ons too: while WooCommerce Multilingual or WPML String Translation stays active, PerfLocale’s screens, the Plugins screen and Tools → Site Health show a warning with a Deactivate button for each (shown to users who can deactivate plugins). - Install and activate PerfLocale. The importer reads the source plugin’s database tables, not its runtime, so everything it needs is still there.
- Match the source plugin’s default language and URL layout. A new PerfLocale install starts with English as its default language. The importers read the source plugin’s settings to check them, but do not copy them. If your site’s main language is another one, add it in PerfLocale → Languages, edit it and tick Default language. Then, under PerfLocale → Settings → URL & Routing, choose the URL Mode and the Default Language URL option that give the URLs your site has now: for example, languages in directories such as
/fr/with no directory for the main language is Subdirectory with Hide the language prefix for the default language ticked. Do this before you import. The default language decides which URLs carry no language prefix, and the WPML and Polylang importers give content that exists only in the default language no language record of its own (PerfLocale reads such content as default-language), so changing the default afterwards moves that content to the new default. The first import from a plugin is refused, and writes nothing, when that plugin’s default language is not PerfLocale’s default language; its message says which language to make the default. For WPML, a URL format that differs from WPML’s gives a warning. - Add every language you intend to migrate in PerfLocale → Languages before running the importer. Language matching is done by
slugfirst (e.g.fron both sides) then falls back to alocaleprefix match (e.g. PerfLocale’sfr_FRagainst WPML’sfr). The first import is refused while a language that has content in the source has no PerfLocale match; once an import from that plugin has run, a later import reports such a language and skips its content. The importer doesn’t create languages for you, by design.wp perflocale migrate <source> --dry-runshows the check before you import. - Decide whether to run the admin UI or WP-CLI. For sites with more than a few hundred posts, use WP-CLI: it avoids PHP max-execution-time limits that the admin-request path can run into on shared hosting.
How it works
Every importer follows the same shape:
- Detection.
can_import()checks that the source plugin’s data exists (e.g. WPML’sicl_translationstable, Polylang’slanguagetaxonomy, TranslatePress’strp_original_stringstable). If not found, the import is refused with a clear error. - Language check. The source’s default language and every language code in the source are matched to PerfLocale languages. The first import stops here when the default languages differ or a language with content has no match (see Safety model).
- Data migration. Posts and terms are grouped into PerfLocale translation groups. Strings get hashed and inserted into
wp_perflocale_strings, then their translations are linked per language. - Batched fetch. All three importers chunk their SELECTs against the source plugin’s tables so peak memory stays bounded regardless of site size. Internal benchmark: 5,000 translation groups × 3 languages (15,000 link writes) completes under 32 MB peak delta. Per-plugin tuning knobs: WPML, Polylang, TranslatePress. The effective batch size for each is exposed in Tools → Site Health → Info → PerfLocale under “Migration importer batch sizes”, so support threads can see the live value at a glance.
- Idempotent re-runs. If a translation group already exists for one of the items being imported, the importer reuses that group and adds any missing siblings. Re-running after a partial failure fills the gaps instead of creating duplicates.
- One import at a time. Every import, from the admin, from a background job or from WP-CLI, and from any of the three plugins, takes one lock per site. A second import started meanwhile is refused with “Another import is already running. Try again when it has finished.” An import that was killed releases the lock 15 minutes after its last batch. The same holds for a background import job: 15 minutes after its worker stopped, starting the import again marks the stopped job failed and queues a new one. A running import that finds its lock gone, or taken over by another import, stops after the batch it is working on, with “The import stopped because another import took over its lock, or the lock was removed, while it was running. What it imported so far is kept. Run the import again when no other import is running; what is already imported is skipped.” WP-CLI exits with that error, the admin shows it, and a background job fails with it and is retried later.
- Caches. When an import ends, including one stopped by a cancel or a pause from the Jobs page, PerfLocale flushes its caches and rebuilds the translation files it serves in files mode.
Safety model
What the importer guarantees:
- Source data is read-only. The importer never writes to WPML’s, Polylang’s, or TranslatePress’s tables. The WPML and Polylang importers also create no posts or terms: they link the posts and terms already on your site. Besides PerfLocale’s own tables, files and settings, they write only what PerfLocale reads the language from: the language of menus, and for WPML the language of WooCommerce orders and category or tag assignments moved to the term’s translation in the post’s language. The TranslatePress importer is different, because TranslatePress keeps no separate posts: it creates one post per translation, and it also writes the language of WooCommerce orders (see its page).
- Languages are matched first. Every source language is matched to a PerfLocale language before any data is written. The first import from a plugin is refused, and writes nothing, when the plugin’s default language is not PerfLocale’s default or when a language with content has no PerfLocale match. After an import from that plugin has run, the same findings are warnings, so a catch-up import is never refused: an unmatched language is reported and skipped, and you can add it and re-run.
- Dry-run via WP-CLI.
wp perflocale migrate <source> --dry-runprints one row per source language (code, locale, items, the PerfLocale language it maps to, status), then any problems, without writing anything or taking the lock. It exits with 0 when the import would run and 1 when it would be refused. When the import would run but skip something, for example content in a language with no PerfLocale language, it ends with a warning naming what it would skip instead ofSuccess:. - Skipped rows are reported. What an import leaves out is counted in the result with the reason: for WPML, for example, an item that was deleted, an item listed in two translation sets, or a translation set that would unlink another item.
- Bulk tools ask first. While translations from WPML, Polylang or TranslatePress are on the site but not imported, Assign Default Language and Generate Missing Translations (Settings → Translation), Create Taxonomy Translations and the WooCommerce Create Page Translations button warn that running them now gives those translations the default language, and run only after Continue anyway?. While an import is running they are refused.
- Imported strings are kept. The string clean-up removes strings a scan has not seen for 90 days. The text domains an import filled are kept out of it, except domains the string scanner reads from code, which follow the scan.
- Disaster-recovery idempotent. Each
translation_groupsrow is created in a single SQL transaction together with a(migration_type, source_key) → group_idmapping inperflocale_migration_source_map. If your host restores a backup that wiped the groups table, re-running the import looks up the mapping first and reuses the existing group_id rather than allocating a duplicate. TranslatePress gets the same protection via aget_translation_in_language()pre-check on the destination side. See the reliability page for the full mechanism. - Resumable. Re-running the importer after a partial run completes the gaps; already-linked items are skipped.
What the importer doesn’t guarantee (and you should know):
- No automatic rollback. There is no “undo migration” button. To go back, restore the database backup you took in step 1. After a WPML or Polylang import you can also deactivate PerfLocale and reactivate the source plugin, since the import changed none of its data. Do not delete translated posts to undo a WPML or Polylang import: they are your existing translations, not copies made by the importer. The TranslatePress page explains how to remove the posts that importer created.
- Per-batch or per-row, not per-import, transactions. The WPML and TranslatePress importers write each batch in one transaction, so a batch is never left half imported: when a database write in it fails, or the database ends its transaction (for example after a deadlock), the batch is rolled back and written again, or reported as failed. The Polylang importer writes each
create_group()+ source-map insert pair in one transaction. The whole import is a sequence of those: if your host kills the PHP process halfway (timeout, OOM), the batches written before stay. Re-running is safe and fills the gaps (source-map lookups + link UNIQUE constraints prevent duplicates), but it’s not all-or-nothing at the migration level. - No per-kind preview.
--dry-runshows the items per source language and whether the import would run; it doesn’t report “this will import 234 posts, 89 terms, 1 412 strings.” You’ll see those totals after the run finishes.
Force-restart after a deliberate DB restore
If you intentionally restore a backup to a known-good pre-import state and want the next migration run to allocate fresh translation_groups rather than reuse stale mappings from the prior run, pass --force-restart to clear the source-map for that importer before the new run starts:
wp perflocale migrate wpml --force-restart --yes
wp perflocale migrate polylang --force-restart --yes
wp perflocale migrate translatepress --force-restart --yesThe flag is a no-op when the source-map is already empty. The CLI logs the number of rows cleared before the import begins.
Caution: --force-restart on a database whose translation_groups rows are still pointing at the source plugin’s data will orphan those groups. Run it only after you’ve restored a clean pre-migration backup. The default (omit the flag) is the safer choice for almost every scenario.
Running the import
From the admin
Go to PerfLocale → Settings → Export & Import. The page shows a status badge for each source plugin whose data is detected on your site:
- “Import from WPML” - visible when
wp_icl_translationsexists. - “Import from Polylang” - visible when the
languagetaxonomy is present. - “Import from TranslatePress” - visible when
wp_trp_original_stringsexists.
Click the button, confirm the prompt, and wait for the page to reload with an import summary.
Large migrations don't time out: PerfLocale's background-jobs system automatically queues big migrations (the threshold is configurable) and redirects you to PerfLocale → Jobs where progress is visible live: the row of a running import names its current stage (posts, terms, strings, menus, order languages and so on) and how many of that stage’s items are done, for example “Posts: 120 of 500”. Small migrations still finish inline so you see the summary immediately. For WPML, the size counts string translations as well as posts and terms, so a site with a large string table imports in the background too. A migration that one web request cannot finish on your host is refused before it starts, with the command to run instead, for example wp perflocale migrate wpml (see Background Jobs).
From WP-CLI
# Confirm the source is detected (no data written).
wp perflocale migrate wpml --dry-run
# Run the real import (prompts for confirmation).
wp perflocale migrate wpml
# Skip the confirmation prompt (for scripting / CI).
wp perflocale migrate wpml --yesReplace wpml with polylang or translatepress as appropriate. Output:
$ wp perflocale migrate wpml
Migrating from wpml...
Success: Migration complete. Imported: 234 posts, 89 terms, 1412 strings.After the import
- Spot-check translations. Open a multilingual post in the standard editor and use the PerfLocale Translations panel to confirm all the expected language versions are linked.
- Verify URLs resolve. Visit
/fr/your-post/,/de/your-post/, etc. and confirm the prefixed language URLs return the correct translated posts. - Check the hreflang tags. View the page source of any translated post and confirm
<link rel="alternate" hreflang="…">tags point at the correct sibling URLs. - Flush caches. If you use an object cache or page cache, flush it - PerfLocale’s internal caches are invalidated automatically, but external caches aren’t.
- Keep the source plugin deactivated. PerfLocale stops as soon as the source plugin is active again. Keep a database backup for at least a week in case you want to go back. The source plugin’s data can stay in the database indefinitely - PerfLocale ignores it.
What does not migrate
These parts of the old plugin have no counterpart in PerfLocale, so the importers do not carry them over:
- Translated URL bases. WPML and some other plugins can translate the base of a post type or taxonomy, such as
/fr/produit/for/product/. PerfLocale uses the same base in every language, so addresses with a translated base do not resolve after the switch. Redirect them with a redirect plugin or server rules if they carry search traffic. - The old plugin’s shortcodes and PHP functions. For example WPML’s
[wpml_language_selector_widget]andwpml_current_language, Polylang’s language-switcher menu item, and TranslatePress’s language-switcher shortcode. Replace them with PerfLocale’s language switcher and helper functions. - “Display as translated” per post type. WPML can list a default-language item in another language when it has no translation there. PerfLocale has no per-type setting for this; see Missing Translation Action and Language Fallbacks for how a missing translation is handled.
Per-source details
Each source plugin has its own quirks - what’s imported, what isn’t, language-code edge cases, and specific verification steps:
For the CLI command reference, see WP-CLI: wp perflocale migrate.