settings = AIBUI_Translation_Settings::get_settings(); $this->current_lang = $this->determine_current_language(); if (is_admin()) { add_filter('manage_edit-post_columns', array($this, 'add_language_column')); add_filter('manage_edit-page_columns', array($this, 'add_language_column')); add_filter('manage_edit-wp_template_part_columns', array($this, 'add_language_column')); add_action('manage_post_posts_custom_column', array($this, 'render_language_column'), 10, 2); add_action('manage_page_posts_custom_column', array($this, 'render_language_column'), 10, 2); add_action('manage_wp_template_part_posts_custom_column', array($this, 'render_language_column'), 10, 2); add_action('restrict_manage_posts', array($this, 'add_language_filter')); add_action('pre_get_posts', array($this, 'filter_admin_by_language')); add_action('admin_head', array($this, 'print_admin_styles')); // Ensure "View" links in admin (list + editor) include the correct ai_lang add_filter('post_type_link', array($this, 'add_lang_query_to_admin_view_link'), 10, 2); add_filter('post_link', array($this, 'add_lang_query_to_admin_view_link'), 10, 3); add_filter('page_link', array($this, 'add_lang_query_to_admin_view_link'), 10, 3); } else { add_filter('query_vars', array($this, 'register_query_var')); // Handle homepage redirect early, before WP decides what to show add_action('template_redirect', array($this, 'maybe_redirect_homepage'), 1); add_action('template_redirect', array($this, 'maybe_redirect_to_translation'), 10); add_action('pre_get_posts', array($this, 'filter_front_queries')); add_action('wp_head', array($this, 'output_hreflang_tags'), 5); add_filter('language_attributes', array($this, 'filter_language_attributes'), 10, 2); add_filter('pre_render_block', array($this, 'maybe_swap_template_part'), 10, 2); } } public function get_current_language() { return $this->current_lang; } public function filter_language_attributes($output, $doctype) { if ($doctype !== 'html') { return $output; } $lang = $this->current_lang; $dir = in_array($lang, array('ar', 'he', 'fa'), true) ? 'rtl' : 'ltr'; return sprintf('lang="%s" dir="%s"', esc_attr($lang), esc_attr($dir)); } public function get_default_language() { return $this->settings['default_lang']; } public function is_switcher_enabled() { return !empty($this->settings['enable_switcher']); } public function get_available_languages() { return $this->settings['available_langs']; } public function get_settings() { return $this->settings; } /** * In the admin, make sure "View" links include the post language as ?ai_lang=... * so that designers land on the matching front-end version and are not redirected. */ public function add_lang_query_to_admin_view_link($permalink, $post, $leavename = null) { if (!is_admin() || empty($post)) { return $permalink; } // $post may be an object or an ID depending on the filter (post_link/page_link/post_type_link) if (!is_object($post)) { $post = get_post($post); } if (!$post) { return $permalink; } // Only append ai_lang if this post actually belongs to a translation group // and has at least one other translation. Otherwise, keep the URL clean. $group = get_post_meta($post->ID, '_ai_translation_group', true); if (!$group) { return $permalink; } $translations = $this->get_group_posts($group, get_post_type($post)); if (empty($translations) || count($translations) <= 1) { // No real translations available for this post return $permalink; } $default_lang = $this->settings['default_lang']; $lang = get_post_meta($post->ID, '_ai_lang', true) ?: $default_lang; // Avoid duplicating the parameter if it's already there. $permalink = remove_query_arg('ai_lang', $permalink); return add_query_arg('ai_lang', $lang, $permalink); } private function determine_current_language() { $available = $this->settings['available_langs']; $default = $this->settings['default_lang']; $lang = $default; if (isset($_GET['ai_lang'])) { $candidate = sanitize_text_field(wp_unslash($_GET['ai_lang'])); if (in_array($candidate, $available, true)) { $lang = $candidate; $this->has_explicit_lang_preference = true; setcookie('aibui_lang', $lang, time() + MONTH_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN); } } elseif (isset($_COOKIE['aibui_lang'])) { $candidate = sanitize_text_field(wp_unslash($_COOKIE['aibui_lang'])); if (in_array($candidate, $available, true)) { $lang = $candidate; $this->has_explicit_lang_preference = true; } } return $lang; } /** Admin columns & filters */ public function add_language_column($columns) { $columns['aibui_translations'] = __('Translations', 'ai-builder'); return $columns; } public function render_language_column($column, $post_id) { if ($column !== 'aibui_translations') { return; } $default = $this->settings['default_lang']; $current_lang = get_post_meta($post_id, '_ai_lang', true) ?: $default; $current_label = AIBUI_Translation_Handler::SUPPORTED_LANGUAGES[$current_lang] ?? strtoupper($current_lang); $items = array( sprintf('%s', esc_html($current_label)) ); $group = get_post_meta($post_id, '_ai_translation_group', true); if ($group) { $translations = $this->get_group_posts($group, get_post_type($post_id)); foreach ($translations as $translation) { if ((int) $translation->ID === (int) $post_id) { continue; } $lang = get_post_meta($translation->ID, '_ai_lang', true) ?: $default; $label = AIBUI_Translation_Handler::SUPPORTED_LANGUAGES[$lang] ?? strtoupper($lang); $items[] = sprintf( '%s', esc_url(get_edit_post_link($translation->ID)), esc_html($label) ); } } echo implode(' | ', $items); } public function print_admin_styles() { $screen = get_current_screen(); if (!$screen || !in_array($screen->id, array('edit-page', 'edit-post', 'edit-wp_template_part'), true)) { return; } ?> is_main_query()) { return; } global $pagenow; if (!in_array($pagenow, array('edit.php'), true)) { return; } $lang = isset($_GET['aibui_lang']) ? sanitize_text_field(wp_unslash($_GET['aibui_lang'])) : ''; if ($lang === '') { return; } $meta_query = (array) $query->get('meta_query'); $meta_query[] = array( 'key' => '_ai_lang', 'value' => $lang, 'compare' => '=' ); $query->set('meta_query', $meta_query); } /** Front language handling */ public function register_query_var($vars) { $vars[] = 'ai_lang'; return $vars; } /** * Handle homepage translation redirect * This runs early to catch the homepage case before WP shows the wrong content */ public function maybe_redirect_homepage() { // Only process if we have an explicit language preference if (!$this->has_explicit_lang_preference) { return; } // Check if we're on the site root (homepage) $request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; $parsed = parse_url($request_uri); $path = isset($parsed['path']) ? trim($parsed['path'], '/') : ''; // If path is empty or just the WordPress subdirectory, we're on homepage $home_path = trim(parse_url(home_url(), PHP_URL_PATH) ?: '', '/'); $is_homepage = ($path === $home_path || $path === ''); if (!$is_homepage) { return; } $default_lang = $this->settings['default_lang']; $desired_lang = $this->current_lang; // Get the front page ID from WordPress settings $show_on_front = get_option('show_on_front'); $front_page_id = get_option('page_on_front'); // If WordPress is set to show a static page as homepage if ($show_on_front === 'page' && $front_page_id) { $front_page_lang = get_post_meta($front_page_id, '_ai_lang', true) ?: $default_lang; // If desired language matches the front page language, no redirect needed // Just ensure we stay on the homepage (the pre_get_posts hook will handle forcing the page) if ($desired_lang === $front_page_lang) { return; } // Find the translation of the front page $group = get_post_meta($front_page_id, '_ai_translation_group', true); if ($group) { $translated = $this->get_translation_in_lang($group, 'page', $desired_lang); if ($translated) { // Redirect to the translated page wp_safe_redirect(get_permalink($translated->ID), 302); exit; } } } // If show_on_front is 'posts' (blog mode), or no static page is set, // look for a page marked as homepage translation for the desired language // This handles the case where the site uses blog mode but has translated "landing" pages // But skip this for default language (let WordPress show the blog/homepage normally) if ($desired_lang !== $default_lang) { $homepage_translation = $this->find_homepage_for_language($desired_lang); if ($homepage_translation) { wp_safe_redirect(get_permalink($homepage_translation->ID), 302); exit; } } } /** * Find a page that serves as homepage for a specific language * Looks for pages with _ai_lang matching and _ai_is_homepage = 1 * Or pages that are translations of the default homepage */ private function find_homepage_for_language($lang) { $default_lang = $this->settings['default_lang']; // If looking for default language, don't redirect (stay on blog/homepage) if ($lang === $default_lang) { return null; } // First, try to find a page explicitly marked as homepage for this language $query = new WP_Query(array( 'post_type' => 'page', 'post_status' => 'publish', 'posts_per_page' => 1, 'meta_query' => array( 'relation' => 'AND', array( 'key' => '_ai_lang', 'value' => $lang, 'compare' => '=' ), array( 'key' => '_ai_is_homepage', 'value' => '1', 'compare' => '=' ) ) )); if ($query->have_posts()) { return $query->posts[0]; } // Fallback: find a page that is a translation of the page_on_front (if any) $front_page_id = get_option('page_on_front'); if ($front_page_id) { $group = get_post_meta($front_page_id, '_ai_translation_group', true); if ($group) { return $this->get_translation_in_lang($group, 'page', $lang); } } return null; } public function maybe_redirect_to_translation() { if (!is_singular()) { return; } global $post; if (!$post) { return; } $default_lang = $this->settings['default_lang']; $post_lang = get_post_meta($post->ID, '_ai_lang', true) ?: $default_lang; // If the user has no explicit language preference (no GET, no cookie), // and lands directly on a page, we treat that page's language as their // preference and simply store it in the cookie without redirecting. if (!$this->has_explicit_lang_preference) { setcookie('aibui_lang', $post_lang, time() + MONTH_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN); return; } $desired_lang = $this->current_lang; if ($desired_lang === $post_lang) { return; } $group = get_post_meta($post->ID, '_ai_translation_group', true); if (!$group) { return; } $translated = $this->get_translation_in_lang($group, get_post_type($post), $desired_lang); if ($translated) { wp_safe_redirect(get_permalink($translated->ID), 302); exit; } } public function filter_front_queries($query) { if (is_admin() || !$query->is_main_query()) { return; } // Force homepage detection when on root with ?ai_lang= parameter $request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; $parsed = parse_url($request_uri); $path = isset($parsed['path']) ? trim($parsed['path'], '/') : ''; $home_path = trim(parse_url(home_url(), PHP_URL_PATH) ?: '', '/'); $is_homepage = ($path === $home_path || $path === ''); if ($is_homepage && isset($_GET['ai_lang'])) { $show_on_front = get_option('show_on_front'); $front_page_id = get_option('page_on_front'); // If WordPress is set to show a static page as homepage if ($show_on_front === 'page' && $front_page_id) { // Force the query to show the front page $query->set('page_id', $front_page_id); $query->is_home = false; $query->is_page = true; $query->is_singular = true; } } $current_lang = $this->current_lang; $default_lang = $this->settings['default_lang']; if ($current_lang === $default_lang) { return; } if ($query->is_singular()) { return; } $meta_query = (array) $query->get('meta_query'); $meta_query[] = array( 'key' => '_ai_lang', 'value' => $current_lang, 'compare' => '=' ); $query->set('meta_query', $meta_query); } /** Hreflang output */ public function output_hreflang_tags() { if (!is_singular()) { return; } global $post; if (!$post) { return; } $group = get_post_meta($post->ID, '_ai_translation_group', true); if (!$group) { return; } $translations = $this->get_group_posts($group, get_post_type($post)); if (empty($translations)) { return; } $links = array(); foreach ($translations as $translation) { $lang = get_post_meta($translation->ID, '_ai_lang', true); if (!$lang) { continue; } $links[$lang] = get_permalink($translation->ID); } if (empty($links)) { return; } foreach ($links as $lang => $url) { printf( '' . "\n", esc_attr($lang), esc_url($url) ); } if (isset($links[$this->settings['default_lang']])) { printf( '' . "\n", esc_url($links[$this->settings['default_lang']]) ); } } /** Helpers */ public function get_translation_in_lang($group, $post_type, $lang) { $posts = $this->get_group_posts($group, $post_type); foreach ($posts as $post) { $post_lang = get_post_meta($post->ID, '_ai_lang', true); if ($post_lang === $lang) { return $post; } } return null; } public function get_group_posts($group, $post_type) { $cache_key = 'aibui_group_' . md5($group . $post_type); $cached = wp_cache_get($cache_key, 'aibui_translations'); if ($cached !== false) { return $cached; } $posts = get_posts(array( 'post_type' => $post_type, 'posts_per_page' => -1, 'post_status' => array('publish', 'draft', 'pending', 'future'), 'meta_query' => array( array( 'key' => '_ai_translation_group', 'value' => $group, 'compare' => '=' ) ) )); wp_cache_set($cache_key, $posts, 'aibui_translations', MINUTE_IN_SECONDS); return $posts; } public function maybe_swap_template_part($pre_render, $block) { if (!is_array($block) || ($block['blockName'] ?? '') !== 'core/template-part') { return $pre_render; } $slug = $block['attrs']['slug'] ?? ''; if (!$slug) { return $pre_render; } $theme = $block['attrs']['theme'] ?? wp_get_theme()->get_stylesheet(); $base_part = $this->get_template_part_post($slug, $theme); if (!$base_part) { return $pre_render; } $group = get_post_meta($base_part->ID, '_ai_translation_group', true); if (!$group) { return $pre_render; } $translated = $this->get_translation_in_lang($group, 'wp_template_part', $this->current_lang); if (!$translated) { return $pre_render; } return do_blocks($translated->post_content); } private function get_template_part_post($slug, $theme) { $parts = get_posts(array( 'post_type' => 'wp_template_part', 'name' => $slug, 'posts_per_page' => 1, 'post_status' => array('publish', 'draft', 'pending', 'future'), 'tax_query' => array( array( 'taxonomy' => 'wp_theme', 'field' => 'name', 'terms' => $theme, ) ) )); return $parts ? $parts[0] : null; } }