Building a Locale Switcher That Respects hreflang and User Choice
Language switchers look trivial: a dropdown with flags. In practice they are where many multilingual sites break their own SEO and annoy users at the same time. This post outlines a switcher design that keeps search engi
Language switchers look trivial: a dropdown with flags. In practice they are where many multilingual sites break their own SEO and annoy users at the same time. This post outlines a switcher design that keeps search engines and humans happy, with small code samples.
Principles
- Every language version has its own URL (
/en/,/de/,/ja/). - The switcher links to the equivalent page, not the homepage of the other language.
- Automatic detection suggests, it never forces a redirect.
- A user's explicit choice is remembered.
Generate links from the same map as hreflang
Keep one data structure that lists equivalents for each page, and use it both for <link rel="alternate" hreflang> tags and for the switcher. If they come from different sources, they will drift.
// alternates is injected server-side: { en: "/en/pricing", de: "/de/preise", ja: "/ja/pricing" }
function renderSwitcher(alternates, current) {
return Object.entries(alternates).map(([lang, href]) =>
`<a href="${href}" hreflang="${lang}" lang="${lang}"${lang === current ? ' aria-current="true"' : ''}>${LABELS[lang]}</a>`
).join("");
}
Use real anchor tags with href. Switchers built from buttons and JavaScript click handlers are invisible to crawlers.
Labels: language names, not flags
Flags represent countries, not languages. Spanish speakers in Mexico do not want a Spanish flag. Show each language in its own script: English, Deutsch, ζ₯ζ¬θͺ, EspaΓ±ol.
Detection as a banner, not a redirect
If the browser language differs from the page language, show a small banner: "This page is available in Deutsch. Switch?" Redirecting automatically based on Accept-Language or IP can prevent crawlers from seeing other versions and traps travellers.
const pref = localStorage.getItem("lang");
const browser = navigator.language.slice(0, 2);
if (!pref && browser !== document.documentElement.lang && alternates[browser]) {
showBanner(browser, alternates[browser]);
}
Remember the choice
When a user clicks a language, store it in localStorage or a cookie. On future visits, still serve the URL they asked for; just use the stored preference to decide whether to show the banner.
Missing translations
If a page has no equivalent in a language, either hide that option or link to the closest parent page with a note. Never link to a 404, and never list it in hreflang.
Test it
Crawl the site and check that every hreflang target returns 200, lists the source page back, and appears in the switcher. A few lines of Python with requests and a regex is enough for small sites.
More detail on URL structures and common hreflang errors is in our hreflang implementation guide.
Published by PandaWM, an overseas marketing team based in Hong Kong.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.