language-selector.tsx67 lines · main
| 1 | 'use client'; |
| 2 | |
| 3 | import { useEffect, useState } from 'react'; |
| 4 | |
| 5 | /** |
| 6 | * Footer language selector. English is the main (default) language; the others |
| 7 | * are written in their OWN language so a speaker recognises their own. |
| 8 | * |
| 9 | * Scope note: this control records the visitor's preferred locale in the |
| 10 | * standard `NEXT_LOCALE` cookie so the choice is remembered and is ready for |
| 11 | * i18n. It does NOT yet translate page text — full multilingual content is a |
| 12 | * separate, larger build. Until then, picking a language is remembered but the |
| 13 | * copy stays English. |
| 14 | */ |
| 15 | const LANGUAGES: readonly { code: string; label: string }[] = [ |
| 16 | { code: 'en', label: 'English' }, |
| 17 | { code: 'de', label: 'Deutsch' }, |
| 18 | { code: 'fr', label: 'Français' }, |
| 19 | { code: 'es', label: 'Español' }, |
| 20 | { code: 'nl', label: 'Nederlands' }, |
| 21 | { code: 'tr', label: 'Türkçe' }, |
| 22 | ]; |
| 23 | |
| 24 | function readLocaleCookie(): string { |
| 25 | if (typeof document === 'undefined') return 'en'; |
| 26 | const match = document.cookie.match(/(?:^|;\s*)NEXT_LOCALE=([^;]+)/); |
| 27 | return match?.[1] ? decodeURIComponent(match[1]) : 'en'; |
| 28 | } |
| 29 | |
| 30 | export function LanguageSelector() { |
| 31 | // Default to English on the server + first paint; sync to the saved choice |
| 32 | // after mount (cookies aren't readable during SSR here). |
| 33 | const [locale, setLocale] = useState('en'); |
| 34 | |
| 35 | useEffect(() => { |
| 36 | setLocale(readLocaleCookie()); |
| 37 | }, []); |
| 38 | |
| 39 | function onChange(event: React.ChangeEvent<HTMLSelectElement>) { |
| 40 | const next = event.target.value; |
| 41 | setLocale(next); |
| 42 | // Site-wide, one year. Lax so normal top-level navigations carry it. |
| 43 | document.cookie = `NEXT_LOCALE=${encodeURIComponent(next)}; path=/; max-age=31536000; samesite=lax`; |
| 44 | } |
| 45 | |
| 46 | return ( |
| 47 | <label className="inline-flex items-center" title="language"> |
| 48 | <span className="sr-only">language</span> |
| 49 | <select |
| 50 | value={locale} |
| 51 | onChange={onChange} |
| 52 | aria-label="select language" |
| 53 | className="cursor-pointer border-0 bg-transparent py-0.5 pl-0 pr-5 font-mono text-[10px] text-[var(--color-text-muted)] outline-none transition-colors hover:text-[var(--color-text)]" |
| 54 | > |
| 55 | {LANGUAGES.map((language) => ( |
| 56 | <option |
| 57 | key={language.code} |
| 58 | value={language.code} |
| 59 | className="bg-[var(--color-surface)] text-[var(--color-text)]" |
| 60 | > |
| 61 | {language.label} |
| 62 | </option> |
| 63 | ))} |
| 64 | </select> |
| 65 | </label> |
| 66 | ); |
| 67 | } |