From b9191b34951277e795284f0d1ac4e5fedcaa97b5 Mon Sep 17 00:00:00 2001 From: m Date: Wed, 1 Apr 2026 12:34:22 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20i18n=20pilot=20=E2=80=94=20shared=20JS?= =?UTF-8?q?=20snippet=20+=20ichbinotto.de=20translation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add shared/i18n.js: client-side language detection snippet. - Detects browser language via navigator.language - Falls back to German, stores preference in localStorage - Swaps text via data-de/data-en attributes on any element - Handles , <meta>, and regular elements - Optional toggle button via data-i18n-toggle attribute - Exposes window.onepagerI18n API for programmatic use Pilot implementation on ichbinotto.de: - All visible text annotated with data-de/data-en - Language toggle button in footer - Title and meta description translated Implements Gitea issue #1 (pilot phase). --- shared/i18n.js | 77 ++++++++++++++++++++++++ sites/ichbinotto.de/index.html | 103 ++++++++++++++++++--------------- 2 files changed, 133 insertions(+), 47 deletions(-) create mode 100644 shared/i18n.js diff --git a/shared/i18n.js b/shared/i18n.js new file mode 100644 index 0000000..44b3296 --- /dev/null +++ b/shared/i18n.js @@ -0,0 +1,77 @@ +/** + * i18n for onepager sites. + * + * Add data-de="..." data-en="..." to any translatable element. + * The element's initial innerHTML is the German default. + * + * Include at bottom of <body>: + * <script src="/shared/i18n.js"></script> + * + * Detection: localStorage override > navigator.language > German default. + * + * Optional language toggle: add data-i18n-toggle to a <button>. + * The button text updates to show the other language (DE/EN). + */ +(function () { + var SUPPORTED = ['de', 'en']; + var DEFAULT = 'de'; + var KEY = 'onepager-lang'; + + function detect() { + var stored = null; + try { stored = localStorage.getItem(KEY); } catch (e) { /* private browsing */ } + if (stored && SUPPORTED.indexOf(stored) !== -1) return stored; + var nav = (navigator.language || navigator.userLanguage || '').slice(0, 2).toLowerCase(); + return SUPPORTED.indexOf(nav) !== -1 ? nav : DEFAULT; + } + + function apply(lang) { + document.documentElement.lang = lang; + + var els = document.querySelectorAll('[data-de][data-en]'); + for (var i = 0; i < els.length; i++) { + var el = els[i]; + var val = el.getAttribute('data-' + lang); + if (val === null) continue; + var tag = el.tagName; + if (tag === 'TITLE') { + document.title = val; + } else if (tag === 'META') { + el.setAttribute('content', val); + } else { + el.innerHTML = val; + } + } + + // Update toggle buttons + var toggles = document.querySelectorAll('[data-i18n-toggle]'); + for (var j = 0; j < toggles.length; j++) { + toggles[j].textContent = lang === 'de' ? 'EN' : 'DE'; + toggles[j].setAttribute('aria-label', + lang === 'de' ? 'Switch to English' : 'Auf Deutsch wechseln'); + } + + try { localStorage.setItem(KEY, lang); } catch (e) { /* private browsing */ } + } + + function toggle() { + var current = document.documentElement.lang || DEFAULT; + apply(current === 'de' ? 'en' : 'de'); + } + + function init() { + apply(detect()); + var toggles = document.querySelectorAll('[data-i18n-toggle]'); + for (var k = 0; k < toggles.length; k++) { + toggles[k].addEventListener('click', toggle); + } + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } + + window.onepagerI18n = { apply: apply, toggle: toggle, detect: detect }; +})(); diff --git a/sites/ichbinotto.de/index.html b/sites/ichbinotto.de/index.html index 6b85368..1640f16 100644 --- a/sites/ichbinotto.de/index.html +++ b/sites/ichbinotto.de/index.html @@ -3,8 +3,8 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Otto — dein sideKIck - + Otto — dein sideKIck +