--- /** * The language switcher. One control, two placements. * * `bar` is the header's: a compact button showing the current language's code. `footer` * is the Language column's: the same button, showing the language's full name, opening * upward so the panel is never clipped off the bottom of the document. Same list, same * hrefs, same markup, same script — the only differences are which label the trigger * shows and which way the panel opens, because a reader who finds one and then the * other should not have to work out whether they do the same thing. * * The footer used to carry a second, different control: every locale as a flat link, * ~20 of them wrapped over two rows. That was not a switcher, it was a wall, and it * grew by one row every time a language was added. * * Text, not flags, in both. A flag is a country: Spanish is not Spain, Dutch is not the * Netherlands, and English belongs to nobody in particular. Each language is written in * its own name, because someone looking for Nederlands is not scanning for "Dutch". * * Every link points at this same page in that language, built from the unprefixed path, * so this works identically on a mint page: /mint/kashu.me and /es/mint/kashu.me are * the same mint. There is no "switch to Spanish and land on the home page" case, which * is the usual failure of a switcher that only knows about the site root. They are real * `` elements, so a crawler follows them and a reader with no JavaScript uses * them. * * The control is a `
` rather than a scripted dropdown. It opens, closes and * takes the keyboard with no JavaScript at all, which matters because the whole point * of this control is being able to leave a page you cannot read. The script below only * adds what `
` does not do by itself: closing on Escape and on a click * elsewhere, and filtering the list as you type. */ import { LOCALES, localePath, localeInfo, useI18n } from '../i18n'; import { pageLocale } from '../i18n/paths'; interface Props { /** `bar` is the header control; `footer` is the one in the Language column. */ placement?: 'bar' | 'footer'; } const { placement = 'bar' } = Astro.props; const { locale, path } = pageLocale(Astro); const t = useI18n(locale); const links = LOCALES.map((other) => ({ code: other.code, label: other.label, href: localePath(path, other.code), current: other.code === locale, })); /* * The header shows two letters, because the full name of the current language is a word * the reader already knows they are reading, and the header row is full. The footer * column has the width for the name and no adjacent nav to crowd, and a column headed * "Language" reads better with "Nederlands" under it than with "NL". */ const trigger = placement === 'footer' ? localeInfo(locale).label : locale.toUpperCase(); /* * The search field earns its place at 20-odd languages and is noise at three. It is * the one part of this control that needs JavaScript, so it is also the one part that * is allowed to be absent: every link is in the panel either way. */ const searchable = LOCALES.length > 8; ---
{/* The trigger label does not say what it is on its own, so the word is in the accessibility tree and out of the way for everyone else. */} {t('lang.label')} {trigger}