Files
oliver 06beab403d lang.js: remember the language a switcher click picked
The switcher links were plain anchors: clicking one navigated but stored nothing, so
the auto-detect on the EN homepage bounced the visitor straight back. A de-DE browser
on /de/ that clicked EN landed on / and was replaced back to /de/ — the 'an explicit
choice always wins' promise in the plan and in llms.txt was only true for ?lang=.

A click now calls remember() with the link's own hreflang, so the choice is stored in
localStorage + the o4p_lang cookie and survives the next session. Selector tests
27 -> 31 (switcher click stores EN, sets the cookie, stores DE, chosen EN survives a
new session).
2026-10-24 21:25:33 +00:00

45 lines
3.8 KiB
JavaScript

/* ODOO4projects lang selector + EN-homepage auto-detect, <script defer src="/static/lang.js">.
Inert on /de/ & /es/; stored choice (?lang= or click) wins forever; detect once per session; no loop.
ES5, no deps. Contract: work/i18n/I18N-BUILD-PLAN.md */
(function () {
"use strict";
var HOME={en:"/",de:"/de/",es:"/es/"},ORDER=["en","de","es"],KEY="o4p_lang",SESS="o4p_lang_checked",YEAR=31536000;
function ls(){try{return window.localStorage||null}catch(e){return null}}
function ss(){try{return window.sessionStorage||null}catch(e){return null}}
function rd(s,k){try{return s?s.getItem(k):null}catch(e){return null}}
function wr(s,k,v){try{if(s)s.setItem(k,v)}catch(e){}}
function gck(k){try{var m=(document.cookie||"").match(new RegExp("(?:^|;\\s*)"+k+"=([^;]*)"));return m?decodeURIComponent(m[1]):null}catch(e){return null}}
function sck(k,v){try{document.cookie=k+"="+encodeURIComponent(v)+";path=/;max-age="+YEAR+";SameSite=Lax"}catch(e){}}
function stored(){return rd(ls(),KEY)||gck(KEY)}
function remember(l){wr(ls(),KEY,l);sck(KEY,l)}
function cur(){var p=location.pathname||"/";return /^\/de(\/|$)/.test(p)?"de":/^\/es(\/|$)/.test(p)?"es":"en"}
function enHome(){var p=location.pathname||"/";return p==="/"||p===""||/^\/index\.html$/i.test(p)}
/* equivalent page in another lang: this page's hreflang alternate, else that lang's homepage */
function urlFor(l){var a,i,h;try{a=document.querySelectorAll('link[rel="alternate"][hreflang]')}catch(e){a=[]}
for(i=0;i<(a?a.length:0);i++){h=(a[i].getAttribute("hreflang")||"").toLowerCase();if(!h)continue;
if(l==="en"&&(h==="en"||h.indexOf("en-")===0))return a[i].href;if(h===l)return a[i].href}return HOME[l]}
var c=cur();
/* (1) explicit ?lang= wins and is remembered */
var q=(location.search||"").match(/[?&]lang=(en|de|es)(?:&|$)/i);
if(q){var pick=q[1].toLowerCase();remember(pick);if(pick!==c){location.replace(urlFor(pick));return}}
/* (2) auto-detect: EN homepage only, once per session, never over a stored choice */
if(c==="en"&&enHome()&&!stored()&&rd(ss(),SESS)!=="1"){wr(ss(),SESS,"1");var pref="";
try{pref=(navigator.languages&&navigator.languages[0])||navigator.language||""}catch(e){pref=""}
pref=String(pref).toLowerCase();
if(pref.indexOf("de")===0){location.replace(HOME.de);return}
if(pref.indexOf("es")===0){location.replace(HOME.es);return}}
/* (3) switcher EN | DE | ES */
render(c);
function render(current){var nav=document.createElement("nav"),i,a;nav.className="lang-switch";nav.setAttribute("aria-label","Language");
for(i=0;i<ORDER.length;i++){a=document.createElement("a");a.href=urlFor(ORDER[i]);a.textContent=ORDER[i].toUpperCase();
a.setAttribute("hreflang",ORDER[i]==="en"?"en-US":ORDER[i]);if(ORDER[i]===current)a.setAttribute("aria-current","true");
/* a click is an explicit choice: remember it, or the auto-detect bounces the visitor back */
a.onclick=function(){var l=String(this.getAttribute("hreflang")||"").toLowerCase();remember(l.indexOf("en")===0?"en":l)};
nav.appendChild(a);if(i<ORDER.length-1)nav.appendChild(document.createTextNode(" | "))}
var slot=null;try{slot=document.querySelector("[data-lang-switcher]")}catch(e){slot=null}
try{if(!document.getElementById("o4p-lang-style")){var s=document.createElement("style");s.id="o4p-lang-style";
s.appendChild(document.createTextNode(".lang-switch{font:600 12px/1 Inter,system-ui,sans-serif;letter-spacing:.06em;white-space:nowrap}.lang-switch a{color:inherit;text-decoration:none}.lang-switch a[aria-current=true]{color:#a3801a;text-decoration:underline}.lang-switch--float{position:fixed;top:10px;right:12px;z-index:99;background:rgba(244,240,232,.92);border:1px solid rgba(201,162,39,.4);border-radius:999px;padding:6px 12px;color:#0d1b2e}"));
document.head.appendChild(s)}
if(slot){slot.appendChild(nav)}else{nav.className="lang-switch lang-switch--float";document.body.appendChild(nav)}}catch(e){}}
})();