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'));
add_action('template_redirect', array($this, 'maybe_redirect_to_translation'));
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;
}
$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;
}
public function maybe_redirect_to_translation()
{
$default_lang = $this->settings['default_lang'];
$desired_lang = $this->current_lang;
// Special handling for the front page (homepage)
if (is_front_page() && $this->has_explicit_lang_preference) {
$front_page_id = get_option('page_on_front');
if ($front_page_id) {
$front_page_lang = get_post_meta($front_page_id, '_ai_lang', true) ?: $default_lang;
if ($desired_lang !== $front_page_lang) {
$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) {
wp_safe_redirect(get_permalink($translated->ID), 302);
exit;
}
}
}
}
return;
}
if (!is_singular()) {
return;
}
global $post;
if (!$post) {
return;
}
$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;
}
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;
}
$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;
}
}