Add language switcher and language detection
This commit is contained in:
@@ -130,10 +130,10 @@
|
||||
<div class="mt-6 pt-8 border-t border-gray-600">
|
||||
<div class="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
|
||||
<p class="text-gray-600 text-sm">
|
||||
© {{ now.Format "2006" }} {{ .Site.Title }}. All rights reserved.
|
||||
© {{ now.Format "2006" }} {{ with .Site.Title }}{{ . }}. {{ end }}{{ i18n "allRightsReserved" }}
|
||||
</p>
|
||||
<a href="https://www.champs-libres.coop/" class="text-sm text-gray-600 hover:text-primary-600" target="_blank" rel="noopener noreferrer">
|
||||
Designed by Champs-Libres
|
||||
{{ i18n "designedBy" }} Champs-Libres
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -89,21 +89,7 @@
|
||||
{{ end }}
|
||||
|
||||
<!-- Mobile Language Switcher -->
|
||||
{{ if hugo.IsMultilingual }}
|
||||
<div class="py-4 border-t border-gray-200 mt-4">
|
||||
<div class="text-lg text-gray-600 font-semibold mb-2">{{ i18n "language" }}</div>
|
||||
{{ range .Site.Languages }}
|
||||
{{ if eq $.Site.Language.Lang .Lang }}
|
||||
<span class="block py-2 text-primary-600 font-bold">{{ .LanguageName }}</span>
|
||||
{{ else }}
|
||||
{{ $langURL := printf "/%s/" .Lang }}
|
||||
<a href="{{ $langURL }}" class="language-switch-link block py-2 text-gray-700 hover:text-primary-600" data-lang="{{ .Lang }}">
|
||||
{{ .LanguageName }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ partial "language-switcher-mobile" . }}
|
||||
|
||||
{{ if not $headerConfig.hideButtons }}
|
||||
<div class="pt-4 space-y-4">
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
{{/*
|
||||
Language preference + browser language detection.
|
||||
|
||||
Rendered inline in <head> on purpose: it must run before the page paints so
|
||||
a visitor is never shown a flash of the wrong language, and inlining avoids
|
||||
an extra HTTP request for ~1 KB of script.
|
||||
|
||||
Hugo hands the script everything it needs, so the script never has to guess
|
||||
URLs: the home page of every language and the translations of *this* page.
|
||||
That keeps it correct even though French is served from the site root ("/")
|
||||
while the other languages sit under a prefix ("/en/", "/nl/").
|
||||
*/}}
|
||||
{{ if hugo.IsMultilingual }}
|
||||
{{ $homes := dict }}
|
||||
{{ range hugo.Sites }}
|
||||
{{ $homes = merge $homes (dict .Language.Lang .Home.RelPermalink) }}
|
||||
{{ end }}
|
||||
{{ $translations := dict }}
|
||||
{{ range .AllTranslations }}
|
||||
{{ $translations = merge $translations (dict .Lang .RelPermalink) }}
|
||||
{{ end }}
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var STORAGE_KEY = 'hugo_preferred_language';
|
||||
var SESSION_KEY = 'language_redirected';
|
||||
|
||||
var CURRENT = {{ .Site.Language.Lang }};
|
||||
var HOMES = {{ $homes | jsonify | safeJS }};
|
||||
var TRANSLATIONS = {{ $translations | jsonify | safeJS }};
|
||||
var AVAILABLE = Object.keys(HOMES);
|
||||
|
||||
function read(store, key) {
|
||||
try { return window[store].getItem(key); } catch (e) { return null; }
|
||||
}
|
||||
|
||||
function write(store, key, value) {
|
||||
try { window[store].setItem(key, value); } catch (e) { /* private mode */ }
|
||||
}
|
||||
|
||||
/* Match a browser language tag ("nl-BE", "en") against the languages this
|
||||
site actually publishes, comparing on the primary subtag. */
|
||||
function matchLanguage(tag) {
|
||||
var primary = String(tag).toLowerCase().split('-')[0];
|
||||
for (var i = 0; i < AVAILABLE.length; i++) {
|
||||
if (AVAILABLE[i].toLowerCase().split('-')[0] === primary) {
|
||||
return AVAILABLE[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function detectFromBrowser() {
|
||||
var tags = navigator.languages || (navigator.language ? [navigator.language] : []);
|
||||
for (var i = 0; i < tags.length; i++) {
|
||||
var match = matchLanguage(tags[i]);
|
||||
if (match) { return match; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function targetFor(lang) {
|
||||
/* Prefer the translation of the page being viewed, so a deep link keeps
|
||||
the visitor on the same page in their language. */
|
||||
return TRANSLATIONS[lang] || HOMES[lang] || null;
|
||||
}
|
||||
|
||||
function resolveAndRedirect() {
|
||||
/* Auto-redirect at most once per browsing session: after that the
|
||||
visitor is free to navigate to any language by hand. */
|
||||
if (read('sessionStorage', SESSION_KEY)) { return; }
|
||||
write('sessionStorage', SESSION_KEY, '1');
|
||||
|
||||
var stored = read('localStorage', STORAGE_KEY);
|
||||
var wanted = (stored && HOMES[stored]) ? stored : detectFromBrowser();
|
||||
|
||||
if (!wanted || wanted === CURRENT) { return; }
|
||||
|
||||
var target = targetFor(wanted);
|
||||
if (target) {
|
||||
window.location.replace(target + window.location.search + window.location.hash);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remember an explicit choice made through the language switcher. */
|
||||
function rememberChoices() {
|
||||
var links = document.querySelectorAll('.language-switch-link');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
links[i].addEventListener('click', function () {
|
||||
var lang = this.getAttribute('data-lang');
|
||||
if (lang) { write('localStorage', STORAGE_KEY, lang); }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resolveAndRedirect();
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', rememberChoices);
|
||||
} else {
|
||||
rememberChoices();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,24 @@
|
||||
{{/*
|
||||
Mobile language switcher, used inside the collapsed navigation menu.
|
||||
Same translation-aware linking as the desktop switcher.
|
||||
*/}}
|
||||
{{ if hugo.IsMultilingual }}
|
||||
{{ $page := . }}
|
||||
<div class="py-4 border-t border-gray-200 mt-4">
|
||||
<div class="text-lg text-gray-600 font-semibold mb-2">{{ i18n "language" }}</div>
|
||||
{{ range hugo.Sites }}
|
||||
{{ $lang := .Language.Lang }}
|
||||
{{ if eq $.Site.Language.Lang $lang }}
|
||||
<span class="block py-2 text-primary-600 font-bold">{{ .Language.LanguageName }}</span>
|
||||
{{ else }}
|
||||
{{ $target := .Home }}
|
||||
{{ range $page.AllTranslations }}
|
||||
{{ if eq .Lang $lang }}{{ $target = . }}{{ end }}
|
||||
{{ end }}
|
||||
<a href="{{ $target.RelPermalink }}" hreflang="{{ $lang }}" lang="{{ $lang }}" class="language-switch-link block py-2 text-gray-700 hover:text-primary-600" data-lang="{{ $lang }}">
|
||||
{{ .Language.LanguageName }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -1,8 +1,16 @@
|
||||
{{/*
|
||||
Desktop language switcher.
|
||||
|
||||
Links point at the translation of the *current* page when one exists, so
|
||||
switching language keeps the visitor where they are. When the current page
|
||||
has no translation we fall back to the home page of the target language.
|
||||
*/}}
|
||||
{{ if hugo.IsMultilingual }}
|
||||
{{ $page := . }}
|
||||
{{ $headerConfig := .Site.Params.header }}
|
||||
{{ $dropdownConfig := $headerConfig.menu.dropdown }}
|
||||
<div class="relative group">
|
||||
<button class="flex items-center {{ with $headerConfig.menu.linkClass }}{{ . }}{{ else }}text-base text-gray-900 hover:text-primary-600 font-bold transition duration-200{{ end }}">
|
||||
<button type="button" aria-label="{{ i18n "switchLanguage" }}" class="flex items-center {{ with $headerConfig.menu.linkClass }}{{ . }}{{ else }}text-base text-gray-900 hover:text-primary-600 font-bold transition duration-200{{ end }}">
|
||||
{{ .Site.Language.LanguageName }}
|
||||
<svg class="ml-2 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
@@ -10,15 +18,19 @@
|
||||
</button>
|
||||
<div class="absolute right-0 mt-2 {{ $dropdownConfig.width | default "w-72" }} opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 ease-in-out">
|
||||
<div class="{{ $dropdownConfig.container_padding | default "py-6" }} {{ $dropdownConfig.background | default "bg-white" }} {{ $dropdownConfig.border | default "border border-gray-100" }} {{ $dropdownConfig.shadow | default "shadow-xl" }} {{ $dropdownConfig.radius | default "rounded-lg" }}">
|
||||
{{ range .Site.Languages }}
|
||||
{{ if ne $.Site.Language.Lang .Lang }}
|
||||
{{ $langURL := printf "/%s/" .Lang }}
|
||||
<a href="{{ $langURL }}" class="language-switch-link block {{ $dropdownConfig.item_padding | default "px-8 py-3" }} {{ $dropdownConfig.text_size | default "text-sm" }} {{ $dropdownConfig.text_color | default "text-gray-700" }} {{ $dropdownConfig.hover_background | default "hover:bg-gray-50" }}" data-lang="{{ .Lang }}">
|
||||
{{ .LanguageName }}
|
||||
{{ range hugo.Sites }}
|
||||
{{ $lang := .Language.Lang }}
|
||||
{{ if ne $.Site.Language.Lang $lang }}
|
||||
{{ $target := .Home }}
|
||||
{{ range $page.AllTranslations }}
|
||||
{{ if eq .Lang $lang }}{{ $target = . }}{{ end }}
|
||||
{{ end }}
|
||||
<a href="{{ $target.RelPermalink }}" hreflang="{{ $lang }}" lang="{{ $lang }}" class="language-switch-link block {{ $dropdownConfig.item_padding | default "px-8 py-3" }} {{ $dropdownConfig.text_size | default "text-sm" }} {{ $dropdownConfig.text_color | default "text-gray-700" }} {{ $dropdownConfig.hover_background | default "hover:bg-gray-50" }}" data-lang="{{ $lang }}">
|
||||
{{ .Language.LanguageName }}
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -14,8 +14,8 @@ Author: Jeroen van der Schee
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ if gt (len .images) 1 }}
|
||||
<button class="carousel-prev" aria-label="Précédent">❮</button>
|
||||
<button class="carousel-next" aria-label="Suivant">❯</button>
|
||||
<button class="carousel-prev" aria-label="{{ i18n "previous" }}">❮</button>
|
||||
<button class="carousel-next" aria-label="{{ i18n "next" }}">❯</button>
|
||||
|
||||
<div class="carousel-dots">
|
||||
{{ range $i, $_ := .images }}
|
||||
|
||||
Reference in New Issue
Block a user