Files
ODOO4projects.com/static/lang.js
T
oliver 937dbd4dbf Publish German and Spanish homepage (/de/, /es/) with hreflang cluster and language switcher
Homepage pilot of the website i18n project. Adds the translated homepage for DE and
ES, the shared selector at /static/lang.js, and the hreflang cluster on all three
language versions (EN included, or the cluster is asymmetric and ignored).

- de/index.html, es/index.html: translator output plus the i18n build (html lang,
  self-referencing canonical, hreflang cluster, lang.js tag, switcher slot in the footer).
- index.html: same cluster + lang.js tag + switcher slot; canonical and og:url
  normalised to the lowercase host.
- static/lang.js: the tested selector (client-side language choice, auto-detect on the
  EN homepage only, once per session; a stored choice or ?lang= always wins).
- sitemap.xml: /de/ and /es/ (priority 1.0, lastmod 2026-10-24).
- llms.txt: new '## Languages' section.

Two gaps in the build recipe were closed while building: a mirrored page cannot use
root-relative source paths (style.css, images/* would resolve to /de/style.css -> 404),
so the build now re-points assets at the site root and keeps page links on the EN route
until the language page exists; and og:url is now this page's own URL instead of the
EN one inherited from the source. Not built yet: support.html, odoo-hosting-2027.html,
the blog, the bootcamp page, the legal pages.
2026-10-24 21:22:13 +00:00

43 lines
3.6 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");
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){}}
})();