WP Residence Help WP Residence Help

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

URL Structure & Permalinks — Developer Deep Dive

1 view 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.

Screenshot: phpMyAdmin view of the wp_wpestate_translation_slugs table schema

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.

30. 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

WP Residence Documentation

  • 01. Getting Started
    • How to Get Support
    • Get your buyer license code.
    • Use SSL / https
    • Server / Theme Requirements
  • 02. Installation & Setup
  • 03. Installation FAQ
  • 06. Search & Filtering
    • Advanced Search Display Settings
    • Advanced Search Form
    • Geolocation Search for Half Map
    • Save Search Theme Options
    • Advanced Search Colors
  • 09. Agent, Agency & Developers
  • 08. Property Pages & Layouts
  • 07. Property Lists, Categories & Archive
  • 13. WPResidence Elementor Studio
  • 10. Blog Posts & Blog List
  • 11. Shortcodes
    • Contact Form
    • Featured Agency/Developer
    • Membership Packages
    • Testimonials
    • Google Map with Property Marker
    • Listings per Agent, Agency or Developer
    • Display Categories
    • Agent List
    • Recent Items Slider
    • Recent items
    • List Properties or Articles by ID
    • Featured Agent
    • Featured Article
    • Featured Property
    • Login & Register Form
    • Icon Content Box Shortcode
  • 12. Widgets
  • 04. Theme Options & Global Settings
    • General Settings
    • User Types Settings
    • Appearance
    • Logos & Favicon
    • Header
    • Footer Style and Colors
    • Price & Currency
    • Property Custom Fields
    • Features & Amenities
    • Listing Labels
    • Theme Slider
    • Permalinks
    • Splash Page
    • Social & Contact
    • Map Settings
    • Pin Management
    • How read from file works
    • General Design Settings
    • Custom Colors Settings
    • Header Design & Colors
    • Mobile Menu Colors
    • User Dashboard Colors
    • Print PDF Design
    • Property, Agent, Blog Lists Design Settings
    • Sidebar Widget Design
    • Font management
    • How to add custom CSS
    • Custom Property Card Unit – Beta version
    • Email Management
    • Import & Export theme options
    • reCaptcha settings
    • YELP API Integration
    • iHomefinder Optima Express IDX
    • MEMBERSHIP & PAYMENT Settings
    • Property Submission Page
    • PayPal Setup
    • Stripe Setup
    • Wire Transfer Payment Method
  • 20. Translations & Languages
  • 26. FAQ
  • 10. Pages
  • 11. Header
  • 12. Footer
  • 05. Maps & Location Settings
  • 18. Payments & Monetization
  • Plugins
    • 19. Included Plugins
    • 22. Third Party Plugins – IDX Compatibility
    • 21. Third-Party Plugins – Multi-Language
    • 23. Third party Plugins – Other
  • Technical
    • 24. Technical how to | Custom Code Required
    • 25. Technical: Child Theme

Join Us On

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