---
title: "Migrating from TranslatePress to PerfLocale - PerfLocale"
description: "Step-by-step guide to importing TranslatePress posts, strings, and slugs into PerfLocale. Admin UI and WP-CLI paths, safety guarantees, verification."
canonical: "https://perflocale.com/docs/migration/translatepress/"
source: "https://perflocale.com/docs/migration/translatepress/"
format: "markdown"
---
# 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** by walking TranslatePress’s string dictionary and substituting translated strings into the original post. 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](https://perflocale.com/docs/migration/) for the shared pre-flight steps (backup, install PerfLocale, add languages).

## What’s imported

-   **Post translations** - for every original post that has at least one translated string in TP’s dictionary, the importer reconstructs the translated content by substituting matched strings, then creates a new language-specific post in `wp_posts` and links it into a PerfLocale translation group. Every translation TranslatePress actually serves is included: the minimum status is `1` (machine-translated) by default, because TranslatePress displays everything with a status other than `0`. Raise the bar to reviewed-only with `add_filter( 'perflocale/migration/translatepress/min_status', fn() => 2 );`.
-   **String translations** - every qualifying entry in `wp_trp_dictionary_*` (one table per language) is inserted into `wp_perflocale_strings` with the appropriate translation row.
-   **Translated slugs** - TranslatePress’s `wp_trp_slug_translations` rows are imported into PerfLocale’s slug-translation store so `/fr/produits/`, `/de/produkte/`, etc. keep working.

## What’s _not_ imported

-   **Untranslated rows at status `0`.** TranslatePress uses status `0` for “not translated” and never serves those, so there is nothing to carry over. Everything at status `1` (machine-translated) and above _is_ imported by default — see the `min_status` filter above if you want reviewed-only content.
-   **The TranslatePress gettext dictionary.** TranslatePress stores plugin/theme gettext strings under a separate `gettext_` domain in its dictionaries. These are imported, but you should still run `wp perflocale strings scan` post-import to cover anything TranslatePress never rendered.
-   **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.

## 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. `10` for smaller chunks.
-   **Plenty of RAM, latency-sensitive**: raise to e.g. `200` for fewer transactions.

```php
add_filter( 'perflocale/migration/translatepress/batch_size', static fn(): int => 100 );
```

Clamped to `5–500`; default `50`. See [`perflocale/migration/translatepress/batch_size`](https://perflocale.com/docs/hooks/#perflocale-migration-translatepress-batch-size) in the hooks reference. The same pattern is available on the [WPML](https://perflocale.com/docs/migration/wpml/#memory-tuning) and [Polylang](https://perflocale.com/docs/migration/polylang/#memory-tuning) 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:

1.  Exact locale match first (PerfLocale language’s `locale` = TP’s code).
2.  Slug-prefix fallback (PerfLocale’s `slug` matches the prefix of TP’s locale, e.g. PerfLocale `fr` matches TP `fr_FR`).

## Run the import

### From the admin

1.  Go to **PerfLocale → Settings → Export & Import**.
2.  Scroll to “Import from TranslatePress”. If you don’t see it, the `wp_trp_original_strings` table isn’t present.
3.  Click **Import from TranslatePress**. Confirm the prompt.
4.  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 --yes
```

## Verification

1.  **New translated posts exist.** Go to _Posts → All Posts_, filter by language, and confirm each language shows the expected number of posts.
2.  **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.
3.  **Strings imported.** Open _PerfLocale → Strings_ and confirm translations for a handful of phrases you remember translating in TP.
4.  **Slugs work.** Visit a translated URL like `/fr/produits/` and confirm it resolves to the French version of the product archive.
5.  **hreflang is correct.** View page source on a translated URL - `<link rel="alternate" hreflang="…">` tags should list every language version with the correct URL.
6.  **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.

## 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. 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. To fill the gaps after import, run PerfLocale’s own machine translation against the affected posts:

```bash
wp perflocale translate --all --to=<lang> --skip-existing
```

The `--skip-existing` flag preserves the strings TranslatePress already supplied and only fills in the missing ones.

### 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 scan` to cover theme/plugin gettext strings TranslatePress never rendered (it only catches strings that appeared on a page it was rendering).
-   Configure [language fallbacks](https://perflocale.com/docs/language-fallbacks/).
-   See the [PerfLocale vs TranslatePress feature comparison](https://perflocale.com/compare/translatepress/) for the structural differences (per-language posts vs dynamic substitution).
