Migrating from WPML

PerfLocale ships a bundled importer for WPML that reads the wp_icl_translations and wp_icl_strings tables and reconstructs your translation groups as native PerfLocale rows. Your WPML data is read-only during the import - nothing is modified or deleted from WPML’s tables, so you can re-run, abandon, or verify the migration at your own pace before switching over.

If you haven’t already, start with the migration overview for the shared pre-flight steps (backup, install PerfLocale, add languages).

What’s imported

  • Post translations - every entry in wp_icl_translations with element_type LIKE 'post_%'. This covers posts, pages, attachments, and all custom post types that WPML was tracking. Translation groups are reconstructed from the WPML trid (translation group ID).
  • Term translations - every entry with element_type LIKE 'tax_%': categories, tags, custom taxonomies.
  • String translations - rows from wp_icl_strings joined with wp_icl_string_translations where the translation status is 10 (WPML’s “translation complete” status). Strings with status 0 (not translated) or 20 (needs update) are skipped.

What’s not imported

  • WPML Media Translation uploads (per-language media files). PerfLocale handles multilingual media differently - via the same post-translation-group mechanism applied to attachments - so re-translating media is a post-migration task in the PerfLocale admin.
  • WPML Menu Sync relationships that rely on WPML-internal IDs. Menus themselves are translated as terms (via the nav_menu_item post type) when WPML tracks them that way, but the sync-on-save logic is a WPML-specific feature.
  • WPML Translation Management workflow state (who was assigned what, status history). PerfLocale has its own workflow addon; assignments don’t carry across.
  • Strings at status 20 (“needs update”). Treat them as not-yet-translated in PerfLocale and revisit in the string-translation admin.

Pre-flight checks

In addition to the shared steps in the overview, there are two WPML-specific things to get right:

Match your language codes

The importer maps WPML’s language_code (from wp_icl_translations) to a PerfLocale language ID using a two-step lookup:

  1. Exact slug match first. If WPML uses fr, add a PerfLocale language with slug fr.
  2. If no slug match, locale prefix match: PerfLocale’s locale (e.g. fr_FR) starts-with WPML’s code (e.g. fr). Useful when WPML uses a short code and PerfLocale tracks a full locale.

If your WPML site used a non-standard code (e.g. en-US with a hyphen instead of an underscore), add a PerfLocale language with the matching slug exactly.

Keep WPML active until you verify

Don’t deactivate WPML before running the importer. The importer reads WPML’s tables directly, but leaving WPML active also means you can open any post in its original editor to cross-check against PerfLocale’s translated post as you spot-check.

Run the import

Upgrade note for 1.0.16: earlier builds had a fatal in the string-migration path (the importer attempted to wrap translated strings in a post-type translation group, which was rejected by the string-group guard). If you attempted a WPML migration on 1.0.15 or earlier and saw a fatal during the string phase, upgrade to 1.0.16+ and re-run - the importer is idempotent, so previously-imported posts and terms stay intact and the missing string translations fill in.

From the admin

  1. Go to PerfLocale → Settings → Export & Import.
  2. Scroll to the “Import from WPML” section. If you don’t see it, the wp_icl_translations table isn’t detected - WPML may never have been installed on this site, or the DB prefix is non-standard.
  3. Click Import from WPML. Confirm the prompt.
  4. Wait. On sites with a few hundred multilingual posts the import typically completes in under 30 seconds. The page reloads with a banner showing counts and any per-language warnings.

From WP-CLI

# Sanity-check: confirm WPML data is detected.
wp perflocale migrate wpml --dry-run

# Run the real import (prompts for confirmation).
wp perflocale migrate wpml

# Skip the prompt when scripting.
wp perflocale migrate wpml --yes

For sites with thousands of multilingual posts or a strict PHP max_execution_time cap, prefer the CLI path - it runs under your shell’s time budget rather than PHP-FPM’s request timeout.

Verification

Work through these in order before deactivating WPML:

  1. Spot-check a post. Open any multilingual post in Posts → All Posts. The PerfLocale sidebar should list every language that existed in WPML, each linking to the correct translated post.
  2. Check terms. Go to Posts → Categories (or any translated taxonomy) and confirm translated terms are linked in the PerfLocale translation column.
  3. Open a string. Navigate to PerfLocale → Strings and filter for a phrase you remember translating in WPML. Confirm the translation is present for the expected language.
  4. Visit a translated URL. Browse to /fr/your-post-slug/ (adjust for your prefix convention). Confirm the page renders the French content, and that the language switcher highlights French.
  5. View source. Confirm hreflang tags in the page head point at the right sibling URLs.

Re-running the import

The WPML importer is idempotent: re-running it after a partial or failed run completes the gaps rather than creating duplicates. Internally, every trid is checked against existing PerfLocale translation groups - if a group already contains any of the posts from that trid, the importer reuses it and links the remaining siblings. If a sibling is already linked to the same group with the same language, it’s skipped silently (no cache churn).

This means if your first run warned about “No PerfLocale language match for WPML code <x>”, you can add the missing language and re-run - the previously-imported content stays intact and the new language’s content fills in.

Rollback

There’s no “undo migration” button. Two paths if you need to back out:

  • Restore from backup - the cleanest undo, since it rewinds any new wp_perflocale_* rows that were written.
  • Manual cleanup while WPML is still active - if you only imported a handful of posts, you can open each newly-created translation post and delete it from Posts → All Posts. The original WPML-managed posts are untouched.

In either case, leave WPML activated until you’re certain you won’t need it - WPML’s tables are not modified by the importer, so reactivating it restores the pre-migration experience immediately.

Troubleshooting

“No matching languages found between WPML and PerfLocale”

Every language in wp_icl_translations.language_code has to match either a PerfLocale language slug or the prefix of a locale. Check the output for specific “No PerfLocale language match for WPML code <x>” warnings, then add the missing languages in PerfLocale → Languages and re-run.

The import timed out

Switch to WP-CLI (see above). The admin-UI path runs inside a regular PHP request and is subject to max_execution_time / PHP-FPM’s request timeout; WP-CLI runs under your shell’s time budget and can handle sites of any size.

I re-ran and now I have duplicate posts

You shouldn’t - the importer is idempotent. If you’re seeing duplicates, they were most likely created by WPML itself (WPML sometimes creates “duplicate” placeholder posts when a translation is queued). Check whether the duplicates existed pre-import via Posts → All Posts filtered by language. If they did, they’re a WPML artefact, not a migration bug.

Next steps