feat(header): custom flag-based language switcher

Replaces WPML's default language selector with a compact branded dropdown :
current language flag + 2-letter code + chevron, with a CSS-only hover/focus
dropdown listing other available languages (flag + native name).

Implementation : remove_all_actions on wpml_add_language_selector + register
our own renderer. Bricks's wpml-language-switcher element (hdrlng) fires the
same action so no template edit needed.

Flags via emoji (🇬🇧 EN, 🇫🇷 FR) — no asset path concerns. Dropdown styled
to match the rest of the header (navy bg, gold-on-hover, subtle border +
shadow). Mobile : dropdown anchors left to stay in viewport.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 20:59:52 +02:00
parent b530b7a76f
commit a5c15b4c7a

182
tools/av-lang-switcher.php Normal file
View File

@@ -0,0 +1,182 @@
<?php
/**
* Plugin Name: Asterion — Custom language switcher (flag dropdown)
* Description: Replaces WPML's default language selector output with a compact
* flag-based dropdown. Renders via wpml_add_language_selector action
* (the same action Bricks's wpml-language-switcher element fires), so no
* change to the Header template is needed — just swap the action handler.
*
* Output : current language flag + 2-letter code button + chevron, with
* a CSS-only hover dropdown listing other available languages.
*/
defined('ABSPATH') || exit;
/* -----------------------------------------------------------------
1. Remove WPML's default language selector action handler
and register our own.
----------------------------------------------------------------- */
add_action('init', function () {
// WPML registers the default selector internally. Remove ALL existing
// callbacks attached to wpml_add_language_selector, then attach ours.
remove_all_actions('wpml_add_language_selector');
add_action('wpml_add_language_selector', 'av_render_lang_switcher');
}, 99);
function av_render_lang_switcher() {
$languages = apply_filters('wpml_active_languages', null, 'skip_missing=0');
if (empty($languages)) return;
// Find current language
$current_code = apply_filters('wpml_current_language', null);
$current = $languages[$current_code] ?? reset($languages);
$others = [];
foreach ($languages as $code => $lang) {
if ($code !== $current_code) $others[] = $lang;
}
// Flag emoji per language code (extend as needed)
$flags = [
'en' => "\u{1F1EC}\u{1F1E7}", // 🇬🇧
'fr' => "\u{1F1EB}\u{1F1F7}", // 🇫🇷
'de' => "\u{1F1E9}\u{1F1EA}",
'es' => "\u{1F1EA}\u{1F1F8}",
'it' => "\u{1F1EE}\u{1F1F9}",
];
$current_flag = $flags[$current['language_code']] ?? "\u{1F310}";
$current_label = strtoupper($current['language_code']);
?>
<div class="av-lang-switcher" tabindex="0" aria-haspopup="true">
<span class="av-lang-current">
<span class="av-lang-flag" aria-hidden="true"><?php echo esc_html($current_flag); ?></span>
<span class="av-lang-code"><?php echo esc_html($current_label); ?></span>
<svg class="av-lang-chevron" width="10" height="6" viewBox="0 0 10 6" fill="none" aria-hidden="true">
<path d="M1 1l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<?php if (! empty($others)): ?>
<ul class="av-lang-list" role="menu">
<?php foreach ($others as $lang):
$flag = $flags[$lang['language_code']] ?? "\u{1F310}";
$url = $lang['url'] ?: home_url('/' . $lang['language_code'] . '/');
// Use native_name (Français / English / Deutsch...) — universally recognizable
// and stays short for the dropdown.
$name = $lang['native_name'] ?: $lang['translated_name'] ?: strtoupper($lang['language_code']);
?>
<li role="none">
<a href="<?php echo esc_url($url); ?>" hreflang="<?php echo esc_attr($lang['language_code']); ?>" role="menuitem">
<span class="av-lang-flag" aria-hidden="true"><?php echo esc_html($flag); ?></span>
<span class="av-lang-name"><?php echo esc_html($name); ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
<?php
}
/* -----------------------------------------------------------------
2. Inline CSS — compact, branded, CSS-only dropdown on hover/focus
----------------------------------------------------------------- */
add_action('wp_head', function () {
?>
<style id="av-lang-switcher-styles">
.av-lang-switcher {
position: relative;
display: inline-block;
cursor: pointer;
user-select: none;
}
.av-lang-current {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.4rem 0.65rem;
color: var(--color-text-on-dark, #fff);
font-size: 0.875rem;
font-weight: 500;
line-height: 1;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 4px;
background: transparent;
transition: background-color 0.15s ease, border-color 0.15s ease;
}
.av-lang-switcher:hover .av-lang-current,
.av-lang-switcher:focus-within .av-lang-current {
background: rgba(255, 255, 255, 0.06);
border-color: rgba(201, 164, 90, 0.4);
}
.av-lang-flag {
font-size: 1rem;
line-height: 1;
}
.av-lang-code {
letter-spacing: 0.05em;
}
.av-lang-chevron {
margin-left: 0.15rem;
opacity: 0.7;
transition: transform 0.15s ease;
}
.av-lang-switcher:hover .av-lang-chevron,
.av-lang-switcher:focus-within .av-lang-chevron {
transform: rotate(180deg);
}
/* Dropdown list — hidden by default, shown on hover/focus */
.av-lang-list {
position: absolute;
top: calc(100% + 0.25rem);
right: 0;
min-width: 11rem;
margin: 0;
padding: 0.4rem 0;
list-style: none;
background: var(--color-brand-navy, #0B1F3A);
border: 1px solid rgba(201, 164, 90, 0.25);
border-radius: 4px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
opacity: 0;
visibility: hidden;
transform: translateY(-4px);
transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s 0.18s;
z-index: 100;
}
.av-lang-switcher:hover .av-lang-list,
.av-lang-switcher:focus-within .av-lang-list {
opacity: 1;
visibility: visible;
transform: translateY(0);
transition-delay: 0s;
}
.av-lang-list a {
display: flex;
align-items: center;
gap: 0.55rem;
padding: 0.55rem 0.9rem;
color: var(--color-text-on-dark, #fff);
font-size: 0.875rem;
font-weight: 500;
text-decoration: none;
white-space: nowrap;
transition: color 0.15s ease, background-color 0.15s ease;
}
.av-lang-list a:hover,
.av-lang-list a:focus {
color: var(--color-brand-gold, #C9A45A);
background: rgba(201, 164, 90, 0.08);
}
.av-lang-list .av-lang-flag {
font-size: 1.1rem;
}
/* Mobile : ensure switcher stays visible in mobile menu context */
@media (max-width: 991px) {
.av-lang-list {
right: auto;
left: 0;
}
}
</style>
<?php
}, 999);