This article maps the taxonomy translation subsystem in the wpresidence-translate plugin (text domain wpr-translate). It covers the settings storage, the term duplication pipeline, the hierarchy synchronizer, admin list-table filters, and the adv6 search compatibility shim. For the broader plugin architecture underpinning a multi-language real estate website, see the plugin overview.
Files In Scope
| File | Role |
|---|---|
| includes/translation-taxonomies.php | Runtime: mode resolution, term duplication, hierarchy sync, term-list filters. |
| includes/admin/taxonomy-translation.php | Admin UI helpers: columns, language filter views, add-translation request handler, edit-term panel. |
| includes/admin/taxonomy-metabox-filters.php | Post-editor metabox filters (get_terms_args, terms_clauses, get_terms, get_the_terms). |
| includes/admin/views/settings-taxonomy.php | Settings screen view (posted to options.php, group wpr_translate_settings). |
| includes/adv6-terms-compat.php | Maps wp_estate_adv6_taxonomy_terms term IDs to the active language. |
Settings Storage
Everything lives under the single plugin option wpr_translate_settings:
array(
'taxonomy_modes' => array( $taxonomy_slug => 'translate' | 'not_translatable' ),
'taxonomy_auto_duplicate' => array( $taxonomy_slug => bool ),
...
)
Readers:
- wpr_translation_get_taxonomy_modes_settings()
- wpr_translation_get_taxonomy_mode( $taxonomy ) – default translate.
- wpr_translation_is_taxonomy_translatable( $taxonomy ) – main gate used everywhere.
- wpr_translation_get_taxonomy_auto_duplicate_settings()
- wpr_translation_is_taxonomy_auto_duplicate_enabled( $taxonomy )
The settings view at includes/admin/views/settings-taxonomy.php renders a row per public taxonomy and preserves hidden values for taxonomies not in the current form (so a temporarily-hidden CPT does not lose its preference).
Element Type & Translation Groups
Each taxonomy maps to a translation element type via wpr_translation_get_taxonomy_element_type( $taxonomy ). Translation groups are stored in {$wpdb->prefix}wpestate_translation_translations with columns including element_id, element_type, trid, language_code, source_language_code, original, and needs_update.
Lookup helpers (defined elsewhere in the plugin but called here):
- wpr_translation_get_translation_group( $term_id, $element_type ) – returns { trid, translations[code => id], source_language }.
- wpr_translate_resolve_original_term_id( $term_id, $taxonomy ) – canonical anchor via term meta.
- wpr_translate_lookup_translated_term_id_strict( $canonical_id, $taxonomy, $language ) – strict mapping with no fallback.
- wpr_translate_link_term_translation_meta( $source, $target, $language, $taxonomy ) – writes meta linkage (wpr_original_term_id and language code).
Term Duplication Pipeline
wpr_translation_duplicate_term_for_language( $source_term_id, $taxonomy, $target_language, $args ) is the entry point. It:
- Validates inputs and rejects non-translatable taxonomies.
- Resolves the canonical source via wpr_translate_resolve_original_term_id().
- Short-circuits if a translation already exists (meta-first lookup, then translation group).
- Resolves the translated parent term id for hierarchical taxonomies.
- Generates a unique slug with wp_unique_term_slug().
- Sets the global guard $GLOBALS[‘wpr_translation_creating_term_translation’] to suppress recursive created_term handling.
- Calls wp_insert_term(). On term_exists error it reuses the existing ID.
- Copies term meta via wpr_translation_duplicate_term_meta() and the legacy taxonomy_{term_id} option (resetting category_tagline).
- Links meta and writes a row to the translation table via wpr_translation_insert_translation_record().
Created / Edited / Deleted Term Hooks
| Action | Callback | What it does |
|---|---|---|
| created_term (pri 20) | wpr_translation_handle_created_term | Generates a new trid, inserts the source record, and fans out duplication to all other languages when auto-duplicate is enabled. |
| edited_term (pri 20) | wpr_translation_handle_edited_term | Reads wpr_translation_term_language and wpr_translation_term_source from POST (with nonce wpr_translation_term_language_{term_id}), updates the group, and syncs hierarchy. |
| delete_term (pri 20) | wpr_translation_handle_deleted_term | Removes the translation row; if the deleted term was the original, promotes another translation to original via translation_id update. |
Hierarchy Sync
wpr_translation_sync_term_translation_hierarchy( $term_id, $taxonomy ) loads the parent’s translation group, finds the per-language translated parent ID, and calls wp_update_term() on each sibling with the correct parent. The recursion guard $GLOBALS[‘wpr_translation_syncing_term_parents’] prevents re-entry through edited_term while the sync is running.
Front-End Term Filtering
wpr_translation_filter_terms_for_hidden_modes() hooks get_terms at priority 20. It:
- Skips admin, REST (wpr_translate_language_router_is_rest_request()), and non-translatable taxonomies.
- Resolves the current language via wpr_translate_get_current_language(), falls back to wpestate_get_current_language().
- For each term: resolves canonical id, then looks up the language-specific id (wpr_translate_lookup_translated_term_id_strict). For the default language, canonical is always used.
- For modes where untranslated terms should not be hidden (wpr_translation_should_hide_untranslated_terms()), falls back to canonical so output does not disappear.
- Rebuilds the result respecting $args[‘fields’] – supports all, ids, tt_ids, names, id=>parent, id=>name, id=>slug.
- Deduplicates by taxonomy|parent|lowercase(name) to avoid duplicate labels.
wpr_translation_force_terms_filters_args() hooks get_terms_args and forces suppress_filters = false when any queried taxonomy is translatable, so the filter above can actually run.
Admin List Table & Edit Screen
Registered in wpr_translation_bootstrap_taxonomy_admin():
add_action( 'admin_init', 'wpr_translation_setup_taxonomy_columns' ); add_action( 'admin_init', 'wpr_translation_handle_add_term_translation_request' ); add_action( 'current_screen', 'wpr_translation_register_taxonomy_language_filter_hooks' ); add_action( 'edit_term_form', 'wpr_translation_render_term_language_panel', 10, 2 );
The language filter injects per-language views into views_{screen_id} with a wpr_term_language query var. The add-translation handler nonces requests using wpr_translation_get_term_translation_nonce_action( $term_id, $taxonomy, $language ).
Post Editor Metabox Filters
includes/admin/taxonomy-metabox-filters.php registers filters on current_screen and uses the four-filter pattern documented at the top of the file:
get_terms_args -> high-level arg adjustments terms_clauses -> SQL JOIN/WHERE for language get_terms -> post-query list filtering get_the_terms -> per-post term list filtering
Advanced Search (adv6) Compatibility
wpr_translate_filter_adv6_terms_in_theme_options() in adv6-terms-compat.php hooks the WPResidence theme options and rewrites wp_estate_adv6_taxonomy_terms so that term IDs configured by admins for the advanced search tab are mapped to their current-language equivalents. This keeps “For Sale / For Rent” and similar action filters pointing at the right terms in every language, using the same wpr_translate_resolve_original_term_id and wpr_translate_lookup_translated_term_id_strict pair as the main filter.
Extension Points
- Add or remove translatable taxonomies by writing directly to wpr_translate_settings[‘taxonomy_modes’] in a migration.
- Skip a specific taxonomy from auto-duplication by setting its taxonomy_auto_duplicate entry to false.
- Use suppress_filters = true on get_terms / WP_Term_Query when you explicitly need the raw cross-language term list (e.g. admin exports).
- Override wpr_translate_lookup_translated_term_id_strict via a plugin if you need a custom resolver – it is called everywhere through function_exists() checks.
Gotchas
- The delete_term handler promotes a replacement only when the deleted term was the group’s original – non-original deletions simply drop the mapping row.
- Non-Latin slugs: wp_unique_term_slug() uses sanitize_title(), which transliterates. If you need to preserve the original script, pre-set a slug before calling wp_insert_term().
- The hierarchy sync only runs for taxonomies marked hierarchical in register_taxonomy().
- REST requests are intentionally skipped by the term filter – term assignment UIs must see the full cross-language term pool.
Related Reading
- Translation Linking (trid system) – the shared TRID mechanism used for both posts and terms.
- WP_Query Language Filtering – the sibling subsystem for posts.
- Database Schema – structure of the translations table.
Product context: multi-language real estate website.