'abdiel-multilingual-manager', 'url' => $domain, ]; return add_query_arg( $args, 'https://joseabdiel.com' . $path ); } private function admin_footer() { echo '
'; echo ''; } public function hooks() { add_action( 'admin_menu', [ $this, 'admin_menu' ] ); add_action( 'admin_menu', [ $this, 'reorder_submenu' ], 999 ); add_action( 'admin_init', [ $this, 'register' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'admin_assets' ] ); } public static function get_languages_option() { $opt = get_option( 'abdiel_amm_languages', [] ); if ( ! is_array( $opt ) ) { $opt = []; } $opt = wp_parse_args( $opt, [ 'default' => 'en', 'items' => [], ] ); if ( ! isset( $opt['items'] ) || ! is_array( $opt['items'] ) ) { $opt['items'] = []; } // Do not auto-insert a language; user will define languages. return $opt; } public static function get_settings_option() { $opt = get_option( 'abdiel_amm_settings', [] ); if ( ! is_array( $opt ) ) { $opt = []; } // Keep defaults stable and NEVER reference request variables here. return wp_parse_args( $opt, [ 'enabled_post_types' => [ 'page', 'post' ], 'switcher_mode' => 'shortcode', // shortcode|menu|floating 'switcher_show_name' => 1, 'switcher_show_icon' => 0, 'switcher_show_code' => 0, 'floating_position' => 'br', // tl|tr|bl|br 'delete_data_on_uninstall' => 0, // URL generation in the switcher. // none -> keep the native permalink // query -> add ?lang=xx // prefix -> /xx/... (requires rewrites) 'url_mode' => 'prefix', 'url_prefix_strategy' => 'per-language', // Optional SEO controls for prefix mode. 'prefix_force_canonical' => 0, 'prefix_force_redirect_301' => 0, // Menu integration / styling 'menu_location' => '', 'menu_term_id' => 0, 'menu_css_selector' => '', 'btn_bg' => '', 'btn_text' => '', 'btn_bg_hover' => '', 'btn_text_hover' => '', ] ); } public function admin_menu() { add_menu_page( __( 'Multilingual Manager', 'abdiel-multilingual-manager' ), __( 'Multilingual Manager', 'abdiel-multilingual-manager' ), 'manage_options', 'abdiel-amm', [ $this, 'render_languages_page' ], 'dashicons-translation', 58 ); add_submenu_page( 'abdiel-amm', __( 'Languages', 'abdiel-multilingual-manager' ), __( 'Languages', 'abdiel-multilingual-manager' ), 'manage_options', 'abdiel-amm', [ $this, 'render_languages_page' ] ); add_submenu_page( 'abdiel-amm', __( 'Settings', 'abdiel-multilingual-manager' ), __( 'Settings', 'abdiel-multilingual-manager' ), 'manage_options', 'abdiel-amm-settings', [ $this, 'render_settings_page' ] ); add_submenu_page( 'abdiel-amm', __( 'Translate', 'abdiel-multilingual-manager' ), __( 'Translate (coming soon)', 'abdiel-multilingual-manager' ), 'manage_options', 'abdiel-amm-translate', [ $this, 'render_translate_page' ] ); add_submenu_page( 'abdiel-amm', __( 'Contact', 'abdiel-multilingual-manager' ), __( 'Contact', 'abdiel-multilingual-manager' ), 'manage_options', 'abdiel-amm-contact', [ $this, 'render_contact_page' ] ); } /** * Sanitize a CSS color value while allowing common formats: * - hex (#fff, #ffffff) * - rgb()/rgba() * - hsl()/hsla() * - transparent / currentColor * - var(--token) */ private static function sanitize_css_color_value( $value ) { $value = is_string( $value ) ? trim( wp_unslash( $value ) ) : ''; if ( $value === '' ) { return ''; } // Common keywords. $lower = strtolower( $value ); if ( in_array( $lower, [ 'transparent', 'currentcolor', 'inherit', 'initial', 'unset' ], true ) ) { return $lower === 'currentcolor' ? 'currentColor' : $lower; } // HEX if ( preg_match( '/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/', $value ) ) { return $value; } // rgb()/rgba() if ( preg_match( '/^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}(\s*,\s*(0|1|0?\.\d+)\s*)?\)$/i', $value ) ) { return $value; } // hsl()/hsla() if ( preg_match( '/^hsla?\(\s*\d{1,3}\s*,\s*\d{1,3}%\s*,\s*\d{1,3}%(\s*,\s*(0|1|0?\.\d+)\s*)?\)$/i', $value ) ) { return $value; } // CSS variables. if ( preg_match( '/^var\(\s*--[a-zA-Z0-9_-]+\s*(,\s*[^\)]+)?\)$/', $value ) ) { return $value; } return ''; } public function register() { register_setting( 'abdiel_amm_languages_group', 'abdiel_amm_languages', [ 'type' => 'array', 'sanitize_callback' => [ $this, 'sanitize_languages' ], 'default' => self::get_languages_option(), ] ); register_setting( 'abdiel_amm_settings_group', 'abdiel_amm_settings', [ 'type' => 'array', 'sanitize_callback' => [ $this, 'sanitize_settings' ], 'default' => self::get_settings_option(), ] ); } public function admin_assets( $hook ) { if ( false === strpos( $hook, 'abdiel-amm' ) ) { return; } wp_enqueue_media(); wp_enqueue_style( 'abdiel-amm-admin', \ABDIEL_AMM_URL . 'assets/admin.css', [], ABDIEL_AMM_VERSION ); wp_enqueue_script( 'abdiel-amm-admin', \ABDIEL_AMM_URL . 'assets/admin.js', [ 'jquery' ], ABDIEL_AMM_VERSION, true ); } public function sanitize_languages( $input ) { if ( ! current_user_can( 'manage_options' ) ) { return self::get_languages_option(); } if ( ! is_array( $input ) ) { $input = []; } $default = isset( $input['default'] ) ? sanitize_key( $input['default'] ) : ''; $items = isset( $input['items'] ) && is_array( $input['items'] ) ? $input['items'] : []; $clean_items = []; foreach ( $items as $code => $row ) { $code = sanitize_key( $code ); if ( empty( $code ) ) { continue; } $clean_items[ $code ] = [ 'name' => isset( $row['name'] ) ? sanitize_text_field( $row['name'] ) : strtoupper( $code ), 'code' => $code, 'locale' => isset( $row['locale'] ) ? sanitize_text_field( $row['locale'] ) : '', 'icon' => isset( $row['icon'] ) ? absint( $row['icon'] ) : 0, 'active' => isset( $row['active'] ) ? (int) (bool) $row['active'] : 0, 'order' => isset( $row['order'] ) ? absint( $row['order'] ) : 0, ]; } // If no languages yet, keep empty (setup not complete). if ( empty( $clean_items ) ) { return [ 'default' => '', 'items' => [] ]; } // Ensure at least one active language. $active_count = 0; foreach ( $clean_items as $row ) { if ( ! empty( $row['active'] ) ) { $active_count++; } } if ( $active_count === 0 && ! empty( $clean_items ) ) { $first = array_key_first( $clean_items ); $clean_items[ $first ]['active'] = 1; } // Default must exist and be active; otherwise pick first active. if ( empty( $clean_items[ $default ] ) || empty( $clean_items[ $default ]['active'] ) ) { foreach ( $clean_items as $code => $row ) { if ( ! empty( $row['active'] ) ) { $default = $code; break; } } } return [ 'default' => $default, 'items' => $clean_items, ]; } public function sanitize_settings( $input ) { if ( ! current_user_can( 'manage_options' ) ) { return self::get_settings_option(); } if ( ! is_array( $input ) ) { $input = []; } $enabled_post_types = []; if ( isset( $input['enabled_post_types'] ) && is_array( $input['enabled_post_types'] ) ) { foreach ( $input['enabled_post_types'] as $pt ) { $pt = sanitize_key( $pt ); if ( post_type_exists( $pt ) ) { $enabled_post_types[] = $pt; } } } if ( empty( $enabled_post_types ) ) { $enabled_post_types = [ 'page', 'post' ]; } $mode = isset( $input['switcher_mode'] ) ? sanitize_key( $input['switcher_mode'] ) : 'shortcode'; if ( ! in_array( $mode, [ 'shortcode', 'menu', 'floating' ], true ) ) { $mode = 'shortcode'; } $pos = isset( $input['floating_position'] ) ? sanitize_key( $input['floating_position'] ) : 'br'; $url_mode = isset( $input['url_mode'] ) ? sanitize_key( $input['url_mode'] ) : 'prefix'; if ( ! in_array( $url_mode, [ 'none', 'query', 'prefix' ], true ) ) { $url_mode = 'none'; } if ( ! in_array( $pos, [ 'tl', 'tr', 'bl', 'br' ], true ) ) { $pos = 'br'; } // Flush rewrites when switching into/out of prefix mode. $prev = self::get_settings_option(); if ( isset( $prev['url_mode'] ) && $prev['url_mode'] !== $url_mode ) { set_transient( 'abdiel_amm_flush_rewrites', 1, 30 ); } return [ 'enabled_post_types' => $enabled_post_types, 'switcher_mode' => $mode, 'switcher_show_name' => ! empty( $input['switcher_show_name'] ) ? 1 : 0, 'switcher_show_icon' => ! empty( $input['switcher_show_icon'] ) ? 1 : 0, 'switcher_show_code' => ! empty( $input['switcher_show_code'] ) ? 1 : 0, 'floating_position' => $pos, 'delete_data_on_uninstall' => ! empty( $input['delete_data_on_uninstall'] ) ? 1 : 0, 'url_mode' => $url_mode, 'url_prefix_strategy' => 'per-language', // Prefix-mode SEO behavior (optional). 'prefix_force_canonical' => ! empty( $input['prefix_force_canonical'] ) ? 1 : 0, 'prefix_force_redirect_301' => ! empty( $input['prefix_force_redirect_301'] ) ? 1 : 0, 'menu_location' => isset( $input['menu_location'] ) ? sanitize_key( $input['menu_location'] ) : '', 'menu_term_id' => isset( $input['menu_term_id'] ) ? absint( $input['menu_term_id'] ) : 0, 'menu_css_selector' => isset( $input['menu_css_selector'] ) ? sanitize_text_field( wp_unslash( $input['menu_css_selector'] ) ) : '', 'btn_bg' => isset( $input['btn_bg'] ) ? self::sanitize_css_color_value( $input['btn_bg'] ) : '', 'btn_text' => isset( $input['btn_text'] ) ? self::sanitize_css_color_value( $input['btn_text'] ) : '', 'btn_bg_hover' => isset( $input['btn_bg_hover'] ) ? self::sanitize_css_color_value( $input['btn_bg_hover'] ) : '', 'btn_text_hover' => isset( $input['btn_text_hover'] ) ? self::sanitize_css_color_value( $input['btn_text_hover'] ) : '', ]; } private function sorted_languages( $langs ) { $items = $langs['items']; uasort( $items, function( $a, $b ) { return (int) $a['order'] <=> (int) $b['order']; } ); $langs['items'] = $items; return $langs; } public function render_languages_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $langs = self::get_languages_option(); $langs = $this->sorted_languages( $langs ); $opt = $langs; ?>

