Content Translation

PerfLocale treats every translatable object (post, page, custom post type, taxonomy term) as a member of a translation group. Each group has one entry per active language; the default language’s entry is the source, every other entry is a translation. The source post is a regular WordPress post - translations are separate WP posts linked to it through an internal join table.

Default language

The default language is the one your WordPress site was originally written in - English, German, whatever. Set it at PerfLocale → Languages (one language row carries a DEFAULT badge). The default language is the canonical source for every piece of content; translations into other languages are derived from it. Changing the default after content exists is possible but requires care - see Migration.

Translatable post types & taxonomies

Out of the box, post and page are translatable, with category and post_tag as the default taxonomies. Enable custom post types and additional public taxonomies at PerfLocale → Settings → Translation — every public type appears there as a checkbox, and integration addons (e.g. WooCommerce) enable their own types automatically. Media attachments are a separate setting because translating alt text / captions is often desired without cloning the file itself.

Creating a translation

  1. Open the source post in the editor.
  2. In the PerfLocale Translations panel (Gutenberg sidebar or the classic-editor metabox), click + Create next to the target language. A new post is created in that language, linked to the source; once it exists the button becomes Edit.
  3. Edit the translated post independently - it’s a normal WP post in every respect, just tied to its source through the translation group.

Alternatively, create a post directly at Posts → Add New and assign its language in the PerfLocale Translations panel — the Set as buttons in the Gutenberg sidebar, or the Language dropdown in the classic-editor metabox. To place a post in an existing translation group, use the source post’s + Create button; two already-existing posts cannot be linked manually from the sidebar.

Translation statuses

Each translation carries one of five statuses tracked separately from WordPress’s post_status. These are per-translation states — what the translator or editor sees in the Translations matrix:

  • Empty - placeholder; no content has been written.
  • Draft - content started but not ready.
  • Pending (label: Pending Review) - written but awaiting review before publication.
  • Published - ready; visitors see this translation.
  • Needs Update - the source post changed after this translation was last saved. Re-save the translation to clear.

The Translations matrix

Visit PerfLocale → Translations for a cross-language overview of every post. Rows are source posts; columns are languages. Each cell shows the translation’s status as a coloured badge with a shortcut arrow to edit. Filter by post type, language, or status; search by title.

Navigation menus are not translated the way posts are. A menu is a nav_menu term, and PerfLocale deliberately never auto-assigns a language to nav_menu terms or puts them in a translation group. Instead you build one menu per language in the ordinary WordPress menu editor, tag each one with a language, and tell PerfLocale which menus belong together. On the front end PerfLocale then swaps in the right menu for the language being viewed.

Go to Appearance → Menus. PerfLocale appends a Menu Language fieldset to the end of the Menu Settings box, below the usual Auto add pages and Display location controls. It contains one dropdown, perflocale_menu_language, listing Not set followed by every active language — each shown with its flag, its native name (its English name when no native name is stored) and its BCP-47 code.

Pick a language and press Save Menu. The choice is stored as term meta on the menu itself. Saving requires the edit_theme_options capability and WordPress’s own nav-menu nonce (field update-nav-menu-nonce, action update-nav_menu); a programmatic menu save from WP-CLI or an importer carries neither, so it never touches the language assignment.

Linking menus across languages

Once a menu has a saved language, reloading the screen adds a Linked Menus block to the same fieldset: one dropdown per other active language, each listing every menu on the site with - None - as the default. Choose the counterpart menu for each language and save.

You only have to do this once, from one menu in the set. When PerfLocale saves the links it builds the whole group — the menu you are editing plus every menu you pointed at — and then writes pointers between every pair in that group, in both directions. Fill in the links on your English menu and the German and Polish menus come back pointing at each other and at English too.

Term meta keyStored onValue
_perflocale_languageThe menu termThe language slug, or empty for Not set
_perflocale_menu_{slug}Every menu in the groupTerm ID of that group’s menu for language {slug}

Renaming a language slug on PerfLocale → Languages rewrites both of these — the _perflocale_menu_ key suffixes and the _perflocale_language values — so an existing menu group survives the rename instead of quietly falling back to None.

