Skip Navigation

This thread is resolved. Here is a description of the problem and solution.

Problem:
Language switcher flag image missing alt attribute.
Solution:
Got to ../wp-content/plugins/sitepress-multilingual-cms/classes/language-switcher/ directory and edit the class-wpml-ls-model-build.php, line 190.
Remove this line:

$ret[ $code ]['flag_alt']   = ( $display_name || $display_native ) ? '' : $data['translated_name'];

And replace it with this line:

$ret[ $code ]['flag_alt']   = ( $display_name || $display_native ) ? $data['native_name'] : $data['translated_name'];

Relevant Documentation:
https://wpml.org/errata/missing-alt-tag-for-flag-image-in-a-language-switcher/

This is the technical support forum for WPML - the multilingual WordPress plugin.

Everyone can read, but only WPML clients can post here. WPML team is replying on the forum 6 days per week, 22 hours per day.

This topic contains 4 replies, has 2 voices.

Last updated by tomasJ-15 1 year, 7 months ago.

Assisted by: Eran Helzer.

Author Posts
May 27, 2023 at 1:55 pm #13726293

tomasJ-15

Hello,
SEO RankMath analyser shows for me results, that images are without ALT attributes. I have tried to fix it using this tutorial, but unsuccessfully https://wpml.org/errata/missing-alt-tag-for-flag-image-in-a-language-switcher/
Could you please help me to fix it? Thank You 🙂

<img class="wpml-ls-flag" src="hidden link" alt="" width="18" height="12">
<img class="wpml-ls-flag" src="hidden link" alt="" width="18" height="12">
<img class="wpml-ls-flag" src="hidden link" alt="" width="18" height="12">
<img class="wpml-ls-flag" src="hidden link" alt="" width="18" height="12">

May 29, 2023 at 8:41 am #13729793

Eran Helzer
Supporter

Languages: English (English ) Hebrew (עברית )

Timezone: Asia/Jerusalem (GMT+02:00)

Hi,

The documentation you followed is indeed what you need to do to achieve what you want. I followed it just now and it worked for me.

Can you please let me know if:

