How to setup WPML custom lang selector in your divi child theme

  1. Copy the header.php, functions.php and related styles with wpml from a theme already having this functionality
    • header.php should have 2 calls to same gbili_lang_sel,
    • functions.php should have 2 functions defining (gbili_lang_sel...),
    • styles are either in styles.css or Divi > Theme Options, make sure z-index : 1000
  2. Make sure to add in Divi > Theme Options > RSS : http://top, it corresponds to the location the menu should be printed to
  3. Start translating pages, and the menu should appear once there are enough
    • don't forget to synchronize the menus in Appearance > Menus > Synchronize menus or in WPML > Synchronize menus
      • There is no problem in translating some menu elements by clicking the top flag while in Appearance > Menus

Divi Options

Inside Wordpress Admin > Divi > Theme Options and General tab, you can scroll down to RSS url field text, and write secondary or top, or whatever you string you have passed as first param to gbili_lang_sel('my_string'). By putting say my_string in the divi RSS option text field, you are telling gbili_lang_sel('my_string') to print the menu on that call. And to not print the menu on any other type of call. Ex: gbili_lang_sel('other_string') will not print the menu, and gbili_lang_sel() neither. Whereas if you do not pass a string in the Divi > Theme Options RSS text field, then only calls to gbili_lang_sel() will return the menu.

functions.php

There are two functions that allow you inject the WPML lang selector menu into your theme. There is no need to tell WPML to use custom lang selector. Once you make a cal to do_action(wpml_add_language_selector) divi already knows.

/**
 * Capture WPML output and modify it
 * @Warning this is based on divi_rss_url
 * Call gbili_lang_sel('some_name') from anywhere
 * in your theme, to get a WPML menu reformated
 * as an html unordered list with EN | FR
 * @param callerLocation string or false by default
 * if not provided, this function will return the menu string
 * if provided, the value must match Divi RSS Theme options field
 * without the http:// which is removed by gbili_lang_sel_location_option()
 * because Divi Rss field will prepend it too
 * @return string with the html menu or empty string
 * depending on callerLocation param value
 */
function gbili_lang_sel($callerLocation=false) {
    ob_start();

    // Prints the wpml language selector
    // We capture the output with ob_start()
    do_action('wpml_add_language_selector');

    // Replace the Languages by ISO - 3 codes
    $gbili_lang_sel = ob_get_clean();
    $gbili_lang_sel = str_replace(
        ['Anglais', 'English', 'Français', 'French'],
        ['EN', 'EN', 'FR', 'FR'],
        $gbili_lang_sel
    );

    // Generate a language selector menu
    $gbili_lang_sel = str_replace('</li><li', '</li><li class="wpml-ls-slot-shortcode_actions wpml-ls-item wpml-ls-item-sep"><span class="wpml-ls-native icl_lang_sel_native">|</span></li><li', $gbili_lang_sel); //place a vertical separator

    // Return the menu only if param matches rrs divi option
    // else return empty string
    if ($callerLocation === gbili_lang_sel_location_option()) {
        return $gbili_lang_sel;
    } else {
        return '';
    }
}

/**
 * Get the Divi RSS Theme option Field,
 * @return string and remove http:// or false if RSS field not set
 */
function gbili_lang_sel_location_option() {
    $option = et_get_option('divi_rss_url', false);
    if (is_string($option)) {
        $option = substr($option, strlen('http://'));
    }
    return $option;
}

header.php

You need to insert two lines in the header.php of your child theme. This is to be able to support both menu layouts Default and Center.

Secondary Menu (for Default Menu Type)

Find the secondary menu closing tab and just before it, add the call to gbili_lang_sel('secondary'):

<?= gbili_lang_sel('secondary');?>
</div> <!-- #et-secondary-menu -->

The lang menu will only show op if the secondary menu is being printed. By default it is not. So to for Divi to print the secondary, you have to tell it to print stuff that it thinks is on the secondary menu (as for example the Social icons). To do that you have to:

  1. Go to Wordpress Admin > Appearance > Personalize
  2. Go to Header & Navigation > Header elements
  3. Check "Show Social Icons" checkbox

You could also add the Phone Number or Email there, and it would force Divi to print the Secondary Menu

Top Menu (for Centered Menu Type)

Then add another call but this time with 'top' as parameter. Find the lines that create the top header:

    <header id="main-header" data-height-onload="<?php echo esc_attr( et_get_option( 'menu_height', '66' ) ); ?>">
        <div class="container clearfix et_menu_container">
            <?= gbili_lang_sel('top');?>

styles.css

Add this to your styles.css in order to have lang selector on both mobile and normal. Note that this will add styles to handle both lang menu locations.

Top Menu: for Centered Menu Type

@media all and (max-width: 980px) {
    .et_pb_fullwidth_header .et_pb_fullwidth_header_container.center .header-content {
        background-color: rgba(255, 255, 255, 1) !important;
        margin: 20px auto !important;
        padding: 7% !important;
        width: 45vh;
    }
}

@media (max-width: 980px) {
    #et-secondary-menu, #et-secondary-nav {
        display: block!important;
        margin-top: 6px!important;
    }

    #et-secondary-menu > .et-social-icons {
        display: none!important;
    }
}

Secondary Menu: for Default Menu Type

At the moment of writing, CSS custom properties are not supported by Microsoft products. And because, old people still use the web, I will not use them, but you can once adoption has evolved.

/* BEGIN ---- Secondary Menu WPML Custom Lang Selector */
#lang_sel_list,
#et-secondary-menu > .lang_sel_list_horizontal,
#et-secondary-menu > .wpml-ls-statics-shortcode_actions,
#et-secondary-menu > .wpml-ls,
#et-secondary-menu > .wpml-ls-legacy-list-horizontal {
    float: right !important;
    max-height:15px !important;
}

#lang_sel_list ul a,
#et-secondary-menu > .lang_sel_list_horizontal ul a,
#et-secondary-menu > .wpml-ls-statics-shortcode_actions ul a,
#et-secondary-menu > .wpml-ls ul a,
#et-secondary-menu > .wpml-ls-legacy-list-horizontal ul a,
#lang_sel_list a:visited,
#et-secondary-menu > .lang_sel_list_horizontal a:visited,
#et-secondary-menu > .wpml-ls-statics-shortcode_actions a:visited,
#et-secondary-menu > .wpml-ls a:visited,
#et-secondary-menu > .wpml-ls-legacy-list-horizontal a:visited {
    background-color: transparent !important;
}

div#et-secondary-menu {
    max-height: 15px !important;
}

.wpml-ls-statics-shortcode_actions, .wpml-ls-statics-shortcode_actions .wpml-ls-sub-menu, .wpml-ls-statics-shortcode_actions a {
    border: none !important;
}
.wpml-ls-legacy-list-horizontal {
    border: none !important;
    padding: 0px !important;
}
.wpml-ls-legacy-list-horizontal a {
    padding: 0px !important;
}

.wpml-ls-item-sep .icl_lang_sel_native {
    margin: 0px 5px;
}

/* Hide social icons from secondary menu on mobile */
@media (max-width: 980px) {
    #et-secondary-menu, #et-secondary-nav {
        display: block!important;
        margin-top: 6px!important;
    }

    #et-secondary-menu > .et-social-icons {
        display: none!important;
    }
}
/* END ---- Secondary Menu WPML Custom Lang Selector */