Changeset 5e61b17 in Klonkt
- Timestamp:
- 06/21/2026 03:16:51 PM (3 months ago)
- Branches:
- main
- Children:
- 11b3ba5
- Parents:
- 4885eab
- Location:
- src
- Files:
-
- 8 edited
-
config/database.js (modified) (1 diff)
-
middleware/render.js (modified) (2 diffs)
-
routes/account.js (modified) (3 diffs)
-
routes/admin-settings.js (modified) (3 diffs)
-
routes/lang.js (modified) (2 diffs)
-
services/i18n.js (modified) (4 diffs)
-
views/pages/account.ejs (modified) (1 diff)
-
views/pages/admin-settings.ejs (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/config/database.js
r4885eab r5e61b17 43 43 // Read-only/kijk-account: kan alles bekijken maar geen wijzigingen doen. 44 44 ensureColumn('users', 'readonly', 'INTEGER DEFAULT 0'); 45 // Persoonlijke interface-taal (nl|en|de). Null = volg de standaard (site/env/browser). 46 ensureColumn('users', 'lang', 'TEXT'); 45 47 // Site-level moderation toggle. 'trust' = auto-approve, 'moderate' = pending until reviewed. 46 48 ensureColumn('sites', 'comments_moderation_mode', "TEXT DEFAULT 'moderate'"); -
src/middleware/render.js
r4885eab r5e61b17 54 54 // avatar-wijziging of rolwissel) zichzelf herstelt zonder opnieuw inloggen. 55 55 if (_u && _u.id) { 56 const _fresh = db.prepare('SELECT avatar_url, role FROM users WHERE id = ?').get(_u.id);57 if (_fresh) _u = { ..._u, avatar_url: _fresh.avatar_url, role: _fresh.role };56 const _fresh = db.prepare('SELECT avatar_url, role, lang FROM users WHERE id = ?').get(_u.id); 57 if (_fresh) _u = { ..._u, avatar_url: _fresh.avatar_url, role: _fresh.role, lang: _fresh.lang }; 58 58 } 59 59 const userOwnsSite = !!(_u && _u.role !== 'god' && … … 78 78 const canSeeBeheer = !!(_u && (_role === 'god' || _role === 'admin' || _role === 'kijker' || userOwnsSite)); 79 79 80 // Interface-taal (bezoeker-instelbaar): sessie-keuze → browser-taal → nl. 81 const _lang = resolveLang(req); 80 // Interface-taal: sessie-keuze (deze sessie) → eigen voorkeur van de ingelogde 81 // gebruiker (users.lang) → admin-ingestelde standaard (Beheer) → env → browser → nl. 82 const _lang = resolveLang(req, { 83 userLang: _u && _u.lang, 84 defaultLang: getSetting('default_lang'), 85 }); 82 86 83 87 // Common locals -
src/routes/account.js
r4885eab r5e61b17 26 26 import { googleConfigured } from '../config/google.js'; 27 27 import { toWebp } from '../services/ImageWebpService.js'; 28 import { SUPPORTED } from '../services/i18n.js'; 28 29 29 30 const __dirname = path.dirname(fileURLToPath(import.meta.url)); … … 60 61 router.get('/', requireAuth, (req, res) => { 61 62 const account = db.prepare(` 62 SELECT id, username, email, role, bio, avatar_url, created_at, password_hash, google_sub 63 SELECT id, username, email, role, bio, avatar_url, created_at, password_hash, google_sub, lang 63 64 FROM users WHERE id = ? 64 65 `).get(req.session.user.id); … … 80 81 }); 81 82 83 // ==================== PERSOONLIJKE INTERFACE-TAAL ==================== 84 // Slaat de taalkeuze op het account op (reist mee over apparaten/sessies) én 85 // zet 'm meteen in de sessie zodat 't direct effect heeft. 86 router.post('/lang', requireAuth, (req, res) => { 87 const code = SUPPORTED.includes(req.body.lang) ? req.body.lang : null; 88 if (code) { 89 db.prepare('UPDATE users SET lang = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(code, req.session.user.id); 90 req.session.user.lang = code; 91 req.session.lang = code; 92 } 93 res.redirect('/account?success=' + encodeURIComponent('Taal opgeslagen')); 94 }); 95 82 96 // De site die deze gebruiker mag bewerken vanuit z'n account: z'n eigen site 83 97 // (owner_id), of voor een god de primaire site. Null als er niets is. -
src/routes/admin-settings.js
r4885eab r5e61b17 22 22 import { requireGod } from '../middleware/auth.js'; 23 23 import { getTenancy, setTenancy, getSetting, setSetting } from '../services/SettingsService.js'; 24 import { SUPPORTED } from '../services/i18n.js'; 24 25 import { mailerStatus, sendMail } from '../config/mailer.js'; 25 26 import { entitlementStatus, premiumUnlocked } from '../services/PatreonService.js'; … … 78 79 hubHeroImage: getSetting('hub_hero_image') || '', 79 80 hubHeroOverlay: clampOverlay(getSetting('hub_hero_overlay')), 81 defaultLang: getSetting('default_lang') || '', 80 82 premium: entitlementStatus(), 81 83 google: { … … 108 110 } 109 111 setTenancy(req.body.tenancy); // valideert naar solo | hub | circle 112 } 113 if (typeof req.body.default_lang !== 'undefined') { 114 // Standaardtaal voor bezoekers (leeg = volg env/browser). Valideert tegen NL/EN/DE. 115 const dl = (req.body.default_lang || '').toString().toLowerCase(); 116 setSetting('default_lang', SUPPORTED.includes(dl) ? dl : ''); 110 117 } 111 118 if (typeof req.body.hub_title !== 'undefined') { -
src/routes/lang.js
r4885eab r5e61b17 3 3 import express from 'express'; 4 4 import { SUPPORTED } from '../services/i18n.js'; 5 import db from '../config/database.js'; 5 6 6 7 const router = express.Router(); … … 9 10 const code = SUPPORTED.includes(req.params.code) ? req.params.code : 'nl'; 10 11 if (req.session) req.session.lang = code; 12 // Ingelogd? Bewaar de keuze ook op het account zodat 'ie meereist over 13 // apparaten/sessies (niet alleen deze sessie-cookie). 14 if (req.session && req.session.user && req.session.user.id) { 15 try { 16 db.prepare('UPDATE users SET lang = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(code, req.session.user.id); 17 req.session.user.lang = code; 18 } catch { /* lang-kolom ontbreekt op een oude DB → sessie-only, geen breuk */ } 19 } 11 20 // Veilige terug-URL: alleen een intern pad (geen open redirect). 12 21 let back = (typeof req.query.r === 'string') ? req.query.r : ''; -
src/services/i18n.js
r4885eab r5e61b17 964 964 'prcv.placeholder': 'Bericht…', 965 965 'prcv.send': 'Stuur', 966 'acct.lang_label': 'Taal', 967 'acct.lang_hint': '— jouw persoonlijke interface-taal; reist mee over apparaten en sessies.', 968 'aset.default_lang': 'Standaardtaal voor bezoekers', 969 'aset.default_lang_hint': 'Wat nieuwe bezoekers zien voordat ze zelf een taal kiezen. Een ingelogde gebruiker met een eigen voorkeur ziet díe.', 970 'aset.default_lang_auto': 'Automatisch (browser-taal)', 966 971 }, 967 972 en: { … … 1910 1915 'prcv.placeholder': 'Message…', 1911 1916 'prcv.send': 'Send', 1917 'acct.lang_label': 'Language', 1918 'acct.lang_hint': '— your personal interface language; travels with you across devices and sessions.', 1919 'aset.default_lang': 'Default language for visitors', 1920 '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.', 1921 'aset.default_lang_auto': 'Automatic (browser language)', 1912 1922 }, 1913 1923 de: { … … 2856 2866 'prcv.placeholder': 'Nachricht…', 2857 2867 'prcv.send': 'Senden', 2868 'acct.lang_label': 'Sprache', 2869 'acct.lang_hint': '— deine persönliche Interface-Sprache; reist mit dir über Geräte und Sitzungen.', 2870 'aset.default_lang': 'Standardsprache für Besucher', 2871 'aset.default_lang_hint': 'Was neue Besucher sehen, bevor sie selbst eine Sprache wählen. Ein angemeldeter Benutzer mit eigener Voreinstellung sieht diese.', 2872 'aset.default_lang_auto': 'Automatisch (Browsersprache)', 2858 2873 }, 2859 2874 }; … … 2869 2884 // Bepaal de taal voor dit request: expliciete sessie-keuze → instance-standaard 2870 2885 // (env KLONKT_DEFAULT_LANG, bv. 'de' voor een Duitse site) → browser-taal → nl. 2871 export function resolveLang(req ) {2886 export function resolveLang(req, opts = {}) { 2872 2887 const s = req && req.session && req.session.lang; 2873 if (s && SUPPORTED.includes(s)) return s; // bezoeker koos zelf 2888 if (s && SUPPORTED.includes(s)) return s; // bezoeker koos zelf (deze sessie) 2889 const u = (opts.userLang || '').toLowerCase(); 2890 if (SUPPORTED.includes(u)) return u; // ingelogde gebruiker: eigen voorkeur 2891 const d = (opts.defaultLang || '').toLowerCase(); 2892 if (SUPPORTED.includes(d)) return d; // admin-ingestelde standaard (Beheer/DB) 2874 2893 const envDefault = (process.env.KLONKT_DEFAULT_LANG || '').toLowerCase(); 2875 if (SUPPORTED.includes(envDefault)) return envDefault; // per-instance standaard 2894 if (SUPPORTED.includes(envDefault)) return envDefault; // per-instance standaard (env) 2876 2895 const al = ((req && req.headers && req.headers['accept-language']) || '').toLowerCase(); 2877 2896 const first = al.split(',')[0].trim().slice(0, 2); -
src/views/pages/account.ejs
r4885eab r5e61b17 90 90 </section> 91 91 92 <%# ── PERSOONLIJKE TAAL ───────────────────────────────────── %> 93 <% if (canMutate) { %> 94 <section class="ax-card"> 95 <div class="ax-card-title"><%= t('acct.lang_label') %></div> 96 <form action="/account/lang" method="post" class="ax-form"> 97 <label class="ax-field"> 98 <span><%= t('acct.lang_label') %> <small><%= t('acct.lang_hint') %></small></span> 99 <select name="lang"> 100 <% langs.forEach(function(l){ var sel = (account && account.lang) ? (account.lang === l.code) : l.active; %> 101 <option value="<%= l.code %>"<%= sel ? ' selected' : '' %>><%= l.name %></option> 102 <% }); %> 103 </select> 104 </label> 105 <div class="ax-form-actions"> 106 <button type="submit" class="ax-btn ax-btn-primary"><%= t('acct.save') %></button> 107 </div> 108 </form> 109 </section> 110 <% } %> 111 92 112 <%# ── SITE (eigenaar mag de naam van z'n site aanpassen) ── %> 93 113 <% if (typeof editableSite !== 'undefined' && editableSite && canMutate) { %> -
src/views/pages/admin-settings.ejs
r4885eab r5e61b17 35 35 <small><%= t('aset.circle_desc') %></small> 36 36 </span> 37 </label> 38 <button type="submit" class="btn btn-primary"><%= t('aset.save') %></button> 39 </form> 40 </section> 41 42 <%# ── Standaardtaal voor bezoekers ── %> 43 <section class="set-card" style="margin-top:1rem"> 44 <h2><%= t('aset.default_lang') %></h2> 45 <p class="set-help"><%= t('aset.default_lang_hint') %></p> 46 <form method="post" action="/admin/settings" class="set-form"> 47 <label class="set-field"> 48 <span><%= t('aset.default_lang') %></span> 49 <select name="default_lang"> 50 <option value=""<%= !defaultLang ? ' selected' : '' %>><%= t('aset.default_lang_auto') %></option> 51 <% langs.forEach(function(l){ %> 52 <option value="<%= l.code %>"<%= defaultLang === l.code ? ' selected' : '' %>><%= l.name %></option> 53 <% }); %> 54 </select> 37 55 </label> 38 56 <button type="submit" class="btn btn-primary"><%= t('aset.save') %></button>
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)