*/
private $presets = array( 'minimal', 'soft', 'compact', 'floating', 'underline', 'segmented', 'borderless', 'glass', 'dark', 'flag-chip', 'capsule', 'solid', 'outline', 'muted', 'floating-popup', 'compact-flag-dropdown', 'language-card', 'labeled-language-button' );
/**
* Languages service.
*
* @var Languages
*/
private $languages;
/**
* Mapping repository.
*
* @var Mapping_Repository
*/
private $mapping;
/**
* Router.
*
* @var Router
*/
private $router;
/**
* Constructor.
*
* @param Languages $languages Languages service.
* @param Mapping_Repository $mapping Mapping repository.
* @param Router $router Router.
*/
public function __construct( Languages $languages, Mapping_Repository $mapping, Router $router ) {
$this->languages = $languages;
$this->mapping = $mapping;
$this->router = $router;
}
/**
* Register hooks.
*
* @return void
*/
public function init() {
add_shortcode( 'sitelang_switcher', array( $this, 'shortcode' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'register_assets' ) );
add_action( 'init', array( $this, 'register_block' ) );
add_action( 'wp', array( $this, 'maybe_add_theme_hook' ) );
add_action( 'wp_footer', array( $this, 'render_mobile_nav_switcher' ), 8 );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_shortcode_cleanup' ) );
}
/**
* Register frontend assets.
*
* @return void
*/
public function register_assets() {
wp_register_style( 'sitelang-frontend', SITELANG_URL . 'assets/css/frontend.css', array(), (string) filemtime( SITELANG_PATH . 'assets/css/frontend.css' ) );
wp_register_script( 'sitelang-frontend', SITELANG_URL . 'assets/js/frontend.js', array(), (string) filemtime( SITELANG_PATH . 'assets/js/frontend.js' ), true );
}
/**
* Register Gutenberg block.
*
* @return void
*/
public function register_block() {
wp_register_script(
'sitelang-switcher-block',
SITELANG_URL . 'assets/js/block.js',
array( 'wp-blocks', 'wp-element', 'wp-components', 'wp-server-side-render' ),
SITELANG_VERSION,
true
);
register_block_type(
'sitelang/switcher',
array(
'editor_script' => 'sitelang-switcher-block',
'render_callback' => array( $this, 'render_block' ),
'attributes' => array(
'mode' => array(
'type' => 'string',
'default' => 'dropdown',
),
'style' => array(
'type' => 'string',
'default' => 'minimal',
),
),
)
);
}
/**
* Attach switcher to configured theme hook.
*
* @return void
*/
public function maybe_add_theme_hook() {
$settings = $this->languages->get_settings();
if ( empty( $settings['hook_enabled'] ) || empty( $settings['hook_name'] ) ) {
return;
}
$hook = sanitize_key( $settings['hook_name'] );
add_action(
$hook,
function () {
$settings = $this->languages->get_settings();
$placement = ! empty( $settings['switcher_placement'] ) ? sanitize_html_class( $settings['switcher_placement'] ) : 'header-floating';
echo '
';
echo $this->render( array( 'placement' => $placement ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '
';
},
30
);
}
/**
* Render an opt-in switcher mount for theme mobile navigation drawers.
*
* The frontend script moves this element into Woodmart's mobile navigation
* or into a custom selector supplied by the administrator. It remains hidden
* when no compatible mobile navigation exists.
*
* @return void
*/
public function render_mobile_nav_switcher() {
if ( is_admin() || wp_doing_ajax() ) {
return;
}
$settings = $this->languages->get_settings();
if ( empty( $settings['mobile_switcher_enabled'] ) ) {
return;
}
$mode = isset( $settings['mobile_switcher_mode'] ) ? sanitize_key( $settings['mobile_switcher_mode'] ) : 'dropdown';
if ( ! in_array( $mode, array( 'dropdown', 'pills' ), true ) ) {
$mode = isset( $settings['switcher_mode'] ) ? sanitize_key( $settings['switcher_mode'] ) : 'dropdown';
}
$selector = isset( $settings['mobile_switcher_target'] ) ? sanitize_text_field( $settings['mobile_switcher_target'] ) : '';
$selector = $selector ? $selector : '.mobile-nav[role="navigation"], .mobile-nav';
echo '';
echo '
';
echo $this->render( array( 'mode' => $mode, 'placement' => 'mobile-nav' ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '
';
}
/**
* Shortcode callback.
*
* @param array $atts Shortcode attributes.
* @return string
*/
public function shortcode( $atts ) {
if ( is_admin() ) {
return '';
}
$atts = shortcode_atts(
array(
'mode' => '',
'style' => '',
'show_name' => '',
'show_flag' => '',
),
$atts,
'sitelang_switcher'
);
return $this->render( $atts );
}
/**
* Load cleanup for themes that print unprocessed header shortcodes in wp-admin.
*
* Some builders reuse frontend header content inside admin screens without
* running do_shortcode(). The shortcode should never be visible there.
*
* @return void
*/
public function enqueue_admin_shortcode_cleanup( $hook = '' ) {
$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
$site_lang_screen = in_array( $page, array( 'sitelang-wc', 'sitelang-wc-setup', 'sitelang-license' ), true )
|| ( $screen && in_array( (string) $screen->post_type, array( 'page', 'post', 'product', 'html_block', 'cms_block' ), true ) );
if ( ! $site_lang_screen ) {
return;
}
wp_enqueue_script( 'sitelang-admin-shortcode-cleanup', SITELANG_URL . 'assets/js/admin-shortcode-cleanup.js', array(), SITELANG_VERSION, true );
}
/**
* Block render callback.
*
* @param array $attributes Block attributes.
* @return string
*/
public function render_block( $attributes ) {
return $this->render( $attributes );
}
/**
* Render switcher.
*
* @param array $args Render args.
* @return string
*/
public function render( array $args = array() ) {
if ( is_admin() && ! wp_doing_ajax() ) {
return '';
}
$languages = (array) apply_filters( 'sitelang_frontend_languages', $this->languages->get_all() );
if ( count( $languages ) < 2 ) {
return '';
}
wp_enqueue_style( 'sitelang-frontend' );
wp_enqueue_script( 'sitelang-frontend' );
$settings = $this->languages->get_settings();
$mode = ! empty( $args['mode'] ) ? sanitize_key( $args['mode'] ) : ( $settings['switcher_mode'] ?? 'dropdown' );
$preset = ! empty( $args['style'] ) ? sanitize_key( $args['style'] ) : ( $settings['switcher_preset'] ?? 'minimal' );
$show_name = '' !== ( $args['show_name'] ?? '' ) ? $this->to_bool( $args['show_name'] ) : ! empty( $settings['switcher_show_name'] );
$show_flag = '' !== ( $args['show_flag'] ?? '' ) ? $this->to_bool( $args['show_flag'] ) : ! empty( $settings['switcher_show_flag'] );
$current = $this->languages->get_current();
if ( ! isset( $languages[ $current ] ) ) {
$current = $this->languages->get_default_code();
}
$style = $this->inline_style( $settings, $args );
if ( ! in_array( $preset, $this->presets, true ) ) {
$preset = 'minimal';
}
if ( 'language-card' === $preset ) {
return $this->render_language_card( $languages, $current, $style, $preset );
}
if ( 'pills' === $mode && ! in_array( $preset, array( 'floating-popup', 'compact-flag-dropdown', 'labeled-language-button' ), true ) ) {
return $this->render_pills( $languages, $current, $show_name, $show_flag, $style, $preset );
}
return $this->render_dropdown( $languages, $current, $show_name, $show_flag, $style, $preset );
}
/**
* Render dropdown mode.
*
* @param array> $languages Languages.
* @param string $current Current code.
* @param bool $show_name Show name.
* @param bool $show_flag Show flag.
* @param string $style Inline custom properties.
* @return string
*/
private function render_dropdown( array $languages, $current, $show_name, $show_flag, $style, $preset ) {
$current_language = isset( $languages[ $current ] ) ? $languages[ $current ] : reset( $languages );
$language_prefix = self::get_language_prefix( $current_language );
$flag_shape = in_array( $preset, array( 'flag-chip', 'language-card', 'labeled-language-button' ), true ) ? 'circular' : 'rectangular';
$trigger_flag_size = 'flag-chip' === $preset ? 'chip' : 'trigger';
$is_disclosure = in_array( $preset, array( 'floating-popup', 'compact-flag-dropdown', 'labeled-language-button' ), true );
$show_flag = in_array( $preset, array( 'floating-popup', 'compact-flag-dropdown', 'labeled-language-button' ), true ) || $show_flag;
self::$instance++;
$menu_id = 'sitelang-language-menu-' . self::$instance;
ob_start();
?>
> $languages Languages.
* @param string $current Current code.
* @param string $style Inline custom properties.
* @param string $preset Preset key.
* @return string
*/
private function render_language_card( array $languages, $current, $style, $preset ) {
ob_start();
?>
> $languages Languages.
* @param string $current Current code.
* @param bool $show_name Show name.
* @param bool $show_flag Show flag.
* @param string $style Inline custom properties.
* @return string
*/
private function render_pills( array $languages, $current, $show_name, $show_flag, $style, $preset ) {
$flag_shape = in_array( $preset, array( 'flag-chip', 'language-card', 'labeled-language-button' ), true ) ? 'circular' : 'rectangular';
$flag_size = 'flag-chip' === $preset ? 'chip' : 'trigger';
ob_start();
?>
$language Language row.
* @param bool $show_name Show name.
* @param bool $show_flag Show flag.
* @return string
*/
private function language_label_html( array $language, $show_name, $show_flag, $flag_shape = 'rectangular', $flag_size = 'menu' ) {
$code = ! empty( $language['code'] ) ? strtoupper( $language['code'] ) : '';
$label = $show_name && ! empty( $language['native_label'] ) ? $language['native_label'] : $code;
$html = '';
if ( $show_flag ) {
$html .= Flag_Assets::render( $language, $flag_shape, 'switcher', $flag_size );
}
$html .= '' . esc_html( $label ) . '';
return $html;
}
/**
* Build dropdown option label HTML.
*
* @param array $language Language row.
* @param bool $show_flag Show flag.
* @return string
*/
private function language_menu_label_html( array $language, $show_flag, $flag_shape = 'rectangular' ) {
$label = ! empty( $language['native_label'] ) ? $language['native_label'] : ( $language['code'] ?? '' );
$html = '';
if ( $show_flag ) {
$html .= Flag_Assets::render( $language, $flag_shape, 'switcher', 'menu' );
}
$html .= '';
return $html;
}
/**
* Return the word "Language" in the active language's native language.
*
* @param array $language Current language row.
* @return string
*/
public static function get_language_prefix( array $language ) {
$words = array(
'af' => 'Taal', 'sq' => 'Gjuha', 'am' => 'ቋንቋ', 'ar' => 'اللغة', 'hy' => 'Լեզու',
'az' => 'Dil', 'eu' => 'Hizkuntza', 'be' => 'Мова', 'bn' => 'ভাষা', 'bs' => 'Jezik',
'bg' => 'Език', 'ca' => 'Idioma', 'zh' => '语言', 'zh-tw' => '語言', 'hr' => 'Jezik',
'cs' => 'Jazyk', 'da' => 'Sprog', 'nl' => 'Taal', 'et' => 'Keel', 'fa' => 'زبان',
'fi' => 'Kieli', 'fr' => 'Langue', 'ka' => 'ენა', 'de' => 'Sprache', 'el' => 'Γλώσσα',
'en' => 'Language', 'gu' => 'ભાષા', 'he' => 'שפה', 'hi' => 'भाषा', 'hu' => 'Nyelv',
'is' => 'Tungumál', 'id' => 'Bahasa', 'ga' => 'Teanga', 'it' => 'Lingua', 'ja' => '言語',
'ko' => '언어', 'lv' => 'Valoda', 'lt' => 'Kalba', 'ms' => 'Bahasa', 'mr' => 'भाषा',
'no' => 'Språk', 'ps' => 'ژبه', 'pl' => 'Język', 'pt' => 'Idioma', 'pt-br' => 'Idioma',
'ro' => 'Limbă', 'ru' => 'Язык', 'sr' => 'Језик', 'sk' => 'Jazyk', 'sl' => 'Jezik',
'es' => 'Idioma', 'sw' => 'Lugha', 'sv' => 'Språk', 'ta' => 'மொழி', 'te' => 'భాష',
'th' => 'ภาษา', 'tr' => 'Dil', 'uk' => 'Мова', 'ur' => 'زبان', 'uz' => 'Til',
'vi' => 'Ngôn ngữ', 'cy' => 'Iaith',
);
$code = isset( $language['code'] ) ? strtolower( str_replace( '_', '-', sanitize_text_field( (string) $language['code'] ) ) ) : '';
return $words[ $code ] ?? __( 'Language', 'sitelang' );
}
/**
* Parse shortcode-style booleans safely.
*
* @param mixed $value Value.
* @return bool
*/
private function to_bool( $value ) {
return in_array( strtolower( (string) $value ), array( '1', 'true', 'yes', 'on' ), true );
}
/**
* Build custom property string.
*
* @param array $settings Settings.
* @return string
*/
private function inline_style( array $settings, array $args = array() ) {
$radius = isset( $settings['switcher_radius'] ) ? min( 32, absint( $settings['switcher_radius'] ) ) : 6;
$bg = ! empty( $settings['switcher_bg'] ) ? sanitize_hex_color( $settings['switcher_bg'] ) : '#ffffff';
$text = ! empty( $settings['switcher_text'] ) ? sanitize_hex_color( $settings['switcher_text'] ) : '#1f2937';
$accent = ! empty( $settings['switcher_accent'] ) ? sanitize_hex_color( $settings['switcher_accent'] ) : '#b68b2d';
$hover_bg = ! empty( $settings['switcher_hover_bg'] ) ? sanitize_hex_color( $settings['switcher_hover_bg'] ) : '#eaf3f5';
$hover_text = ! empty( $settings['switcher_hover_text'] ) ? sanitize_hex_color( $settings['switcher_hover_text'] ) : '#045f78';
return sprintf(
'--sitelang-bg:%1$s;--sitelang-text:%2$s;--sitelang-accent:%3$s;--sitelang-radius:%4$dpx;--sitelang-hover-bg:%5$s;--sitelang-hover-text:%6$s;',
$bg ? $bg : '#ffffff',
$text ? $text : '#1f2937',
$accent ? $accent : '#b68b2d',
$radius,
$hover_bg ? $hover_bg : '#eaf3f5',
$hover_text ? $hover_text : '#045f78'
);
}
}