$row ) : ?>
/>
'width:24px;height:24px;border-radius:4px;' ] ); endif; ?>

and SEO hints.', 'abdiel-multilingual-manager' ); ?>


Españoleses_ES0
Englishenen_US1
Portuguêsptpt_PT / pt_BR2
Italianoitit_IT3
Françaisfrfr_FR4
Deutschdede_DE5
admin_footer(); ?>
true ], 'objects' ); ?>

admin_footer(); ?>

What this feature will do

This section will allow you to generate and manage translations directly from WordPress.

Status: Coming soon. We are testing a private beta build.

Request early access You can contact us to join the beta and test the translation workflow.

admin_footer(); ?>
tracking_url( '/' ); $donate = $this->tracking_url( '/donate/' ); // Optional banner (user can upload their own image into assets/img/). $banner_candidates = [ 'assets/img/abdiel-banner.webp', 'assets/img/banner-abdiel.webp', 'assets/img/abdiel-banner.png', 'assets/img/abdiel-banner.jpg', 'assets/img/abdiel-banner.svg', ]; $banner_url = ''; foreach ( $banner_candidates as $rel ) { if ( file_exists( \ABDIEL_AMM_DIR . $rel ) ) { $banner_url = \ABDIEL_AMM_URL . $rel; break; } } ?>

admin_footer(); ?>