You followed the exact steps from the errata and it still didn't work, or if you had any trouble following it?
Your language switchers were added using WPML as is explained in our docs (https://wpml.org/documentation/getting-started-guide/language-setup/language-switcher-options/) or maybe you added it via some other method?
Let me know.

Regards,
Eran

May 29, 2023 at 12:55 pm #13732499

tomasJ-15

Dear Eran,
thank you for your reply, yes I have followed the exact steps from errata. What comes with language switcher, it appeared automatically when I activated WPML Plugin 🙂 Nothing done manually.
below you will find the class-wpml-ls-model-build.php content.

there is no line:

$ret[ $code ]['flag_alt'] = ( $display_name || $display_native ) ? '' : $data['translated_name'];

________________________________
<?php

class WPML_LS_Model_Build extends WPML_SP_User {

const LINK_CSS_CLASS = 'wpml-ls-link';

/* @var WPML_LS_Settings $settings */
private $settings;

/* @var WPML_Mobile_Detect $mobile_detect */
private $mobile_detect;

/* @var bool $is_touch_screen */
private $is_touch_screen = false;

/* @var string $css_prefix */
private $css_prefix;

private $allowed_vars = [
'languages' => 'array',
'current_language_code' => 'string',
'css_classes' => 'string',
'css_classes_link' => 'string',
'backward_compatibility' => 'array',
];

private $allowed_language_vars = [
'code' => 'string',
'url' => 'string',
'flag_url' => 'string',
'flag_title' => 'string',
'flag_alt' => 'string',
'native_name' => 'string',
'display_name' => 'string',
'is_current' => 'bool',
'css_classes' => 'string',
'db_id' => 'string',
'menu_item_parent' => 'mixed',
'is_parent' => 'bool',
'backward_compatibility' => 'array',
'flag_width' => 'int',
'flag_height' => 'int',
];

/**
* WPML_Language_Switcher_Render_Model constructor.
*
* @param WPML_LS_Settings $settings
* @param SitePress $sitepress
* @param string $css_prefix
*/
public function __construct( $settings, $sitepress, $css_prefix ) {
$this->settings = $settings;
$this->css_prefix = $css_prefix;
parent::__construct( $sitepress );
}

/**
* @param WPML_LS_Slot $slot
* @param array $template_data
*
* @return array
*/
public function get( $slot, $template_data = [] ) {
$vars = [];

$vars['current_language_code'] = $this->sitepress->get_current_language();
$vars['languages'] = $this->get_language_items( $slot, $template_data );
$vars['css_classes'] = $this->get_slot_css_classes( $slot );
$vars['css_classes_link'] = self::LINK_CSS_CLASS;

$vars = $this->add_backward_compatibility_to_wrapper( $vars, $slot );

return $this->sanitize_vars( $vars, $this->allowed_vars );
}

/**
* @param WPML_LS_Slot $slot
*
* @return string
*/
public function get_slot_css_classes( $slot ) {
$classes = [ $this->get_slot_css_main_class( $slot->group(), $slot->slug() ) ];

$classes[] = trim( $this->css_prefix, '-' );

if ( $this->sitepress->is_rtl( $this->sitepress->get_current_language() ) ) {
$classes[] = $this->css_prefix . 'rtl';
}

$classes = $this->add_user_agent_touch_device_classes( $classes );

/**
* Filter the css classes for the language switcher wrapper
* The wrapper is not available for menus
*
* @param array $classes
*/
$classes = apply_filters( 'wpml_ls_model_css_classes', $classes );

return implode( ' ', $classes );
}

/**
* @param string $group
* @param string $slug
*
* @return string
*/
public function get_slot_css_main_class( $group, $slug ) {
return $this->css_prefix . $group . '-' . $slug;
}

/**
* @return string
*/
public function get_css_prefix() {
return $this->css_prefix;
}

/**
* @param WPML_LS_Slot $slot
* @param array $template_data
*
* @return array
*/
private function get_language_items( $slot, $template_data ) {
$ret = [];

$get_ls_args = [
'skip_missing' => ! $this->settings->get_setting( 'link_empty' ),
];

if ( $slot->is_post_translations() ) {
$get_ls_args['skip_missing'] = true;
} elseif ( ( WPML_Root_Page::uses_html_root() || WPML_Root_Page::get_root_id() ) && WPML_Root_Page::is_current_request_root() ) {
$get_ls_args['skip_missing'] = false;
}

$flag_width = $slot->get( 'include_flag_width' );
$flag_height = $slot->get( 'include_flag_height' );

$languages = $this->sitepress->get_ls_languages( $get_ls_args );
$languages = is_array( $languages ) ? $languages : [];

if ( $languages ) {

foreach ( $languages as $code => $data ) {

$is_current_language = $code === $this->sitepress->get_current_language();

if ( ! $slot->get( 'display_link_for_current_lang' ) && $is_current_language ) {
continue;
}

$ret[ $code ] = [
'code' => $code,
'url' => $data['url'],
];

if ( $flag_width ) {
$ret[ $code ]['flag_width'] = $flag_width;
}
if ( $flag_height ) {
$ret[ $code ]['flag_height'] = $flag_height;
}

/* @deprecated Use 'wpml_ls_language_url' instead */
$ret[ $code ]['url'] = apply_filters( 'WPML_filter_link', $ret[ $code ]['url'], $data );

/**
* This filter allows to change the URL for each languages links in the switcher
*
* @param string $ret [ $code ]['url'] The language URL to be filtered
* @param array $data The language information
*/
$ret[ $code ]['url'] = apply_filters( 'wpml_ls_language_url', $ret[ $code ]['url'], $data );

$ret[ $code ]['url'] = $this->sitepress->get_wp_api()->is_admin() ? '#' : $ret[ $code ]['url'];

$css_classes = $this->get_language_css_classes( $slot, $code );

$display_native = $slot->get( 'display_names_in_native_lang' );
$display_name = $slot->get( 'display_names_in_current_lang' );

if ( $slot->get( 'display_flags' ) ) {
$ret[ $code ]['flag_url'] = $this->filter_flag_url( $data['country_flag_url'], $template_data );
$ret[ $code ]['flag_title'] = $data['native_name'];
$ret[ $code ]['flag_alt'] = ( $display_name || $display_native ) ? '' : $data['translated_name'];
}

if ( $display_native ) {
$ret[ $code ]['native_name'] = $data['native_name'];
}

if ( $display_name ) {
$ret[ $code ]['display_name'] = $data['translated_name'];
}

if ( $is_current_language ) {
$ret[ $code ]['is_current'] = true;
array_push( $css_classes, $this->css_prefix . 'current-language' );
}

if ( $slot->is_menu() ) {
$ret[ $code ]['db_id'] = $this->get_menu_item_id( $code, $slot );
$ret[ $code ]['menu_item_parent'] = $slot->get( 'is_hierarchical' ) && ! $is_current_language && $slot->get( 'display_link_for_current_lang' )
? $this->get_menu_item_id( $this->sitepress->get_current_language(), $slot ) : 0;
$ret[ $code ]['is_parent'] = $slot->get( 'is_hierarchical' ) && $is_current_language;

array_unshift( $css_classes, 'menu-item' );
array_push( $css_classes, $this->css_prefix . 'menu-item' );
}

$ret[ $code ]['css_classes'] = $css_classes;
}

$i = 1;
foreach ( $ret as &$lang ) {
if ( $i === 1 ) {
array_push( $lang['css_classes'], $this->css_prefix . 'first-item' );
}

if ( $i === count( $ret ) ) {
array_push( $lang['css_classes'], $this->css_prefix . 'last-item' );
}

$lang = $this->add_backward_compatibility_to_languages( $lang, $slot );

/**
* Filter the css classes for each language item
*
* @param array $lang ['css_classes']
*/
$lang['css_classes'] = apply_filters( 'wpml_ls_model_language_css_classes', $lang['css_classes'] );

$lang['css_classes'] = implode( ' ', $lang['css_classes'] );

$lang = $this->sanitize_vars( $lang, $this->allowed_language_vars );
$i++;
}

}

return $ret;
}

/**
* @param string $url
* @param array $template_data
*
* @return string
*/
private function filter_flag_url( $url, $template_data = [] ) {
$wp_upload_dir = wp_upload_dir();
$has_custom_flag = strpos( $url, $wp_upload_dir['baseurl'] . '/flags/' ) === 0 ? true : false;

if ( ! $has_custom_flag && ! empty( $template_data['flags_base_uri'] ) ) {
$url = trailingslashit( $template_data['flags_base_uri'] ) . basename( $url );

if ( isset( $template_data['flag_extension'] ) ) {
$old_ext = pathinfo( $url, PATHINFO_EXTENSION );
$url = preg_replace( '#' . $old_ext . '$#', $template_data['flag_extension'], $url, 1 );
}
}

return $url;
}

/**
* @param WPML_LS_Slot $slot
* @param string $code
*
* @return array
*/
private function get_language_css_classes( $slot, $code ) {
return [
$this->css_prefix . 'slot-' . $slot->slug(),
$this->css_prefix . 'item',
$this->css_prefix . 'item-' . $code,
];
}

/**
* @param array $classes
*
* @return array
*/
private function add_user_agent_touch_device_classes( $classes ) {

if ( is_null( $this->mobile_detect ) ) {
require_once WPML_PLUGIN_PATH . '/lib/mobile-detect.php';
$this->mobile_detect = new WPML_Mobile_Detect();
$this->is_touch_screen = $this->mobile_detect->isMobile() || $this->mobile_detect->isTablet();
}

if ( $this->is_touch_screen ) {
$classes[] = $this->css_prefix . 'touch-device';
}

return $classes;
}

/**
* @return bool
*/
private function needs_backward_compatibility() {
return (bool) $this->settings->get_setting( 'migrated' );
}

/**
* @param string $code
* @param WPML_LS_Slot $slot
*
* @return string
*/
private function get_menu_item_id( $code, $slot ) {
return $this->css_prefix . $slot->slug() . '-' . $code;
}

/**
* @param array $vars
* @param array $allowed_vars
*
* @return array
*/
private function sanitize_vars( $vars, $allowed_vars ) {
$sanitized = [];

foreach ( $allowed_vars as $allowed_var => $type ) {
if ( isset( $vars[ $allowed_var ] ) ) {
switch ( $type ) {
case 'array':
$sanitized[ $allowed_var ] = (array) $vars[ $allowed_var ];
break;

case 'string':
$sanitized[ $allowed_var ] = (string) $vars[ $allowed_var ];
break;

case 'int':
$sanitized[ $allowed_var ] = (int) $vars[ $allowed_var ];
break;

case 'bool':
$sanitized[ $allowed_var ] = (bool) $vars[ $allowed_var ];
break;

case 'mixed':
$sanitized[ $allowed_var ] = $vars[ $allowed_var ];
break;
}
}
}

return $sanitized;
}

/**
* @param array $lang
* @param WPML_LS_Slot $slot
*
* @return array
*/
private function add_backward_compatibility_to_languages( $lang, $slot ) {

if ( $this->needs_backward_compatibility() ) {

$is_current_language = isset( $lang['is_current'] ) && $lang['is_current'];

if ( $slot->is_menu() ) {

if ( $is_current_language ) {
array_unshift( $lang['css_classes'], 'menu-item-language-current' );
}

array_unshift( $lang['css_classes'], 'menu-item-language' );
}

if ( $slot->is_sidebar() || $slot->is_shortcode_actions() ) {

if ( $this->is_legacy_template( $slot->template(), 'list-vertical' )
|| $this->is_legacy_template( $slot->template(), 'list-horizontal' )
) {
array_unshift( $lang['css_classes'], 'icl-' . $lang['code'] );
$lang['backward_compatibility']['css_classes_a'] = $is_current_language ?
'lang_sel_sel' : 'lang_sel_other';
}

if ( $this->is_legacy_template( $slot->template(), 'dropdown' )
|| $this->is_legacy_template( $slot->template(), 'dropdown-click' )
) {

if ( $is_current_language ) {
$lang['backward_compatibility']['css_classes_a'] = 'lang_sel_sel icl-' . $lang['code'];
} else {
array_unshift( $lang['css_classes'], 'icl-' . $lang['code'] );
}
}
}
}

return $lang;
}

/**
* @param array $vars
* @param WPML_LS_Slot $slot
*
* @return mixed
*/
private function add_backward_compatibility_to_wrapper( $vars, $slot ) {

if ( $this->needs_backward_compatibility() ) {

if ( $slot->is_sidebar() || $slot->is_shortcode_actions() ) {

if ( $this->is_legacy_template( $slot->template(), 'list-vertical' )
|| $this->is_legacy_template( $slot->template(), 'list-horizontal' )
) {
$vars['backward_compatibility']['css_id'] = 'lang_sel_list';

if ( $this->is_legacy_template( $slot->template(), 'list-horizontal' ) ) {
$vars['css_classes'] = 'lang_sel_list_horizontal ' . $vars['css_classes'];
} else {
$vars['css_classes'] = 'lang_sel_list_vertical ' . $vars['css_classes'];
}
}

if ( $this->is_legacy_template( $slot->template(), 'dropdown' ) ) {
$vars['backward_compatibility']['css_id'] = 'lang_sel';
}

if ( $this->is_legacy_template( $slot->template(), 'dropdown-click' ) ) {
$vars['backward_compatibility']['css_id'] = 'lang_sel_click';
}
}

if ( $slot->is_post_translations() ) {
$vars['css_classes'] = 'icl_post_in_other_langs ' . $vars['css_classes'];
}

if ( $slot->is_footer() ) {
$vars['backward_compatibility']['css_id'] = 'lang_sel_footer';
}

$vars['backward_compatibility']['css_classes_flag'] = 'iclflag';
$vars['backward_compatibility']['css_classes_native'] = 'icl_lang_sel_native';
$vars['backward_compatibility']['css_classes_display'] = 'icl_lang_sel_translated';
$vars['backward_compatibility']['css_classes_bracket'] = 'icl_lang_sel_bracket';
}

return $vars;
}

/**
* @param string $template_slug
* @param string|null $type
*
* @return bool
*/
private function is_legacy_template( $template_slug, $type = null ) {
$templates = $this->settings->get_core_templates();
$ret = in_array( $template_slug, $templates, true );

if ( $ret && array_key_exists( $type, $templates ) ) {
$ret = $templates[ $type ] === $template_slug;
}

return $ret;
}
}

May 30, 2023 at 5:40 am #13735887

Eran Helzer
Supporter

Languages: English (English ) Hebrew (עברית )

Timezone: Asia/Jerusalem (GMT+02:00)

Hi,

It is good that you gave the entire file, I now see the issue. The line in question is line number 190. If you delete it entirely and replace it with the following line then the images will have ALT attributes:

$ret[ $code ]['flag_alt']   = ( $display_name || $display_native ) ? $data['native_name'] : $data['translated_name'];

If you need any further assistance, don't hesitate to ask.

Regards,
Eran