---
title: "Troubleshooting - Diagnose and Fix Problems - PerfLocale"
description: "Match the admin notice, Site Health card or WP-CLI error in front of you to its cause and its fix, quoted from PerfLocale's own wording."
canonical: "https://perflocale.com/docs/troubleshooting/"
source: "https://perflocale.com/docs/troubleshooting/"
format: "markdown"
---
# Troubleshooting

Nearly every PerfLocale failure announces itself in one of three places: a red admin notice, a card under **Tools → Site Health**, or a WP-CLI error. This page is organised by _what you see_, and quotes the plugin’s own wording verbatim so you can match the exact message in front of you.

## Start with Site Health

PerfLocale registers 23 direct tests on **Tools → Site Health → Status**, each badged **Translation**. Being _direct_ tests, they run synchronously the moment you open the Status tab. Two of them reach outside PHP — the hreflang card fetches the homepage over HTTP, and the MT reachability card does a DNS lookup — so on a host with blocked egress those are the two that make the tab wait. Together they cover the configuration and runtime states that have to be right for translation to work at all:

| Test name | Answers |
| --- | --- |
| PerfLocale database tables | Do the plugin’s tables exist? |
| PerfLocale database schema version | Did every migration land? |
| PerfLocale PHP extensions | Are mbstring, dom, libxml, simplexml, json loaded? |
| PerfLocale default language | Can URLs be resolved at all? |
| PerfLocale string translation scale | Is database string mode carrying too large a per-request map? |
| PerfLocale rewrite rules | Do language-prefixed URLs route? |
| PerfLocale multisite language hosts | Do generated hostnames exist in the network? |
| PerfLocale plugin conflicts | Is another multilingual plugin active? |
| PerfLocale addon health | Has an addon been auto-disabled? |
| PerfLocale addon schema | Does an addon have a pending table migration? |
| PerfLocale MT usage | How close is the monthly character limit? |
| PerfLocale MT provider reachability | Does the provider host resolve in DNS? |
| PerfLocale exchange-rate freshness | Are WooCommerce rates updating? |
| PerfLocale translation files | Are the compiled .l10n.php files present and current? |
| PerfLocale translation files directory | Can PHP write into uploads? |
| PerfLocale object cache | Is anything cached between requests? |
| PerfLocale hreflang output | Does the homepage actually render hreflang links? |
| PerfLocale background-jobs health | Will queued jobs ever run? |
| PerfLocale circuit breakers | Is an external service being refused? |
| PerfLocale eager-link-map state | Is the autoloaded lookup map within its size envelope? |
| PerfLocale background cron schedule | Are the four recurring events scheduled? |
| PerfLocale stuck translations | Any rows abandoned mid-translation? |
| PerfLocale orphan translation rows | Any links pointing at deleted posts or terms? |

The **Info** tab carries a **PerfLocale** section described as `Translation, routing, and integration state for the PerfLocale multilingual plugin. Paste this section when asking for support.` The machine-translation API keys are the only secrets it touches, and it reports each one as `configured` or `not configured` — never the value — so the section is safe to paste into a public support thread.

**Results can lag a fix.** The shared row counts, the translation-file counts and the hreflang probe are cached for five minutes; the three heavy queries — database string-mode scale, stuck translations and orphan rows — are cached for an hour. After a repair, a card may keep showing the old verdict until its window expires. The stuck-translations and orphan-rows descriptions end with `(Checked %3$s ago; the count refreshes hourly.)` so you can see how old the number is. Saving a PerfLocale setting or editing a string clears the five-minute caches, but not the hourly ones.

## “Cannot run while WPML is active” — nothing happens after activating PerfLocale

You activate PerfLocale, and the only thing that appears is a red notice:

```
PerfLocale: Cannot run while WPML is active. Please deactivate one of the two
multilingual plugins to avoid conflicts.
```

There is no **PerfLocale** menu, no Site Health cards, and `wp perflocale` is an unrecognised command.

**What it means.** This is not a warning you can dismiss and work around. `Bootstrap::init()` checks for a conflicting multilingual plugin as its very first act and, on a match, registers that one admin notice and returns. Nothing else is registered — not the service container, not the router, not the admin pages, not the WP-CLI commands, not the Site Health tests. PerfLocale and WPML, Polylang or TranslatePress would fight over URL routing, query filtering and post meta, so the plugin refuses to boot rather than corrupt your site.

