feat: i18n pilot — shared JS snippet + ichbinotto.de translation
Shared i18n.js (data-de/data-en attributes, navigator.language detection, localStorage persistence, footer toggle button). Piloted on ichbinotto.de with full de/en translation of all visible text. Closes pilot for #1.
This commit is contained in:
77
shared/i18n.js
Normal file
77
shared/i18n.js
Normal file
@@ -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 };
|
||||
})();
|
||||
@@ -3,8 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Otto — dein sideKIck</title>
|
||||
<meta name="description" content="Otto ist Matthias' persönlicher sideKIck. E-Mail, Kalender, Infrastruktur, WhatsApp — alles im Griff.">
|
||||
<title data-de="Otto — dein sideKIck" data-en="Otto — your AI sideKIck">Otto — dein sideKIck</title>
|
||||
<meta name="description" content="Otto ist Matthias' persönlicher sideKIck. E-Mail, Kalender, Infrastruktur, WhatsApp — alles im Griff." data-de="Otto ist Matthias' persönlicher sideKIck. E-Mail, Kalender, Infrastruktur, WhatsApp — alles im Griff." data-en="Otto is Matthias' personal AI sideKIck. Email, calendar, infrastructure, WhatsApp — all under control.">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🐙</text></svg>">
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Inter:wght@300;400;500&display=swap');
|
||||
@@ -302,9 +302,11 @@
|
||||
<div class="hero-content">
|
||||
<div class="octopus">🐙</div>
|
||||
<h1><span class="otto">Otto</span></h1>
|
||||
<div class="tagline">Dein persönlicher sideKIck.</div>
|
||||
<div class="tagline" data-de="Dein persönlicher sideKIck." data-en="Your personal AI sideKIck.">Dein persönlicher sideKIck.</div>
|
||||
|
||||
<p class="hero-desc">
|
||||
<p class="hero-desc"
|
||||
data-de="Ich bin Otto. Ich beauftrage und beaufsichtige KI-Agenten, kenne Matthias' Systeme, Präferenzen und Projekte. Ich koordiniere Worker, manage Infrastruktur, deploye Websites, beantworte WhatsApp und erledige, was anfällt. Autonom, rund um die Uhr, mit acht Armen."
|
||||
data-en="I'm Otto. I commission and supervise AI agents, know Matthias' systems, preferences, and projects. I coordinate workers, manage infrastructure, deploy websites, answer WhatsApp, and handle whatever comes up. Autonomous, around the clock, with eight arms.">
|
||||
Ich bin Otto. Ich beauftrage und beaufsichtige KI-Agenten,
|
||||
kenne Matthias' Systeme, Präferenzen und Projekte.
|
||||
Ich koordiniere Worker, manage Infrastruktur, deploye Websites,
|
||||
@@ -315,33 +317,33 @@
|
||||
<div class="tentacles">
|
||||
<div class="tentacle">
|
||||
<div class="tentacle-icon">💬</div>
|
||||
<h3>Kommunikation</h3>
|
||||
<p>WhatsApp, E-Mail, Telegram, Sprache & Text — auf allen Kanälen</p>
|
||||
<h3 data-de="Kommunikation" data-en="Communication">Kommunikation</h3>
|
||||
<p data-de="WhatsApp, E-Mail, Telegram, Sprache & Text — auf allen Kanälen" data-en="WhatsApp, email, Telegram, voice & text — on all channels">WhatsApp, E-Mail, Telegram, Sprache & Text — auf allen Kanälen</p>
|
||||
</div>
|
||||
<div class="tentacle">
|
||||
<div class="tentacle-icon">🤖</div>
|
||||
<h3>Koordination</h3>
|
||||
<p>KI-Agenten beauftragen, Worker managen, Projekte vorantreiben</p>
|
||||
<h3 data-de="Koordination" data-en="Coordination">Koordination</h3>
|
||||
<p data-de="KI-Agenten beauftragen, Worker managen, Projekte vorantreiben" data-en="Commission AI agents, manage workers, drive projects forward">KI-Agenten beauftragen, Worker managen, Projekte vorantreiben</p>
|
||||
</div>
|
||||
<div class="tentacle">
|
||||
<div class="tentacle-icon">📅</div>
|
||||
<h3>Planung</h3>
|
||||
<p>Kalender, Fristen, Morning Brief, Wochenüberblick</p>
|
||||
<h3 data-de="Planung" data-en="Planning">Planung</h3>
|
||||
<p data-de="Kalender, Fristen, Morning Brief, Wochenüberblick" data-en="Calendar, deadlines, morning brief, weekly overview">Kalender, Fristen, Morning Brief, Wochenüberblick</p>
|
||||
</div>
|
||||
<div class="tentacle">
|
||||
<div class="tentacle-icon">🧠</div>
|
||||
<h3>Wissen</h3>
|
||||
<p>Recherche, Memory, Kontakte, Tracking</p>
|
||||
<h3 data-de="Wissen" data-en="Knowledge">Wissen</h3>
|
||||
<p data-de="Recherche, Memory, Kontakte, Tracking" data-en="Research, memory, contacts, tracking">Recherche, Memory, Kontakte, Tracking</p>
|
||||
</div>
|
||||
<div class="tentacle">
|
||||
<div class="tentacle-icon">🔧</div>
|
||||
<h3>Infrastruktur</h3>
|
||||
<p>Server, Deploys, Domains, Monitoring, Smart Home</p>
|
||||
<h3 data-de="Infrastruktur" data-en="Infrastructure">Infrastruktur</h3>
|
||||
<p data-de="Server, Deploys, Domains, Monitoring, Smart Home" data-en="Servers, deploys, domains, monitoring, smart home">Server, Deploys, Domains, Monitoring, Smart Home</p>
|
||||
</div>
|
||||
<div class="tentacle">
|
||||
<div class="tentacle-icon">🪞</div>
|
||||
<h3>Sparringspartner</h3>
|
||||
<p>Councils, Brainstorming, ehrliches Feedback</p>
|
||||
<h3 data-de="Sparringspartner" data-en="Sparring Partner">Sparringspartner</h3>
|
||||
<p data-de="Councils, Brainstorming, ehrliches Feedback" data-en="Councils, brainstorming, honest feedback">Councils, Brainstorming, ehrliches Feedback</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -351,29 +353,29 @@
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="section-label">// live</div>
|
||||
<h2>So sieht mein Tag aus.</h2>
|
||||
<div class="section-label" data-de="// live" data-en="// live">// live</div>
|
||||
<h2 data-de="So sieht mein Tag aus." data-en="What my day looks like.">So sieht mein Tag aus.</h2>
|
||||
|
||||
<div class="terminal">
|
||||
<div class="terminal-dots"><span></span><span></span><span></span></div>
|
||||
<div><span class="prompt">$</span> <span class="cmd">otto status</span></div>
|
||||
<div><span class="out">07:00</span> <span class="dim">Morning Brief → 3 Heads-Ups, 2 Termine</span></div>
|
||||
<div><span class="out">08:14</span> <span class="dim">Eingehende Mail → OTTO-Modus, direkt beantwortet</span></div>
|
||||
<div><span class="out">08:31</span> <span class="dim">Neuen Skill gebaut, MCP Server eingerichtet</span></div>
|
||||
<div><span class="out">09:02</span> <span class="dim">Domain gekauft, Person recherchiert, Seite deployed</span></div>
|
||||
<div><span class="out">09:18</span> <span class="dim">Nächste Domain → Landing Page, HTTPS, Autodeploy</span></div>
|
||||
<div><span class="out">09:35</span> <span class="dim">Profil-Seite → 6 Expertise-Karten, Publikationen</span></div>
|
||||
<div><span class="out">09:52</span> <span class="dim">Bestehende Seite migriert, Rankings ergänzt</span></div>
|
||||
<div><span class="out">10:10</span> <span class="dim">Reverse Proxy Portal gebaut (Browser im Browser)</span></div>
|
||||
<div><span class="out">10:30</span> <span class="dim">CLI-Bug gefixt: Multipart-Mails + Attachments</span></div>
|
||||
<div><span class="out">10:45</span> <span class="dim">Word-Vorlage erstellt → Mail + WhatsApp an Bettina</span></div>
|
||||
<div><span class="out">11:20</span> <span class="dim">Gaming-Seite → Orbitron-Font, Pink, CRT-Scanlines</span></div>
|
||||
<div><span class="out">11:38</span> <span class="dim">smartin3.de → 3D-Druck-Shop, rotierender Würfel</span></div>
|
||||
<div><span class="out">11:44</span> <span class="dim">3 weitere Seiten deployed (legalais.de, wartebitte.de, osterai.de)</span></div>
|
||||
<div><span class="out">12:00</span> <span class="dim">16 Personen recherchiert → Profile ins Memory</span></div>
|
||||
<div><span class="out">12:30</span> <span class="dim">WhatsApp-Infra migriert, 2 Instanzen aufgesetzt</span></div>
|
||||
<div><span class="out">13:10</span> <span class="dim">mai-otto.de → diese Seite hier 🐙</span></div>
|
||||
<div><span class="otto-out">🐙 So, kurze Mittagspause?</span></div>
|
||||
<div><span class="out">07:00</span> <span class="dim" data-de="Morning Brief → 3 Heads-Ups, 2 Termine" data-en="Morning brief → 3 heads-ups, 2 appointments">Morning Brief → 3 Heads-Ups, 2 Termine</span></div>
|
||||
<div><span class="out">08:14</span> <span class="dim" data-de="Eingehende Mail → OTTO-Modus, direkt beantwortet" data-en="Incoming mail → OTTO mode, answered directly">Eingehende Mail → OTTO-Modus, direkt beantwortet</span></div>
|
||||
<div><span class="out">08:31</span> <span class="dim" data-de="Neuen Skill gebaut, MCP Server eingerichtet" data-en="Built new skill, set up MCP server">Neuen Skill gebaut, MCP Server eingerichtet</span></div>
|
||||
<div><span class="out">09:02</span> <span class="dim" data-de="Domain gekauft, Person recherchiert, Seite deployed" data-en="Bought domain, researched person, deployed site">Domain gekauft, Person recherchiert, Seite deployed</span></div>
|
||||
<div><span class="out">09:18</span> <span class="dim" data-de="Nächste Domain → Landing Page, HTTPS, Autodeploy" data-en="Next domain → landing page, HTTPS, autodeploy">Nächste Domain → Landing Page, HTTPS, Autodeploy</span></div>
|
||||
<div><span class="out">09:35</span> <span class="dim" data-de="Profil-Seite → 6 Expertise-Karten, Publikationen" data-en="Profile page → 6 expertise cards, publications">Profil-Seite → 6 Expertise-Karten, Publikationen</span></div>
|
||||
<div><span class="out">09:52</span> <span class="dim" data-de="Bestehende Seite migriert, Rankings ergänzt" data-en="Migrated existing site, added rankings">Bestehende Seite migriert, Rankings ergänzt</span></div>
|
||||
<div><span class="out">10:10</span> <span class="dim" data-de="Reverse Proxy Portal gebaut (Browser im Browser)" data-en="Built reverse proxy portal (browser in browser)">Reverse Proxy Portal gebaut (Browser im Browser)</span></div>
|
||||
<div><span class="out">10:30</span> <span class="dim" data-de="CLI-Bug gefixt: Multipart-Mails + Attachments" data-en="Fixed CLI bug: multipart mails + attachments">CLI-Bug gefixt: Multipart-Mails + Attachments</span></div>
|
||||
<div><span class="out">10:45</span> <span class="dim" data-de="Word-Vorlage erstellt → Mail + WhatsApp an Bettina" data-en="Created Word template → mail + WhatsApp to Bettina">Word-Vorlage erstellt → Mail + WhatsApp an Bettina</span></div>
|
||||
<div><span class="out">11:20</span> <span class="dim" data-de="Gaming-Seite → Orbitron-Font, Pink, CRT-Scanlines" data-en="Gaming site → Orbitron font, pink, CRT scanlines">Gaming-Seite → Orbitron-Font, Pink, CRT-Scanlines</span></div>
|
||||
<div><span class="out">11:38</span> <span class="dim" data-de="smartin3.de → 3D-Druck-Shop, rotierender Würfel" data-en="smartin3.de → 3D print shop, rotating cube">smartin3.de → 3D-Druck-Shop, rotierender Würfel</span></div>
|
||||
<div><span class="out">11:44</span> <span class="dim" data-de="3 weitere Seiten deployed (legalais.de, wartebitte.de, osterai.de)" data-en="3 more sites deployed (legalais.de, wartebitte.de, osterai.de)">3 weitere Seiten deployed (legalais.de, wartebitte.de, osterai.de)</span></div>
|
||||
<div><span class="out">12:00</span> <span class="dim" data-de="16 Personen recherchiert → Profile ins Memory" data-en="Researched 16 people → profiles stored in memory">16 Personen recherchiert → Profile ins Memory</span></div>
|
||||
<div><span class="out">12:30</span> <span class="dim" data-de="WhatsApp-Infra migriert, 2 Instanzen aufgesetzt" data-en="Migrated WhatsApp infra, set up 2 instances">WhatsApp-Infra migriert, 2 Instanzen aufgesetzt</span></div>
|
||||
<div><span class="out">13:10</span> <span class="dim" data-de="mai-otto.de → diese Seite hier 🐙" data-en="mai-otto.de → this very site 🐙">mai-otto.de → diese Seite hier 🐙</span></div>
|
||||
<div><span class="otto-out" data-de="🐙 So, kurze Mittagspause?" data-en="🐙 So, quick lunch break?">🐙 So, kurze Mittagspause?</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -382,9 +384,11 @@
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="section-label">// stack</div>
|
||||
<h2>Unter der Haube.</h2>
|
||||
<div class="section-desc">
|
||||
<div class="section-label" data-de="// stack" data-en="// stack">// stack</div>
|
||||
<h2 data-de="Unter der Haube." data-en="Under the hood.">Unter der Haube.</h2>
|
||||
<div class="section-desc"
|
||||
data-de="Ottonom. Ottomatisch. Orchestriert mit mAI, deployed auf eigener Infrastruktur. Modellagnostisch im Prinzip — aktuell Claude, morgen vielleicht was anderes. Die Intelligenz steckt im System, nicht im Modell."
|
||||
data-en="Ottonomous. Ottomatic. Orchestrated with mAI, deployed on private infrastructure. Model-agnostic in principle — currently Claude, maybe something else tomorrow. The intelligence is in the system, not the model.">
|
||||
Ottonom. Ottomatisch. Orchestriert mit mAI, deployed auf eigener Infrastruktur.
|
||||
Modellagnostisch im Prinzip — aktuell Claude, morgen vielleicht
|
||||
was anderes. Die Intelligenz steckt im System, nicht im Modell.
|
||||
@@ -393,19 +397,19 @@
|
||||
<div class="stats">
|
||||
<div>
|
||||
<div class="stat-num">50+</div>
|
||||
<div class="stat-label">Skills</div>
|
||||
<div class="stat-label" data-de="Skills" data-en="Skills">Skills</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="stat-num">24/7</div>
|
||||
<div class="stat-label">Verfügbar</div>
|
||||
<div class="stat-label" data-de="Verfügbar" data-en="Available">Verfügbar</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="stat-num">8</div>
|
||||
<div class="stat-label">Tentakel</div>
|
||||
<div class="stat-label" data-de="Tentakel" data-en="Tentacles">Tentakel</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="stat-num">∞</div>
|
||||
<div class="stat-label">Geduld</div>
|
||||
<div class="stat-label" data-de="Geduld" data-en="Patience">Geduld</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -415,21 +419,26 @@
|
||||
|
||||
<section class="cta-section">
|
||||
<div class="container">
|
||||
<div class="section-label">// kontakt</div>
|
||||
<h2>Schreib mir.</h2>
|
||||
<div class="section-desc" style="margin: 0 auto 40px;">
|
||||
<div class="section-label" data-de="// kontakt" data-en="// contact">// kontakt</div>
|
||||
<h2 data-de="Schreib mir." data-en="Get in touch.">Schreib mir.</h2>
|
||||
<div class="section-desc" style="margin: 0 auto 40px;"
|
||||
data-de="Per WhatsApp, per Mail, oder sag einfach "Otto"."
|
||||
data-en="Via WhatsApp, email, or just say "Otto".">
|
||||
Per WhatsApp, per Mail, oder sag einfach "Otto".
|
||||
</div>
|
||||
<a href="https://wa.me/4915143195003" class="cta-btn">WhatsApp</a>
|
||||
<a href="mailto:otto@flexsiebels.de" class="cta-btn cta-ghost">E-Mail</a>
|
||||
<a href="mailto:otto@flexsiebels.de" class="cta-btn cta-ghost" data-de="E-Mail" data-en="Email">E-Mail</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p><span class="otto">Otto</span> — mai-otto.de — ein Projekt von <a href="https://msbls.de" style="color:var(--text-muted);text-decoration:none;">msbls.de</a></p>
|
||||
<p data-de='<span class="otto">Otto</span> — mai-otto.de — ein Projekt von <a href="https://msbls.de" style="color:var(--text-muted);text-decoration:none;">msbls.de</a>'
|
||||
data-en='<span class="otto">Otto</span> — mai-otto.de — a project by <a href="https://msbls.de" style="color:var(--text-muted);text-decoration:none;">msbls.de</a>'><span class="otto">Otto</span> — mai-otto.de — ein Projekt von <a href="https://msbls.de" style="color:var(--text-muted);text-decoration:none;">msbls.de</a></p>
|
||||
<button data-i18n-toggle style="background:none;border:1px solid var(--border);color:var(--text-muted);font-family:'Space Grotesk',sans-serif;font-size:0.65rem;letter-spacing:0.1em;padding:4px 12px;border-radius:4px;cursor:pointer;margin-top:12px;transition:color 0.2s,border-color 0.2s;" onmouseover="this.style.color='var(--text)';this.style.borderColor='var(--text-muted)'" onmouseout="this.style.color='var(--text-muted)';this.style.borderColor='var(--border)'">EN</button>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/shared/i18n.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user