source: Klonkt/src/services/i18n.js@ bffb348

main
Last change on this file since bffb348 was bffb348, checked in by roboburr <roboburr@…>, 3 months ago

feat(i18n): per-instance default language via KLONKT_DEFAULT_LANG

resolveLang: session choice → env KLONKT_DEFAULT_LANG (e.g. 'de') → browser → nl.
Makes an instance default to German/English/… without affecting other sites.

Co-Authored-By: Claude <noreply@…>

  • Property mode set to 100644
File size: 11.0 KB
Line 
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
8export const SUPPORTED = ['nl', 'en', 'de'];
9export const LANG_NAMES = { nl: 'Nederlands', en: 'English', de: 'Deutsch' };
10
11const 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
188export 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.
198export 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}
Note: See TracBrowser for help on using the repository browser.