Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision 4885eabc577791921a46ec8cb5d38e2b4ae1c649)
+++ src/config/database.js	(revision 5e61b173e0211ca213200be10da0a4230dcc31cf)
@@ -43,4 +43,6 @@
   // Read-only/kijk-account: kan alles bekijken maar geen wijzigingen doen.
   ensureColumn('users', 'readonly', 'INTEGER DEFAULT 0');
+  // Persoonlijke interface-taal (nl|en|de). Null = volg de standaard (site/env/browser).
+  ensureColumn('users', 'lang', 'TEXT');
   // Site-level moderation toggle. 'trust' = auto-approve, 'moderate' = pending until reviewed.
   ensureColumn('sites', 'comments_moderation_mode', "TEXT DEFAULT 'moderate'");
Index: src/middleware/render.js
===================================================================
--- src/middleware/render.js	(revision 4885eabc577791921a46ec8cb5d38e2b4ae1c649)
+++ src/middleware/render.js	(revision 5e61b173e0211ca213200be10da0a4230dcc31cf)
@@ -54,6 +54,6 @@
   // avatar-wijziging of rolwissel) zichzelf herstelt zonder opnieuw inloggen.
   if (_u && _u.id) {
-    const _fresh = db.prepare('SELECT avatar_url, role FROM users WHERE id = ?').get(_u.id);
-    if (_fresh) _u = { ..._u, avatar_url: _fresh.avatar_url, role: _fresh.role };
+    const _fresh = db.prepare('SELECT avatar_url, role, lang FROM users WHERE id = ?').get(_u.id);
+    if (_fresh) _u = { ..._u, avatar_url: _fresh.avatar_url, role: _fresh.role, lang: _fresh.lang };
   }
   const userOwnsSite = !!(_u && _u.role !== 'god' &&
@@ -78,6 +78,10 @@
   const canSeeBeheer = !!(_u && (_role === 'god' || _role === 'admin' || _role === 'kijker' || userOwnsSite));
 
-  // Interface-taal (bezoeker-instelbaar): sessie-keuze → browser-taal → nl.
-  const _lang = resolveLang(req);
+  // Interface-taal: sessie-keuze (deze sessie) → eigen voorkeur van de ingelogde
+  // gebruiker (users.lang) → admin-ingestelde standaard (Beheer) → env → browser → nl.
+  const _lang = resolveLang(req, {
+    userLang: _u && _u.lang,
+    defaultLang: getSetting('default_lang'),
+  });
 
   // Common locals
Index: src/routes/account.js
===================================================================
--- src/routes/account.js	(revision 4885eabc577791921a46ec8cb5d38e2b4ae1c649)
+++ src/routes/account.js	(revision 5e61b173e0211ca213200be10da0a4230dcc31cf)
@@ -26,4 +26,5 @@
 import { googleConfigured } from '../config/google.js';
 import { toWebp } from '../services/ImageWebpService.js';
+import { SUPPORTED } from '../services/i18n.js';
 
 const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -60,5 +61,5 @@
 router.get('/', requireAuth, (req, res) => {
   const account = db.prepare(`
-    SELECT id, username, email, role, bio, avatar_url, created_at, password_hash, google_sub
+    SELECT id, username, email, role, bio, avatar_url, created_at, password_hash, google_sub, lang
     FROM users WHERE id = ?
   `).get(req.session.user.id);
@@ -80,4 +81,17 @@
 });
 
+// ==================== PERSOONLIJKE INTERFACE-TAAL ====================
+// Slaat de taalkeuze op het account op (reist mee over apparaten/sessies) én
+// zet 'm meteen in de sessie zodat 't direct effect heeft.
+router.post('/lang', requireAuth, (req, res) => {
+  const code = SUPPORTED.includes(req.body.lang) ? req.body.lang : null;
+  if (code) {
+    db.prepare('UPDATE users SET lang = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(code, req.session.user.id);
+    req.session.user.lang = code;
+    req.session.lang = code;
+  }
+  res.redirect('/account?success=' + encodeURIComponent('Taal opgeslagen'));
+});
+
 // De site die deze gebruiker mag bewerken vanuit z'n account: z'n eigen site
 // (owner_id), of voor een god de primaire site. Null als er niets is.
Index: src/routes/admin-settings.js
===================================================================
--- src/routes/admin-settings.js	(revision 4885eabc577791921a46ec8cb5d38e2b4ae1c649)
+++ src/routes/admin-settings.js	(revision 5e61b173e0211ca213200be10da0a4230dcc31cf)
@@ -22,4 +22,5 @@
 import { requireGod } from '../middleware/auth.js';
 import { getTenancy, setTenancy, getSetting, setSetting } from '../services/SettingsService.js';
+import { SUPPORTED } from '../services/i18n.js';
 import { mailerStatus, sendMail } from '../config/mailer.js';
 import { entitlementStatus, premiumUnlocked } from '../services/PatreonService.js';
@@ -78,4 +79,5 @@
     hubHeroImage: getSetting('hub_hero_image') || '',
     hubHeroOverlay: clampOverlay(getSetting('hub_hero_overlay')),
+    defaultLang: getSetting('default_lang') || '',
     premium: entitlementStatus(),
     google: {
@@ -108,4 +110,9 @@
       }
       setTenancy(req.body.tenancy); // valideert naar solo | hub | circle
+    }
+    if (typeof req.body.default_lang !== 'undefined') {
+      // Standaardtaal voor bezoekers (leeg = volg env/browser). Valideert tegen NL/EN/DE.
+      const dl = (req.body.default_lang || '').toString().toLowerCase();
+      setSetting('default_lang', SUPPORTED.includes(dl) ? dl : '');
     }
     if (typeof req.body.hub_title !== 'undefined') {
Index: src/routes/lang.js
===================================================================
--- src/routes/lang.js	(revision 4885eabc577791921a46ec8cb5d38e2b4ae1c649)
+++ src/routes/lang.js	(revision 5e61b173e0211ca213200be10da0a4230dcc31cf)
@@ -3,4 +3,5 @@
 import express from 'express';
 import { SUPPORTED } from '../services/i18n.js';
+import db from '../config/database.js';
 
 const router = express.Router();
@@ -9,4 +10,12 @@
   const code = SUPPORTED.includes(req.params.code) ? req.params.code : 'nl';
   if (req.session) req.session.lang = code;
+  // Ingelogd? Bewaar de keuze ook op het account zodat 'ie meereist over
+  // apparaten/sessies (niet alleen deze sessie-cookie).
+  if (req.session && req.session.user && req.session.user.id) {
+    try {
+      db.prepare('UPDATE users SET lang = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(code, req.session.user.id);
+      req.session.user.lang = code;
+    } catch { /* lang-kolom ontbreekt op een oude DB → sessie-only, geen breuk */ }
+  }
   // Veilige terug-URL: alleen een intern pad (geen open redirect).
   let back = (typeof req.query.r === 'string') ? req.query.r : '';
Index: src/services/i18n.js
===================================================================
--- src/services/i18n.js	(revision 4885eabc577791921a46ec8cb5d38e2b4ae1c649)
+++ src/services/i18n.js	(revision 5e61b173e0211ca213200be10da0a4230dcc31cf)
@@ -964,4 +964,9 @@
     'prcv.placeholder': 'Bericht…',
     'prcv.send': 'Stuur',
+    'acct.lang_label': 'Taal',
+    'acct.lang_hint': '— jouw persoonlijke interface-taal; reist mee over apparaten en sessies.',
+    'aset.default_lang': 'Standaardtaal voor bezoekers',
+    'aset.default_lang_hint': 'Wat nieuwe bezoekers zien voordat ze zelf een taal kiezen. Een ingelogde gebruiker met een eigen voorkeur ziet díe.',
+    'aset.default_lang_auto': 'Automatisch (browser-taal)',
   },
   en: {
@@ -1910,4 +1915,9 @@
     'prcv.placeholder': 'Message…',
     'prcv.send': 'Send',
+    'acct.lang_label': 'Language',
+    'acct.lang_hint': '— your personal interface language; travels with you across devices and sessions.',
+    'aset.default_lang': 'Default language for visitors',
+    'aset.default_lang_hint': 'What new visitors see before they pick a language themselves. A logged-in user with their own preference sees that instead.',
+    'aset.default_lang_auto': 'Automatic (browser language)',
   },
   de: {
@@ -2856,4 +2866,9 @@
     'prcv.placeholder': 'Nachricht…',
     'prcv.send': 'Senden',
+    'acct.lang_label': 'Sprache',
+    'acct.lang_hint': '— deine persönliche Interface-Sprache; reist mit dir über Geräte und Sitzungen.',
+    'aset.default_lang': 'Standardsprache für Besucher',
+    'aset.default_lang_hint': 'Was neue Besucher sehen, bevor sie selbst eine Sprache wählen. Ein angemeldeter Benutzer mit eigener Voreinstellung sieht diese.',
+    'aset.default_lang_auto': 'Automatisch (Browsersprache)',
   },
 };
@@ -2869,9 +2884,13 @@
 // Bepaal de taal voor dit request: expliciete sessie-keuze → instance-standaard
 // (env KLONKT_DEFAULT_LANG, bv. 'de' voor een Duitse site) → browser-taal → nl.
-export function resolveLang(req) {
+export function resolveLang(req, opts = {}) {
   const s = req && req.session && req.session.lang;
-  if (s && SUPPORTED.includes(s)) return s;            // bezoeker koos zelf
+  if (s && SUPPORTED.includes(s)) return s;             // bezoeker koos zelf (deze sessie)
+  const u = (opts.userLang || '').toLowerCase();
+  if (SUPPORTED.includes(u)) return u;                  // ingelogde gebruiker: eigen voorkeur
+  const d = (opts.defaultLang || '').toLowerCase();
+  if (SUPPORTED.includes(d)) return d;                  // admin-ingestelde standaard (Beheer/DB)
   const envDefault = (process.env.KLONKT_DEFAULT_LANG || '').toLowerCase();
-  if (SUPPORTED.includes(envDefault)) return envDefault; // per-instance standaard
+  if (SUPPORTED.includes(envDefault)) return envDefault; // per-instance standaard (env)
   const al = ((req && req.headers && req.headers['accept-language']) || '').toLowerCase();
   const first = al.split(',')[0].trim().slice(0, 2);
Index: src/views/pages/account.ejs
===================================================================
--- src/views/pages/account.ejs	(revision 4885eabc577791921a46ec8cb5d38e2b4ae1c649)
+++ src/views/pages/account.ejs	(revision 5e61b173e0211ca213200be10da0a4230dcc31cf)
@@ -90,4 +90,24 @@
   </section>
 
+  <%# ── PERSOONLIJKE TAAL ───────────────────────────────────── %>
+  <% if (canMutate) { %>
+  <section class="ax-card">
+    <div class="ax-card-title"><%= t('acct.lang_label') %></div>
+    <form action="/account/lang" method="post" class="ax-form">
+      <label class="ax-field">
+        <span><%= t('acct.lang_label') %> <small><%= t('acct.lang_hint') %></small></span>
+        <select name="lang">
+          <% langs.forEach(function(l){ var sel = (account && account.lang) ? (account.lang === l.code) : l.active; %>
+            <option value="<%= l.code %>"<%= sel ? ' selected' : '' %>><%= l.name %></option>
+          <% }); %>
+        </select>
+      </label>
+      <div class="ax-form-actions">
+        <button type="submit" class="ax-btn ax-btn-primary"><%= t('acct.save') %></button>
+      </div>
+    </form>
+  </section>
+  <% } %>
+
   <%# ── SITE (eigenaar mag de naam van z'n site aanpassen) ── %>
   <% if (typeof editableSite !== 'undefined' && editableSite && canMutate) { %>
Index: src/views/pages/admin-settings.ejs
===================================================================
--- src/views/pages/admin-settings.ejs	(revision 4885eabc577791921a46ec8cb5d38e2b4ae1c649)
+++ src/views/pages/admin-settings.ejs	(revision 5e61b173e0211ca213200be10da0a4230dcc31cf)
@@ -35,4 +35,22 @@
           <small><%= t('aset.circle_desc') %></small>
         </span>
+      </label>
+      <button type="submit" class="btn btn-primary"><%= t('aset.save') %></button>
+    </form>
+  </section>
+
+  <%# ── Standaardtaal voor bezoekers ── %>
+  <section class="set-card" style="margin-top:1rem">
+    <h2><%= t('aset.default_lang') %></h2>
+    <p class="set-help"><%= t('aset.default_lang_hint') %></p>
+    <form method="post" action="/admin/settings" class="set-form">
+      <label class="set-field">
+        <span><%= t('aset.default_lang') %></span>
+        <select name="default_lang">
+          <option value=""<%= !defaultLang ? ' selected' : '' %>><%= t('aset.default_lang_auto') %></option>
+          <% langs.forEach(function(l){ %>
+            <option value="<%= l.code %>"<%= defaultLang === l.code ? ' selected' : '' %>><%= l.name %></option>
+          <% }); %>
+        </select>
       </label>
       <button type="submit" class="btn btn-primary"><%= t('aset.save') %></button>
