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;
}
}