WP Manifestindependent plugin directory
manifest / seo / trp-seo-slug-localizer

TRP SEO Slug Localizer

Per-language SEO title, meta description and slug overrides for TranslatePress (free edition translates visible content only)

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/denmlt/trp-seo-slug-localizer/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Per-language SEO title, meta description and slug overrides for TranslatePress.

TranslatePress translates what a visitor can see. Everything a search engine reads instead of sees — the <title>, the meta description, the URL — stays in the source language unless the paid SEO Pack add-on is installed. Every secondary language therefore ships the source language's titles and the source language's URLs, which is exactly what search engines index.

This plugin stores an override per language and serves it in the places that matter.

Any language set. Nothing here is tied to a particular pair. The languages, their codes and their URL slugs are read from TranslatePress itself, so a site with one secondary language and a site with six behave the same: every secondary language gets its own block on the edit screen and its own --language value on the command line. The default language is deliberately left alone — it already has its title, description and slug in the post itself.

What it does

SEO title pre_get_document_title, plus Rank Math's title, Open Graph and Twitter tags
Meta description Rank Math's description, Open Graph and Twitter tags
Slug permalinks, rewrite rules, and a 301 from the untranslated URL
Post type archives the same three, from one settings screen
Taxonomies a translated base segment, plus a default term title and description
hreflang & switcher TranslatePress' own URLs are corrected to match the translated slugs
XML sitemap every language version listed and annotated (Rank Math)
Terms title, description and slug, on the term edit screen

Rank Math is optional. Without it, the document title is still translated; the meta description is left to whatever SEO plugin is in charge.

Requirements

  • WordPress 6.0+ (tested up to 7.1)
  • PHP 8.0+
  • TranslatePress 3.x
  • Pretty permalinks enabled

Install

Via Composer, on Bedrock-style installs:

composer config repositories.trp-seo-slug-localizer vcs https://github.com/denmlt/trp-seo-slug-localizer
composer require dyuzhaev/trp-seo-slug-localizer

Or drop the directory into wp-content/plugins/ and activate it.

Use

One page: the TranslatePress SEO / Slug Overrides box on the edit screen, one block per secondary language. Leave a field empty to keep the original.

Archives: Settings → TRP SEO Slug Localizer.

In bulk, via WP-CLI — which is also how the same values get onto a production database, since overrides live in the database and not in the code. ru_RU below is just an example; pass whichever TranslatePress language code you need:

wp trpssl list --language=ru_RU
wp trpssl set 128 --language=ru_RU --title="Протезирование зубов в Запорожье" --description="…"
wp trpssl archive services --language=ru_RU --title="Услуги" --slug=uslugi
wp trpssl taxonomy job-title --language=ru_RU --slug=specialisty
wp trpssl term 4 --language=ru_RU --title="Врачи" --slug=vrachi
wp trpssl import overrides.json --dry-run

import takes a list of entries. id is a post ID, or one of the three prefixed forms — archive:<post_type>, taxonomy:<taxonomy>, term:<term_id>:

[
  {"id": 128, "language": "ru_RU", "title": "…", "description": "…"},
  {"id": "archive:services", "language": "ru_RU", "title": "…", "slug": "uslugi"},
  {"id": "taxonomy:job-title", "language": "ru_RU", "slug": "specialisty"},
  {"id": "term:4", "language": "ru_RU", "title": "Врачи", "slug": "vrachi"}
]

For theme authors

A theme that prints its own metadata — JSON-LD, say — should ask the plugin rather than read the SEO plugin's post meta, which is stored in one language only:

$title = function_exists('trpssl_current_override')
    ? trpssl_current_override('seo_title')
    : '';

trpssl/supported_post_types and trpssl/supported_taxonomies filter what gets an override slot.

How the slug rewriting works

The plugin never guesses at a URL. It asks WordPress for the real permalink with its own filters temporarily disabled, strips the install path and the language prefix, swaps the archive prefix and the final segment for their translated counterparts, then puts the language prefix back. Rewrite rules are registered for those paths, and template_redirect sends a visitor from the untranslated address to the translated one with a 301, query string and all.

TranslatePress builds its hreflang tags and its language switcher from its own idea of a URL, so the plugin filters trp_get_url_for_language as well. Without that, hreflang would point at an address that only answers with a 301 — and an hreflang that disagrees with the canonical is an hreflang Google discards. The same filter fixes the way back: from a page whose slug is translated, TranslatePress would otherwise drop the prefix and point at a path that does not exist in the source language.

Term URLs work the same way, in two parts. The taxonomy base — /job-title/ — is one value per taxonomy, and each slug after it is taken from the term that owns it, so a hierarchical taxonomy translates parents and children alike. The base is derived from a real term link rather than from the rewrite settings, so with_front and custom bases are accounted for.

Only posts and terms that actually carry an override are considered, and both lists are cached — the lookup runs on every front-end request.

The sitemap

Free TranslatePress lists nothing but the source language, so translated pages exist only as hreflang tags inside HTML — which a crawler has to fetch a page to see. They are never offered for discovery, and that is the slowest way to get a language version indexed.

With Rank Math present, the plugin writes the sitemap the way Google documents multilingual sitemaps: every language version is its own <url> entry, and every entry carries an <xhtml:link> for each version, itself included. On a site with one secondary language that doubles the number of listed addresses.

Two things to know about Rank Math while working on this. It caches generated sitemaps as files under uploads/rank-math/, so nothing changes on screen until those are deleted — wp rankmath sitemap generate alone does not clear them. And it builds the sitemap from a query of its own, handing the entry filter a bare stdClass row rather than a WP_Post; an instanceof check there matches nothing and silently drops every annotation.

Notes and limits

  • The static front page keeps its address; its SEO overrides still apply.
  • Term archives fall back to the taxonomy-wide title and description, so a taxonomy with hundreds of terms does not need hundreds of hand-written titles.
  • Changing any slug schedules a rewrite flush on the next request.
  • Date and author archives are out of scope.

Development

composer install
composer test    # 30 unit tests, no WordPress needed
composer lint

The logic that can produce a wrong URL — path arithmetic, language resolution, override normalisation — lives in src/ as pure functions with no WordPress dependencies, which is what the test suite covers. The WordPress-facing classes in includes/ wire those to hooks.

License

GPL-2.0-or-later. Author: Den Dyuzhaev.