
🔄 WPML language switcher: ISO codes only, no flags or names
Country flags in a site menu are a questionable choice. Half the audience sees the Union Jack as their own flag, while the other half does not. The US flag standing in for English? That misses Canadians and Australians entirely. And full language names ("Русский," "Українська," "English") eat up half the menu on a mobile screen.
A compact RU | UK | EN format solves both problems: it reads instantly, takes up almost no space, and is not tied to geography. These are standard two-letter ISO language codes, understood without translation in any country.
Below are two ways to replace the default WPML switcher with ISO codes. The first uses a menu-items filter (a simple insert into functions.php). The second is a shortcode that renders the switcher anywhere on the site.
💡 Quick overview:
- Method 1: replacing navigation menu items via the
wp_nav_menu_itemshook, for a built-in switcher in the header - Method 2: the
[wpml_custom_lang_ISO]shortcode, insertable anywhere in the theme (widget, footer, mobile menu) - CSS: styling the
|separator, hiding the active language, responsive design
What the result looks like
Once the code is added, the language switcher displays in the RU | UK | EN format, short and clean. The active language is highlighted or hidden (controlled via CSS), while the remaining languages are clickable and lead to the corresponding page translation.

The code uses WPML's built-in icl_get_languages() function, which returns an array of the site's active languages with all the relevant data: translation URL, language code, active status. There are no third-party dependencies, only WPML's own API.
Method 1: replacing menu items via wp_nav_menu_items
This method intercepts the WordPress navigation menu output and replaces the default language items with compact ISO codes and a separator.
Where to insert it: the child theme's functions.php or via the Code Snippets plugin (safer, won't be erased when the theme updates).
Before adding the code, make a full site backup. The code is non-destructive and only adds output, but a backup is good practice whenever you edit functions.php.
1 /** 2 * Replaces WPML language menu items with ISO codes. 3 * theme_location is set for your menu. 4 */ 5 add_filter( 'wp_nav_menu_items', 'wpml_iso_menu_items', 10, 2 ); 6 7 function wpml_iso_menu_items( $items, $args ) { 8 // Check that the WPML function is available 9 if ( ! function_exists( 'icl_get_languages' ) ) { 10 return $items; 11 } 12 13 $languages = icl_get_languages( 'skip_missing=0&orderby=code' ); 14 15 // Specify here the theme_location of your menu (e.g. 'primary', 'top') 16 if ( empty( $languages ) || $args->theme_location !== 'primary' ) { 17 return $items; 18 } 19 20 $lang_links = ''; 21 22 foreach ( $languages as $lang ) { 23 $code = strtoupper( $lang['language_code'] ); 24 25 if ( $lang['active'] ) { 26 $lang_links .= '<span class="wpml-lang-item active">' . $code . '</span>'; 27 } else { 28 $lang_links .= '<a href="' . esc_url( $lang['url'] ) . '" class="wpml-lang-item">' . $code . '</a>'; 29 } 30 31 $lang_links .= '<span class="wpml-lang-sep">|</span>'; 32 } 33 34 // Remove the last separator 35 $lang_links = rtrim( $lang_links, '<span class="wpml-lang-sep">|</span>' ); 36 37 return $items . '<li class="menu-item wpml-iso-switcher">' . $lang_links . '</li>'; 38 }
What happens here. The wp_nav_menu_items hook fires when each menu on the site is being built. The condition $args->theme_location !== 'primary' restricts the replacement to only the menu you need; replace 'primary' with the theme_location of your menu (check wp_nav_menu() in your template or in the admin panel under Appearance > Menus > Display locations).
The function icl_get_languages('skip_missing=0') retrieves all published languages. skip_missing=0 means "show the language even if the current page has no translation," so the user lands on that language's home page instead of a 404.
strtoupper() converts codes to uppercase: ru → RU. If you need lowercase, remove the call.
Method 2: the [wpml_custom_lang_ISO] shortcode
The shortcode is more versatile: insert [wpml_custom_lang_ISO] anywhere, a widget, a page body, or a PHP template via do_shortcode(). It works well for a mobile menu, footer, or a block below the header.
Where to insert it: functions.php (the same file as Method 1). You can keep both methods active at the same time; they will not conflict.
1 /** 2 * Shortcode [wpml_custom_lang_ISO] — outputs the language switcher in ISO format. 3 * Insert: [wpml_custom_lang_ISO] in text or <?php echo do_shortcode('[wpml_custom_lang_ISO]'); ?> in the template. 4 */ 5 add_shortcode( 'wpml_custom_lang_ISO', 'wpml_shortcode_lang_iso' ); 6 7 function wpml_shortcode_lang_iso() { 8 if ( ! function_exists( 'icl_get_languages' ) ) { 9 return ''; 10 } 11 12 $languages = icl_get_languages( 'skip_missing=0&orderby=code&order=desc' ); 13 $output = ''; 14 15 if ( empty( $languages ) ) { 16 return ''; 17 } 18 19 foreach ( $languages as $lang ) { 20 $code = strtoupper( $lang['language_code'] ); 21 22 if ( $lang['active'] ) { 23 $output .= '<span class="wpml-lang-item active">' . $code . '</span>'; 24 } else { 25 $output .= '<a href="' . esc_url( $lang['url'] ) . '" class="wpml-lang-item">' . $code . '</a>'; 26 } 27 28 $output .= '<span class="wpml-lang-sep">|</span>'; 29 } 30 31 // Remove the last separator 32 $output = rtrim( $output, '<span class="wpml-lang-sep">|</span>' ); 33 34 return '<div class="wpml-iso-lang-switcher">' . $output . '</div>'; 35 }
The difference from Method 1: the shortcode returns an HTML string rather than appending items to a menu. This means you can place it anywhere, with no dependency on theme_location. The order=desc parameter reverses the language order (optional; remove it if you do not need it).
How to find your menu's theme_location
If you are using Method 1, you need to specify the theme_location argument exactly. Open the template file where the menu is called (usually header.php) and look for a line like:
1 wp_nav_menu( array( 'theme_location' => 'primary', 'menu_id' => 'primary-menu' ) );
The value in single quotes, for example 'primary', is your theme_location. Common names are primary for the main menu and top for the top bar. Substitute yours into the $args->theme_location !== 'primary' condition in the code above.
Another way: in the admin panel, go to Appearance > Menus > the "Manage Locations" tab, which lists all registered theme_location values for your theme.
CSS styles for the switcher
A minimal set of styles to keep the switcher looking clean. Add them under Appearance > Customize > Additional CSS or in the child theme's style.css file.
1 /* Switcher container */ 2 .wpml-iso-lang-switcher, 3 .wpml-iso-switcher { 4 display: flex; 5 align-items: center; 6 gap: 0; 7 font-family: 'Oswald', sans-serif; 8 font-size: 16px; 9 font-weight: 600; 10 text-transform: uppercase; 11 } 12 13 /* Links and active language */ 14 .wpml-lang-item { 15 color: #33445d; 16 text-decoration: none; 17 padding: 4px 2px; 18 transition: color 0.2s; 19 } 20 21 a.wpml-lang-item:hover { 22 color: #0073aa; 23 } 24 25 .wpml-lang-item.active { 26 color: #999; 27 cursor: default; 28 } 29 30 /* Separator */ 31 .wpml-lang-sep { 32 margin: 0 6px; 33 color: #ccc; 34 font-weight: 400; 35 } 36 37 /* Hide the active language and the separator following it */ 38 .wpml-lang-item.active, 39 .wpml-lang-item.active + .wpml-lang-sep { 40 display: none; 41 } 42 43 /* Mobile adaptation */ 44 @media screen and (max-width: 768px) { 45 .wpml-iso-lang-switcher, 46 .wpml-iso-switcher { 47 font-size: 18px; 48 padding: 8px 0; 49 } 50 }
The key trick here: the selector .wpml-lang-item.active + .wpml-lang-sep hides the separator immediately after the active language. If the active language is hidden, the separator next to it disappears as well, so there is no dangling | at the beginning or end of the row.
The Oswald font is used as an example. Replace it with your theme's font or remove the font-family line entirely to inherit from the theme. After adding the CSS, test the switcher at mobile resolutions using developer tools (F12 > Toggle device toolbar).
⁉️🤔 Frequently asked questions
Can I show language names instead of ISO codes?
> Yes, replace $lang['language_code'] with $lang['native_name'] in both code variants. You will get "Русский | English | Українська." Alternatively, $lang['translated_name'] outputs names in the current page's language: if the visitor is on the English version, they will see "Russian | English | Ukrainian." All available array keys are listed in the official WPML documentation for the icl_get_languages() function.
How do I remove a language that has no translation for the current page?
Change the
skip_missing=0parameter toskip_missing=1in theicl_get_languages()call. Languages without a translation will simply disappear from the switcher, and there will be no broken link in their place. This is convenient for sites where not all pages are translated: the menu stays clean, with no links leading to the home page or a 404.
The switcher does not appear. What is wrong?
Check three things. First, is the WPML plugin active and are languages added under WPML > Languages? Second, does the
theme_locationin the code match the actual menu (Method 1)? Third, is the shortcode called viado_shortcode()if you are using it in a PHP template rather than in page content (Method 2)? If everything is correct but the switcher still does not show, enableWP_DEBUGin wp-config.php and check error.log; there may be a syntax error in functions.php.
How do I add flags alongside the codes, for example 🇬🇧 + EN?
Add a flag emoji before
$codewith a condition:if ($lang['language_code'] === 'en') $code = '🇬🇧 ' . $code;. Keep in mind, though, that flag emojis may not render on Windows. A more reliable approach is to use CSS flags via::beforewith background-image, or WPML's built-in flags.
Will this code work with Polylang or TranslatePress?
No:
icl_get_languages()is specifically a WPML API. For Polylang the equivalent function ispll_the_languages(), and TranslatePress has its own set of hooks. The logic is the same, but the function names differ.
Takeaways: menu or shortcode
The menu filter method is for those who need the switcher strictly in the navigation and know their theme_location for certain. Minimal effort: insert the code, replace the location name, done. The downside: it is tied to a single menu; for a second one you would need to duplicate the hook with a different theme_location.
The shortcode is the way to go if the switcher needs to be in a non-standard spot: a mobile panel, footer, or sidebar. Add the shortcode to an "HTML" widget or a template and control its position through standard WordPress tools. The advantage: you can insert it in multiple places without duplicating code.
In practice, both methods coexist peacefully in a single functions.php. Set up the filter for the main menu and the shortcode for the mobile version, and you get a clean RU | UK | EN on every device. And if you need the switcher a third time (for example in the footer), just add another do_shortcode('[wpml_custom_lang_ISO]') to the appropriate template.
Which switcher format do you use: flags, names, or ISO codes? Share your experience setting up multilingual sites in the comments; it would be interesting to compare approaches.



