| 1 | // i18n — eenvoudige interface-vertaling (UI-strings), bezoeker-instelbaar.
|
|---|
| 2 | //
|
|---|
| 3 | // Taalkeuze: req.session.lang (gezet via /lang/:code) → anders browser-taal
|
|---|
| 4 | // (Accept-Language) → anders 'nl'. De helper t(lang, key, vars) zoekt de string op
|
|---|
| 5 | // in DICT[lang], valt terug op 'nl', dan op de key zelf. Alleen INTERFACE-teksten;
|
|---|
| 6 | // door gebruikers geschreven content (posts, sitenaam, bio) wordt niet vertaald.
|
|---|
| 7 |
|
|---|
| 8 | export const SUPPORTED = ['nl', 'en', 'de'];
|
|---|
| 9 | export const LANG_NAMES = { nl: 'Nederlands', en: 'English', de: 'Deutsch' };
|
|---|
| 10 |
|
|---|
| 11 | const DICT = {
|
|---|
| 12 | nl: {
|
|---|
| 13 | 'nav.back_to_site': '← Terug naar site',
|
|---|
| 14 | 'nav.home': 'Home',
|
|---|
| 15 | 'nav.archive': 'Archief',
|
|---|
| 16 | 'nav.search': 'Zoeken',
|
|---|
| 17 | 'nav.theme': 'Thema wisselen',
|
|---|
| 18 | 'nav.install': 'App installeren',
|
|---|
| 19 | 'nav.login': 'Inloggen',
|
|---|
| 20 | 'nav.logout': 'Uitloggen',
|
|---|
| 21 | 'nav.admin': 'Beheer',
|
|---|
| 22 | 'nav.account': 'Account',
|
|---|
| 23 | 'nav.profile': 'Profiel',
|
|---|
| 24 | 'nav.favorites': 'Favorieten',
|
|---|
| 25 | 'nav.new_post': 'Nieuwe post',
|
|---|
| 26 | 'nav.language': 'Taal',
|
|---|
| 27 | 'switch.agenda': 'Agenda',
|
|---|
| 28 | 'switch.solo': 'Solo',
|
|---|
| 29 | 'switch.circle': 'Cirkel',
|
|---|
| 30 | 'switch.timeline': 'Tijdlijn',
|
|---|
| 31 | 'switch.grid': 'Grid',
|
|---|
| 32 | 'postnav.newer': 'Nieuwer',
|
|---|
| 33 | 'postnav.older': 'Ouder',
|
|---|
| 34 | 'postnav.newest': 'Nieuwste post',
|
|---|
| 35 | 'postnav.oldest': 'Oudste post',
|
|---|
| 36 | 'footer.subscribe_cta': 'Blijf op de hoogte',
|
|---|
| 37 | 'footer.subscribe': 'Inschrijven',
|
|---|
| 38 | 'footer.install': 'Installeer app',
|
|---|
| 39 | 'common.email_placeholder': 'jouw@email.nl',
|
|---|
| 40 | 'common.back_to_admin': '← Beheer',
|
|---|
| 41 | // Agenda (publiek)
|
|---|
| 42 | 'agenda.title': 'Agenda',
|
|---|
| 43 | 'agenda.empty': 'Geen aangekondigde evenementen op dit moment.',
|
|---|
| 44 | 'agenda.tickets': 'Tickets',
|
|---|
| 45 | 'agenda.notify_h': 'Mis geen evenement',
|
|---|
| 46 | 'agenda.notify_sub': 'Laat je e-mail achter en je krijgt een seintje bij een nieuw evenement. Uitschrijven kan altijd.',
|
|---|
| 47 | 'agenda.notify_btn': 'Houd me op de hoogte',
|
|---|
| 48 | 'agenda.msg_done': '✓ Je staat op de lijst — je hoort het zodra er een evenement wordt aangekondigd.',
|
|---|
| 49 | 'agenda.msg_check': '✉ Check je mail om je aanmelding te bevestigen.',
|
|---|
| 50 | // Downloads (publiek)
|
|---|
| 51 | 'downloads.title': 'Downloads',
|
|---|
| 52 | 'downloads.sub': 'Gratis te downloaden — laat je e-mailadres achter en je krijgt het bestand.',
|
|---|
| 53 | 'downloads.empty': 'Er staan momenteel geen downloads klaar.',
|
|---|
| 54 | 'downloads.btn': '⬇ Download',
|
|---|
| 55 | // Beheer-dashboard
|
|---|
| 56 | 'admin.title': 'Beheer',
|
|---|
| 57 | 'admin.tagline_solo': 'Solo-modus — jouw site.',
|
|---|
| 58 | 'admin.tagline_hub': 'Hub-modus — bedrijfssite met gebruikers, elk hun eigen Klonkt Hub.',
|
|---|
| 59 | 'admin.b_sites': '🌐 Sites', 'admin.b_users': '👥 Gebruikers', 'admin.b_audio': '🎵 Audio',
|
|---|
| 60 | 'admin.b_playlists': '📃 Playlists', 'admin.b_comments': '💬 Reacties', 'admin.b_seo': '🔎 SEO',
|
|---|
| 61 | 'admin.b_settings': '⚙️ Instellingen', 'admin.b_newpost': '✍️ Nieuwe post', 'admin.b_look': '🎨 Uiterlijk',
|
|---|
| 62 | 'admin.b_makesite': '🎨 Maak je site aan', 'admin.b_circle': '🔗 Cirkel', 'admin.b_stats': '📊 Statistieken',
|
|---|
| 63 | 'admin.b_newsletter': '✉️ Nieuwsbrief', 'admin.b_perskit': '📰 Perskit', 'admin.b_downloads': '⬇ Downloads',
|
|---|
| 64 | 'admin.b_linkbio': '🔗 Link-in-bio', 'admin.b_agenda': '📅 Agenda', 'admin.b_google': '🔑 Google',
|
|---|
| 65 | 'admin.b_updates': '🔄 Updates', 'admin.b_help': '📖 Handleiding',
|
|---|
| 66 | 'admin.st_users': 'Gebruikers', 'admin.st_sites': 'Sites', 'admin.st_posts': 'Posts', 'admin.st_published': 'Gepubliceerd',
|
|---|
| 67 | 'admin.sec_posts': 'Posts', 'admin.sec_sites': 'Sites', 'admin.sec_users': 'Users',
|
|---|
| 68 | 'admin.draft': 'Concept', 'admin.edit': 'Bewerken', 'admin.view': 'Bekijk',
|
|---|
| 69 | 'admin.th_slug': 'Slug', 'admin.th_title': 'Titel', 'admin.th_owner': 'Eigenaar', 'admin.th_created': 'Aangemaakt',
|
|---|
| 70 | 'admin.th_username': 'Gebruikersnaam', 'admin.th_email': 'E-mail', 'admin.th_role': 'Rol', 'admin.th_joined': 'Lid sinds',
|
|---|
| 71 | },
|
|---|
| 72 | en: {
|
|---|
| 73 | 'nav.back_to_site': '← Back to site',
|
|---|
| 74 | 'nav.home': 'Home',
|
|---|
| 75 | 'nav.archive': 'Archive',
|
|---|
| 76 | 'nav.search': 'Search',
|
|---|
| 77 | 'nav.theme': 'Toggle theme',
|
|---|
| 78 | 'nav.install': 'Install app',
|
|---|
| 79 | 'nav.login': 'Log in',
|
|---|
| 80 | 'nav.logout': 'Log out',
|
|---|
| 81 | 'nav.admin': 'Admin',
|
|---|
| 82 | 'nav.account': 'Account',
|
|---|
| 83 | 'nav.profile': 'Profile',
|
|---|
| 84 | 'nav.favorites': 'Favorites',
|
|---|
| 85 | 'nav.new_post': 'New post',
|
|---|
| 86 | 'nav.language': 'Language',
|
|---|
| 87 | 'switch.agenda': 'Agenda',
|
|---|
| 88 | 'switch.solo': 'Solo',
|
|---|
| 89 | 'switch.circle': 'Circle',
|
|---|
| 90 | 'switch.timeline': 'Timeline',
|
|---|
| 91 | 'switch.grid': 'Grid',
|
|---|
| 92 | 'postnav.newer': 'Newer',
|
|---|
| 93 | 'postnav.older': 'Older',
|
|---|
| 94 | 'postnav.newest': 'Newest post',
|
|---|
| 95 | 'postnav.oldest': 'Oldest post',
|
|---|
| 96 | 'footer.subscribe_cta': 'Stay in the loop',
|
|---|
| 97 | 'footer.subscribe': 'Subscribe',
|
|---|
| 98 | 'footer.install': 'Install app',
|
|---|
| 99 | 'common.email_placeholder': 'you@email.com',
|
|---|
| 100 | 'common.back_to_admin': '← Admin',
|
|---|
| 101 | 'agenda.title': 'Agenda',
|
|---|
| 102 | 'agenda.empty': 'No announced events at the moment.',
|
|---|
| 103 | 'agenda.tickets': 'Tickets',
|
|---|
| 104 | 'agenda.notify_h': 'Never miss an event',
|
|---|
| 105 | 'agenda.notify_sub': 'Leave your email and we’ll let you know about new events. Unsubscribe any time.',
|
|---|
| 106 | 'agenda.notify_btn': 'Keep me posted',
|
|---|
| 107 | 'agenda.msg_done': '✓ You’re on the list — we’ll let you know when an event is announced.',
|
|---|
| 108 | 'agenda.msg_check': '✉ Check your email to confirm your sign-up.',
|
|---|
| 109 | 'downloads.title': 'Downloads',
|
|---|
| 110 | 'downloads.sub': 'Free to download — leave your email and you’ll get the file.',
|
|---|
| 111 | 'downloads.empty': 'No downloads available right now.',
|
|---|
| 112 | 'downloads.btn': '⬇ Download',
|
|---|
| 113 | 'admin.title': 'Admin',
|
|---|
| 114 | 'admin.tagline_solo': 'Solo mode — your site.',
|
|---|
| 115 | 'admin.tagline_hub': 'Hub mode — a company site with users, each their own Klonkt Hub.',
|
|---|
| 116 | 'admin.b_sites': '🌐 Sites', 'admin.b_users': '👥 Users', 'admin.b_audio': '🎵 Audio',
|
|---|
| 117 | 'admin.b_playlists': '📃 Playlists', 'admin.b_comments': '💬 Comments', 'admin.b_seo': '🔎 SEO',
|
|---|
| 118 | 'admin.b_settings': '⚙️ Settings', 'admin.b_newpost': '✍️ New post', 'admin.b_look': '🎨 Appearance',
|
|---|
| 119 | 'admin.b_makesite': '🎨 Create your site', 'admin.b_circle': '🔗 Circle', 'admin.b_stats': '📊 Statistics',
|
|---|
| 120 | 'admin.b_newsletter': '✉️ Newsletter', 'admin.b_perskit': '📰 Press kit', 'admin.b_downloads': '⬇ Downloads',
|
|---|
| 121 | 'admin.b_linkbio': '🔗 Link-in-bio', 'admin.b_agenda': '📅 Agenda', 'admin.b_google': '🔑 Google',
|
|---|
| 122 | 'admin.b_updates': '🔄 Updates', 'admin.b_help': '📖 Manual',
|
|---|
| 123 | 'admin.st_users': 'Users', 'admin.st_sites': 'Sites', 'admin.st_posts': 'Posts', 'admin.st_published': 'Published',
|
|---|
| 124 | 'admin.sec_posts': 'Posts', 'admin.sec_sites': 'Sites', 'admin.sec_users': 'Users',
|
|---|
| 125 | 'admin.draft': 'Draft', 'admin.edit': 'Edit', 'admin.view': 'View',
|
|---|
| 126 | 'admin.th_slug': 'Slug', 'admin.th_title': 'Title', 'admin.th_owner': 'Owner', 'admin.th_created': 'Created',
|
|---|
| 127 | 'admin.th_username': 'Username', 'admin.th_email': 'Email', 'admin.th_role': 'Role', 'admin.th_joined': 'Joined',
|
|---|
| 128 | },
|
|---|
| 129 | de: {
|
|---|
| 130 | 'nav.back_to_site': '← Zurück zur Seite',
|
|---|
| 131 | 'nav.home': 'Start',
|
|---|
| 132 | 'nav.archive': 'Archiv',
|
|---|
| 133 | 'nav.search': 'Suche',
|
|---|
| 134 | 'nav.theme': 'Thema wechseln',
|
|---|
| 135 | 'nav.install': 'App installieren',
|
|---|
| 136 | 'nav.login': 'Anmelden',
|
|---|
| 137 | 'nav.logout': 'Abmelden',
|
|---|
| 138 | 'nav.admin': 'Verwaltung',
|
|---|
| 139 | 'nav.account': 'Konto',
|
|---|
| 140 | 'nav.profile': 'Profil',
|
|---|
| 141 | 'nav.favorites': 'Favoriten',
|
|---|
| 142 | 'nav.new_post': 'Neuer Beitrag',
|
|---|
| 143 | 'nav.language': 'Sprache',
|
|---|
| 144 | 'switch.agenda': 'Termine',
|
|---|
| 145 | 'switch.solo': 'Solo',
|
|---|
| 146 | 'switch.circle': 'Kreis',
|
|---|
| 147 | 'switch.timeline': 'Zeitleiste',
|
|---|
| 148 | 'switch.grid': 'Raster',
|
|---|
| 149 | 'postnav.newer': 'Neuer',
|
|---|
| 150 | 'postnav.older': 'Älter',
|
|---|
| 151 | 'postnav.newest': 'Neuester Beitrag',
|
|---|
| 152 | 'postnav.oldest': 'Ältester Beitrag',
|
|---|
| 153 | 'footer.subscribe_cta': 'Bleib auf dem Laufenden',
|
|---|
| 154 | 'footer.subscribe': 'Abonnieren',
|
|---|
| 155 | 'footer.install': 'App installieren',
|
|---|
| 156 | 'common.email_placeholder': 'du@email.de',
|
|---|
| 157 | 'common.back_to_admin': '← Verwaltung',
|
|---|
| 158 | 'agenda.title': 'Termine',
|
|---|
| 159 | 'agenda.empty': 'Zurzeit keine angekündigten Termine.',
|
|---|
| 160 | 'agenda.tickets': 'Tickets',
|
|---|
| 161 | 'agenda.notify_h': 'Keinen Termin verpassen',
|
|---|
| 162 | 'agenda.notify_sub': 'Hinterlasse deine E-Mail und wir melden uns bei neuen Terminen. Abmelden jederzeit möglich.',
|
|---|
| 163 | 'agenda.notify_btn': 'Halt mich auf dem Laufenden',
|
|---|
| 164 | 'agenda.msg_done': '✓ Du stehst auf der Liste — wir melden uns, sobald ein Termin angekündigt wird.',
|
|---|
| 165 | 'agenda.msg_check': '✉ Prüfe deine E-Mail, um die Anmeldung zu bestätigen.',
|
|---|
| 166 | 'downloads.title': 'Downloads',
|
|---|
| 167 | 'downloads.sub': 'Kostenlos herunterladen — hinterlasse deine E-Mail und du bekommst die Datei.',
|
|---|
| 168 | 'downloads.empty': 'Momentan keine Downloads verfügbar.',
|
|---|
| 169 | 'downloads.btn': '⬇ Herunterladen',
|
|---|
| 170 | 'admin.title': 'Verwaltung',
|
|---|
| 171 | 'admin.tagline_solo': 'Solo-Modus — deine Seite.',
|
|---|
| 172 | 'admin.tagline_hub': 'Hub-Modus — eine Firmenseite mit Nutzern, je ein eigener Klonkt Hub.',
|
|---|
| 173 | 'admin.b_sites': '🌐 Seiten', 'admin.b_users': '👥 Nutzer', 'admin.b_audio': '🎵 Audio',
|
|---|
| 174 | 'admin.b_playlists': '📃 Playlists', 'admin.b_comments': '💬 Kommentare', 'admin.b_seo': '🔎 SEO',
|
|---|
| 175 | 'admin.b_settings': '⚙️ Einstellungen', 'admin.b_newpost': '✍️ Neuer Beitrag', 'admin.b_look': '🎨 Aussehen',
|
|---|
| 176 | 'admin.b_makesite': '🎨 Seite erstellen', 'admin.b_circle': '🔗 Kreis', 'admin.b_stats': '📊 Statistik',
|
|---|
| 177 | 'admin.b_newsletter': '✉️ Newsletter', 'admin.b_perskit': '📰 Pressekit', 'admin.b_downloads': '⬇ Downloads',
|
|---|
| 178 | 'admin.b_linkbio': '🔗 Link-in-Bio', 'admin.b_agenda': '📅 Termine', 'admin.b_google': '🔑 Google',
|
|---|
| 179 | 'admin.b_updates': '🔄 Updates', 'admin.b_help': '📖 Anleitung',
|
|---|
| 180 | 'admin.st_users': 'Nutzer', 'admin.st_sites': 'Seiten', 'admin.st_posts': 'Beiträge', 'admin.st_published': 'Veröffentlicht',
|
|---|
| 181 | 'admin.sec_posts': 'Beiträge', 'admin.sec_sites': 'Seiten', 'admin.sec_users': 'Nutzer',
|
|---|
| 182 | 'admin.draft': 'Entwurf', 'admin.edit': 'Bearbeiten', 'admin.view': 'Ansehen',
|
|---|
| 183 | 'admin.th_slug': 'Slug', 'admin.th_title': 'Titel', 'admin.th_owner': 'Eigentümer', 'admin.th_created': 'Erstellt',
|
|---|
| 184 | 'admin.th_username': 'Benutzername', 'admin.th_email': 'E-Mail', 'admin.th_role': 'Rolle', 'admin.th_joined': 'Mitglied seit',
|
|---|
| 185 | },
|
|---|
| 186 | };
|
|---|
| 187 |
|
|---|
| 188 | export function t(lang, key, vars) {
|
|---|
| 189 | const l = SUPPORTED.includes(lang) ? lang : 'nl';
|
|---|
| 190 | let s = (DICT[l] && DICT[l][key]);
|
|---|
| 191 | if (s === undefined) s = (DICT.nl[key] !== undefined ? DICT.nl[key] : key);
|
|---|
| 192 | if (vars) for (const k in vars) s = s.replace(new RegExp('\\{' + k + '\\}', 'g'), vars[k]);
|
|---|
| 193 | return s;
|
|---|
| 194 | }
|
|---|
| 195 |
|
|---|
| 196 | // Bepaal de taal voor dit request: expliciete sessie-keuze → instance-standaard
|
|---|
| 197 | // (env KLONKT_DEFAULT_LANG, bv. 'de' voor een Duitse site) → browser-taal → nl.
|
|---|
| 198 | export function resolveLang(req) {
|
|---|
| 199 | const s = req && req.session && req.session.lang;
|
|---|
| 200 | if (s && SUPPORTED.includes(s)) return s; // bezoeker koos zelf
|
|---|
| 201 | const envDefault = (process.env.KLONKT_DEFAULT_LANG || '').toLowerCase();
|
|---|
| 202 | if (SUPPORTED.includes(envDefault)) return envDefault; // per-instance standaard
|
|---|
| 203 | const al = ((req && req.headers && req.headers['accept-language']) || '').toLowerCase();
|
|---|
| 204 | const first = al.split(',')[0].trim().slice(0, 2);
|
|---|
| 205 | if (SUPPORTED.includes(first)) return first; // browser-voorkeur
|
|---|
| 206 | return 'nl';
|
|---|
| 207 | }
|
|---|