While you are editing a menu that has an explicit language, PerfLocale narrows the post-type panels in the “Add menu items” column so they only offer content in that language. Pages are filtered through get_pages; posts and custom post types through a pre_get_posts constraint scoped to the nav-menus screen. Taxonomy panels — Categories, Tags and any other term picker — are left alone and still list every term.

The post-type picker filter is strict. Only content explicitly linked to the menu’s language is offered. Posts and pages that PerfLocale is not managing at all — content that predates the plugin and has never been given a language — are excluded rather than shown as a fallback, so a menu for one language cannot silently collect unassigned content. If a page you expect is missing from the picker, give it a language in the editor’s Translations panel first.

A menu left on Not set is never filtered: its pickers show everything, exactly as they do without PerfLocale.

Separately, whenever two or more languages are active, PerfLocale adds a small language-code badge to the item labels in that column so you can tell at a glance which language each candidate belongs to. The badge data covers every published post, page or custom-post-type item PerfLocale has linked to a language, newest-modified first, capped by the perflocale/menu/badge_post_limit filter.

For a theme location, PerfLocale looks up the menu the theme has assigned to that location and, if the request is in another language, substitutes that menu’s counterpart before WordPress renders it. The same substitution happens when a template passes a numeric menu ID directly. A menu passed by name or slug is left alone.

Resolution works like this: if the assigned menu is already tagged with the request language it is used as-is; otherwise PerfLocale follows that menu’s _perflocale_menu_{slug} pointer. A pointer is only honoured if the target menu still exists and has not since been re-tagged to a different language — a target left on Not set is still trusted, since the usual pattern is to tag one menu in the group and simply link the rest. If nothing resolves, the theme’s original menu renders unchanged. That is the fallback the field’s own help text describes: If no menu is linked for a language, the default menu will be shown.

Items inside a menu that has no language assigned are handled individually instead. A classic menu item stores the object it points at, and its permalink resolves to that object’s own language — so on a translated page a shared menu would link every visitor back to the source language. PerfLocale re-points post-type and taxonomy items at the current language’s counterpart where one exists. Custom links and archive items are never rewritten, and a post whose counterpart is not published is skipped so the item keeps a URL that works.

Menus that do carry a language assignment are left untouched at item level. They were curated for that language on purpose, and the menu swap above has already picked the right one.

One further fix applies to every menu: an item pointing at the static front page (or at any page in the front page’s translation group) is rewritten to the home URL of the language that page belongs to — the default language when the page carries none — rather than to the page’s own permalink, so a German front-page item lands on /de/ and not /de/homepage/. The rewrite goes through PerfLocale’s URL converter, so it is correct in every URL mode — subdirectory, subdomain, domain per language and query parameter alike.

Classic menus only. Everything in this section concerns menus rendered by wp_nav_menu(). Block themes render the Navigation block instead, which never calls wp_nav_menu(), so the per-language menu swap does not apply there; PerfLocale localises those links at the block level instead, by filtering the rendered core/navigation-link and core/navigation-submenu output.

One filter is PerfLocale’s own — see the hooks reference for its full entry; the rest are the WordPress hooks the menu integration attaches to, listed so you can judge ordering against your theme.

HookKindWhat PerfLocale does
perflocale/menu/badge_post_limitFilter (PerfLocale)Row cap for the language-badge lookup. int, default 5000; 0 loads every row.
wp_nav_menu_argsFilterSwaps a theme location or numeric menu ID to that group’s menu for the request language.
wp_nav_menu_objectsFilter (priority 9)Re-points post and term items at the current language’s counterpart, in menus with no language assigned.
wp_nav_menu_objectsFilter (priority 10)Rewrites front-page items to the per-language home URL.
wp_update_nav_menuActionSaves the menu language and propagates the linked-menu pointers.
wp_create_nav_menuActionSame, for a menu created rather than updated.
load-nav-menus.phpActionFallback save, for the case where the menu ID changes mid-request.
admin_enqueue_scriptsAction (priority 20)Adds the Menu Language field and the badge script on nav-menus.php.
admin_head-nav-menus.phpActionInstalls the get_pages and pre_get_posts picker filters.

← Back to Docs