WP Residence Help WP Residence Help

  • WPRESIDENCE
  • Video Tutorials
  • Client Support
  • API
Home / WPEstate / WPResidence Translate Plugin / URL Structure & Permalinks — Developer Deep Dive

URL Structure & Permalinks — Developer Deep Dive

111 views 0

This article documents how wpresidence-translate (text domain wpr-translate) builds language-aware URLs from the moment WordPress asks for a permalink until the moment the localized string is returned to the caller. If you are integrating a custom post type, writing a child-theme override, or debugging a stray URL on a multi-language real estate website, start here.

Files Involved

File Role
includes/language-permalinks.php Hooks into WordPress permalink filters and routes every generated link through the localizer.
includes/functions-permalinks.php Shared URL builder — strips any existing language prefix and injects the target one.
includes/permalinks-helpers.php Low-level helpers (wpr_translation_get_known_language_slugs(), wpr_translation_build_url_from_parts()).
includes/activator.php Creates the {prefix}wpestate_translation_slugs table during activation.

The Filter Stack

wpr_translate_language_permalinks_bootstrap() registers six filters at priority 10:

add_filter( 'post_type_link',          'wpr_translate_filter_post_type_link',          10, 4 );
add_filter( 'post_link',               'wpr_translate_filter_post_link',               10, 3 );
add_filter( 'page_link',               'wpr_translate_filter_page_link',               10, 3 );
add_filter( 'term_link',               'wpr_translate_filter_term_link',               10, 3 );
add_filter( 'post_type_archive_link',  'wpr_translate_filter_post_type_archive_link',  10, 2 );
add_filter( 'home_url',                'wpr_translate_filter_home_url',                10, 4 );

Each callback funnels into wpr_translate_localise_post_link() or calls wpr_translate_prefix_url_for_language() directly. The end of the chain is always wpr_translation_localize_url_for_language() in functions-permalinks.php.

Screenshot: Filter stack diagram showing permalink filter flow from WordPress core to URL builder

Picking the Target Language

wpr_translate_get_element_language( $element_id, $element_type ) runs a prepared query against {$wpdb->prefix}wpestate_translation_translations:

SELECT language_code
FROM {prefix}wpestate_translation_translations
WHERE element_id = %d AND element_type = %s
LIMIT 1

The element_type argument is prefixed per kind:

  • Posts and pages → post_{post_type} (e.g. post_estate_property, post_page).
  • Taxonomy terms → tax_{taxonomy} (e.g. tax_property_city).

Results are memoised in $GLOBALS[‘wpr_translate_language_cache’] keyed by “{element_type}:{element_id}” to avoid repeated queries during a single request. Cache misses are stored as null so a second lookup short-circuits just as fast.

The URL Localizer

wpr_translation_localize_url_for_language( $url, $language ) is the heart of the system. Its algorithm:

  1. wp_parse_url() the incoming URL.
  2. Strip the home URL’s base path segments (so the helper also works in subdirectory WP installs).
  3. Build a known slugs array from wpr_translation_get_known_language_slugs() plus the target language’s slug and sanitised code.
  4. Shift off any leading segment that matches a known language slug. This makes the helper idempotent: calling it twice leaves the URL unchanged.
  5. If the target language is not the default, array_unshift() its slug onto the relative segments.
  6. Rejoin base + relative segments, respect the original trailing-slash decision, and rebuild the URL via wpr_translation_build_url_from_parts().

Home and base parts are statically cached on the first call so repeated use inside a single request is cheap.

Slug Storage for Translated Content

Per-language content slugs live in {prefix}wpestate_translation_slugs, created by wpr_translate_activator_create_tables():

Column Type Notes
id BIGINT UNSIGNED Auto-increment primary key.
element_id BIGINT UNSIGNED Post or term ID.
element_type VARCHAR(45) post_* / tax_* convention, same as translations table.
language_code VARCHAR(7) Matches {prefix}wpestate_translation_languages.code.
slug VARCHAR(255) Currently active slug for this (element, language) pair.
old_slugs TEXT Serialized history of previous slugs; drives old-URL → new-URL lookups.
updated_at DATETIME ON UPDATE CURRENT_TIMESTAMP.

