fix(lang): redirect par langue reste sur l'hôte courant (.onion compris) + détection langue fiable

- base mailto hote courant via Host header (http pour .onion, https sinon) :
  canonical/hreflang restent sur le domaine actuel, plus de fuite vers le Funnel
- meta refresh rendu relatif et différé à 1.5s pour laisser le JS choisir la langue
- localStorage isolé dans son propre try/catch : la détection navigator s'exécute
  même quand le stockage est bloqué (Tor), plus de Français systématique
This commit is contained in:
riricdev 2026-09-06 16:28:37 +02:00
commit 03c0c134fe

View file

@ -139,9 +139,18 @@ function html(body: string, cache = "no-cache") {
}) })
} }
function langRedirectPage(rest: string): string { function baseUrl(req: Request, fallback = SITE_URL): string {
const fr = `${SITE_URL}/fr${rest}` const host = (req.headers.get("host") || "").trim()
const en = `${SITE_URL}/en${rest}` if (!host) return fallback
// Tor = HTTP, tout le reste est servi en HTTPS (le schéma suit l'hôte courant,
// pour que la redirection de langue reste sur le domaine actuel, .onion compris).
const scheme = host.toLowerCase().endsWith(".onion") ? "http" : "https"
return `${scheme}://${host}`
}
function langRedirectPage(rest: string, base: string): string {
const fr = `${base}/fr${rest}`
const en = `${base}/en${rest}`
const restJs = JSON.stringify(rest) const restJs = JSON.stringify(rest)
return `<!doctype html> return `<!doctype html>
<html lang="fr"> <html lang="fr">
@ -154,18 +163,18 @@ function langRedirectPage(rest: string): string {
<link rel="alternate" hreflang="fr" href="${fr}" /> <link rel="alternate" hreflang="fr" href="${fr}" />
<link rel="alternate" hreflang="en" href="${en}" /> <link rel="alternate" hreflang="en" href="${en}" />
<link rel="alternate" hreflang="x-default" href="${fr}" /> <link rel="alternate" hreflang="x-default" href="${fr}" />
<meta http-equiv="refresh" content="0; url=${fr}" /> <meta http-equiv="refresh" content="1.5; url=/fr${rest}" />
<script>(function(){ <script>(function(){
var rest=${restJs}; var rest=${restJs};
var saved=null;
try{ saved=localStorage.getItem("zektyc-lang"); }catch(e){}
var target="/fr"+rest; var target="/fr"+rest;
try{ if(saved==="en"||saved==="fr"){
var saved=localStorage.getItem("zektyc-lang"); target="/"+saved+rest;
if(saved==="en"||saved==="fr"){ target="/"+saved+rest; } } else {
else { var ln=(navigator.languages&&navigator.languages.length?navigator.languages[0]:(navigator.language||navigator.userLanguage||"")).toLowerCase();
var ln=(navigator.language||navigator.userLanguage||"").toLowerCase();
if(ln.indexOf("en")===0) target="/en"+rest; if(ln.indexOf("en")===0) target="/en"+rest;
} }
}catch(e){}
if(target.indexOf("/en")===0){ if(target.indexOf("/en")===0){
fetch(target,{method:"HEAD",cache:"no-store"}).then(function(r){ fetch(target,{method:"HEAD",cache:"no-store"}).then(function(r){
location.replace(r.ok?target:"/fr"+rest); location.replace(r.ok?target:"/fr"+rest);
@ -1288,8 +1297,8 @@ server = serve({
return new Response(null, { status: 301, headers: { Location: `/en/zmap${sub}/` } }) return new Response(null, { status: 301, headers: { Location: `/en/zmap${sub}/` } })
} }
const artMatch = matchArticle(pathname, articles) const artMatch = matchArticle(pathname, articles)
if (artMatch) return html(langRedirectPage(artPath(artMatch)), "no-cache") if (artMatch) return html(langRedirectPage(artPath(artMatch), baseUrl(req)), "no-cache")
if (isHtmlFile(pathname)) return html(langRedirectPage(pathname === "/" ? "/" : pathname), "no-cache") if (isHtmlFile(pathname)) return html(langRedirectPage(pathname === "/" ? "/" : pathname, baseUrl(req)), "no-cache")
} }
const p = resolveLangPath(pathname) ?? pathname const p = resolveLangPath(pathname) ?? pathname