**Why it triggers.** Detection runs twice over, so the two main files can load in either order. First it looks for the competitor’s version constant — `ICL_SITEPRESS_VERSION` (WPML), `POLYLANG_VERSION` (Polylang), `TRP_PLUGIN_VERSION` (TranslatePress). If PerfLocale’s main file happened to load first, the constant isn’t defined yet, so it falls back to reading `active_plugins` for `sitepress-multilingual-cms/sitepress.php`, `polylang/polylang.php`, `polylang-pro/polylang.php` or `translatepress-multilingual/index.php`. On multisite it also merges in `active_sitewide_plugins`, so a network-activated competitor is caught on every subsite.

**The fix.** Deactivate the other plugin at **Plugins → Installed Plugins** (and on multisite check **Network Admin → Plugins** too). Deactivating is safe — it leaves every row in the database untouched, and PerfLocale’s importers read those tables directly rather than calling the other plugin’s runtime. Then bring your data across:

```bash
wp perflocale migrate wpml --dry-run
wp perflocale migrate wpml
```

Full walkthroughs live in the [migration guide](https://perflocale.com/docs/migration/). The Site Health card `PerfLocale plugin conflicts` exists for the residual case where a competitor’s constant only becomes defined _after_ PerfLocale’s own file has already run — a copy in a renamed folder that the `active_plugins` fallback does not match, for instance. It reports the same verdict as critical: `PerfLocale cannot run while %s is active - they would fight over URL routing, query filtering, and post meta. Deactivate one of the two.`

## My `/de/` URLs return 404

Site Health shows:

```
Language-prefixed rewrite rules are missing

Visit Settings → Permalinks and click Save to force WordPress to flush rewrite
rules. Without this, translated URLs will return 404.
```

**What it means.** In subdirectory URL mode, PerfLocale adds language-prefixed rules to WordPress’ rewrite table. The test reads the stored `rewrite_rules` option and looks for a rule containing the plugin’s language query variable. If none is found, WordPress has no idea what `/de/` means and hands back a 404.

**Why it happens.** The rules are written when PerfLocale flushes, but a rewrite table can be clobbered by another plugin, a database import, or a migration that copied `wp_options` from a site where the rules never existed.

**The fix.** Open **Settings → Permalinks** and press **Save Changes**. You don’t need to change anything — saving is what makes WordPress rebuild the table. The Site Health card links straight there with a **Go to Permalinks** button. On a healthy site the same card reads `Language-prefixed rewrite rules are active`.

**If you use Plain permalinks.** Two different verdicts are possible, and they are not the same problem:

-   `Plain permalinks work, but pretty permalinks are recommended` — the language prefix is parsed straight from the request URI, so nothing is broken as long as the web server routes every path to WordPress (typical for nginx, and for Apache with the standard WordPress `.htaccess` block).
-   `Plain permalinks on Apache may break language URLs` — raised when the server is Apache and WordPress reports that URL rewriting is unavailable. As the message puts it: `Language-prefixed URLs such as /de/ can return a server-level 404 before WordPress loads.` Enable pretty permalinks, or restore the standard WordPress rewrite block in `.htaccess`.

Other URL modes are unaffected and the test says so explicitly: `URL mode doesn't require rewrite rules` — `Rewrite rules are only used in subdirectory mode.` If you are on query or domain mode and chasing a 404, this is not your cause.

## Translated links show WordPress’ “site does not exist” screen (multisite)

On a network in subdomain or per-language-domain mode, Site Health raises `Language hostnames are not registered in this network`, and the **URL & Routing** settings tab shows the same thing at save time: `subdomain URL mode generates hostnames this network cannot resolve.`

**What it means.** No plugin can fix this, because the failure happens before plugins load. As the plugin’s own message explains: `On multisite, WordPress matches the requested hostname against the network's site list before any plugin loads, and none of these is registered there - a visitor following a translated link gets WordPress's "site does not exist" screen instead of a translation.`

**The fix**, in the message’s words: `Either add each hostname as a site in the network (and point its DNS or server alias at this install), or switch PerfLocale to subdirectory or query URL mode, which keep this site's hostname.` The card links to **Network Admin → Sites → Add New** and to the URL mode setting.

The hostnames are matched against the network’s own site list — one site query each, no DNS and no HTTP — and only a bounded number are looked at per run. When the cap bites, the card is deliberately downgraded from a pass to a recommendation, headed `Checked %1$d of %2$d language hostnames`, warning that `The remaining hostnames were NOT checked`. A capped run is not a promise that every hostname is registered.

## No hreflang tags in my page source

Site Health reports one of:

```
No hreflang alternate links detected on the homepage

Hreflang output is enabled in settings but the homepage did not render any
`<link rel="alternate" hreflang>` tags. Check theme or cache rules that might
strip head elements, and make sure the homepage is translatable.
```

or the thinner-than-expected variant: `The homepage rendered %1$d hreflang link(s) but %2$d languages are active. Translations for missing languages may not be linked yet, or a caching layer is serving a pre-translation copy.`

**How the check works.** It is a real probe, not a settings read: PerfLocale fetches its own homepage over HTTP with the header `X-PerfLocale-Health-Check: 1`, then counts the alternate links in _both_ placements — the `<link>` tags in the HTML and the entries in the `Link` response header. That means it catches page caches and theme filters that strip head elements — things a settings inspection would miss.

**Work through these in order.**

1.  Confirm the feature is on. If it is off, the card says so rather than failing: `Hreflang output is disabled in settings`. Enable **Output hreflang tags for multilingual pages.** under **PerfLocale → Settings → SEO**.
2.  Check you have more than one active language — with fewer than two the test is skipped: `Hreflang check skipped (fewer than 2 active languages)`.
3.  Purge your page cache. A full-page cache populated before translations existed will keep serving the pre-translation HTML to the probe and to search engines alike.
4.  Look at **Hreflang Placement** on the same settings tab. If it is set to **HTTP Header**, the Site Health probe still finds the links, but there is nothing in the HTML for a “view source” check to find — inspect the response headers instead, or switch to **Both**.
5.  If the tags are missing only for some languages, tick **List every active language in hreflang - even ones where the post has no translation yet.**

A separate result, `Homepage could not be fetched for the hreflang sanity check`, means the probe itself failed: `This is usually a loopback-block or a self-signed-certificate issue.` That is a hosting condition, not an hreflang bug — your visitors may be seeing the tags perfectly well.

## String translations are saved but the site still shows English

You translated strings under **PerfLocale → Strings**, the rows are there, and the front end is unchanged. In files mode, three distinct Site Health verdicts point at three different causes.

**1\. The compiled files were never written.**

```
Compiled translation files are missing

String translations exist in the database but no compiled `.l10n.php` files were
found. Files normally regenerate automatically when a translation is saved -
this may indicate a file-permission problem on the uploads directory.
```

**2\. The files are behind the database.** `Translation files are stale` — `%1$d compiled file(s) on disk, but the newest translation row (%2$s) is newer than the oldest file (%3$s). Regenerate so the file cache reflects the current DB state.`

**3\. The translations are stranded.** `Some translations need to be re-linked` — `%d saved translation(s) are not connected to the file-generation pipeline, so the compiled files cannot be produced yet. Click "Regenerate translation files" - it will reconnect them and write the files in one step.`

**The fix for all three** is the same button. Go to **PerfLocale → Settings → Performance** and press **Regenerate Translation Files**. It repairs stranded rows first, then compiles, and reports both numbers back to you. Each Site Health card carries a **Go to Performance settings** link to the same screen.

A healthy site reports `Translation files are present` with the file count and path. If you have not translated any strings yet, the honest answer appears instead of an error: `No translation files needed yet` — `Files will generate automatically once you translate a string.`

## “Translation-files directory is not writable”

```
PerfLocale cannot write compiled .l10n.php files into %s. Ask your host to make
the uploads directory writable by PHP, or switch Settings → Performance → String
translation mode to "Database".
```

**What it means.** Files mode compiles each language and text domain into a `.l10n.php` file under uploads. If PHP can’t write there — a read-only filesystem, a container with an unmounted uploads volume, wrong ownership after a restore — no file is ever produced and the front end keeps falling back to the source language.

**Two ways out.** Fix the permissions on the uploads directory, or switch **String Translation Mode** to **Database (Lazy Load)** on **PerfLocale → Settings → Performance**. Database mode is described in the plugin as `No file generation needed - useful for development or read-only file systems.` Once you switch, the file checks stand down with `String translation mode is not file-based, so no writable directory is needed.`

A related result, `The WordPress uploads directory is not available`, quotes WordPress’ own error back to you (`WordPress reported: %s`) — that is a core-level uploads problem affecting far more than PerfLocale.

## Background jobs sit in the queue and never run

Bulk translations, imports and migrations are queued and nothing happens. Site Health raises a **critical**:

```
PerfLocale background jobs cannot run

DISABLE_WP_CRON is set to true AND Action Scheduler is not available — neither of
PerfLocale's job runners can execute. Background translations, watchdog sweeps,
and lock cleanup will all stall. Either install/enable Action Scheduler, set up a
real OS-level cron pointing at wp-cron.php, or set DISABLE_WP_CRON back to false.
```

The background-jobs card states the same thing from the other direction: `Background jobs are enqueued in WP-Cron but DISABLE_WP_CRON is set. Configure an external system cron to hit wp-cron.php every minute, install WooCommerce or the Action Scheduler plugin, or jobs will sit in the queue indefinitely.` **PerfLocale → Jobs** shows a matching banner: **WP-Cron is disabled on this site.**

**Pick one of three fixes.**

-   **An OS-level cron.** Keep `DISABLE_WP_CRON` as it is and have the server hit `wp-cron.php` on a schedule. This is the usual arrangement on managed hosting.
-   **Action Scheduler.** If WooCommerce or the standalone Action Scheduler plugin is active, PerfLocale can bypass WP-Cron entirely. The Jobs page offers this directly when it detects the library: `Action Scheduler is loaded - switch the engine to "Action Scheduler (auto)" in Settings → Performance`. The **Background Engine** setting is on that tab.
-   **Turn WP-Cron back on** by setting `DISABLE_WP_CRON` to `false` in `wp-config.php`.

A softer warning, **WP-Cron drift detected.**, appears when events are merely overdue: `WP-Cron only fires when traffic hits the site - on low-traffic stores, daily events can be delayed by hours or days.` That is WP-Cron working as designed, and an external cron or Action Scheduler is the answer there too.

## “PerfLocale cron schedules are missing”

PerfLocale schedules four recurring events on every site: `perflocale_jobs_watchdog` (background-jobs watchdog), `perflocale_jobs_gc` (background-jobs garbage collector), `perflocale_lock_cleanup` (expired-lock reaper) and `perflocale_mt_usage_gc` (machine-translation usage cleanup). When any is absent:

```
These PerfLocale recurring events are not scheduled and won't run: %s. PerfLocale
re-registers them automatically on any admin page load (checked at most once every
6 hours), so loading wp-admin again is usually enough. If they stay missing, saving
any PerfLocale setting forces an immediate re-check, and deactivating then
reactivating the plugin rebuilds them from scratch.
```

**Escalate in that order** — load wp-admin, then save any PerfLocale setting to force an immediate re-check, then deactivate and reactivate. If only the garbage collector is gone you get its own card instead: `The daily perflocale_jobs_gc cron event is missing. Stuck jobs and stale lock rows will not be cleaned up.` You can also run maintenance by hand:

```bash
wp perflocale jobs gc       # run garbage collection now
wp perflocale jobs resume   # re-enqueue queued/running jobs after a reactivation
```

## Jobs stay queued even though cron is healthy

```
Background-jobs queue is paused

Workers are re-queueing jobs every 5 minutes instead of running them. Visit
PerfLocale → Settings → Performance and uncheck "Pause queue" to resume processing.
```

**What it means.** Somebody pulled the operator brake. This is a deliberate setting, not a fault — a worker that picks up a paused job writes `Worker paused by operator; re-queued.` to the job log and schedules itself again instead of executing, so nothing is lost while it is on. The re-check interval is filterable with `perflocale/jobs/pause_recheck_seconds`. **PerfLocale → Jobs** shows the same banner: **Queue paused.**

**The fix.** Untick **Pause queue** on **PerfLocale → Settings → Performance**, or from the command line:

```bash
wp perflocale jobs unpause
wp perflocale jobs list
```

## Machine translation fails on every request

Which message you get tells you which of five different causes you have.

| Message | Cause and fix |
| --- | --- |
| No machine translation provider has been selected. Open PerfLocale → Settings → Addons → Machine Translation and pick a provider. | No provider chosen. Pick one on that subtab. |
| %s is not configured. Check its settings under PerfLocale → Settings → Addons → Machine Translation. | Provider selected but incomplete — usually a missing API key. See API keys for supplying it by constant or environment variable instead of the database. |
| The configured MT provider host does not resolve | Site Health’s DNS probe found no address for the selected provider’s host: DNS lookup for %1$s (provider: %2$s) returned no address. This usually means an outbound firewall or a container without DNS. The probe knows four hosted endpoints — api.deepl.com, api-free.deepl.com, translation.googleapis.com and api.cognitive.microsofttranslator.com; anything else is skipped with this test covers only hosted providers. |
| Translation provider %1$s is temporarily unreachable (circuit breaker open; retry in %2$ds). | A run of failures tripped the breaker, so calls are refused rather than piling retries onto a failing dependency. See below. |
| Monthly character limit reached (%1$s / %2$s). Translation blocked to prevent overage charges. | Your own cap, not the provider’s. Raise or clear it under Settings → Addons → Machine Translation. |

**On the character limit.** A pre-flight check refuses whole batches before any API call with `Monthly character limit would be exceeded (~%1$s characters required, limit %2$s). Translation blocked to prevent overage charges.`, and metadata is skipped separately with `Meta fields skipped: monthly machine-translation character limit reached.` Site Health escalates as you approach the wall — first `Machine-translation usage is high`, then `Machine-translation usage is near the monthly limit`: `Further translation requests will fail until next month or until you raise the limit in Settings → Addons → Machine Translation.` Setting the limit to `0` means unlimited, and the card then reads `Machine translation is unlimited`.

**On circuit breakers.** The Site Health card `PerfLocale circuit breakers` lists any that are open: `One or more circuit breakers are OPEN — calls to the listed external services are being refused to prevent piling retries onto a failing dependency.` Each row carries a **Reset now** action. From the command line:

```bash
wp perflocale breakers list
wp perflocale breakers status mt_deepl
wp perflocale breakers reset mt_deepl
```

Reset only after you have confirmed the upstream is healthy — otherwise the breaker simply re-opens. A breaker in its half-open probe state is reported too: `The following breakers cooled down and are now allowing the next call as a probe: %s. If the probe succeeds the breaker closes; if it fails it re-opens for another cooldown cycle.` There is more in [Reliability & Circuit Breakers](https://perflocale.com/docs/reliability/).

Bulk actions on **PerfLocale → Translations** surface a shorter version of the same problem: `Machine translation is unavailable. Configure a provider with a valid API key under Settings → Addons → Machine Translation.`

## Translations stuck “in progress” for days

```
Some translations have been stuck for over a week

%1$d translation rows have been in in_progress or pending status for more than
%2$d days. This usually means background jobs crashed mid-run, an MT provider
stopped responding, or workers were assigned the rows but never returned. Check
the PerfLocale Jobs admin page for failed/retried entries, then either re-dispatch
or manually mark them.
```

**What it means.** Rows have sat in `in_progress` or `pending` for more than seven days. The age is measured against the database clock — `DATE_SUB( NOW(), INTERVAL %d DAY )`, not a PHP timestamp — so a database server in a different timezone won’t produce false alarms.

**The fix.** Open **PerfLocale → Jobs** and look for failed or endlessly-retried entries, then retry or cancel them. By command line:

```bash
wp perflocale jobs list --status=failed --format=json
wp perflocale jobs retry <job-id>
wp perflocale jobs cancel <job-id>
```

If the underlying cause was a stalled runner, fix that first — see [background jobs never run](#jobs-never-run) above. The count is cached for an hour, so a clean-up won’t clear the card immediately.

## “PerfLocale has orphan translation rows”

```
Found %1$d post-translation rows and %2$d term-translation rows pointing at
posts/terms that no longer exist. This usually happens after a direct DB import, a
destructive bulk-delete that bypassed wp_delete_post()/wp_delete_term(), or a
partial restore. Run `wp perflocale health-check` to review, then
`wp perflocale health-check --fix` to clean up.
```

**What it means.** Translation links whose target post or term has been deleted out from under the plugin. PerfLocale cleans up after itself when content is removed through WordPress’ own functions; these rows come from deletions that bypassed them.

**The fix.** Review first, then repair:

```bash
wp perflocale health-check
wp perflocale health-check --fix
```

The plugin is explicit about what `--fix` touches: `The fix deletes the dangling link rows (and any other stale database references it finds); your posts, terms and their translations are not touched.` Take a database backup first anyway. A clean site reports `Every translation row points at a real post/term.`

## Exchange rates are not updating

Two very different causes produce the same stale rates, and the remedies are _not_ interchangeable — so PerfLocale tells you which one you have.

**A rate source exists but the sync isn’t running:**

```
Exchange rates have not been updated recently (last sync: %s). Check that WP-Cron
is running, or sync manually from the Currencies settings.
```

**No rate source is wired at all:**

```
Automatic exchange-rate sync is on, but no rate source is configured, so rates
cannot update (last sync: %s). Register a provider with the
perflocale/woocommerce/exchange_rate_providers filter, supply rates directly with
perflocale/woocommerce/exchange_rates_fetched, or turn auto-sync off in the
Currencies settings.
```

**Why the second one exists.** No rate provider ships with the plugin. With nothing registered, both the scheduled sync and the **Sync Now** button abort before any network call is made — so chasing WP-Cron would be chasing the wrong thing entirely.

**The fix.** Register a provider through `perflocale/woocommerce/exchange_rate_providers`, or return rates directly from `perflocale/woocommerce/exchange_rates_fetched`. If you would rather maintain rates by hand, untick **Auto-Sync Exchange Rates** under **PerfLocale → Settings → Addons → WooCommerce**; the card then reads `Automatic exchange-rate sync is disabled` — `Exchange rates are managed manually - no freshness warning applies.` Working code for both filters is in [Exchange Rates](https://perflocale.com/docs/exchange-rates/), which also covers [provider-side failures](https://perflocale.com/docs/exchange-rates/#troubleshooting) such as empty responses and invalid currency codes.

The same screen has a **Sync Status** row with a **Sync Now** button and a **Last synced** timestamp, which is the quickest way to test a newly-registered provider.

## “PerfLocale database tables are missing”

```
One or more PerfLocale tables weren't created. This usually happens after
restoring a backup from a time before the plugin was activated, or when the
database user lacks CREATE TABLE privileges. Deactivate and reactivate the plugin
to recreate them.
```

**The fix** is in the message: deactivate and reactivate at **Plugins → Installed Plugins**. If the tables still don’t appear, your database user is missing `CREATE TABLE` — that is a hosting change, not a plugin setting. The **Info** tab shows `Tables present: No - needs repair` for the same condition.

A related activation failure gets its own notice: `PerfLocale could not seed the default English language. Database error: %s`. Activation is deliberately allowed to complete rather than dying half-way, so you can fix it by hand — add a language at **PerfLocale → Languages** once the database problem is resolved.

## “A pending database migration was not applied”

```
Stored schema version %1$d is behind the expected version %2$d. Deactivate and
reactivate the plugin, or run `wp perflocale db-migrate`, so the migration
completes.
```

**The fix.** Run the migration directly:

```bash
wp perflocale db-migrate
```

Two neighbouring verdicts mean different things. `Database schema has never been installed` — ``The `perflocale_db_version` option is missing. This usually means the activation hook did not run.`` — calls for a deactivate and reactivate. `Database schema is newer than the installed plugin` — `This usually indicates a plugin downgrade. Re-install the newer version or the schema may not match what the code expects.` — means you rolled the plugin files back without rolling the database back.

If a migration actually failed, you will have seen it happen: `PerfLocale: Database migration to version %1$d failed: %2$s. The plugin will retry on next page load.` The version is deliberately not recorded on a partial step, so the retry is safe.

## “No default language is set”

```
PerfLocale cannot resolve URLs without a default language. Add a language in
PerfLocale → Languages and mark it as default.
```

**What it means.** Routing, the language switcher, hreflang emission and every translation feature need a default language to fall back to. Without one, none of them can resolve a URL.

**The fix.** Add a language at **PerfLocale → Languages** and mark it default, or from the command line:

```bash
wp perflocale languages list
wp perflocale languages add en_US --default
```

Two refusals you may hit on that screen. `The default language was not changed. Only an active language can be the default — activate it first, then set it as default.` And, if the slug can’t be expressed in a URL: `The slug %1$s cannot be used in URLs, so nothing was saved. Use two to five lowercase letters, optionally with one region suffix — for example de or pt-br.` Duplicate slugs and duplicate locales are refused the same way, with the offending value named: `each language must have a unique URL identifier` and `each language must have a unique WordPress locale`.

## “Required PHP extensions are missing”

```
PerfLocale relies on the following extensions. Ask your host to enable them or
features like XLIFF import, string scanning, and REST requests will fail
unpredictably
```

**What it means.** PerfLocale needs `mbstring`, `dom`, `libxml`, `simplexml` and `json`. XLIFF import and export use the XML trio, every text path uses `mbstring`, and the REST layer and provider calls need `json`. The failures are the unpredictable kind, which is why this is flagged critical rather than left to surface as a mystery error later.

**The fix** is your host’s to make, and the plugin names the packages: `Most shared hosts enable these by default; they are usually provided by the php-xml, php-mbstring, and php-json packages.`

## “PerfLocale: Broken addon(s) disabled”

A red notice appears listing one or more addons, each with a **Retry** button:

```
PerfLocale: Broken addon(s) disabled - the addon(s) listed below threw errors
three times in a row and have been auto-disabled to protect your site. Fix the
underlying issue (deactivate the third-party plugin, update it, or check the debug
log) then click Retry.
```

**What it means.** An addon failed to boot three consecutive times and was quarantined so a broken integration can’t take the site down with it. Site Health carries the matching card: `One or more PerfLocale addons are disabled` — `Check the error log for the root cause, then click "Retry" in the PerfLocale admin notice to restore them.`

**The fix.** Deal with the root cause first — usually a third-party plugin that the addon integrates with, recently updated or recently broken — then press **Retry** in the notice, which clears the failure counter. **PerfLocale → Addons** lists the current state of every addon.

Two related messages. `One or more PerfLocale addons have pending schema migrations`: ``Deactivate and reactivate the affected addons, or run `wp perflocale addon migrate <id>` for each one, to bring their schemas current.`` And a persistent notice headed **PerfLocale: addon migration / uninstall issues**, which lists the recorded failures and tells you how to clear them: `Clear via: wp perflocale addon errors --clear`.

## “PerfLocale is caching nothing between requests”

```
This site has a persistent object cache (Redis, Memcached, or equivalent), but
PerfLocale's "Object Cache" setting is unchecked. With a drop-in installed
PerfLocale skips its database transient fallback entirely, so this setting is its
only persistent layer - unchecking it leaves no persistent caching at all, and
every language, translation and slug lookup is recomputed on each request.
```

**What it means.** This is the worst of both worlds and easy to fall into: the drop-in is installed, so PerfLocale stops using its database transient fallback, but the setting that would use the drop-in is off — leaving nothing persistent at all.

**The fix.** Re-enable **Object Cache** under **PerfLocale → Settings → Performance** unless you are deliberately isolating PerfLocale from a misbehaving cache backend.

The inverse warning, `Object cache is enabled in settings but no persistent backend is active`, is harmless: `PerfLocale is configured to use the object cache, but WordPress is running with the default per-request cache.` Install a drop-in when you are ready, or leave it — nothing is broken. And with the setting off and no drop-in present, the card passes with `This site has no persistent object cache, so PerfLocale still caches to transients in the database.`

## “Database string mode is loading a large per-request blob”

```
The largest language holds %1$s translated strings (over %2$s). In database mode,
PerfLocale loads a language's entire string map on the first translated string of
every page request. Switching the string-translation mode to "Files" serves each
language and text domain from an opcache-compiled file instead, removing that
per-request cost. Files mode requires a writable uploads directory.
```

**What it means.** The warning fires when the heaviest single language reaches 20,000 translated strings — adjustable with the `perflocale/site_health/string_blob_threshold` filter if your rows are unusually small or large.

**The fix.** Switch **String Translation Mode** to **Translation Files (Recommended)** on **PerfLocale → Settings → Performance**, then press **Regenerate Translation Files**. In files mode the card stands down entirely: `Files mode serves each language/domain from an opcache-compiled file, so the string count does not affect per-request memory.`

A neighbouring card, `PerfLocale eager-link-map is over the size sentinel`, reports that the autoloaded translation-link map tripped its own size guard and disabled itself, so lookups fall back to per-row database queries. That is a correctness-preserving fallback, not a fault. Archive stale translation groups, or raise the cap with the `perflocale/cache/eager_map_byte_cap` filter if your hosting can absorb the `alloptions` cost. More context in [Production Tuning](https://perflocale.com/docs/production-tuning/).

## An import or upload does nothing at all

You submit a large file on a PerfLocale screen and land back on the same page having apparently changed nothing:

```
PerfLocale: That submission was too large for this server, so PHP discarded it
before WordPress saw it — nothing was uploaded, imported or changed. This server
accepts at most %1$s per file and %2$s per submission. Use a smaller file, or ask
your host to raise the PHP upload_max_filesize and post_max_size settings.
```

**What it means.** PHP threw the request body away before WordPress ran, so no form handler executed. Nothing was half-imported — the site is exactly as it was.

**The fix.** Use a smaller file, raise `upload_max_filesize` and `post_max_size`, or bypass the web request entirely with WP-CLI, which is not subject to either:

```bash
wp perflocale import /tmp/perflocale-backup.json
wp perflocale po-import /tmp/fr.po --lang=fr
```

A PO file uploaded through the admin screen that clears PHP’s limits can still exceed the importer’s own: `The uploaded PO file is larger than the importer accepts (limit: %s). Split it into smaller files, or raise the ceiling with the perflocale/po/max_bytes filter.` Two shorter refusals are exactly what they say: `PO import requires a language and a file.` and `The uploaded file could not be read. Please try again.`

## “Settings could not be saved”

The Settings screen now reports a refused write instead of showing the success notice regardless. The database declines a value it cannot store — one that is too long for its column, or that carries characters the column does not accept — and it declines the whole write rather than shortening it, so nothing changes and your previous settings are intact.

The usual cause is one over-long free-text field. Shorten it and save again. Re-saving a tab without changing anything is not an error and still reports success.

## “The PO file is not valid UTF-8” on import

PerfLocale refuses a PO file that is not valid UTF-8, and refuses one whose entries all carry an empty translation. Both are deliberate, and the reason is worth knowing: gettext parsing drops entries it cannot decode without reporting an error, so a file saved as ISO-8859-1 or Windows-1252 reads as valid-but-empty. Imported in **Replace** mode that would delete every existing translation for the language and put nothing back.

Re-save the file as UTF-8 in your editor or translation tool and import again. Poedit does this under _Catalogue → Properties → Charset_.

## “A database error stopped the write” when saving translations

On **PerfLocale → Strings**:

```
%d translations could not be saved and were discarded — a database error stopped
the write. Copy your text before reloading, then try again.
```

And on **PerfLocale → Translations**: `Nothing was flagged: the database rejected the update. The translations are unchanged — check the error log and try again.`

**What it means.** The write reached the database and was refused. Both messages are deliberate about the outcome so you know whether to retype — the strings message tells you to copy your text _before_ reloading, because the discarded content is not held anywhere.

**The fix.** Check the PHP error log with `WP_DEBUG` and `WP_DEBUG_LOG` enabled. Common causes are a full disk, an exhausted connection pool, or a character-set mismatch on a restored database rejecting characters in the translation.

## Still stuck?

Before opening a thread, take a copy of **Tools → Site Health → Info → PerfLocale**. It is built for exactly this — `Paste this section when asking for support.` — and reports your PerfLocale version, schema version, URL mode, language-detection order, active languages, translatable post types and taxonomies, row counts, detected SEO plugin, builder and theme, and active addons. The machine-translation API keys appear only as `configured` or `not configured`, never as the value itself, so the dump is safe to paste in public.

Include the exact message you saw, quoted in full, plus the WordPress and PHP versions and whether the site is multisite.

-   [Support](https://perflocale.com/support/) — the channels for bug reports, questions and feature requests.
-   [wordpress.org/support/plugin/perflocale/](https://wordpress.org/support/plugin/perflocale/) — the WordPress.org community support forum.