Indexes: UNIQUE KEY uniq_slug (element_id, language_code) enforces one active slug per language; KEY lang (language_code) speeds per-language scans.

 

Generating a Permalink for a Specific Translation

wpr_translation_get_post_language_permalink( $post_id, $language_code ) is the safe helper when you need a URL for a known language — for example, to render a language switcher:

  1. Resolves the language payload via wpr_translate_get_language().
  2. Temporarily sets the current language with wpr_translate_set_current_language(), calls get_permalink(), then restores the previous language.
  3. Funnels the result through wpr_translation_localize_url_for_language() for a final normalisation pass.
  4. Includes compatibility branches for Polylang (pll_switch_language, pll_permalink) and WPML (wpml_switch_language, wpml_permalink) when either plugin is present.

Archive & Home URL Nuance

wpr_translate_filter_post_type_archive_link() and wpr_translate_filter_home_url() do not look up the element in the translations table — there is no element. They read wpr_translate_get_current_language() and fall back to wpr_translate_get_default_language(). Implication: if you call home_url() while the current language context has not yet been established (very early hooks, CLI), the URL will use the default language prefix.

Default Language Is Prefix-Free

wpr_translate_is_default_language() short-circuits slug injection. The builder still strips any stray default-language prefix from the path so defensively-formed URLs normalise correctly, but it never adds one. This is by design — the default language keeps the clean URL.

Extending the Behavior

  • Add a custom post type — no action required. As long as translations are recorded with element_type = ‘post_{post_type}’, the existing filters handle URL rewriting.
  • Override slug generation — copy the relevant filter in a child-theme plugin and guard it with remove_filter() before adding your own at the same priority.
  • Bypass localization for a specific URL — WordPress calls home_url() and friends through filters; return your URL on an earlier priority or use remove_filter() for the duration of your call.
  • Clear the per-request cache — unset the relevant key in $GLOBALS[‘wpr_translate_language_cache’] after programmatically changing a post’s language mapping during the same request.

Gotchas

  • Do not call sanitize_title() on language slugs in extension code — non-Latin characters are preserved verbatim by the plugin and sanitize_title() would strip them.
  • The filter chain runs on every URL WordPress emits. Be conservative with additional filters at the same priority to avoid doubling or cancelling the prefix.
  • The slugs table stores history in old_slugs but the plugin does not auto-generate 301 redirects from them — that is on the rewrite layer.

Related Reading

  • Language Detection & Redirects — how the active language is chosen before URL filters run.
  • Rewrite Rules & Query Vars — the inbound side of the URL contract.
  • Translation Linking (trid system) — how element IDs are grouped across languages.

Product context and pricing are on the multi-language real estate website page.

WPEstate / WPResidence Translate Plugin

Related Articles

  • String Scanner — Developer Guide
  • The String Scanner
  • Gettext Pipeline & MO Files — Developer Guide
  • Gettext & MO Files — Making Translations Appear on the Front End

Help Categories

  • 18Agent, Agency & Developers
  • 5Blog Posts & Blog Lists
  • 38Elementor Shortcodes Built-In
  • 45FAQ
  • 15Footer
  • 5Getting Started
  • 37Header
  • 2IDX & MLSImport
  • 6Installation & Setup
  • 23Installation FAQ
  • 23Maps & Location Settings
  • 21Multi-Language - Third Party Plugins
  • 6Other Third party Plugins
  • 20Pages
  • 4Payments & Monetization
  • 20Property Lists, Categories & Archive
  • 37Property Pages & Layouts
  • 31Search & Filtering
  • 162Technical how to | Custom Code Required
  • 8Technical: Actions and filters
  • 6Technical: Child Theme
  • 86Theme Options & Global Settings
  • 7Translations & Languages
  • 16WPBakery Shortcodes
  • 50WPEstate / WPResidence Translate Plugin
  • 51WPResidence / WPEstate CRM
  • 50WPResidence 5.0 Documentation
  • 8WPResidence Elementor Studio

Join Us On

Powered by WP Estate - All Rights Reserved
  • WPRESIDENCE
  • Video Tutorials
  • Client Support
  • API