Migrating from TranslatePress
TranslatePress and PerfLocale use fundamentally different storage models. TranslatePress keeps string-level translations in its own tables (wp_trp_original_strings + wp_trp_dictionary_*) and reconstructs pages at render time by swapping matched strings. PerfLocale uses the standard WordPress model - one post per language, each with its own post_content. Migration therefore reconstructs full post content from TranslatePress’s string dictionary, replacing in the original post the parts TranslatePress translated on the page (see How post content is rebuilt). Then, when that’s done, it writes a brand-new translated post per language into wp_posts.
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 - for every original post whose title, content or excerpt TranslatePress translated, the importer rebuilds the translated content (see How post content is rebuilt), then creates a new language-specific post in
wp_postsand links it into a PerfLocale translation group. Every translation TranslatePress actually serves is included: the minimum status is1(machine-translated) by default, because TranslatePress displays everything with a status other than0. Raise the bar to reviewed-only withadd_filter( 'perflocale/migration/translatepress/min_status', fn() => 2 );. While the importer creates these posts, PerfLocale’s own save-time automation is paused for them: they are not labelled with the default language, Auto-Create Stubs makes no empty drafts for them, and auto-translate on publish does not run over the imported text. Each created translation keeps the original’s author, dates, discussion settings, password and slug, so the addresses TranslatePress served keep working; it gets the original’s meta fields, featured image and terms (in their translated versions where they exist); a child page goes under its parent’s translation; and a WooCommerce product gets its product type and variations as PerfLocale’s own translations do. The excerpt, including a WooCommerce short description, is taken from the dictionary too. - Term names - dictionary translations of category, tag and other term names and descriptions become the term’s translation in that language, created when it has none.
- Menus - each menu in a theme location is copied into each language with its translated labels, linked to the menu it copies, so every language shows its own menu.
- Site title and tagline - their dictionary translations become PerfLocale’s translations of them.
- String translations - every qualifying entry in
wp_trp_dictionary_*(one table per language) is inserted intowp_perflocale_stringswith the appropriate translation row. Plural forms are kept. - Translated slugs - TranslatePress’s
wp_trp_slug_translationsrows are imported into PerfLocale’s slug-translation store so/fr/produits/,/de/produkte/, etc. keep working. - Order languages - WooCommerce orders that TranslatePress tagged with a language (its
trp_languageorder meta) get that language in PerfLocale, so their customer emails keep it. An order that already has a PerfLocale language keeps it. Orders whose TranslatePress language has no PerfLocale language are counted in the result, and their emails use the default language.
What’s not imported
- Untranslated rows at status
0. TranslatePress uses status0for “not translated” and never serves those, so there is nothing to carry over. Everything at status1(machine-translated) and above is imported by default - see themin_statusfilter above if you want reviewed-only content. - Copied gettext translations. TranslatePress keeps plugin and theme gettext strings in its own tables. Its own translations of them (machine-translated or reviewed) are imported. Rows it copied from a similar string or from the language pack are not, so the language pack keeps supplying that text; the
perflocale/migration/translatepress/include_gettext_copiesfilter imports them too. Runwp perflocale strings scanafter the import to cover anything TranslatePress never rendered. - Text outside any post that matches nothing. Dictionary translations that belong to no post and match no term name, menu label, site title, tagline or excerpt (widget text, theme or plugin output) are counted per language and not imported.
- Languages TranslatePress had not published. Their translations are imported only when PerfLocale has a matching language that is switched off, so visitors do not see them until you switch it on. Without one, the language is skipped with a notice.
- Language-switcher shortcode, and anything else in What does not migrate.
- Translated post and page slugs from TranslatePress SEO Pack are not carried over: translated posts keep the original slug. Add redirects for any old translated-slug URLs that search engines have indexed.
- Meta translations (SEO title/description via TP’s SEO Pack addon) for unsupported SEO plugins. PerfLocale handles Yoast, RankMath, SEOPress, AIOSEO, Slim SEO, and The SEO Framework natively - confirm your SEO plugin is in that list before relying on meta translations.
How post content is rebuilt
The importer translates only what TranslatePress translated on the page, and replaces each of those parts as a whole:
- a whole text between tags, compared after TranslatePress’s own trimming, or a whole line of a text that spans several lines;
- the whole value of a
title,aria-labeloraltattribute, the placeholder of a form field, and the label of a submit, button or reset input; - text settings of blocks and quoted shortcode attribute values, but not links, class names, styles, colours or sizes;
- a TranslatePress translation block, which covers one HTML element; in classic-editor content, also a paragraph that WordPress adds when the post is shown.
Everything else keeps its original text: words inside a longer text, links and other attributes, the contents of scripts, styles and text areas, and comments. A post whose only matches were parts of longer texts gets no translation. If part of a post cannot be read, that part keeps its original text and the import reports the post (“Part of post … could not be parsed, so that part keeps its source-language text in the imported translations.”).
Pre-flight checks
Know your database size
The TranslatePress importer does more work per post than the WPML or Polylang importers, because it reconstructs content rather than just relinking existing posts. For a site with thousands of translated posts, the admin-UI path may hit PHP’s max_execution_time. The importer calls set_time_limit(300) to extend the budget to 5 minutes, but some hosts disable that function.
If your host caps PHP execution or your site has more than a few hundred translated posts, use the WP-CLI path instead - it runs under your shell’s time budget and can handle any site size. The default 300s time limit is also filterable:
// Raise the time cap to 15 minutes before running the importer.
add_filter( 'perflocale/migration/time_limit', fn() => 900 );Batch size
The importer processes posts in chunks of 50 by default. Between batches, the cache is flushed to keep memory usage flat even for very large sites. You can override the chunk size via filter - useful in two directions:
- Tight memory budget (shared hosting, low
memory_limit): lower the value to e.g.10for smaller chunks. - Plenty of RAM, latency-sensitive: raise to e.g.
200for fewer transactions.
add_filter( 'perflocale/migration/translatepress/batch_size', static fn(): int => 100 );Clamped to 5–500; default 50. See perflocale/migration/translatepress/batch_size in the hooks reference. The same pattern is available on the WPML and Polylang importers.
Match your language codes
TranslatePress stores locales in its options blob (trp_settings), e.g. fr_FR, de_DE. The importer maps those to PerfLocale languages via:
- Exact locale match first (PerfLocale language’s
locale= TP’s code). - Slug-prefix fallback (PerfLocale’s
slugmatches the prefix of TP’s locale, e.g. PerfLocalefrmatches TPfr_FR).
The first import is refused, and writes nothing, while TranslatePress’s default language is not PerfLocale’s default or a published TranslatePress language with translations has no match. wp perflocale migrate translatepress --dry-run shows the check. Once an import has run, the same findings are warnings.
Run the import
From the admin
- Go to PerfLocale → Settings → Export & Import.
- Scroll to “Import from TranslatePress”. If you don’t see it, the
wp_trp_original_stringstable isn’t present. - Click Import from TranslatePress. Confirm the prompt.
- The page reloads with import counts. Expect this to take longer than WPML/Polylang imports because of the content-reconstruction work per post.
From WP-CLI (recommended for larger sites)
# Sanity-check: confirm TranslatePress data is detected.
wp perflocale migrate translatepress --dry-run
# Run the real import (prompts for confirmation).
wp perflocale migrate translatepress
# Skip the prompt when scripting.
wp perflocale migrate translatepress --yesVerification
- New translated posts exist. Go to Posts → All Posts, filter by language, and confirm each language shows the expected number of posts.
- Reconstructed content looks right. Open a translated post in the editor and visit the frontend URL on a browser session where TranslatePress is still driving the render, then compare the two. Layout should be identical; wording should match.
- Strings imported. Open PerfLocale → Strings and confirm translations for a handful of phrases you remember translating in TP.
- Slugs work. Visit a translated URL like
/fr/produits/and confirm it resolves to the French version of the product archive. - hreflang is correct. View page source on a translated URL -
<link rel="alternate" hreflang="…">tags should list every language version with the correct URL. - Cache flushed. PerfLocale flushes its internal caches at the end of a TranslatePress import. If you use an external object cache or a page cache (WP Rocket, LiteSpeed, etc.), flush it manually.
Re-running the import
Re-running is safe. For each original post, the importer checks whether a PerfLocale group already exists for it and reuses the group when found, adding missing siblings. Already-linked translations at the same language are skipped. If new strings were added to TranslatePress after your first run, re-running picks them up. The import remembers how far it got through the posts; when the source language or the mapped languages changed since, it starts again from the first post, and translations that already exist are skipped.
Rollback
TranslatePress migration creates new posts in wp_posts (one per translated language per original post), so the cleanest undo is to restore from backup. If that’s not possible:
- Open Posts → All Posts, filter by non-default languages, and bulk-delete the newly-created translation posts. The import also created term translations and menu copies; delete those too. This leaves TranslatePress’s dictionary untouched, so reactivating TP restores its behaviour.
- Reactivate TranslatePress alongside PerfLocale is not recommended long-term - both will attempt to render language variants and you’ll get unpredictable behaviour. Keep only one active at a time.
Troubleshooting
Admin-UI import timed out
Expected on larger sites. Switch to WP-CLI, which doesn’t share PHP-FPM’s request timeout.
Some posts imported with partial translations
This usually means TranslatePress had no translation at all (status 0) for some of the original post’s strings - or that you raised perflocale/migration/translatepress/min_status to 2 and the source strings were only machine-translated. A translated string that is only part of a longer text also stays in the original language (see How post content is rebuilt). To translate the posts the import left without a translation, run PerfLocale’s own machine translation:
wp perflocale translate --all --to=<lang> --skip-existingThis translates only the posts that have no translation in that language yet: --skip-existing leaves every post that already has one alone, including the translations the import created. To finish a translation that was imported with some text still in the original language, open it in the block editor and translate the remaining blocks, for example with Fill in from source on each block.
Old TranslatePress URLs 404 after the import
Flush rewrite rules: wp rewrite flush. TranslatePress’s URL conversion is runtime-only, so after you deactivate it, PerfLocale needs its rewrite rules re-registered.
Next steps
- Run
wp perflocale strings scanto cover theme/plugin gettext strings TranslatePress never rendered (it only catches strings that appeared on a page it was rendering). - Configure language fallbacks.
- See the PerfLocale vs TranslatePress feature comparison for the structural differences (per-language posts vs dynamic substitution).