Export & Import
Translations need to travel: between staging and production, to an external translation agency, to a colleague’s computer for review, to backup storage. PerfLocale supports three formats covering every common use case - pick the one that fits the receiver. Each has its own home in the admin, so it’s worth knowing which screen to open.
Formats
- JSON - PerfLocale’s own format, and the one the Settings → Export & Import screen speaks. Round-trips everything the plugin owns: settings, role capabilities, languages, translation link groups, strings and their translations, translated slugs, and content hashes. Use for site-to-site migration or full backups.
- .po - classic gettext format, for string translations only. Export and import it from PerfLocale → Strings (the Export PO / Import PO buttons next to the page heading) or via WP-CLI, then edit in Poedit or Loco.
- XLIFF 2.0 - the industry-standard translation exchange format for handing post content to professional translators. Open in any CAT tool (SDL Trados, memoQ, Phrase, Crowdin, etc.). Post titles, content, and excerpts travel as
<unit>/<segment>elements per the XLIFF 2.0 schema (urn:oasis:names:tc:xliff:document:2.0). XLIFF has no admin screen of its own — it is exposed through the two REST routes described below.
Exporting via the admin UI
At Settings → Export & Import, the Export row lists one checkbox per section — Settings, Roles & Capabilities, Languages, Translation Links, String Translations, Slug Translations, Content Hashes — all ticked by default, with a Select All toggle above them. Untick what you don’t need and click Export Data. The result is always a single JSON file.
Small exports stream directly to your browser; large ones queue as a data_export background job and the admin redirects to PerfLocale → Jobs. When the job completes, a single-use Download button appears on the job row — click it and the export file is streamed to your browser and deleted from the server in the same request. The async/sync split is governed by the Data export (JSON) threshold under Settings → Performance → Background Thresholds (default: 5,000 rows).
Importing via the admin UI
Same page, Import section. Drop in (or browse for) a PerfLocale JSON export, choose Merge or Replace, and submit. The importer validates the envelope before writing and refuses a file whose format_version it doesn’t understand.
Large imports queue automatically via PerfLocale's background-jobs system (Action Scheduler when loaded; WP-Cron otherwise) and the admin redirects to PerfLocale → Jobs for live progress. Small operations still finish inline. The uploaded file is persisted under wp-content/uploads/perflocale/temp/ for the worker to read and is cleaned up on completion.
WP-CLI (faster for large exports)
For sites with tens of thousands of translations, the CLI is still a strong choice (streams directly to disk, runs under your shell's time budget, easy to schedule). Each subcommand writes a single file; format is dictated by the subcommand, not a flag:
# Full-site JSON envelope (everything PerfLocale owns - settings, role caps,
# languages, translation links, strings, slugs, content hashes).
# The envelope embeds a `format_version` field so older importers refuse
# mismatched payloads.
wp perflocale export /tmp/full-backup.json
wp perflocale import /tmp/full-backup.json # additive merge
wp perflocale import /tmp/full-backup.json --mode=replace # truncate-then-load
# Gettext PO files for string translations, one language at a time
wp perflocale po-export /tmp/de.po --lang=de
wp perflocale po-export /tmp/de-mytheme.po --lang=de --domain=mytheme
wp perflocale po-import /tmp/de.po --lang=de
wp perflocale po-import /tmp/de.po --lang=de --replace
# Multisite: one envelope keyed by blog ID, all sites at once
wp perflocale network-export /tmp/network.json
wp perflocale network-export /tmp/network.json --include-inactive
wp perflocale network-import /tmp/network.json --mode=replace
wp perflocale network-import /tmp/network.json --site=42 # restore one slice on a single-site target
# `wp perflocale import` reads PerfLocale JSON envelopes only. XLIFF goes
# through the REST routes below, not through the CLI.
See WP-CLI Commands → Export & Import for the complete flag reference.
Merge vs. replace semantics
- Merge (default) - additive. Existing rows are kept and foreign keys are re-mapped via id-maps so cross-site references survive intact. Note that re-importing the same backup in Merge mode allocates fresh
translation_groupsrows, because that table has no natural key to match on; the accompanying links de-duplicate through their(object_id, language_id)UNIQUE constraint, so the surplus groups end up linked to nothing and are swept by the daily orphan-group GC. Harmless, but not free. - Replace - clears the target tables (scoped to the current site on multisite) before loading. Use it when you genuinely want to wipe the destination's PerfLocale state first, and for repeatable, deterministic re-imports.
Replace is all-or-nothing. The wipe and the reload run inside one database transaction, and any row the database refuses rolls the whole import back — including the settings, add-on settings, disabled-add-on list and role grants the bundle carried. You get your pre-import state and an error naming the table and the row count, rather than a table that is missing the rows that failed. The same applies to po-import --replace: a refused translation rolls the language back instead of leaving it part-replaced.
Because of that, an import that reports errors is a failed import. wp perflocale import, po-import and network-import all exit non-zero when they do, so a restore script can rely on the exit code alone.
Translation-agency handoff pattern
XLIFF export and import are REST endpoints rather than admin screens, so this flow is driven from a script or an HTTP client authenticated as a user with the perflocale_import_export capability (and edit_post on every post involved):
POST /wp-json/perflocale/v1/xliff/exportwithpost_ids,source_lang, andtarget_lang. You get back an XLIFF 2.0 document containing the title, content, and excerpt units for those posts.- Agency opens the XLIFF in their CAT tool, fills in the
<target>elements, returns the file. POST /wp-json/perflocale/v1/xliff/importwith the returned document asxliff. For each unit, PerfLocale writes the translated text onto the target-language sibling of the source post, creating that sibling as a draft if it doesn’t exist yet. The source post is never modified.- Review the drafts in the editor and publish when you’re happy. Units with no
<target>are skipped rather than blanking anything.
A file the importer cannot read — an empty body, XML that does not parse, a document declaring entities, or a trgLang matching no active language — comes back as 400 invalid_xliff. Retrying it will not help; fix the file. A 500 means something broke on the server. If a file repeats the same unit id (XLIFF 2.0 requires them to be unique), only the last occurrence is applied — the committed result is identical and the import does one write instead of one per repeat.
Content from translators who don’t hold unfiltered_html is passed through the same tightened HTML allowlist PerfLocale applies to machine-translation output, so an agency file can’t smuggle markup past WordPress’s own capability model.
What isn’t included in exports
The exporter ships everything you need to reconstitute a site’s multilingual configuration and translation data. A small set of things is deliberately left out, because including them would either leak secrets, queue meaningless work on the destination, or duplicate state that’s regenerated automatically:
- API keys / tokens / secrets / passwords — every settings key matching
*_api_key,*_key,*_token,*_secret, or*_passwordis stripped from the settings blob before write. The destination operator re-enters them (or supplies them viawp-config.phpconstants / env vars). This is the right behavior for security: a JSON file shouldn’t round-trip your DeepL key through email. - Webhook subscriber URLs — site-specific infrastructure with HMAC secrets; the destination should re-register its own integrations.
- Background-jobs queue state — the
jobstable holds operational state with source-site-specific user / post / blog IDs. Importing it would queue meaningless work on the destination. The destination starts with an empty queue and dispatches its own. - Per-user admin UI preferences — user-meta keys like
perflocale_strings_hidden_langsare user-id-specific and not portable across sites with different user tables. - Generated translation files — the
.l10n.phpoutput files inuploads/perflocale/translations/are regenerated fromstrings+string_translationson the destination. Shipping the build artifacts would multiply export size for no benefit. - Internal caches — the addon-failure tracker, the active-engine memo, the eager-link-map, and similar diagnostic / runtime options are auto-rebuilt from the data that is exported.
Cross-site ID remapping
The importer doesn’t blindly insert primary-key values from the export. It resolves language_id references via the language slug, so a German row exported from a site where German has id = 3 lands correctly under whatever id German happens to have on the destination. Same for string_id via original_hash, group IDs via group type + member identity, etc. This is what makes export → import work cleanly between sites that were configured independently.
Format version + compatibility
Every export carries a format_version integer (currently 1). The importer accepts the current version and any older version it knows how to migrate. It refuses newer versions with a clear error rather than risk silent data loss on a partial-format-understanding import. Upgrade the destination plugin first if you see that error.
Related
- WP-CLI Commands
- Migration - from WPML / Polylang / TranslatePress.
- Content Translation