source: Klonkt/src/services/i18n.js@ 929d98d

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

feat(i18n phase 4): post comments/like/related + search page (NL/EN/DE)

post.ejs (comment heading with plural, empty state, reply/delete/cancel,
comment form, pending notice, related posts), search.ejs (title, placeholder,
button, results with plural, sections, empty), like button (login title).

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

  • Property mode set to 100644
File size: 18.3 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 // Welkom / first-run
72 'welcome.title': 'Welkom bij Klonkt',
73 'welcome.tagline': 'Een zelf-gehost publicatieplatform.',
74 'welcome.have_account': 'Al een account? Inloggen',
75 'welcome.note': 'Maak je beheerdersaccount aan om te beginnen — daarna is registratie gesloten.',
76 'welcome.nosite': 'Hoi {user}! Er is nog geen site ingesteld.',
77 // Auth (login/register)
78 'auth.admin_login_title': 'Beheerder inloggen',
79 'auth.username_or_email': 'Gebruikersnaam of e-mail',
80 'auth.password': 'Wachtwoord',
81 'auth.forgot': 'Wachtwoord vergeten?',
82 'auth.google_btn': 'Inloggen met Google',
83 'auth.public_sub': 'Log in om te reageren en je favorieten te bewaren.',
84 'auth.admin_box_q': 'Beheerder?',
85 'auth.admin_box_sub': 'Log in met gebruikersnaam & wachtwoord',
86 'auth.google_unavail': 'Inloggen met Google is op deze site nog niet ingesteld.',
87 'auth.create_admin': 'Beheerder aanmaken',
88 'auth.reg_intro': 'Eerste keer opzetten — maak je beheerdersaccount aan. Dit kan maar één keer.',
89 'auth.f_username': 'Gebruikersnaam (3-32 tekens, letters/cijfers/_-)',
90 'auth.f_email': 'E-mail',
91 'auth.f_password': 'Wachtwoord (min 8 tekens)',
92 // Bottom-tab (mobiel)
93 'tab.home': 'Home', 'tab.search': 'Zoek', 'tab.write': 'Schrijven', 'tab.profile': 'Profiel',
94 // Reacties + gerelateerd (post-pagina)
95 'comments.heading_one': '{n} reactie', 'comments.heading_other': '{n} reacties',
96 'comments.empty': 'Nog geen reacties.',
97 'comments.to_start': 'om de conversatie te starten.',
98 'comments.reply': 'Reageer', 'comments.delete': 'Verwijder', 'comments.cancel': 'Annuleren',
99 'comments.delete_confirm': 'Deze reactie verwijderen?',
100 'comments.reply_to': 'Antwoord aan {name}…',
101 'comments.add_as': 'Reageer als', 'comments.placeholder': 'Deel je gedachten…',
102 'comments.post': 'Plaats reactie', 'comments.login_to_comment': 'Log in om te reageren',
103 'comments.pending': 'Je reactie wacht op goedkeuring. Hij verschijnt zodra een beheerder hem goedkeurt.',
104 'related.title': 'Gerelateerde posts',
105 // Zoeken + like
106 'search.placeholder': 'Zoek posts en nummers…', 'search.button': 'Zoek',
107 'search.error': 'Kon die zoekopdracht niet uitvoeren. Probeer een eenvoudiger term.',
108 'search.results_one': '{n} resultaat voor “{q}”', 'search.results_other': '{n} resultaten voor “{q}”',
109 'search.section_tracks': 'Nummers', 'search.section_posts': 'Posts',
110 'search.empty': 'Niets gevonden.', 'search.in_post': 'in post →',
111 'like.login_title': 'Log in om deze post te liken',
112 },
113 en: {
114 'nav.back_to_site': '← Back to site',
115 'nav.home': 'Home',
116 'nav.archive': 'Archive',
117 'nav.search': 'Search',
118 'nav.theme': 'Toggle theme',
119 'nav.install': 'Install app',
120 'nav.login': 'Log in',
121 'nav.logout': 'Log out',
122 'nav.admin': 'Admin',
123 'nav.account': 'Account',
124 'nav.profile': 'Profile',
125 'nav.favorites': 'Favorites',
126 'nav.new_post': 'New post',
127 'nav.language': 'Language',
128 'switch.agenda': 'Agenda',
129 'switch.solo': 'Solo',
130 'switch.circle': 'Circle',
131 'switch.timeline': 'Timeline',
132 'switch.grid': 'Grid',
133 'postnav.newer': 'Newer',
134 'postnav.older': 'Older',
135 'postnav.newest': 'Newest post',
136 'postnav.oldest': 'Oldest post',
137 'footer.subscribe_cta': 'Stay in the loop',
138 'footer.subscribe': 'Subscribe',
139 'footer.install': 'Install app',
140 'common.email_placeholder': 'you@email.com',
141 'common.back_to_admin': '← Admin',
142 'agenda.title': 'Agenda',
143 'agenda.empty': 'No announced events at the moment.',
144 'agenda.tickets': 'Tickets',
145 'agenda.notify_h': 'Never miss an event',
146 'agenda.notify_sub': 'Leave your email and we’ll let you know about new events. Unsubscribe any time.',
147 'agenda.notify_btn': 'Keep me posted',
148 'agenda.msg_done': '✓ You’re on the list — we’ll let you know when an event is announced.',
149 'agenda.msg_check': '✉ Check your email to confirm your sign-up.',
150 'downloads.title': 'Downloads',
151 'downloads.sub': 'Free to download — leave your email and you’ll get the file.',
152 'downloads.empty': 'No downloads available right now.',
153 'downloads.btn': '⬇ Download',
154 'admin.title': 'Admin',
155 'admin.tagline_solo': 'Solo mode — your site.',
156 'admin.tagline_hub': 'Hub mode — a company site with users, each their own Klonkt Hub.',
157 'admin.b_sites': '🌐 Sites', 'admin.b_users': '👥 Users', 'admin.b_audio': '🎵 Audio',
158 'admin.b_playlists': '📃 Playlists', 'admin.b_comments': '💬 Comments', 'admin.b_seo': '🔎 SEO',
159 'admin.b_settings': '⚙️ Settings', 'admin.b_newpost': '✍️ New post', 'admin.b_look': '🎨 Appearance',
160 'admin.b_makesite': '🎨 Create your site', 'admin.b_circle': '🔗 Circle', 'admin.b_stats': '📊 Statistics',
161 'admin.b_newsletter': '✉️ Newsletter', 'admin.b_perskit': '📰 Press kit', 'admin.b_downloads': '⬇ Downloads',
162 'admin.b_linkbio': '🔗 Link-in-bio', 'admin.b_agenda': '📅 Agenda', 'admin.b_google': '🔑 Google',
163 'admin.b_updates': '🔄 Updates', 'admin.b_help': '📖 Manual',
164 'admin.st_users': 'Users', 'admin.st_sites': 'Sites', 'admin.st_posts': 'Posts', 'admin.st_published': 'Published',
165 'admin.sec_posts': 'Posts', 'admin.sec_sites': 'Sites', 'admin.sec_users': 'Users',
166 'admin.draft': 'Draft', 'admin.edit': 'Edit', 'admin.view': 'View',
167 'admin.th_slug': 'Slug', 'admin.th_title': 'Title', 'admin.th_owner': 'Owner', 'admin.th_created': 'Created',
168 'admin.th_username': 'Username', 'admin.th_email': 'Email', 'admin.th_role': 'Role', 'admin.th_joined': 'Joined',
169 'welcome.title': 'Welcome to Klonkt',
170 'welcome.tagline': 'A self-hosted publishing platform.',
171 'welcome.have_account': 'Already have an account? Log in',
172 'welcome.note': 'Create your admin account to get started — registration closes after that.',
173 'welcome.nosite': 'Hi {user}! No site is configured yet.',
174 'auth.admin_login_title': 'Admin login',
175 'auth.username_or_email': 'Username or email',
176 'auth.password': 'Password',
177 'auth.forgot': 'Forgot password?',
178 'auth.google_btn': 'Sign in with Google',
179 'auth.public_sub': 'Log in to comment and save your favorites.',
180 'auth.admin_box_q': 'Admin?',
181 'auth.admin_box_sub': 'Log in with username & password',
182 'auth.google_unavail': 'Google sign-in isn’t set up on this site yet.',
183 'auth.create_admin': 'Create admin',
184 'auth.reg_intro': 'First-time setup — create your admin account. This can only be done once.',
185 'auth.f_username': 'Username (3-32 chars, letters/digits/_-)',
186 'auth.f_email': 'Email',
187 'auth.f_password': 'Password (min 8 chars)',
188 'tab.home': 'Home', 'tab.search': 'Search', 'tab.write': 'Write', 'tab.profile': 'Profile',
189 'comments.heading_one': '{n} comment', 'comments.heading_other': '{n} comments',
190 'comments.empty': 'No comments yet.',
191 'comments.to_start': 'to start the conversation.',
192 'comments.reply': 'Reply', 'comments.delete': 'Delete', 'comments.cancel': 'Cancel',
193 'comments.delete_confirm': 'Delete this comment?',
194 'comments.reply_to': 'Reply to {name}…',
195 'comments.add_as': 'Add a comment as', 'comments.placeholder': 'Share your thoughts…',
196 'comments.post': 'Post comment', 'comments.login_to_comment': 'Log in to comment',
197 'comments.pending': 'Your comment is awaiting moderation. It will appear once an admin approves it.',
198 'related.title': 'Related posts',
199 'search.placeholder': 'Search posts and tracks…', 'search.button': 'Search',
200 'search.error': 'Couldn’t run that search. Try a simpler term.',
201 'search.results_one': '{n} result for “{q}”', 'search.results_other': '{n} results for “{q}”',
202 'search.section_tracks': 'Tracks', 'search.section_posts': 'Posts',
203 'search.empty': 'Nothing found.', 'search.in_post': 'in post →',
204 'like.login_title': 'Log in to like this post',
205 },
206 de: {
207 'nav.back_to_site': '← Zurück zur Seite',
208 'nav.home': 'Start',
209 'nav.archive': 'Archiv',
210 'nav.search': 'Suche',
211 'nav.theme': 'Thema wechseln',
212 'nav.install': 'App installieren',
213 'nav.login': 'Anmelden',
214 'nav.logout': 'Abmelden',
215 'nav.admin': 'Verwaltung',
216 'nav.account': 'Konto',
217 'nav.profile': 'Profil',
218 'nav.favorites': 'Favoriten',
219 'nav.new_post': 'Neuer Beitrag',
220 'nav.language': 'Sprache',
221 'switch.agenda': 'Termine',
222 'switch.solo': 'Solo',
223 'switch.circle': 'Kreis',
224 'switch.timeline': 'Zeitleiste',
225 'switch.grid': 'Raster',
226 'postnav.newer': 'Neuer',
227 'postnav.older': 'Älter',
228 'postnav.newest': 'Neuester Beitrag',
229 'postnav.oldest': 'Ältester Beitrag',
230 'footer.subscribe_cta': 'Bleib auf dem Laufenden',
231 'footer.subscribe': 'Abonnieren',
232 'footer.install': 'App installieren',
233 'common.email_placeholder': 'du@email.de',
234 'common.back_to_admin': '← Verwaltung',
235 'agenda.title': 'Termine',
236 'agenda.empty': 'Zurzeit keine angekündigten Termine.',
237 'agenda.tickets': 'Tickets',
238 'agenda.notify_h': 'Keinen Termin verpassen',
239 'agenda.notify_sub': 'Hinterlasse deine E-Mail und wir melden uns bei neuen Terminen. Abmelden jederzeit möglich.',
240 'agenda.notify_btn': 'Halt mich auf dem Laufenden',
241 'agenda.msg_done': '✓ Du stehst auf der Liste — wir melden uns, sobald ein Termin angekündigt wird.',
242 'agenda.msg_check': '✉ Prüfe deine E-Mail, um die Anmeldung zu bestätigen.',
243 'downloads.title': 'Downloads',
244 'downloads.sub': 'Kostenlos herunterladen — hinterlasse deine E-Mail und du bekommst die Datei.',
245 'downloads.empty': 'Momentan keine Downloads verfügbar.',
246 'downloads.btn': '⬇ Herunterladen',
247 'admin.title': 'Verwaltung',
248 'admin.tagline_solo': 'Solo-Modus — deine Seite.',
249 'admin.tagline_hub': 'Hub-Modus — eine Firmenseite mit Nutzern, je ein eigener Klonkt Hub.',
250 'admin.b_sites': '🌐 Seiten', 'admin.b_users': '👥 Nutzer', 'admin.b_audio': '🎵 Audio',
251 'admin.b_playlists': '📃 Playlists', 'admin.b_comments': '💬 Kommentare', 'admin.b_seo': '🔎 SEO',
252 'admin.b_settings': '⚙️ Einstellungen', 'admin.b_newpost': '✍️ Neuer Beitrag', 'admin.b_look': '🎨 Aussehen',
253 'admin.b_makesite': '🎨 Seite erstellen', 'admin.b_circle': '🔗 Kreis', 'admin.b_stats': '📊 Statistik',
254 'admin.b_newsletter': '✉️ Newsletter', 'admin.b_perskit': '📰 Pressekit', 'admin.b_downloads': '⬇ Downloads',
255 'admin.b_linkbio': '🔗 Link-in-Bio', 'admin.b_agenda': '📅 Termine', 'admin.b_google': '🔑 Google',
256 'admin.b_updates': '🔄 Updates', 'admin.b_help': '📖 Anleitung',
257 'admin.st_users': 'Nutzer', 'admin.st_sites': 'Seiten', 'admin.st_posts': 'Beiträge', 'admin.st_published': 'Veröffentlicht',
258 'admin.sec_posts': 'Beiträge', 'admin.sec_sites': 'Seiten', 'admin.sec_users': 'Nutzer',
259 'admin.draft': 'Entwurf', 'admin.edit': 'Bearbeiten', 'admin.view': 'Ansehen',
260 'admin.th_slug': 'Slug', 'admin.th_title': 'Titel', 'admin.th_owner': 'Eigentümer', 'admin.th_created': 'Erstellt',
261 'admin.th_username': 'Benutzername', 'admin.th_email': 'E-Mail', 'admin.th_role': 'Rolle', 'admin.th_joined': 'Mitglied seit',
262 'welcome.title': 'Willkommen bei Klonkt',
263 'welcome.tagline': 'Eine selbstgehostete Publishing-Plattform.',
264 'welcome.have_account': 'Schon ein Konto? Anmelden',
265 'welcome.note': 'Lege dein Administrator-Konto an, um zu starten — danach ist die Registrierung geschlossen.',
266 'welcome.nosite': 'Hallo {user}! Es ist noch keine Seite eingerichtet.',
267 'auth.admin_login_title': 'Administrator-Anmeldung',
268 'auth.username_or_email': 'Benutzername oder E-Mail',
269 'auth.password': 'Passwort',
270 'auth.forgot': 'Passwort vergessen?',
271 'auth.google_btn': 'Mit Google anmelden',
272 'auth.public_sub': 'Melde dich an, um zu kommentieren und Favoriten zu speichern.',
273 'auth.admin_box_q': 'Administrator?',
274 'auth.admin_box_sub': 'Mit Benutzername & Passwort anmelden',
275 'auth.google_unavail': 'Google-Anmeldung ist auf dieser Seite noch nicht eingerichtet.',
276 'auth.create_admin': 'Administrator anlegen',
277 'auth.reg_intro': 'Ersteinrichtung — lege dein Administrator-Konto an. Das geht nur einmal.',
278 'auth.f_username': 'Benutzername (3-32 Zeichen, Buchstaben/Ziffern/_-)',
279 'auth.f_email': 'E-Mail',
280 'auth.f_password': 'Passwort (min. 8 Zeichen)',
281 'tab.home': 'Start', 'tab.search': 'Suche', 'tab.write': 'Schreiben', 'tab.profile': 'Profil',
282 'comments.heading_one': '{n} Kommentar', 'comments.heading_other': '{n} Kommentare',
283 'comments.empty': 'Noch keine Kommentare.',
284 'comments.to_start': 'um das Gespräch zu starten.',
285 'comments.reply': 'Antworten', 'comments.delete': 'Löschen', 'comments.cancel': 'Abbrechen',
286 'comments.delete_confirm': 'Diesen Kommentar löschen?',
287 'comments.reply_to': 'Antwort an {name}…',
288 'comments.add_as': 'Kommentieren als', 'comments.placeholder': 'Teile deine Gedanken…',
289 'comments.post': 'Kommentar posten', 'comments.login_to_comment': 'Zum Kommentieren anmelden',
290 'comments.pending': 'Dein Kommentar wartet auf Freigabe. Er erscheint, sobald ein Administrator ihn genehmigt.',
291 'related.title': 'Ähnliche Beiträge',
292 'search.placeholder': 'Beiträge und Titel suchen…', 'search.button': 'Suchen',
293 'search.error': 'Diese Suche konnte nicht ausgeführt werden. Versuch einen einfacheren Begriff.',
294 'search.results_one': '{n} Ergebnis für „{q}“', 'search.results_other': '{n} Ergebnisse für „{q}“',
295 'search.section_tracks': 'Titel', 'search.section_posts': 'Beiträge',
296 'search.empty': 'Nichts gefunden.', 'search.in_post': 'im Beitrag →',
297 'like.login_title': 'Zum Liken anmelden',
298 },
299};
300
301export function t(lang, key, vars) {
302 const l = SUPPORTED.includes(lang) ? lang : 'nl';
303 let s = (DICT[l] && DICT[l][key]);
304 if (s === undefined) s = (DICT.nl[key] !== undefined ? DICT.nl[key] : key);
305 if (vars) for (const k in vars) s = s.replace(new RegExp('\\{' + k + '\\}', 'g'), vars[k]);
306 return s;
307}
308
309// Bepaal de taal voor dit request: expliciete sessie-keuze → instance-standaard
310// (env KLONKT_DEFAULT_LANG, bv. 'de' voor een Duitse site) → browser-taal → nl.
311export function resolveLang(req) {
312 const s = req && req.session && req.session.lang;
313 if (s && SUPPORTED.includes(s)) return s; // bezoeker koos zelf
314 const envDefault = (process.env.KLONKT_DEFAULT_LANG || '').toLowerCase();
315 if (SUPPORTED.includes(envDefault)) return envDefault; // per-instance standaard
316 const al = ((req && req.headers && req.headers['accept-language']) || '').toLowerCase();
317 const first = al.split(',')[0].trim().slice(0, 2);
318 if (SUPPORTED.includes(first)) return first; // browser-voorkeur
319 return 'nl';
320}
Note: See TracBrowser for help on using the repository browser.